| // 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 "flutter/lib/gpu/formats.h" | |
| #include "flutter/lib/gpu/render_pipeline.h" | |
| #include "flutter/lib/gpu/shader.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_library.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<const impeller::Context>& 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::VertexBuffer& RenderPass::GetVertexBuffer() { | |
| return vertex_buffer_; | |
| } | |
| 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<RenderPipeline> pipeline) { | |
| render_pipeline_ = std::move(pipeline); | |
| } | |
| std::shared_ptr<impeller::Pipeline<impeller::PipelineDescriptor>> | |
| 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); | |
| auto 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 | |
| static impeller::Color ToImpellerColor(uint32_t argb) { | |
| return impeller::Color::MakeRGBA8((argb >> 16) & 0xFF, // R | |
| (argb >> 8) & 0xFF, // G | |
| argb & 0xFF, // B | |
| argb >> 24); // A | |
| } | |
| //---------------------------------------------------------------------------- | |
| /// Exports | |
| /// | |
| void InternalFlutterGpu_RenderPass_Initialize(Dart_Handle wrapper) { | |
| auto res = fml::MakeRefCounted<flutter::gpu::RenderPass>(); | |
| res->AssociateWithDartWrapper(wrapper); | |
| } | |
| Dart_Handle InternalFlutterGpu_RenderPass_SetColorAttachment( | |
| flutter::gpu::RenderPass* wrapper, | |
| int color_attachment_index, | |
| int load_action, | |
| int store_action, | |
| int clear_color, | |
| 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 = ToImpellerColor(static_cast<uint32_t>(clear_color)); | |
| desc.texture = texture->GetTexture(); | |
| if (!Dart_IsNull(resolve_texture_wrapper)) { | |
| flutter::gpu::Texture* resolve_texture = | |
| tonic::DartConverter<flutter::gpu::Texture*>::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<flutter::gpu::RenderPipeline>(pipeline); | |
| wrapper->SetPipeline(std::move(ref)); | |
| } | |
| template <typename TBuffer> | |
| 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<impeller::BufferView> 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 <typename TBuffer> | |
| 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 <typename TBuffer> | |
| 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 impeller::SampledImageSlot* image_slot = | |
| shader->GetUniformTexture(uniform_name); | |
| // TODO(bdero): Return an error string stating that no uniform texture with | |
| // this name exists and throw an exception. | |
| if (!image_slot) { | |
| 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<const impeller::Sampler>& sampler = | |
| wrapper->GetContext()->GetSamplerLibrary()->GetSampler(sampler_desc); | |
| return command.BindResource( | |
| shader->GetShaderStage(), impeller::DescriptorType::kSampledImage, | |
| *image_slot, impeller::ShaderMetadata{}, 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); | |
| } | |
| bool InternalFlutterGpu_RenderPass_Draw(flutter::gpu::RenderPass* wrapper) { | |
| return wrapper->Draw(); | |
| } |