}
}
-std::vector<Command*> TestGraphicsCommandBuffer::GetCommandsByType(CommandTypeMask mask)
+std::vector<const Command*> TestGraphicsCommandBuffer::GetCommandsByType(CommandTypeMask mask) const
{
- std::vector<Command*> mCommandStack{};
+ std::vector<const Command*> mCommandStack{};
for(auto& cmd : mCommands)
{
if(uint32_t(cmd.type) == (mask & uint32_t(cmd.type)))
return mCommandStack;
}
-std::vector<Command*> TestGraphicsCommandBuffer::GetChildCommandsByType(CommandTypeMask mask)
+std::vector<const Command*> TestGraphicsCommandBuffer::GetChildCommandsByType(CommandTypeMask mask) const
{
- std::vector<Command*> mCommandStack{};
+ std::vector<const Command*> mCommandStack{};
for(auto& cmd : mCommands)
{
if(uint32_t(cmd.type) == (mask & uint32_t(cmd.type)))
struct
{
- std::vector<TestGraphicsCommandBuffer*> buffers;
+ std::vector<const TestGraphicsCommandBuffer*> buffers;
} executeCommandBuffers;
} data;
mCallStack.PushCall("EndRenderPass", "");
}
- void ExecuteCommandBuffers(std::vector<CommandBuffer*>&& commandBuffers) override
+ void ExecuteCommandBuffers(std::vector<const CommandBuffer*>&& commandBuffers) override
{
mCommands.emplace_back();
auto& cmd = mCommands.back();
cmd.data.executeCommandBuffers.buffers.reserve(commandBuffers.size());
for(auto&& item : commandBuffers)
{
- cmd.data.executeCommandBuffers.buffers.emplace_back(static_cast<TestGraphicsCommandBuffer*>(item));
+ cmd.data.executeCommandBuffers.buffers.emplace_back(static_cast<const TestGraphicsCommandBuffer*>(item));
}
mCallStack.PushCall("ExecuteCommandBuffers", "");
}
/**
* Retrieves commands of specified type
*/
- std::vector<Command*> GetCommandsByType(CommandTypeMask mask);
+ std::vector<const Command*> GetCommandsByType(CommandTypeMask mask) const;
- std::vector<Command*> GetChildCommandsByType(CommandTypeMask mask);
+ std::vector<const Command*> GetChildCommandsByType(CommandTypeMask mask) const;
private:
TraceCallStack& mCallStack;
case CommandType::BIND_PIPELINE:
{
currentPipeline = Uncast<TestGraphicsPipeline>(cmd.data.bindPipeline.pipeline);
-
- // Bind framebuffer if different. @todo Move to RenderPass
- auto framebuffer = currentPipeline->framebufferState.framebuffer;
- if(framebuffer && framebuffer != currentFramebuffer)
- {
- auto graphicsFramebuffer = Uncast<TestGraphicsFramebuffer>(framebuffer);
- graphicsFramebuffer->Bind();
- }
- else
- {
- if(currentFramebuffer)
- currentFramebuffer->Bind();
- else
- mGl.BindFramebuffer(GL_FRAMEBUFFER, 0);
- }
BindPipeline(currentPipeline);
break;
}
// Process secondary command buffers
for(auto& buf : cmd.data.executeCommandBuffers.buffers)
{
- ProcessCommandBuffer(*static_cast<TestGraphicsCommandBuffer*>(buf));
+ ProcessCommandBuffer(*Uncast<TestGraphicsCommandBuffer>(buf));
}
break;
}
};
std::vector<ProgramCache> mProgramCache;
+ struct PipelineCache
+ {
+ };
+
std::vector<UniformData> mCustomUniforms;
};
*
* @param[in] commandBuffers List of buffers to execute
*/
- virtual void ExecuteCommandBuffers( std::vector<CommandBuffer*>&& commandBuffers ) = 0;
+ virtual void ExecuteCommandBuffers(std::vector<const CommandBuffer*>&& commandBuffers) = 0;
/**
* @brief Draw primitives
CommandBuffer(CommandBuffer&&) = default;
CommandBuffer& operator=(CommandBuffer&&) = default;
};
-} // Namespace Dali
+} // namespace Dali::Graphics
#endif
* A stack of scissor clips at each depth of clipping is maintained, so it can be applied and unapplied.
* As the clips are hierarchical, this RenderItems AABB is clipped against the current "active" scissor bounds via an intersection operation.
* @param[in] item The current RenderItem about to be rendered
+ * @param[in,out] commandBuffer The command buffer to write into
* @param[in] context The context
* @param[in] instruction The render-instruction to process.
*/
-inline void RenderAlgorithms::SetupScissorClipping(const RenderItem& item, Context& context, const RenderInstruction& instruction)
+inline void RenderAlgorithms::SetupScissorClipping(
+ const RenderItem& item,
+ Graphics::CommandBuffer& commandBuffer,
+ Context& context,
+ const RenderInstruction& instruction)
{
// Get the number of child scissors in the stack (do not include layer or root box).
size_t childStackDepth = mScissorStack.size() - 1u;
// Enable the scissor test based on the above calculation
if(scissorEnabled)
{
- mGraphicsCommandBuffer->SetScissorTestEnable( scissorEnabled );
+ commandBuffer.SetScissorTestEnable(scissorEnabled);
}
// If scissor is enabled, we use the calculated screen-space coordinates (now in the stack).
{
useScissorBox.y = (instruction.mFrameBuffer->GetHeight() - useScissorBox.height) - useScissorBox.y;
}
- Graphics::Rect2D scissorBox = {
- useScissorBox.x, useScissorBox.y,
- uint32_t(useScissorBox.width), uint32_t(useScissorBox.height)
- };
- mGraphicsCommandBuffer->SetScissor( scissorBox );
+ Graphics::Rect2D scissorBox = {
+ useScissorBox.x, useScissorBox.y, uint32_t(useScissorBox.width), uint32_t(useScissorBox.height)};
+ commandBuffer.SetScissor(scissorBox);
}
}
}
inline void RenderAlgorithms::SetupClipping(const RenderItem& item,
+ Graphics::CommandBuffer& commandBuffer,
Context& context,
bool& usedStencilBuffer,
uint32_t& lastClippingDepth,
// As both scissor and stencil clips can be nested, we may be simultaneously traversing up the scissor tree, requiring a scissor to be un-done. Whilst simultaneously adding a new stencil clip.
// We process both based on our current and old clipping depths for each mode.
// Both methods with return rapidly if there is nothing to be done for that type of clipping.
- SetupScissorClipping(item, context, instruction);
+ SetupScissorClipping(item, commandBuffer, context, instruction);
if(stencilBufferAvailable == Integration::StencilBufferAvailable::TRUE)
{
bool firstDepthBufferUse(true);
mViewportRectangle = viewport;
- mGraphicsCommandBuffer->SetViewport(ViewportFromClippingBox(mViewportRectangle, orientation));
+
+ auto* mutableRenderList = const_cast<RenderList*>(&renderList);
+ auto& secondaryCommandBuffer = mutableRenderList->GetCommandBuffer(mGraphicsController);
+ secondaryCommandBuffer.Reset();
+
+ secondaryCommandBuffer.SetViewport(ViewportFromClippingBox(mViewportRectangle, orientation));
mHasLayerScissor = false;
// Setup Scissor testing (for both viewport and per-node scissor)
if(!rootClippingRect.IsEmpty())
{
Graphics::Viewport graphicsViewport = ViewportFromClippingBox(mViewportRectangle, 0);
- mGraphicsCommandBuffer->SetScissorTestEnable(true);
- mGraphicsCommandBuffer->SetScissor(Rect2DFromRect(rootClippingRect, orientation, graphicsViewport));
+ secondaryCommandBuffer.SetScissorTestEnable(true);
+ secondaryCommandBuffer.SetScissor(Rect2DFromRect(rootClippingRect, orientation, graphicsViewport));
mScissorStack.push_back(rootClippingRect);
}
// We are not performing a layer clip and no clipping rect set. Add the viewport as the root scissor rectangle.
else if(!renderList.IsClipping())
{
- mGraphicsCommandBuffer->SetScissorTestEnable(false);
+ secondaryCommandBuffer.SetScissorTestEnable(false);
mScissorStack.push_back(mViewportRectangle);
}
if(renderList.IsClipping())
{
Graphics::Viewport graphicsViewport = ViewportFromClippingBox(mViewportRectangle, 0);
- mGraphicsCommandBuffer->SetScissorTestEnable(true);
+ secondaryCommandBuffer.SetScissorTestEnable(true);
const ClippingBox& layerScissorBox = renderList.GetClippingBox();
- mGraphicsCommandBuffer->SetScissor(Rect2DFromClippingBox(layerScissorBox, orientation, graphicsViewport));
+ secondaryCommandBuffer.SetScissor(Rect2DFromClippingBox(layerScissorBox, orientation, graphicsViewport));
mScissorStack.push_back(layerScissorBox);
mHasLayerScissor = true;
}
- mGraphicsRenderItemCommandBuffers.clear();
- // Loop through all RenderList in the RenderList, set up any prerequisites to render them, then perform the render.
+ // Loop through all RenderItems in the RenderList, set up any prerequisites to render them, then perform the render.
for(uint32_t index = 0u; index < count; ++index)
{
const RenderItem& item = renderList.GetItem(index);
// Set up clipping based on both the Renderer and Actor APIs.
// The Renderer API will be used if specified. If AUTO, the Actors automatic clipping feature will be used.
- SetupClipping(item, context, usedStencilBuffer, lastClippingDepth, lastClippingId, stencilBufferAvailable, instruction);
+ SetupClipping(item, secondaryCommandBuffer, context, usedStencilBuffer, lastClippingDepth, lastClippingId, stencilBufferAvailable, instruction);
if(DALI_LIKELY(item.mRenderer))
{
auto const MAX_QUEUE = item.mRenderer->GetDrawCommands().empty() ? 1 : DevelRenderer::RENDER_QUEUE_MAX;
for(auto queue = 0u; queue < MAX_QUEUE; ++queue)
{
- // Render the item. If rendered, add its command buffer into the list
- if( item.mRenderer->Render(context, bufferIndex, *item.mNode, item.mModelMatrix, item.mModelViewMatrix, viewMatrix, projectionMatrix, item.mSize, !item.mIsOpaque, boundTextures, instruction, queue) )
- {
- mGraphicsRenderItemCommandBuffers.emplace_back( item.mRenderer->GetGraphicsCommandBuffer() );
- }
+ // Render the item. It will write into the command buffer everything it has to render
+ item.mRenderer->Render(secondaryCommandBuffer, bufferIndex, *item.mNode, item.mModelMatrix, item.mModelViewMatrix, viewMatrix, projectionMatrix, item.mSize, !item.mIsOpaque, boundTextures, instruction, queue);
}
}
}
}
-
- // Execute command buffers
- if(!mGraphicsRenderItemCommandBuffers.empty())
- {
- mGraphicsCommandBuffer->ExecuteCommandBuffers( std::move(mGraphicsRenderItemCommandBuffers) );
- }
}
RenderAlgorithms::RenderAlgorithms(Graphics::Controller& graphicsController)
{
mGraphicsCommandBuffer = mGraphicsController.CreateCommandBuffer(
Graphics::CommandBufferCreateInfo()
- .SetLevel(Graphics::CommandBufferLevel::SECONDARY),
+ .SetLevel(Graphics::CommandBufferLevel::PRIMARY),
nullptr);
}
else
{
mGraphicsCommandBuffer->Reset();
}
-
- // Reset list of secondary buffers to submit
- mGraphicsRenderItemCommandBuffers.clear();
}
void RenderAlgorithms::SubmitCommandBuffer()
{
// Submit main command buffer
Graphics::SubmitInfo submitInfo;
- submitInfo.cmdBuffer.push_back( mGraphicsCommandBuffer.get() );
+ submitInfo.cmdBuffer.push_back(mGraphicsCommandBuffer.get());
submitInfo.flags = 0 | Graphics::SubmitFlagBits::FLUSH;
- mGraphicsController.SubmitCommandBuffers( submitInfo );
+ mGraphicsController.SubmitCommandBuffers(submitInfo);
}
void RenderAlgorithms::ProcessRenderInstruction(const RenderInstruction& instruction,
if(viewMatrix && projectionMatrix)
{
- const RenderListContainer::SizeType count = instruction.RenderListCount();
+ std::vector<const Graphics::CommandBuffer*> buffers;
+ const RenderListContainer::SizeType count = instruction.RenderListCount();
// Iterate through each render list in order. If a pair of render lists
// are marked as interleaved, then process them together.
viewport,
rootClippingRect,
orientation);
+
+ // Execute command buffer
+ auto* commandBuffer = renderList->GetCommandBuffer();
+ if(commandBuffer)
+ {
+ buffers.push_back(commandBuffer);
+ }
}
}
+ if(buffers.size() > 0)
+ {
+ mGraphicsCommandBuffer->ExecuteCommandBuffers(std::move(buffers));
+ }
}
}
* - If the node is a clipping node, apply the nodes clip intersected with the current/parent scissor clip.
* - If we have gone up the scissor hierarchy, and need to un-apply a scissor clip.
* - Disable scissor clipping completely if it is not needed
- * @param[in] item The current RenderItem (about to be rendered)
- * @param[in] context The current Context
- * @param[in] instruction The render-instruction to process.
+ * @param[in] item The current RenderItem (about to be rendered)
+ * @param[in] commandBuffer The command buffer to write into
+ * @param[in] context The current Context
+ * @param[in] instruction The render-instruction to process.
*/
- inline void SetupScissorClipping(const Dali::Internal::SceneGraph::RenderItem& item, Context& context, const Dali::Internal::SceneGraph::RenderInstruction& instruction);
+ inline void SetupScissorClipping(
+ const Dali::Internal::SceneGraph::RenderItem& item,
+ Graphics::CommandBuffer& commandBuffer,
+ Context& context,
+ const Dali::Internal::SceneGraph::RenderInstruction& instruction);
/**
* @brief Set up the clipping based on the specified clipping settings.
* @param[in] item The current RenderItem (about to be rendered)
* @param[in] context The context
+ * @param[in,out] commandBuffer The command buffer to write commands to
* @param[in/out] usedStencilBuffer True if the stencil buffer has been used so far within this RenderList. Used by StencilMode::ON.
* @param[in/out] lastClippingDepth The stencil depth of the last renderer drawn. Used by the clipping feature.
* @param[in/out] lastClippingId The clipping ID of the last renderer drawn. Used by the clipping feature.
* @param[in] instruction The render-instruction to process.
*/
inline void SetupClipping(const Dali::Internal::SceneGraph::RenderItem& item,
+ Graphics::CommandBuffer& commandBuffer,
Context& context,
bool& usedStencilBuffer,
uint32_t& lastClippingDepth,
// INTERNAL INCLUDES
#include <dali/devel-api/common/owner-container.h>
+#include <dali/graphics-api/graphics-controller.h>
#include <dali/internal/render/common/render-item.h>
#include <dali/public-api/math/rect.h>
return mHasColorRenderItems;
}
+ Graphics::CommandBuffer& GetCommandBuffer(Graphics::Controller& controller)
+ {
+ if(!mGraphicsCommandBuffer)
+ {
+ mGraphicsCommandBuffer = controller.CreateCommandBuffer(
+ Graphics::CommandBufferCreateInfo().SetLevel(Graphics::CommandBufferLevel::SECONDARY), nullptr);
+ }
+ return *mGraphicsCommandBuffer.get();
+ }
+
+ const Graphics::CommandBuffer* GetCommandBuffer() const
+ {
+ return mGraphicsCommandBuffer.get();
+ }
+
private:
/*
* Copy constructor and assignment operator not defined
RenderItemContainer mItems; ///< Each item is a renderer and matrix pair
uint32_t mNextFree; ///< index for the next free item to use
+ mutable Graphics::UniquePtr<Graphics::CommandBuffer> mGraphicsCommandBuffer{nullptr};
+
ClippingBox* mClippingBox; ///< The clipping box, in window coordinates, when clipping is enabled
Layer* mSourceLayer; ///< The originating layer where the renderers are from
bool mHasColorRenderItems : 1; ///< True if list contains color render items
#include <dali/internal/render/common/render-instruction.h>
#include <dali/internal/render/data-providers/node-data-provider.h>
#include <dali/internal/render/data-providers/uniform-map-data-provider.h>
-#include <dali/internal/render/gl-resources/context.h>
#include <dali/internal/render/renderers/render-sampler.h>
#include <dali/internal/render/renderers/render-texture.h>
#include <dali/internal/render/renderers/render-vertex-buffer.h>
mGeometry->Upload(*mGraphicsController);
}
-bool Renderer::Render(Context& context,
+bool Renderer::Render(Graphics::CommandBuffer& commandBuffer,
BufferIndex bufferIndex,
const SceneGraph::NodeDataProvider& node,
const Matrix& modelMatrix,
return false;
}
- // Create command buffer if not present
- if(!mGraphicsCommandBuffer)
- {
- mGraphicsCommandBuffer = mGraphicsController->CreateCommandBuffer(
- Graphics::CommandBufferCreateInfo()
- .SetLevel(Graphics::CommandBufferLevel::SECONDARY),
- nullptr);
- }
- else
- {
- mGraphicsCommandBuffer->Reset();
- }
-
- auto& commandBuffer = mGraphicsCommandBuffer;
-
// Set blending mode
if(!mDrawCommands.empty())
{
return false;
}
- // Temporarily create a pipeline here - this will be used for transporting
- // topology, vertex format, attrs, rasterization state
- mGraphicsPipeline = PrepareGraphicsPipeline(*program, instruction, blend, std::move(mGraphicsPipeline));
+ // Prepare the graphics pipeline. This may either re-use an existing pipeline or create a new one.
+ auto& pipeline = PrepareGraphicsPipeline(*program, instruction, node, blend);
- commandBuffer->BindPipeline(*mGraphicsPipeline.get());
+ commandBuffer.BindPipeline(pipeline);
- BindTextures(*commandBuffer.get(), boundTextures);
+ BindTextures(commandBuffer, boundTextures);
BuildUniformIndexMap(bufferIndex, node, size, *program);
- WriteUniformBuffer(bufferIndex, *commandBuffer.get(), program, instruction, node, modelMatrix, modelViewMatrix, viewMatrix, projectionMatrix, size);
+ WriteUniformBuffer(bufferIndex, commandBuffer, program, instruction, node, modelMatrix, modelViewMatrix, viewMatrix, projectionMatrix, size);
bool drawn = false; // Draw can fail if there are no vertex buffers or they haven't been uploaded yet
// @todo We should detect this case much earlier to prevent unnecessary work
- //@todo manage mDrawCommands in the same way as above command buffer?!
if(mDrawCommands.empty())
{
- drawn = mGeometry->Draw(*mGraphicsController, *commandBuffer.get(), mIndexedDrawFirstElement, mIndexedDrawElementsCount);
+ drawn = mGeometry->Draw(*mGraphicsController, commandBuffer, mIndexedDrawFirstElement, mIndexedDrawElementsCount);
}
else
{
for(auto& cmd : commands)
{
- // @todo This should generate a command buffer per cmd
- // Tests WILL fail. (Temporarily commented out)
- mGeometry->Draw(*mGraphicsController, *commandBuffer.get(), cmd->firstIndex, cmd->elementCount);
+ mGeometry->Draw(*mGraphicsController, commandBuffer, cmd->firstIndex, cmd->elementCount);
}
}
- // Command buffer contains Texture bindings, vertex bindings, index buffer binding, pipeline(vertex format)
- // @todo We should return the command buffer(s) and let the calling method submit
- // If not drawn, then don't add command buffer to submit info, and if empty, don't
- // submit.
- /*
- if(drawn)
- {
- Graphics::SubmitInfo submitInfo{{}, 0 | Graphics::SubmitFlagBits::FLUSH};
- submitInfo.cmdBuffer.push_back(commandBuffer.get());
- mGraphicsController->SubmitCommandBuffers(submitInfo);
- }
- */
mUpdated = false;
return drawn;
}
return false;
}
-Graphics::UniquePtr<Graphics::Pipeline> Renderer::PrepareGraphicsPipeline(
+Graphics::Pipeline& Renderer::PrepareGraphicsPipeline(
Program& program,
const Dali::Internal::SceneGraph::RenderInstruction& instruction,
- bool blend,
- Graphics::UniquePtr<Graphics::Pipeline>&& oldPipeline)
+ const SceneGraph::NodeDataProvider& node,
+ bool blend)
{
Graphics::InputAssemblyState inputAssemblyState{};
Graphics::VertexInputState vertexInputState{};
mUpdated = true;
- // Create a new pipeline
- // @todo Passed as pointers - shallow copy will break. Implementation MUST deep copy.
- return mGraphicsController->CreatePipeline(
- Graphics::PipelineCreateInfo()
- .SetInputAssemblyState(&inputAssemblyState)
- .SetVertexInputState(&vertexInputState)
- .SetRasterizationState(&rasterizationState)
- .SetColorBlendState(&colorBlendState)
- .SetProgramState(&programState)
- .SetNextExtension(&mLegacyProgram),
- std::move(oldPipeline));
+ // Create the pipeline
+ Graphics::PipelineCreateInfo createInfo;
+ createInfo
+ .SetInputAssemblyState(&inputAssemblyState)
+ .SetVertexInputState(&vertexInputState)
+ .SetRasterizationState(&rasterizationState)
+ .SetColorBlendState(&colorBlendState)
+ .SetProgramState(&programState)
+ .SetNextExtension(&mLegacyProgram);
+
+ // Store a pipeline per renderer per render (renderer can be owned by multiple nodes,
+ // and re-drawn in multiple instructions).
+ // @todo This is only needed because ColorBlend state can change. Fixme!
+ // This is ameliorated by the fact that implementation caches pipelines, and we're only storing
+ // handles.
+ auto hash = HashedPipeline::GetHash(&node, &instruction, blend);
+ HashedPipeline* hashedPipeline = nullptr;
+ for(auto& element : mGraphicsPipelines)
+ {
+ if(element.mHash == hash)
+ {
+ hashedPipeline = &element;
+ break;
+ }
+ }
+
+ if(hashedPipeline != nullptr)
+ {
+ hashedPipeline->mGraphicsPipeline = mGraphicsController->CreatePipeline(
+ createInfo,
+ std::move(hashedPipeline->mGraphicsPipeline));
+ }
+ else
+ {
+ mGraphicsPipelines.emplace_back();
+ mGraphicsPipelines.back().mHash = hash;
+ mGraphicsPipelines.back().mGraphicsPipeline = mGraphicsController->CreatePipeline(createInfo, nullptr);
+ hashedPipeline = &mGraphicsPipelines.back();
+ }
+ return *hashedPipeline->mGraphicsPipeline.get();
}
} // namespace Render
/**
* Called to render during RenderManager::Render().
- * @param[in] context The context used for rendering
+ * @param[in,out] commandBuffer The command buffer to write into
* @param[in] bufferIndex The index of the previous update buffer.
* @param[in] node The node using this renderer
* @param[in] modelViewMatrix The model-view matrix.
*
* @return True if the content has been rendered, false if skipped.
*/
- bool Render(Context& context,
+ bool Render(Graphics::CommandBuffer& commandBuffer,
BufferIndex bufferIndex,
const SceneGraph::NodeDataProvider& node,
const Matrix& modelMatrix,
void BindTextures(Graphics::CommandBuffer& commandBuffer, Vector<Graphics::Texture*>& boundTextures);
/**
- * Prepare a pipeline for this renderer
+ * Prepare a pipeline for this renderer.
+ *
+ * As a renderer can be re-used in a single frame (e.g. being used by multiple nodes, or
+ * by non-exclusive render tasks), we store a pipeline per node/instruction.
+ * In practice, the implementation will cached pipelines, so we normally only have
+ * multiple handles.
*/
- Graphics::UniquePtr<Graphics::Pipeline> PrepareGraphicsPipeline(
+ Graphics::Pipeline& PrepareGraphicsPipeline(
Program& program,
const Dali::Internal::SceneGraph::RenderInstruction& instruction,
- bool blend,
- Graphics::UniquePtr<Graphics::Pipeline>&& oldPipeline);
+ const SceneGraph::NodeDataProvider& node,
+ bool blend);
/**
* Setup and write data to the uniform buffer
Render::UniformBufferManager* mUniformBufferManager{};
std::vector<Graphics::UniformBufferBinding> mUniformBufferBindings{};
- Graphics::UniquePtr<Graphics::Pipeline> mGraphicsPipeline{}; ///< The graphics pipeline. (Cached implementation)
- std::vector<Graphics::ShaderState> mShaderStates{};
+ std::vector<Graphics::ShaderState> mShaderStates{};
using Hash = unsigned long;
struct UniformIndexMap
UniformIndexMappings mUniformIndexMap;
Vector<int32_t> mAttributeLocations;
+ uint64_t mUniformsHash;
- uint64_t mUniformsHash;
+ struct HashedPipeline
+ {
+ uint64_t mHash{0u};
+ Graphics::UniquePtr<Graphics::Pipeline> mGraphicsPipeline{nullptr};
+ inline static uint64_t GetHash(const void* node, const void* instruction, bool blend)
+ {
+ return (reinterpret_cast<uint64_t>(node) << 32) | ((reinterpret_cast<uint64_t>(instruction) & 0xFFFFFFF) << 1) | blend;
+ }
+ };
+ std::vector<HashedPipeline> mGraphicsPipelines{};
StencilParameters mStencilParameters; ///< Struct containing all stencil related options
BlendingOptions mBlendingOptions; ///< Blending options including blend color, blend func and blend equation