diff --git a/Assets/Shaders/ParticleEmit_CS.glsl b/Assets/Shaders/ParticleEmit_CS.glsl new file mode 100644 index 00000000..421e2ce1 --- /dev/null +++ b/Assets/Shaders/ParticleEmit_CS.glsl @@ -0,0 +1,117 @@ +#version 450 + +layout(local_size_x = 128) in; + +struct EmitterParameters +{ + vec4 angularMin; + vec4 angularMax; + vec4 lifeAndSizeRange; // min life, max life, min size, max size +} + +struct ParticleData +{ + vec4 position; + vec4 rotation; + vec4 velocity; + vec4 acceleration; + vec4 scaleAndDecay; + float life; + uint textureIndex; +} + +struct GenericData +{ + //! Delta time + float dt; + + //! Elapsed time of the application + float elapsedTime; + + //! Viewport width of the scene (excluding imgui, that means smaller than window) + uint viewportWidth; + + //! Ditto but for height + uint viewportHeight; +}; + +layout (set = 0, binding = 0) uniform GenericDataBuffer +{ + GenericData data; +} genericDataBuffer; + +layout (std430, set = 2, binding = 0) readonly buffer EmitterBuffer +{ + EmitterParameters data; +} emitterParams; + +layout (std430, set = 2, binding = 1) coherent restrict buffer ParticlesInputBuffer +{ + ParticleData data[]; +} inputParticles; + +// output buffer not needed +// layout (std430, set = 2, binding = 2) coherent restrict buffer ParticlesOutputBuffer +// { +// ParticleData data[]; +// } outputParticles; + +layout (std430, set = 2, binding = 3) coherent restrict buffer ParticlesFreelistBuffer +{ + int freeCount; + int freeIndices[]; + +} freelist; + + +// push constants +layout(std140, push_constant) uniform EmitterPushConstant +{ + vec4 emitterPosition; + uint emissionCount; + +} emitterPushConstant; + +uint pcg_hash(uint seed) +{ + uint state = seed * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +// Used to advance the PCG state. +uint rand_pcg(inout uint rng_state) +{ + uint state = rng_state; + rng_state = rng_state * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +// Advances the prng state and returns the corresponding random float. +float rand(inout uint state) +{ + uint x = rand_pcg(state); + state = x; + return float(x)*uintBitsToFloat(0x2f800004u); +} + +void main() +{ + uint emitterInvocationIndex = gl_GlobalInvocationID.x; + + if (emitterInvocationIndex >= emitterPushConstant.emissionCount) + return; + + int freelistIndex = atomicAdd (freelist.freeCount, -1) - 1; + if (freelistIndex < 0) + atomicAdd (freelist.freeCount, 1); + + ParticleData particle; + + int index = freelist.freeIndices[freelistIndex]; + particle.position = emitterPosition; + particle.life = emitterParams.10.0f; + + particles[index] = particle; +} \ No newline at end of file diff --git a/Assets/Shaders/ParticleUpdate_CS.glsl b/Assets/Shaders/ParticleUpdate_CS.glsl new file mode 100644 index 00000000..7649ee63 --- /dev/null +++ b/Assets/Shaders/ParticleUpdate_CS.glsl @@ -0,0 +1,141 @@ +#version 450 + +layout(local_size_x = 128) in; + +struct DrawArraysIndirectArgs +{ + uint count; + uint instanceCount; + uint first; + uint baseInstance; +}; + +struct ParticleData +{ + vec4 position; + vec4 rotation; + vec4 velocity; + vec4 acceleration; + vec4 scaleAndDecay; + float life; + uint textureIndex; +} + +struct GenericData +{ + //! Delta time + float dt; + + //! Elapsed time of the application + float elapsedTime; + + //! Viewport width of the scene (excluding imgui, that means smaller than window) + uint viewportWidth; + + //! Ditto but for height + uint viewportHeight; +}; + +layout(set = 1, binding = 0) uniform CameraData +{ + vec4 position; + mat4 vpMat; + mat4 viewMat; + mat4 projMat; +} cameraData; + + +layout (set = 0, binding = 0) uniform GenericDataBuffer +{ + GenericData data; +} genericDataBuffer; + +layout (std430, set = 2, binding = 1) coherent restrict readonly buffer ParticlesInputBuffer +{ + ParticleData data[]; +} inputParticles; + +// output buffer not needed +layout (std430, set = 2, binding = 2) coherent restrict buffer ParticlesOutputBuffer +{ + ParticleData data[]; +} outputParticles; + +layout (std430, set = 2, binding = 3) coherent restrict buffer ParticlesFreelistBuffer +{ + int freeCount; + int freeIndices[]; + +} freelist; + +layout (std430, set = 2, binding = 4) coherent restrict buffer IndicesData +{ + uint indices[]; +}; + +layout (std140, set = 2, binding = 5) coherent restrict uniform IndirectDrawArgs +{ + DrawArraysIndirectArgs indirectArgs; +}; + +// push constants +layout(std140, push_constant) uniform EmitterPushConstant +{ + vec4 emitterPosition; + uint emissionCount; + +} emitterPushConstant; + +uint pcg_hash(uint seed) +{ + uint state = seed * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +// Used to advance the PCG state. +uint rand_pcg(inout uint rng_state) +{ + uint state = rng_state; + rng_state = rng_state * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +// Advances the prng state and returns the corresponding random float. +float rand(inout uint state) +{ + uint x = rand_pcg(state); + state = x; + return float(x)*uintBitsToFloat(0x2f800004u); +} + +void main() +{ + uint index = gl_GlobalInvocationID.x; + + ParticleData particle = inputParticles.data[index]; + + if (particle.lifetime > 0.0f) + { + // particle.position += particle.velocity * dt; + + // particle.lifetime -= dt; + // particle.size -= 1.2f * dt; + // particle.color += 1.0f * dt; + + if (particle.lifetime < 0.0f || particle.size < 0.0f) + { + particle.lifetime = 0.0f; + particle.position.x = 99999.0f; + + outputParticles.data[index] = particle; + freelist.freeIndices[atomicAdd(freelist.freeCount, 1)] = int (index); + return; + } + + outputParticles.data[index] = particle; + uint drawIndex = atomicAdd (indirectArgs.instanceCount, 1); + indices[drawIndex] = index; + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsPredefinedData.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsPredefinedData.cpp index 2613c832..a15687cb 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsPredefinedData.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsPredefinedData.cpp @@ -64,6 +64,7 @@ namespace SHADE ({ {SHPredefinedDescriptorTypes::STATIC_DATA, 0}, {SHPredefinedDescriptorTypes::CAMERA, 1}, + {SHPredefinedDescriptorTypes::PARTICLES, 2}, }); } @@ -200,6 +201,73 @@ namespace SHADE Handle materialBoneDataPerInstanceLayout = logicalDevice->CreateDescriptorSetLayout({ materialDataBinding, boneDataBinding }); SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, materialBoneDataPerInstanceLayout->GetVkHandle(), "[Descriptor Set Layout] Material and Bone Globals"); + // particle emitter binding + SHVkDescriptorSetLayout::Binding emitterDataBinding + { + .Type = vk::DescriptorType::eStorageBufferDynamic, + .Stage = vk::ShaderStageFlagBits::eCompute, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::PARTICLE_EMITTER_DATA, + .DescriptorCount = 1, + }; + + // particle input binding + SHVkDescriptorSetLayout::Binding particleInputDataBinding + { + .Type = vk::DescriptorType::eStorageBufferDynamic, + .Stage = vk::ShaderStageFlagBits::eCompute, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::PARTICLE_INPUT_DATA, + .DescriptorCount = 1, + }; + + // particle output binding + SHVkDescriptorSetLayout::Binding particleOutputDataBinding + { + .Type = vk::DescriptorType::eStorageBufferDynamic, + .Stage = vk::ShaderStageFlagBits::eCompute | vk::ShaderStageFlagBits::eVertex, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::PARTICLE_OUTPUT_DATA, + .DescriptorCount = 1, + }; + + // particle freelist binding + SHVkDescriptorSetLayout::Binding particleFreelistBinding + { + .Type = vk::DescriptorType::eStorageBuffer, // non dynamic because we only need 1 copy for all frames + .Stage = vk::ShaderStageFlagBits::eCompute, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::PARTICLE_FREELIST_DATA, + .DescriptorCount = 1, + }; + + // particle indices binding + SHVkDescriptorSetLayout::Binding particleIndicesBinding + { + .Type = vk::DescriptorType::eStorageBufferDynamic, // dynamic because we have multiple copies. + .Stage = vk::ShaderStageFlagBits::eCompute | vk::ShaderStageFlagBits::eVertex, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::PARTICLE_INDICES_DATA, + .DescriptorCount = 1, + }; + + // particle draw call binding + SHVkDescriptorSetLayout::Binding particleDrawDataBinding + { + .Type = vk::DescriptorType::eUniformBufferDynamic, // UBO (Because lesser data), dynamic (1 set for each frame) + .Stage = vk::ShaderStageFlagBits::eCompute, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::PARTICLE_DRAW_DATA, + .DescriptorCount = 1, + }; + + Handle particleDescSetLayout = logicalDevice->CreateDescriptorSetLayout( + { + emitterDataBinding, + particleInputDataBinding, + particleOutputDataBinding, + particleFreelistBinding, + particleIndicesBinding, + particleDrawDataBinding + }); + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, particleDescSetLayout->GetVkHandle(), "[Descriptor Set Layout] Particle System Data"); + + + predefinedLayouts.push_back(staticGlobalLayout); predefinedLayouts.push_back(lightDataDescSetLayout); predefinedLayouts.push_back(cameraDataGlobalLayout); @@ -207,6 +275,7 @@ namespace SHADE predefinedLayouts.push_back(fontDataDescSetLayout); predefinedLayouts.push_back(shadowMapDescLayout); predefinedLayouts.push_back(materialBoneDataPerInstanceLayout); + predefinedLayouts.push_back(particleDescSetLayout); perSystemData[SHUtilities::ConvertEnum(SystemType::BATCHING)].descSetLayouts = GetPredefinedDescSetLayouts ( @@ -245,7 +314,8 @@ namespace SHADE perSystemData[SHUtilities::ConvertEnum(SystemType::PARTICLE_RENEDERING)].descSetLayouts = GetPredefinedDescSetLayouts ( SHGraphicsPredefinedData::PredefinedDescSetLayoutTypes::STATIC_DATA | - SHGraphicsPredefinedData::PredefinedDescSetLayoutTypes::CAMERA + SHGraphicsPredefinedData::PredefinedDescSetLayoutTypes::CAMERA | + SHGraphicsPredefinedData::PredefinedDescSetLayoutTypes::PARTICLES ); } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsPredefinedData.h b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsPredefinedData.h index 80af6c30..4fbf06c1 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsPredefinedData.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsPredefinedData.h @@ -29,7 +29,8 @@ namespace SHADE MATERIALS = 0b00001000, FONT = 0b00010000, SHADOW = 0b00100000, - MATERIAL_AND_BONES = 0b01000000 + MATERIAL_AND_BONES = 0b01000000, + PARTICLES = 0b10000000 }; enum class SystemType diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedDescriptorTypes.h b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedDescriptorTypes.h index 723a3c5a..0b6f0c2f 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedDescriptorTypes.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedDescriptorTypes.h @@ -16,5 +16,6 @@ namespace SHADE FONT, RENDER_GRAPH_NODE_COMPUTE_RESOURCE, RENDER_GRAPH_RESOURCE, + PARTICLES, }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h index 54b02608..af810dae 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h @@ -229,6 +229,61 @@ namespace SHADE */ /***************************************************************************/ static constexpr uint32_t BONE_MATRIX_DATA = 1; + + /***************************************************************************/ + /*! + \brief + Descriptor set binding for particle emitter data. + + */ + /***************************************************************************/ + static constexpr uint32_t PARTICLE_EMITTER_DATA = 0; + + /***************************************************************************/ + /*! + \brief + Descriptor set binding for input particle data. + + */ + /***************************************************************************/ + static constexpr uint32_t PARTICLE_INPUT_DATA = 1; + + /***************************************************************************/ + /*! + \brief + Descriptor set binding for output particle data. + + */ + /***************************************************************************/ + static constexpr uint32_t PARTICLE_OUTPUT_DATA = 2; + + /***************************************************************************/ + /*! + \brief + Descriptor set binding for particle freelist data. + + */ + /***************************************************************************/ + static constexpr uint32_t PARTICLE_FREELIST_DATA = 3; + + /***************************************************************************/ + /*! + \brief + Descriptor set binding for particle indices data. + + */ + /***************************************************************************/ + static constexpr uint32_t PARTICLE_INDICES_DATA = 4; + + /***************************************************************************/ + /*! + \brief + Descriptor set binding for bone matrix data. + + */ + /***************************************************************************/ + static constexpr uint32_t PARTICLE_DRAW_DATA = 5; + }; struct VertexBufferBindings diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleEmitterComponent.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleEmitterComponent.cpp index 1c7e5a9c..e991e8e1 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleEmitterComponent.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleEmitterComponent.cpp @@ -7,7 +7,6 @@ namespace SHADE { timeBeforeEmission = emissionInterval; - // initialize all buffers here } void SHParticleEmitterComponent::OnDestroy(void) @@ -17,7 +16,7 @@ namespace SHADE void SHParticleEmitterComponent::Emit(void) noexcept { - + toEmit = true; } } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleEmitterComponent.h b/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleEmitterComponent.h index 7f6787aa..30f7f107 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleEmitterComponent.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleEmitterComponent.h @@ -3,10 +3,13 @@ #include "Resource/SHHandle.h" #include "Math/Vector/SHVec4.h" #include "ECS_Base/Components/SHComponent.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" namespace SHADE { class SHVkBuffer; + class SHVkDescriptorSetGroup; + class SHVkDescriptorSetLayout; class SHParticleEmitterComponent : public SHComponent { @@ -37,15 +40,12 @@ namespace SHADE //! Acceleration of the particle SHVec4 acceleration; - //! Scale of the texture - float scale; + //! x scale, x scale decay, y scale and y scale decay + SHVec4 scaleAndDecay; //! Life of the particle float life; - //! life decay rate - float lifeDecayRate; - //! Texture into the desc array that the particle is using uint32_t textureIndex; }; @@ -53,6 +53,9 @@ namespace SHADE //! Max number of particles of this emitter uint32_t maxParticles; + //! num bytes of all particles in 1 chunk (1 frame) + uint32_t chunkSize; + //! emission count per emit uint32_t emissionCount; @@ -69,20 +72,33 @@ namespace SHADE Handle particleData; //! Freelist data - Handle freelistData; + Handle freelistData; - //! Freelist data + //! Indices data Handle indicesData; - //! Freelist data + //! draw call data Handle drawCallData; + //! We need more descriptor sets because the operations on a frame's particle data needs to rely on the previous frame's. Each set + //! will contain 2 bindings that point to 2 buffers (input and output). + Handle particleDescriptorSet; + //! Emitter's data on the CPU side. To be copied to GPU. - GPUEmitterStruct emitterDataCPU; + GPUEmitterStruct cpuEmitterData; //! If passive, emitter emits particles based on timer above. bool isPassive; + //! Have the particle system initialize variables in this component when this is false + bool initialized; + + //! If true, particle system will emit particles in Run function. Sets to false every frame. + bool toEmit; + + //! For all the dynamic SSBOs in the descriptor set + std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS> dynamicOffsets{}; + public: void OnCreate(void) override final; void OnDestroy(void) override final; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleSubSustem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleSubSustem.cpp index 7e2f5115..e1d33753 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleSubSustem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleSubSustem.cpp @@ -5,12 +5,147 @@ #include "Graphics/Devices/SHVkLogicalDevice.h" #include "Graphics/MiddleEnd/Particles/SHParticleEmitterComponent.h" #include "ECS_Base/Managers/SHComponentManager.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" +#include "Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h" +#include "Math/Transform/SHTransformComponent.h" +#include "Graphics/Buffers/SHVkBuffer.h" namespace SHADE { - - void SHParticleSubSystem::Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle VS, Handle FS, Handle emitCS, Handle defaultUpdateCS) noexcept + + void SHParticleSubSystem::InitializeComponent(SHParticleEmitterComponent& comp) noexcept { + uint32_t emitterStructAligned = logicalDevice->PadSSBOSize(sizeof (SHParticleEmitterComponent::GPUEmitterStruct)); + uint32_t particleStructAligned = logicalDevice->PadUBOSize(sizeof (SHParticleEmitterComponent::GPUParticleStruct)); + uint32_t sizeofIndirectCmd = static_cast(sizeof(vk::DrawIndirectCommand)); + uint32_t sizeofUint = static_cast(sizeof(uint32_t)); + + // TODO: temporary only. + static constexpr uint32_t NUM_PARTICLES = 500; + comp.maxParticles = NUM_PARTICLES; + + // offset into the buffer for input and output + uint32_t const PARTICLE_FRAME_CHUNK_SIZE = (particleStructAligned * comp.maxParticles); + + // Buffer Initialization + { + // count, value + std::vector freelistInit(comp.maxParticles + 1, 0); + freelistInit[0] = comp.maxParticles; + + // Particle emitter buffer. Multiple copies, Host-visible mapped. We want multiple copies because we'll be writing to it from the CPU. We don't want to do that while the GPU + // is using it during compute operations so we write to another portion. + comp.emitterData = logicalDevice->CreateBuffer(SHGraphicsConstants::NUM_FRAME_BUFFERS * emitterStructAligned, nullptr, 0, vk::BufferUsageFlagBits::eStorageBuffer, VMA_MEMORY_USAGE_AUTO, VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT); + + // buffer for particle data: pure GPU memory, no transfers, no flags. We want to triple buffer this so that we can submit work to the GPU + // without having to wait for rendering to finish reading the data + comp.particleData = logicalDevice->CreateBuffer(SHGraphicsConstants::NUM_FRAME_BUFFERS * particleStructAligned * NUM_PARTICLES, nullptr, 0, vk::BufferUsageFlagBits::eStorageBuffer, VMA_MEMORY_USAGE_AUTO, {}); + + // Buffer for freelist data. 1 copy only, host-visible mapped. We only need 1 copy because it is only required in compute. If it was used or read in another + // stage we would need more copies. + comp.freelistData = logicalDevice->CreateBuffer(sizeofUint * (comp.maxParticles + 1), freelistInit.data(), sizeofUint * (comp.maxParticles + 1), vk::BufferUsageFlagBits::eStorageBuffer, VMA_MEMORY_USAGE_AUTO, VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT); + + // Buffer for indices. NUM_FRAME_BUFFERS copies since it's used in rendering. host-visible mapped. + comp.indicesData = logicalDevice->CreateBuffer(sizeofUint * comp.maxParticles, nullptr, 0, vk::BufferUsageFlagBits::eStorageBuffer, VMA_MEMORY_USAGE_AUTO, VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT); + + // Draw call data will not be tampered with after this initialization except for one variable: instanceCount, which will be modified from compute shader + std::array indirectCommands{}; + for (auto& cmd : indirectCommands) + { + cmd.vertexCount = 4; + cmd.firstVertex = 0; + cmd.firstInstance = 0; + cmd.instanceCount = 0; + } + + // buffer to store draw call data. Non-indexed, host-visible mapped, triple buffered. + comp.drawCallData = logicalDevice->CreateBuffer(SHGraphicsConstants::NUM_FRAME_BUFFERS * sizeofIndirectCmd, indirectCommands.data(), SHGraphicsConstants::NUM_FRAME_BUFFERS * sizeofIndirectCmd, vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eIndirectBuffer, VMA_MEMORY_USAGE_AUTO, VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT); + + } + + // Descriptor set initialization + { + // Get particle desc set layout + auto descSetLayout = SHGraphicsPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsPredefinedData::PredefinedDescSetLayoutTypes::PARTICLES); + + // Since we are populating the set as is, the set index will be 0 + static constexpr uint32_t PARTICLE_DATA_SET_INDEX = 0; + + // Variable desc counts, all ignored anyway (This is required but its a dumb interface. You can only blame yourself, Brandon. ) + std::vector const VARIABLE_COUNTS = {0u,0u,0u,0u,0u}; + + // allocate new desc set + comp.particleDescriptorSet = descPool->Allocate(descSetLayout, VARIABLE_COUNTS); + + // convenience handle + auto set = comp.particleDescriptorSet; + + // After buffers are created, we want to populate all bindings(6) with the buffers + set->ModifyWriteDescBuffer(PARTICLE_DATA_SET_INDEX, SHGraphicsConstants::DescriptorSetBindings::PARTICLE_EMITTER_DATA, { &comp.emitterData, 1 }, 0, emitterStructAligned); + set->ModifyWriteDescBuffer(PARTICLE_DATA_SET_INDEX, SHGraphicsConstants::DescriptorSetBindings::PARTICLE_INPUT_DATA, { &comp.particleData, 1 }, 0, PARTICLE_FRAME_CHUNK_SIZE); // input and output will be th same until we bind using dynamic offsets + set->ModifyWriteDescBuffer(PARTICLE_DATA_SET_INDEX, SHGraphicsConstants::DescriptorSetBindings::PARTICLE_OUTPUT_DATA, { &comp.particleData, 1 }, 0, PARTICLE_FRAME_CHUNK_SIZE); + set->ModifyWriteDescBuffer(PARTICLE_DATA_SET_INDEX, SHGraphicsConstants::DescriptorSetBindings::PARTICLE_FREELIST_DATA, { &comp.freelistData, 1 }, 0, sizeofUint * (comp.maxParticles + 1)); + set->ModifyWriteDescBuffer(PARTICLE_DATA_SET_INDEX, SHGraphicsConstants::DescriptorSetBindings::PARTICLE_INDICES_DATA, { &comp.indicesData, 1 }, 0, sizeofUint * (comp.maxParticles)); + set->ModifyWriteDescBuffer(PARTICLE_DATA_SET_INDEX, SHGraphicsConstants::DescriptorSetBindings::PARTICLE_DRAW_DATA, { &comp.drawCallData, 1 }, 0, sizeofIndirectCmd); + } + + comp.initialized = true; + comp.timeBeforeEmission = comp.emissionInterval; + comp.toEmit = false; + comp.chunkSize = PARTICLE_FRAME_CHUNK_SIZE; + + for (uint32_t i = 0; i < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++i) + { + auto& offsets = comp.dynamicOffsets[i]; + + uint32_t inputOffset = PARTICLE_FRAME_CHUNK_SIZE * ((i + SHGraphicsConstants::NUM_FRAME_BUFFERS - 1) % SHGraphicsConstants::NUM_FRAME_BUFFERS); // take previous frame's data + uint32_t outputOffset = PARTICLE_FRAME_CHUNK_SIZE * i; + + // In the order of: + // 1. Emitter data + // 2. Particle input + // 3. Particle output + // 4. Particle draw data + offsets[DYOFF_INDEX_EMITTER] = i * emitterStructAligned; + offsets[DYOFF_INDEX_PARTICLE_INPUT] = inputOffset; + offsets[DYOFF_INDEX_PARTICLE_OUTPUT] = outputOffset; + offsets[DYOFF_INDEX_INDICES_DATA] = i * sizeofIndirectCmd; + offsets[DYOFF_INDEX_DRAW_DATA] = i * sizeofUint * comp.maxParticles; + } + } + + void SHParticleSubSystem::EmitComponent(Handle cmdBuffer, SHParticleEmitterComponent& comp, uint32_t frameIndex) noexcept + { + auto const& mappings = SHGraphicsPredefinedData::GetMappings(SHGraphicsPredefinedData::SystemType::PARTICLE_RENEDERING); + auto* transform = SHComponentManager::GetComponent(comp.GetEID()); + + // bind the descriptor sets required for emitting particles + cmdBuffer->BindDescriptorSet(comp.particleDescriptorSet, SH_PIPELINE_TYPE::COMPUTE, mappings.at(SHPredefinedDescriptorTypes::PARTICLES), comp.dynamicOffsets[frameIndex]); + + cmdBuffer->SetPushConstantVariable("EmitterPushConstant.emitterPosition", transform->GetWorldPosition(), SH_PIPELINE_TYPE::COMPUTE); + cmdBuffer->SetPushConstantVariable("EmitterPushConstant.emissionCount", comp.emissionCount, SH_PIPELINE_TYPE::COMPUTE); + + // emit particles + cmdBuffer->ComputeDispatch((comp.emissionCount / EMITTER_WORKGROUP_SIZE) + 1, 1, 1); + } + + void SHParticleSubSystem::UpdateCompoennt(Handle cmdBuffer, SHParticleEmitterComponent& comp, uint32_t frameIndex) noexcept + { + auto const& mappings = SHGraphicsPredefinedData::GetMappings(SHGraphicsPredefinedData::SystemType::PARTICLE_RENEDERING); + + // bind the descriptor sets required for emitting particles + cmdBuffer->BindDescriptorSet(comp.particleDescriptorSet, SH_PIPELINE_TYPE::COMPUTE, mappings.at(SHPredefinedDescriptorTypes::PARTICLES), comp.dynamicOffsets[frameIndex]); + + // dispatch the compute shaders to update particles + cmdBuffer->ComputeDispatch((comp.maxParticles / EMITTER_WORKGROUP_SIZE) + 1, 1, 1); + } + + void SHParticleSubSystem::Init(Handle device, Handle inDescPool, Handle compatibleRenderpass, Handle subpass, Handle VS, Handle FS, Handle emitCS, Handle defaultUpdateCS) noexcept + { + descPool = inDescPool; + logicalDevice = device; + /*-----------------------------------------------------------------------*/ /* INITIALIZE ALL PIPELINES */ /*-----------------------------------------------------------------------*/ @@ -41,6 +176,7 @@ namespace SHADE defaultUpdatePipelineData.pipelineLayout = logicalDevice->CreatePipelineLayout(defaultUpdatePlParams); defaultUpdatePipelineData.pipeline = logicalDevice->CreateComputePipeline(defaultUpdatePipelineData.pipelineLayout); + /*-----------------------------------------------------------------------*/ /* OTHER INITIALIZATION */ /*-----------------------------------------------------------------------*/ @@ -50,10 +186,35 @@ namespace SHADE void SHParticleSubSystem::Run(Handle cmdBuffer, uint32_t frameIndex, float dt) noexcept { auto& emitters = SHComponentManager::GetDense(); + auto const& mappings = SHGraphicsPredefinedData::GetMappings(SHGraphicsPredefinedData::SystemType::PARTICLE_RENEDERING); + // Get offset into GPU emitter data (for updating) + uint32_t emitterDataOffset = frameIndex * sizeof (SHParticleEmitterComponent::GPUEmitterStruct); + + //! Barriers to make sure emitting shader is done completely before update is run. + //! Every emitter will have its own barrier. + std::vector preUpdateBarriers{}; + preUpdateBarriers.resize(emitters.size()); + + /*-----------------------------------------------------------------------*/ + /* BEGIN EMITTING PARTICES */ + /*-----------------------------------------------------------------------*/ + + // TODO: Might need to issue a barrier here for input particle data + + // bind the pipeline for emitting particles + cmdBuffer->BindPipeline(emittingPipelineData.pipeline); + + // Generic data + SHGlobalDescriptorSets::BindGenericAndTextureData(logicalDevice, cmdBuffer, SH_PIPELINE_TYPE::COMPUTE, mappings.at(SHPredefinedDescriptorTypes::STATIC_DATA), frameIndex); + + uint32_t i = 0; for (auto& emitter : emitters) { - // Emit emitters here if there are ready to be emitted + if (!emitter.initialized) + InitializeComponent(emitter); + + // Set emitter emit flag to true here if there are ready to be emitted if (emitter.isPassive) { // decrement emission timer @@ -65,15 +226,60 @@ namespace SHADE // reset timer emitter.timeBeforeEmission = emitter.emissionInterval; - // Call dispatch here to emit particles + // Emit later + emitter.toEmit = true; } } + + if (emitter.toEmit) // take note that if emitter is not passive, this can also be set to true outside of this function + { + // Copy data to host visible buffer of emitter + emitter.emitterData->WriteToMemory (&emitter.cpuEmitterData, sizeof (SHParticleEmitterComponent::GPUEmitterStruct), 0, emitterDataOffset); + + // Call emit function here + EmitComponent(cmdBuffer, emitter, frameIndex); + } + + // make new barrier on stack... + vk::BufferMemoryBarrier barrier + { + .srcAccessMask = vk::AccessFlagBits::eShaderWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead, + .buffer = emitter.particleData->GetVkBuffer(), + .offset = emitter.dynamicOffsets[frameIndex][DYOFF_INDEX_PARTICLE_INPUT], + .size = emitter.chunkSize + }; + + // ...copy assign barrier on heap + preUpdateBarriers[i] = barrier; + + emitter.toEmit = false; + ++i; } + + // issue the barrier to wait + cmdBuffer->PipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eComputeShader, {}, {}, preUpdateBarriers, {}); + + /*-----------------------------------------------------------------------*/ + /* EMITTING PARTICLES DONE, BEGIN UPDATES.... */ + /*-----------------------------------------------------------------------*/ + + // bind the pipeline for updating + cmdBuffer->BindPipeline(defaultUpdatePipelineData.pipeline); + + for (auto& emitter : emitters) + { + UpdateCompoennt(cmdBuffer, emitter, frameIndex); + } + + } void SHParticleSubSystem::Render(Handle cmdBuffer, Handle renderer, uint32_t frameIndex) noexcept { auto& emitters = SHComponentManager::GetDense(); + + // TODO: Issue barrier for output particle data. Semaphore should also be issued outside in SHGraphicsSystem } void SHParticleSubSystem::Exit(void) noexcept diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleSubSustem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleSubSustem.h index 44e34237..61bd9af4 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleSubSustem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Particles/SHParticleSubSustem.h @@ -19,12 +19,21 @@ namespace SHADE class SHSubpass; class SHVkShaderModule; class SHRenderer; + class SHParticleEmitterComponent; class SHParticleSubSystem { private: + static constexpr uint32_t EMITTER_WORKGROUP_SIZE = 128; + static constexpr uint32_t DYOFF_INDEX_EMITTER = 0; + static constexpr uint32_t DYOFF_INDEX_PARTICLE_INPUT = 1; + static constexpr uint32_t DYOFF_INDEX_PARTICLE_OUTPUT = 2; + static constexpr uint32_t DYOFF_INDEX_INDICES_DATA = 3; + static constexpr uint32_t DYOFF_INDEX_DRAW_DATA = 4; + + // To hold data for a pipeline and pipeline layout. // We want this here because particles require 3 pipeline sets: // - Rendering @@ -39,6 +48,18 @@ namespace SHADE Handle pipelineLayout; }; +#if 0 // not used, mainly for convenience to show what shaders use + // Push constant data for emitters + struct EmitterShaderPC + { + //! Emitter position + SHVec4 emitterPosition; + + //! emission count for 1 single emission + uint32_t emissionCount; + }; +#endif + //! Logical device for creation and destruction Handle logicalDevice; @@ -51,9 +72,16 @@ namespace SHADE //! Pipeline data for updating particles PipelineData defaultUpdatePipelineData; + //! Desc pool for particle component desc set allocation + Handle descPool; + + + void InitializeComponent (SHParticleEmitterComponent& comp) noexcept; + void EmitComponent (Handle cmdBuffer, SHParticleEmitterComponent& comp, uint32_t frameIndex) noexcept; + void UpdateCompoennt (Handle cmdBuffer, SHParticleEmitterComponent& comp, uint32_t frameIndex) noexcept; public: - void Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle VS, Handle FS, Handle emitCS, Handle defaultUpdateCS) noexcept; + void Init(Handle device, Handle inDescPool, Handle compatibleRenderpass, Handle subpass, Handle VS, Handle FS, Handle emitCS, Handle defaultUpdateCS) noexcept; void Run(Handle cmdBuffer, uint32_t frameIndex, float dt) noexcept;