| // 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 "impeller/entity/contents/runtime_effect_contents.h" |
| |
| #include <algorithm> |
| #include <future> |
| #include <memory> |
| |
| #include "flutter/fml/logging.h" |
| #include "flutter/fml/make_copyable.h" |
| #include "impeller/base/validation.h" |
| #include "impeller/core/formats.h" |
| #include "impeller/core/runtime_types.h" |
| #include "impeller/core/shader_types.h" |
| #include "impeller/entity/contents/content_context.h" |
| #include "impeller/entity/runtime_effect.vert.h" |
| #include "impeller/renderer/capabilities.h" |
| #include "impeller/renderer/pipeline_library.h" |
| #include "impeller/renderer/render_pass.h" |
| #include "impeller/renderer/shader_function.h" |
| #include "impeller/renderer/vertex_descriptor.h" |
| |
| namespace impeller { |
| |
| void RuntimeEffectContents::SetRuntimeStage( |
| std::shared_ptr<RuntimeStage> runtime_stage) { |
| runtime_stage_ = std::move(runtime_stage); |
| } |
| |
| void RuntimeEffectContents::SetUniformData( |
| std::shared_ptr<std::vector<uint8_t>> uniform_data) { |
| uniform_data_ = std::move(uniform_data); |
| } |
| |
| void RuntimeEffectContents::SetTextureInputs( |
| std::vector<TextureInput> texture_inputs) { |
| texture_inputs_ = std::move(texture_inputs); |
| } |
| |
| bool RuntimeEffectContents::CanInheritOpacity(const Entity& entity) const { |
| return false; |
| } |
| |
| static ShaderType GetShaderType(RuntimeUniformType type) { |
| switch (type) { |
| case kSampledImage: |
| return ShaderType::kSampledImage; |
| case kFloat: |
| return ShaderType::kFloat; |
| case kStruct: |
| return ShaderType::kStruct; |
| } |
| } |
| |
| static std::shared_ptr<ShaderMetadata> MakeShaderMetadata( |
| const RuntimeUniformDescription& uniform) { |
| auto metadata = std::make_shared<ShaderMetadata>(); |
| metadata->name = uniform.name; |
| metadata->members.emplace_back(ShaderStructMemberMetadata{ |
| .type = GetShaderType(uniform.type), |
| .size = uniform.GetSize(), |
| .byte_length = uniform.bit_width / 8, |
| }); |
| |
| return metadata; |
| } |
| |
| bool RuntimeEffectContents::BootstrapShader( |
| const ContentContext& renderer) const { |
| if (!RegisterShader(renderer)) { |
| return false; |
| } |
| ContentContextOptions options; |
| options.color_attachment_pixel_format = |
| renderer.GetContext()->GetCapabilities()->GetDefaultColorFormat(); |
| CreatePipeline(renderer, options, /*async=*/true); |
| return true; |
| } |
| |
| bool RuntimeEffectContents::RegisterShader( |
| const ContentContext& renderer) const { |
| const std::shared_ptr<Context>& context = renderer.GetContext(); |
| const std::shared_ptr<ShaderLibrary>& library = context->GetShaderLibrary(); |
| |
| std::shared_ptr<const ShaderFunction> function = library->GetFunction( |
| runtime_stage_->GetEntrypoint(), ShaderStage::kFragment); |
| |
| //-------------------------------------------------------------------------- |
| /// Resolve runtime stage function. |
| /// |
| |
| if (function && runtime_stage_->IsDirty()) { |
| renderer.ClearCachedRuntimeEffectPipeline(runtime_stage_->GetEntrypoint()); |
| context->GetPipelineLibrary()->RemovePipelinesWithEntryPoint(function); |
| library->UnregisterFunction(runtime_stage_->GetEntrypoint(), |
| ShaderStage::kFragment); |
| |
| function = nullptr; |
| } |
| |
| if (!function) { |
| std::promise<bool> promise; |
| auto future = promise.get_future(); |
| |
| library->RegisterFunction( |
| runtime_stage_->GetEntrypoint(), |
| ToShaderStage(runtime_stage_->GetShaderStage()), |
| runtime_stage_->GetCodeMapping(), |
| fml::MakeCopyable([promise = std::move(promise)](bool result) mutable { |
| promise.set_value(result); |
| })); |
| |
| if (!future.get()) { |
| VALIDATION_LOG << "Failed to build runtime effect (entry point: " |
| << runtime_stage_->GetEntrypoint() << ")"; |
| return false; |
| } |
| |
| function = library->GetFunction(runtime_stage_->GetEntrypoint(), |
| ShaderStage::kFragment); |
| if (!function) { |
| VALIDATION_LOG |
| << "Failed to fetch runtime effect function immediately after " |
| "registering it (entry point: " |
| << runtime_stage_->GetEntrypoint() << ")"; |
| return false; |
| } |
| |
| runtime_stage_->SetClean(); |
| } |
| return true; |
| } |
| |
| std::shared_ptr<Pipeline<PipelineDescriptor>> |
| RuntimeEffectContents::CreatePipeline(const ContentContext& renderer, |
| ContentContextOptions options, |
| bool async) const { |
| const std::shared_ptr<Context>& context = renderer.GetContext(); |
| const std::shared_ptr<ShaderLibrary>& library = context->GetShaderLibrary(); |
| const std::shared_ptr<const Capabilities>& caps = context->GetCapabilities(); |
| const auto color_attachment_format = caps->GetDefaultColorFormat(); |
| const auto stencil_attachment_format = caps->GetDefaultDepthStencilFormat(); |
| |
| using VS = RuntimeEffectVertexShader; |
| |
| PipelineDescriptor desc; |
| desc.SetLabel("Runtime Stage"); |
| desc.AddStageEntrypoint( |
| library->GetFunction(VS::kEntrypointName, ShaderStage::kVertex)); |
| desc.AddStageEntrypoint(library->GetFunction(runtime_stage_->GetEntrypoint(), |
| ShaderStage::kFragment)); |
| |
| std::shared_ptr<VertexDescriptor> vertex_descriptor = |
| std::make_shared<VertexDescriptor>(); |
| vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs, |
| VS::kInterleavedBufferLayout); |
| vertex_descriptor->RegisterDescriptorSetLayouts(VS::kDescriptorSetLayouts); |
| vertex_descriptor->RegisterDescriptorSetLayouts( |
| runtime_stage_->GetDescriptorSetLayouts().data(), |
| runtime_stage_->GetDescriptorSetLayouts().size()); |
| desc.SetVertexDescriptor(std::move(vertex_descriptor)); |
| desc.SetColorAttachmentDescriptor( |
| 0u, {.format = color_attachment_format, .blending_enabled = true}); |
| |
| desc.SetStencilAttachmentDescriptors(StencilAttachmentDescriptor{}); |
| desc.SetStencilPixelFormat(stencil_attachment_format); |
| |
| desc.SetDepthStencilAttachmentDescriptor(DepthAttachmentDescriptor{}); |
| desc.SetDepthPixelFormat(stencil_attachment_format); |
| |
| options.ApplyToPipelineDescriptor(desc); |
| if (async) { |
| context->GetPipelineLibrary()->GetPipeline(desc, async); |
| return nullptr; |
| } |
| |
| auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc, async).Get(); |
| if (!pipeline) { |
| VALIDATION_LOG << "Failed to get or create runtime effect pipeline."; |
| return nullptr; |
| } |
| |
| return pipeline; |
| } |
| |
| bool RuntimeEffectContents::Render(const ContentContext& renderer, |
| const Entity& entity, |
| RenderPass& pass) const { |
| const std::shared_ptr<Context>& context = renderer.GetContext(); |
| const std::shared_ptr<ShaderLibrary>& library = context->GetShaderLibrary(); |
| |
| //-------------------------------------------------------------------------- |
| /// Get or register shader. Flutter will do this when the runtime effect |
| /// is first loaded, but this check is added to supporting testing of the |
| /// Aiks API and non-flutter usage of Impeller. |
| /// |
| if (!RegisterShader(renderer)) { |
| return false; |
| } |
| |
| //-------------------------------------------------------------------------- |
| /// Fragment stage uniforms. |
| /// |
| BindFragmentCallback bind_callback = [this, &renderer, |
| &context](RenderPass& pass) { |
| size_t minimum_sampler_index = 100000000; |
| size_t buffer_index = 0; |
| size_t buffer_offset = 0; |
| |
| for (const auto& uniform : runtime_stage_->GetUniforms()) { |
| std::shared_ptr<ShaderMetadata> metadata = MakeShaderMetadata(uniform); |
| |
| switch (uniform.type) { |
| case kSampledImage: { |
| // Sampler uniforms are ordered in the IPLR according to their |
| // declaration and the uniform location reflects the correct offset to |
| // be mapped to - except that it may include all proceeding float |
| // uniforms. For example, a float sampler that comes after 4 float |
| // uniforms may have a location of 4. To convert to the actual offset |
| // we need to find the largest location assigned to a float uniform |
| // and then subtract this from all uniform locations. This is more or |
| // less the same operation we previously performed in the shader |
| // compiler. |
| minimum_sampler_index = |
| std::min(minimum_sampler_index, uniform.location); |
| break; |
| } |
| case kFloat: { |
| FML_DCHECK(renderer.GetContext()->GetBackendType() != |
| Context::BackendType::kVulkan) |
| << "Uniform " << uniform.name |
| << " had unexpected type kFloat for Vulkan backend."; |
| |
| size_t alignment = |
| std::max(uniform.bit_width / 8, DefaultUniformAlignment()); |
| BufferView buffer_view = renderer.GetTransientsBuffer().Emplace( |
| uniform_data_->data() + buffer_offset, uniform.GetSize(), |
| alignment); |
| |
| ShaderUniformSlot uniform_slot; |
| uniform_slot.name = uniform.name.c_str(); |
| uniform_slot.ext_res_0 = uniform.location; |
| pass.BindResource(ShaderStage::kFragment, |
| DescriptorType::kUniformBuffer, uniform_slot, |
| metadata, std::move(buffer_view)); |
| buffer_index++; |
| buffer_offset += uniform.GetSize(); |
| break; |
| } |
| case kStruct: { |
| FML_DCHECK(renderer.GetContext()->GetBackendType() == |
| Context::BackendType::kVulkan); |
| ShaderUniformSlot uniform_slot; |
| uniform_slot.name = uniform.name.c_str(); |
| uniform_slot.binding = uniform.location; |
| |
| // TODO(jonahwilliams): rewrite this to emplace directly into |
| // HostBuffer. |
| std::vector<float> uniform_buffer; |
| uniform_buffer.reserve(uniform.struct_layout.size()); |
| size_t uniform_byte_index = 0u; |
| for (const auto& byte_type : uniform.struct_layout) { |
| if (byte_type == 0) { |
| uniform_buffer.push_back(0.f); |
| } else if (byte_type == 1) { |
| uniform_buffer.push_back(reinterpret_cast<float*>( |
| uniform_data_->data())[uniform_byte_index++]); |
| } else { |
| FML_UNREACHABLE(); |
| } |
| } |
| size_t alignment = std::max(sizeof(float) * uniform_buffer.size(), |
| DefaultUniformAlignment()); |
| |
| BufferView buffer_view = renderer.GetTransientsBuffer().Emplace( |
| reinterpret_cast<const void*>(uniform_buffer.data()), |
| sizeof(float) * uniform_buffer.size(), alignment); |
| pass.BindResource(ShaderStage::kFragment, |
| DescriptorType::kUniformBuffer, uniform_slot, |
| ShaderMetadata{}, std::move(buffer_view)); |
| } |
| } |
| } |
| |
| size_t sampler_index = 0; |
| for (const auto& uniform : runtime_stage_->GetUniforms()) { |
| std::shared_ptr<ShaderMetadata> metadata = MakeShaderMetadata(uniform); |
| |
| switch (uniform.type) { |
| case kSampledImage: { |
| FML_DCHECK(sampler_index < texture_inputs_.size()); |
| auto& input = texture_inputs_[sampler_index]; |
| |
| const std::unique_ptr<const Sampler>& sampler = |
| context->GetSamplerLibrary()->GetSampler( |
| input.sampler_descriptor); |
| |
| SampledImageSlot image_slot; |
| image_slot.name = uniform.name.c_str(); |
| image_slot.binding = uniform.binding; |
| image_slot.texture_index = uniform.location - minimum_sampler_index; |
| pass.BindResource(ShaderStage::kFragment, |
| DescriptorType::kSampledImage, image_slot, |
| *metadata, input.texture, sampler); |
| |
| sampler_index++; |
| break; |
| } |
| default: |
| continue; |
| } |
| } |
| return true; |
| }; |
| |
| /// Now that the descriptor set layouts are known, get the pipeline. |
| using VS = RuntimeEffectVertexShader; |
| |
| PipelineBuilderCallback pipeline_callback = |
| [&](ContentContextOptions options) { |
| // Pipeline creation callback for the cache handler to call. |
| return renderer.GetCachedRuntimeEffectPipeline( |
| runtime_stage_->GetEntrypoint(), options, [&]() { |
| return CreatePipeline(renderer, options, /*async=*/false); |
| }); |
| }; |
| |
| return ColorSourceContents::DrawGeometry<VS>(renderer, entity, pass, |
| pipeline_callback, |
| VS::FrameInfo{}, bind_callback); |
| } |
| |
| } // namespace impeller |