diff --git a/tests/SchedulerBenchmark/main.cpp b/tests/SchedulerBenchmark/main.cpp new file mode 100644 index 000000000..80bf50a14 --- /dev/null +++ b/tests/SchedulerBenchmark/main.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include "task.hpp" +#include +#include +#include + +int main() +{ + Nz::Modules core; + + constexpr unsigned int imageDimensions = 4096; // Will produce imageDimensions² rays + constexpr unsigned int tileSize = 128; + + std::minstd_rand randEngine(std::random_device{}()); + std::uniform_real_distribution posDis(-1.f, 1.f); + std::uniform_real_distribution velDis(-10.f, 10.f); + + Nz::TaskScheduler taskScheduler; + + std::cout << "Initializing..." << std::endl; + + // from https://raytracing.github.io/books/RayTracingInOneWeekend.html + double focalLength = 1.0; + double viewportHeight = 2.0; + double viewportWidth = viewportHeight * (static_cast(imageDimensions) / imageDimensions); + Nz::Vector3d cameraCenter = Nz::Vector3d::Zero(); + + Nz::Vector3d viewportU = Nz::Vector3d(viewportWidth, 0, 0); + Nz::Vector3d viewportV = Nz::Vector3d(0, -viewportHeight, 0); + Nz::Vector3d viewportUpperLeft = cameraCenter - Nz::Vector3d(0, 0, focalLength) - viewportU / 2 - viewportV / 2; + + SceneData sceneData; + sceneData.imageSize = imageDimensions; + sceneData.pixels = std::make_unique(imageDimensions * imageDimensions * 3); + sceneData.pixelDeltaU = viewportU / imageDimensions; + sceneData.pixelDeltaV = viewportV / imageDimensions; + sceneData.originPos = viewportUpperLeft + 0.5 * (sceneData.pixelDeltaU + sceneData.pixelDeltaV); + sceneData.cameraPos = cameraCenter; + sceneData.spheres.push_back(Nz::Sphered(Nz::Vector3d(-1.5, 0.5, -10.0), 0.5)); + sceneData.spheres.push_back(Nz::Sphered(Nz::Vector3d(0.0, 0.75, -2.5), 0.75)); + sceneData.spheres.push_back(Nz::Sphered(Nz::Vector3d(1.5, 0.5, -10.0), 0.5)); + + std::cout << "Warming up..." << std::endl; + RayCast(sceneData, Nz::Vector2ui(0, 0), Nz::Vector2ui(imageDimensions, imageDimensions)); + + std::cout << "Measuring mono-threaded..." << std::endl; + + Nz::Time t1 = Nz::GetElapsedNanoseconds(); + RayCast(sceneData, Nz::Vector2ui(0, 0), Nz::Vector2ui(imageDimensions, imageDimensions)); + Nz::Time t2 = Nz::GetElapsedNanoseconds(); + + std::cout << "Mono-threaded update time: " << (t2 - t1) << std::endl; + + std::cout << "Measuring task-scheduler..." << std::endl; + + unsigned int boxCount = imageDimensions / tileSize; + if (imageDimensions % tileSize != 0) + boxCount++; + + std::vector> boxes(boxCount * boxCount); + for (unsigned int i = 0; i < boxes.size(); ++i) + { + unsigned int x = i % boxCount; + unsigned int y = i % boxCount; + + Nz::Vector2ui mins(x * tileSize, y * tileSize); + Nz::Vector2ui maxs = mins + Nz::Vector2ui(tileSize); + maxs.Minimize(Nz::Vector2ui(imageDimensions)); + + Nz::Vector2ui dims = maxs - mins; + boxes[i] = std::make_pair(mins, dims); + } + + Nz::Time t3 = Nz::GetElapsedNanoseconds(); + std::atomic_uint64_t acc = 0; + std::atomic_uint boxCountAcc = 0; + std::atomic_uint threadCounter = 0; + + for (auto&& [offset, dims] : boxes) + { + taskScheduler.AddTask([&, offset, dims] + { + thread_local std::size_t threadId = ++threadCounter; + + Nz::Time taskStart = Nz::GetElapsedNanoseconds(); + RayCast(sceneData, offset, dims); + Nz::Time taskEnd = Nz::GetElapsedNanoseconds(); + + acc += (taskEnd - taskStart).AsNanoseconds(); + boxCountAcc++; + }); + } + taskScheduler.WaitForTasks(); + + Nz::Time t4 = Nz::GetElapsedNanoseconds(); + + Nz::Time taskSchedulerTime = t4 - t3; + std::cout << "task-scheduler update time: " << taskSchedulerTime << std::endl; + std::cout << "accumulated task time: " << Nz::Time::Nanoseconds(acc) << std::endl; + std::cout << "difference: " << taskSchedulerTime - Nz::Time::Nanoseconds(acc) << std::endl; + std::cout << "thread count: " << threadCounter << std::endl; + std::cout << "box count: " << boxCountAcc << std::endl; + + static_assert(sizeof(PixelColor) == 3 * sizeof(Nz::UInt8)); + + Nz::Image image(Nz::ImageType::E2D, Nz::PixelFormat::RGB8, imageDimensions, imageDimensions); + image.Update(sceneData.pixels.get()); + image.SaveToFile(Nz::Utf8Path("raycast_test.png")); +} diff --git a/tests/SchedulerBenchmark/task.cpp b/tests/SchedulerBenchmark/task.cpp new file mode 100644 index 000000000..0bcca569f --- /dev/null +++ b/tests/SchedulerBenchmark/task.cpp @@ -0,0 +1,62 @@ +#include "task.hpp" +#include +#include + + +bool HitSphere(const Nz::Vector3d& center, double radius, const Nz::Rayd& r) +{ + Nz::Vector3d oc = r.origin - center; + double a = Nz::Vector3d::DotProduct(r.direction, r.direction); + double b = 2.0 * Nz::Vector3d::DotProduct(oc, r.direction); + double c = Nz::Vector3d::DotProduct(oc, oc) - radius * radius; + double discriminant = b * b - 4 * a * c; + return (discriminant >= 0); +} + +Nz::Color RayColor(const SceneData& sceneData, const Nz::Rayd& r) +{ + double closestHit = std::numeric_limits::infinity(); + const Nz::Sphered* closestSphere = nullptr; + for (const Nz::Sphered& sphere : sceneData.spheres) + { + double t; + if (r.Intersect(sphere, &t, nullptr) && t < closestHit) + { + closestHit = t; + closestSphere = &sphere; + } + } + + if (closestSphere) + { + Nz::Vector3d hitPos = r.GetPoint(closestHit); + Nz::Vector3d hitNormal = Nz::Vector3d::Normalize(hitPos - closestSphere->GetPosition()); + Nz::Vector3d normalAsColor = (hitNormal + Nz::Vector3d(1.0)) / 2.0; + return Nz::Color((float) normalAsColor.x, (float)normalAsColor.y, (float)normalAsColor.z); + } + else + { + Nz::Vector3d unitDirection = Nz::Vector3d::Normalize(r.direction); + double a = 0.5 * (unitDirection.y + 1.0); + return (1.0 - a) * Nz::Color(1.0, 1.0, 1.0) + a * Nz::Color(0.5, 0.7, 1.0); + } +} + +void RayCast(SceneData& sceneData, const Nz::Vector2ui& origin, const Nz::Vector2ui& dims) +{ + for (std::size_t j = 0; j < dims.y; ++j) + { + std::size_t y = origin.y + j; + for (std::size_t i = 0; i < dims.x; ++i) + { + std::size_t x = origin.x + i; + Nz::Vector3d pixelCenter = sceneData.originPos + (double(x) * sceneData.pixelDeltaU) + (double(y) * sceneData.pixelDeltaV); + Nz::Vector3d rayDirection = pixelCenter - sceneData.cameraPos; + Nz::Rayd ray(sceneData.cameraPos, rayDirection); + Nz::Color color = RayColor(sceneData, ray); + + PixelColor& pixelColor = sceneData.pixels[y * sceneData.imageSize + x]; + color.ToRGB8(color, &pixelColor.r, &pixelColor.g, &pixelColor.b); + } + } +} diff --git a/tests/SchedulerBenchmark/task.hpp b/tests/SchedulerBenchmark/task.hpp new file mode 100644 index 000000000..d179487fc --- /dev/null +++ b/tests/SchedulerBenchmark/task.hpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +// Don't use Nz::Color as it's 4 floats and may get us memory-bound +struct PixelColor +{ + Nz::UInt8 r, g, b; +}; + +struct SceneData +{ + std::size_t imageSize; + std::unique_ptr pixels; + std::vector spheres; + Nz::Vector3d cameraPos; + Nz::Vector3d originPos; + Nz::Vector3d pixelDeltaU; + Nz::Vector3d pixelDeltaV; +}; + +void RayCast(SceneData& sceneData, const Nz::Vector2ui& origin, const Nz::Vector2ui& dims); diff --git a/tests/SchedulerBenchmark/xmake.lua b/tests/SchedulerBenchmark/xmake.lua new file mode 100644 index 000000000..dc228314d --- /dev/null +++ b/tests/SchedulerBenchmark/xmake.lua @@ -0,0 +1,5 @@ +target("SchedulerBenchmark") + add_deps("NazaraCore", "NazaraUtility") + add_files("main.cpp") + add_headerfiles("task.hpp") + add_files("task.cpp")