diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index 808dbe5a2..dce8593d6 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -20,3 +20,4 @@ add_subdirectory(fluid_simulation/shaders) add_subdirectory(gbuffer/shaders) add_subdirectory(materials/shaders) add_subdirectory(oit_demo/shaders) +add_subdirectory(scene_renderer/shaders) diff --git a/cmake/ShaderCompile.cmake b/cmake/ShaderCompile.cmake index 7cacd9136..7ec72734d 100644 --- a/cmake/ShaderCompile.cmake +++ b/cmake/ShaderCompile.cmake @@ -56,7 +56,7 @@ function(internal_add_compile_shader_target TARGET_NAME) add_custom_command( OUTPUT "${ARG_OUTPUT_FILE}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "------ Compiling ${ARG_SHADER_STAGE} Shader [${ARG_OUTPUT_FORMAT}] ------" + COMMENT "------ Compiling ${ARG_SHADER_STAGE} Shader [${ARG_OUTPUT_FORMAT}] ------${INCLUDE_DIRS}" MAIN_DEPENDENCY "${ARG_SOURCE}" DEPENDS ${ARG_INCLUDES} COMMAND ${CMAKE_COMMAND} -E echo "[${ARG_OUTPUT_FORMAT}] Compiling ${ARG_SHADER_STAGE} ${ARG_SOURCE} to ${ARG_OUTPUT_FILE}" diff --git a/include/ppx/scene/scene_forward_renderer.h b/include/ppx/scene/scene_forward_renderer.h new file mode 100644 index 000000000..33ed5f82b --- /dev/null +++ b/include/ppx/scene/scene_forward_renderer.h @@ -0,0 +1,66 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ppx_scene_forward_renderer_h +#define ppx_scene_forward_renderer_h + +#include "ppx/scene/scene_config.h" +#include "ppx/scene/scene_renderer.h" + +namespace ppx { +namespace scene { + +class ForwardRenderer + : public scene::Renderer +{ +private: + ForwardRenderer(grfx::Device* pDevice, uint32_t numInFlightFrames); + +public: + virtual ~ForwardRenderer(); + + static ppx::Result Create(grfx::Device* pDevice, uint32_t numFramesInFlight, scene::Renderer** ppRenderer); + +private: + ppx::Result CreateObjects(); + void DestroyObjects(); + + ppx::Result RenderInternal(scene::RenderOutput* pOutput, grfx::Semaphore* pRenderCompleteSemaphore) override; + +private: + struct Frame; + + ppx::Result RenderToOutput( + ForwardRenderer::Frame& frame, + scene::RenderOutput* pOutput, + grfx::Semaphore* pRenderCompleteSemaphore); + +private: + struct Frame + { + scene::RenderPass depthPrePass; + scene::RenderPass shadowPass; + scene::RenderPass lightingPass; + grfx::CommandBufferPtr renderOutputCmd; + grfx::SemaphorePtr timelineSemaphore; + uint64_t timelineValue = 0; + }; + + std::vector mFrames; +}; + +} // namespace scene +} // namespace ppx + +#endif // ppx_scene_forward_renderer_h diff --git a/include/ppx/scene/scene_renderer.h b/include/ppx/scene/scene_renderer.h new file mode 100644 index 000000000..d3cb4995b --- /dev/null +++ b/include/ppx/scene/scene_renderer.h @@ -0,0 +1,229 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ppx_scene_renderer_h +#define ppx_scene_renderer_h + +#include "ppx/scene/scene_config.h" + +namespace ppx { +namespace scene { + +struct GraphicsPipeline +{ + std::string idString = ""; + grfx::DescriptorSetLayoutPtr descriptorSetLayout = nullptr; + grfx::PipelineInterfacePtr pipelineInterface = nullptr; + grfx::GraphicsPipelinePtr pipeline = nullptr; +}; + +// ------------------------------------------------------------------------------------------------- + +struct ComputePipeline +{ + std::string idString = ""; + grfx::DescriptorSetLayoutPtr descriptorSetLayout = nullptr; + grfx::PipelineInterfacePtr pipelineInterface = nullptr; + grfx::GraphicsPipelinePtr pipeline = nullptr; +}; + +// ------------------------------------------------------------------------------------------------- + +struct RenderTargetAttachment +{ + grfx::ImagePtr image = nullptr; + grfx::RenderTargetViewPtr renderTargetView = nullptr; + grfx::SampledImageViewPtr sampledImageView = nullptr; + grfx::RenderTargetClearValue clearValue = {}; +}; + +// ------------------------------------------------------------------------------------------------- + +struct DepthStencilAttachment +{ + grfx::ImagePtr image = nullptr; + grfx::DepthStencilViewPtr dephtStencilView = nullptr; + grfx::SampledImageViewPtr sampledImageView = nullptr; + grfx::DepthStencilClearValue clearValue = {}; +}; + +// ------------------------------------------------------------------------------------------------- + +struct RenderPass +{ + std::string name = ""; + std::vector renderTargetAttachments = {}; + scene::DepthStencilAttachment depthStencilAttachment = {}; + grfx::RenderPassPtr renderPass = nullptr; +}; + +// ------------------------------------------------------------------------------------------------- + +struct ComputePass +{ + const scene::GraphicsPipeline* pPipeline = nullptr; + std::vector inputBuffers = {}; + std::vector inputTextures = {}; + std::vector outputBuffers = {}; + std::vector outputTextures = {}; +}; + +// ------------------------------------------------------------------------------------------------- + +class RenderOutput +{ +protected: + RenderOutput( + scene::Renderer* pRenderer); + +public: + virtual ~RenderOutput(); + + scene::Renderer* GetRenderer() const { return mRenderer; } + + virtual ppx::Result GetRenderTargetImage( + grfx::Image** ppImage, + grfx::Semaphore* pImageReadySemaphore) = 0; + + virtual bool IsSwapchain() const { return false; } + +private: + scene::Renderer* mRenderer = nullptr; +}; + +// ------------------------------------------------------------------------------------------------- + +class RenderOutputToImage + : public scene::RenderOutput +{ +protected: + RenderOutputToImage( + scene::Renderer* pRenderer, + grfx::Image* pInitialImage = nullptr); + +public: + virtual ~RenderOutputToImage(); + + static ppx::Result Create( + scene::Renderer* pRenderer, + grfx::Image* pInitialImage, // Can be NULL + scene::RenderOutputToImage** ppRendererOutput); + + static void Destroy(scene::RenderOutputToImage* pRendererOutput); + + virtual ppx::Result GetRenderTargetImage( + grfx::Image** ppImage, + grfx::Semaphore* pImageReadySemaphore) override; + + void SetImage(grfx::Image* pImage); + +private: + grfx::Image* mImage = nullptr; +}; + +// ------------------------------------------------------------------------------------------------- + +class RenderOutputToSwapchain + : public scene::RenderOutput +{ +protected: + RenderOutputToSwapchain( + scene::Renderer* pRenderer, + grfx::Swapchain* pInitialSwapchain); + +public: + virtual ~RenderOutputToSwapchain(); + + static ppx::Result Create( + scene::Renderer* pRenderer, + grfx::Swapchain* pInitialSwapchain, // Can be NULL + scene::RenderOutputToSwapchain** ppRendererOutput); + + static void Destroy(scene::RenderOutputToSwapchain* pRendererOutput); + + virtual ppx::Result GetRenderTargetImage( + grfx::Image** ppImage, + grfx::Semaphore* pImageReadySemaphore) override; + + virtual bool IsSwapchain() const override { return true; } + + void SetSwapchain(grfx::Swapchain* pSwapchain); + +private: + ppx::Result CreateObject(); + void DestroyObject(); + +private: + grfx::Swapchain* mSwapchain = nullptr; + grfx::FencePtr mFence = nullptr; + uint32_t mImageIndex = 0; +}; + +// ------------------------------------------------------------------------------------------------- + +class Renderer +{ +protected: + Renderer( + grfx::Device* pDevice, + uint32_t numInFlightFrames); + +public: + virtual ~Renderer(); + + grfx::Device* GetDevice() const { return mDevice; } + + uint32_t GetNumInFlightFrames() const { return mNumInFlightFrames; } + scene::Scene* GetScene() const { return mScene; } + + void SetScene(scene::Scene* pScene); + + ppx::Result Render( + scene::RenderOutput* pOutput, + grfx::Semaphore* pRenderCompleteSemaphore); + +protected: + ppx::Result GetRenderOutputRenderPass( + grfx::Image* pImage, + grfx::RenderPass** ppRenderPass); + +private: + virtual ppx::Result RenderInternal( + scene::RenderOutput* pOutput, + grfx::Semaphore* pRenderCompleteSemaphore) = 0; + + // Create render pass with 1 render target using pImage. + // OVerride this method in derived renderers to customize output render passes. + // + virtual ppx::Result CreateOutputRenderPass( + grfx::Image* pImage, + grfx::RenderPass** ppRenderPass); + +protected: + grfx::Device* mDevice = nullptr; + uint32_t mNumInFlightFrames = 0; + uint32_t mNumFramesRendered = 0; + uint32_t mCurrentFrameIndex = 0; + uint2 mRenderResolution = uint2(0, 0); + std::vector mGraphicsPipelines = {}; + std::vector mComputePipelines = {}; + std::unordered_map mOutputRenderPasses = {}; + bool mEnableDepthPrePass = false; + scene::Scene* mScene = nullptr; +}; + +} // namespace scene +} // namespace ppx + +#endif // ppx_scene_renderer_h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 00e43ab90..d01f99c53 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -283,19 +283,23 @@ list( list( APPEND PPX_SCENE_HEADER_FILES ${INC_DIR}/ppx/scene/scene_config.h + ${INC_DIR}/ppx/scene/scene_forward_renderer.h ${INC_DIR}/ppx/scene/scene_material.h ${INC_DIR}/ppx/scene/scene_mesh.h ${INC_DIR}/ppx/scene/scene_node.h ${INC_DIR}/ppx/scene/scene_resource_manager.h + ${INC_DIR}/ppx/scene/scene_renderer.h ${INC_DIR}/ppx/scene/scene_scene.h ) list( APPEND PPX_SCENE_SOURCE_FILES + ${SRC_DIR}/ppx/scene/scene_forward_renderer.cpp ${SRC_DIR}/ppx/scene/scene_material.cpp ${SRC_DIR}/ppx/scene/scene_mesh.cpp ${SRC_DIR}/ppx/scene/scene_node.cpp ${SRC_DIR}/ppx/scene/scene_resource_manager.cpp + ${SRC_DIR}/ppx/scene/scene_renderer.cpp ${SRC_DIR}/ppx/scene/scene_scene.cpp ) diff --git a/src/ppx/scene/scene_forward_renderer.cpp b/src/ppx/scene/scene_forward_renderer.cpp new file mode 100644 index 000000000..54abafcfb --- /dev/null +++ b/src/ppx/scene/scene_forward_renderer.cpp @@ -0,0 +1,180 @@ +#include "ppx/scene/scene_forward_renderer.h" +#include "ppx/grfx/grfx_device.h" + +namespace ppx { +namespace scene { + +// ------------------------------------------------------------------------------------------------- +// ForwardRenderer +// ------------------------------------------------------------------------------------------------- +ForwardRenderer::ForwardRenderer( + grfx::Device* pDevice, + uint32_t numInFlightFrames) + : scene::Renderer(pDevice, numInFlightFrames) +{ +} + +ForwardRenderer::~ForwardRenderer() +{ + this->DestroyObjects(); +} + +ppx::Result ForwardRenderer::CreateObjects() +{ + for (uint32_t i = 0; i < GetNumInFlightFrames(); ++i) { + Frame frame = {}; + + PPX_CHECKED_CALL(GetDevice()->GetGraphicsQueue()->CreateCommandBuffer(&frame.renderOutputCmd)); + + /* + grfx::SemaphoreCreateInfo semaCreateInfo = {}; + PPX_CHECKED_CALL(GetDevice()->CreateSemaphore(&semaCreateInfo, &frame.imageAcquiredSemaphore)); + + grfx::FenceCreateInfo fenceCreateInfo = {true}; // Create signaled + PPX_CHECKED_CALL(GetDevice()->CreateFence(&fenceCreateInfo, &frame.renderCompleteFence)); + */ + mFrames.push_back(frame); + } + + return ppx::SUCCESS; +} + +void ForwardRenderer::DestroyObjects() +{ + for (auto& frame : mFrames) { + GetDevice()->GetGraphicsQueue()->DestroyCommandBuffer(frame.renderOutputCmd); + // GetDevice()->DestroySemaphore(frame.imageAcquiredSemaphore); + // GetDevice()->DestroyFence(frame.renderCompleteFence); + } +} + +ppx::Result ForwardRenderer::Create(grfx::Device* pDevice, uint32_t numInFlightFrames, scene::Renderer** ppRenderer) +{ + if (IsNull(ppRenderer)) { + return ppx::ERROR_UNEXPECTED_NULL_ARGUMENT; + } + + scene::ForwardRenderer* pRenderer = new scene::ForwardRenderer(pDevice, numInFlightFrames); + if (IsNull(pRenderer)) { + return ppx::ERROR_ALLOCATION_FAILED; + } + + auto ppxres = pRenderer->CreateObjects(); + if (Failed(ppxres)) { + delete pRenderer; + return ppxres; + } + + *ppRenderer = pRenderer; + + return ppx::SUCCESS; +} + +ppx::Result ForwardRenderer::RenderToOutput( + ForwardRenderer::Frame& frame, + scene::RenderOutput* pOutput, + grfx::Semaphore* pRenderCompleteSemaphore) +{ + // Get output render target image + grfx::Image* pOutputImage = nullptr; + // + // auto ppxres = pOutput->GetRenderTargetImage(&pOutputImage, frame.imageAcquiredSemaphore.Get()); + // if (Failed(ppxres)) { + // return ppxres; + //} + + // Get render pass for render output render target image + grfx::RenderPass* pOutputRenderPass = nullptr; + // + auto ppxres = GetRenderOutputRenderPass( + pOutputImage, + &pOutputRenderPass); + if (Failed(ppxres)) { + return ppxres; + } + + // Output render command buffer + { + auto& cmd = frame.renderOutputCmd; + + // Begin command buffer + ppxres = cmd->Begin(); + if (Failed(ppxres)) { + return ppxres; + } + + // Transition swapchain image if needed + if (pOutput->IsSwapchain()) { + cmd->TransitionImageLayout( + pOutputImage, + PPX_ALL_SUBRESOURCES, + grfx::RESOURCE_STATE_PRESENT, + grfx::RESOURCE_STATE_RENDER_TARGET); + } + + grfx::RenderPassBeginInfo beginInfo = {}; + beginInfo.pRenderPass = pOutputRenderPass; + beginInfo.renderArea = pOutputRenderPass->GetRenderArea(); + + cmd->BeginRenderPass(&beginInfo); + { + cmd->ClearRenderTarget(pOutputImage, {1, 0, 0, 1}); + } + cmd->EndRenderPass(); + + // Transition swapchain image if needed + if (pOutput->IsSwapchain()) { + cmd->TransitionImageLayout( + pOutputImage, + PPX_ALL_SUBRESOURCES, + grfx::RESOURCE_STATE_RENDER_TARGET, + grfx::RESOURCE_STATE_PRESENT); + } + + // End command buffer + ppxres = cmd->End(); + if (Failed(ppxres)) { + return ppxres; + } + + // Submit output render command buffer + { + grfx::SubmitInfo submitInfo = {}; + submitInfo.commandBufferCount = 1; + submitInfo.ppCommandBuffers = &cmd; + // submitInfo.waitSemaphoreCount = 1; + // submitInfo.ppWaitSemaphores = &frame.imageAcquiredSemaphore; + // submitInfo.signalSemaphoreCount = 1; + // submitInfo.ppSignalSemaphores = &pRenderCompleteSemaphore; + // submitInfo.pFence = frame.renderCompleteFence; + + PPX_CHECKED_CALL(GetDevice()->GetGraphicsQueue()->Submit(&submitInfo)); + } + } + + return ppx::SUCCESS; +} + +ppx::Result ForwardRenderer::RenderInternal( + scene::RenderOutput* pOutput, + grfx::Semaphore* pRenderCompleteSemaphore) +{ + auto& frame = mFrames[mCurrentFrameIndex]; + + // Wait for and reset render complete fence + // PPX_CHECKED_CALL(frame.renderCompleteFence->WaitAndReset()); + + // Render to output + auto ppxres = RenderToOutput( + frame, + pOutput, + pRenderCompleteSemaphore); + if (Failed(ppxres)) { + return ppxres; + } + + return ppx::SUCCESS; +} + +} // namespace scene +} // namespace ppx diff --git a/src/ppx/scene/scene_renderer.cpp b/src/ppx/scene/scene_renderer.cpp new file mode 100644 index 000000000..69aa63379 --- /dev/null +++ b/src/ppx/scene/scene_renderer.cpp @@ -0,0 +1,292 @@ +#include "ppx/scene/scene_renderer.h" +#include "ppx/grfx/grfx_device.h" +#include "ppx/grfx/grfx_swapchain.h" + +namespace ppx { +namespace scene { + +// ------------------------------------------------------------------------------------------------- +// RendererOutput +// ------------------------------------------------------------------------------------------------- +RenderOutput::RenderOutput( + scene::Renderer* pRenderer) + : mRenderer(pRenderer) +{ +} + +RenderOutput::~RenderOutput() +{ +} + +// ------------------------------------------------------------------------------------------------- +// RendererOutputToImage +// ------------------------------------------------------------------------------------------------- +RenderOutputToImage::RenderOutputToImage( + scene::Renderer* pRenderer, + grfx::Image* pInitialImage) + : scene::RenderOutput(pRenderer), + mImage(pInitialImage) +{ +} + +RenderOutputToImage::~RenderOutputToImage() +{ +} + +ppx::Result RenderOutputToImage::Create( + scene::Renderer* pRenderer, + grfx::Image* pInitialImage, + scene::RenderOutputToImage** ppRendererOutput) +{ + if (IsNull(pRenderer) || IsNull(ppRendererOutput)) { + return ppx::ERROR_UNEXPECTED_NULL_ARGUMENT; + } + + auto pObject = new scene::RenderOutputToImage(pRenderer, pInitialImage); + if (IsNull(pObject)) { + return ppx::ERROR_ALLOCATION_FAILED; + } + + *ppRendererOutput = pObject; + + return ppx::SUCCESS; +} + +void RenderOutputToImage::Destroy(scene::RenderOutputToImage* pRendererOutput) +{ + if (IsNull(pRendererOutput)) { + return; + } + + delete pRendererOutput; +} + +ppx::Result RenderOutputToImage::GetRenderTargetImage( + grfx::Image** ppImage, + grfx::Semaphore* pImageReadySemaphore) +{ + (void)pImageReadySemaphore; + + if (IsNull(ppImage)) { + return ppx::ERROR_UNEXPECTED_NULL_ARGUMENT; + } + + *ppImage = mImage; + + return ppx::SUCCESS; +} + +void RenderOutputToImage::SetImage(grfx::Image* pImage) +{ + mImage = pImage; +} + +// ------------------------------------------------------------------------------------------------- +// RendererOutputToSwapchain +// ------------------------------------------------------------------------------------------------- +RenderOutputToSwapchain::RenderOutputToSwapchain( + scene::Renderer* pRenderer, + grfx::Swapchain* pInitialSwapchain) + : scene::RenderOutput(pRenderer), + mSwapchain(pInitialSwapchain) +{ +} + +RenderOutputToSwapchain::~RenderOutputToSwapchain() +{ +} + +ppx::Result RenderOutputToSwapchain::CreateObject() +{ + // Create fence for image acquisition + { + grfx::FenceCreateInfo createInfo = {}; + + auto ppxres = GetRenderer()->GetDevice()->CreateFence( + &createInfo, + &mFence); + if (Failed(ppxres)) { + return ppxres; + } + } + + return ppx::SUCCESS; +} + +void RenderOutputToSwapchain::DestroyObject() +{ + if (mFence) { + GetRenderer()->GetDevice()->DestroyFence(mFence); + mFence.Reset(); + } +} + +ppx::Result RenderOutputToSwapchain::Create( + scene::Renderer* pRenderer, + grfx::Swapchain* pInitialSwapchain, + scene::RenderOutputToSwapchain** ppRendererOutput) +{ + if (IsNull(pRenderer) || IsNull(ppRendererOutput)) { + return ppx::ERROR_UNEXPECTED_NULL_ARGUMENT; + } + + auto pRendererOutput = new scene::RenderOutputToSwapchain( + pRenderer, + pInitialSwapchain); + if (IsNull(pRendererOutput)) { + return ppx::ERROR_ALLOCATION_FAILED; + } + + auto ppxres = pRendererOutput->CreateObject(); + if (Failed(ppxres)) { + delete pRendererOutput; + return ppxres; + } + + *ppRendererOutput = pRendererOutput; + + return ppx::SUCCESS; +} + +void RenderOutputToSwapchain::Destroy(scene::RenderOutputToSwapchain* pRendererOutput) +{ + if (IsNull(pRendererOutput)) { + return; + } + + pRendererOutput->DestroyObject(); + + delete pRendererOutput; +} + +ppx::Result RenderOutputToSwapchain::GetRenderTargetImage( + grfx::Image** ppImage, + grfx::Semaphore* pImageReadySemaphore) +{ + if (IsNull(ppImage)) { + return ppx::ERROR_UNEXPECTED_NULL_ARGUMENT; + } + + auto ppxres = mSwapchain->AcquireNextImage( + UINT64_MAX, + pImageReadySemaphore, + mFence, + &mImageIndex); + if (Failed(ppxres)) { + return ppxres; + } + + ppxres = mFence->WaitAndReset(); + if (Failed(ppxres)) { + return ppxres; + } + + *ppImage = mSwapchain->GetColorImage(mImageIndex).Get(); + + return ppx::SUCCESS; +} + +void RenderOutputToSwapchain::SetSwapchain(grfx::Swapchain* pSwapchain) +{ + mSwapchain = pSwapchain; +} + +// ------------------------------------------------------------------------------------------------- +// Renderer +// ------------------------------------------------------------------------------------------------- +Renderer::Renderer( + grfx::Device* pDevice, + uint32_t numInFlightFrames) + : mDevice(pDevice), + mNumInFlightFrames(numInFlightFrames) +{ +} + +Renderer::~Renderer() +{ +} + +ppx::Result Renderer::GetRenderOutputRenderPass( + grfx::Image* pImage, + grfx::RenderPass** ppRenderPass) +{ + if (IsNull(pImage) || IsNull(ppRenderPass)) { + return ppx::ERROR_UNEXPECTED_NULL_ARGUMENT; + } + + grfx::RenderPassPtr renderPass = nullptr; + + // Find a render pass for pImage, if there isn't one create it. + auto it = mOutputRenderPasses.find(pImage); + if (it != mOutputRenderPasses.end()) { + renderPass = (*it).second; + } + else { + auto ppxres = CreateOutputRenderPass( + pImage, + &renderPass); + if (Failed(ppxres)) { + return ppxres; + } + + mOutputRenderPasses[pImage] = renderPass; + } + + *ppRenderPass = renderPass.Get(); + + return ppx::SUCCESS; +} + +void Renderer::SetScene(scene::Scene* pScene) +{ + mScene = pScene; +} + +ppx::Result Renderer::Render( + scene::RenderOutput* pOutput, + grfx::Semaphore* pRenderCompleteSemaphore) +{ + if (IsNull(pOutput)) { + return ppx::ERROR_UNEXPECTED_NULL_ARGUMENT; + } + + auto ppxres = this->RenderInternal( + pOutput, + pRenderCompleteSemaphore); + if (Failed(ppxres)) { + return ppxres; + } + + return ppx::SUCCESS; +} + +ppx::Result Renderer::CreateOutputRenderPass( + grfx::Image* pImage, + grfx::RenderPass** ppRenderPass) +{ + if (IsNull(pImage) || IsNull(ppRenderPass)) { + return ppx::ERROR_UNEXPECTED_NULL_ARGUMENT; + } + + grfx::RenderPassCreateInfo3 createInfo = {}; + createInfo.width = pImage->GetWidth(); + createInfo.height = pImage->GetHeight(); + createInfo.renderTargetCount = 1; + createInfo.pRenderTargetImages[0] = pImage; + createInfo.renderTargetClearValues[0] = grfx::RenderTargetClearValue{0, 0, 0, 0}; + createInfo.renderTargetLoadOps[0] = grfx::ATTACHMENT_LOAD_OP_LOAD; + createInfo.renderTargetStoreOps[0] = grfx::ATTACHMENT_STORE_OP_STORE; + + grfx::RenderPassPtr renderPass = nullptr; + auto ppxres = GetDevice()->CreateRenderPass(&createInfo, &renderPass); + if (Failed(ppxres)) { + return ppxres; + } + + *ppRenderPass = renderPass.Get(); + + return ppx::SUCCESS; +} + +} // namespace scene +} // namespace ppx