diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 8338fce6e0928..0bad4d89e830d 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -671,6 +671,10 @@ FILE: ../../../flutter/impeller/renderer/allocator.cc FILE: ../../../flutter/impeller/renderer/allocator.h FILE: ../../../flutter/impeller/renderer/backend/gles/allocator_gles.cc FILE: ../../../flutter/impeller/renderer/backend/gles/allocator_gles.h +FILE: ../../../flutter/impeller/renderer/backend/gles/blit_command_gles.cc +FILE: ../../../flutter/impeller/renderer/backend/gles/blit_command_gles.h +FILE: ../../../flutter/impeller/renderer/backend/gles/blit_pass_gles.cc +FILE: ../../../flutter/impeller/renderer/backend/gles/blit_pass_gles.h FILE: ../../../flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc FILE: ../../../flutter/impeller/renderer/backend/gles/buffer_bindings_gles.h FILE: ../../../flutter/impeller/renderer/backend/gles/capabilities_gles.cc @@ -712,6 +716,10 @@ FILE: ../../../flutter/impeller/renderer/backend/gles/texture_gles.cc FILE: ../../../flutter/impeller/renderer/backend/gles/texture_gles.h FILE: ../../../flutter/impeller/renderer/backend/metal/allocator_mtl.h FILE: ../../../flutter/impeller/renderer/backend/metal/allocator_mtl.mm +FILE: ../../../flutter/impeller/renderer/backend/metal/blit_command_mtl.h +FILE: ../../../flutter/impeller/renderer/backend/metal/blit_command_mtl.mm +FILE: ../../../flutter/impeller/renderer/backend/metal/blit_pass_mtl.h +FILE: ../../../flutter/impeller/renderer/backend/metal/blit_pass_mtl.mm FILE: ../../../flutter/impeller/renderer/backend/metal/command_buffer_mtl.h FILE: ../../../flutter/impeller/renderer/backend/metal/command_buffer_mtl.mm FILE: ../../../flutter/impeller/renderer/backend/metal/context_mtl.h @@ -742,6 +750,8 @@ FILE: ../../../flutter/impeller/renderer/backend/metal/vertex_descriptor_mtl.h FILE: ../../../flutter/impeller/renderer/backend/metal/vertex_descriptor_mtl.mm FILE: ../../../flutter/impeller/renderer/backend/vulkan/allocator_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/allocator_vk.h +FILE: ../../../flutter/impeller/renderer/backend/vulkan/blit_pass_vk.cc +FILE: ../../../flutter/impeller/renderer/backend/vulkan/blit_pass_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/capabilities_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/command_buffer_vk.cc @@ -773,6 +783,10 @@ FILE: ../../../flutter/impeller/renderer/backend/vulkan/texture_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/vertex_descriptor_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/vertex_descriptor_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/vk.h +FILE: ../../../flutter/impeller/renderer/blit_command.cc +FILE: ../../../flutter/impeller/renderer/blit_command.h +FILE: ../../../flutter/impeller/renderer/blit_pass.cc +FILE: ../../../flutter/impeller/renderer/blit_pass.h FILE: ../../../flutter/impeller/renderer/buffer.cc FILE: ../../../flutter/impeller/renderer/buffer.h FILE: ../../../flutter/impeller/renderer/buffer_view.cc diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc index f8e2a2ddffea8..b06e0ca57a776 100644 --- a/impeller/display_list/display_list_dispatcher.cc +++ b/impeller/display_list/display_list_dispatcher.cc @@ -22,6 +22,7 @@ #include "impeller/geometry/path_builder.h" #include "impeller/geometry/scalar.h" #include "impeller/geometry/vertices.h" +#include "impeller/renderer/formats.h" #include "impeller/typographer/backends/skia/text_frame_skia.h" #include "third_party/skia/include/core/SkColor.h" @@ -778,6 +779,11 @@ static impeller::SamplerDescriptor ToSamplerDescriptor( desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear; desc.label = "Linear Sampler"; break; + case flutter::DlImageSampling::kMipmapLinear: + desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear; + desc.mip_filter = impeller::MipFilter::kLinear; + desc.label = "Mipmap Linear Sampler"; + break; default: break; } diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index 5d205a4f1464a..d955a90bfa9df 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -16,6 +16,8 @@ impeller_shaders("shader_fixtures") { "impeller.vert", "instanced_draw.frag", "instanced_draw.vert", + "mipmaps.frag", + "mipmaps.vert", "simple.vert", "test_texture.frag", "test_texture.vert", diff --git a/impeller/fixtures/mipmaps.frag b/impeller/fixtures/mipmaps.frag new file mode 100644 index 0000000000000..05114fdda4d91 --- /dev/null +++ b/impeller/fixtures/mipmaps.frag @@ -0,0 +1,18 @@ +// 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. + +uniform FragInfo { + float lod; +} +frag_info; + +uniform sampler2D tex; + +in vec2 v_uv; + +out vec4 frag_color; + +void main() { + frag_color = textureLod(tex, v_uv, frag_info.lod); +} diff --git a/impeller/fixtures/mipmaps.vert b/impeller/fixtures/mipmaps.vert new file mode 100644 index 0000000000000..c31cb3049468c --- /dev/null +++ b/impeller/fixtures/mipmaps.vert @@ -0,0 +1,18 @@ +// 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. + +uniform VertInfo { + mat4 mvp; +} +vert_info; + +in vec2 vertex_position; +in vec2 uv; + +out vec2 v_uv; + +void main() { + gl_Position = vert_info.mvp * vec4(vertex_position, 0.0, 1.0); + v_uv = uv; +} diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc index 1c757d16e80da..d2058ef6afa6a 100644 --- a/impeller/playground/playground.cc +++ b/impeller/playground/playground.cc @@ -334,7 +334,8 @@ std::optional Playground::LoadFixtureImageRGBA( } std::shared_ptr Playground::CreateTextureForFixture( - const char* fixture_name) const { + const char* fixture_name, + bool enable_mipmapping) const { auto image = LoadFixtureImageRGBA(fixture_name); if (!image.has_value()) { return nullptr; @@ -343,7 +344,8 @@ std::shared_ptr Playground::CreateTextureForFixture( auto texture_descriptor = TextureDescriptor{}; texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; texture_descriptor.size = image->GetSize(); - texture_descriptor.mip_count = 1u; + texture_descriptor.mip_count = + enable_mipmapping ? image->GetSize().MipCount() : 1u; auto texture = renderer_->GetContext()->GetPermanentsAllocator()->CreateTexture( diff --git a/impeller/playground/playground.h b/impeller/playground/playground.h index f26447112f27b..d5f8eb6a652d9 100644 --- a/impeller/playground/playground.h +++ b/impeller/playground/playground.h @@ -57,7 +57,8 @@ class Playground : public ::testing::TestWithParam { const char* fixture_name) const; std::shared_ptr CreateTextureForFixture( - const char* fixture_name) const; + const char* fixture_name, + bool enable_mipmapping = false) const; std::shared_ptr CreateTextureCubeForFixture( std::array fixture_names) const; diff --git a/impeller/renderer/BUILD.gn b/impeller/renderer/BUILD.gn index b61d4462de43c..ab659f9da81f2 100644 --- a/impeller/renderer/BUILD.gn +++ b/impeller/renderer/BUILD.gn @@ -8,6 +8,10 @@ impeller_component("renderer") { sources = [ "allocator.cc", "allocator.h", + "blit_command.cc", + "blit_command.h", + "blit_pass.cc", + "blit_pass.h", "buffer.cc", "buffer.h", "buffer_view.cc", diff --git a/impeller/renderer/backend/gles/BUILD.gn b/impeller/renderer/backend/gles/BUILD.gn index 85f0a377bf4c9..7141abc3304ed 100644 --- a/impeller/renderer/backend/gles/BUILD.gn +++ b/impeller/renderer/backend/gles/BUILD.gn @@ -16,6 +16,10 @@ impeller_component("gles") { sources = [ "allocator_gles.cc", "allocator_gles.h", + "blit_command_gles.cc", + "blit_command_gles.h", + "blit_pass_gles.cc", + "blit_pass_gles.h", "buffer_bindings_gles.cc", "buffer_bindings_gles.h", "capabilities_gles.cc", @@ -60,8 +64,11 @@ impeller_component("gles") { if (!is_android && !is_fuchsia) { public_configs = [ ":gles_config" ] sources += [ - "//third_party/angle/include/GLES2/gl2.h", "//third_party/angle/include/GLES2/gl2ext.h", + + # The GLES3 API is a superset of GLES2. Although we target GLES2, we use + # some GLES3 features if the driver supports them. + "//third_party/angle/include/GLES3/gl3.h", ] } diff --git a/impeller/renderer/backend/gles/blit_command_gles.cc b/impeller/renderer/backend/gles/blit_command_gles.cc new file mode 100644 index 0000000000000..2dde4c9741381 --- /dev/null +++ b/impeller/renderer/backend/gles/blit_command_gles.cc @@ -0,0 +1,134 @@ +// 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/renderer/backend/gles/blit_command_gles.h" + +#include "flutter/fml/closure.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/backend/gles/texture_gles.h" + +namespace impeller { + +BlitEncodeGLES::~BlitEncodeGLES() = default; + +static void DeleteFBO(const ProcTableGLES& gl, GLuint fbo, GLenum type) { + if (fbo != GL_NONE) { + gl.BindFramebuffer(type, GL_NONE); + gl.DeleteFramebuffers(1u, &fbo); + } +}; + +static std::optional ConfigureFBO( + const ProcTableGLES& gl, + const std::shared_ptr& texture, + GLenum fbo_type) { + auto handle = TextureGLES::Cast(texture.get())->GetGLHandle(); + if (!handle.has_value()) { + return std::nullopt; + } + + if (TextureGLES::Cast(*texture).IsWrapped()) { + // The texture is attached to the default FBO, so there's no need to + // create/configure one. + gl.BindFramebuffer(fbo_type, 0); + return 0; + } + + GLuint fbo; + gl.GenFramebuffers(1u, &fbo); + gl.BindFramebuffer(fbo_type, fbo); + + if (!TextureGLES::Cast(*texture).SetAsFramebufferAttachment( + fbo_type, fbo, TextureGLES::AttachmentPoint::kColor0)) { + VALIDATION_LOG << "Could not attach texture to framebuffer."; + DeleteFBO(gl, fbo, fbo_type); + return std::nullopt; + } + + if (gl.CheckFramebufferStatus(fbo_type) != GL_FRAMEBUFFER_COMPLETE) { + VALIDATION_LOG << "Could not create a complete frambuffer."; + DeleteFBO(gl, fbo, fbo_type); + return std::nullopt; + } + + return fbo; +}; + +BlitCopyTextureToTextureCommandGLES::~BlitCopyTextureToTextureCommandGLES() = + default; + +std::string BlitCopyTextureToTextureCommandGLES::GetLabel() const { + return label; +} + +bool BlitCopyTextureToTextureCommandGLES::Encode( + const ReactorGLES& reactor) const { + const auto& gl = reactor.GetProcTable(); + + // glBlitFramebuffer is a GLES3 proc. Since we target GLES2, we need to + // emulate the blit when it's not available in the driver. + if (!gl.BlitFramebuffer.IsAvailable()) { + // TODO(bdero): Emulate the blit using a raster draw call here. + FML_LOG(ERROR) << "Texture blit fallback not implemented yet for GLES2."; + return false; + } + + GLuint read_fbo = GL_NONE; + GLuint draw_fbo = GL_NONE; + fml::ScopedCleanupClosure delete_fbos([&gl, &read_fbo, &draw_fbo]() { + DeleteFBO(gl, read_fbo, GL_READ_FRAMEBUFFER); + DeleteFBO(gl, draw_fbo, GL_DRAW_FRAMEBUFFER); + }); + + { + auto read = ConfigureFBO(gl, source, GL_READ_FRAMEBUFFER); + if (!read.has_value()) { + return false; + } + read_fbo = read.value(); + } + + { + auto draw = ConfigureFBO(gl, destination, GL_DRAW_FRAMEBUFFER); + if (!draw.has_value()) { + return false; + } + draw_fbo = draw.value(); + } + + gl.Disable(GL_SCISSOR_TEST); + gl.Disable(GL_DEPTH_TEST); + gl.Disable(GL_STENCIL_TEST); + + gl.BlitFramebuffer(source_region.origin.x, // srcX0 + source_region.origin.y, // srcY0 + source_region.size.width, // srcX1 + source_region.size.height, // srcY1 + destination_origin.x, // dstX0 + destination_origin.y, // dstY0 + source_region.size.width, // dstX1 + source_region.size.height, // dstY1 + GL_COLOR_BUFFER_BIT, // mask + GL_NEAREST // filter + ); + + return true; +}; + +BlitGenerateMipmapCommandGLES::~BlitGenerateMipmapCommandGLES() = default; + +std::string BlitGenerateMipmapCommandGLES::GetLabel() const { + return label; +} + +bool BlitGenerateMipmapCommandGLES::Encode(const ReactorGLES& reactor) const { + auto texture_gles = TextureGLES::Cast(texture.get()); + if (!texture_gles->GenerateMipmaps()) { + return false; + } + + return true; +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/blit_command_gles.h b/impeller/renderer/backend/gles/blit_command_gles.h new file mode 100644 index 0000000000000..2fd57fbe1e1ab --- /dev/null +++ b/impeller/renderer/backend/gles/blit_command_gles.h @@ -0,0 +1,41 @@ +// 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. + +#pragma once + +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/backend/gles/reactor_gles.h" +#include "impeller/renderer/blit_command.h" + +namespace impeller { + +/// Mixin for dispatching GLES commands. +struct BlitEncodeGLES : BackendCast { + virtual ~BlitEncodeGLES(); + + virtual std::string GetLabel() const = 0; + + [[nodiscard]] virtual bool Encode(const ReactorGLES& reactor) const = 0; +}; + +struct BlitCopyTextureToTextureCommandGLES + : public BlitEncodeGLES, + public BlitCopyTextureToTextureCommand { + ~BlitCopyTextureToTextureCommandGLES() override; + + std::string GetLabel() const override; + + [[nodiscard]] bool Encode(const ReactorGLES& reactor) const override; +}; + +struct BlitGenerateMipmapCommandGLES : public BlitEncodeGLES, + public BlitGenerateMipmapCommand { + ~BlitGenerateMipmapCommandGLES() override; + + std::string GetLabel() const override; + + [[nodiscard]] bool Encode(const ReactorGLES& reactor) const override; +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/blit_pass_gles.cc b/impeller/renderer/backend/gles/blit_pass_gles.cc new file mode 100644 index 0000000000000..07d6c732bda00 --- /dev/null +++ b/impeller/renderer/backend/gles/blit_pass_gles.cc @@ -0,0 +1,124 @@ +// 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/renderer/backend/gles/blit_pass_gles.h" + +#include +#include + +#include "flutter/fml/trace_event.h" +#include "impeller/base/config.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/backend/gles/blit_command_gles.h" +#include "impeller/renderer/backend/gles/device_buffer_gles.h" +#include "impeller/renderer/backend/gles/formats_gles.h" +#include "impeller/renderer/backend/gles/pipeline_gles.h" +#include "impeller/renderer/backend/gles/proc_table_gles.h" +#include "impeller/renderer/backend/gles/texture_gles.h" +#include "impeller/renderer/formats.h" + +namespace impeller { + +BlitPassGLES::BlitPassGLES(ReactorGLES::Ref reactor) + : reactor_(std::move(reactor)), + is_valid_(reactor_ && reactor_->IsValid()) {} + +// |BlitPass| +BlitPassGLES::~BlitPassGLES() = default; + +// |BlitPass| +bool BlitPassGLES::IsValid() const { + return is_valid_; +} + +// |BlitPass| +void BlitPassGLES::OnSetLabel(std::string label) { + label_ = std::move(label); +} + +[[nodiscard]] bool EncodeCommandsInReactor( + const std::shared_ptr& transients_allocator, + const ReactorGLES& reactor, + const std::vector>& commands, + const std::string& label) { + TRACE_EVENT0("impeller", __FUNCTION__); + + if (commands.empty()) { + return true; + } + + const auto& gl = reactor.GetProcTable(); + + fml::ScopedCleanupClosure pop_pass_debug_marker( + [&gl]() { gl.PopDebugGroup(); }); + if (!label.empty()) { + gl.PushDebugGroup(label); + } else { + pop_pass_debug_marker.Release(); + } + + for (const auto& command : commands) { + fml::ScopedCleanupClosure pop_cmd_debug_marker( + [&gl]() { gl.PopDebugGroup(); }); + auto label = command->GetLabel(); + if (!label.empty()) { + gl.PushDebugGroup(label); + } else { + pop_cmd_debug_marker.Release(); + } + + if (!command->Encode(reactor)) { + return false; + } + } + + return true; +} + +// |BlitPass| +bool BlitPassGLES::EncodeCommands( + const std::shared_ptr& transients_allocator) const { + if (!IsValid()) { + return false; + } + if (commands_.empty()) { + return true; + } + + return reactor_->AddOperation([transients_allocator, &commands = commands_, + label = label_](const auto& reactor) { + auto result = + EncodeCommandsInReactor(transients_allocator, reactor, commands, label); + FML_CHECK(result) << "Must be able to encode GL commands without error."; + }); +} + +// |BlitPass| +void BlitPassGLES::OnCopyTextureToTextureCommand( + std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + IPoint destination_origin, + std::string label) { + auto command = std::make_unique(); + command->label = label; + command->source = std::move(source); + command->destination = std::move(destination); + command->source_region = source_region; + command->destination_origin = destination_origin; + + commands_.emplace_back(std::move(command)); +} + +// |BlitPass| +void BlitPassGLES::OnGenerateMipmapCommand(std::shared_ptr texture, + std::string label) { + auto command = std::make_unique(); + command->label = label; + command->texture = std::move(texture); + + commands_.emplace_back(std::move(command)); +} + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/blit_pass_gles.h b/impeller/renderer/backend/gles/blit_pass_gles.h new file mode 100644 index 0000000000000..2268f5dd542ff --- /dev/null +++ b/impeller/renderer/backend/gles/blit_pass_gles.h @@ -0,0 +1,53 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "flutter/impeller/renderer/backend/gles/reactor_gles.h" +#include "flutter/impeller/renderer/blit_pass.h" +#include "impeller/renderer/backend/gles/blit_command_gles.h" + +namespace impeller { + +class BlitPassGLES final : public BlitPass { + public: + // |BlitPass| + ~BlitPassGLES() override; + + private: + friend class CommandBufferGLES; + + std::vector> commands_; + ReactorGLES::Ref reactor_; + std::string label_; + bool is_valid_ = false; + + explicit BlitPassGLES(ReactorGLES::Ref reactor); + + // |BlitPass| + bool IsValid() const override; + + // |BlitPass| + void OnSetLabel(std::string label) override; + + // |BlitPass| + bool EncodeCommands( + const std::shared_ptr& transients_allocator) const override; + + // |BlitPass| + void OnCopyTextureToTextureCommand(std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + IPoint destination_origin, + std::string label) override; + + // |BlitPass| + void OnGenerateMipmapCommand(std::shared_ptr texture, + std::string label) override; + + FML_DISALLOW_COPY_AND_ASSIGN(BlitPassGLES); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/gles/command_buffer_gles.cc b/impeller/renderer/backend/gles/command_buffer_gles.cc index 57e86a937e82c..7bb99354c079c 100644 --- a/impeller/renderer/backend/gles/command_buffer_gles.cc +++ b/impeller/renderer/backend/gles/command_buffer_gles.cc @@ -5,6 +5,7 @@ #include "impeller/renderer/backend/gles/command_buffer_gles.h" #include "impeller/base/config.h" +#include "impeller/renderer/backend/gles/blit_pass_gles.h" #include "impeller/renderer/backend/gles/render_pass_gles.h" namespace impeller { @@ -54,4 +55,16 @@ std::shared_ptr CommandBufferGLES::OnCreateRenderPass( return pass; } +// |CommandBuffer| +std::shared_ptr CommandBufferGLES::OnCreateBlitPass() const { + if (!IsValid()) { + return nullptr; + } + auto pass = std::shared_ptr(new BlitPassGLES(reactor_)); + if (!pass->IsValid()) { + return nullptr; + } + return pass; +} + } // namespace impeller diff --git a/impeller/renderer/backend/gles/command_buffer_gles.h b/impeller/renderer/backend/gles/command_buffer_gles.h index e1a3592ad048e..22b26464379d7 100644 --- a/impeller/renderer/backend/gles/command_buffer_gles.h +++ b/impeller/renderer/backend/gles/command_buffer_gles.h @@ -37,6 +37,9 @@ class CommandBufferGLES final : public CommandBuffer { std::shared_ptr OnCreateRenderPass( RenderTarget target) const override; + // |CommandBuffer| + std::shared_ptr OnCreateBlitPass() const override; + FML_DISALLOW_COPY_AND_ASSIGN(CommandBufferGLES); }; diff --git a/impeller/renderer/backend/gles/formats_gles.h b/impeller/renderer/backend/gles/formats_gles.h index bde996e3f8a6f..fb5ce27e0a384 100644 --- a/impeller/renderer/backend/gles/formats_gles.h +++ b/impeller/renderer/backend/gles/formats_gles.h @@ -163,6 +163,18 @@ constexpr std::optional ToVertexAttribType(ShaderType type) { FML_UNREACHABLE(); } +constexpr GLenum ToTextureType(TextureType type) { + switch (type) { + case TextureType::kTexture2D: + return GL_TEXTURE_2D; + case TextureType::kTexture2DMultisample: + return GL_TEXTURE_2D_MULTISAMPLE; + case TextureType::kTextureCube: + return GL_TEXTURE_CUBE_MAP; + } + FML_UNREACHABLE(); +} + constexpr std::optional ToTextureTarget(TextureType type) { switch (type) { case TextureType::kTexture2D: diff --git a/impeller/renderer/backend/gles/gles.h b/impeller/renderer/backend/gles/gles.h index 2711c36642479..599e3c9433c97 100644 --- a/impeller/renderer/backend/gles/gles.h +++ b/impeller/renderer/backend/gles/gles.h @@ -4,6 +4,6 @@ #pragma once -#include "GLES2/gl2.h" +#include "GLES3/gl3.h" #define GL_GLEXT_PROTOTYPES #include "GLES2/gl2ext.h" diff --git a/impeller/renderer/backend/gles/proc_table_gles.cc b/impeller/renderer/backend/gles/proc_table_gles.cc index 95fbc84700fae..a01246ed23087 100644 --- a/impeller/renderer/backend/gles/proc_table_gles.cc +++ b/impeller/renderer/backend/gles/proc_table_gles.cc @@ -87,6 +87,7 @@ ProcTableGLES::ProcTableGLES(Resolver resolver) { reinterpret_cast(fn_ptr); \ proc_ivar.error_fn = error_fn; \ } + FOR_EACH_IMPELLER_GLES3_PROC(IMPELLER_PROC); FOR_EACH_IMPELLER_EXT_PROC(IMPELLER_PROC); #undef IMPELLER_PROC diff --git a/impeller/renderer/backend/gles/proc_table_gles.h b/impeller/renderer/backend/gles/proc_table_gles.h index 222dd7d2f1f80..ee98d44e3cf09 100644 --- a/impeller/renderer/backend/gles/proc_table_gles.h +++ b/impeller/renderer/backend/gles/proc_table_gles.h @@ -123,6 +123,7 @@ struct GLProc { PROC(FramebufferTexture2D); \ PROC(FrontFace); \ PROC(GenBuffers); \ + PROC(GenerateMipmap); \ PROC(GenFramebuffers); \ PROC(GenRenderbuffers); \ PROC(GenTextures); \ @@ -162,6 +163,8 @@ struct GLProc { PROC(VertexAttribPointer); \ PROC(Viewport); +#define FOR_EACH_IMPELLER_GLES3_PROC(PROC) PROC(BlitFramebuffer); + #define FOR_EACH_IMPELLER_EXT_PROC(PROC) \ PROC(DiscardFramebufferEXT); \ PROC(PushDebugGroupKHR); \ @@ -188,6 +191,7 @@ class ProcTableGLES { GLProc name = {"gl" #name, nullptr}; FOR_EACH_IMPELLER_PROC(IMPELLER_PROC); + FOR_EACH_IMPELLER_GLES3_PROC(IMPELLER_PROC); FOR_EACH_IMPELLER_EXT_PROC(IMPELLER_PROC); #undef IMPELLER_PROC diff --git a/impeller/renderer/backend/gles/render_pass_gles.cc b/impeller/renderer/backend/gles/render_pass_gles.cc index c0392239f6e2b..3dae613f4ad31 100644 --- a/impeller/renderer/backend/gles/render_pass_gles.cc +++ b/impeller/renderer/backend/gles/render_pass_gles.cc @@ -174,19 +174,19 @@ struct RenderPassData { if (auto color = TextureGLES::Cast(pass_data.color_attachment.get())) { if (!color->SetAsFramebufferAttachment( - fbo, TextureGLES::AttachmentPoint::kColor0)) { + GL_FRAMEBUFFER, fbo, TextureGLES::AttachmentPoint::kColor0)) { return false; } } if (auto depth = TextureGLES::Cast(pass_data.depth_attachment.get())) { if (!depth->SetAsFramebufferAttachment( - fbo, TextureGLES::AttachmentPoint::kDepth)) { + GL_FRAMEBUFFER, fbo, TextureGLES::AttachmentPoint::kDepth)) { return false; } } if (auto stencil = TextureGLES::Cast(pass_data.stencil_attachment.get())) { if (!stencil->SetAsFramebufferAttachment( - fbo, TextureGLES::AttachmentPoint::kStencil)) { + GL_FRAMEBUFFER, fbo, TextureGLES::AttachmentPoint::kStencil)) { return false; } } diff --git a/impeller/renderer/backend/gles/sampler_gles.cc b/impeller/renderer/backend/gles/sampler_gles.cc index d9b3fba473bb8..72fea36726f95 100644 --- a/impeller/renderer/backend/gles/sampler_gles.cc +++ b/impeller/renderer/backend/gles/sampler_gles.cc @@ -7,6 +7,7 @@ #include "impeller/renderer/backend/gles/formats_gles.h" #include "impeller/renderer/backend/gles/proc_table_gles.h" #include "impeller/renderer/backend/gles/texture_gles.h" +#include "impeller/renderer/formats.h" namespace impeller { @@ -18,12 +19,29 @@ bool SamplerGLES::IsValid() const { return true; } -static GLint ToParam(MinMagFilter filter) { - switch (filter) { - case MinMagFilter::kNearest: - return GL_NEAREST; - case MinMagFilter::kLinear: - return GL_LINEAR; +static GLint ToParam(MinMagFilter minmag_filter, MipFilter mip_filter) { + switch (mip_filter) { + case MipFilter::kNone: + switch (minmag_filter) { + case MinMagFilter::kNearest: + return GL_NEAREST; + case MinMagFilter::kLinear: + return GL_LINEAR; + } + case MipFilter::kNearest: + switch (minmag_filter) { + case MinMagFilter::kNearest: + return GL_NEAREST_MIPMAP_NEAREST; + case MinMagFilter::kLinear: + return GL_LINEAR_MIPMAP_NEAREST; + } + case MipFilter::kLinear: + switch (minmag_filter) { + case MinMagFilter::kNearest: + return GL_NEAREST_MIPMAP_LINEAR; + case MinMagFilter::kLinear: + return GL_LINEAR_MIPMAP_LINEAR; + } } FML_UNREACHABLE(); } @@ -54,9 +72,9 @@ bool SamplerGLES::ConfigureBoundTexture(const TextureGLES& texture, const auto& desc = GetDescriptor(); gl.TexParameteri(target.value(), GL_TEXTURE_MIN_FILTER, - ToParam(desc.min_filter)); + ToParam(desc.min_filter, desc.mip_filter)); gl.TexParameteri(target.value(), GL_TEXTURE_MAG_FILTER, - ToParam(desc.mag_filter)); + ToParam(desc.mag_filter, MipFilter::kNone)); gl.TexParameteri(target.value(), GL_TEXTURE_WRAP_S, ToAddressMode(desc.width_address_mode)); gl.TexParameteri(target.value(), GL_TEXTURE_WRAP_T, diff --git a/impeller/renderer/backend/gles/texture_gles.cc b/impeller/renderer/backend/gles/texture_gles.cc index 752d80c854bfe..f88bd377dacb2 100644 --- a/impeller/renderer/backend/gles/texture_gles.cc +++ b/impeller/renderer/backend/gles/texture_gles.cc @@ -12,6 +12,7 @@ #include "impeller/base/config.h" #include "impeller/base/validation.h" #include "impeller/renderer/backend/gles/formats_gles.h" +#include "impeller/renderer/formats.h" namespace impeller { @@ -349,11 +350,15 @@ void TextureGLES::InitializeContentsIfNecessary() const { } } -bool TextureGLES::Bind() const { +std::optional TextureGLES::GetGLHandle() const { if (!IsValid()) { - return false; + return std::nullopt; } - auto handle = reactor_->GetGLHandle(handle_); + return reactor_->GetGLHandle(handle_); +} + +bool TextureGLES::Bind() const { + auto handle = GetGLHandle(); if (!handle.has_value()) { return false; } @@ -375,6 +380,37 @@ bool TextureGLES::Bind() const { return true; } +bool TextureGLES::GenerateMipmaps() const { + if (!IsValid()) { + return false; + } + + auto type = GetTextureDescriptor().type; + switch (type) { + case TextureType::kTexture2D: + break; + case TextureType::kTexture2DMultisample: + VALIDATION_LOG << "Generating mipmaps for multisample textures is not " + "supported in the GLES backend."; + return false; + case TextureType::kTextureCube: + break; + } + + if (!Bind()) { + return false; + } + + auto handle = GetGLHandle(); + if (!handle.has_value()) { + return false; + } + + const auto& gl = reactor_->GetProcTable(); + gl.GenerateMipmap(ToTextureType(type)); + return true; +} + TextureGLES::Type TextureGLES::GetType() const { return type_; } @@ -390,20 +426,21 @@ static GLenum ToAttachmentPoint(TextureGLES::AttachmentPoint point) { } } -bool TextureGLES::SetAsFramebufferAttachment(GLuint fbo, +bool TextureGLES::SetAsFramebufferAttachment(GLenum target, + GLuint fbo, AttachmentPoint point) const { if (!IsValid()) { return false; } InitializeContentsIfNecessary(); - auto handle = reactor_->GetGLHandle(handle_); + auto handle = GetGLHandle(); if (!handle.has_value()) { return false; } const auto& gl = reactor_->GetProcTable(); switch (type_) { case Type::kTexture: - gl.FramebufferTexture2D(GL_FRAMEBUFFER, // target + gl.FramebufferTexture2D(target, // target ToAttachmentPoint(point), // attachment GL_TEXTURE_2D, // textarget handle.value(), // texture @@ -411,7 +448,7 @@ bool TextureGLES::SetAsFramebufferAttachment(GLuint fbo, ); break; case Type::kRenderBuffer: - gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, // target + gl.FramebufferRenderbuffer(target, // target ToAttachmentPoint(point), // attachment GL_RENDERBUFFER, // render-buffer target handle.value() // render-buffer diff --git a/impeller/renderer/backend/gles/texture_gles.h b/impeller/renderer/backend/gles/texture_gles.h index 24b07a7594518..03c8b5cbcfdd7 100644 --- a/impeller/renderer/backend/gles/texture_gles.h +++ b/impeller/renderer/backend/gles/texture_gles.h @@ -33,14 +33,19 @@ class TextureGLES final : public Texture, // |Texture| ~TextureGLES() override; + std::optional GetGLHandle() const; + [[nodiscard]] bool Bind() const; + [[nodiscard]] bool GenerateMipmaps() const; + enum class AttachmentPoint { kColor0, kDepth, kStencil, }; - [[nodiscard]] bool SetAsFramebufferAttachment(GLuint fbo, + [[nodiscard]] bool SetAsFramebufferAttachment(GLenum target, + GLuint fbo, AttachmentPoint point) const; Type GetType() const; diff --git a/impeller/renderer/backend/metal/BUILD.gn b/impeller/renderer/backend/metal/BUILD.gn index 4458a07b41105..8a1fe8eed2c99 100644 --- a/impeller/renderer/backend/metal/BUILD.gn +++ b/impeller/renderer/backend/metal/BUILD.gn @@ -8,6 +8,10 @@ impeller_component("metal") { sources = [ "allocator_mtl.h", "allocator_mtl.mm", + "blit_command_mtl.h", + "blit_command_mtl.mm", + "blit_pass_mtl.h", + "blit_pass_mtl.mm", "command_buffer_mtl.h", "command_buffer_mtl.mm", "context_mtl.h", diff --git a/impeller/renderer/backend/metal/blit_command_mtl.h b/impeller/renderer/backend/metal/blit_command_mtl.h new file mode 100644 index 0000000000000..e502cdd2c8d46 --- /dev/null +++ b/impeller/renderer/backend/metal/blit_command_mtl.h @@ -0,0 +1,43 @@ +// 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. + +#pragma once + +#include + +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/blit_command.h" + +namespace impeller { + +/// Mixin for dispatching Metal commands. +struct BlitEncodeMTL : BackendCast { + virtual ~BlitEncodeMTL(); + + virtual std::string GetLabel() const = 0; + + [[nodiscard]] virtual bool Encode( + id encoder) const = 0; +}; + +struct BlitCopyTextureToTextureCommandMTL + : public BlitCopyTextureToTextureCommand, + public BlitEncodeMTL { + ~BlitCopyTextureToTextureCommandMTL() override; + + std::string GetLabel() const override; + + [[nodiscard]] bool Encode(id encoder) const override; +}; + +struct BlitGenerateMipmapCommandMTL : public BlitGenerateMipmapCommand, + public BlitEncodeMTL { + ~BlitGenerateMipmapCommandMTL() override; + + std::string GetLabel() const override; + + [[nodiscard]] bool Encode(id encoder) const override; +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/blit_command_mtl.mm b/impeller/renderer/backend/metal/blit_command_mtl.mm new file mode 100644 index 0000000000000..c3704a951b5a0 --- /dev/null +++ b/impeller/renderer/backend/metal/blit_command_mtl.mm @@ -0,0 +1,70 @@ +// 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/renderer/backend/metal/blit_command_mtl.h" + +#include "impeller/renderer/backend/metal/texture_mtl.h" + +namespace impeller { + +BlitEncodeMTL::~BlitEncodeMTL() = default; + +BlitCopyTextureToTextureCommandMTL::~BlitCopyTextureToTextureCommandMTL() = + default; + +std::string BlitCopyTextureToTextureCommandMTL::GetLabel() const { + return label; +} + +bool BlitCopyTextureToTextureCommandMTL::Encode( + id encoder) const { + auto source_mtl = TextureMTL::Cast(*source).GetMTLTexture(); + if (!source_mtl) { + return false; + } + + auto destination_mtl = TextureMTL::Cast(*destination).GetMTLTexture(); + if (!destination_mtl) { + return false; + } + + auto source_origin_mtl = + MTLOriginMake(source_region.origin.x, source_region.origin.y, 0); + auto source_size_mtl = + MTLSizeMake(source_region.size.width, source_region.size.height, 1); + auto destination_origin_mtl = + MTLOriginMake(destination_origin.x, destination_origin.y, 0); + + [encoder copyFromTexture:source_mtl + sourceSlice:0 + sourceLevel:0 + sourceOrigin:source_origin_mtl + sourceSize:source_size_mtl + toTexture:destination_mtl + destinationSlice:0 + destinationLevel:0 + destinationOrigin:destination_origin_mtl]; + + return true; +}; + +BlitGenerateMipmapCommandMTL::~BlitGenerateMipmapCommandMTL() = default; + +std::string BlitGenerateMipmapCommandMTL::GetLabel() const { + return label; +} + +bool BlitGenerateMipmapCommandMTL::Encode( + id encoder) const { + auto texture_mtl = TextureMTL::Cast(*texture).GetMTLTexture(); + if (!texture_mtl) { + return false; + } + + [encoder generateMipmapsForTexture:texture_mtl]; + + return true; +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/blit_pass_mtl.h b/impeller/renderer/backend/metal/blit_pass_mtl.h new file mode 100644 index 0000000000000..4c9b41bbabded --- /dev/null +++ b/impeller/renderer/backend/metal/blit_pass_mtl.h @@ -0,0 +1,56 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/backend/metal/blit_command_mtl.h" +#include "impeller/renderer/blit_pass.h" + +namespace impeller { + +class BlitPassMTL final : public BlitPass { + public: + // |RenderPass| + ~BlitPassMTL() override; + + private: + friend class CommandBufferMTL; + + std::vector> commands_; + id buffer_ = nil; + std::string label_; + bool is_valid_ = false; + + explicit BlitPassMTL(id buffer); + + // |BlitPass| + bool IsValid() const override; + + // |BlitPass| + void OnSetLabel(std::string label) override; + + // |BlitPass| + bool EncodeCommands( + const std::shared_ptr& transients_allocator) const override; + + bool EncodeCommands(id pass) const; + + // |BlitPass| + void OnCopyTextureToTextureCommand(std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + IPoint destination_origin, + std::string label) override; + + // |BlitPass| + void OnGenerateMipmapCommand(std::shared_ptr texture, + std::string label) override; + + FML_DISALLOW_COPY_AND_ASSIGN(BlitPassMTL); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/blit_pass_mtl.mm b/impeller/renderer/backend/metal/blit_pass_mtl.mm new file mode 100644 index 0000000000000..d65cc544ce367 --- /dev/null +++ b/impeller/renderer/backend/metal/blit_pass_mtl.mm @@ -0,0 +1,117 @@ +// 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/renderer/backend/metal/blit_pass_mtl.h" +#include +#include +#include + +#include "flutter/fml/closure.h" +#include "flutter/fml/logging.h" +#include "flutter/fml/trace_event.h" +#include "impeller/base/backend_cast.h" +#include "impeller/renderer/backend/metal/blit_command_mtl.h" +#include "impeller/renderer/backend/metal/device_buffer_mtl.h" +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/pipeline_mtl.h" +#include "impeller/renderer/backend/metal/sampler_mtl.h" +#include "impeller/renderer/backend/metal/texture_mtl.h" +#include "impeller/renderer/blit_command.h" +#include "impeller/renderer/formats.h" +#include "impeller/renderer/host_buffer.h" +#include "impeller/renderer/shader_types.h" + +namespace impeller { + +BlitPassMTL::BlitPassMTL(id buffer) : buffer_(buffer) { + if (!buffer_) { + return; + } + is_valid_ = true; +} + +BlitPassMTL::~BlitPassMTL() = default; + +bool BlitPassMTL::IsValid() const { + return is_valid_; +} + +void BlitPassMTL::OnSetLabel(std::string label) { + if (label.empty()) { + return; + } + label_ = std::move(label); +} + +bool BlitPassMTL::EncodeCommands( + const std::shared_ptr& transients_allocator) const { + TRACE_EVENT0("impeller", "BlitPassMTL::EncodeCommands"); + if (!IsValid()) { + return false; + } + + auto blit_command_encoder = [buffer_ blitCommandEncoder]; + + if (!blit_command_encoder) { + return false; + } + + if (!label_.empty()) { + [blit_command_encoder setLabel:@(label_.c_str())]; + } + + // Success or failure, the pass must end. The buffer can only process one pass + // at a time. + fml::ScopedCleanupClosure auto_end( + [blit_command_encoder]() { [blit_command_encoder endEncoding]; }); + + return EncodeCommands(blit_command_encoder); +} + +bool BlitPassMTL::EncodeCommands(id encoder) const { + fml::closure pop_debug_marker = [encoder]() { [encoder popDebugGroup]; }; + for (const auto& command : commands_) { + fml::ScopedCleanupClosure auto_pop_debug_marker(pop_debug_marker); + auto label = command->GetLabel(); + if (!label.empty()) { + [encoder pushDebugGroup:@(label.c_str())]; + } else { + auto_pop_debug_marker.Release(); + } + + if (command->Encode(encoder)) { + return false; + } + } + return true; +} + +// |BlitPass| +void BlitPassMTL::OnCopyTextureToTextureCommand( + std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + IPoint destination_origin, + std::string label) { + auto command = std::make_unique(); + command->label = label; + command->source = std::move(source); + command->destination = std::move(destination); + command->source_region = source_region; + command->destination_origin = destination_origin; + + commands_.emplace_back(std::move(command)); +} + +// |BlitPass| +void BlitPassMTL::OnGenerateMipmapCommand(std::shared_ptr texture, + std::string label) { + auto command = std::make_unique(); + command->label = label; + command->texture = std::move(texture); + + commands_.emplace_back(std::move(command)); +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.h b/impeller/renderer/backend/metal/command_buffer_mtl.h index 5c06cfd3012b1..6c038d675376a 100644 --- a/impeller/renderer/backend/metal/command_buffer_mtl.h +++ b/impeller/renderer/backend/metal/command_buffer_mtl.h @@ -38,6 +38,9 @@ class CommandBufferMTL final : public CommandBuffer { std::shared_ptr OnCreateRenderPass( RenderTarget target) const override; + // |CommandBuffer| + std::shared_ptr OnCreateBlitPass() const override; + FML_DISALLOW_COPY_AND_ASSIGN(CommandBufferMTL); }; diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.mm b/impeller/renderer/backend/metal/command_buffer_mtl.mm index e7d6c56199449..90bcbf1cf9256 100644 --- a/impeller/renderer/backend/metal/command_buffer_mtl.mm +++ b/impeller/renderer/backend/metal/command_buffer_mtl.mm @@ -4,6 +4,7 @@ #include "impeller/renderer/backend/metal/command_buffer_mtl.h" +#include "impeller/renderer/backend/metal/blit_pass_mtl.h" #include "impeller/renderer/backend/metal/render_pass_mtl.h" namespace impeller { @@ -202,4 +203,17 @@ static void LogMTLCommandBufferErrorIfPresent(id buffer) { return pass; } +std::shared_ptr CommandBufferMTL::OnCreateBlitPass() const { + if (!buffer_) { + return nullptr; + } + + auto pass = std::shared_ptr(new BlitPassMTL(buffer_)); + if (!pass->IsValid()) { + return nullptr; + } + + return pass; +} + } // namespace impeller diff --git a/impeller/renderer/backend/metal/formats_mtl.h b/impeller/renderer/backend/metal/formats_mtl.h index 4099007385f4a..ef9bdc31fe0fc 100644 --- a/impeller/renderer/backend/metal/formats_mtl.h +++ b/impeller/renderer/backend/metal/formats_mtl.h @@ -275,6 +275,18 @@ constexpr MTLSamplerMinMagFilter ToMTLSamplerMinMagFilter(MinMagFilter filter) { return MTLSamplerMinMagFilterNearest; } +constexpr MTLSamplerMipFilter ToMTLSamplerMipFilter(MipFilter filter) { + switch (filter) { + case MipFilter::kNone: + return MTLSamplerMipFilterNotMipmapped; + case MipFilter::kNearest: + return MTLSamplerMipFilterNearest; + case MipFilter::kLinear: + return MTLSamplerMipFilterLinear; + } + return MTLSamplerMipFilterNotMipmapped; +} + constexpr MTLSamplerAddressMode ToMTLSamplerAddressMode( SamplerAddressMode mode) { switch (mode) { diff --git a/impeller/renderer/backend/metal/sampler_library_mtl.mm b/impeller/renderer/backend/metal/sampler_library_mtl.mm index 781303c7d789a..c9099c509e646 100644 --- a/impeller/renderer/backend/metal/sampler_library_mtl.mm +++ b/impeller/renderer/backend/metal/sampler_library_mtl.mm @@ -25,6 +25,7 @@ auto desc = [[MTLSamplerDescriptor alloc] init]; desc.minFilter = ToMTLSamplerMinMagFilter(descriptor.min_filter); desc.magFilter = ToMTLSamplerMinMagFilter(descriptor.mag_filter); + desc.mipFilter = ToMTLSamplerMipFilter(descriptor.mip_filter); desc.sAddressMode = ToMTLSamplerAddressMode(descriptor.width_address_mode); desc.tAddressMode = ToMTLSamplerAddressMode(descriptor.height_address_mode); desc.rAddressMode = ToMTLSamplerAddressMode(descriptor.depth_address_mode); diff --git a/impeller/renderer/backend/vulkan/BUILD.gn b/impeller/renderer/backend/vulkan/BUILD.gn index 34c3787ce289d..bde65b844f691 100644 --- a/impeller/renderer/backend/vulkan/BUILD.gn +++ b/impeller/renderer/backend/vulkan/BUILD.gn @@ -8,6 +8,8 @@ impeller_component("vulkan") { sources = [ "allocator_vk.cc", "allocator_vk.h", + "blit_pass_vk.cc", + "blit_pass_vk.h", "capabilities_vk.cc", "capabilities_vk.h", "command_buffer_vk.cc", diff --git a/impeller/renderer/backend/vulkan/blit_pass_vk.cc b/impeller/renderer/backend/vulkan/blit_pass_vk.cc new file mode 100644 index 0000000000000..03f0510ada630 --- /dev/null +++ b/impeller/renderer/backend/vulkan/blit_pass_vk.cc @@ -0,0 +1,11 @@ +// 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/renderer/backend/vulkan/blit_pass_vk.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/blit_pass_vk.h b/impeller/renderer/backend/vulkan/blit_pass_vk.h new file mode 100644 index 0000000000000..f05d878c725ec --- /dev/null +++ b/impeller/renderer/backend/vulkan/blit_pass_vk.h @@ -0,0 +1,46 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/renderer/blit_pass.h" + +namespace impeller { + +class BlitPassVK final : public BlitPass { + public: + // |BlitPass| + ~BlitPassVK() override; + + private: + friend class CommandBufferVK; + + BlitPassVK(); + + // |BlitPass| + bool IsValid() const override; + + // |BlitPass| + void OnSetLabel(std::string label) override; + + // |BlitPass| + bool EncodeCommands( + const std::shared_ptr& transients_allocator) const override; + + // |BlitPass| + void OnCopyTextureToTextureCommand(std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + IPoint destination_origin, + std::string label) override; + + // |BlitPass| + void OnGenerateMipmapCommand(std::shared_ptr texture, + std::string label) override; + + FML_DISALLOW_COPY_AND_ASSIGN(BlitPassVK); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.cc b/impeller/renderer/backend/vulkan/command_buffer_vk.cc index aac4ee840de61..0a33906c1f2de 100644 --- a/impeller/renderer/backend/vulkan/command_buffer_vk.cc +++ b/impeller/renderer/backend/vulkan/command_buffer_vk.cc @@ -30,4 +30,8 @@ std::shared_ptr CommandBufferVK::OnCreateRenderPass( FML_UNREACHABLE(); } +std::shared_ptr CommandBufferVK::OnCreateBlitPass() const { + FML_UNREACHABLE(); +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.h b/impeller/renderer/backend/vulkan/command_buffer_vk.h index 14d864bcb413f..cb166f8689ecb 100644 --- a/impeller/renderer/backend/vulkan/command_buffer_vk.h +++ b/impeller/renderer/backend/vulkan/command_buffer_vk.h @@ -32,6 +32,9 @@ class CommandBufferVK final : public CommandBuffer { std::shared_ptr OnCreateRenderPass( RenderTarget target) const override; + // |CommandBuffer| + std::shared_ptr OnCreateBlitPass() const override; + FML_DISALLOW_COPY_AND_ASSIGN(CommandBufferVK); }; diff --git a/impeller/renderer/blit_command.cc b/impeller/renderer/blit_command.cc new file mode 100644 index 0000000000000..0e01e8dccf565 --- /dev/null +++ b/impeller/renderer/blit_command.cc @@ -0,0 +1,11 @@ +// 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/renderer/blit_command.h" + +namespace impeller { + +// + +} // namespace impeller diff --git a/impeller/renderer/blit_command.h b/impeller/renderer/blit_command.h new file mode 100644 index 0000000000000..289e95a35f72c --- /dev/null +++ b/impeller/renderer/blit_command.h @@ -0,0 +1,26 @@ +// 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. + +#pragma once + +#include "impeller/renderer/texture.h" + +namespace impeller { + +struct BlitCommand { + std::string label; +}; + +struct BlitCopyTextureToTextureCommand : public BlitCommand { + std::shared_ptr source; + std::shared_ptr destination; + IRect source_region; + IPoint destination_origin; +}; + +struct BlitGenerateMipmapCommand : public BlitCommand { + std::shared_ptr texture; +}; + +} // namespace impeller diff --git a/impeller/renderer/blit_pass.cc b/impeller/renderer/blit_pass.cc new file mode 100644 index 0000000000000..7e2b0e7843f3e --- /dev/null +++ b/impeller/renderer/blit_pass.cc @@ -0,0 +1,91 @@ +// 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/renderer/blit_pass.h" +#include + +#include "impeller/base/strings.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/blit_command.h" +#include "impeller/renderer/host_buffer.h" + +namespace impeller { + +BlitPass::BlitPass() : transients_buffer_(HostBuffer::Create()) {} + +BlitPass::~BlitPass() = default; + +HostBuffer& BlitPass::GetTransientsBuffer() { + return *transients_buffer_; +} + +void BlitPass::SetLabel(std::string label) { + if (label.empty()) { + return; + } + transients_buffer_->SetLabel(SPrintF("%s Transients", label.c_str())); + OnSetLabel(std::move(label)); +} + +bool BlitPass::AddCopy(std::shared_ptr source, + std::shared_ptr destination, + std::optional source_region, + IPoint destination_origin, + std::string label) { + if (!source) { + VALIDATION_LOG << "Attempted to add a texture blit with no source."; + return false; + } + if (!destination) { + VALIDATION_LOG << "Attempted to add a texture blit with no destination."; + return false; + } + + if (source->GetTextureDescriptor().sample_count != + destination->GetTextureDescriptor().sample_count) { + VALIDATION_LOG << SPrintF( + "The source sample count (%d) must match the destination sample count " + "(%d) for blits.", + source->GetTextureDescriptor().sample_count, + destination->GetTextureDescriptor().sample_count); + return false; + } + + if (!source_region.has_value()) { + source_region = IRect::MakeSize(source->GetSize()); + } + + // Clip the source image. + source_region = + source_region->Intersection(IRect::MakeSize(source->GetSize())); + if (!source_region.has_value()) { + return true; // Nothing to blit. + } + + // Clip the destination image. + source_region = source_region->Intersection( + IRect(-destination_origin, destination->GetSize())); + if (!source_region.has_value()) { + return true; // Nothing to blit. + } + + OnCopyTextureToTextureCommand(std::move(source), std::move(destination), + source_region.value(), destination_origin, + label); + return true; +} + +bool BlitPass::GenerateMipmap(std::shared_ptr texture, + std::string label) { + if (!texture) { + VALIDATION_LOG << "Attempted to add an invalid mipmap generation command " + "with no texture."; + return false; + } + + OnGenerateMipmapCommand(std::move(texture), label); + return true; +} + +} // namespace impeller diff --git a/impeller/renderer/blit_pass.h b/impeller/renderer/blit_pass.h new file mode 100644 index 0000000000000..e5fd8e0bb054e --- /dev/null +++ b/impeller/renderer/blit_pass.h @@ -0,0 +1,105 @@ +// 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. + +#pragma once + +#include +#include + +#include "impeller/renderer/blit_command.h" +#include "impeller/renderer/texture.h" + +namespace impeller { + +class HostBuffer; +class Allocator; + +//------------------------------------------------------------------------------ +/// @brief Blit passes encode blit into the underlying command buffer. +/// +/// Blit passes can be obtained from the command buffer in which +/// the pass is meant to encode commands into. +/// +/// @see `CommandBuffer` +/// +class BlitPass { + public: + virtual ~BlitPass(); + + virtual bool IsValid() const = 0; + + void SetLabel(std::string label); + + HostBuffer& GetTransientsBuffer(); + + //---------------------------------------------------------------------------- + /// @brief Record a command to copy the contents of one texture to + /// another texture. The blit area is limited by the intersection + /// of the texture coverage with respect the source region and + /// destination origin. + /// No work is encoded into the command buffer at this time. + /// + /// @param[in] source The texture to read for copying. + /// @param[in] destination The texture to overwrite using the source + /// contents. + /// @param[in] source_region The optional region of the source texture + /// to use for copying. If not specified, the + /// full size of the source texture is used. + /// @param[in] destination_origin The origin to start writing to in the + /// destination texture. + /// @param[in] label The optional debug label to give the + /// command. + /// + /// @return If the command was valid for subsequent commitment. + /// + bool AddCopy(std::shared_ptr source, + std::shared_ptr destination, + std::optional source_region = std::nullopt, + IPoint destination_origin = {}, + std::string label = ""); + + //---------------------------------------------------------------------------- + /// @brief Record a command to generate all mip levels for a texture. + /// No work is encoded into the command buffer at this time. + /// + /// @param[in] texture The texture to generate mipmaps for. + /// @param[in] label The optional debug label to give the command. + /// + /// @return If the command was valid for subsequent commitment. + /// + bool GenerateMipmap(std::shared_ptr texture, std::string label = ""); + + //---------------------------------------------------------------------------- + /// @brief Encode the recorded commands to the underlying command buffer. + /// + /// @param transients_allocator The transients allocator. + /// + /// @return If the commands were encoded to the underlying command + /// buffer. + /// + virtual bool EncodeCommands( + const std::shared_ptr& transients_allocator) const = 0; + + protected: + std::shared_ptr transients_buffer_; + + explicit BlitPass(); + + virtual void OnSetLabel(std::string label) = 0; + + virtual void OnCopyTextureToTextureCommand( + std::shared_ptr source, + std::shared_ptr destination, + IRect source_region, + IPoint destination_origin, + std::string label) = 0; + + virtual void OnGenerateMipmapCommand(std::shared_ptr texture, + std::string label) = 0; + + private: + FML_DISALLOW_COPY_AND_ASSIGN(BlitPass); +}; + +} // namespace impeller diff --git a/impeller/renderer/command_buffer.cc b/impeller/renderer/command_buffer.cc index 9b0dc620b4c67..75ff9e700f78a 100644 --- a/impeller/renderer/command_buffer.cc +++ b/impeller/renderer/command_buffer.cc @@ -27,4 +27,13 @@ std::shared_ptr CommandBuffer::CreateRenderPass( return nullptr; } +std::shared_ptr CommandBuffer::CreateBlitPass() const { + auto pass = OnCreateBlitPass(); + if (pass && pass->IsValid()) { + pass->SetLabel("BlitPass"); + return pass; + } + return nullptr; +} + } // namespace impeller diff --git a/impeller/renderer/command_buffer.h b/impeller/renderer/command_buffer.h index 8d47ca148fe80..92328a0ae3197 100644 --- a/impeller/renderer/command_buffer.h +++ b/impeller/renderer/command_buffer.h @@ -8,6 +8,7 @@ #include #include "flutter/fml/macros.h" +#include "impeller/renderer/blit_pass.h" namespace impeller { @@ -65,20 +66,29 @@ class CommandBuffer { //---------------------------------------------------------------------------- /// @brief Create a render pass to record render commands into. /// - /// @param[in] desc The description of the render target this pass will - /// target. + /// @param[in] render_target The description of the render target this pass + /// will target. /// /// @return A valid render pass or null. /// std::shared_ptr CreateRenderPass( RenderTarget render_target) const; + //---------------------------------------------------------------------------- + /// @brief Create a blit pass to record blit commands into. + /// + /// @return A valid blit pass or null. + /// + std::shared_ptr CreateBlitPass() const; + protected: CommandBuffer(); virtual std::shared_ptr OnCreateRenderPass( RenderTarget render_target) const = 0; + virtual std::shared_ptr OnCreateBlitPass() const = 0; + private: FML_DISALLOW_COPY_AND_ASSIGN(CommandBuffer); }; diff --git a/impeller/renderer/formats.h b/impeller/renderer/formats.h index 126a569b35287..ebf318f42ca26 100644 --- a/impeller/renderer/formats.h +++ b/impeller/renderer/formats.h @@ -199,6 +199,16 @@ enum class MinMagFilter { kLinear, }; +enum class MipFilter { + /// Always sample from mip level 0. Other mip levels are ignored. + kNone, + /// Sample from the nearest mip level. + kNearest, + /// Sample from the two nearest mip levels and linearly interpolate between + /// them. + kLinear, +}; + enum class SamplerAddressMode { kClampToEdge, kRepeat, diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index bdb6dbf5aaae8..f473a286dd8ea 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/fml/time/time_point.h" #include "flutter/testing/testing.h" +#include "impeller/base/strings.h" #include "impeller/fixtures/box_fade.frag.h" #include "impeller/fixtures/box_fade.vert.h" #include "impeller/fixtures/colors.frag.h" @@ -12,6 +13,8 @@ #include "impeller/fixtures/impeller.vert.h" #include "impeller/fixtures/instanced_draw.frag.h" #include "impeller/fixtures/instanced_draw.vert.h" +#include "impeller/fixtures/mipmaps.frag.h" +#include "impeller/fixtures/mipmaps.vert.h" #include "impeller/fixtures/test_texture.frag.h" #include "impeller/fixtures/test_texture.vert.h" #include "impeller/geometry/path_builder.h" @@ -442,6 +445,237 @@ TEST_P(RendererTest, CanRenderInstanced) { } #endif // IMPELLER_ENABLE_METAL +TEST_P(RendererTest, CanBlitTextureToTexture) { + auto context = GetContext(); + ASSERT_TRUE(context); + + using VS = MipmapsVertexShader; + using FS = MipmapsFragmentShader; + auto desc = PipelineBuilder::MakeDefaultPipelineDescriptor(*context); + ASSERT_TRUE(desc.has_value()); + desc->SetSampleCount(SampleCount::kCount4); + auto mipmaps_pipeline = + context->GetPipelineLibrary()->GetRenderPipeline(std::move(desc)).get(); + ASSERT_TRUE(mipmaps_pipeline); + + TextureDescriptor texture_desc; + texture_desc.format = PixelFormat::kR8G8B8A8UNormInt; + texture_desc.size = {800, 600}; + texture_desc.mip_count = 1u; + texture_desc.usage = + static_cast(TextureUsage::kRenderTarget) | + static_cast(TextureUsage::kShaderRead); + auto texture = context->GetPermanentsAllocator()->CreateTexture( + StorageMode::kHostVisible, texture_desc); + ASSERT_TRUE(texture); + + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + auto boston = CreateTextureForFixture("boston.jpg"); + ASSERT_TRUE(bridge && boston); + auto sampler = context->GetSamplerLibrary()->GetSampler({}); + ASSERT_TRUE(sampler); + + // Vertex buffer. + VertexBufferBuilder vertex_builder; + vertex_builder.SetLabel("Box"); + auto size = Point(boston->GetSize()); + vertex_builder.AddVertices({ + {{0, 0}, {0.0, 0.0}}, // 1 + {{size.x, 0}, {1.0, 0.0}}, // 2 + {{size.x, size.y}, {1.0, 1.0}}, // 3 + {{0, 0}, {0.0, 0.0}}, // 1 + {{size.x, size.y}, {1.0, 1.0}}, // 3 + {{0, size.y}, {0.0, 1.0}}, // 4 + }); + auto vertex_buffer = + vertex_builder.CreateVertexBuffer(*context->GetTransientsAllocator()); + ASSERT_TRUE(vertex_buffer); + + Renderer::RenderCallback callback = [&](RenderTarget& render_target) { + auto buffer = context->CreateRenderCommandBuffer(); + if (!buffer) { + return false; + } + buffer->SetLabel("Playground Command Buffer"); + + { + auto pass = buffer->CreateBlitPass(); + if (!pass) { + return false; + } + pass->SetLabel("Playground Blit Pass"); + + if (render_target.GetColorAttachments().empty()) { + return false; + } + + // Blit `bridge` to the top left corner of the texture. + pass->AddCopy(bridge, texture); + + pass->EncodeCommands(context->GetTransientsAllocator()); + } + + { + auto pass = buffer->CreateRenderPass(render_target); + if (!pass) { + return false; + } + pass->SetLabel("Playground Render Pass"); + { + Command cmd; + cmd.label = "Image"; + cmd.pipeline = mipmaps_pipeline; + + cmd.BindVertices(vertex_buffer); + + VS::VertInfo vert_info; + vert_info.mvp = Matrix::MakeOrthographic(pass->GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + VS::BindVertInfo(cmd, + pass->GetTransientsBuffer().EmplaceUniform(vert_info)); + + FS::FragInfo frag_info; + frag_info.lod = 0; + FS::BindFragInfo(cmd, + pass->GetTransientsBuffer().EmplaceUniform(frag_info)); + + auto sampler = context->GetSamplerLibrary()->GetSampler({}); + FS::BindTex(cmd, texture, sampler); + + pass->AddCommand(std::move(cmd)); + } + pass->EncodeCommands(context->GetTransientsAllocator()); + } + + if (!buffer->SubmitCommands()) { + return false; + } + return true; + }; + OpenPlaygroundHere(callback); +} + +TEST_P(RendererTest, CanGenerateMipmaps) { + auto context = GetContext(); + ASSERT_TRUE(context); + + using VS = MipmapsVertexShader; + using FS = MipmapsFragmentShader; + auto desc = PipelineBuilder::MakeDefaultPipelineDescriptor(*context); + ASSERT_TRUE(desc.has_value()); + desc->SetSampleCount(SampleCount::kCount4); + auto mipmaps_pipeline = + context->GetPipelineLibrary()->GetRenderPipeline(std::move(desc)).get(); + ASSERT_TRUE(mipmaps_pipeline); + + auto boston = CreateTextureForFixture("boston.jpg", true); + ASSERT_TRUE(boston); + + // Vertex buffer. + VertexBufferBuilder vertex_builder; + vertex_builder.SetLabel("Box"); + auto size = Point(boston->GetSize()); + vertex_builder.AddVertices({ + {{0, 0}, {0.0, 0.0}}, // 1 + {{size.x, 0}, {1.0, 0.0}}, // 2 + {{size.x, size.y}, {1.0, 1.0}}, // 3 + {{0, 0}, {0.0, 0.0}}, // 1 + {{size.x, size.y}, {1.0, 1.0}}, // 3 + {{0, size.y}, {0.0, 1.0}}, // 4 + }); + auto vertex_buffer = + vertex_builder.CreateVertexBuffer(*context->GetPermanentsAllocator()); + ASSERT_TRUE(vertex_buffer); + + bool first_frame = true; + Renderer::RenderCallback callback = [&](RenderTarget& render_target) { + if (first_frame) { + ImGui::SetNextWindowPos({10, 10}); + } + + const char* mip_filter_names[] = {"None", "Nearest", "Linear"}; + const MipFilter mip_filters[] = {MipFilter::kNone, MipFilter::kNearest, + MipFilter::kLinear}; + const char* min_filter_names[] = {"Nearest", "Linear"}; + const MinMagFilter min_filters[] = {MinMagFilter::kNearest, + MinMagFilter::kLinear}; + + // UI state. + static int selected_mip_filter = 2; + static int selected_min_filter = 0; + static float lod = 4.5; + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Combo("Mip filter", &selected_mip_filter, mip_filter_names, + sizeof(mip_filter_names) / sizeof(char*)); + ImGui::Combo("Min filter", &selected_min_filter, min_filter_names, + sizeof(min_filter_names) / sizeof(char*)); + ImGui::SliderFloat("LOD", &lod, 0, boston->GetMipCount() - 1); + ImGui::End(); + + auto buffer = context->CreateRenderCommandBuffer(); + if (!buffer) { + return false; + } + buffer->SetLabel("Playground Command Buffer"); + + if (first_frame) { + auto pass = buffer->CreateBlitPass(); + if (!pass) { + return false; + } + pass->SetLabel("Playground Blit Pass"); + + pass->GenerateMipmap(boston, "Boston Mipmap"); + + pass->EncodeCommands(context->GetTransientsAllocator()); + } + + first_frame = false; + + { + auto pass = buffer->CreateRenderPass(render_target); + if (!pass) { + return false; + } + pass->SetLabel("Playground Render Pass"); + { + Command cmd; + cmd.label = "Image LOD"; + cmd.pipeline = mipmaps_pipeline; + + cmd.BindVertices(vertex_buffer); + + VS::VertInfo vert_info; + vert_info.mvp = Matrix::MakeOrthographic(pass->GetRenderTargetSize()) * + Matrix::MakeScale(GetContentScale()); + VS::BindVertInfo(cmd, + pass->GetTransientsBuffer().EmplaceUniform(vert_info)); + + FS::FragInfo frag_info; + frag_info.lod = lod; + FS::BindFragInfo(cmd, + pass->GetTransientsBuffer().EmplaceUniform(frag_info)); + + SamplerDescriptor sampler_desc; + sampler_desc.mip_filter = mip_filters[selected_mip_filter]; + sampler_desc.min_filter = min_filters[selected_min_filter]; + auto sampler = context->GetSamplerLibrary()->GetSampler(sampler_desc); + FS::BindTex(cmd, boston, sampler); + + pass->AddCommand(std::move(cmd)); + } + pass->EncodeCommands(context->GetTransientsAllocator()); + } + + if (!buffer->SubmitCommands()) { + return false; + } + return true; + }; + OpenPlaygroundHere(callback); +} + TEST_P(RendererTest, TheImpeller) { using VS = ImpellerVertexShader; using FS = ImpellerFragmentShader; diff --git a/impeller/renderer/sampler_descriptor.h b/impeller/renderer/sampler_descriptor.h index d028f203fc37c..6ebb96e4e70cf 100644 --- a/impeller/renderer/sampler_descriptor.h +++ b/impeller/renderer/sampler_descriptor.h @@ -18,6 +18,7 @@ class Context; struct SamplerDescriptor final : public Comparable { MinMagFilter min_filter = MinMagFilter::kNearest; MinMagFilter mag_filter = MinMagFilter::kNearest; + MipFilter mip_filter = MipFilter::kNone; SamplerAddressMode width_address_mode = SamplerAddressMode::kClampToEdge; SamplerAddressMode height_address_mode = SamplerAddressMode::kClampToEdge; @@ -27,13 +28,15 @@ struct SamplerDescriptor final : public Comparable { // Comparable std::size_t GetHash() const override { - return fml::HashCombine(min_filter, mag_filter, width_address_mode, - height_address_mode, depth_address_mode); + return fml::HashCombine(min_filter, mag_filter, mip_filter, + width_address_mode, height_address_mode, + depth_address_mode); } // Comparable bool IsEqual(const SamplerDescriptor& o) const override { return min_filter == o.min_filter && mag_filter == o.mag_filter && + mip_filter == o.mip_filter && width_address_mode == o.width_address_mode && height_address_mode == o.height_address_mode && depth_address_mode == o.depth_address_mode; diff --git a/impeller/renderer/texture.cc b/impeller/renderer/texture.cc index 3b63a02065904..4a2a335b4709f 100644 --- a/impeller/renderer/texture.cc +++ b/impeller/renderer/texture.cc @@ -42,6 +42,10 @@ bool Texture::SetContents(std::shared_ptr mapping, return true; } +size_t Texture::GetMipCount() const { + return GetTextureDescriptor().mip_count; +} + const TextureDescriptor& Texture::GetTextureDescriptor() const { return desc_; } diff --git a/impeller/renderer/texture.h b/impeller/renderer/texture.h index 9d71147582f03..6ddb0dcde5ce9 100644 --- a/impeller/renderer/texture.h +++ b/impeller/renderer/texture.h @@ -31,6 +31,8 @@ class Texture { virtual ISize GetSize() const = 0; + size_t GetMipCount() const; + const TextureDescriptor& GetTextureDescriptor() const; TextureIntent GetIntent() const;