LibAccelGfx+LibWeb: Add support for stacking context opacity

For each stacking context with an opacity less than 1, we create a
separate framebuffer. We then blit the texture attached to this
framebuffer with the specified opacity.

To avoid the performance overhead of reading pixels from the texture
into Gfx::Bitmap, a new method that allows for direct blitting from
the texture is introduced, named blit_scaled_texture().
This commit is contained in:
Aliaksandr Kalenik 2023-11-23 15:28:51 +01:00 committed by Andreas Kling
parent cb90daadc7
commit 5f7ac559a7
Notes: sideshowbarker 2024-07-17 04:49:48 +09:00
8 changed files with 133 additions and 74 deletions

View file

@ -114,7 +114,7 @@ Texture create_texture()
GLuint texture;
glGenTextures(1, &texture);
verify_no_error();
return { texture };
return { texture, {} };
}
void bind_texture(Texture const& texture)
@ -123,11 +123,12 @@ void bind_texture(Texture const& texture)
verify_no_error();
}
void upload_texture_data(Texture const& texture, Gfx::Bitmap const& bitmap)
void upload_texture_data(Texture& texture, Gfx::Bitmap const& bitmap)
{
VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRx8888 || bitmap.format() == Gfx::BitmapFormat::BGRA8888);
bind_texture(texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap.width(), bitmap.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, bitmap.scanline(0));
texture.size = bitmap.size();
verify_no_error();
}
@ -231,7 +232,7 @@ Framebuffer create_framebuffer(Gfx::IntSize size)
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size.width(), size.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
GLuint fbo;
glGenFramebuffers(1, &fbo);
@ -244,7 +245,7 @@ Framebuffer create_framebuffer(Gfx::IntSize size)
verify_no_error();
return { fbo, texture };
return { fbo, Texture { texture, size } };
}
void bind_framebuffer(Framebuffer const& framebuffer)
@ -257,7 +258,7 @@ void delete_framebuffer(Framebuffer const& framebuffer)
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.fbo_id);
glDeleteFramebuffers(1, &framebuffer.fbo_id);
glDeleteTextures(1, &framebuffer.texture_id);
delete_texture(framebuffer.texture);
verify_no_error();
}

View file

