diff --git a/engine/src/flutter/impeller/compositor/render_pass.mm b/engine/src/flutter/impeller/compositor/render_pass.mm index 206aecab551..f390d0cacf7 100644 --- a/engine/src/flutter/impeller/compositor/render_pass.mm +++ b/engine/src/flutter/impeller/compositor/render_pass.mm @@ -4,6 +4,9 @@ #include "impeller/compositor/render_pass.h" +#include +#include + #include "flutter/fml/closure.h" #include "flutter/fml/logging.h" #include "impeller/base/base.h" @@ -176,7 +179,147 @@ bool RenderPass::FinishEncoding(Allocator& transients_allocator) const { return EncodeCommands(transients_allocator, pass); } -static bool Bind(id pass, +//----------------------------------------------------------------------------- +/// @brief Ensures that bindings on the pass are not redundantly set or +/// updated. Avoids making the driver do additional checks and makes +/// the frame insights during profiling and instrumentation not +/// complain about the same. +/// +/// There should be no change to rendering if this caching was +/// absent. +/// +struct PassBindingsCache { + PassBindingsCache(id pass) : pass_(pass) {} + + PassBindingsCache(const PassBindingsCache&) = delete; + + PassBindingsCache(PassBindingsCache&&) = delete; + + void SetRenderPipelineState(id pipeline) { + if (pipeline == pipeline_) { + return; + } + pipeline_ = pipeline; + [pass_ setRenderPipelineState:pipeline_]; + } + + void SetDepthStencilState(id depth_stencil) { + if (depth_stencil_ == depth_stencil) { + return; + } + depth_stencil_ = depth_stencil; + [pass_ setDepthStencilState:depth_stencil_]; + } + + bool SetBuffer(ShaderStage stage, + uint64_t index, + uint64_t offset, + id buffer) { + auto& buffers_map = buffers_[stage]; + auto found = buffers_map.find(index); + if (found != buffers_map.end() && found->second.buffer == buffer) { + // The right buffer is bound. Check if its offset needs to be updated. + if (found->second.offset == offset) { + // Buffer and its offset is identical. Nothing to do. + return true; + } + + // Only the offset needs to be updated. + found->second.offset = offset; + + switch (stage) { + case ShaderStage::kVertex: + [pass_ setVertexBufferOffset:offset atIndex:index]; + return true; + case ShaderStage::kFragment: + [pass_ setFragmentBufferOffset:offset atIndex:index]; + return true; + default: + FML_DCHECK(false) + << "Cannot update buffer offset of an unknown stage."; + return false; + } + return true; + } + buffers_map[index] = {buffer, offset}; + switch (stage) { + case ShaderStage::kVertex: + [pass_ setVertexBuffer:buffer offset:offset atIndex:index]; + return true; + case ShaderStage::kFragment: + [pass_ setFragmentBuffer:buffer offset:offset atIndex:index]; + return true; + default: + FML_DCHECK(false) << "Cannot bind buffer to unknown shader stage."; + return false; + } + return false; + } + + bool SetTexture(ShaderStage stage, uint64_t index, id texture) { + auto& texture_map = textures_[stage]; + auto found = texture_map.find(index); + if (found != texture_map.end() && found->second == texture) { + // Already bound. + return true; + } + texture_map[index] = texture; + switch (stage) { + case ShaderStage::kVertex: + [pass_ setVertexTexture:texture atIndex:index]; + return true; + case ShaderStage::kFragment: + [pass_ setFragmentTexture:texture atIndex:index]; + return true; + default: + FML_DCHECK(false) << "Cannot bind buffer to unknown shader stage."; + return false; + } + return false; + } + + bool SetSampler(ShaderStage stage, + uint64_t index, + id sampler) { + auto& sampler_map = samplers_[stage]; + auto found = sampler_map.find(index); + if (found != sampler_map.end() && found->second == sampler) { + // Already bound. + return true; + } + sampler_map[index] = sampler; + switch (stage) { + case ShaderStage::kVertex: + [pass_ setVertexSamplerState:sampler atIndex:index]; + return true; + case ShaderStage::kFragment: + [pass_ setFragmentSamplerState:sampler atIndex:index]; + return true; + default: + FML_DCHECK(false) << "Cannot bind buffer to unknown shader stage."; + return false; + } + return false; + } + + private: + struct BufferOffsetPair { + id buffer = nullptr; + size_t offset = 0u; + }; + using BufferMap = std::map; + using TextureMap = std::map>; + using SamplerMap = std::map>; + + const id pass_; + id pipeline_ = nullptr; + id depth_stencil_ = nullptr; + std::map buffers_; + std::map textures_; + std::map samplers_; +}; + +static bool Bind(PassBindingsCache& pass, Allocator& allocator, ShaderStage stage, size_t bind_index, @@ -196,25 +339,10 @@ static bool Bind(id pass, return false; } - switch (stage) { - case ShaderStage::kVertex: - [pass setVertexBuffer:buffer offset:view.range.offset atIndex:bind_index]; - return true; - case ShaderStage::kFragment: - [pass setFragmentBuffer:buffer - offset:view.range.offset - atIndex:bind_index]; - return true; - default: - FML_DLOG(ERROR) << "Cannot bind buffer to unknown shader stage."; - return false; - } - - return false; + return pass.SetBuffer(stage, bind_index, view.range.offset, buffer); } -static bool Bind(id pass, - Allocator& allocator, +static bool Bind(PassBindingsCache& pass, ShaderStage stage, size_t bind_index, const Texture& texture) { @@ -222,23 +350,10 @@ static bool Bind(id pass, return false; } - switch (stage) { - case ShaderStage::kVertex: - [pass setVertexTexture:texture.GetMTLTexture() atIndex:bind_index]; - return true; - case ShaderStage::kFragment: - [pass setFragmentTexture:texture.GetMTLTexture() atIndex:bind_index]; - return true; - default: - FML_DLOG(ERROR) << "Cannot bind buffer to unknown shader stage."; - return false; - } - - return false; + return pass.SetTexture(stage, bind_index, texture.GetMTLTexture()); } -static bool Bind(id pass, - Allocator& allocator, +static bool Bind(PassBindingsCache& pass, ShaderStage stage, size_t bind_index, const Sampler& sampler) { @@ -246,48 +361,33 @@ static bool Bind(id pass, return false; } - switch (stage) { - case ShaderStage::kVertex: - [pass setVertexSamplerState:sampler.GetMTLSamplerState() - atIndex:bind_index]; - - return true; - case ShaderStage::kFragment: - [pass setFragmentSamplerState:sampler.GetMTLSamplerState() - atIndex:bind_index]; - return true; - default: - FML_DLOG(ERROR) << "Cannot bind buffer to unknown shader stage."; - return false; - } - - return false; + return pass.SetSampler(stage, bind_index, sampler.GetMTLSamplerState()); } bool RenderPass::EncodeCommands(Allocator& allocator, id pass) const { - // There a numerous opportunities here to ensure bindings are not repeated. - // Stuff like setting the vertex buffer bindings over and over when just the - // offsets could be updated (as recommended in best practices). - auto bind_stage_resources = [&allocator, pass](const Bindings& bindings, - ShaderStage stage) -> bool { + PassBindingsCache pass_bindings(pass); + auto bind_stage_resources = [&allocator, &pass_bindings]( + const Bindings& bindings, + ShaderStage stage) -> bool { for (const auto buffer : bindings.buffers) { - if (!Bind(pass, allocator, stage, buffer.first, buffer.second)) { + if (!Bind(pass_bindings, allocator, stage, buffer.first, buffer.second)) { return false; } } for (const auto texture : bindings.textures) { - if (!Bind(pass, allocator, stage, texture.first, *texture.second)) { + if (!Bind(pass_bindings, stage, texture.first, *texture.second)) { return false; } } for (const auto sampler : bindings.samplers) { - if (!Bind(pass, allocator, stage, sampler.first, *sampler.second)) { + if (!Bind(pass_bindings, stage, sampler.first, *sampler.second)) { return false; } } return true; }; + fml::closure pop_debug_marker = [pass]() { [pass popDebugGroup]; }; for (const auto& command : commands_) { if (command.index_count == 0u) { @@ -300,8 +400,10 @@ bool RenderPass::EncodeCommands(Allocator& allocator, } else { auto_pop_debug_marker.Release(); } - [pass setRenderPipelineState:command.pipeline->GetMTLRenderPipelineState()]; - [pass setDepthStencilState:command.pipeline->GetMTLDepthStencilState()]; + pass_bindings.SetRenderPipelineState( + command.pipeline->GetMTLRenderPipelineState()); + pass_bindings.SetDepthStencilState( + command.pipeline->GetMTLDepthStencilState()); [pass setFrontFacingWinding:MTLWindingClockwise]; [pass setCullMode:MTLCullModeBack]; if (!bind_stage_resources(command.vertex_bindings, ShaderStage::kVertex)) {