/**************************************************************************** Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. https://www.cocos.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include #include #include "FGDispatcherGraphs.h" #include "LayoutGraphGraphs.h" #include "LayoutGraphTypes.h" #include "LayoutGraphUtils.h" #include "NativeBuiltinUtils.h" #include "NativePipelineFwd.h" #include "NativePipelineTypes.h" #include "NativeRenderGraphUtils.h" #include "NativeUtils.h" #include "PrivateTypes.h" #include "RenderGraphGraphs.h" #include "RenderGraphTypes.h" #include "RenderingModule.h" #include "cocos/renderer/gfx-base/GFXBarrier.h" #include "cocos/renderer/gfx-base/GFXDef-common.h" #include "cocos/renderer/gfx-base/GFXDescriptorSetLayout.h" #include "cocos/renderer/gfx-base/GFXDevice.h" #include "cocos/renderer/pipeline/Define.h" #include "cocos/renderer/pipeline/InstancedBuffer.h" #include "cocos/renderer/pipeline/PipelineStateManager.h" #include "cocos/scene/Model.h" #include "cocos/scene/Octree.h" #include "cocos/scene/Pass.h" #include "cocos/scene/RenderScene.h" #include "cocos/scene/Skybox.h" #include "details/GraphView.h" #include "details/GslUtils.h" #include "details/Range.h" namespace cc { namespace render { namespace { constexpr uint32_t INVALID_ID = 0xFFFFFFFF; constexpr gfx::Color RASTER_COLOR{0.0, 1.0, 0.0, 1.0}; constexpr gfx::Color RASTER_UPLOAD_COLOR{1.0, 1.0, 0.0, 1.0}; constexpr gfx::Color RENDER_QUEUE_COLOR{0.0, 0.5, 0.5, 1.0}; constexpr gfx::Color COMPUTE_COLOR{0.0, 0.0, 1.0, 1.0}; gfx::MarkerInfo makeMarkerInfo(const char* str, const gfx::Color& color) { return gfx::MarkerInfo{str, color}; } struct RenderGraphVisitorContext { RenderGraphVisitorContext(RenderGraphVisitorContext&&) = delete; RenderGraphVisitorContext(RenderGraphVisitorContext const&) = delete; RenderGraphVisitorContext& operator=(RenderGraphVisitorContext&&) = delete; RenderGraphVisitorContext& operator=(RenderGraphVisitorContext const&) = delete; NativeRenderContext& context; LayoutGraphData& lg; const RenderGraph& g; ResourceGraph& resourceGraph; const FrameGraphDispatcher& fgd; const ccstd::pmr::vector& validPasses; gfx::Device* device = nullptr; gfx::CommandBuffer* cmdBuff = nullptr; NativePipeline* ppl = nullptr; ccstd::pmr::unordered_map< RenderGraph::vertex_descriptor, gfx::DescriptorSet*>& renderGraphDescriptorSet; ccstd::pmr::unordered_map< RenderGraph::vertex_descriptor, gfx::DescriptorSet*>& profilerPerPassDescriptorSets; ccstd::pmr::unordered_map< RenderGraph::vertex_descriptor, gfx::DescriptorSet*>& perInstanceDescriptorSets; ProgramLibrary* programLib = nullptr; CustomRenderGraphContext customContext; boost::container::pmr::memory_resource* scratch = nullptr; gfx::RenderPass* currentPass = nullptr; uint32_t subpassIndex = 0; LayoutGraphData::vertex_descriptor currentPassLayoutID = LayoutGraphData::null_vertex(); RenderGraph::vertex_descriptor currentInFlightPassID = RenderGraph::null_vertex(); Mat4 currentProjMatrix{}; }; void clear(gfx::RenderPassInfo& info) { info.colorAttachments.clear(); info.depthStencilAttachment = {}; info.depthStencilResolveAttachment = {}; info.subpasses.clear(); info.dependencies.clear(); } uint8_t getRasterViewPassInputSlot(const RasterView& view) { std::ignore = view; CC_EXPECTS(false); // not implemented yet return 0; } uint8_t getRasterViewPassOutputSlot(const RasterView& view) { std::ignore = view; CC_EXPECTS(false); // not implemented yet return 0; } uint32_t getRasterPassInputCount(const RasterPass& pass) { uint32_t numInputs = 0; for (const auto& [name, view] : pass.rasterViews) { if (view.accessType == AccessType::READ || view.accessType == AccessType::READ_WRITE) { ++numInputs; } } return numInputs; } uint32_t getRasterPassOutputCount(const RasterPass& pass) { uint32_t numOutputs = 0; for (const auto& [name, view] : pass.rasterViews) { if (view.attachmentType != AttachmentType::RENDER_TARGET) { continue; } if (view.accessType == AccessType::READ_WRITE || view.accessType == AccessType::WRITE) { ++numOutputs; } } return numOutputs; } uint32_t getRasterPassResolveCount(const RasterPass& pass) { std::ignore = pass; return 0; } uint32_t getRasterPassPreserveCount(const RasterPass& pass) { std::ignore = pass; return 0; } gfx::GeneralBarrier* getGeneralBarrier(gfx::Device* device, const RasterView& view) { if (view.accessType != AccessType::WRITE) { // Input return device->getGeneralBarrier({ gfx::AccessFlagBit::COLOR_ATTACHMENT_READ, gfx::AccessFlagBit::COLOR_ATTACHMENT_READ, }); } if (view.accessType != AccessType::READ) { // Output auto accessFlagBit = view.attachmentType == AttachmentType::RENDER_TARGET ? gfx::AccessFlagBit::COLOR_ATTACHMENT_WRITE : gfx::AccessFlagBit::DEPTH_STENCIL_ATTACHMENT_WRITE; return device->getGeneralBarrier({accessFlagBit, accessFlagBit}); } return nullptr; } ResourceGraph::vertex_descriptor getResourceID(const ccstd::pmr::string& name, const FrameGraphDispatcher& fgd) { return fgd.realResourceID(name); } PersistentRenderPassAndFramebuffer createPersistentRenderPassAndFramebuffer( RenderGraphVisitorContext& ctx, const RasterPass& pass, boost::container::pmr::memory_resource* /*scratch*/) { auto& resg = ctx.resourceGraph; PersistentRenderPassAndFramebuffer data(pass.get_allocator()); auto [rpInfo, fbInfo, clearColors, clearDepth, clearStencil] = ctx.fgd.getRenderPassAndFrameBuffer(ctx.currentInFlightPassID, resg); // CC_ENSURES(rpInfo.colorAttachments.size() == data.clearColors.size()); CC_ENSURES(rpInfo.colorAttachments.size() == fbInfo.colorTextures.size()); data.clearColors = std::move(clearColors); data.clearDepth = clearDepth; data.clearStencil = clearStencil; data.renderPass = ctx.device->createRenderPass(rpInfo); fbInfo.renderPass = data.renderPass; data.framebuffer = ctx.device->createFramebuffer(fbInfo); return data; } PersistentRenderPassAndFramebuffer& fetchOrCreateFramebuffer( RenderGraphVisitorContext& ctx, const RasterPass& pass, boost::container::pmr::memory_resource* scratch) { auto iter = ctx.resourceGraph.renderPasses.find(pass); if (iter == ctx.resourceGraph.renderPasses.end()) { bool added = false; std::tie(iter, added) = ctx.resourceGraph.renderPasses.emplace( pass, createPersistentRenderPassAndFramebuffer(ctx, pass, scratch)); CC_ENSURES(added); } return iter->second; } struct RenderGraphFilter { bool operator()(RenderGraph::vertex_descriptor u) const { return validPasses->operator[](u); } const ccstd::pmr::vector* validPasses = nullptr; }; void bindDescValue(gfx::DescriptorSet& desc, uint32_t binding, gfx::Buffer* value) { desc.bindBuffer(binding, value); } void bindDescValue(gfx::DescriptorSet& desc, uint32_t binding, gfx::Texture* value) { desc.bindTexture(binding, value); } void bindDescValue(gfx::DescriptorSet& desc, uint32_t binding, gfx::Sampler* value) { desc.bindSampler(binding, value); } template void bindGlobalDesc(gfx::DescriptorSet& desc, uint32_t binding, T* value) { bindDescValue(desc, binding, value); } uint32_t getDescBinding(uint32_t descId, const DescriptorSetData& descData) { const auto& layoutData = descData; // find descriptor binding for (const auto& block : layoutData.descriptorSetLayoutData.descriptorBlocks) { for (uint32_t i = 0; i != block.descriptors.size(); ++i) { if (descId == block.descriptors[i].descriptorID.value) { return block.offset + i; } } } return 0xFFFFFFFF; } void updateGlobal( RenderGraphVisitorContext& ctx, DescriptorSetData& descriptorSetData, const RenderData& data, bool isUpdate = false) { const auto& constants = data.constants; const auto& samplers = data.samplers; const auto& textures = data.textures; auto& device = *ctx.device; auto& descriptorSet = *descriptorSetData.descriptorSet; for (const auto& [key, value] : constants) { const auto bindId = getDescBinding(key, descriptorSetData); if (bindId == INVALID_ID) { continue; } auto* buffer = descriptorSet.getBuffer(bindId); bool haveBuff = true; if (!buffer && !isUpdate) { gfx::BufferInfo info{ gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST, gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE, static_cast(value.size()), static_cast(value.size())}; buffer = device.createBuffer(info); haveBuff = false; } CC_ENSURES(buffer); if (isUpdate) { buffer->update(value.data()); } if (!haveBuff) { bindGlobalDesc(descriptorSet, bindId, buffer); } } for (const auto& [key, value] : textures) { const auto bindId = getDescBinding(key, descriptorSetData); if (bindId == INVALID_ID) { continue; } auto* tex = descriptorSet.getTexture(bindId); if (!tex || isUpdate) { bindGlobalDesc(descriptorSet, bindId, value.get()); } } for (const auto& [key, value] : samplers) { const auto bindId = getDescBinding(key, descriptorSetData); if (bindId == INVALID_ID) { continue; } auto* sampler = descriptorSet.getSampler(bindId); if (!sampler || isUpdate) { bindGlobalDesc(descriptorSet, bindId, value); } } } void updateCpuUniformBuffer( const LayoutGraphData& lg, const RenderData& user, const gfx::UniformBlock& uniformBlock, bool bInit, ccstd::pmr::vector& buffer) { // calculate uniform block size const auto bufferSize = uniformBlock.count * getUniformBlockSize(uniformBlock.members); // check pre-condition CC_EXPECTS(buffer.size() == bufferSize); // reset buffer if (bInit) { std::fill(buffer.begin(), buffer.end(), 0); } uint32_t offset = 0; for (const auto& value : uniformBlock.members) { CC_EXPECTS(value.count); const auto typeSize = getTypeSize(value.type); const auto totalSize = typeSize * value.count; CC_ENSURES(typeSize); CC_ENSURES(totalSize); const auto valueID = [&]() { auto iter = lg.constantIndex.find(std::string_view{value.name}); CC_EXPECTS(iter != lg.constantIndex.end()); return iter->second.value; }(); const auto iter2 = user.constants.find(valueID); if (iter2 == user.constants.end() || iter2->second.empty()) { if (bInit) { if (value.type == gfx::Type::MAT4) { // init matrix to identity CC_EXPECTS(sizeof(Mat4) == typeSize); const Mat4 id{}; for (uint32_t i = 0; i != value.count; ++i) { memcpy(buffer.data() + offset + i * typeSize, id.m, typeSize); } } } offset += totalSize; continue; } const auto& source = iter2->second; CC_EXPECTS(source.size() == totalSize); CC_EXPECTS(offset + totalSize <= bufferSize); memcpy(buffer.data() + offset, source.data(), std::min(source.size(), totalSize)); // safe guard min offset += totalSize; } CC_ENSURES(offset == bufferSize); } void uploadUniformBuffer( gfx::DescriptorSet* passSet, uint32_t bindID, UniformBlockResource& resource, gfx::CommandBuffer* cmdBuff) { auto* buffer = resource.bufferPool.allocateBuffer(); CC_ENSURES(buffer); cmdBuff->updateBuffer(buffer, resource.cpuBuffer.data(), static_cast(resource.cpuBuffer.size())); CC_EXPECTS(passSet); passSet->bindBuffer(bindID, buffer); } gfx::DescriptorSet* initDescriptorSet( ResourceGraph& resg, gfx::Device* device, gfx::CommandBuffer* cmdBuff, const gfx::DefaultResource& defaultResource, const LayoutGraphData& lg, const PmrFlatMap& resourceIndex, const DescriptorSetData& set, const RenderData& user, LayoutGraphNodeResource& node, const ResourceAccessNode* accessNode = nullptr, const SceneResource* sceneResource = nullptr) { // update per pass resources const auto& data = set.descriptorSetLayoutData; gfx::DescriptorSet* newSet = node.descriptorSetPool.allocateDescriptorSet(); for (const auto& block : data.descriptorBlocks) { CC_EXPECTS(block.descriptors.size() == block.capacity); auto bindID = block.offset; switch (block.type) { case DescriptorTypeOrder::DYNAMIC_UNIFORM_BUFFER: case DescriptorTypeOrder::UNIFORM_BUFFER: { for (const auto& d : block.descriptors) { // get uniform block const auto& uniformBlock = data.uniformBlocks.at(d.descriptorID); auto& resource = node.uniformBuffers.at(d.descriptorID); updateCpuUniformBuffer(lg, user, uniformBlock, true, resource.cpuBuffer); CC_ENSURES(resource.bufferPool.bufferSize == resource.cpuBuffer.size()); // upload gfx buffer uploadUniformBuffer(newSet, bindID, resource, cmdBuff); // increase slot // TODO(zhouzhenglong): here binding will be refactored in the future // current implementation is incorrect, and we assume d.count == 1 CC_EXPECTS(d.count == 1); bindID += d.count; } break; } case DescriptorTypeOrder::SAMPLER_TEXTURE: { CC_EXPECTS(newSet); for (const auto& d : block.descriptors) { CC_EXPECTS(d.count == 1); CC_EXPECTS(d.type >= gfx::Type::SAMPLER1D && d.type <= gfx::Type::SAMPLER_CUBE); // texture if (auto iter = resourceIndex.find(d.descriptorID); iter != resourceIndex.end()) { // render graph textures auto* texture = resg.getTexture(iter->second); CC_ENSURES(texture); newSet->bindTexture(bindID, texture); } else { // user provided textures bool found = false; if (auto iter = user.textures.find(d.descriptorID.value); iter != user.textures.end()) { newSet->bindTexture(bindID, iter->second.get()); found = true; } else if (sceneResource) { auto iter = sceneResource->resourceIndex.find(d.descriptorID); if (iter != sceneResource->resourceIndex.end()) { CC_EXPECTS(iter->second == ResourceType::STORAGE_IMAGE); auto* pTex = sceneResource->storageImages.at(d.descriptorID).get(); newSet->bindTexture(bindID, pTex); found = true; } } if (!found) { // default textures gfx::TextureType type{}; switch (d.type) { case gfx::Type::SAMPLER1D: type = gfx::TextureType::TEX1D; break; case gfx::Type::SAMPLER1D_ARRAY: type = gfx::TextureType::TEX1D_ARRAY; break; case gfx::Type::SAMPLER2D: type = gfx::TextureType::TEX2D; break; case gfx::Type::SAMPLER2D_ARRAY: type = gfx::TextureType::TEX2D_ARRAY; break; case gfx::Type::SAMPLER3D: type = gfx::TextureType::TEX3D; break; case gfx::Type::SAMPLER_CUBE: type = gfx::TextureType::CUBE; break; default: break; } newSet->bindTexture(bindID, defaultResource.getTexture(type)); } } // texture end // user provided samplers if (auto iter = user.samplers.find(d.descriptorID.value); iter != user.samplers.end()) { newSet->bindSampler(bindID, iter->second); } // increase descriptor binding offset bindID += d.count; } break; } case DescriptorTypeOrder::SAMPLER: for (const auto& d : block.descriptors) { CC_EXPECTS(d.count == 1); auto iter = user.samplers.find(d.descriptorID.value); if (iter != user.samplers.end()) { newSet->bindSampler(bindID, iter->second); } else { gfx::SamplerInfo info{}; auto* sampler = device->getSampler(info); newSet->bindSampler(bindID, sampler); } bindID += d.count; } break; case DescriptorTypeOrder::TEXTURE: // not supported yet CC_EXPECTS(false); break; case DescriptorTypeOrder::DYNAMIC_STORAGE_BUFFER: case DescriptorTypeOrder::STORAGE_BUFFER: CC_EXPECTS(newSet); for (const auto& d : block.descriptors) { bool found = false; CC_EXPECTS(d.count == 1); if (auto iter = resourceIndex.find(d.descriptorID); iter != resourceIndex.end()) { // render graph textures auto* buffer = resg.getBuffer(iter->second); CC_ENSURES(buffer); newSet->bindBuffer(bindID, buffer); found = true; } else if (sceneResource) { auto iter = sceneResource->resourceIndex.find(d.descriptorID); if (iter != sceneResource->resourceIndex.end()) { CC_EXPECTS(iter->second == ResourceType::STORAGE_BUFFER); auto* pBuffer = sceneResource->storageBuffers.at(d.descriptorID).get(); newSet->bindBuffer(bindID, pBuffer); found = true; } } if (!found) { newSet->bindBuffer(bindID, defaultResource.getBuffer()); } bindID += d.count; } break; case DescriptorTypeOrder::STORAGE_IMAGE: // not supported yet CC_EXPECTS(newSet); for (const auto& d : block.descriptors) { CC_EXPECTS(d.count == 1); CC_EXPECTS(d.type == gfx::Type::IMAGE2D); auto iter = resourceIndex.find(d.descriptorID); if (iter != resourceIndex.end()) { gfx::AccessFlags access = gfx::AccessFlagBit::NONE; if (accessNode != nullptr) { const auto& resID = iter->second; // whole access only now. auto parentID = parent(resID, resg); parentID = parentID == ResourceGraph::null_vertex() ? resID : parentID; const auto& resName = get(ResourceGraph::NameTag{}, resg, parentID); access = accessNode->resourceStatus.at(resName).accessFlag; } // render graph textures auto* texture = resg.getTexture(iter->second); CC_ENSURES(texture); newSet->bindTexture(bindID, texture, 0, access); } bindID += d.count; } break; case DescriptorTypeOrder::INPUT_ATTACHMENT: { for (const auto& [descID, resID] : resourceIndex) { std::ignore = descID; // render graph textures auto* texture = resg.getTexture(resID); gfx::AccessFlags access = gfx::AccessFlagBit::NONE; if (accessNode != nullptr) { // whole access only now. auto parentID = parent(resID, resg); parentID = parentID == ResourceGraph::null_vertex() ? resID : parentID; const auto& resName = get(ResourceGraph::NameTag{}, resg, parentID); access = accessNode->resourceStatus.at(resName).accessFlag; } CC_ENSURES(texture); newSet->bindTexture(bindID, texture, 0, access); bindID += 1; } }; break; default: CC_EXPECTS(false); break; } } newSet->update(); return newSet; } gfx::DescriptorSet* updatePerPassDescriptorSet( gfx::CommandBuffer* cmdBuff, const LayoutGraphData& lg, const DescriptorSetData& set, const RenderData& user, LayoutGraphNodeResource& node) { // update per pass resources const auto& data = set.descriptorSetLayoutData; auto& prevSet = node.descriptorSetPool.getCurrentDescriptorSet(); gfx::DescriptorSet* newSet = node.descriptorSetPool.allocateDescriptorSet(); for (const auto& block : data.descriptorBlocks) { CC_EXPECTS(block.descriptors.size() == block.capacity); auto bindID = block.offset; switch (block.type) { case DescriptorTypeOrder::DYNAMIC_UNIFORM_BUFFER: case DescriptorTypeOrder::UNIFORM_BUFFER: { for (const auto& d : block.descriptors) { // get uniform block const auto& uniformBlock = data.uniformBlocks.at(d.descriptorID); auto& resource = node.uniformBuffers.at(d.descriptorID); updateCpuUniformBuffer(lg, user, uniformBlock, false, resource.cpuBuffer); // upload gfx buffer uploadUniformBuffer(newSet, bindID, resource, cmdBuff); // increase slot // TODO(zhouzhenglong): here binding will be refactored in the future // current implementation is incorrect, and we assume d.count == 1 CC_EXPECTS(d.count == 1); bindID += d.count; } break; } case DescriptorTypeOrder::SAMPLER_TEXTURE: { CC_EXPECTS(newSet); for (const auto& d : block.descriptors) { CC_EXPECTS(d.count == 1); CC_EXPECTS(d.type >= gfx::Type::SAMPLER1D && d.type <= gfx::Type::SAMPLER_CUBE); // textures if (auto iter = user.textures.find(d.descriptorID.value); iter != user.textures.end()) { newSet->bindTexture(bindID, iter->second.get()); } else { auto* prevTexture = prevSet.getTexture(bindID); CC_ENSURES(prevTexture); newSet->bindTexture(bindID, prevTexture); } // samplers if (auto iter = user.samplers.find(d.descriptorID.value); iter != user.samplers.end()) { newSet->bindSampler(bindID, iter->second); } // increase descriptor binding offset bindID += d.count; } break; } case DescriptorTypeOrder::SAMPLER: for (const auto& d : block.descriptors) { CC_EXPECTS(d.count == 1); auto iter = user.samplers.find(d.descriptorID.value); if (iter != user.samplers.end()) { newSet->bindSampler(bindID, iter->second); } else { auto* prevSampler = prevSet.getSampler(bindID); CC_ENSURES(prevSampler); newSet->bindSampler(bindID, prevSampler); } bindID += d.count; } break; case DescriptorTypeOrder::TEXTURE: // not supported yet CC_EXPECTS(false); break; case DescriptorTypeOrder::DYNAMIC_STORAGE_BUFFER: case DescriptorTypeOrder::STORAGE_BUFFER: CC_EXPECTS(newSet); for (const auto& d : block.descriptors) { bool found = false; CC_EXPECTS(d.count == 1); if (auto iter = user.buffers.find(d.descriptorID.value); iter != user.buffers.end()) { newSet->bindBuffer(bindID, iter->second.get()); found = true; } else { auto* prevBuffer = prevSet.getBuffer(bindID); CC_ENSURES(prevBuffer); newSet->bindBuffer(bindID, prevBuffer); } auto name = lg.valueNames[d.descriptorID.value]; bindID += d.count; } break; case DescriptorTypeOrder::STORAGE_IMAGE: // not supported yet CC_EXPECTS(false); break; case DescriptorTypeOrder::INPUT_ATTACHMENT: CC_EXPECTS(newSet); for (const auto& d : block.descriptors) { CC_EXPECTS(d.count == 1); auto iter = user.textures.find(d.descriptorID.value); if (iter != user.textures.end()) { newSet->bindTexture(bindID, iter->second.get()); } else { auto* prevTexture = prevSet.getTexture(bindID); if (prevTexture) { newSet->bindTexture(bindID, prevTexture); } } bindID += d.count; } break; default: CC_EXPECTS(false); break; } } newSet->update(); return newSet; } gfx::DescriptorSet* updateCameraUniformBufferAndDescriptorSet( RenderGraphVisitorContext& ctx, RenderGraph::vertex_descriptor sceneID) { gfx::DescriptorSet* perPassSet = nullptr; // update states CC_EXPECTS(ctx.currentPassLayoutID != LayoutGraphData::null_vertex()); const auto& passLayoutID = ctx.currentPassLayoutID; auto& layout = get(LayoutGraphData::LayoutTag{}, ctx.lg, passLayoutID); auto iter = layout.descriptorSets.find(UpdateFrequency::PER_PASS); if (iter != layout.descriptorSets.end()) { auto& set = iter->second; auto& node = ctx.context.layoutGraphResources.at(passLayoutID); const auto& user = get(RenderGraph::DataTag{}, ctx.g, sceneID); // notice: sceneID perPassSet = updatePerPassDescriptorSet(ctx.cmdBuff, ctx.lg, set, user, node); } return perPassSet; } void submitUICommands( gfx::RenderPass* renderPass, uint32_t subpassOrPassLayoutID, const scene::Camera* camera, gfx::CommandBuffer* cmdBuff) { const auto& batches = camera->getScene()->getBatches(); for (auto* batch : batches) { if (!(camera->getVisibility() & batch->getVisFlags())) { continue; } const auto& passes = batch->getPasses(); for (size_t i = 0; i < batch->getShaders().size(); ++i) { const scene::Pass* pass = passes[i]; if (pass->getSubpassOrPassID() != subpassOrPassLayoutID) { continue; } auto* shader = batch->getShaders()[i]; auto* inputAssembler = batch->getInputAssembler(); auto* ds = batch->getDescriptorSet(); auto* pso = pipeline::PipelineStateManager::getOrCreatePipelineState( pass, shader, inputAssembler, renderPass); cmdBuff->bindPipelineState(pso); cmdBuff->bindDescriptorSet( static_cast(pipeline::SetIndex::MATERIAL), pass->getDescriptorSet()); cmdBuff->bindInputAssembler(inputAssembler); cmdBuff->bindDescriptorSet( static_cast(pipeline::SetIndex::LOCAL), ds); cmdBuff->draw(batch->getDrawInfo()); } } } void submitProfilerCommands( RenderGraphVisitorContext& ctx, RenderGraph::vertex_descriptor vertID, const RasterPass& rasterPass) { const auto* profiler = ctx.ppl->getProfiler(); if (!profiler || !profiler->isEnabled()) { return; } auto* renderPass = ctx.currentPass; auto* cmdBuff = ctx.cmdBuff; const auto& submodel = profiler->getSubModels()[0]; auto* pass = submodel->getPass(0); auto* ia = submodel->getInputAssembler(); auto* pso = pipeline::PipelineStateManager::getOrCreatePipelineState( pass, submodel->getShader(0), ia, renderPass); // profiler pass gfx::Viewport profilerViewport{}; gfx::Rect profilerScissor{}; profilerViewport.width = profilerScissor.width = rasterPass.width; profilerViewport.height = profilerScissor.height = rasterPass.height; cmdBuff->setViewport(profilerViewport); cmdBuff->setScissor(profilerScissor); auto* passSet = ctx.profilerPerPassDescriptorSets.at(vertID); CC_ENSURES(passSet); cmdBuff->bindPipelineState(pso); cmdBuff->bindDescriptorSet(static_cast(pipeline::SetIndex::GLOBAL), passSet); cmdBuff->bindDescriptorSet(static_cast(pipeline::SetIndex::MATERIAL), pass->getDescriptorSet()); cmdBuff->bindDescriptorSet(static_cast(pipeline::SetIndex::LOCAL), submodel->getDescriptorSet()); cmdBuff->bindInputAssembler(ia); cmdBuff->draw(ia); } const PmrTransparentMap>& getComputeViews(RenderGraph::vertex_descriptor passID, const RenderGraph& rg) { if (holds(passID, rg)) { return get(RasterPassTag{}, passID, rg).computeViews; } if (holds(passID, rg)) { return get(RasterSubpassTag{}, passID, rg).computeViews; } CC_EXPECTS(holds(passID, rg)); return get(ComputeTag{}, passID, rg).computeViews; } struct RenderGraphUploadVisitor : boost::dfs_visitor<> { void updateAndCreatePerPassDescriptorSet(RenderGraph::vertex_descriptor vertID) const { auto* perPassSet = updateCameraUniformBufferAndDescriptorSet(ctx, vertID); if (perPassSet) { ctx.renderGraphDescriptorSet[vertID] = perPassSet; } } void uploadBlitOrDispatchUniformBlokcs( RenderGraph::vertex_descriptor vertID, scene::Pass& pass) const { pass.update(); // get shader auto& shader = *pass.getShaderVariant(); // update material ubo and descriptor set // get or create program per-instance descriptor set auto& node = ctx.context.layoutGraphResources.at(pass.getPhaseID()); auto iter = node.programResources.find(std::string_view{shader.getName()}); if (iter == node.programResources.end()) { // make program resource auto res = node.programResources.emplace( std::piecewise_construct, std::forward_as_tuple(shader.getName()), std::forward_as_tuple()); CC_ENSURES(res.second); iter = res.first; auto& instance = res.first->second; // make per-instance layout IntrusivePtr instanceSetLayout = &const_cast( ctx.programLib->getLocalDescriptorSetLayout( ctx.device, pass.getPhaseID(), shader.getName())); // init per-instance descriptor set pool instance.descriptorSetPool.init(ctx.device, std::move(instanceSetLayout)); for (const auto& block : shader.getBlocks()) { if (static_cast(block.set) != pipeline::SetIndex::LOCAL) { continue; } const auto& name = block.name; auto iter = ctx.lg.attributeIndex.find(std::string_view{name}); CC_EXPECTS(iter != ctx.lg.attributeIndex.end()); const auto attrID = iter->second; auto sz = getUniformBlockSize(block.members); const auto bDynamic = isDynamicUniformBlock(block.name); instance.uniformBuffers[attrID].init(ctx.device, sz, bDynamic); } } // update per-instance buffer and descriptor set const auto& programLib = *dynamic_cast(ctx.programLib); const auto& data = programLib.localLayoutData; auto& instance = iter->second; auto* set = instance.descriptorSetPool.allocateDescriptorSet(); CC_ENSURES(set); for (const auto& block : shader.getBlocks()) { if (static_cast(block.set) != pipeline::SetIndex::LOCAL) { continue; } // find descriptor name ID const auto& name = block.name; auto iter = ctx.lg.attributeIndex.find(std::string_view{name}); CC_EXPECTS(iter != ctx.lg.attributeIndex.end()); const auto attrID = iter->second; // get uniformBuffer auto& uniformBuffer = instance.uniformBuffers[attrID]; CC_EXPECTS(uniformBuffer.cpuBuffer.size() == uniformBuffer.bufferPool.bufferSize); // fill cpu buffer auto& cpuData = uniformBuffer.cpuBuffer; if (false) { // NOLINT(readability-simplify-boolean-expr) for (const auto& v : block.members) { CC_LOG_INFO(v.name.c_str()); } } // create and upload buffer auto* buffer = uniformBuffer.createFromCpuBuffer(); // set buffer descriptor const auto binding = data.bindingMap.at(attrID); set->bindBuffer(binding, buffer); } ctx.perInstanceDescriptorSets[vertID] = set; } const SceneResource* getFirstSceneResource(RenderGraph::vertex_descriptor vertID) const { const auto& g = ctx.g; CC_EXPECTS(holds(vertID, g)); for (const auto e : makeRange(children(vertID, g))) { const auto sceneID = target(e, g); if (holds(sceneID, g)) { const auto& sceneData = get(SceneTag{}, sceneID, g); auto iter = ctx.context.renderSceneResources.find(sceneData.scene); if (iter != ctx.context.renderSceneResources.end()) { return &iter->second; } } } return nullptr; } void discover_vertex( RenderGraph::vertex_descriptor vertID, const boost::filtered_graph< AddressableView, boost::keep_all, RenderGraphFilter>& gv) const { std::ignore = gv; CC_EXPECTS(ctx.currentPassLayoutID != LayoutGraphData::null_vertex()); if (holds(vertID, ctx.g) || holds(vertID, ctx.g)) { // const auto& pass = get(RasterPassTag{}, vertID, ctx.g); const auto& computeViews = holds(vertID, ctx.g) ? get(RasterPassTag{}, vertID, ctx.g).computeViews : get(ComputeTag{}, vertID, ctx.g).computeViews; // render pass const auto& layoutName = get(RenderGraph::LayoutTag{}, ctx.g, vertID); const auto& layoutID = locate(LayoutGraphData::null_vertex(), layoutName, ctx.lg); CC_EXPECTS(layoutID == ctx.currentPassLayoutID); // get layout auto& layout = get(LayoutGraphData::LayoutTag{}, ctx.lg, layoutID); // update states auto iter = layout.descriptorSets.find(UpdateFrequency::PER_PASS); if (iter == layout.descriptorSets.end()) { return; } // build pass resources const auto& resourceIndex = ctx.fgd.buildDescriptorIndex(computeViews, ctx.scratch); // populate set auto& set = iter->second; const auto& user = get(RenderGraph::DataTag{}, ctx.g, vertID); auto& node = ctx.context.layoutGraphResources.at(layoutID); const auto& accessNode = ctx.fgd.getAccessNode(vertID); auto* perPassSet = initDescriptorSet( ctx.resourceGraph, ctx.device, ctx.cmdBuff, *ctx.context.defaultResource, ctx.lg, resourceIndex, set, user, node, &accessNode); CC_ENSURES(perPassSet); ctx.renderGraphDescriptorSet[vertID] = perPassSet; } else if (holds(vertID, ctx.g)) { const auto& queue = get(QueueTag{}, vertID, ctx.g); if (queue.phaseID == LayoutGraphData::null_vertex()) { return; } const auto layoutID = queue.phaseID; const auto passID = parent(vertID, ctx.g); const auto& computeViews = getComputeViews(passID, ctx.g); // get layout auto& layout = get(LayoutGraphData::LayoutTag{}, ctx.lg, layoutID); auto iter = layout.descriptorSets.find(UpdateFrequency::PER_PHASE); if (iter == layout.descriptorSets.end()) { return; } // build pass resources const auto& resourceIndex = ctx.fgd.buildDescriptorIndex(computeViews, ctx.scratch); // find scene resource const auto* const sceneResource = getFirstSceneResource(vertID); // populate set auto& set = iter->second; const auto& user = get(RenderGraph::DataTag{}, ctx.g, vertID); auto& node = ctx.context.layoutGraphResources.at(layoutID); auto* perPhaseSet = initDescriptorSet( ctx.resourceGraph, ctx.device, ctx.cmdBuff, *ctx.context.defaultResource, ctx.lg, resourceIndex, set, user, node, nullptr, sceneResource); CC_ENSURES(perPhaseSet); ctx.renderGraphDescriptorSet[vertID] = perPhaseSet; } else if (holds(vertID, ctx.g)) { const auto& sceneData = get(SceneTag{}, vertID, ctx.g); if (sceneData.camera) { updateAndCreatePerPassDescriptorSet(vertID); ctx.currentProjMatrix = sceneData.camera->getMatProj(); } } else if (holds(vertID, ctx.g)) { const auto& blit = get(BlitTag{}, vertID, ctx.g); if (blit.camera) { updateAndCreatePerPassDescriptorSet(vertID); ctx.currentProjMatrix = blit.camera->getMatProj(); } // get pass auto& pass = *blit.material->getPasses()->at(static_cast(blit.passID)); uploadBlitOrDispatchUniformBlokcs(vertID, pass); } else if (holds(vertID, ctx.g)) { const auto& dispatch = get(DispatchTag{}, vertID, ctx.g); auto& pass = *dispatch.material->getPasses()->at(static_cast(dispatch.passID)); uploadBlitOrDispatchUniformBlokcs(vertID, pass); } else if (holds(vertID, ctx.g)) { const auto& subpass = get(RasterSubpassTag{}, vertID, ctx.g); // render pass const auto& layoutName = get(RenderGraph::LayoutTag{}, ctx.g, vertID); auto parentLayoutID = ctx.currentPassLayoutID; auto layoutID = parentLayoutID; if (!layoutName.empty()) { auto parentID = parent(ctx.currentPassLayoutID, ctx.lg); if (parentID != LayoutGraphData::null_vertex()) { parentLayoutID = parentID; } layoutID = locate(parentLayoutID, layoutName, ctx.lg); } ctx.currentPassLayoutID = layoutID; // get layout auto& layout = get(LayoutGraphData::LayoutTag{}, ctx.lg, layoutID); // update states auto iter = layout.descriptorSets.find(UpdateFrequency::PER_PASS); if (iter == layout.descriptorSets.end()) { return; } const auto& resourceIndex = ctx.fgd.buildDescriptorIndex(subpass.computeViews, subpass.rasterViews, ctx.scratch); // populate set auto& set = iter->second; const auto& user = get(RenderGraph::DataTag{}, ctx.g, vertID); auto& node = ctx.context.layoutGraphResources.at(layoutID); const auto& accessNode = ctx.fgd.getAccessNode(vertID); auto* perPassSet = initDescriptorSet( ctx.resourceGraph, ctx.device, ctx.cmdBuff, *ctx.context.defaultResource, ctx.lg, resourceIndex, set, user, node, &accessNode); CC_ENSURES(perPassSet); ctx.renderGraphDescriptorSet[vertID] = perPassSet; } } RenderGraphVisitorContext& ctx; }; struct RenderGraphVisitor : boost::dfs_visitor<> { void submitBarriers(const std::vector& barriers) const { auto& resg = ctx.resourceGraph; auto sz = barriers.size(); ccstd::pmr::vector buffers(ctx.scratch); ccstd::pmr::vector bufferBarriers(ctx.scratch); ccstd::pmr::vector textures(ctx.scratch); ccstd::pmr::vector textureBarriers(ctx.scratch); buffers.reserve(sz); bufferBarriers.reserve(sz); textures.reserve(sz); textureBarriers.reserve(sz); for (const auto& barrier : barriers) { const auto resID = barrier.resourceID; const auto& desc = get(ResourceGraph::DescTag{}, resg, resID); const auto& resource = get(ResourceGraph::DescTag{}, resg, resID); switch (desc.dimension) { case ResourceDimension::BUFFER: { auto* buffer = resg.getBuffer(resID); const auto* bufferBarrier = static_cast(barrier.barrier); buffers.emplace_back(buffer); bufferBarriers.emplace_back(bufferBarrier); break; } case ResourceDimension::TEXTURE1D: case ResourceDimension::TEXTURE2D: case ResourceDimension::TEXTURE3D: default: { auto* texture = resg.getTexture(resID); const auto* textureBarrier = static_cast(barrier.barrier); textures.emplace_back(texture); textureBarriers.emplace_back(textureBarrier); break; } } } CC_EXPECTS(buffers.size() == bufferBarriers.size()); CC_EXPECTS(textures.size() == textureBarriers.size()); ctx.cmdBuff->pipelineBarrier( nullptr, bufferBarriers.data(), buffers.data(), static_cast(bufferBarriers.size()), textureBarriers.data(), textures.data(), static_cast(textureBarriers.size())); } void frontBarriers(RenderGraph::vertex_descriptor vertID) const { const auto& barrier = ctx.fgd.getBarrier(vertID); if (!barrier.frontBarriers.empty()) { submitBarriers(barrier.frontBarriers); } } void rearBarriers(RenderGraph::vertex_descriptor vertID) const { const auto& barrier = ctx.fgd.getBarrier(vertID); if (!barrier.rearBarriers.empty()) { submitBarriers(barrier.rearBarriers); } } void tryBindPerPassDescriptorSet(RenderGraph::vertex_descriptor vertID) const { auto iter = ctx.renderGraphDescriptorSet.find(vertID); if (iter != ctx.renderGraphDescriptorSet.end()) { CC_ENSURES(iter->second); ctx.cmdBuff->bindDescriptorSet( static_cast(pipeline::SetIndex::GLOBAL), iter->second); } } void tryBindPerPhaseDescriptorSet(RenderGraph::vertex_descriptor vertID) const { auto iter = ctx.renderGraphDescriptorSet.find(vertID); if (iter != ctx.renderGraphDescriptorSet.end()) { CC_ENSURES(iter->second); static_assert(static_cast(pipeline::SetIndex::COUNT) == 3); ctx.cmdBuff->bindDescriptorSet( static_cast(pipeline::SetIndex::COUNT), iter->second); } } void begin(const RasterPass& pass, RenderGraph::vertex_descriptor vertID) const { const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& passes = ctx.ppl->custom.renderPasses; auto iter = passes.find(renderData.custom); if (iter != passes.end()) { iter->second->beginRenderPass(ctx.customContext, vertID); return; } } // viewport auto vp = pass.viewport; if (vp.width == 0 && vp.height == 0) { vp.width = pass.width; vp.height = pass.height; } // scissor gfx::Rect scissor{0, 0, vp.width, vp.height}; // render pass { ctx.currentInFlightPassID = vertID; auto& res = fetchOrCreateFramebuffer(ctx, pass, ctx.scratch); const auto& data = res; auto* cmdBuff = ctx.cmdBuff; cmdBuff->beginRenderPass( data.renderPass.get(), data.framebuffer.get(), scissor, data.clearColors.data(), data.clearDepth, data.clearStencil); ctx.currentPass = data.renderPass.get(); } // PerPass DescriptorSet tryBindPerPassDescriptorSet(vertID); } void begin(const RasterSubpass& subpass, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) #if CC_DEBUG ctx.cmdBuff->insertMarker(makeMarkerInfo(get(RenderGraph::NameTag{}, ctx.g, vertID).c_str(), RASTER_COLOR)); #endif const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& subpasses = ctx.ppl->custom.renderSubpasses; auto iter = subpasses.find(renderData.custom); if (iter != subpasses.end()) { iter->second->beginRenderSubpass(ctx.customContext, vertID); return; } } // PerPass DescriptorSet if (subpass.subpassID) { ctx.cmdBuff->nextSubpass(); } // ctx.cmdBuff->setViewport(subpass); tryBindPerPassDescriptorSet(vertID); ctx.subpassIndex = subpass.subpassID; // noop } void begin(const ComputeSubpass& subpass, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& subpasses = ctx.ppl->custom.computeSubpasses; auto iter = subpasses.find(renderData.custom); if (iter != subpasses.end()) { iter->second->beginComputeSubpass(ctx.customContext, vertID); return; } } std::ignore = subpass; std::ignore = vertID; // noop } void begin(const ComputePass& pass, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) #if CC_DEBUG ctx.cmdBuff->beginMarker(makeMarkerInfo(get(RenderGraph::NameTag{}, ctx.g, vertID).c_str(), COMPUTE_COLOR)); #endif const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& passes = ctx.ppl->custom.computePasses; auto iter = passes.find(renderData.custom); if (iter != passes.end()) { iter->second->beginComputePass(ctx.customContext, vertID); return; } } std::ignore = pass; std::ignore = vertID; for (const auto& [name, views] : pass.computeViews) { for (const auto& view : views) { if (view.clearFlags != gfx::ClearFlags::NONE) { // clear resources } } } tryBindPerPassDescriptorSet(vertID); } void begin(const ResolvePass& pass, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) std::ignore = pass; std::ignore = vertID; for (const auto& copy : pass.resolvePairs) { // TODO(zhenglong.zhou): resolve } } void copyTexture( const CopyPair& copy, ResourceGraph::vertex_descriptor srcID, ResourceGraph::vertex_descriptor dstID) const { auto& resg = ctx.resourceGraph; std::vector copyInfos(copy.mipLevels, gfx::TextureCopy{}); gfx::Texture* srcTexture = resg.getTexture(srcID); gfx::Texture* dstTexture = resg.getTexture(dstID); CC_ENSURES(srcTexture); CC_ENSURES(dstTexture); if (!srcTexture || !dstTexture) { return; } const auto& srcInfo = srcTexture->getInfo(); const auto& dstInfo = dstTexture->getInfo(); CC_ENSURES(srcInfo.width == dstInfo.width); CC_ENSURES(srcInfo.height == dstInfo.height); CC_ENSURES(srcInfo.depth == dstInfo.depth); for (uint32_t i = 0; i < copy.mipLevels; ++i) { auto& copyInfo = copyInfos[i]; copyInfo.srcSubres.mipLevel = copy.sourceMostDetailedMip + i; copyInfo.srcSubres.baseArrayLayer = copy.sourceFirstSlice; copyInfo.srcSubres.layerCount = copy.numSlices; copyInfo.dstSubres.mipLevel = copy.targetMostDetailedMip + i; copyInfo.dstSubres.baseArrayLayer = copy.targetFirstSlice; copyInfo.dstSubres.layerCount = copy.numSlices; copyInfo.srcOffset = {0, 0, 0}; copyInfo.dstOffset = {0, 0, 0}; copyInfo.extent = {srcInfo.width, srcInfo.height, srcInfo.depth}; } ctx.cmdBuff->copyTexture(srcTexture, dstTexture, copyInfos.data(), static_cast(copyInfos.size())); } void copyBuffer( // NOLINT(readability-convert-member-functions-to-static) const CopyPair& copy, ResourceGraph::vertex_descriptor srcID, ResourceGraph::vertex_descriptor dstID) const { // TODO(zhouzhenglong): add impl std::ignore = copy; std::ignore = srcID; std::ignore = dstID; } void uploadTexture( // NOLINT(readability-convert-member-functions-to-static) const UploadPair& upload, ResourceGraph::vertex_descriptor dstID) const { // TODO(zhouzhenglong): add impl std::ignore = upload; std::ignore = dstID; } void uploadBuffer( const UploadPair& upload, ResourceGraph::vertex_descriptor dstID) const { auto& resg = ctx.resourceGraph; gfx::Buffer* dstBuffer = resg.getBuffer(dstID); CC_ENSURES(dstBuffer); if (!dstBuffer) { return; } ctx.cmdBuff->updateBuffer(dstBuffer, upload.source.data(), static_cast(upload.source.size())); } void begin(const CopyPass& pass, RenderGraph::vertex_descriptor vertID) const { std::ignore = pass; std::ignore = vertID; auto& resg = ctx.resourceGraph; // currently, only texture to texture supported. for (const auto& copy : pass.copyPairs) { auto srcID = findVertex(copy.source, resg); auto dstID = findVertex(copy.target, resg); CC_ENSURES(srcID != RenderGraph::null_vertex()); CC_ENSURES(dstID != RenderGraph::null_vertex()); if (srcID == RenderGraph::null_vertex() || dstID == RenderGraph::null_vertex()) { continue; } const bool sourceIsTexture = resg.isTexture(srcID); const bool targetIsTexture = resg.isTexture(dstID); CC_ENSURES(sourceIsTexture == targetIsTexture); if (sourceIsTexture != targetIsTexture) { continue; } if (targetIsTexture) { copyTexture(copy, srcID, dstID); } else { copyBuffer(copy, srcID, dstID); } } // copy from cpu for (const auto& upload : pass.uploadPairs) { auto dstID = findVertex(upload.target, resg); CC_ENSURES(dstID != RenderGraph::null_vertex()); if (dstID == RenderGraph::null_vertex()) { continue; } const bool targetIsTexture = resg.isTexture(dstID); if (targetIsTexture) { uploadTexture(upload, dstID); } else { uploadBuffer(upload, dstID); } } } void begin(const MovePass& pass, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) std::ignore = pass; std::ignore = vertID; // if fully optimized, move pass should have been removed from graph // here we just do copy for (const auto& copy : pass.movePairs) { } } void begin(const RaytracePass& pass, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) std::ignore = pass; std::ignore = vertID; // not implemented yet CC_EXPECTS(false); } void begin(const RenderQueue& queue, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) #if CC_DEBUG ctx.cmdBuff->beginMarker(makeMarkerInfo(get(RenderGraph::NameTag{}, ctx.g, vertID).c_str(), RENDER_QUEUE_COLOR)); #endif const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& queues = ctx.ppl->custom.renderQueues; auto iter = queues.find(renderData.custom); if (iter != queues.end()) { iter->second->beginRenderQueue(ctx.customContext, vertID); return; } } if (queue.viewport.width != 0 && queue.viewport.height != 0) { ctx.cmdBuff->setViewport(queue.viewport); } // PerPhase DescriptorSet tryBindPerPhaseDescriptorSet(vertID); } void begin(const SceneData& sceneData, RenderGraph::vertex_descriptor sceneID) const { // NOLINT(readability-convert-member-functions-to-static) const auto* const camera = sceneData.camera; CC_EXPECTS(camera); if (camera) { // update camera data tryBindPerPassDescriptorSet(sceneID); } const auto* scene = camera->getScene(); const auto& queueDesc = ctx.context.sceneCulling.renderQueueIndex.at(sceneID); const auto& queue = ctx.context.sceneCulling.renderQueues[queueDesc.renderQueueTarget.value]; queue.recordCommands(ctx.cmdBuff, ctx.currentPass, 0); if (any(sceneData.flags & SceneFlags::REFLECTION_PROBE)) { queue.probeQueue.removeMacro(); } if (any(sceneData.flags & SceneFlags::UI)) { submitUICommands(ctx.currentPass, ctx.currentPassLayoutID, camera, ctx.cmdBuff); } } void begin(const Blit& blit, RenderGraph::vertex_descriptor vertID) const { const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& commands = ctx.ppl->custom.renderCommands; auto iter = commands.find(renderData.custom); if (iter != commands.end()) { iter->second->beginRenderCommand(ctx.customContext, vertID); return; } } const auto& programLib = *dynamic_cast(ctx.programLib); CC_EXPECTS(blit.material); CC_EXPECTS(blit.material->getPasses()); if (blit.camera) { tryBindPerPassDescriptorSet(vertID); } // get pass auto& pass = *blit.material->getPasses()->at(static_cast(blit.passID)); // get shader auto& shader = *pass.getShaderVariant(); // get pso auto* pso = pipeline::PipelineStateManager::getOrCreatePipelineState( &pass, &shader, ctx.context.fullscreenQuad.quadIA.get(), ctx.currentPass, ctx.subpassIndex); if (!pso) { return; } // auto* perInstanceSet = ctx.perInstanceDescriptorSets.at(vertID); // execution ctx.cmdBuff->bindPipelineState(pso); ctx.cmdBuff->bindDescriptorSet( static_cast(pipeline::SetIndex::MATERIAL), pass.getDescriptorSet()); // ctx.cmdBuff->bindDescriptorSet( // static_cast(pipeline::SetIndex::LOCAL), perInstanceSet); ctx.cmdBuff->bindInputAssembler(ctx.context.fullscreenQuad.quadIA.get()); ctx.cmdBuff->draw(ctx.context.fullscreenQuad.quadIA.get()); } void begin(const Dispatch& dispatch, RenderGraph::vertex_descriptor vertID) const { std::ignore = vertID; auto& programLib = *ctx.programLib; CC_EXPECTS(dispatch.material); CC_EXPECTS(dispatch.material->getPasses()); // get pass auto& pass = *dispatch.material->getPasses()->at(static_cast(dispatch.passID)); // get shader auto& shader = *pass.getShaderVariant(); // get pso auto* pso = programLib.getComputePipelineState( pass.getDevice(), pass.getPhaseID(), pass.getProgram(), pass.getDefines(), nullptr); CC_EXPECTS(pso); // auto* perInstanceSet = ctx.perInstanceDescriptorSets.at(vertID); // execution ctx.cmdBuff->bindPipelineState(pso); ctx.cmdBuff->bindDescriptorSet( static_cast(pipeline::SetIndex::MATERIAL), pass.getDescriptorSet()); // ctx.cmdBuff->bindDescriptorSet( // static_cast(pipeline::SetIndex::LOCAL), perInstanceSet); ctx.cmdBuff->dispatch(gfx::DispatchInfo{ dispatch.threadGroupCountX, dispatch.threadGroupCountY, dispatch.threadGroupCountZ, }); } void begin(const ccstd::pmr::vector& pass, RenderGraph::vertex_descriptor vertID) const { } void begin(const gfx::Viewport& pass, RenderGraph::vertex_descriptor vertID) const { } void end(const RasterPass& pass, RenderGraph::vertex_descriptor vertID) const { const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& passes = ctx.ppl->custom.renderPasses; auto iter = passes.find(renderData.custom); if (iter != passes.end()) { iter->second->endRenderPass(ctx.customContext, vertID); return; } } if (pass.showStatistics) { submitProfilerCommands(ctx, vertID, pass); } ctx.cmdBuff->endRenderPass(); ctx.currentPass = nullptr; ctx.currentPassLayoutID = LayoutGraphData::null_vertex(); } void end(const RasterSubpass& subpass, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& subpasses = ctx.ppl->custom.renderSubpasses; auto iter = subpasses.find(renderData.custom); if (iter != subpasses.end()) { iter->second->endRenderSubpass(ctx.customContext, vertID); return; } } std::ignore = subpass; std::ignore = vertID; ctx.subpassIndex = 0; // noop } void end(const ComputeSubpass& subpass, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& subpasses = ctx.ppl->custom.computeSubpasses; auto iter = subpasses.find(renderData.custom); if (iter != subpasses.end()) { iter->second->endComputeSubpass(ctx.customContext, vertID); return; } } std::ignore = subpass; std::ignore = vertID; // noop } void end(const ComputePass& pass, RenderGraph::vertex_descriptor vertID) const { const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& passes = ctx.ppl->custom.computePasses; auto iter = passes.find(renderData.custom); if (iter != passes.end()) { iter->second->endComputePass(ctx.customContext, vertID); return; } } #if CC_DEBUG ctx.cmdBuff->endMarker(); #endif std::ignore = pass; } void end(const ResolvePass& pass, RenderGraph::vertex_descriptor vertID) const { } void end(const CopyPass& pass, RenderGraph::vertex_descriptor vertID) const { } void end(const MovePass& pass, RenderGraph::vertex_descriptor vertID) const { } void end(const RaytracePass& pass, RenderGraph::vertex_descriptor vertID) const { // NOLINT(readability-convert-member-functions-to-static) std::ignore = pass; std::ignore = vertID; // not implemented yet CC_EXPECTS(false); } void end(const RenderQueue& pass, RenderGraph::vertex_descriptor vertID) const { const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& queues = ctx.ppl->custom.renderQueues; auto iter = queues.find(renderData.custom); if (iter != queues.end()) { iter->second->endRenderQueue(ctx.customContext, vertID); return; } } #if CC_DEBUG ctx.cmdBuff->endMarker(); #endif std::ignore = pass; } void end(const SceneData& pass, RenderGraph::vertex_descriptor vertID) const { } void end(const Blit& pass, RenderGraph::vertex_descriptor vertID) const { const auto& renderData = get(RenderGraph::DataTag{}, ctx.g, vertID); if (!renderData.custom.empty()) { const auto& commands = ctx.ppl->custom.renderCommands; auto iter = commands.find(renderData.custom); if (iter != commands.end()) { iter->second->endRenderCommand(ctx.customContext, vertID); return; } } std::ignore = pass; } void end(const Dispatch& pass, RenderGraph::vertex_descriptor vertID) const { } void end(const ccstd::pmr::vector& pass, RenderGraph::vertex_descriptor vertID) const { } void end(const gfx::Viewport& pass, RenderGraph::vertex_descriptor vertID) const { } void mountResource(const ccstd::pmr::string& name) const { // NOLINT(misc-no-recursion) auto resIter = ctx.fgd.resourceAccessGraph.resourceIndex.find(name); if (resIter != ctx.fgd.resourceAccessGraph.resourceIndex.end()) { auto resID = resIter->second; auto& resg = ctx.resourceGraph; resg.mount(ctx.device, resID); for (const auto& subres : makeRange(children(resID, resg))) { const auto& subresName = get(ResourceGraph::NameTag{}, resg, subres.target); mountResource(subresName); } } } void mountResources(const Subpass& pass) const { // mount managed resources for (const auto& [name, view] : pass.rasterViews) { mountResource(name); } for (const auto& [name, views] : pass.computeViews) { mountResource(name); } for (const auto& resolve : pass.resolvePairs) { mountResource(resolve.target); } } void mountResources(const RasterPass& pass) const { // mount managed resources for (const auto& [name, view] : pass.rasterViews) { mountResource(name); } for (const auto& [name, views] : pass.computeViews) { mountResource(name); } for (const auto& subpass : pass.subpassGraph.subpasses) { mountResources(subpass); } } void mountResources(const ComputePass& pass) const { auto& resg = ctx.resourceGraph; PmrFlatSet mounted(ctx.scratch); for (const auto& [name, views] : pass.computeViews) { mountResource(name); } } void mountResources(const ComputeSubpass& pass) const { auto& resg = ctx.resourceGraph; PmrFlatSet mounted(ctx.scratch); for (const auto& [name, views] : pass.computeViews) { mountResource(name); } } void mountResources(const RaytracePass& pass) const { auto& resg = ctx.resourceGraph; PmrFlatSet mounted(ctx.scratch); for (const auto& [name, views] : pass.computeViews) { mountResource(name); } } void mountResources(const ResolvePass& pass) const { auto& resg = ctx.resourceGraph; PmrFlatSet mounted(ctx.scratch); for (const auto& pair : pass.resolvePairs) { mountResource(pair.source); mountResource(pair.target); } } void mountResources(const CopyPass& pass) const { auto& resg = ctx.resourceGraph; PmrFlatSet mounted(ctx.scratch); for (const auto& pair : pass.copyPairs) { mountResource(pair.source); mountResource(pair.target); } for (const auto& pair : pass.uploadPairs) { mountResource(pair.target); } } void mountResources(const MovePass& pass) const { // not supported yet } void discover_vertex( RenderGraph::vertex_descriptor vertID, const boost::filtered_graph, boost::keep_all, RenderGraphFilter>& gv) const { std::ignore = gv; visitObject( vertID, ctx.g, [&](const RasterPass& pass) { #if CC_DEBUG ctx.cmdBuff->beginMarker(makeMarkerInfo(get(RenderGraph::NameTag{}, ctx.g, vertID).c_str(), RASTER_COLOR)); #endif mountResources(pass); { const auto& layoutName = get(RenderGraph::LayoutTag{}, ctx.g, vertID); const auto& layoutID = locate(LayoutGraphData::null_vertex(), layoutName, ctx.lg); ctx.currentPassLayoutID = layoutID; } // update UniformBuffers and DescriptorSets in all children { #if CC_DEBUG ctx.cmdBuff->beginMarker(makeMarkerInfo("Upload", RASTER_UPLOAD_COLOR)); #endif auto colors = ctx.g.colors(ctx.scratch); RenderGraphUploadVisitor visitor{{}, ctx}; boost::depth_first_visit(gv, vertID, visitor, get(colors, ctx.g)); #if CC_DEBUG ctx.cmdBuff->endMarker(); #endif } if (pass.showStatistics) { const auto* profiler = ctx.ppl->getProfiler(); if (profiler && profiler->isEnabled()) { // current pass RenderData user(ctx.scratch); setMat4Impl(user, ctx.lg, "cc_matProj", ctx.currentProjMatrix); auto* renderPass = ctx.currentPass; auto* cmdBuff = ctx.cmdBuff; const auto& submodel = profiler->getSubModels()[0]; auto* pass = submodel->getPass(0); auto& layout = get(LayoutGraphData::LayoutTag{}, ctx.lg, pass->getSubpassOrPassID()); auto iter = layout.descriptorSets.find(UpdateFrequency::PER_PASS); if (iter != layout.descriptorSets.end()) { auto& set = iter->second; auto& node = ctx.context.layoutGraphResources.at(pass->getSubpassOrPassID()); PmrFlatMap resourceIndex(ctx.scratch); auto* perPassSet = initDescriptorSet( ctx.resourceGraph, ctx.device, ctx.cmdBuff, *ctx.context.defaultResource, ctx.lg, resourceIndex, set, user, node); CC_ENSURES(perPassSet); ctx.profilerPerPassDescriptorSets[vertID] = perPassSet; } else { CC_EXPECTS(false); // TODO(zhouzhenglong): set descriptor set to empty } } } // execute render pass frontBarriers(vertID); begin(pass, vertID); }, [&](const RasterSubpass& subpass) { // mountResources(subpass); { const auto& layoutName = get(RenderGraph::LayoutTag{}, ctx.g, vertID); const auto& layoutID = locate(LayoutGraphData::null_vertex(), layoutName, ctx.lg); ctx.currentPassLayoutID = layoutID; } begin(subpass, vertID); }, [&](const ComputeSubpass& subpass) { // mountResources(subpass); { const auto& layoutName = get(RenderGraph::LayoutTag{}, ctx.g, vertID); const auto& layoutID = locate(LayoutGraphData::null_vertex(), layoutName, ctx.lg); ctx.currentPassLayoutID = layoutID; } begin(subpass, vertID); }, [&](const ComputePass& pass) { mountResources(pass); { const auto& layoutName = get(RenderGraph::LayoutTag{}, ctx.g, vertID); const auto& layoutID = locate(LayoutGraphData::null_vertex(), layoutName, ctx.lg); ctx.currentPassLayoutID = layoutID; } { auto colors = ctx.g.colors(ctx.scratch); RenderGraphUploadVisitor visitor{{}, ctx}; boost::depth_first_visit(gv, vertID, visitor, get(colors, ctx.g)); } frontBarriers(vertID); begin(pass, vertID); }, [&](const ResolvePass& pass) { mountResources(pass); frontBarriers(vertID); begin(pass, vertID); }, [&](const CopyPass& pass) { mountResources(pass); frontBarriers(vertID); begin(pass, vertID); }, [&](const MovePass& pass) { mountResources(pass); frontBarriers(vertID); begin(pass, vertID); }, [&](const RaytracePass& pass) { mountResources(pass); frontBarriers(vertID); begin(pass, vertID); }, [&](const auto& queue) { begin(queue, vertID); }); } void finish_vertex( RenderGraph::vertex_descriptor vertID, const boost::filtered_graph, boost::keep_all, RenderGraphFilter>& gv) const { std::ignore = gv; visitObject( vertID, ctx.g, [&](const RasterPass& pass) { end(pass, vertID); rearBarriers(vertID); #if CC_DEBUG ctx.cmdBuff->endMarker(); #endif }, [&](const RasterSubpass& subpass) { end(subpass, vertID); }, [&](const ComputeSubpass& subpass) { end(subpass, vertID); }, [&](const ComputePass& pass) { end(pass, vertID); rearBarriers(vertID); }, [&](const ResolvePass& pass) { end(pass, vertID); rearBarriers(vertID); }, [&](const CopyPass& pass) { end(pass, vertID); rearBarriers(vertID); }, [&](const MovePass& pass) { end(pass, vertID); rearBarriers(vertID); }, [&](const RaytracePass& pass) { end(pass, vertID); rearBarriers(vertID); }, [&](const auto& queue) { end(queue, vertID); }); } RenderGraphVisitorContext& ctx; }; struct RenderGraphCullVisitor : boost::dfs_visitor<> { void discover_vertex( // NOLINTNEXTLINE(misc-unused-parameters) RenderGraph::vertex_descriptor vertID, const AddressableView& gv) const { validPasses[vertID] = false; } ccstd::pmr::vector& validPasses; }; struct ResourceCleaner { explicit ResourceCleaner(ResourceGraph& resourceGraphIn) noexcept : resourceGraph(resourceGraphIn), prevFenceValue(resourceGraph.nextFenceValue) { ++resourceGraph.nextFenceValue; } ResourceCleaner(const ResourceCleaner&) = delete; ResourceCleaner& operator=(const ResourceCleaner&) = delete; ~ResourceCleaner() noexcept { resourceGraph.unmount(prevFenceValue); } ResourceGraph& resourceGraph; uint64_t prevFenceValue = 0; }; struct RenderGraphContextCleaner { explicit RenderGraphContextCleaner(NativeRenderContext& contextIn) noexcept : context(contextIn), prevFenceValue(context.nextFenceValue) { ++context.nextFenceValue; context.clearPreviousResources(prevFenceValue); context.renderSceneResources.clear(); context.sceneCulling.clear(); } RenderGraphContextCleaner(const RenderGraphContextCleaner&) = delete; RenderGraphContextCleaner& operator=(const RenderGraphContextCleaner&) = delete; ~RenderGraphContextCleaner() noexcept = default; NativeRenderContext& context; uint64_t prevFenceValue = 0; }; struct CommandSubmitter { CommandSubmitter(gfx::Device* deviceIn, const std::vector& cmdBuffersIn) : device(deviceIn), cmdBuffers(cmdBuffersIn) { CC_EXPECTS(cmdBuffers.size() == 1); primaryCommandBuffer = cmdBuffers.at(0); primaryCommandBuffer->begin(); } CommandSubmitter(const CommandSubmitter&) = delete; CommandSubmitter& operator=(const CommandSubmitter&) = delete; ~CommandSubmitter() noexcept { primaryCommandBuffer->end(); device->flushCommands(cmdBuffers); device->getQueue()->submit(cmdBuffers); } gfx::Device* device = nullptr; const std::vector& cmdBuffers; gfx::CommandBuffer* primaryCommandBuffer = nullptr; }; void extendResourceLifetime(const NativeRenderQueue& queue, ResourceGroup& group) { // keep instanceBuffers for (const auto& batch : queue.opaqueInstancingQueue.sortedBatches) { group.instancingBuffers.emplace(batch); } for (const auto& batch : queue.transparentInstancingQueue.sortedBatches) { group.instancingBuffers.emplace(batch); } } void collectStatistics(const NativePipeline& ppl, PipelineStatistics& stats) { // resources stats.numRenderPasses = static_cast(ppl.resourceGraph.renderPasses.size()); stats.totalManagedTextures = static_cast(ppl.resourceGraph.managedTextures.size()); stats.numManagedTextures = 0; for (const auto& tex : ppl.resourceGraph.managedTextures) { if (tex.texture) { ++stats.numManagedTextures; } } // layout graph stats.numUploadBuffers = 0; stats.numUploadBufferViews = 0; stats.numFreeUploadBuffers = 0; stats.numFreeUploadBufferViews = 0; stats.numDescriptorSets = 0; stats.numFreeDescriptorSets = 0; for (const auto& node : ppl.nativeContext.layoutGraphResources) { for (const auto& [nameID, buffer] : node.uniformBuffers) { stats.numUploadBuffers += static_cast(buffer.bufferPool.currentBuffers.size()); stats.numUploadBufferViews += static_cast(buffer.bufferPool.currentBufferViews.size()); stats.numFreeUploadBuffers += static_cast(buffer.bufferPool.freeBuffers.size()); stats.numFreeUploadBufferViews += static_cast(buffer.bufferPool.freeBufferViews.size()); } stats.numDescriptorSets += static_cast(node.descriptorSetPool.currentDescriptorSets.size()); stats.numFreeDescriptorSets += static_cast(node.descriptorSetPool.freeDescriptorSets.size()); } // scene stats.numInstancingBuffers = 0; stats.numInstancingUniformBlocks = 0; for (const auto& [key, group] : ppl.nativeContext.resourceGroups) { stats.numInstancingBuffers += group.instancingBuffers.size(); for (const auto& buffer : group.instancingBuffers) { stats.numInstancingUniformBlocks += static_cast(buffer->getInstances().size()); } } } } // namespace void NativePipeline::executeRenderGraph(const RenderGraph& rg) { auto& ppl = *this; auto* scratch = &ppl.unsyncPool; ppl.resourceGraph.validateSwapchains(); RenderGraphContextCleaner contextCleaner(ppl.nativeContext); ResourceCleaner cleaner(ppl.resourceGraph); auto& lg = ppl.programLibrary->layoutGraph; FrameGraphDispatcher fgd( ppl.resourceGraph, rg, lg, &ppl.unsyncPool, scratch); fgd.enableMemoryAliasing(false); fgd.enablePassReorder(false); fgd.setParalellWeight(0); fgd.run(); AddressableView graphView(rg); ccstd::pmr::vector validPasses(num_vertices(rg), true, scratch); auto colors = rg.colors(scratch); { // Mark all culled vertices RenderGraphCullVisitor visitor{{}, validPasses}; for (const auto& vertID : fgd.resourceAccessGraph.culledPasses) { const auto nodeID = get(ResourceAccessGraph::PassIDTag{}, fgd.resourceAccessGraph, vertID); if (nodeID == RenderGraph::null_vertex()) { continue; } const auto passID = fgd.resourceAccessGraph.passIndex.at(nodeID); boost::depth_first_visit(graphView, passID, visitor, get(colors, rg)); } colors.clear(); colors.resize(num_vertices(rg), boost::white_color); } // scene culling { auto& context = ppl.nativeContext; auto& sceneCulling = context.sceneCulling; sceneCulling.buildRenderQueues(rg, lg, ppl); auto& group = ppl.nativeContext.resourceGroups[context.nextFenceValue]; // notice: we cannot use ranged-for of sceneCulling.renderQueues CC_EXPECTS(sceneCulling.numRenderQueues <= sceneCulling.renderQueues.size()); for (uint32_t queueID = 0; queueID != sceneCulling.numRenderQueues; ++queueID) { const auto& queue = sceneCulling.renderQueues[queueID]; extendResourceLifetime(queue, group); } } // light manangement { auto& ctx = ppl.nativeContext; ctx.lightResources.clear(); ctx.lightResources.buildLights( ctx.sceneCulling, ppl.pipelineSceneData->isHDR(), ppl.pipelineSceneData->getShadows()); } // gpu driven if constexpr (ENABLE_GPU_DRIVEN) { // TODO(jilin): consider populating renderSceneResources here const scene::RenderScene* const ptr = nullptr; auto& sceneResource = ppl.nativeContext.renderSceneResources[ptr]; const auto& nameID = lg.attributeIndex.find("cc_xxxDescriptor")->second; sceneResource.resourceIndex.emplace(nameID, ResourceType::STORAGE_BUFFER); sceneResource.storageBuffers.emplace(nameID, nullptr); } // Execute all valid passes { boost::filtered_graph< AddressableView, boost::keep_all, RenderGraphFilter> fg(graphView, boost::keep_all{}, RenderGraphFilter{&validPasses}); CommandSubmitter submit(ppl.device, ppl.getCommandBuffers()); // upload buffers { auto& ctx = ppl.nativeContext; #if CC_DEBUG submit.primaryCommandBuffer->beginMarker(makeMarkerInfo("Internal Upload", RASTER_UPLOAD_COLOR)); #endif // scene const auto& sceneCulling = ppl.nativeContext.sceneCulling; for (uint32_t queueID = 0; queueID != sceneCulling.numRenderQueues; ++queueID) { // notice: we cannot use ranged-for of sceneCulling.renderQueues CC_EXPECTS(sceneCulling.numRenderQueues <= sceneCulling.renderQueues.size()); const auto& queue = sceneCulling.renderQueues[queueID]; queue.opaqueInstancingQueue.uploadBuffers(submit.primaryCommandBuffer); queue.transparentInstancingQueue.uploadBuffers(submit.primaryCommandBuffer); } // lights ctx.lightResources.buildLightBuffer(submit.primaryCommandBuffer); ctx.lightResources.tryUpdateRenderSceneLocalDescriptorSet(sceneCulling); #if CC_DEBUG submit.primaryCommandBuffer->endMarker(); #endif } ccstd::pmr::unordered_map< RenderGraph::vertex_descriptor, gfx::DescriptorSet*> renderGraphDescriptorSet(scratch); ccstd::pmr::unordered_map< RenderGraph::vertex_descriptor, gfx::DescriptorSet*> profilerPerPassDescriptorSets(scratch); ccstd::pmr::unordered_map< RenderGraph::vertex_descriptor, gfx::DescriptorSet*> perInstanceDescriptorSets(scratch); // submit commands RenderGraphVisitorContext ctx{ ppl.nativeContext, lg, rg, ppl.resourceGraph, fgd, validPasses, ppl.device, submit.primaryCommandBuffer, &ppl, renderGraphDescriptorSet, profilerPerPassDescriptorSets, perInstanceDescriptorSets, programLibrary, CustomRenderGraphContext{ custom.currentContext, &rg, &ppl.resourceGraph, submit.primaryCommandBuffer, }, scratch}; RenderGraphVisitor visitor{{}, ctx}; auto colors = rg.colors(scratch); for (const auto vertID : ctx.g.sortedVertices) { if (holds(vertID, ctx.g) || holds(vertID, ctx.g) || holds(vertID, ctx.g)) { boost::depth_first_visit(fg, vertID, visitor, get(colors, ctx.g)); } } } // collect statistics collectStatistics(*this, statistics); } } // namespace render } // namespace cc