@ -15,6 +15,7 @@
# include <GL/gl.h>
#endif
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
namespace AccelGfx::GL {
@ -41,6 +42,7 @@ struct Uniform {
struct Texture {
GLuint id;
Optional<Gfx::IntSize> size;
};
struct Buffer {
@ -53,7 +55,7 @@ struct VertexArray {
struct Framebuffer {
GLuint fbo_id;
GLuint texture_id;
GL::Texture texture;
};
void set_viewport(Gfx::IntRect);
@ -70,7 +72,7 @@ void delete_program(Program const&);
Texture create_texture();
void bind_texture(Texture const&);
void upload_texture_data(Texture const& texture, Gfx::Bitmap const& bitmap);
void upload_texture_data(Texture& texture, Gfx::Bitmap const& bitmap);
void delete_texture(Texture const&);
void set_uniform(Uniform const& uniform, float, float);

View file

@ -142,6 +142,16 @@ OwnPtr<Painter> Painter::create()
return make<Painter>(context);
}
OwnPtr<Painter> Painter::create_with_glyphs_texture_from_painter(Painter const& painter)
{
auto& context = Context::the();
auto glyphs_texture_painter = make<Painter>(context);
glyphs_texture_painter->m_glyphs_texture = painter.m_glyphs_texture;
glyphs_texture_painter->m_glyphs_texture_size = painter.m_glyphs_texture_size;
glyphs_texture_painter->m_glyphs_texture_map = painter.m_glyphs_texture_map;
return glyphs_texture_painter;
}
Painter::Painter(Context& context)
: m_context(context)
, m_rectangle_program(Program::create(Program::Name::RectangleProgram, vertex_shader_source, solid_color_fragment_shader_source))
@ -333,54 +343,11 @@ static GL::ScalingMode to_gl_scaling_mode(Painter::ScalingMode scaling_mode)
void Painter::draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::FloatRect const& src_rect, ScalingMode scaling_mode)
{
bind_target_canvas();
m_blit_program.use();
// FIXME: We should reuse textures across repaints if possible.
auto texture = GL::create_texture();
GL::upload_texture_data(texture, bitmap);
auto scaling_mode_gl = to_gl_scaling_mode(scaling_mode);
GL::set_texture_scale_mode(scaling_mode_gl);
auto dst_rect_in_clip_space = to_clip_space(transform().map(dst_rect));
auto src_rect_in_texture_space = to_texture_space(src_rect, bitmap.size());
Vector<GLfloat> vertices;
vertices.ensure_capacity(16);
auto add_vertex = [&](auto const& p, auto const& s) {
vertices.append(p.x());
vertices.append(p.y());
vertices.append(s.x());
vertices.append(s.y());
};
add_vertex(dst_rect_in_clip_space.top_left(), src_rect_in_texture_space.top_left());
add_vertex(dst_rect_in_clip_space.bottom_left(), src_rect_in_texture_space.bottom_left());
add_vertex(dst_rect_in_clip_space.bottom_right(), src_rect_in_texture_space.bottom_right());
add_vertex(dst_rect_in_clip_space.top_right(), src_rect_in_texture_space.top_right());
auto vbo = GL::create_buffer();
GL::upload_to_buffer(vbo, vertices);
auto vao = GL::create_vertex_array();
GL::bind_vertex_array(vao);
GL::bind_buffer(vbo);
auto vertex_position_attribute = m_blit_program.get_attribute_location("aVertexPosition");
GL::set_vertex_attribute(vertex_position_attribute, 0, 4);
auto color_uniform = m_blit_program.get_uniform_location("uColor");
GL::set_uniform(color_uniform, 1, 1, 1, 1);
GL::enable_blending();
GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4);
blit_scaled_texture(dst_rect, texture, src_rect, scaling_mode);
GL::delete_texture(texture);
GL::delete_buffer(vbo);
GL::delete_vertex_array(vao);
}
void Painter::prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs)
@ -662,4 +629,65 @@ void Painter::flush(Gfx::Bitmap& bitmap)
GL::read_pixels({ 0, 0, bitmap.width(), bitmap.height() }, bitmap);
}
void Painter::blit_canvas(Gfx::IntRect const& dst_rect, Canvas const& canvas, float opacity)
{
blit_canvas(dst_rect.to_type<float>(), canvas, opacity);
}
void Painter::blit_canvas(Gfx::FloatRect const& dst_rect, Canvas const& canvas, float opacity)
{
auto texture = GL::Texture(canvas.framebuffer().texture);
blit_scaled_texture(dst_rect, texture, { { 0, 0 }, canvas.size() }, Painter::ScalingMode::NearestNeighbor, opacity);
}
void Painter::blit_scaled_texture(Gfx::FloatRect const& dst_rect, GL::Texture const& texture, Gfx::FloatRect const& src_rect, ScalingMode scaling_mode, float opacity)
{
bind_target_canvas();
m_blit_program.use();
auto dst_rect_in_clip_space = to_clip_space(transform().map(dst_rect));
auto src_rect_in_texture_space = to_texture_space(src_rect, *texture.size);
Vector<GLfloat> vertices;
vertices.ensure_capacity(16);
auto add_vertex = [&](auto const& p, auto const& s) {
vertices.append(p.x());
vertices.append(p.y());
vertices.append(s.x());
vertices.append(s.y());
};
add_vertex(dst_rect_in_clip_space.top_left(), src_rect_in_texture_space.top_left());
add_vertex(dst_rect_in_clip_space.bottom_left(), src_rect_in_texture_space.bottom_left());
add_vertex(dst_rect_in_clip_space.bottom_right(), src_rect_in_texture_space.bottom_right());
add_vertex(dst_rect_in_clip_space.top_right(), src_rect_in_texture_space.top_right());
auto vbo = GL::create_buffer();
GL::upload_to_buffer(vbo, vertices);
auto vao = GL::create_vertex_array();
GL::bind_vertex_array(vao);
GL::bind_buffer(vbo);
auto vertex_position_attribute = m_blit_program.get_attribute_location("aVertexPosition");
GL::set_vertex_attribute(vertex_position_attribute, 0, 4);
auto color_uniform = m_blit_program.get_uniform_location("uColor");
GL::set_uniform(color_uniform, 1, 1, 1, opacity);
GL::bind_texture(texture);
auto scaling_mode_gl = to_gl_scaling_mode(scaling_mode);
GL::set_texture_scale_mode(scaling_mode_gl);
GL::enable_blending();
GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4);
GL::delete_buffer(vbo);
GL::delete_vertex_array(vao);
}
}

