// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter/lib/gpu/render_pass.h" #include #include #include "flutter/lib/gpu/formats.h" #include "flutter/lib/gpu/render_pipeline.h" #include "flutter/lib/gpu/shader.h" #include "fml/make_copyable.h" #include "fml/memory/ref_ptr.h" #include "impeller/core/buffer_view.h" #include "impeller/core/formats.h" #include "impeller/core/sampler_descriptor.h" #include "impeller/core/shader_types.h" #include "impeller/core/vertex_buffer.h" #include "impeller/geometry/color.h" #include "impeller/renderer/pipeline.h" #include "impeller/renderer/pipeline_descriptor.h" #include "impeller/renderer/pipeline_library.h" #include "lib/ui/ui_dart_state.h" #include "tonic/converter/dart_converter.h" namespace flutter { namespace gpu { IMPLEMENT_WRAPPERTYPEINFO(flutter_gpu, RenderPass); RenderPass::RenderPass() : vertex_buffer_( impeller::VertexBuffer{.index_type = impeller::IndexType::kNone}){}; RenderPass::~RenderPass() = default; const std::shared_ptr& RenderPass::GetContext() const { return render_pass_->GetContext(); } impeller::Command& RenderPass::GetCommand() { return command_; } const impeller::Command& RenderPass::GetCommand() const { return command_; } impeller::RenderTarget& RenderPass::GetRenderTarget() { return render_target_; } const impeller::RenderTarget& RenderPass::GetRenderTarget() const { return render_target_; } impeller::ColorAttachmentDescriptor& RenderPass::GetColorAttachmentDescriptor( size_t color_attachment_index) { auto color = color_descriptors_.find(color_attachment_index); if (color == color_descriptors_.end()) { return color_descriptors_[color_attachment_index] = {}; } return color->second; } impeller::DepthAttachmentDescriptor& RenderPass::GetDepthAttachmentDescriptor() { return depth_desc_; } impeller::StencilAttachmentDescriptor& RenderPass::GetStencilFrontAttachmentDescriptor() { return stencil_front_desc_; } impeller::StencilAttachmentDescriptor& RenderPass::GetStencilBackAttachmentDescriptor() { return stencil_back_desc_; } impeller::VertexBuffer& RenderPass::GetVertexBuffer() { return vertex_buffer_; } impeller::PipelineDescriptor& RenderPass::GetPipelineDescriptor() { return pipeline_descriptor_; } bool RenderPass::Begin(flutter::gpu::CommandBuffer& command_buffer) { render_pass_ = command_buffer.GetCommandBuffer()->CreateRenderPass(render_target_); if (!render_pass_) { return false; } command_buffer.AddRenderPass(render_pass_); return true; } void RenderPass::SetPipeline(fml::RefPtr pipeline) { render_pipeline_ = std::move(pipeline); } std::shared_ptr> RenderPass::GetOrCreatePipeline() { // Infer the pipeline layout based on the shape of the RenderTarget. auto pipeline_desc = pipeline_descriptor_; for (const auto& it : render_target_.GetColorAttachments()) { auto& color = GetColorAttachmentDescriptor(it.first); color.format = render_target_.GetRenderTargetPixelFormat(); } pipeline_desc.SetColorAttachmentDescriptors(color_descriptors_); { auto stencil = render_target_.GetStencilAttachment(); if (stencil && impeller::IsStencilWritable( stencil->texture->GetTextureDescriptor().format)) { pipeline_desc.SetStencilPixelFormat( stencil->texture->GetTextureDescriptor().format); pipeline_desc.SetStencilAttachmentDescriptors(stencil_front_desc_, stencil_back_desc_); } else { pipeline_desc.ClearStencilAttachments(); } } { auto depth = render_target_.GetDepthAttachment(); if (depth && impeller::IsDepthWritable( depth->texture->GetTextureDescriptor().format)) { pipeline_desc.SetDepthPixelFormat( depth->texture->GetTextureDescriptor().format); pipeline_desc.SetDepthStencilAttachmentDescriptor(depth_desc_); } else { pipeline_desc.ClearDepthAttachment(); } } auto& context = *GetContext(); render_pipeline_->BindToPipelineDescriptor(*context.GetShaderLibrary(), pipeline_desc); std::shared_ptr> pipeline; if (context.GetBackendType() == impeller::Context::BackendType::kOpenGLES && !context.GetPipelineLibrary()->HasPipeline(pipeline_desc)) { // For GLES, new pipeline creation must be done on the reactor (raster) // thread. We're about the draw, so we need to synchronize with a raster // task in order to get the new pipeline. Depending on how busy the raster // thread is, this could hang the UI thread long enough to miss a frame. // Note that this branch is only called if a new pipeline actually needs to // be built. auto dart_state = flutter::UIDartState::Current(); std::promise< std::shared_ptr>> pipeline_promise; auto pipeline_future = pipeline_promise.get_future(); fml::TaskRunner::RunNowOrPostTask( dart_state->GetTaskRunners().GetRasterTaskRunner(), fml::MakeCopyable([promise = std::move(pipeline_promise), context = GetContext(), pipeline_desc]() mutable { promise.set_value( context->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get()); })); pipeline = pipeline_future.get(); } else { pipeline = context.GetPipelineLibrary()->GetPipeline(pipeline_desc).Get(); } FML_DCHECK(pipeline) << "Couldn't resolve render pipeline"; return pipeline; } impeller::Command RenderPass::ProvisionRasterCommand() { impeller::Command result = command_; result.pipeline = GetOrCreatePipeline(); result.BindVertices(vertex_buffer_); return result; } bool RenderPass::Draw() { impeller::Command result = ProvisionRasterCommand(); #ifdef IMPELLER_DEBUG render_pass_->SetCommandLabel(result.label); #endif // IMPELLER_DEBUG render_pass_->SetPipeline(result.pipeline); render_pass_->SetStencilReference(result.stencil_reference); render_pass_->SetBaseVertex(result.base_vertex); if (result.viewport.has_value()) { render_pass_->SetViewport(result.viewport.value()); } if (result.scissor.has_value()) { render_pass_->SetScissor(result.scissor.value()); } render_pass_->SetVertexBuffer(result.vertex_buffer); for (const auto& buffer : result.vertex_bindings.buffers) { render_pass_->BindResource(impeller::ShaderStage::kVertex, impeller::DescriptorType::kUniformBuffer, buffer.slot, *buffer.view.GetMetadata(), buffer.view.resource); } for (const auto& texture : result.vertex_bindings.sampled_images) { render_pass_->BindResource(impeller::ShaderStage::kVertex, impeller::DescriptorType::kSampledImage, texture.slot, *texture.texture.GetMetadata(), texture.texture.resource, texture.sampler); } for (const auto& buffer : result.fragment_bindings.buffers) { render_pass_->BindResource(impeller::ShaderStage::kFragment, impeller::DescriptorType::kUniformBuffer, buffer.slot, *buffer.view.GetMetadata(), buffer.view.resource); } for (const auto& texture : result.fragment_bindings.sampled_images) { render_pass_->BindResource(impeller::ShaderStage::kFragment, impeller::DescriptorType::kSampledImage, texture.slot, *texture.texture.GetMetadata(), texture.texture.resource, texture.sampler); } return render_pass_->Draw().ok(); } } // namespace gpu } // namespace flutter //---------------------------------------------------------------------------- /// Exports /// void InternalFlutterGpu_RenderPass_Initialize(Dart_Handle wrapper) { auto res = fml::MakeRefCounted(); res->AssociateWithDartWrapper(wrapper); } Dart_Handle InternalFlutterGpu_RenderPass_SetColorAttachment( flutter::gpu::RenderPass* wrapper, int color_attachment_index, int load_action, int store_action, float clear_color_r, float clear_color_g, float clear_color_b, float clear_color_a, flutter::gpu::Texture* texture, Dart_Handle resolve_texture_wrapper) { impeller::ColorAttachment desc; desc.load_action = flutter::gpu::ToImpellerLoadAction(load_action); desc.store_action = flutter::gpu::ToImpellerStoreAction(store_action); desc.clear_color = impeller::Color(clear_color_r, clear_color_g, clear_color_b, clear_color_a); desc.texture = texture->GetTexture(); if (!Dart_IsNull(resolve_texture_wrapper)) { flutter::gpu::Texture* resolve_texture = tonic::DartConverter::FromDart( resolve_texture_wrapper); desc.resolve_texture = resolve_texture->GetTexture(); } wrapper->GetRenderTarget().SetColorAttachment(desc, color_attachment_index); return Dart_Null(); } Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment( flutter::gpu::RenderPass* wrapper, int depth_load_action, int depth_store_action, float depth_clear_value, int stencil_load_action, int stencil_store_action, int stencil_clear_value, flutter::gpu::Texture* texture) { { impeller::DepthAttachment desc; desc.load_action = flutter::gpu::ToImpellerLoadAction(depth_load_action); desc.store_action = flutter::gpu::ToImpellerStoreAction(depth_store_action); desc.clear_depth = depth_clear_value; desc.texture = texture->GetTexture(); wrapper->GetRenderTarget().SetDepthAttachment(desc); } { impeller::StencilAttachment desc; desc.load_action = flutter::gpu::ToImpellerLoadAction(stencil_load_action); desc.store_action = flutter::gpu::ToImpellerStoreAction(stencil_store_action); desc.clear_stencil = stencil_clear_value; desc.texture = texture->GetTexture(); wrapper->GetRenderTarget().SetStencilAttachment(desc); } return Dart_Null(); } Dart_Handle InternalFlutterGpu_RenderPass_Begin( flutter::gpu::RenderPass* wrapper, flutter::gpu::CommandBuffer* command_buffer) { if (!wrapper->Begin(*command_buffer)) { return tonic::ToDart("Failed to begin RenderPass"); } return Dart_Null(); } void InternalFlutterGpu_RenderPass_BindPipeline( flutter::gpu::RenderPass* wrapper, flutter::gpu::RenderPipeline* pipeline) { auto ref = fml::RefPtr(pipeline); wrapper->SetPipeline(std::move(ref)); } template static void BindVertexBuffer(flutter::gpu::RenderPass* wrapper, TBuffer buffer, int offset_in_bytes, int length_in_bytes, int vertex_count) { auto& vertex_buffer = wrapper->GetVertexBuffer(); vertex_buffer.vertex_buffer = impeller::BufferView{ .buffer = buffer, .range = impeller::Range(offset_in_bytes, length_in_bytes), }; // If the index type is set, then the `vertex_count` becomes the index // count... So don't overwrite the count if it's already been set when binding // the index buffer. // TODO(bdero): Consider just doing a more traditional API with // draw(vertexCount) and drawIndexed(indexCount). This is fine, // but overall it would be a bit more explicit and we wouldn't // have to document this behavior where the presence of the index // buffer always takes precedent. if (vertex_buffer.index_type == impeller::IndexType::kNone) { vertex_buffer.vertex_count = vertex_count; } } void InternalFlutterGpu_RenderPass_BindVertexBufferDevice( flutter::gpu::RenderPass* wrapper, flutter::gpu::DeviceBuffer* device_buffer, int offset_in_bytes, int length_in_bytes, int vertex_count) { BindVertexBuffer(wrapper, device_buffer->GetBuffer(), offset_in_bytes, length_in_bytes, vertex_count); } void InternalFlutterGpu_RenderPass_BindVertexBufferHost( flutter::gpu::RenderPass* wrapper, flutter::gpu::HostBuffer* host_buffer, int offset_in_bytes, int length_in_bytes, int vertex_count) { std::optional view = host_buffer->GetBufferViewForOffset(offset_in_bytes); if (!view.has_value()) { FML_LOG(ERROR) << "Failed to bind vertex buffer due to invalid HostBuffer offset: " << offset_in_bytes; return; } BindVertexBuffer(wrapper, view->buffer, view->range.offset, view->range.length, vertex_count); } template static void BindIndexBuffer(flutter::gpu::RenderPass* wrapper, TBuffer buffer, int offset_in_bytes, int length_in_bytes, int index_type, int index_count) { auto& vertex_buffer = wrapper->GetVertexBuffer(); vertex_buffer.index_buffer = impeller::BufferView{ .buffer = buffer, .range = impeller::Range(offset_in_bytes, length_in_bytes), }; vertex_buffer.index_type = flutter::gpu::ToImpellerIndexType(index_type); vertex_buffer.vertex_count = index_count; } void InternalFlutterGpu_RenderPass_BindIndexBufferDevice( flutter::gpu::RenderPass* wrapper, flutter::gpu::DeviceBuffer* device_buffer, int offset_in_bytes, int length_in_bytes, int index_type, int index_count) { BindIndexBuffer(wrapper, device_buffer->GetBuffer(), offset_in_bytes, length_in_bytes, index_type, index_count); } void InternalFlutterGpu_RenderPass_BindIndexBufferHost( flutter::gpu::RenderPass* wrapper, flutter::gpu::HostBuffer* host_buffer, int offset_in_bytes, int length_in_bytes, int index_type, int index_count) { auto view = host_buffer->GetBufferViewForOffset(offset_in_bytes); if (!view.has_value()) { FML_LOG(ERROR) << "Failed to bind index buffer due to invalid HostBuffer offset: " << offset_in_bytes; return; } BindIndexBuffer(wrapper, view->buffer, view->range.offset, view->range.length, index_type, index_count); } template static bool BindUniform(flutter::gpu::RenderPass* wrapper, flutter::gpu::Shader* shader, Dart_Handle uniform_name_handle, TBuffer buffer, int offset_in_bytes, int length_in_bytes) { auto& command = wrapper->GetCommand(); auto uniform_name = tonic::StdStringFromDart(uniform_name_handle); const flutter::gpu::Shader::UniformBinding* uniform_struct = shader->GetUniformStruct(uniform_name); // TODO(bdero): Return an error string stating that no uniform struct with // this name exists and throw an exception. if (!uniform_struct) { return false; } return command.BindResource( shader->GetShaderStage(), impeller::DescriptorType::kUniformBuffer, uniform_struct->slot, uniform_struct->metadata, impeller::BufferView{ .buffer = buffer, .range = impeller::Range(offset_in_bytes, length_in_bytes), }); } bool InternalFlutterGpu_RenderPass_BindUniformDevice( flutter::gpu::RenderPass* wrapper, flutter::gpu::Shader* shader, Dart_Handle uniform_name_handle, flutter::gpu::DeviceBuffer* device_buffer, int offset_in_bytes, int length_in_bytes) { return BindUniform(wrapper, shader, uniform_name_handle, device_buffer->GetBuffer(), offset_in_bytes, length_in_bytes); } bool InternalFlutterGpu_RenderPass_BindUniformHost( flutter::gpu::RenderPass* wrapper, flutter::gpu::Shader* shader, Dart_Handle uniform_name_handle, flutter::gpu::HostBuffer* host_buffer, int offset_in_bytes, int length_in_bytes) { auto view = host_buffer->GetBufferViewForOffset(offset_in_bytes); if (!view.has_value()) { FML_LOG(ERROR) << "Failed to bind index buffer due to invalid HostBuffer offset: " << offset_in_bytes; return false; } return BindUniform(wrapper, shader, uniform_name_handle, view->buffer, view->range.offset, view->range.length); } bool InternalFlutterGpu_RenderPass_BindTexture( flutter::gpu::RenderPass* wrapper, flutter::gpu::Shader* shader, Dart_Handle uniform_name_handle, flutter::gpu::Texture* texture, int min_filter, int mag_filter, int mip_filter, int width_address_mode, int height_address_mode) { auto& command = wrapper->GetCommand(); auto uniform_name = tonic::StdStringFromDart(uniform_name_handle); const flutter::gpu::Shader::TextureBinding* texture_binding = shader->GetUniformTexture(uniform_name); // TODO(bdero): Return an error string stating that no uniform texture with // this name exists and throw an exception. if (!texture_binding) { return false; } impeller::SamplerDescriptor sampler_desc; sampler_desc.min_filter = flutter::gpu::ToImpellerMinMagFilter(min_filter); sampler_desc.mag_filter = flutter::gpu::ToImpellerMinMagFilter(mag_filter); sampler_desc.mip_filter = flutter::gpu::ToImpellerMipFilter(mip_filter); sampler_desc.width_address_mode = flutter::gpu::ToImpellerSamplerAddressMode(width_address_mode); sampler_desc.height_address_mode = flutter::gpu::ToImpellerSamplerAddressMode(height_address_mode); const std::unique_ptr& sampler = wrapper->GetContext()->GetSamplerLibrary()->GetSampler(sampler_desc); return command.BindResource(shader->GetShaderStage(), impeller::DescriptorType::kSampledImage, texture_binding->slot, texture_binding->metadata, texture->GetTexture(), sampler); } void InternalFlutterGpu_RenderPass_ClearBindings( flutter::gpu::RenderPass* wrapper) { auto& command = wrapper->GetCommand(); command.vertex_buffer = {}; command.vertex_bindings = {}; command.fragment_bindings = {}; } void InternalFlutterGpu_RenderPass_SetColorBlendEnable( flutter::gpu::RenderPass* wrapper, int color_attachment_index, bool enable) { auto& color = wrapper->GetColorAttachmentDescriptor(color_attachment_index); color.blending_enabled = enable; } void InternalFlutterGpu_RenderPass_SetColorBlendEquation( flutter::gpu::RenderPass* wrapper, int color_attachment_index, int color_blend_operation, int source_color_blend_factor, int destination_color_blend_factor, int alpha_blend_operation, int source_alpha_blend_factor, int destination_alpha_blend_factor) { auto& color = wrapper->GetColorAttachmentDescriptor(color_attachment_index); color.color_blend_op = flutter::gpu::ToImpellerBlendOperation(color_blend_operation); color.src_color_blend_factor = flutter::gpu::ToImpellerBlendFactor(source_color_blend_factor); color.dst_color_blend_factor = flutter::gpu::ToImpellerBlendFactor(destination_color_blend_factor); color.alpha_blend_op = flutter::gpu::ToImpellerBlendOperation(alpha_blend_operation); color.src_alpha_blend_factor = flutter::gpu::ToImpellerBlendFactor(source_alpha_blend_factor); color.dst_alpha_blend_factor = flutter::gpu::ToImpellerBlendFactor(destination_alpha_blend_factor); } void InternalFlutterGpu_RenderPass_SetDepthWriteEnable( flutter::gpu::RenderPass* wrapper, bool enable) { auto& depth = wrapper->GetDepthAttachmentDescriptor(); depth.depth_write_enabled = true; } void InternalFlutterGpu_RenderPass_SetDepthCompareOperation( flutter::gpu::RenderPass* wrapper, int compare_operation) { auto& depth = wrapper->GetDepthAttachmentDescriptor(); depth.depth_compare = flutter::gpu::ToImpellerCompareFunction(compare_operation); } void InternalFlutterGpu_RenderPass_SetStencilReference( flutter::gpu::RenderPass* wrapper, int stencil_reference) { auto& command = wrapper->GetCommand(); command.stencil_reference = static_cast(stencil_reference); } void InternalFlutterGpu_RenderPass_SetStencilConfig( flutter::gpu::RenderPass* wrapper, int stencil_compare_operation, int stencil_fail_operation, int depth_fail_operation, int depth_stencil_pass_operation, int read_mask, int write_mask, int target_face) { impeller::StencilAttachmentDescriptor desc; desc.stencil_compare = flutter::gpu::ToImpellerCompareFunction(stencil_compare_operation); desc.stencil_failure = flutter::gpu::ToImpellerStencilOperation(stencil_fail_operation); desc.depth_failure = flutter::gpu::ToImpellerStencilOperation(depth_fail_operation); desc.depth_stencil_pass = flutter::gpu::ToImpellerStencilOperation(depth_stencil_pass_operation); desc.read_mask = static_cast(read_mask); desc.write_mask = static_cast(write_mask); // Corresponds to the `StencilFace` enum in `gpu/lib/src/render_pass.dart`. if (target_face != 2 /* both or front */) { wrapper->GetStencilFrontAttachmentDescriptor() = desc; } if (target_face != 1 /* both or back */) { wrapper->GetStencilBackAttachmentDescriptor() = desc; } } void InternalFlutterGpu_RenderPass_SetCullMode( flutter::gpu::RenderPass* wrapper, int cull_mode) { impeller::PipelineDescriptor& pipeline_descriptor = wrapper->GetPipelineDescriptor(); pipeline_descriptor.SetCullMode(flutter::gpu::ToImpellerCullMode(cull_mode)); } void InternalFlutterGpu_RenderPass_SetPrimitiveType( flutter::gpu::RenderPass* wrapper, int primitive_type) { impeller::PipelineDescriptor& pipeline_descriptor = wrapper->GetPipelineDescriptor(); pipeline_descriptor.SetPrimitiveType( flutter::gpu::ToImpellerPrimitiveType(primitive_type)); } void InternalFlutterGpu_RenderPass_SetWindingOrder( flutter::gpu::RenderPass* wrapper, int winding_order) { impeller::PipelineDescriptor& pipeline_descriptor = wrapper->GetPipelineDescriptor(); pipeline_descriptor.SetWindingOrder( flutter::gpu::ToImpellerWindingOrder(winding_order)); } bool InternalFlutterGpu_RenderPass_Draw(flutter::gpu::RenderPass* wrapper) { return wrapper->Draw(); }