diff --git a/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.cc index 9bc8da8538..0fcb35e632 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.cc @@ -133,6 +133,10 @@ CapabilitiesGLES::CapabilitiesGLES(const ProcTableGLES& gl) { gl.GetIntegerv(GL_MAX_SAMPLES_EXT, &value); supports_offscreen_msaa_ = value >= 4; } + } else if (desc->GetGlVersion().major_version >= 3 && desc->IsES()) { + GLint value = 0; + gl.GetIntegerv(GL_MAX_SAMPLES, &value); + supports_offscreen_msaa_ = value >= 4; } is_es_ = desc->IsES(); is_angle_ = desc->IsANGLE(); diff --git a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h index 8bc3ca76ba..95cac18673 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h @@ -247,6 +247,7 @@ void(glDepthRange)(GLdouble n, GLdouble f); PROC(UniformBlockBinding); \ PROC(BindBufferRange); \ PROC(WaitSync); \ + PROC(RenderbufferStorageMultisample) \ PROC(BlitFramebuffer); #define FOR_EACH_IMPELLER_EXT_PROC(PROC) \ diff --git a/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles.cc index e0d1d4a7ff..4c5aa202f2 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/render_pass_gles.cc @@ -130,6 +130,7 @@ struct RenderPassData { Scalar clear_depth = 1.0; std::shared_ptr color_attachment; + std::shared_ptr resolve_attachment; std::shared_ptr depth_attachment; std::shared_ptr stencil_attachment; @@ -214,6 +215,7 @@ void RenderPassGLES::ResetGLState(const ProcTableGLES& gl) { TextureGLES& color_gles = TextureGLES::Cast(*pass_data.color_attachment); const bool is_default_fbo = color_gles.IsWrapped(); + std::optional fbo = 0; if (is_default_fbo) { if (color_gles.GetFBO().has_value()) { // NOLINTNEXTLINE(bugprone-unchecked-optional-access) @@ -222,7 +224,7 @@ void RenderPassGLES::ResetGLState(const ProcTableGLES& gl) { } else { // Create and bind an offscreen FBO. if (!color_gles.GetCachedFBO().IsDead()) { - auto fbo = reactor.GetGLHandle(color_gles.GetCachedFBO()); + fbo = reactor.GetGLHandle(color_gles.GetCachedFBO()); if (!fbo.has_value()) { return false; } @@ -231,7 +233,7 @@ void RenderPassGLES::ResetGLState(const ProcTableGLES& gl) { HandleGLES cached_fbo = reactor.CreateUntrackedHandle(HandleType::kFrameBuffer); color_gles.SetCachedFBO(cached_fbo); - auto fbo = reactor.GetGLHandle(cached_fbo); + fbo = reactor.GetGLHandle(cached_fbo); if (!fbo.has_value()) { return false; } @@ -520,6 +522,54 @@ void RenderPassGLES::ResetGLState(const ProcTableGLES& gl) { } } + if (pass_data.resolve_attachment && + !gl.GetCapabilities()->SupportsImplicitResolvingMSAA() && + !is_default_fbo) { + FML_DCHECK(pass_data.resolve_attachment != pass_data.color_attachment); + // Perform multisample resolve via blit. + // Create and bind a resolve FBO. + GLuint resolve_fbo; + gl.GenFramebuffers(1u, &resolve_fbo); + gl.BindFramebuffer(GL_FRAMEBUFFER, resolve_fbo); + + if (!TextureGLES::Cast(*pass_data.resolve_attachment) + .SetAsFramebufferAttachment( + GL_FRAMEBUFFER, TextureGLES::AttachmentType::kColor0)) { + return false; + } + + auto status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + if (gl.CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + VALIDATION_LOG << "Could not create a complete frambuffer: " + << DebugToFramebufferError(status); + return false; + } + + // Bind MSAA renderbuffer to read framebuffer. + gl.BindFramebuffer(GL_READ_FRAMEBUFFER, fbo.value()); + gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo); + + RenderPassGLES::ResetGLState(gl); + auto size = pass_data.color_attachment->GetSize(); + + gl.BlitFramebuffer(/*srcX0=*/0, + /*srcY0=*/0, + /*srcX1=*/size.width, + /*srcY1=*/size.height, + /*dstX0=*/0, + /*dstY0=*/0, + /*dstX1=*/size.width, + /*dstY1=*/size.height, + /*mask=*/GL_COLOR_BUFFER_BIT, + /*filter=*/GL_NEAREST); + + gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, GL_NONE); + gl.BindFramebuffer(GL_READ_FRAMEBUFFER, GL_NONE); + gl.DeleteFramebuffers(1u, &resolve_fbo); + // Rebind the original FBO so that we can discard it below. + gl.BindFramebuffer(GL_FRAMEBUFFER, fbo.value()); + } + if (gl.DiscardFramebufferEXT.IsAvailable()) { std::array attachments; size_t attachment_count = 0; @@ -580,6 +630,7 @@ bool RenderPassGLES::OnEncodeCommands(const Context& context) const { /// Setup color data. /// pass_data->color_attachment = color0.texture; + pass_data->resolve_attachment = color0.resolve_texture; pass_data->clear_color = color0.clear_color; pass_data->clear_color_attachment = CanClearAttachment(color0.load_action); pass_data->discard_color_attachment = @@ -587,10 +638,13 @@ bool RenderPassGLES::OnEncodeCommands(const Context& context) const { // When we are using EXT_multisampled_render_to_texture, it is implicitly // resolved when we bind the texture to the framebuffer. We don't need to - // discard the attachment when we are done. + // discard the attachment when we are done. If not using + // EXT_multisampled_render_to_texture but still using MSAA we discard the + // attachment as normal. if (color0.resolve_texture) { - FML_DCHECK(context.GetCapabilities()->SupportsImplicitResolvingMSAA()); - pass_data->discard_color_attachment = false; + pass_data->discard_color_attachment = + pass_data->discard_color_attachment && + !context.GetCapabilities()->SupportsImplicitResolvingMSAA(); } //---------------------------------------------------------------------------- diff --git a/engine/src/flutter/impeller/renderer/backend/gles/test/texture_gles_unittests.cc b/engine/src/flutter/impeller/renderer/backend/gles/test/texture_gles_unittests.cc index 4406a98214..a0b5259011 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/test/texture_gles_unittests.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/test/texture_gles_unittests.cc @@ -11,6 +11,7 @@ #include "impeller/core/texture_descriptor.h" #include "impeller/renderer/backend/gles/handle_gles.h" #include "impeller/renderer/backend/gles/proc_table_gles.h" +#include "impeller/renderer/backend/gles/test/mock_gles.h" namespace impeller::testing { @@ -77,11 +78,19 @@ TEST_P(TextureGLESTest, Binds2DTexture) { ASSERT_TRUE(texture); - EXPECT_EQ( - TextureGLES::Cast(*texture).ComputeTypeForBinding(GL_READ_FRAMEBUFFER), - TextureGLES::Type::kTexture); - EXPECT_EQ(TextureGLES::Cast(*texture).ComputeTypeForBinding(GL_FRAMEBUFFER), - TextureGLES::Type::kTextureMultisampled); + if (GetContext()->GetCapabilities()->SupportsImplicitResolvingMSAA()) { + EXPECT_EQ( + TextureGLES::Cast(*texture).ComputeTypeForBinding(GL_READ_FRAMEBUFFER), + TextureGLES::Type::kTexture); + EXPECT_EQ(TextureGLES::Cast(*texture).ComputeTypeForBinding(GL_FRAMEBUFFER), + TextureGLES::Type::kTextureMultisampled); + } else { + EXPECT_EQ( + TextureGLES::Cast(*texture).ComputeTypeForBinding(GL_READ_FRAMEBUFFER), + TextureGLES::Type::kRenderBufferMultisampled); + EXPECT_EQ(TextureGLES::Cast(*texture).ComputeTypeForBinding(GL_FRAMEBUFFER), + TextureGLES::Type::kRenderBufferMultisampled); + } } } // namespace impeller::testing diff --git a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc index 7f6e910335..15292f3e7e 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc @@ -45,7 +45,8 @@ static bool IsDepthStencilFormat(PixelFormat format) { } static TextureGLES::Type GetTextureTypeFromDescriptor( - const TextureDescriptor& desc) { + const TextureDescriptor& desc, + const std::shared_ptr& capabilities) { const auto usage = static_cast(desc.usage); const auto render_target = TextureUsage::kRenderTarget; const auto is_msaa = desc.sample_count == SampleCount::kCount4; @@ -53,7 +54,9 @@ static TextureGLES::Type GetTextureTypeFromDescriptor( return is_msaa ? TextureGLES::Type::kRenderBufferMultisampled : TextureGLES::Type::kRenderBuffer; } - return is_msaa ? TextureGLES::Type::kTextureMultisampled + return is_msaa ? (capabilities->SupportsImplicitResolvingMSAA() + ? TextureGLES::Type::kTextureMultisampled + : TextureGLES::Type::kRenderBufferMultisampled) : TextureGLES::Type::kTexture; } @@ -192,7 +195,9 @@ TextureGLES::TextureGLES(std::shared_ptr reactor, std::optional external_handle) : Texture(desc), reactor_(std::move(reactor)), - type_(GetTextureTypeFromDescriptor(GetTextureDescriptor())), + type_(GetTextureTypeFromDescriptor( + GetTextureDescriptor(), + reactor_->GetProcTable().GetCapabilities())), handle_(external_handle.has_value() ? external_handle.value() : reactor_->CreateUntrackedHandle(ToHandleType(type_))), @@ -367,7 +372,7 @@ static std::optional ToRenderBufferFormat(PixelFormat format) { switch (format) { case PixelFormat::kB8G8R8A8UNormInt: case PixelFormat::kR8G8B8A8UNormInt: - return GL_RGBA4; + return GL_RGBA8; case PixelFormat::kR32G32B32A32Float: return GL_RGBA32F; case PixelFormat::kR16G16B16A16Float: @@ -457,21 +462,33 @@ void TextureGLES::InitializeContentsIfNecessary() const { } gl.BindRenderbuffer(GL_RENDERBUFFER, handle.value()); { - TRACE_EVENT0("impeller", "RenderBufferStorageInitialization"); if (type_ == Type::kRenderBufferMultisampled) { - gl.RenderbufferStorageMultisampleEXT( - GL_RENDERBUFFER, // target - 4, // samples - render_buffer_format.value(), // internal format - size.width, // width - size.height // height - ); + // BEWARE: these functions are not at all equivalent! the extensions + // are from EXT_multisampled_render_to_texture and cannot be used + // with regular GLES 3.0 multisampled renderbuffers/textures. + if (gl.GetCapabilities()->SupportsImplicitResolvingMSAA()) { + gl.RenderbufferStorageMultisampleEXT( + /*target=*/GL_RENDERBUFFER, // + /*samples=*/4, // + /*internal_format=*/render_buffer_format.value(), // + /*width=*/size.width, // + /*height=*/size.height // + ); + } else { + gl.RenderbufferStorageMultisample( + /*target=*/GL_RENDERBUFFER, // + /*samples=*/4, // + /*internal_format=*/render_buffer_format.value(), // + /*width=*/size.width, // + /*height=*/size.height // + ); + } } else { gl.RenderbufferStorage( - GL_RENDERBUFFER, // target - render_buffer_format.value(), // internal format - size.width, // width - size.height // height + /*target=*/GL_RENDERBUFFER, // + /*internal_format=*/render_buffer_format.value(), // + /*width=*/size.width, // + /*height=*/size.height // ); } }