View file

@ -29,6 +29,7 @@ class Painter {
public:
static OwnPtr<Painter> create();
static OwnPtr<Painter> create_with_glyphs_texture_from_painter(Painter const& painter);
Painter(Context&);
~Painter();
@ -86,6 +87,9 @@ public:
void fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color const& color, CornerRadius const& top_left_radius, CornerRadius const& top_right_radius, CornerRadius const& bottom_left_radius, CornerRadius const& bottom_right_radius);
void fill_rect_with_rounded_corners(Gfx::FloatRect const& rect, Color const& color, CornerRadius const& top_left_radius, CornerRadius const& top_right_radius, CornerRadius const& bottom_left_radius, CornerRadius const& bottom_right_radius);
void blit_canvas(Gfx::IntRect const& dst_rect, Canvas const&, float opacity = 1.0f);
void blit_canvas(Gfx::FloatRect const& dst_rect, Canvas const&, float opacity = 1.0f);
private:
Context& m_context;
@ -96,6 +100,7 @@ private:
[[nodiscard]] State& state() { return m_state_stack.last(); }
[[nodiscard]] State const& state() const { return m_state_stack.last(); }
void blit_scaled_texture(Gfx::FloatRect const& dst_rect, GL::Texture const&, Gfx::FloatRect const& src_rect, ScalingMode, float opacity = 1.0f);
void bind_target_canvas();
[[nodiscard]] Gfx::FloatRect to_clip_space(Gfx::FloatRect const& screen_rect) const;

View file

@ -8,13 +8,22 @@
namespace Web::Painting {
PaintingCommandExecutorGPU::PaintingCommandExecutorGPU(AccelGfx::Painter& painter)
: m_painter(painter)
PaintingCommandExecutorGPU::PaintingCommandExecutorGPU(Gfx::Bitmap& bitmap)
: m_target_bitmap(bitmap)
{
auto painter = AccelGfx::Painter::create();
auto canvas = AccelGfx::Canvas::create(bitmap.size());
painter->set_target_canvas(canvas);
stacking_contexts.append({ .canvas = canvas,
.painter = move(painter),
.opacity = 1.0f,
.destination = {} });
}
PaintingCommandExecutorGPU::~PaintingCommandExecutorGPU()
{
VERIFY(stacking_contexts.size() == 1);
painter().flush(m_target_bitmap);
}
CommandResult PaintingCommandExecutorGPU::draw_glyph_run(Vector<Gfx::DrawGlyphOrEmoji> const& glyph_run, Color const& color)
@ -75,16 +84,32 @@ CommandResult PaintingCommandExecutorGPU::set_font(Gfx::Font const&)
return CommandResult::Continue;
}
CommandResult PaintingCommandExecutorGPU::push_stacking_context(float, bool, Gfx::IntRect const&, Gfx::IntPoint post_transform_translation, CSS::ImageRendering, StackingContextTransform, Optional<StackingContextMask>)
CommandResult PaintingCommandExecutorGPU::push_stacking_context(float opacity, bool, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering, StackingContextTransform, Optional<StackingContextMask>)
{
painter().save();
painter().translate(post_transform_translation.to_type<float>());
if (opacity < 1) {
auto painter = AccelGfx::Painter::create_with_glyphs_texture_from_painter(this->painter());
auto canvas = AccelGfx::Canvas::create(source_paintable_rect.size());
painter->set_target_canvas(canvas);
painter->translate(-source_paintable_rect.location().to_type<float>());
stacking_contexts.append({ .canvas = canvas,
.painter = move(painter),
.opacity = opacity,
.destination = source_paintable_rect });
} else {
painter().save();
painter().translate(post_transform_translation.to_type<float>());
}
return CommandResult::Continue;
}
CommandResult PaintingCommandExecutorGPU::pop_stacking_context()
{
painter().restore();
if (stacking_contexts.last().opacity < 1) {
auto stacking_context = stacking_contexts.take_last();
painter().blit_canvas(stacking_context.destination, *stacking_context.canvas, stacking_context.opacity);
} else {
painter().restore();
}
return CommandResult::Continue;
}
@ -283,7 +308,7 @@ bool PaintingCommandExecutorGPU::would_be_fully_clipped_by_painter(Gfx::IntRect)
void PaintingCommandExecutorGPU::prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs)
{
m_painter.prepare_glyph_texture(unique_glyphs);
painter().prepare_glyph_texture(unique_glyphs);
}
}

