LibGfx+LibWeb: Create PaintingSurface snapshot without GPU->RAM readback

...by constructing ImmutableBitmap directly from SkImage.

This is a huge optimization for the case when content of canvas is
painted onto another canvas, as it allows pixels to remain in GPU memory
throughout the process.

Fixes performance regression on https://playbiolab.com/ introduced by
switching to GPU-backend for canvas.
This commit is contained in:
Aliaksandr Kalenik 2024-11-09 06:47:16 +01:00 committed by Alexander Kalenik
parent 1a01a71568
commit 4b93e27698
Notes: github-actions[bot] 2024-11-09 20:20:52 +00:00
7 changed files with 29 additions and 21 deletions

View file

@ -5,6 +5,7 @@
*/
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/PaintingSurface.h>
#include <core/SkBitmap.h>
#include <core/SkImage.h>
@ -14,7 +15,7 @@ namespace Gfx {
struct ImmutableBitmapImpl {
sk_sp<SkImage> sk_image;
SkBitmap sk_bitmap;
RefPtr<Gfx::Bitmap> gfx_bitmap;
Variant<NonnullRefPtr<Gfx::Bitmap>, NonnullRefPtr<Gfx::PaintingSurface>, Empty> source;
};
int ImmutableBitmap::width() const
@ -49,15 +50,14 @@ SkImage const* ImmutableBitmap::sk_image() const
RefPtr<Gfx::Bitmap const> ImmutableBitmap::bitmap() const
{
return m_impl->gfx_bitmap;
// FIXME: Implement for PaintingSurface
return m_impl->source.get<NonnullRefPtr<Gfx::Bitmap>>();
}
Color ImmutableBitmap::get_pixel(int x, int y) const
{
if (m_impl->gfx_bitmap) {
return m_impl->gfx_bitmap->get_pixel(x, y);
}
VERIFY_NOT_REACHED();
// FIXME: Implement for PaintingSurface
return m_impl->source.get<NonnullRefPtr<Gfx::Bitmap>>()->get_pixel(x, y);
}
static SkColorType to_skia_color_type(Gfx::BitmapFormat format)
@ -96,7 +96,15 @@ NonnullRefPtr<ImmutableBitmap> ImmutableBitmap::create(NonnullRefPtr<Bitmap> bit
impl.sk_bitmap.installPixels(info, const_cast<void*>(static_cast<void const*>(bitmap->scanline(0))), bitmap->pitch());
impl.sk_bitmap.setImmutable();
impl.sk_image = impl.sk_bitmap.asImage();
impl.gfx_bitmap = bitmap;
impl.source = bitmap;
return adopt_ref(*new ImmutableBitmap(make<ImmutableBitmapImpl>(impl)));
}
NonnullRefPtr<ImmutableBitmap> ImmutableBitmap::create_snapshot_from_painting_surface(NonnullRefPtr<PaintingSurface> painting_surface)
{
ImmutableBitmapImpl impl;
impl.sk_image = painting_surface->sk_image_snapshot<sk_sp<SkImage>>();
impl.source = painting_surface;
return adopt_ref(*new ImmutableBitmap(make<ImmutableBitmapImpl>(impl)));
}

View file

@ -22,6 +22,7 @@ struct ImmutableBitmapImpl;
class ImmutableBitmap final : public RefCounted<ImmutableBitmap> {
public:
static NonnullRefPtr<ImmutableBitmap> create(NonnullRefPtr<Bitmap> bitmap);
static NonnullRefPtr<ImmutableBitmap> create_snapshot_from_painting_surface(NonnullRefPtr<PaintingSurface>);
~ImmutableBitmap();

View file

@ -92,15 +92,6 @@ PaintingSurface::PaintingSurface(NonnullOwnPtr<Impl>&& impl)
PaintingSurface::~PaintingSurface() = default;
NonnullRefPtr<ImmutableBitmap> PaintingSurface::create_snapshot() const
{
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, size()).value();
auto image_info = SkImageInfo::Make(bitmap->width(), bitmap->height(), kBGRA_8888_SkColorType, kPremul_SkAlphaType);
SkPixmap const pixmap(image_info, bitmap->begin(), bitmap->pitch());
sk_surface().readPixels(pixmap, 0, 0);
return ImmutableBitmap::create(bitmap);
}
void PaintingSurface::read_into_bitmap(Gfx::Bitmap& bitmap)
{
auto color_type = to_skia_color_type(bitmap.format());
@ -130,6 +121,12 @@ SkSurface& PaintingSurface::sk_surface() const
return *m_impl->surface;
}
template<>
sk_sp<SkImage> PaintingSurface::sk_image_snapshot() const
{
return m_impl->surface->makeImageSnapshot();
}
void PaintingSurface::flush() const
{
if (auto context = m_impl->context) {

View file

@ -31,7 +31,6 @@ public:
static NonnullRefPtr<PaintingSurface> wrap_metal_surface(Gfx::MetalTexture&, RefPtr<SkiaBackendContext>);
#endif
NonnullRefPtr<ImmutableBitmap> create_snapshot() const;
void read_into_bitmap(Bitmap&);
IntSize size() const;
@ -40,6 +39,9 @@ public:
SkCanvas& canvas() const;
SkSurface& sk_surface() const;
template<typename T>
T sk_image_snapshot() const;
void flush() const;
~PaintingSurface();

View file

@ -131,7 +131,7 @@ WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> CanvasPattern::create(JS::Realm& r
auto bitmap = image.visit(
[](JS::Handle<HTMLImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->immutable_bitmap(); },
[](JS::Handle<SVG::SVGImageElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->current_image_bitmap(); },
[](JS::Handle<HTMLCanvasElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return source->surface()->create_snapshot(); },
[](JS::Handle<HTMLCanvasElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*source->surface()); },
[](JS::Handle<HTMLVideoElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); },
[](JS::Handle<ImageBitmap> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); });

View file

@ -134,7 +134,7 @@ WebIDL::ExceptionOr<void> CanvasRenderingContext2D::draw_image_internal(CanvasIm
auto surface = source->surface();
if (!surface)
return {};
return source->surface()->create_snapshot();
return Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*surface);
},
[](JS::Handle<HTMLVideoElement> const& source) -> RefPtr<Gfx::ImmutableBitmap> { return Gfx::ImmutableBitmap::create(*source->bitmap()); },
[](JS::Handle<ImageBitmap> const& source) -> RefPtr<Gfx::ImmutableBitmap> {
@ -401,7 +401,7 @@ WebIDL::ExceptionOr<JS::GCPtr<ImageData>> CanvasRenderingContext2D::get_image_da
// NOTE: We don't attempt to create the underlying bitmap here; if it doesn't exist, it's like copying only transparent black pixels (which is a no-op).
if (!canvas_element().surface())
return image_data;
auto const snapshot = canvas_element().surface()->create_snapshot();
auto const snapshot = Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*canvas_element().surface());
// 5. Let the source rectangle be the rectangle whose corners are the four points (sx, sy), (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh).
auto source_rect = Gfx::Rect { x, y, abs_width, abs_height };

View file

@ -268,7 +268,7 @@ String HTMLCanvasElement::to_data_url(StringView type, Optional<double> quality)
return "data:,"_string;
// 3. Let file be a serialization of this canvas element's bitmap as a file, passing type and quality if given.
auto snapshot = m_surface->create_snapshot();
auto snapshot = Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*m_surface);
auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, m_surface->size()));
m_surface->read_into_bitmap(*bitmap);
auto file = serialize_bitmap(bitmap, type, move(quality));