LibWeb: Add API to allow DecodedImageData to paint itself directly

Instead of painting DecodedImageData by first asking it for a bitmap
and then painting that, this commit adds two new APIs:

- frame_rect(frame_index):

    Gets the size of the animation frame at the given index.

- paint(context, ...):

    Paints the DecodedImageData into a DisplayListRecordingContext.

The main powerful thing here is that this allows SVGDecodedImageData
to render itself using the GPU when available.
This commit is contained in:
Andreas Kling 2025-11-04 21:20:00 +01:00 committed by Andreas Kling
parent cebd4cc10d
commit 4c2a02370d
Notes: github-actions[bot] 2025-11-05 08:13:04 +00:00
6 changed files with 97 additions and 5 deletions

View file

@ -8,6 +8,8 @@
#include <LibGfx/Bitmap.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/DisplayListRecordingContext.h>
namespace Web::HTML {
@ -56,4 +58,14 @@ Optional<CSSPixelFraction> AnimatedBitmapDecodedImageData::intrinsic_aspect_rati
return CSSPixels(m_frames.first().bitmap->width()) / CSSPixels(m_frames.first().bitmap->height());
}
Optional<Gfx::IntRect> AnimatedBitmapDecodedImageData::frame_rect(size_t frame_index) const
{
return m_frames[frame_index].bitmap->rect();
}
void AnimatedBitmapDecodedImageData::paint(DisplayListRecordingContext& context, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const
{
context.display_list_recorder().draw_scaled_immutable_bitmap(dst_rect, clip_rect, *m_frames[frame_index].bitmap, scaling_mode);
}
}

View file

