[Impeller] OpenGL MSAA for desktop/web devices. (#163939)

Add support for MSAA without the render to texture extension. This
allows our CI goldens to run with anti aliasing.

Fixes https://github.com/flutter/flutter/issues/158360 (again)
This commit is contained in:
Jonah Williams 2025-03-10 16:16:28 -07:00 committed by GitHub
parent 1beba504d9
commit 4fef40c0f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 111 additions and 26 deletions

View File

@ -133,6 +133,10 @@ CapabilitiesGLES::CapabilitiesGLES(const ProcTableGLES& gl) {
gl.GetIntegerv(GL_MAX_SAMPLES_EXT, &value); gl.GetIntegerv(GL_MAX_SAMPLES_EXT, &value);
supports_offscreen_msaa_ = value >= 4; 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_es_ = desc->IsES();
is_angle_ = desc->IsANGLE(); is_angle_ = desc->IsANGLE();

View File

@ -247,6 +247,7 @@ void(glDepthRange)(GLdouble n, GLdouble f);
PROC(UniformBlockBinding); \ PROC(UniformBlockBinding); \
PROC(BindBufferRange); \ PROC(BindBufferRange); \
PROC(WaitSync); \ PROC(WaitSync); \
PROC(RenderbufferStorageMultisample) \
PROC(BlitFramebuffer); PROC(BlitFramebuffer);
#define FOR_EACH_IMPELLER_EXT_PROC(PROC) \ #define FOR_EACH_IMPELLER_EXT_PROC(PROC) \

View File

@ -130,6 +130,7 @@ struct RenderPassData {
Scalar clear_depth = 1.0; Scalar clear_depth = 1.0;
std::shared_ptr<Texture> color_attachment; std::shared_ptr<Texture> color_attachment;
std::shared_ptr<Texture> resolve_attachment;
std::shared_ptr<Texture> depth_attachment; std::shared_ptr<Texture> depth_attachment;
std::shared_ptr<Texture> stencil_attachment; std::shared_ptr<Texture> stencil_attachment;
@ -214,6 +215,7 @@ void RenderPassGLES::ResetGLState(const ProcTableGLES& gl) {
TextureGLES& color_gles = TextureGLES::Cast(*pass_data.color_attachment); TextureGLES& color_gles = TextureGLES::Cast(*pass_data.color_attachment);
const bool is_default_fbo = color_gles.IsWrapped(); const bool is_default_fbo = color_gles.IsWrapped();
std::optional<GLuint> fbo = 0;
if (is_default_fbo) { if (is_default_fbo) {
if (color_gles.GetFBO().has_value()) { if (color_gles.GetFBO().has_value()) {
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
@ -222,7 +224,7 @@ void RenderPassGLES::ResetGLState(const ProcTableGLES& gl) {
} else { } else {
// Create and bind an offscreen FBO. // Create and bind an offscreen FBO.
if (!color_gles.GetCachedFBO().IsDead()) { if (!color_gles.GetCachedFBO().IsDead()) {
auto fbo = reactor.GetGLHandle(color_gles.GetCachedFBO()); fbo = reactor.GetGLHandle(color_gles.GetCachedFBO());
if (!fbo.has_value()) { if (!fbo.has_value()) {
return false; return false;
} }
@ -231,7 +233,7 @@ void RenderPassGLES::ResetGLState(const ProcTableGLES& gl) {
HandleGLES cached_fbo = HandleGLES cached_fbo =
reactor.CreateUntrackedHandle(HandleType::kFrameBuffer); reactor.CreateUntrackedHandle(HandleType::kFrameBuffer);
color_gles.SetCachedFBO(cached_fbo); color_gles.SetCachedFBO(cached_fbo);
auto fbo = reactor.GetGLHandle(cached_fbo); fbo = reactor.GetGLHandle(cached_fbo);
if (!fbo.has_value()) { if (!fbo.has_value()) {
return false; 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()) { if (gl.DiscardFramebufferEXT.IsAvailable()) {
std::array<GLenum, 3> attachments; std::array<GLenum, 3> attachments;
size_t attachment_count = 0; size_t attachment_count = 0;
@ -580,6 +630,7 @@ bool RenderPassGLES::OnEncodeCommands(const Context& context) const {
/// Setup color data. /// Setup color data.
/// ///
pass_data->color_attachment = color0.texture; pass_data->color_attachment = color0.texture;
pass_data->resolve_attachment = color0.resolve_texture;
pass_data->clear_color = color0.clear_color; pass_data->clear_color = color0.clear_color;
pass_data->clear_color_attachment = CanClearAttachment(color0.load_action); pass_data->clear_color_attachment = CanClearAttachment(color0.load_action);
pass_data->discard_color_attachment = 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 // 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 // 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) { if (color0.resolve_texture) {
FML_DCHECK(context.GetCapabilities()->SupportsImplicitResolvingMSAA()); pass_data->discard_color_attachment =
pass_data->discard_color_attachment = false; pass_data->discard_color_attachment &&
!context.GetCapabilities()->SupportsImplicitResolvingMSAA();
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------

View File

@ -11,6 +11,7 @@
#include "impeller/core/texture_descriptor.h" #include "impeller/core/texture_descriptor.h"
#include "impeller/renderer/backend/gles/handle_gles.h" #include "impeller/renderer/backend/gles/handle_gles.h"
#include "impeller/renderer/backend/gles/proc_table_gles.h" #include "impeller/renderer/backend/gles/proc_table_gles.h"
#include "impeller/renderer/backend/gles/test/mock_gles.h"
namespace impeller::testing { namespace impeller::testing {
@ -77,11 +78,19 @@ TEST_P(TextureGLESTest, Binds2DTexture) {
ASSERT_TRUE(texture); ASSERT_TRUE(texture);
EXPECT_EQ( if (GetContext()->GetCapabilities()->SupportsImplicitResolvingMSAA()) {
TextureGLES::Cast(*texture).ComputeTypeForBinding(GL_READ_FRAMEBUFFER), EXPECT_EQ(
TextureGLES::Type::kTexture); TextureGLES::Cast(*texture).ComputeTypeForBinding(GL_READ_FRAMEBUFFER),
EXPECT_EQ(TextureGLES::Cast(*texture).ComputeTypeForBinding(GL_FRAMEBUFFER), TextureGLES::Type::kTexture);
TextureGLES::Type::kTextureMultisampled); 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 } // namespace impeller::testing

View File

@ -45,7 +45,8 @@ static bool IsDepthStencilFormat(PixelFormat format) {
} }
static TextureGLES::Type GetTextureTypeFromDescriptor( static TextureGLES::Type GetTextureTypeFromDescriptor(
const TextureDescriptor& desc) { const TextureDescriptor& desc,
const std::shared_ptr<const CapabilitiesGLES>& capabilities) {
const auto usage = static_cast<TextureUsageMask>(desc.usage); const auto usage = static_cast<TextureUsageMask>(desc.usage);
const auto render_target = TextureUsage::kRenderTarget; const auto render_target = TextureUsage::kRenderTarget;
const auto is_msaa = desc.sample_count == SampleCount::kCount4; const auto is_msaa = desc.sample_count == SampleCount::kCount4;
@ -53,7 +54,9 @@ static TextureGLES::Type GetTextureTypeFromDescriptor(
return is_msaa ? TextureGLES::Type::kRenderBufferMultisampled return is_msaa ? TextureGLES::Type::kRenderBufferMultisampled
: TextureGLES::Type::kRenderBuffer; : TextureGLES::Type::kRenderBuffer;
} }
return is_msaa ? TextureGLES::Type::kTextureMultisampled return is_msaa ? (capabilities->SupportsImplicitResolvingMSAA()
? TextureGLES::Type::kTextureMultisampled
: TextureGLES::Type::kRenderBufferMultisampled)
: TextureGLES::Type::kTexture; : TextureGLES::Type::kTexture;
} }
@ -192,7 +195,9 @@ TextureGLES::TextureGLES(std::shared_ptr<ReactorGLES> reactor,
std::optional<HandleGLES> external_handle) std::optional<HandleGLES> external_handle)
: Texture(desc), : Texture(desc),
reactor_(std::move(reactor)), reactor_(std::move(reactor)),
type_(GetTextureTypeFromDescriptor(GetTextureDescriptor())), type_(GetTextureTypeFromDescriptor(
GetTextureDescriptor(),
reactor_->GetProcTable().GetCapabilities())),
handle_(external_handle.has_value() handle_(external_handle.has_value()
? external_handle.value() ? external_handle.value()
: reactor_->CreateUntrackedHandle(ToHandleType(type_))), : reactor_->CreateUntrackedHandle(ToHandleType(type_))),
@ -367,7 +372,7 @@ static std::optional<GLenum> ToRenderBufferFormat(PixelFormat format) {
switch (format) { switch (format) {
case PixelFormat::kB8G8R8A8UNormInt: case PixelFormat::kB8G8R8A8UNormInt:
case PixelFormat::kR8G8B8A8UNormInt: case PixelFormat::kR8G8B8A8UNormInt:
return GL_RGBA4; return GL_RGBA8;
case PixelFormat::kR32G32B32A32Float: case PixelFormat::kR32G32B32A32Float:
return GL_RGBA32F; return GL_RGBA32F;
case PixelFormat::kR16G16B16A16Float: case PixelFormat::kR16G16B16A16Float:
@ -457,21 +462,33 @@ void TextureGLES::InitializeContentsIfNecessary() const {
} }
gl.BindRenderbuffer(GL_RENDERBUFFER, handle.value()); gl.BindRenderbuffer(GL_RENDERBUFFER, handle.value());
{ {
TRACE_EVENT0("impeller", "RenderBufferStorageInitialization");
if (type_ == Type::kRenderBufferMultisampled) { if (type_ == Type::kRenderBufferMultisampled) {
gl.RenderbufferStorageMultisampleEXT( // BEWARE: these functions are not at all equivalent! the extensions
GL_RENDERBUFFER, // target // are from EXT_multisampled_render_to_texture and cannot be used
4, // samples // with regular GLES 3.0 multisampled renderbuffers/textures.
render_buffer_format.value(), // internal format if (gl.GetCapabilities()->SupportsImplicitResolvingMSAA()) {
size.width, // width gl.RenderbufferStorageMultisampleEXT(
size.height // height /*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 { } else {
gl.RenderbufferStorage( gl.RenderbufferStorage(
GL_RENDERBUFFER, // target /*target=*/GL_RENDERBUFFER, //
render_buffer_format.value(), // internal format /*internal_format=*/render_buffer_format.value(), //
size.width, // width /*width=*/size.width, //
size.height // height /*height=*/size.height //
); );
} }
} }