From e34ba8c05da8d6369c4dfebc00b0f6f3e53146b9 Mon Sep 17 00:00:00 2001 From: SirLynix Date: Wed, 4 Jan 2023 17:57:26 +0100 Subject: [PATCH] Add ComputeParticlesTest Renderer: Add a way to execute commands on the device --- .../compute/compute_particle_texture.nzsl | 30 + assets/shaders/compute/compute_particles.nzsl | 50 ++ .../Nazara/OpenGLRenderer/OpenGLDevice.hpp | 4 + .../OpenGLRenderer/Wrapper/CoreFunctions.hpp | 1 + include/Nazara/Renderer/RenderDevice.hpp | 6 + .../Nazara/VulkanRenderer/VulkanDevice.hpp | 4 + .../VulkanRenderer/Wrapper/CommandBuffer.hpp | 5 +- .../OpenGLCommandBufferBuilder.cpp | 2 +- src/Nazara/OpenGLRenderer/OpenGLDevice.cpp | 29 + .../VulkanCommandBufferBuilder.cpp | 2 +- src/Nazara/VulkanRenderer/VulkanDevice.cpp | 23 + tests/ComputeParticlesTest/main.cpp | 581 ++++++++++++++++++ tests/ComputeParticlesTest/xmake.lua | 3 + 13 files changed, 736 insertions(+), 4 deletions(-) create mode 100644 assets/shaders/compute/compute_particle_texture.nzsl create mode 100644 assets/shaders/compute/compute_particles.nzsl create mode 100644 tests/ComputeParticlesTest/main.cpp create mode 100644 tests/ComputeParticlesTest/xmake.lua diff --git a/assets/shaders/compute/compute_particle_texture.nzsl b/assets/shaders/compute/compute_particle_texture.nzsl new file mode 100644 index 000000000..19a388873 --- /dev/null +++ b/assets/shaders/compute/compute_particle_texture.nzsl @@ -0,0 +1,30 @@ +[nzsl_version("1.0")] +module Compute.ParticleTexture; + +external +{ + [binding(0)] output_tex: texture2D[f32, writeonly, rgba8] +} + +struct Input +{ + [builtin(global_invocation_indices)] global_invocation_id: vec3[u32] +} + +[entry(compute)] +[workgroup(32, 32, 1)] +fn main(input: Input) +{ + let indices = vec2[i32](input.global_invocation_id.xy); + + let uv = vec2[f32](indices) / vec2[f32](256.0, 256.0); + uv -= vec2[f32](0.5, 0.5); + + let outputColor = vec4[f32] + ( + (pow(1.0 - length(uv), 20.0)).xxx, + 1.0 + ); + + output_tex.Write(indices, outputColor); +} \ No newline at end of file diff --git a/assets/shaders/compute/compute_particles.nzsl b/assets/shaders/compute/compute_particles.nzsl new file mode 100644 index 000000000..4cf25318e --- /dev/null +++ b/assets/shaders/compute/compute_particles.nzsl @@ -0,0 +1,50 @@ +[nzsl_version("1.0")] +module Compute.Particles; + +[layout(std140)] +struct Particle +{ + color: vec3[f32], + position: vec2[f32], + velocity: vec2[f32] +} + +[layout(std140)] +struct ParticleData +{ + particle_count: u32, + particles: dyn_array[Particle] +} + +[layout(std140)] +struct SceneData +{ + deltaTime: f32, + mousePos: vec2[f32] +} + +external +{ + [binding(0)] data: storage[ParticleData], + [binding(1)] sceneData: uniform[SceneData] +} + +struct Input +{ + [builtin(global_invocation_indices)] indices: vec3[u32] +} + +[entry(compute)] +[workgroup(64, 1, 1)] +fn main(input: Input) +{ + let index = input.indices.x; + if (index >= data.particle_count) + return; + + let attract_pos = sceneData.mousePos; + let dist = length(attract_pos - data.particles[index].position); + data.particles[index].velocity += 10000.0 * (attract_pos - data.particles[index].position) * sceneData.deltaTime / (dist * dist); + + data.particles[index].position += data.particles[index].velocity * sceneData.deltaTime; +} \ No newline at end of file diff --git a/include/Nazara/OpenGLRenderer/OpenGLDevice.hpp b/include/Nazara/OpenGLRenderer/OpenGLDevice.hpp index cd9a5a784..412503875 100644 --- a/include/Nazara/OpenGLRenderer/OpenGLDevice.hpp +++ b/include/Nazara/OpenGLRenderer/OpenGLDevice.hpp @@ -32,6 +32,8 @@ namespace Nz std::shared_ptr CreateContext(GL::ContextParams params) const; std::shared_ptr CreateContext(GL::ContextParams params, WindowHandle handle) const; + void Execute(const FunctionRef& callback, QueueType queueType) override; + const RenderDeviceInfo& GetDeviceInfo() const override; const RenderDeviceFeatures& GetEnabledFeatures() const override; inline const GL::Context& GetReferenceContext() const; @@ -57,6 +59,8 @@ namespace Nz inline void NotifySamplerDestruction(GLuint sampler) const; inline void NotifyTextureDestruction(GLuint texture) const; + void WaitForIdle() override; + OpenGLDevice& operator=(const OpenGLDevice&) = delete; OpenGLDevice& operator=(OpenGLDevice&&) = delete; ///TODO? diff --git a/include/Nazara/OpenGLRenderer/Wrapper/CoreFunctions.hpp b/include/Nazara/OpenGLRenderer/Wrapper/CoreFunctions.hpp index d10560f3a..ac0a8e5c4 100644 --- a/include/Nazara/OpenGLRenderer/Wrapper/CoreFunctions.hpp +++ b/include/Nazara/OpenGLRenderer/Wrapper/CoreFunctions.hpp @@ -99,6 +99,7 @@ typedef void (GL_APIENTRYP PFNGLSPECIALIZESHADERPROC) (GLuint shader, const GLch cb(glEnable, PFNGLENABLEPROC) \ cb(glEnableVertexAttribArray, PFNGLENABLEVERTEXATTRIBARRAYPROC) \ cb(glEndQuery, PFNGLENDQUERYPROC) \ + cb(glFinish, PFNGLFINISHPROC) \ cb(glFlush, PFNGLFLUSHPROC) \ cb(glFramebufferRenderbuffer, PFNGLFRAMEBUFFERRENDERBUFFERPROC) \ cb(glFramebufferTexture2D, PFNGLFRAMEBUFFERTEXTURE2DPROC) \ diff --git a/include/Nazara/Renderer/RenderDevice.hpp b/include/Nazara/Renderer/RenderDevice.hpp index 7668457a4..7391fb19f 100644 --- a/include/Nazara/Renderer/RenderDevice.hpp +++ b/include/Nazara/Renderer/RenderDevice.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ namespace Nz { + class CommandBufferBuilder; class CommandPool; class ShaderModule; @@ -38,6 +40,8 @@ namespace Nz RenderDevice() = default; virtual ~RenderDevice(); + virtual void Execute(const FunctionRef& callback, QueueType queueType) = 0; + virtual const RenderDeviceInfo& GetDeviceInfo() const = 0; virtual const RenderDeviceFeatures& GetEnabledFeatures() const = 0; @@ -58,6 +62,8 @@ namespace Nz virtual bool IsTextureFormatSupported(PixelFormat format, TextureUsage usage) const = 0; + virtual void WaitForIdle() = 0; + static void ValidateFeatures(const RenderDeviceFeatures& supportedFeatures, RenderDeviceFeatures& enabledFeatures); }; } diff --git a/include/Nazara/VulkanRenderer/VulkanDevice.hpp b/include/Nazara/VulkanRenderer/VulkanDevice.hpp index 23de7ab14..9cc995ec6 100644 --- a/include/Nazara/VulkanRenderer/VulkanDevice.hpp +++ b/include/Nazara/VulkanRenderer/VulkanDevice.hpp @@ -23,6 +23,8 @@ namespace Nz VulkanDevice(VulkanDevice&&) = delete; ///TODO? ~VulkanDevice(); + void Execute(const FunctionRef& callback, QueueType queueType) override; + const RenderDeviceInfo& GetDeviceInfo() const override; const RenderDeviceFeatures& GetEnabledFeatures() const override; @@ -42,6 +44,8 @@ namespace Nz bool IsTextureFormatSupported(PixelFormat format, TextureUsage usage) const override; + void WaitForIdle() override; + VulkanDevice& operator=(const VulkanDevice&) = delete; VulkanDevice& operator=(VulkanDevice&&) = delete; ///TODO? diff --git a/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.hpp b/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.hpp index 336ea2a40..e3686864d 100644 --- a/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.hpp +++ b/include/Nazara/VulkanRenderer/Wrapper/CommandBuffer.hpp @@ -122,8 +122,9 @@ namespace Nz::Vk public: using AutoFree::AutoFree; - operator VkCommandBuffer() const { return Get(); } - }; + operator VkCommandBuffer() const { return Get(); } + }; + } } #include diff --git a/src/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.cpp b/src/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.cpp index f4dc05ced..2808c8f9f 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.cpp @@ -160,7 +160,7 @@ namespace Nz void OpenGLCommandBufferBuilder::PostTransferBarrier() { - /* nothing to do */ + m_commandBuffer.InsertMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); } void OpenGLCommandBufferBuilder::SetScissor(const Recti& scissorRegion) diff --git a/src/Nazara/OpenGLRenderer/OpenGLDevice.cpp b/src/Nazara/OpenGLRenderer/OpenGLDevice.cpp index 5a8c9d896..85e988a3f 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLDevice.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLDevice.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -155,6 +156,22 @@ namespace Nz #endif } + void OpenGLDevice::Execute(const FunctionRef& callback, QueueType /*queueType*/) + { + const GL::Context* activeContext = GL::Context::GetCurrentContext(); + if (!activeContext || activeContext->GetDevice() != this) + { + if (!GL::Context::SetCurrentContext(m_referenceContext.get())) + throw std::runtime_error("failed to activate context"); + } + + OpenGLCommandBuffer commandBuffer; //< TODO: Use a pool and remove default constructor + OpenGLCommandBufferBuilder builder(commandBuffer); + callback(builder); + + commandBuffer.Execute(); + } + const RenderDeviceInfo& OpenGLDevice::GetDeviceInfo() const { return m_deviceInfo; @@ -318,4 +335,16 @@ namespace Nz return false; } + + void OpenGLDevice::WaitForIdle() + { + const GL::Context* activeContext = GL::Context::GetCurrentContext(); + if (!activeContext || activeContext->GetDevice() != this) + { + if (!GL::Context::SetCurrentContext(m_referenceContext.get())) + throw std::runtime_error("failed to activate context"); + } + + m_referenceContext->glFinish(); + } } diff --git a/src/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.cpp b/src/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.cpp index 9939d3bd6..092384374 100644 --- a/src/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.cpp +++ b/src/Nazara/VulkanRenderer/VulkanCommandBufferBuilder.cpp @@ -320,7 +320,7 @@ namespace Nz void VulkanCommandBufferBuilder::PostTransferBarrier() { - m_commandBuffer.MemoryBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_UNIFORM_READ_BIT); + m_commandBuffer.MemoryBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT); } void VulkanCommandBufferBuilder::SetScissor(const Recti& scissorRegion) diff --git a/src/Nazara/VulkanRenderer/VulkanDevice.cpp b/src/Nazara/VulkanRenderer/VulkanDevice.cpp index cb833e856..58256599e 100644 --- a/src/Nazara/VulkanRenderer/VulkanDevice.cpp +++ b/src/Nazara/VulkanRenderer/VulkanDevice.cpp @@ -3,6 +3,7 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include #include #include @@ -13,12 +14,29 @@ #include #include #include +#include #include namespace Nz { VulkanDevice::~VulkanDevice() = default; + void VulkanDevice::Execute(const FunctionRef& callback, QueueType queueType) + { + Vk::AutoCommandBuffer commandBuffer = AllocateCommandBuffer(queueType); + if (!commandBuffer->Begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT)) + throw std::runtime_error("failed to begin command buffer: " + TranslateVulkanError(commandBuffer->GetLastErrorCode())); + + VulkanCommandBufferBuilder builder(commandBuffer); + callback(builder); + + if (!commandBuffer->End()) + throw std::runtime_error("failed to build command buffer: " + TranslateVulkanError(commandBuffer->GetLastErrorCode())); + + GetQueue(GetDefaultFamilyIndex(queueType), 0).Submit(commandBuffer); + GetQueue(GetDefaultFamilyIndex(queueType), 0).WaitIdle(); + } + const RenderDeviceInfo& VulkanDevice::GetDeviceInfo() const { return m_renderDeviceInfo; @@ -144,4 +162,9 @@ namespace Nz VkFormatProperties formatProperties = GetInstance().GetPhysicalDeviceFormatProperties(GetPhysicalDevice(), vulkanFormat); return formatProperties.optimalTilingFeatures & flags; //< Assume optimal tiling } + + void VulkanDevice::WaitForIdle() + { + Device::WaitForIdle(); + } } diff --git a/tests/ComputeParticlesTest/main.cpp b/tests/ComputeParticlesTest/main.cpp new file mode 100644 index 000000000..513a76fe8 --- /dev/null +++ b/tests/ComputeParticlesTest/main.cpp @@ -0,0 +1,581 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +NAZARA_REQUEST_DEDICATED_GPU() + +struct SpriteRenderData +{ + std::shared_ptr vertexBuffer; + std::shared_ptr shaderBinding; +}; + +struct SpriteRenderPipeline +{ + std::shared_ptr pipeline; + std::shared_ptr pipelineLayout; + +}; + +std::shared_ptr BuildComputePipeline(Nz::RenderDevice& device, std::shared_ptr pipelineLayout, std::shared_ptr moduleResolver); +SpriteRenderPipeline BuildSpritePipeline(Nz::RenderDevice& device); +SpriteRenderData BuildSpriteData(Nz::RenderDevice& device, const SpriteRenderPipeline& pipelineData, const Nz::Rectf& textureRect, const Nz::Vector2f& screenSize, const Nz::RenderBufferView& buffer, const Nz::RenderBufferView& particleBuffer, std::shared_ptr texture, std::shared_ptr sampler); +std::shared_ptr GenerateSpriteTexture(Nz::RenderDevice& device, std::shared_ptr moduleResolver); + +int main() +{ + Nz::Vector2ui windowSize = { 1280, 720 }; + + std::filesystem::path resourceDir = "assets/examples"; + if (!std::filesystem::is_directory(resourceDir) && std::filesystem::is_directory("../.." / resourceDir)) + resourceDir = "../.." / resourceDir; + + Nz::Renderer::Config rendererConfig; + std::cout << "Run using Vulkan? (y/n)" << std::endl; + if (std::getchar() == 'y') + rendererConfig.preferredAPI = Nz::RenderAPI::Vulkan; + else + rendererConfig.preferredAPI = Nz::RenderAPI::OpenGL; + + Nz::Modules nazara(rendererConfig); + + Nz::RenderDeviceFeatures enabledFeatures; + enabledFeatures.computeShaders = true; + enabledFeatures.storageBuffers = true; + enabledFeatures.textureReadWrite = true; + + std::shared_ptr device = Nz::Renderer::Instance()->InstanciateRenderDevice(0, enabledFeatures); + + nzsl::FieldOffsets particleLayout(nzsl::StructLayout::Std140); + std::size_t particleColorOffset = particleLayout.AddField(nzsl::StructFieldType::Float3); + std::size_t particlePosOffset = particleLayout.AddField(nzsl::StructFieldType::Float2); + std::size_t particleVelOffset = particleLayout.AddField(nzsl::StructFieldType::Float2); + + std::size_t particleSize = particleLayout.GetAlignedSize(); + + constexpr std::size_t maxParticleCount = 10'000; + constexpr std::size_t initialParticleCount = maxParticleCount; + + nzsl::FieldOffsets bufferLayout(nzsl::StructLayout::Std140); + std::size_t particleCountOffset = bufferLayout.AddField(nzsl::StructFieldType::UInt1); + std::size_t particlesOffset = bufferLayout.AddStructArray(particleLayout, maxParticleCount); + + std::size_t bufferSize = bufferLayout.GetAlignedSize(); + + std::vector particleBufferInitialData(bufferSize); + Nz::AccessByOffset(particleBufferInitialData.data(), particleCountOffset) = initialParticleCount; + + std::mt19937 rand(std::random_device{}()); + std::uniform_real_distribution colorDis(0.f, 1.f); + std::uniform_real_distribution posXDis(0.f, float(windowSize.x)); + std::uniform_real_distribution posYDis(0.f, float(windowSize.y)); + std::uniform_real_distribution velDis(-20.f, 20.f); + + for (std::size_t i = 0; i < initialParticleCount; ++i) + { + std::size_t baseOffset = particlesOffset + particleSize * i; + + Nz::AccessByOffset(particleBufferInitialData.data(), baseOffset + particleColorOffset) = Nz::Vector3f(colorDis(rand), colorDis(rand), colorDis(rand)); + //Nz::AccessByOffset(particleBufferInitialData.data(), baseOffset + particleColorOffset) = (i > 2500) ? Nz::Vector3f(0.f, 1.f, 0.f) : Nz::Vector3f(0.f, 0.f, 1.f); + Nz::AccessByOffset(particleBufferInitialData.data(), baseOffset + particlePosOffset) = Nz::Vector2f(posXDis(rand), posYDis(rand)); + Nz::AccessByOffset(particleBufferInitialData.data(), baseOffset + particleVelOffset) = Nz::Vector2f(velDis(rand), velDis(rand)); + } + + std::shared_ptr particleBuffer = device->InstantiateBuffer(Nz::BufferType::Storage, bufferSize, Nz::BufferUsage::DeviceLocal, particleBufferInitialData.data()); + + nzsl::FieldOffsets sceneBufferLayout(nzsl::StructLayout::Std140); + std::size_t deltaTimeOffset = sceneBufferLayout.AddField(nzsl::StructFieldType::Float1); + std::size_t mousePosOffset = sceneBufferLayout.AddField(nzsl::StructFieldType::Float2); + + std::size_t sceneBufferSize = sceneBufferLayout.GetAlignedSize(); + + std::shared_ptr sceneDataBuffer = device->InstantiateBuffer(Nz::BufferType::Uniform, sceneBufferSize, Nz::BufferUsage::DeviceLocal | Nz::BufferUsage::Dynamic); + + // Compute part + Nz::RenderPipelineLayoutInfo computePipelineLayoutInfo; + computePipelineLayoutInfo.bindings.assign({ + { + 0, 0, 1, + Nz::ShaderBindingType::StorageBuffer, + nzsl::ShaderStageType::Compute + }, + { + 0, 1, 1, + Nz::ShaderBindingType::UniformBuffer, + nzsl::ShaderStageType::Compute + } + }); + + std::shared_ptr computePipelineLayout = device->InstantiateRenderPipelineLayout(computePipelineLayoutInfo); + + std::shared_ptr moduleResolver = std::make_shared(); + moduleResolver->RegisterModuleDirectory(resourceDir / "../shaders/", true); + + std::shared_ptr computePipeline; + try + { + computePipeline = BuildComputePipeline(*device, computePipelineLayout, moduleResolver); + } + catch (const std::exception& e) + { + std::cerr << "failed to compile compute shaders: " << e.what() << std::endl; + std::abort(); + } + + std::atomic_bool hasNewPipeline = false; + + std::shared_ptr computeBinding = computePipelineLayout->AllocateShaderBinding(0); + computeBinding->Update({ + { + 0, + Nz::ShaderBinding::StorageBufferBinding { + particleBuffer.get(), 0, bufferSize + } + }, + { + 1, + Nz::ShaderBinding::UniformBufferBinding { + sceneDataBuffer.get(), 0, sceneBufferSize + } + } + }); + + moduleResolver->OnModuleUpdated.Connect([&](nzsl::ModuleResolver*, const std::string& moduleName) + { + std::cout << moduleName << " has been updated" << std::endl; + hasNewPipeline = true; + }); + + std::string windowTitle = "Compute test"; + + Nz::RenderWindow window; + if (!window.Create(device, Nz::VideoMode(windowSize.x, windowSize.y, 32), windowTitle)) + { + std::cout << "Failed to create Window" << std::endl; + std::abort(); + } + + constexpr float textureSize = 512.f; + float margin = (windowSize.y - textureSize) * 0.5f; + + nzsl::FieldOffsets viewerLayout(nzsl::StructLayout::Std140); + std::size_t projectionMatrixOffset = viewerLayout.AddMatrix(nzsl::StructFieldType::Float1, 4, 4, true); + + std::size_t viewerBufferSize = viewerLayout.GetAlignedSize(); + + std::vector viewerBufferInitialData(viewerBufferSize); + Nz::AccessByOffset(viewerBufferInitialData.data(), projectionMatrixOffset) = Nz::Matrix4f::Ortho(0.f, windowSize.x, 0.f, windowSize.y); + + std::shared_ptr uniformBuffer = device->InstantiateBuffer(Nz::BufferType::Uniform, viewerBufferSize, Nz::BufferUsage::DeviceLocal, viewerBufferInitialData.data()); + + std::shared_ptr texture = GenerateSpriteTexture(*device, moduleResolver); + std::shared_ptr textureSampler = device->InstantiateTextureSampler({}); + + SpriteRenderPipeline spriteRenderPipeline = BuildSpritePipeline(*device); + SpriteRenderData spriteRenderData1 = BuildSpriteData(*device, spriteRenderPipeline, Nz::Rectf(-8.f, -8.f, 16.f, 16.f), Nz::Vector2f(windowSize), uniformBuffer.get(), particleBuffer.get(), texture, textureSampler); + + Nz::MillisecondClock fpsClock; + Nz::HighPrecisionClock updateClock; + unsigned int fps = 0; + + while (window.IsOpen()) + { + window.ProcessEvents(); + + Nz::RenderFrame frame = window.AcquireFrame(); + if (!frame) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + + if (hasNewPipeline) + { + try + { + hasNewPipeline = false; + std::shared_ptr newComputePipeline = BuildComputePipeline(*device, computePipelineLayout, moduleResolver); + frame.PushForRelease(std::move(computePipeline)); + computePipeline = std::move(newComputePipeline); + } + catch (const std::exception& e) + { + std::cerr << e.what() << std::endl; + } + } + + float deltaTime = updateClock.Restart().AsSeconds(); + + Nz::UploadPool& uploadPool = frame.GetUploadPool(); + + const Nz::RenderTarget* windowRT = window.GetRenderTarget(); + frame.Execute([&](Nz::CommandBufferBuilder& builder) + { + builder.BeginDebugRegion("Upload scene data", Nz::Color::Yellow()); + { + Nz::Vector2i mousePos = Nz::Mouse::GetPosition(window); + + auto& allocation = uploadPool.Allocate(sceneBufferSize); + Nz::AccessByOffset(allocation.mappedPtr, deltaTimeOffset) = deltaTime; + Nz::AccessByOffset(allocation.mappedPtr, mousePosOffset) = Nz::Vector2f(mousePos.x, windowSize.y - mousePos.y); + + builder.PreTransferBarrier(); + builder.CopyBuffer(allocation, sceneDataBuffer.get()); + builder.PostTransferBarrier(); + } + builder.EndDebugRegion(); + + builder.BeginDebugRegion("Compute part", Nz::Color::Blue()); + { + builder.BindComputePipeline(*computePipeline); + builder.BindComputeShaderBinding(0, *computeBinding); + builder.Dispatch(maxParticleCount / 64 + 1, 1, 1); + } + builder.EndDebugRegion(); + + builder.BeginDebugRegion("Main window rendering", Nz::Color::Green()); + { + Nz::Recti renderRect(0, 0, window.GetSize().x, window.GetSize().y); + + Nz::CommandBufferBuilder::ClearValues clearValues[2]; + clearValues[0].color = Nz::Color::Black(); + clearValues[1].depth = 1.f; + clearValues[1].stencil = 0; + + builder.BeginRenderPass(windowRT->GetFramebuffer(frame.GetFramebufferIndex()), windowRT->GetRenderPass(), renderRect, { clearValues[0], clearValues[1] }); + { + builder.SetScissor(Nz::Recti{ 0, 0, int(windowSize.x), int(windowSize.y) }); + builder.SetViewport(Nz::Recti{ 0, 0, int(windowSize.x), int(windowSize.y) }); + + builder.BindRenderPipeline(*spriteRenderPipeline.pipeline); + + builder.BindVertexBuffer(0, *spriteRenderData1.vertexBuffer); + builder.BindRenderShaderBinding(0, *spriteRenderData1.shaderBinding); + builder.Draw(4, initialParticleCount); + } + builder.EndRenderPass(); + } + builder.EndDebugRegion(); + + }, Nz::QueueType::Graphics); + + frame.Present(); + + fps++; + + if (fpsClock.RestartIfOver(Nz::Time::Second())) + { + window.SetTitle(windowTitle + " - " + Nz::NumberToString(fps) + " FPS"); + fps = 0; + } + } + + return EXIT_SUCCESS; +} + +std::shared_ptr BuildComputePipeline(Nz::RenderDevice& device, std::shared_ptr pipelineLayout, std::shared_ptr moduleResolver) +{ + nzsl::Ast::ModulePtr shaderModule = moduleResolver->Resolve("Compute.Particles"); + if (!shaderModule) + { + std::cout << "Failed to parse shader module" << std::endl; + std::abort(); + } + + nzsl::ShaderWriter::States states; + states.optimize = true; + + auto computeShader = device.InstantiateShaderModule(nzsl::ShaderStageType::Compute, *shaderModule, states); + if (!computeShader) + { + std::cout << "Failed to instantiate shader" << std::endl; + std::abort(); + } + + Nz::ComputePipelineInfo computePipelineInfo; + computePipelineInfo.pipelineLayout = pipelineLayout; + computePipelineInfo.shaderModule = computeShader; + + std::shared_ptr pipeline = device.InstantiateComputePipeline(computePipelineInfo); + if (!pipeline) + { + std::cout << "Failed to instantiate compute pipeline" << std::endl; + std::abort(); + } + + return pipeline; +} + +const char fragVertSource[] = R"( +[nzsl_version("1.0")] +module; + +[layout(std140)] +struct Particle +{ + color: vec3[f32], + position: vec2[f32], + velocity: vec2[f32] +} + +[layout(std140)] +struct ParticleData +{ + particle_count: u32, + particles: dyn_array[Particle] +} + +struct ViewerData +{ + projectionMatrix: mat4[f32] +} + +external +{ + [binding(0)] viewerData: uniform[ViewerData], + [binding(1)] particleData: storage[ParticleData], + [binding(2)] texture: sampler2D[f32] +} + +struct FragOut +{ + [location(0)] color: vec4[f32] +} + +struct VertIn +{ + [location(0)] pos: vec2[f32], + [location(1)] uv: vec2[f32], + [builtin(instance_index)] particle_index: i32 +} + +struct VertOut +{ + [location(0)] uv: vec2[f32], + [location(1)] color: vec3[f32], + [builtin(position)] pos: vec4[f32] +} + +[entry(frag)] +fn main(input: VertOut) -> FragOut +{ + let output: FragOut; + output.color = vec4[f32](input.color, 1.0) * texture.Sample(input.uv); + + return output; +} + +[entry(vert)] +fn main(input: VertIn) -> VertOut +{ + let output: VertOut; + output.pos = viewerData.projectionMatrix * vec4[f32](input.pos + particleData.particles[input.particle_index].position, 0.0, 1.0); + output.color = particleData.particles[input.particle_index].color; + output.uv = input.uv; + return output; +} +)"; + +SpriteRenderPipeline BuildSpritePipeline(Nz::RenderDevice& device) +{ + try + { + nzsl::Ast::ModulePtr shaderModule = nzsl::Parse(std::string_view(fragVertSource, sizeof(fragVertSource))); + if (!shaderModule) + { + std::cout << "Failed to parse shader module" << std::endl; + std::abort(); + } + + nzsl::ShaderWriter::States states; + states.optimize = true; + + auto fragVertShader = device.InstantiateShaderModule(nzsl::ShaderStageType::Fragment | nzsl::ShaderStageType::Vertex, *shaderModule, states); + if (!fragVertShader) + { + std::cout << "Failed to instantiate shader" << std::endl; + std::abort(); + } + + std::shared_ptr vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XY_UV); + + SpriteRenderPipeline pipelineData; + + Nz::RenderPipelineLayoutInfo pipelineLayoutInfo; + pipelineLayoutInfo.bindings.assign({ + { + 0, 0, 1, + Nz::ShaderBindingType::UniformBuffer, + nzsl::ShaderStageType::Vertex + }, + { + 0, 1, 1, + Nz::ShaderBindingType::StorageBuffer, + nzsl::ShaderStageType::Vertex | nzsl::ShaderStageType::Fragment + }, + { + 0, 2, 1, + Nz::ShaderBindingType::Sampler, + nzsl::ShaderStageType::Fragment + } + }); + + pipelineData.pipelineLayout = device.InstantiateRenderPipelineLayout(std::move(pipelineLayoutInfo)); + + Nz::RenderPipelineInfo pipelineInfo; + pipelineInfo.blending = true; + pipelineInfo.blend.dstColor = Nz::BlendFunc::One; + pipelineInfo.blend.srcColor = Nz::BlendFunc::One; + pipelineInfo.blend.dstAlpha = Nz::BlendFunc::ConstantAlpha; + pipelineInfo.primitiveMode = Nz::PrimitiveMode::TriangleStrip; + pipelineInfo.pipelineLayout = pipelineData.pipelineLayout; + pipelineInfo.shaderModules.push_back(fragVertShader); + pipelineInfo.vertexBuffers.push_back({ + 0, vertexDeclaration + }); + + pipelineData.pipeline = device.InstantiateRenderPipeline(std::move(pipelineInfo)); + + return pipelineData; + } + catch (const std::exception& e) + { + std::cerr << e.what() << std::endl; + std::abort(); + } +} + +SpriteRenderData BuildSpriteData(Nz::RenderDevice& device, const SpriteRenderPipeline& pipelineData, const Nz::Rectf& textureRect, const Nz::Vector2f& screenSize, const Nz::RenderBufferView& buffer, const Nz::RenderBufferView& particleBuffer, std::shared_ptr texture, std::shared_ptr sampler) +{ + try + { + std::array pos; + pos[0].position = textureRect.GetCorner(Nz::RectCorner::LeftBottom); + pos[0].uv = Nz::Vector2f(0.f, 0.f); + pos[1].position = textureRect.GetCorner(Nz::RectCorner::LeftTop); + pos[1].uv = Nz::Vector2f(0.f, 1.f); + pos[2].position = textureRect.GetCorner(Nz::RectCorner::RightBottom); + pos[2].uv = Nz::Vector2f(1.f, 0.f); + pos[3].position = textureRect.GetCorner(Nz::RectCorner::RightTop); + pos[3].uv = Nz::Vector2f(1.f, 1.f); + + SpriteRenderData renderData; + renderData.vertexBuffer = device.InstantiateBuffer(Nz::BufferType::Vertex, 4 * 4 * sizeof(float), Nz::BufferUsage::DeviceLocal, pos.data()); + + renderData.shaderBinding = pipelineData.pipelineLayout->AllocateShaderBinding(0); + renderData.shaderBinding->Update({ + { + 0, + Nz::ShaderBinding::UniformBufferBinding { + buffer.GetBuffer(), buffer.GetOffset(), buffer.GetSize() + } + }, + { + 1, + Nz::ShaderBinding::StorageBufferBinding { + particleBuffer.GetBuffer(), particleBuffer.GetOffset(), particleBuffer.GetSize() + } + }, + { + 2, + Nz::ShaderBinding::SampledTextureBinding { + texture.get(), sampler.get() + } + } + }); + + return renderData; + } + catch (const std::exception& e) + { + std::cerr << e.what() << std::endl; + std::abort(); + } +} + +std::shared_ptr GenerateSpriteTexture(Nz::RenderDevice& device, std::shared_ptr moduleResolver) +{ + nzsl::Ast::ModulePtr shaderModule = moduleResolver->Resolve("Compute.ParticleTexture"); + if (!shaderModule) + { + std::cout << "Failed to parse shader module" << std::endl; + std::abort(); + } + + nzsl::ShaderWriter::States states; + states.optimize = true; + + auto computeShader = device.InstantiateShaderModule(nzsl::ShaderStageType::Compute, *shaderModule, states); + if (!computeShader) + { + std::cout << "Failed to instantiate shader" << std::endl; + std::abort(); + } + + Nz::RenderPipelineLayoutInfo pipelineLayoutInfo; + pipelineLayoutInfo.bindings.assign({ + { + 0, 0, 1, + Nz::ShaderBindingType::Texture, + nzsl::ShaderStageType::Compute + } + }); + + std::shared_ptr pipelineLayout = device.InstantiateRenderPipelineLayout(std::move(pipelineLayoutInfo)); + + + Nz::ComputePipelineInfo computePipelineInfo; + computePipelineInfo.pipelineLayout = pipelineLayout; + computePipelineInfo.shaderModule = computeShader; + + std::shared_ptr pipeline = device.InstantiateComputePipeline(std::move(computePipelineInfo)); + if (!pipeline) + { + std::cout << "Failed to instantiate compute pipeline" << std::endl; + std::abort(); + } + + // Destination texture + Nz::TextureInfo texParams; + texParams.type = Nz::ImageType::E2D; + texParams.pixelFormat = Nz::PixelFormat::RGBA8; + texParams.width = texParams.height = 256; + texParams.usageFlags = Nz::TextureUsage::ShaderReadWrite | Nz::TextureUsage::ShaderSampling; + + std::shared_ptr targetTexture = device.InstantiateTexture(texParams); + + Nz::ShaderBindingPtr binding = pipelineLayout->AllocateShaderBinding(0); + binding->Update({ + { + 0, + Nz::ShaderBinding::TextureBinding { + targetTexture.get(), Nz::TextureAccess::WriteOnly + } + } + }); + + device.Execute([&](Nz::CommandBufferBuilder& builder) + { + builder.TextureBarrier(Nz::PipelineStage::BottomOfPipe, Nz::PipelineStage::ComputeShader, {}, Nz::MemoryAccess::ShaderWrite, Nz::TextureLayout::Undefined, Nz::TextureLayout::General, *targetTexture); + + builder.BindComputePipeline(*pipeline); + builder.BindComputeShaderBinding(0, *binding); + builder.Dispatch(texParams.width / 32, texParams.height / 32, 1); + + builder.TextureBarrier(Nz::PipelineStage::ComputeShader, Nz::PipelineStage::FragmentShader, Nz::MemoryAccess::ShaderWrite, Nz::MemoryAccess::ShaderRead, Nz::TextureLayout::General, Nz::TextureLayout::ColorInput, *targetTexture); + + }, Nz::QueueType::Compute); + + return targetTexture; +} diff --git a/tests/ComputeParticlesTest/xmake.lua b/tests/ComputeParticlesTest/xmake.lua new file mode 100644 index 000000000..a1c8d430b --- /dev/null +++ b/tests/ComputeParticlesTest/xmake.lua @@ -0,0 +1,3 @@ +target("ComputeParticlesTest") + add_deps("NazaraRenderer") + add_files("main.cpp")