View file

@ -51,13 +51,23 @@ public:
virtual bool needs_prepare_glyphs_texture() const override { return true; }
void prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const&) override;
PaintingCommandExecutorGPU(AccelGfx::Painter& painter);
PaintingCommandExecutorGPU(Gfx::Bitmap& bitmap);
~PaintingCommandExecutorGPU() override;
private:
AccelGfx::Painter& painter() { return m_painter; }
Gfx::Bitmap& m_target_bitmap;
AccelGfx::Painter& m_painter;
struct StackingContext {
RefPtr<AccelGfx::Canvas> canvas;
OwnPtr<AccelGfx::Painter> painter;
float opacity;
Gfx::IntRect destination;
};
[[nodiscard]] AccelGfx::Painter const& painter() const { return *stacking_contexts.last().painter; }
[[nodiscard]] AccelGfx::Painter& painter() { return *stacking_contexts.last().painter; }
Vector<StackingContext> stacking_contexts;
};
}

View file

@ -43,12 +43,6 @@ PageHost::PageHost(ConnectionFromClient& client)
m_client.async_did_invalidate_content_rect({ m_invalidation_rect.x().value(), m_invalidation_rect.y().value(), m_invalidation_rect.width().value(), m_invalidation_rect.height().value() });
m_invalidation_rect = {};
});
if (s_use_gpu_painter) {
#ifdef HAS_ACCELERATED_GRAPHICS
m_accelerated_painter = AccelGfx::Painter::create();
#endif
}
}
PageHost::~PageHost() = default;
@ -164,10 +158,8 @@ void PageHost::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& targ
if (s_use_gpu_painter) {
#ifdef HAS_ACCELERATED_GRAPHICS
m_accelerated_painter->set_target_canvas(AccelGfx::Canvas::create(target.size()));
Web::Painting::PaintingCommandExecutorGPU painting_command_executor(*m_accelerated_painter);
Web::Painting::PaintingCommandExecutorGPU painting_command_executor(target);
recording_painter.execute(painting_command_executor);
m_accelerated_painter->flush(target);
#endif
} else {
Web::Painting::PaintingCommandExecutorCPU painting_command_executor(target);

View file

@ -143,10 +143,6 @@ private:
Web::CSS::PreferredColorScheme m_preferred_color_scheme { Web::CSS::PreferredColorScheme::Auto };
RefPtr<WebDriverConnection> m_webdriver;
#ifdef HAS_ACCELERATED_GRAPHICS
OwnPtr<AccelGfx::Painter> m_accelerated_painter;
#endif
};
}