@ -35,6 +35,9 @@ public:
virtual Optional<CSSPixels> intrinsic_height() const override;
virtual Optional<CSSPixelFraction> intrinsic_aspect_ratio() const override;
virtual Optional<Gfx::IntRect> frame_rect(size_t frame_index) const override;
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const override;
private:
AnimatedBitmapDecodedImageData(Vector<Frame>&&, size_t loop_count, bool animated);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2023-2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,8 +7,10 @@
#pragma once
#include <AK/RefCounted.h>
#include <LibGfx/ScalingMode.h>
#include <LibGfx/Size.h>
#include <LibJS/Heap/Cell.h>
#include <LibWeb/Forward.h>
#include <LibWeb/PixelUnits.h>
namespace Web::HTML {
@ -20,6 +22,9 @@ class DecodedImageData : public JS::Cell {
public:
virtual ~DecodedImageData();
virtual Optional<Gfx::IntRect> frame_rect([[maybe_unused]] size_t frame_index) const = 0;
virtual void paint([[maybe_unused]] DisplayListRecordingContext&, [[maybe_unused]] size_t frame_index, [[maybe_unused]] Gfx::IntRect dst_rect, [[maybe_unused]] Gfx::IntRect clip_rect, [[maybe_unused]] Gfx::ScalingMode scaling_mode) const = 0;
virtual RefPtr<Gfx::ImmutableBitmap> bitmap(size_t frame_index, Gfx::IntSize = {}) const = 0;
virtual int frame_duration(size_t frame_index) const = 0;

View file

@ -143,16 +143,18 @@ Navigable::Navigable(GC::Ref<Page> page, bool is_svg_page)
{
all_navigables().set(*this);
auto display_list_player_type = page->client().display_list_player_type();
if (display_list_player_type == DisplayListPlayerType::SkiaGPUIfAvailable) {
m_skia_backend_context = get_skia_backend_context();
}
if (!m_is_svg_page) {
auto display_list_player_type = page->client().display_list_player_type();
OwnPtr<Painting::DisplayListPlayerSkia> skia_player;
if (display_list_player_type == DisplayListPlayerType::SkiaGPUIfAvailable) {
m_skia_backend_context = get_skia_backend_context();
skia_player = make<Painting::DisplayListPlayerSkia>(m_skia_backend_context);
} else {
skia_player = make<Painting::DisplayListPlayerSkia>();
}
m_rendering_thread.set_skia_player(move(skia_player));
m_rendering_thread.start(display_list_player_type);
}

View file

@ -19,7 +19,6 @@
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
#include <LibWeb/Painting/DisplayListRecordingContext.h>
#include <LibWeb/Painting/ViewportPaintable.h>
#include <LibWeb/SVG/SVGDecodedImageData.h>
#include <LibWeb/SVG/SVGSVGElement.h>
#include <LibWeb/XML/XMLDocumentBuilder.h>
@ -120,6 +119,33 @@ RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const
return bitmap;
}
RefPtr<Gfx::PaintingSurface> SVGDecodedImageData::render_to_surface(Gfx::IntSize size) const
{
VERIFY(m_document->navigable());
auto surface = Gfx::PaintingSurface::create_with_size(m_document->navigable()->skia_backend_context(), size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
m_document->navigable()->set_viewport_size(size.to_type<CSSPixels>());
m_document->update_layout(DOM::UpdateLayoutReason::SVGDecodedImageDataRender);
auto display_list = m_document->record_display_list({});
if (!display_list)
return nullptr;
switch (m_page_client->display_list_player_type()) {
case DisplayListPlayerType::SkiaGPUIfAvailable:
case DisplayListPlayerType::SkiaCPU: {
Painting::DisplayListPlayerSkia display_list_player;
display_list_player.execute(*display_list, {}, surface);
break;
}
default:
VERIFY_NOT_REACHED();
}
return surface;
}
RefPtr<Gfx::ImmutableBitmap> SVGDecodedImageData::bitmap(size_t, Gfx::IntSize size) const
{
if (size.is_empty())
@ -192,4 +218,39 @@ void SVGDecodedImageData::SVGPageClient::visit_edges(Visitor& visitor)
visitor.visit(m_svg_page);
}
Optional<Gfx::IntRect> SVGDecodedImageData::frame_rect(size_t) const
{
return {};
}
RefPtr<Gfx::PaintingSurface> SVGDecodedImageData::surface(size_t, Gfx::IntSize size) const
{
if (size.is_empty())
return nullptr;
if (auto it = m_cached_rendered_surfaces.find(size); it != m_cached_rendered_surfaces.end())
return it->value;
// Prevent the cache from growing too big.
// FIXME: Evict least used entries.
if (m_cached_rendered_surfaces.size() > 10)
m_cached_rendered_surfaces.remove(m_cached_rendered_surfaces.begin());
auto surface = render_to_surface(size);
if (!surface)
return nullptr;
m_cached_rendered_surfaces.set(size, *surface);
return surface;
}
void SVGDecodedImageData::paint(DisplayListRecordingContext& context, size_t, Gfx::IntRect dst_rect, Gfx::IntRect, Gfx::ScalingMode scaling_mode) const
{
auto surface = this->surface(0, dst_rect.size());
if (!surface)
return;
Gfx::IntRect src_rect(0, 0, dst_rect.width(), dst_rect.height());
context.display_list_recorder().draw_painting_surface(dst_rect, *surface, src_rect, scaling_mode);
}
}

View file

@ -37,13 +37,22 @@ public:
virtual void visit_edges(Cell::Visitor& visitor) override;
virtual Optional<Gfx::IntRect> frame_rect(size_t frame_index) const override;
virtual void paint(DisplayListRecordingContext&, size_t frame_index, Gfx::IntRect dst_rect, Gfx::IntRect clip_rect, Gfx::ScalingMode scaling_mode) const override;
private:
SVGDecodedImageData(GC::Ref<Page>, GC::Ref<SVGPageClient>, GC::Ref<DOM::Document>, GC::Ref<SVG::SVGSVGElement>);
RefPtr<Gfx::Bitmap> render(Gfx::IntSize) const;
RefPtr<Gfx::PaintingSurface> surface(size_t frame_index, Gfx::IntSize) const;
RefPtr<Gfx::PaintingSurface> render_to_surface(Gfx::IntSize) const;
// FIXME: Remove this once everything is using surfaces instead.
mutable HashMap<Gfx::IntSize, NonnullRefPtr<Gfx::ImmutableBitmap>> m_cached_rendered_bitmaps;
mutable HashMap<Gfx::IntSize, NonnullRefPtr<Gfx::PaintingSurface>> m_cached_rendered_surfaces;
GC::Ref<Page> m_page;
GC::Ref<SVGPageClient> m_page_client;