diff --git a/res/shaders/DepthCullingTask.slang b/res/shaders/DepthCullingTask.slang index e002949..f8d3242 100644 --- a/res/shaders/DepthCullingTask.slang +++ b/res/shaders/DepthCullingTask.slang @@ -11,6 +11,7 @@ groupshared float4x4 modelViewProjection; struct DepthData { Texture2D texture; + RWStructuredBuffer buffer; } ParameterBlock pDepthAttachment; @@ -21,7 +22,6 @@ void taskMain( uint threadID: SV_GroupThreadID, uint groupID: SV_GroupID, ) { - const uint mipLevels = uint(log2(max(pViewParams.screenDimensions.x, pViewParams.screenDimensions.y))); if (threadID == 0) { head = 0; @@ -57,8 +57,9 @@ void taskMain( // if the meshlet is outside of the frustum, we skip it since we cant do depth culling anyways if(meshlet.bounding.insideFrustum(viewFrustum)) { + uint2 mipDimensions = uint2((uint(pViewParams.screenDimensions.x) + BLOCK_SIZE - 1) / BLOCK_SIZE, (uint(pViewParams.screenDimensions.y) + BLOCK_SIZE - 1) / BLOCK_SIZE); // now we calculate what mip level we need to only sample up to 4 texels covering the entire meshlet - uint2 screenCornerMin = uint2(uint(pViewParams.screenDimensions.x), uint(pViewParams.screenDimensions.y)); + uint2 screenCornerMin = mipDimensions; uint2 screenCornerMax = uint2(0, 0); // we use reverse depth, so higher values are closer float maxDepth = 0; @@ -75,26 +76,27 @@ void taskMain( for(uint i = 0; i < 8; ++i) { float4 clipCorner = mul(modelViewProjection, corners[i]); - float4 screenCorner = clipToScreen(clipCorner); + float4 screenCorner = clipToScreen(clipCorner) / BLOCK_SIZE; screenCornerMin = uint2(min(screenCornerMin.x, uint(screenCorner.x)), min(screenCornerMin.y, uint(screenCorner.y))); screenCornerMax = uint2(max(screenCornerMax.x, uint(screenCorner.x)), max(screenCornerMax.y, uint(screenCorner.y))); maxDepth = max(maxDepth, screenCorner.z); } } - uint mipLevel = 0; + uint mipOffset = 0; // in theory this wouldnt work if no corner was in screen, as min would be greater that max, however we verified that with view culling while(screenCornerMax.x - screenCornerMin.x > 1 || screenCornerMax.y - screenCornerMin.y > 1) { - mipLevel++; + mipOffset += mipDimensions.x * mipDimensions.y; + mipDimensions = uint2(mipDimensions.x + 1, mipDimensions.y + 1) / 2; screenCornerMin /= 2; screenCornerMax /= 2; } - // now we sample 4 texels from the depth at the calculated mip level, this should give us the - float d1 = pDepthAttachment.texture.Load(int3(screenCornerMin.x, screenCornerMin.y, mipLevel)).r; - float d2 = pDepthAttachment.texture.Load(int3(screenCornerMin.x, screenCornerMax.y, mipLevel)).r; - float d3 = pDepthAttachment.texture.Load(int3(screenCornerMax.x, screenCornerMin.y, mipLevel)).r; - float d4 = pDepthAttachment.texture.Load(int3(screenCornerMax.x, screenCornerMax.y, mipLevel)).r; + // now we sample 4 texels from the depth at the calculated mip level, this should give us the screen extent of the meshlet + float d1 = pDepthAttachment.buffer[mipOffset + (screenCornerMin.y * mipDimensions.x) + screenCornerMin.x]; + float d2 = pDepthAttachment.buffer[mipOffset + (screenCornerMin.y * mipDimensions.x) + screenCornerMax.x]; + float d3 = pDepthAttachment.buffer[mipOffset + (screenCornerMax.y * mipDimensions.x) + screenCornerMin.x]; + float d4 = pDepthAttachment.buffer[mipOffset + (screenCornerMax.y * mipDimensions.x) + screenCornerMax.x]; // we want to check if the minimum depth (the value farthest away) is smaller than the maximum bounding box depth // otherwise, there is no way for the meshlet to be visible diff --git a/res/shaders/DepthMipGen.slang b/res/shaders/DepthMipGen.slang new file mode 100644 index 0000000..e1a3241 --- /dev/null +++ b/res/shaders/DepthMipGen.slang @@ -0,0 +1,82 @@ +import Common; + +struct DepthData +{ + Texture2D texture; + RWStructuredBuffer buffer; +} +ParameterBlock pDepthAttachment; + +struct MipParam +{ + uint srcMipOffset; + uint dstMipOffset; + uint2 srcMipDim; + uint2 dstMipDim; +} +layout(push_constants) +ConstantBuffer pMipParam; + +float getSrcDepth(uint2 pos) +{ + return pDepthAttachment.buffer[pMipParam.srcMipOffset + pos.x + (pos.y * pMipParam.srcMipDim.x)]; +} + +void setDstDepth(uint2 pos, float depth) +{ + pDepthAttachment.buffer[pMipParam.dstMipOffset + pos.x + (pos.y * pMipParam.dstMipDim.x)] = depth; +} + +[numthreads(BLOCK_SIZE, BLOCK_SIZE, 1)] +[shader("compute")] +void reduceLevel( + uint3 threadID: SV_GroupThreadID, + uint3 groupID: SV_GroupID, +){ + uint2 minCoords = (groupID.xy * BLOCK_SIZE + threadID.xy) * 2; + + if(minCoords.x >= pMipParam.srcMipDim.x || minCoords.y >= pMipParam.srcMipDim.y) + { + return; + } + + uint2 maxCoords = uint2(min(minCoords.x + 1, pMipParam.srcMipDim.x - 1), min(minCoords.y + 1, pMipParam.srcMipDim.y - 1)); + + float d0 = getSrcDepth(uint2(minCoords.x, minCoords.y)); + float d1 = getSrcDepth(uint2(maxCoords.x, minCoords.y)); + float d2 = getSrcDepth(uint2(minCoords.x, maxCoords.y)); + float d3 = getSrcDepth(uint2(maxCoords.x, maxCoords.y)); + + float minDepth = min(min(d0, d1), min(d2, d3)); + setDstDepth(minCoords / 2, minDepth); +} + +groupshared uint uMinDepth; + +[numthreads(BLOCK_SIZE, BLOCK_SIZE, 1)] +[shader("compute")] +void initialReduce( + uint3 threadID: SV_GroupThreadID, + uint3 groupID: SV_GroupID, +) { + uint reducedWidth = uint(pViewParams.screenDimensions.x) / BLOCK_SIZE; + int2 groupOffset = groupID.xy * BLOCK_SIZE; + int2 texCoord = groupOffset + threadID.xy; + float fDepth = pDepthAttachment.texture.Load(int3(texCoord, 0)).r; + uint uDepth = asuint(fDepth); + if(groupID.x == 0 && groupID.y == 0) + { + uMinDepth = 0xffffffff; + } + GroupMemoryBarrierWithGroupSync(); + + InterlockedMin(uMinDepth, uDepth); + + GroupMemoryBarrierWithGroupSync(); + + float fMinDepth = asfloat(uMinDepth); + if(threadID.x == 0 && threadID.y == 0) + { + pDepthAttachment.buffer[groupID.x + (groupID.y * reducedWidth)] = fMinDepth; + } +} diff --git a/res/shaders/LightCulling.slang b/res/shaders/LightCulling.slang index 225868c..541847d 100644 --- a/res/shaders/LightCulling.slang +++ b/res/shaders/LightCulling.slang @@ -89,18 +89,18 @@ void cullLights(ComputeShaderInput in) float maxDepthWS = clipToWorld(float4(0, 0, fMaxDepth, 1)).z; float nearClipWS = clipToWorld(float4(0, 0, 0, 1)).z; - Plane minPlane = {float3(0, 0, -1), -minDepthWS}; + Plane maxPlane = {float3(0, 0, -1), -maxDepthWS}; for ( uint i = in.groupIndex; i < pLightEnv.numPointLights; i += BLOCK_SIZE * BLOCK_SIZE ) { PointLight light = pLightEnv.pointLights[i]; #ifdef LIGHT_CULLING - if(light.insideFrustum(groupFrustum, light.getPosition(), nearClipWS, maxDepthWS)) + if(light.insideFrustum(groupFrustum, light.getPosition(), nearClipWS, minDepthWS)) #endif { tAppendLight(i); #ifdef LIGHT_CULLING - if(!light.insidePlane(minPlane, light.getPosition())) + if(!light.insidePlane(maxPlane, light.getPosition())) #endif { oAppendLight(i); diff --git a/src/Engine/Graphics/RenderPass/DepthCullingPass.cpp b/src/Engine/Graphics/RenderPass/DepthCullingPass.cpp index 478f4df..29713a4 100644 --- a/src/Engine/Graphics/RenderPass/DepthCullingPass.cpp +++ b/src/Engine/Graphics/RenderPass/DepthCullingPass.cpp @@ -1,5 +1,6 @@ #include "DepthCullingPass.h" #include "Graphics/Shader.h" +#include using namespace Seele; @@ -7,25 +8,40 @@ extern bool usePositionOnly; extern bool useDepthCulling; DepthCullingPass::DepthCullingPass(Gfx::PGraphics graphics, PScene scene) : RenderPass(graphics, scene) { - depthTextureLayout = graphics->createDescriptorLayout("pDepthAttachment"); - depthTextureLayout->addDescriptorBinding(Gfx::DescriptorBinding{ + depthAttachmentLayout = graphics->createDescriptorLayout("pDepthAttachment"); + depthAttachmentLayout->addDescriptorBinding(Gfx::DescriptorBinding{ .binding = 0, .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - .shaderStages = Gfx::SE_SHADER_STAGE_TASK_BIT_EXT | Gfx::SE_SHADER_STAGE_MESH_BIT_EXT, + .shaderStages = Gfx::SE_SHADER_STAGE_TASK_BIT_EXT | Gfx::SE_SHADER_STAGE_MESH_BIT_EXT | Gfx::SE_SHADER_STAGE_COMPUTE_BIT, }); - depthTextureLayout->create(); + depthAttachmentLayout->addDescriptorBinding(Gfx::DescriptorBinding{ + .binding = 1, + .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .shaderStages = Gfx::SE_SHADER_STAGE_TASK_BIT_EXT | Gfx::SE_SHADER_STAGE_COMPUTE_BIT, + }); + depthAttachmentLayout->create(); - depthPrepassLayout = graphics->createPipelineLayout("DepthPrepassLayout"); - depthPrepassLayout->addDescriptorLayout(viewParamsLayout); - depthPrepassLayout->addDescriptorLayout(depthTextureLayout); - depthPrepassLayout->addPushConstants(Gfx::SePushConstantRange{ + depthCullingLayout = graphics->createPipelineLayout("DepthPrepassLayout"); + depthCullingLayout->addDescriptorLayout(viewParamsLayout); + depthCullingLayout->addDescriptorLayout(depthAttachmentLayout); + depthCullingLayout->addPushConstants(Gfx::SePushConstantRange{ .stageFlags = Gfx::SE_SHADER_STAGE_TASK_BIT_EXT | Gfx::SE_SHADER_STAGE_VERTEX_BIT, .offset = 0, .size = sizeof(VertexData::DrawCallOffsets), }); + + depthComputeLayout = graphics->createPipelineLayout("DepthComputeLayout"); + depthComputeLayout->addDescriptorLayout(viewParamsLayout); + depthComputeLayout->addDescriptorLayout(depthAttachmentLayout); + depthComputeLayout->addPushConstants(Gfx::SePushConstantRange{ + .stageFlags = Gfx::SE_SHADER_STAGE_COMPUTE_BIT, + .offset = 0, + .size = sizeof(MipParam), + }); + if (graphics->supportMeshShading()) { graphics->getShaderCompiler()->registerRenderPass("DepthPass", Gfx::PassConfig{ - .baseLayout = depthPrepassLayout, + .baseLayout = depthCullingLayout, .taskFile = "DepthCullingTask", .mainFile = "DepthCullingMesh", .fragmentFile = "VisibilityPass", @@ -37,7 +53,7 @@ DepthCullingPass::DepthCullingPass(Gfx::PGraphics graphics, PScene scene) : Rend }); } else { graphics->getShaderCompiler()->registerRenderPass("DepthPass", Gfx::PassConfig{ - .baseLayout = depthPrepassLayout, + .baseLayout = depthCullingLayout, .taskFile = "", .mainFile = "LegacyPass", .fragmentFile = "VisibilityPass", @@ -55,26 +71,49 @@ DepthCullingPass::~DepthCullingPass() {} void DepthCullingPass::beginFrame(const Component::Camera& cam) { RenderPass::beginFrame(cam); } void DepthCullingPass::render() { - depthAttachment.getTexture()->changeLayout(Gfx::SE_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + depthAttachment.getTexture()->changeLayout(Gfx::SE_IMAGE_LAYOUT_GENERAL, Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, Gfx::SE_ACCESS_TRANSFER_READ_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT); - depthMipTexture->changeLayout(Gfx::SE_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, Gfx::SE_ACCESS_SHADER_READ_BIT, - Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT, Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, - Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT); - graphics->copyTexture(depthAttachment.getTexture(), Gfx::PTexture2D(depthMipTexture)); - depthMipTexture->generateMipmaps(); + Gfx::PDescriptorSet set = depthAttachmentLayout->allocateDescriptorSet(); + set->updateTexture(0, Gfx::PTexture2D(depthAttachment.getTexture())); + set->updateBuffer(1, depthMipBuffer); + set->writeChanges(); + + Gfx::OComputeCommand computeCommand = graphics->createComputeCommand("DepthMipGenCommand"); + computeCommand->bindPipeline(depthInitialReduce); + computeCommand->bindDescriptor({viewParamsSet, set}); + UVector2 reduceDimensions = UVector2(viewport->getOwner()->getFramebufferWidth() + BLOCK_SIZE - 1, + viewport->getOwner()->getFramebufferHeight() + BLOCK_SIZE - 1) / + uint32(BLOCK_SIZE); + computeCommand->dispatch(reduceDimensions.x, reduceDimensions.y, 1); + + computeCommand->bindPipeline(depthMipGen); + computeCommand->bindDescriptor({viewParamsSet, set}); + + for (uint32 i = 0; i < mipOffsets.size() - 1; ++i) { + depthMipBuffer->pipelineBarrier(Gfx::SE_ACCESS_SHADER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT, + Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + MipParam params = { + .srcMipOffset = mipOffsets[i], + .dstMipOffset = mipOffsets[i + 1], + .srcMipDim = mipDims[i], + .dstMipDim = mipDims[i + 1], + }; + computeCommand->pushConstants(Gfx::SE_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(MipParam), ¶ms); + UVector2 threadGroups = (((mipDims[i] + UVector2(1, 1)) / 2u) + UVector2(BLOCK_SIZE - 1, BLOCK_SIZE - 1)) / uint32(BLOCK_SIZE); + computeCommand->dispatch(threadGroups.x, threadGroups.y, 1); + } + + depthMipBuffer->pipelineBarrier(Gfx::SE_ACCESS_SHADER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_TASK_SHADER_BIT_EXT); depthAttachment.getTexture()->changeLayout( Gfx::SE_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, Gfx::SE_ACCESS_TRANSFER_READ_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | Gfx::SE_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT); - depthMipTexture->changeLayout(Gfx::SE_IMAGE_LAYOUT_GENERAL, Gfx::SE_ACCESS_TRANSFER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_TRANSFER_BIT, - Gfx::SE_ACCESS_SHADER_READ_BIT, Gfx::SE_PIPELINE_STAGE_TASK_SHADER_BIT_EXT); - Gfx::PDescriptorSet set = depthTextureLayout->allocateDescriptorSet(); - set->updateTexture(0, Gfx::PTexture2D(depthMipTexture)); - set->writeChanges(); query->beginQuery(); graphics->beginRenderPass(renderPass); if (useDepthCulling) { @@ -187,12 +226,51 @@ void DepthCullingPass::render() { void DepthCullingPass::endFrame() {} void DepthCullingPass::publishOutputs() { - uint32 width = viewport->getOwner()->getFramebufferWidth(); - uint32 height = viewport->getOwner()->getFramebufferHeight(); - uint32 mipLevels = static_cast(std::floor(std::log2(std::max(width, height)))) + 1; - TextureCreateInfo depthMipInfo = { - .format = Gfx::SE_FORMAT_D32_SFLOAT, .width = width, .height = height, .mipLevels = mipLevels, .name = "DepthMipTexture"}; - depthMipTexture = graphics->createTexture2D(depthMipInfo); + uint32 width = (viewport->getOwner()->getFramebufferWidth() + BLOCK_SIZE - 1) / BLOCK_SIZE; + uint32 height = (viewport->getOwner()->getFramebufferHeight() + BLOCK_SIZE - 1) / BLOCK_SIZE; + uint32 bufferSize = 0; + while (width > 1 && height > 1) { + mipOffsets.add(bufferSize); + mipDims.add(UVector2(width, height)); + bufferSize += width * height; + width = max((width + 1) / 2, 1); + height = max((height + 1) / 2, 1); + } + ShaderBufferCreateInfo depthMipInfo = { + .sourceData = + { + .size = bufferSize * sizeof(uint32), + .data = nullptr, + }, + .numElements = bufferSize, + .name = "DepthMipBuffer", + }; + depthMipBuffer = graphics->createShaderBuffer(depthMipInfo); + + ShaderCreateInfo mipComputeInfo = { + .name = "DepthMipCompute", + .mainModule = "DepthMipGen", + .entryPoint = "initialReduce", + .rootSignature = depthComputeLayout, + }; + + depthInitialReduceShader = graphics->createComputeShader(mipComputeInfo); + depthComputeLayout->create(); + + Gfx::ComputePipelineCreateInfo pipelineCreateInfo = { + .computeShader = depthInitialReduceShader, + .pipelineLayout = depthComputeLayout, + }; + depthInitialReduce = graphics->createComputePipeline(pipelineCreateInfo); + + mipComputeInfo.entryPoint = "reduceLevel"; + + depthMipGenShader = graphics->createComputeShader(mipComputeInfo); + + pipelineCreateInfo.computeShader = depthMipGenShader; + + depthMipGen = graphics->createComputePipeline(pipelineCreateInfo); + query = graphics->createPipelineStatisticsQuery(); resources->registerQueryOutput("DEPTH_QUERY", query); } diff --git a/src/Engine/Graphics/RenderPass/DepthCullingPass.h b/src/Engine/Graphics/RenderPass/DepthCullingPass.h index aca64d7..45e65bb 100644 --- a/src/Engine/Graphics/RenderPass/DepthCullingPass.h +++ b/src/Engine/Graphics/RenderPass/DepthCullingPass.h @@ -1,6 +1,7 @@ #pragma once #include "MinimalEngine.h" #include "RenderPass.h" +#include "Graphics/Pipeline.h" #include "Graphics/Query.h" namespace Seele { @@ -17,13 +18,28 @@ class DepthCullingPass : public RenderPass { virtual void createRenderPass() override; private: - Gfx::OTexture2D depthMipTexture; + constexpr static uint64 BLOCK_SIZE = 32; + struct MipParam { + uint32 srcMipOffset; + uint32 dstMipOffset; + UVector2 srcMipDim; + UVector2 dstMipDim; + }; + Array mipOffsets; + Array mipDims; + Gfx::OShaderBuffer depthMipBuffer; Gfx::RenderTargetAttachment depthAttachment; Gfx::RenderTargetAttachment visibilityAttachment; - Gfx::ODescriptorLayout depthTextureLayout; - Gfx::OPipelineLayout depthPrepassLayout; + Gfx::ODescriptorLayout depthAttachmentLayout; + Gfx::OPipelineLayout depthCullingLayout; Gfx::OPipelineStatisticsQuery query; + Gfx::OPipelineLayout depthComputeLayout; + Gfx::OComputeShader depthInitialReduceShader; + Gfx::PComputePipeline depthInitialReduce; + Gfx::OComputeShader depthMipGenShader; + Gfx::PComputePipeline depthMipGen; + Gfx::PShaderBuffer cullingBuffer; }; DEFINE_REF(DepthCullingPass)