Add ComputeParticlesTest
Renderer: Add a way to execute commands on the device
This commit is contained in:
parent
9e7b98a017
commit
e34ba8c05d
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -32,6 +32,8 @@ namespace Nz
|
|||
std::shared_ptr<GL::Context> CreateContext(GL::ContextParams params) const;
|
||||
std::shared_ptr<GL::Context> CreateContext(GL::ContextParams params, WindowHandle handle) const;
|
||||
|
||||
void Execute(const FunctionRef<void(CommandBufferBuilder& builder)>& 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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <Nazara/Renderer/Texture.hpp>
|
||||
#include <Nazara/Renderer/TextureSampler.hpp>
|
||||
#include <Nazara/Utility/PixelFormat.hpp>
|
||||
#include <Nazara/Utils/FunctionRef.hpp>
|
||||
#include <NZSL/ShaderWriter.hpp>
|
||||
#include <NZSL/Ast/Module.hpp>
|
||||
#include <memory>
|
||||
|
|
@ -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<void(CommandBufferBuilder& builder)>& 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);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ namespace Nz
|
|||
VulkanDevice(VulkanDevice&&) = delete; ///TODO?
|
||||
~VulkanDevice();
|
||||
|
||||
void Execute(const FunctionRef<void(CommandBufferBuilder& builder)>& 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?
|
||||
|
||||
|
|
|
|||
|
|
@ -122,8 +122,9 @@ namespace Nz::Vk
|
|||
public:
|
||||
using AutoFree::AutoFree;
|
||||
|
||||
operator VkCommandBuffer() const { return Get(); }
|
||||
};
|
||||
operator VkCommandBuffer() const { return Get(); }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#include <Nazara/VulkanRenderer/Wrapper/CommandBuffer.inl>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <Nazara/OpenGLRenderer/OpenGLDevice.hpp>
|
||||
#include <Nazara/OpenGLRenderer/OpenGLBuffer.hpp>
|
||||
#include <Nazara/OpenGLRenderer/OpenGLCommandBufferBuilder.hpp>
|
||||
#include <Nazara/OpenGLRenderer/OpenGLCommandPool.hpp>
|
||||
#include <Nazara/OpenGLRenderer/OpenGLComputePipeline.hpp>
|
||||
#include <Nazara/OpenGLRenderer/OpenGLFboFramebuffer.hpp>
|
||||
|
|
@ -155,6 +156,22 @@ namespace Nz
|
|||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDevice::Execute(const FunctionRef<void(CommandBufferBuilder& builder)>& 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||
|
||||
#include <Nazara/VulkanRenderer/VulkanDevice.hpp>
|
||||
#include <Nazara/VulkanRenderer/VulkanCommandBufferBuilder.hpp>
|
||||
#include <Nazara/VulkanRenderer/VulkanCommandPool.hpp>
|
||||
#include <Nazara/VulkanRenderer/VulkanComputePipeline.hpp>
|
||||
#include <Nazara/VulkanRenderer/VulkanRenderPass.hpp>
|
||||
|
|
@ -13,12 +14,29 @@
|
|||
#include <Nazara/VulkanRenderer/VulkanTexture.hpp>
|
||||
#include <Nazara/VulkanRenderer/VulkanTextureFramebuffer.hpp>
|
||||
#include <Nazara/VulkanRenderer/VulkanTextureSampler.hpp>
|
||||
#include <Nazara/VulkanRenderer/Wrapper/QueueHandle.hpp>
|
||||
#include <Nazara/VulkanRenderer/Debug.hpp>
|
||||
|
||||
namespace Nz
|
||||
{
|
||||
VulkanDevice::~VulkanDevice() = default;
|
||||
|
||||
void VulkanDevice::Execute(const FunctionRef<void(CommandBufferBuilder& builder)>& 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,581 @@
|
|||
#include <Nazara/Core.hpp>
|
||||
#include <Nazara/Math.hpp>
|
||||
#include <Nazara/Platform.hpp>
|
||||
#include <Nazara/Renderer.hpp>
|
||||
#include <NZSL/FilesystemModuleResolver.hpp>
|
||||
#include <NZSL/LangWriter.hpp>
|
||||
#include <NZSL/Parser.hpp>
|
||||
#include <NZSL/Math/FieldOffsets.hpp>
|
||||
#include <Nazara/Utility.hpp>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
|
||||
NAZARA_REQUEST_DEDICATED_GPU()
|
||||
|
||||
struct SpriteRenderData
|
||||
{
|
||||
std::shared_ptr<Nz::RenderBuffer> vertexBuffer;
|
||||
std::shared_ptr<Nz::ShaderBinding> shaderBinding;
|
||||
};
|
||||
|
||||
struct SpriteRenderPipeline
|
||||
{
|
||||
std::shared_ptr<Nz::RenderPipeline> pipeline;
|
||||
std::shared_ptr<Nz::RenderPipelineLayout> pipelineLayout;
|
||||
|
||||
};
|
||||
|
||||
std::shared_ptr<Nz::ComputePipeline> BuildComputePipeline(Nz::RenderDevice& device, std::shared_ptr<Nz::RenderPipelineLayout> pipelineLayout, std::shared_ptr<nzsl::ModuleResolver> 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<Nz::Texture> texture, std::shared_ptr<Nz::TextureSampler> sampler);
|
||||
std::shared_ptr<Nz::Texture> GenerateSpriteTexture(Nz::RenderDevice& device, std::shared_ptr<nzsl::ModuleResolver> 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<Nz::Renderer> nazara(rendererConfig);
|
||||
|
||||
Nz::RenderDeviceFeatures enabledFeatures;
|
||||
enabledFeatures.computeShaders = true;
|
||||
enabledFeatures.storageBuffers = true;
|
||||
enabledFeatures.textureReadWrite = true;
|
||||
|
||||
std::shared_ptr<Nz::RenderDevice> 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<Nz::UInt8> particleBufferInitialData(bufferSize);
|
||||
Nz::AccessByOffset<Nz::UInt32&>(particleBufferInitialData.data(), particleCountOffset) = initialParticleCount;
|
||||
|
||||
std::mt19937 rand(std::random_device{}());
|
||||
std::uniform_real_distribution<float> colorDis(0.f, 1.f);
|
||||
std::uniform_real_distribution<float> posXDis(0.f, float(windowSize.x));
|
||||
std::uniform_real_distribution<float> posYDis(0.f, float(windowSize.y));
|
||||
std::uniform_real_distribution<float> velDis(-20.f, 20.f);
|
||||
|
||||
for (std::size_t i = 0; i < initialParticleCount; ++i)
|
||||
{
|
||||
std::size_t baseOffset = particlesOffset + particleSize * i;
|
||||
|
||||
Nz::AccessByOffset<Nz::Vector3f&>(particleBufferInitialData.data(), baseOffset + particleColorOffset) = Nz::Vector3f(colorDis(rand), colorDis(rand), colorDis(rand));
|
||||
//Nz::AccessByOffset<Nz::Vector3f&>(particleBufferInitialData.data(), baseOffset + particleColorOffset) = (i > 2500) ? Nz::Vector3f(0.f, 1.f, 0.f) : Nz::Vector3f(0.f, 0.f, 1.f);
|
||||
Nz::AccessByOffset<Nz::Vector2f&>(particleBufferInitialData.data(), baseOffset + particlePosOffset) = Nz::Vector2f(posXDis(rand), posYDis(rand));
|
||||
Nz::AccessByOffset<Nz::Vector2f&>(particleBufferInitialData.data(), baseOffset + particleVelOffset) = Nz::Vector2f(velDis(rand), velDis(rand));
|
||||
}
|
||||
|
||||
std::shared_ptr<Nz::RenderBuffer> 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<Nz::RenderBuffer> 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<Nz::RenderPipelineLayout> computePipelineLayout = device->InstantiateRenderPipelineLayout(computePipelineLayoutInfo);
|
||||
|
||||
std::shared_ptr<nzsl::FilesystemModuleResolver> moduleResolver = std::make_shared<nzsl::FilesystemModuleResolver>();
|
||||
moduleResolver->RegisterModuleDirectory(resourceDir / "../shaders/", true);
|
||||
|
||||
std::shared_ptr<Nz::ComputePipeline> 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<Nz::ShaderBinding> 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<Nz::UInt8> viewerBufferInitialData(viewerBufferSize);
|
||||
Nz::AccessByOffset<Nz::Matrix4f&>(viewerBufferInitialData.data(), projectionMatrixOffset) = Nz::Matrix4f::Ortho(0.f, windowSize.x, 0.f, windowSize.y);
|
||||
|
||||
std::shared_ptr<Nz::RenderBuffer> uniformBuffer = device->InstantiateBuffer(Nz::BufferType::Uniform, viewerBufferSize, Nz::BufferUsage::DeviceLocal, viewerBufferInitialData.data());
|
||||
|
||||
std::shared_ptr<Nz::Texture> texture = GenerateSpriteTexture(*device, moduleResolver);
|
||||
std::shared_ptr<Nz::TextureSampler> 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<Nz::ComputePipeline> 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<float&>(allocation.mappedPtr, deltaTimeOffset) = deltaTime;
|
||||
Nz::AccessByOffset<Nz::Vector2f&>(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<Nz::ComputePipeline> BuildComputePipeline(Nz::RenderDevice& device, std::shared_ptr<Nz::RenderPipelineLayout> pipelineLayout, std::shared_ptr<nzsl::ModuleResolver> 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<Nz::ComputePipeline> 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<Nz::VertexDeclaration> 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<Nz::Texture> texture, std::shared_ptr<Nz::TextureSampler> sampler)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::array<Nz::VertexStruct_XY_UV, 4> 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<Nz::Texture> GenerateSpriteTexture(Nz::RenderDevice& device, std::shared_ptr<nzsl::ModuleResolver> 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<Nz::RenderPipelineLayout> pipelineLayout = device.InstantiateRenderPipelineLayout(std::move(pipelineLayoutInfo));
|
||||
|
||||
|
||||
Nz::ComputePipelineInfo computePipelineInfo;
|
||||
computePipelineInfo.pipelineLayout = pipelineLayout;
|
||||
computePipelineInfo.shaderModule = computeShader;
|
||||
|
||||
std::shared_ptr<Nz::ComputePipeline> 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<Nz::Texture> 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;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
target("ComputeParticlesTest")
|
||||
add_deps("NazaraRenderer")
|
||||
add_files("main.cpp")
|
||||
Loading…
Reference in New Issue