From ca44522f96b303b9460ef263f0740d4d2728491f Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 15 Nov 2023 16:34:23 -0500 Subject: [PATCH] Properly caching sphere pipelines. - Don't resource leak pipelines when enable/disable depth/alpha. - Sphere pipelines are now created on demand. - Pipeline are cleaned up during shutdown. --- .../GraphicsBenchmarkApp.cpp | 116 ++++++++++++------ .../graphics_pipeline/GraphicsBenchmarkApp.h | 54 +++++++- 2 files changed, 129 insertions(+), 41 deletions(-) diff --git a/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.cpp b/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.cpp index c60ce3830..8c61f27cf 100644 --- a/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.cpp +++ b/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.cpp @@ -158,13 +158,6 @@ void GraphicsBenchmarkApp::Setup() mMeshesIndexer.AddDimension(kAvailableVbFormats.size()); mMeshesIndexer.AddDimension(kAvailableVertexAttrLayouts.size()); } - // Graphics pipelines indexer - { - mGraphicsPipelinesIndexer.AddDimension(kAvailableVsShaders.size()); - mGraphicsPipelinesIndexer.AddDimension(kAvailablePsShaders.size()); - mGraphicsPipelinesIndexer.AddDimension(kAvailableVbFormats.size()); - mGraphicsPipelinesIndexer.AddDimension(kAvailableVertexAttrLayouts.size()); - } // Sampler { grfx::SamplerCreateInfo samplerCreateInfo = {}; @@ -240,6 +233,22 @@ void GraphicsBenchmarkApp::Setup() } } +void GraphicsBenchmarkApp::Shutdown() +{ + for (const auto& kv : mPipelines) { + GetDevice()->DestroyGraphicsPipeline(kv.second); + } + mPipelines.clear(); + GetDevice()->DestroyGraphicsPipeline(mSkyBox.pipeline); + mSkyBox.pipeline.Reset(); + for (auto& pipeline : mQuadsPipelines) { + GetDevice()->DestroyGraphicsPipeline(pipeline); + pipeline.Reset(); + } + + // TODO: cleanup non-pipeline resources +} + void GraphicsBenchmarkApp::SetupSkyBoxResources() { // Textures @@ -526,37 +535,67 @@ void GraphicsBenchmarkApp::SetupSpheresPipelines() piCreateInfo.sets[0].pLayout = mSphere.descriptorSetLayout; PPX_CHECKED_CALL(GetDevice()->CreatePipelineInterface(&piCreateInfo, &mSphere.pipelineInterface)); - uint32_t pipelineIndex = 0; - for (size_t i = 0; i < kAvailableVsShaders.size(); i++) { - for (size_t j = 0; j < kAvailablePsShaders.size(); j++) { - for (size_t k = 0; k < kAvailableVbFormats.size(); k++) { - // Interleaved pipeline - grfx::GraphicsPipelineCreateInfo2 gpCreateInfo = {}; - gpCreateInfo.VS = {mVsShaders[i].Get(), "vsmain"}; - gpCreateInfo.PS = {mPsShaders[j].Get(), "psmain"}; - gpCreateInfo.vertexInputState.bindingCount = 1; - gpCreateInfo.vertexInputState.bindings[0] = mSphereMeshes[2 * k + 0]->GetDerivedVertexBindings()[0]; - gpCreateInfo.topology = grfx::PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - gpCreateInfo.polygonMode = grfx::POLYGON_MODE_FILL; - gpCreateInfo.cullMode = grfx::CULL_MODE_BACK; - gpCreateInfo.frontFace = grfx::FRONT_FACE_CCW; - gpCreateInfo.depthReadEnable = pDepthTestWrite->GetValue(); - gpCreateInfo.depthWriteEnable = pDepthTestWrite->GetValue(); - gpCreateInfo.blendModes[0] = pAlphaBlend->GetValue() ? grfx::BLEND_MODE_ALPHA : grfx::BLEND_MODE_NONE; - gpCreateInfo.outputState.renderTargetCount = 1; - gpCreateInfo.outputState.renderTargetFormats[0] = GetSwapchain()->GetColorFormat(); - gpCreateInfo.outputState.depthStencilFormat = GetSwapchain()->GetDepthFormat(); - gpCreateInfo.pPipelineInterface = mSphere.pipelineInterface; - PPX_CHECKED_CALL(GetDevice()->CreateGraphicsPipeline(&gpCreateInfo, &mPipelines[pipelineIndex++])); - - // Position Planar Pipeline - gpCreateInfo.vertexInputState.bindingCount = 2; - gpCreateInfo.vertexInputState.bindings[0] = mSphereMeshes[2 * k + 1]->GetDerivedVertexBindings()[0]; - gpCreateInfo.vertexInputState.bindings[1] = mSphereMeshes[2 * k + 1]->GetDerivedVertexBindings()[1]; - PPX_CHECKED_CALL(GetDevice()->CreateGraphicsPipeline(&gpCreateInfo, &mPipelines[pipelineIndex++])); - } - } + // Pre-load the current pipeline variant. + GetSpherePipeline(); +} + +Result GraphicsBenchmarkApp::CompileSpherePipeline(const SpherePipelineKey& key) +{ + if (mPipelines.find(key) != mPipelines.end()) { + return SUCCESS; + } + grfx::GraphicsPipelinePtr pipeline = nullptr; + bool interleaved = key.vertexAttributeLayout == 0; + size_t meshIndex = 2 * key.vertexFormat + (interleaved ? 0 : 1); + grfx::BlendMode blendMode = (key.enableAlphaBlend ? grfx::BLEND_MODE_ALPHA : grfx::BLEND_MODE_NONE); + // Interleaved pipeline + grfx::GraphicsPipelineCreateInfo2 gpCreateInfo = {}; + gpCreateInfo.VS = {mVsShaders[key.vs].Get(), "vsmain"}; + gpCreateInfo.PS = {mPsShaders[key.ps].Get(), "psmain"}; + if (interleaved) { + gpCreateInfo.vertexInputState.bindingCount = 1; + gpCreateInfo.vertexInputState.bindings[0] = mSphereMeshes[meshIndex]->GetDerivedVertexBindings()[0]; } + else { + // Position Planar Pipeline + gpCreateInfo.vertexInputState.bindingCount = 2; + gpCreateInfo.vertexInputState.bindings[0] = mSphereMeshes[meshIndex]->GetDerivedVertexBindings()[0]; + gpCreateInfo.vertexInputState.bindings[1] = mSphereMeshes[meshIndex]->GetDerivedVertexBindings()[1]; + } + gpCreateInfo.topology = grfx::PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + gpCreateInfo.polygonMode = grfx::POLYGON_MODE_FILL; + gpCreateInfo.cullMode = grfx::CULL_MODE_BACK; + gpCreateInfo.frontFace = grfx::FRONT_FACE_CCW; + gpCreateInfo.depthReadEnable = key.enableDepth; + gpCreateInfo.depthWriteEnable = key.enableDepth; + gpCreateInfo.blendModes[0] = blendMode; + gpCreateInfo.outputState.renderTargetCount = 1; + gpCreateInfo.outputState.renderTargetFormats[0] = GetSwapchain()->GetColorFormat(); + gpCreateInfo.outputState.depthStencilFormat = GetSwapchain()->GetDepthFormat(); + gpCreateInfo.pPipelineInterface = mSphere.pipelineInterface; + + Result ppxres = GetDevice()->CreateGraphicsPipeline(&gpCreateInfo, &pipeline); + if (ppxres == SUCCESS) { + // Insert a new pipeline to the cache. + // We don't delete old pipeline while we run benchmark + // which require this function to be synchronized to end of frame. + mPipelines[key] = pipeline; + } + return ppxres; +} + +// Compile or load from cache currently required pipeline. +grfx::GraphicsPipelinePtr GraphicsBenchmarkApp::GetSpherePipeline() +{ + SpherePipelineKey key = {}; + key.ps = static_cast(pKnobPs->GetIndex()); + key.vs = static_cast(pKnobVs->GetIndex()); + key.vertexFormat = static_cast(pKnobVbFormat->GetIndex()); + key.vertexAttributeLayout = static_cast(pKnobVertexAttrLayout->GetIndex()); + key.enableDepth = pDepthTestWrite->GetValue(); + key.enableAlphaBlend = pAlphaBlend->GetValue(); + PPX_CHECKED_CALL(CompileSpherePipeline(key)); + return mPipelines[key]; } void GraphicsBenchmarkApp::SetupFullscreenQuadsPipelines() @@ -1068,8 +1107,7 @@ void GraphicsBenchmarkApp::RecordCommandBufferSkyBox(PerFrame& frame) void GraphicsBenchmarkApp::RecordCommandBufferSpheres(PerFrame& frame) { // Bind resources - const size_t pipelineIndex = mGraphicsPipelinesIndexer.GetIndex({pKnobVs->GetIndex(), pKnobPs->GetIndex(), pKnobVbFormat->GetIndex(), pKnobVertexAttrLayout->GetIndex()}); - frame.cmd->BindGraphicsPipeline(mPipelines[pipelineIndex]); + frame.cmd->BindGraphicsPipeline(GetSpherePipeline()); const size_t meshIndex = mMeshesIndexer.GetIndex({pKnobLOD->GetIndex(), pKnobVbFormat->GetIndex(), pKnobVertexAttrLayout->GetIndex()}); frame.cmd->BindIndexBuffer(mSphereMeshes[meshIndex]); frame.cmd->BindVertexBuffers(mSphereMeshes[meshIndex]); diff --git a/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.h b/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.h index 64d2822b9..b3b89406e 100644 --- a/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.h +++ b/benchmarks/graphics_pipeline/GraphicsBenchmarkApp.h @@ -25,6 +25,7 @@ #include #include +#include static constexpr uint32_t kMaxSphereInstanceCount = 3000; static constexpr uint32_t kSeed = 89977; @@ -98,6 +99,7 @@ class GraphicsBenchmarkApp virtual void InitKnobs() override; virtual void Config(ppx::ApplicationSettings& settings) override; virtual void Setup() override; + virtual void Shutdown() override; virtual void MouseMove(int32_t x, int32_t y, int32_t dx, int32_t dy, uint32_t buttons) override; virtual void KeyDown(ppx::KeyCode key) override; virtual void KeyUp(ppx::KeyCode key) override; @@ -164,7 +166,51 @@ class GraphicsBenchmarkApp std::string name; }; + struct SpherePipelineKey + { + uint8_t ps; + uint8_t vs; + uint8_t vertexFormat; + uint8_t vertexAttributeLayout; + bool enableDepth; + bool enableAlphaBlend; + + static_assert(kAvailablePsShaders.size() < (1 << (8 * sizeof(ps)))); + static_assert(kAvailableVsShaders.size() < (1 << (8 * sizeof(vs)))); + static_assert(kAvailableVbFormats.size() < (1 << (8 * sizeof(vertexFormat)))); + static_assert(kAvailableVertexAttrLayouts.size() < (1 << (8 * sizeof(vertexAttributeLayout)))); + + bool operator==(const SpherePipelineKey& rhs) const + { + return ps == rhs.ps && + vs == rhs.vs && + vertexFormat == rhs.vertexFormat && + vertexAttributeLayout == rhs.vertexAttributeLayout && + enableDepth == rhs.enableDepth && + enableAlphaBlend == rhs.enableAlphaBlend; + } + + struct Hash + { + // Not a good hash function, but good enough. + size_t operator()(const SpherePipelineKey& key) const + { + size_t res = 0; + + res = (res << 2) | key.ps; + res = (res << 1) | key.vs; + res = (res << 1) | key.vertexFormat; + res = (res << 1) | key.vertexAttributeLayout; + res = (res << 1) | (key.enableDepth ? 1 : 0); + res = (res << 1) | (key.enableAlphaBlend ? 1 : 0); + return res; + } + }; + }; + private: + using SpherePipelineMap = std::unordered_map; + std::vector mPerFrame; FreeCamera mCamera; float3 mLightPosition = float3(10, 250, 10); @@ -188,10 +234,9 @@ class GraphicsBenchmarkApp grfx::TexturePtr mNormalMapTexture; grfx::TexturePtr mMetalRoughnessTexture; grfx::TexturePtr mWhitePixelTexture; - std::array mPipelines; + SpherePipelineMap mPipelines; std::array mSphereMeshes; std::vector mSphereLODs; - MultiDimensionalIndexer mGraphicsPipelinesIndexer; MultiDimensionalIndexer mMeshesIndexer; // Fullscreen quads resources @@ -246,6 +291,8 @@ class GraphicsBenchmarkApp void SetupSpheresPipelines(); void SetupFullscreenQuadsPipelines(); + Result CompileSpherePipeline(const SpherePipelineKey& key); + // Update descriptors // Note: Descriptors can be updated within rendering loop void UpdateSkyBoxDescriptors(); @@ -284,6 +331,9 @@ class GraphicsBenchmarkApp // Loads shader at shaderBaseDir/fileName and creates it at ppShaderModule void SetupShader(const std::filesystem::path& fileName, grfx::ShaderModule** ppShaderModule); + + // Compile or load from cache currently required pipeline. + grfx::GraphicsPipelinePtr GetSpherePipeline(); }; #endif // BENCHMARKS_GRAPHICS_PIPELINE_GRAPHICS_BENCHMARK_APP_H \ No newline at end of file