[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);
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();

View File

@ -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) \

View File

@ -130,6 +130,7 @@ struct RenderPassData {
Scalar clear_depth = 1.0;
std::shared_ptr<Texture> color_attachment;
std::shared_ptr<Texture> resolve_attachment;
std::shared_ptr<Texture> depth_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);
const bool is_default_fbo = color_gles.IsWrapped();
std::optional<GLuint> 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<GLenum, 3> 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();
}
//----------------------------------------------------------------------------

View File

@ -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

View File

@ -45,7 +45,8 @@ static bool IsDepthStencilFormat(PixelFormat format) {
}
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 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<ReactorGLES> reactor,
std::optional<HandleGLES> 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<GLenum> 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 //
);
}
}