diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index b07937b..67529c2 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -16,7 +16,8 @@ jobs: matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] c_compiler: [ gcc, clang-17, cl, icx ] - build_type: [ RelWithDebInfo ] + build_type: [ Debug ] + other_options: [ "", "-DOPENMP=1" ] exclude: - os: ubuntu-latest c_compiler: cl @@ -82,12 +83,12 @@ jobs: sudo apt-get install -y intel-oneapi-dpcpp-cpp-compiler echo "/opt/intel/oneapi/compiler/latest/bin" >> $GITHUB_PATH - - name: Install Raylib dependencies (Ubuntu) + - name: Install APT dependencies (Ubuntu) id: install-deps-u if: matrix.os == 'ubuntu-latest' run: > sudo apt install libasound2-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev libglu1-mesa-dev - libxcursor-dev libxinerama-dev + libxcursor-dev libxinerama-dev libomp-15-dev - name: Install Clang 17 (macOS) if: matrix.os == 'macos-latest' @@ -96,40 +97,16 @@ jobs: echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH ls /usr/local/opt/llvm/bin - - name: Print PATH (macOS) - if: matrix.os == 'macos-latest' - run: | - echo "PATH = $PATH" - - name: Configure CMake - if: matrix.cxx_flags == '' run: > cmake -B ${{ steps.strings.outputs.build-output-dir }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -S ${{ github.workspace }} - - - name: Configure CMake (Coverage) - if: matrix.cxx_flags != '' - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DCMAKE_CXX_FLAGS="${{ matrix.cxx_flags }}" - -DCMAKE_C_FLAGS="${{ matrix.c_flags }}" + ${{ matrix.other_options }} -S ${{ github.workspace }} - name: Build # Build your program with the given configuration. Note that --config is needed because the default Windows # generator is a multi-config generator (Visual Studio generator). run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} - - - name: Test - if: matrix.cxx_flags == '' - working-directory: ${{ steps.strings.outputs.build-output-dir }} - # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default - # Windows generator is a multi-config generator (Visual Studio generator). - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --build-config ${{ matrix.build_type }} diff --git a/.github/workflows/cover.yml b/.github/workflows/cover.yml index bba9013..0b33663 100644 --- a/.github/workflows/cover.yml +++ b/.github/workflows/cover.yml @@ -19,7 +19,7 @@ jobs: shell: bash run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - - name: Instal build dependencies + - name: Install build dependencies run: | # LLVM 17 wget https://apt.llvm.org/llvm.sh diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index f76fc79..72d1096 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -34,7 +34,7 @@ jobs: shell: bash run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - - name: Instal build dependencies + - name: Install build dependencies run: | # LLVM 17 wget https://apt.llvm.org/llvm.sh diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index 5943b49..8296883 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -9,8 +9,22 @@ add_executable(grass main.cpp if (MSVC) target_compile_options(grass PRIVATE /W4) + if (OPENMP) + target_compile_options(grass PRIVATE /openmp:llvm) + endif () else () target_compile_options(grass PRIVATE -Wall -Wextra -Wpedantic) + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT APPLE) + if (OPENMP) + target_compile_options(grass PRIVATE -fopenmp=libiomp5) + target_link_options(grass PRIVATE -fopenmp=libiomp5) + endif () + else () + if (OPENMP) + target_compile_options(grass PRIVATE -fopenmp) + target_link_options(grass PRIVATE -fopenmp) + endif () + endif () endif () target_link_libraries(grass raylib) diff --git a/demo/README.md b/demo/README.md index c94ea93..171c524 100644 --- a/demo/README.md +++ b/demo/README.md @@ -67,3 +67,24 @@ When publishing the demo online, use a host that uses HTTPS connections. (Screenshot: Add localhost:8080 as an exception) ![Step 2. Add localhost:8080 as an exception](disarm1.png) + +## Building with OpenMP Parallelism + +On Windows/MSVC, you can build the application with OpenMP-based parallelism applied. + +When configuring the project, simply set the `OPENMP` flag: + +``` +cmake -B build <... stuff ...> -DOPENMP=1 +``` + +Build normally. + +When running the application, supply the `OMP_WAIT_POLICY` environment variable to `PASSIVE` +to avoid the spinlocks. + +```powershell +$Env:OMP_WAIT_POLICY = "PASSIVE" + + +``` diff --git a/demo/Table.h b/demo/Table.h index 48984c9..8f58c65 100644 --- a/demo/Table.h +++ b/demo/Table.h @@ -3,12 +3,15 @@ #include #include +#include #include +#include #include #include #include #include #include +#include #include #include @@ -137,10 +140,11 @@ class Table : public std::vector { auto norm = std::norm(group.xy - circle), rsq = square(group.radius); // If a non-singular group either: // - contains the center of `circle` inside said group's circle, or + // - if circles are overlapping, resolve more detail, or // - the (underapproximated) view angle is too wide, then // resolve more detail. - if (group.many && - (norm < rsq || square(tan_angle_threshold) < rsq / norm)) + if (group.many && (norm < rsq || norm < square(circle.radius) || + square(tan_angle_threshold) < rsq / norm)) return !TRUNCATE; // Compute the acceleration due to the group. // Also, insert the value of G, the universal gravitational constant, in a @@ -176,19 +180,24 @@ class Table : public std::vector { else return {}; }; + // Compute the Barnes-Hut tree over the particles this has. - auto const tree = - bh::tree>(begin(), end(), morton_masked); + using E = Physicals; + auto const tree = bh::tree(begin(), end(), morton_masked); // Iterate over the particles, summing up their forces. - for (auto i = begin(); i != end(); ++i) { - auto &&p = *i; - // Supposing that particle p is located instead at the position xy below, - // what is the acceleration experienced by p due to all the other - // particles or approximations (g)? + auto const b = begin(); + auto const m = static_cast(size()); + auto n = 0; +#pragma omp parallel for + for (n = 0; n < m; ++n) { + auto &&p = (*this)[n]; + // Supposing that particle p is located at the position xy below, instead + // of p's own xy, what is the acceleration experienced by p due to all the + // other particles or approximations (g)? auto ig = Integrator{p.xy, p.v}; - ig.step(dt, [this, &tree, &p, &i](auto xy) { - return this->accelerate(tree, {xy, p.radius}, i); + ig.step(dt, [this, &tree, &p, b, n](auto xy) { + return this->accelerate(tree, {xy, p.radius}, b + n); }); p.xy = ig.y0, p.v = ig.y1; } diff --git a/demo/env.h b/demo/env.h index cc3cc32..18b92c5 100644 --- a/demo/env.h +++ b/demo/env.h @@ -9,12 +9,12 @@ namespace env { std::optional get(char const *sv) { #ifdef _WIN32 - char *s; - size_t n; + char *s{}; + size_t n{}; // On Windows, _dupenv_s is the Microsoft-recommended way of reading an // environment variable. When the variable isn't found, the error (e) is 0 and // the count (n) is also 0 (also, the buffer [s] is set to NULL). - if (auto e = _dupenv_s(&s, &n, sv); e || !n) + if (auto e = _dupenv_s(&s, &n, sv); e || !s) return {}; std::string t{s, n}; // According to Microsoft, the buffer s must be freed using a call to `free`. diff --git a/demo/main.cpp b/demo/main.cpp index 13b96d5..31d835a 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -15,9 +14,13 @@ #include "Table.h" #include "env.h" #include "user.h" +#include +#include using namespace phy; +namespace main_program { + /// Constants controlling the program. struct Constants { /// Inclusive upper limit of the number of particles. @@ -53,7 +56,7 @@ struct Constants { }; template static constexpr Table figure8() { - Table table; + Table table; // Make the mystical figure-8 shape below work at first. // (This G value is too large in most cases, so I will lower it once the @@ -211,7 +214,7 @@ struct State { EndDrawing(); } - User make_user() { + User make_user() const { User u; if (constants.flags.galaxies) u.control.demo = false; @@ -222,10 +225,13 @@ struct State { return constants.flags.galaxies ? galaxies(constants) : figure8(); } } state; +} // namespace main_program -void do_loop() { state.loop(); } +void do_loop() { main_program::state.loop(); } static int do_main() { + using namespace main_program; + SetConfigFlags(FLAG_WINDOW_RESIZABLE); InitWindow(600, 600, "Grass Gravity Simulation");