LibWeb+Compositor: Avoid canvas readback for canvas drawImage
2D canvas rendering now lives in the compositor, but drawing one canvas into another still converted the source HTMLCanvasElement into a DecodedImageFrame in WebContent. That forced a compositor readback for every drawImage(canvas, ...) call before sending the destination canvas commands back to the compositor. Teach the canvas command stream to carry a DrawCanvas command that names the source canvas surface. The destination 2D context now flushes the source canvas, records that command, and immediately flushes the destination command list so the compositor copies the source surface at the drawImage call boundary. Bitmap sources continue to use DrawBitmap, and true readback APIs still read pixels explicitly.
This commit is contained in:
parent
f215d9bb9a
commit
116668651b
6 changed files with 114 additions and 7 deletions
|
|
@ -207,6 +207,33 @@ ErrorOr<Gfx::CanvasCommands::DrawBitmap> decode(Decoder& decoder)
|
|||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommands::DrawCanvas const& command)
|
||||
{
|
||||
TRY(encoder.encode(command.source_canvas_id));
|
||||
TRY(encoder.encode(command.dst_rect));
|
||||
TRY(encoder.encode(command.src_rect));
|
||||
TRY(encoder.encode(command.scaling_mode));
|
||||
TRY(encoder.encode(command.filter));
|
||||
TRY(encoder.encode(command.global_alpha));
|
||||
TRY(encoder.encode(command.compositing_and_blending_operator));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Gfx::CanvasCommands::DrawCanvas> decode(Decoder& decoder)
|
||||
{
|
||||
return Gfx::CanvasCommands::DrawCanvas {
|
||||
.source_canvas_id = TRY(decoder.decode<u64>()),
|
||||
.dst_rect = TRY(decoder.decode<Gfx::FloatRect>()),
|
||||
.src_rect = TRY(decoder.decode<Gfx::IntRect>()),
|
||||
.scaling_mode = TRY(decoder.decode<Gfx::ScalingMode>()),
|
||||
.filter = TRY(decoder.decode<Optional<Gfx::Filter>>()),
|
||||
.global_alpha = TRY(decoder.decode<float>()),
|
||||
.compositing_and_blending_operator = TRY(decoder.decode<Gfx::CompositingAndBlendingOperator>()),
|
||||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Gfx::CanvasCommands::FillPath const& command)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -82,6 +82,16 @@ struct DrawBitmap {
|
|||
CompositingAndBlendingOperator compositing_and_blending_operator { CompositingAndBlendingOperator::SourceOver };
|
||||
};
|
||||
|
||||
struct DrawCanvas {
|
||||
u64 source_canvas_id { 0 };
|
||||
FloatRect dst_rect;
|
||||
IntRect src_rect;
|
||||
ScalingMode scaling_mode { ScalingMode::NearestNeighbor };
|
||||
Optional<Filter> filter;
|
||||
float global_alpha { 1 };
|
||||
CompositingAndBlendingOperator compositing_and_blending_operator { CompositingAndBlendingOperator::SourceOver };
|
||||
};
|
||||
|
||||
struct FillPath {
|
||||
Path path;
|
||||
CanvasPaintStyle style;
|
||||
|
|
@ -128,6 +138,7 @@ using CanvasCommand = Variant<
|
|||
CanvasCommands::ClearRect,
|
||||
CanvasCommands::FillRect,
|
||||
CanvasCommands::DrawBitmap,
|
||||
CanvasCommands::DrawCanvas,
|
||||
CanvasCommands::FillPath,
|
||||
CanvasCommands::StrokePath,
|
||||
CanvasCommands::SetTransform,
|
||||
|
|
@ -196,6 +207,11 @@ ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::DrawBitmap const&);
|
|||
template<>
|
||||
ErrorOr<Gfx::CanvasCommands::DrawBitmap> decode(Decoder&);
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::DrawCanvas const&);
|
||||
template<>
|
||||
ErrorOr<Gfx::CanvasCommands::DrawCanvas> decode(Decoder&);
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder&, Gfx::CanvasCommands::FillPath const&);
|
||||
template<>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,16 @@
|
|||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/PainterSkia.h>
|
||||
#include <LibGfx/PaintingSurface.h>
|
||||
#include <LibGfx/SkiaUtils.h>
|
||||
#include <core/SkCanvas.h>
|
||||
#include <core/SkPaint.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
CanvasCommandPlayer::CanvasCommandPlayer(RefPtr<SkiaBackendContext> skia_backend_context, IntSize size, BitmapFormat format, AlphaType alpha_type)
|
||||
CanvasCommandPlayer::CanvasCommandPlayer(RefPtr<SkiaBackendContext> skia_backend_context, IntSize size, BitmapFormat format, AlphaType alpha_type, CanvasSurfaceResolver canvas_surface_resolver)
|
||||
: m_surface(PaintingSurface::create_with_size(size, format, alpha_type, move(skia_backend_context)))
|
||||
, m_painter(make<PainterSkia>(*m_surface))
|
||||
, m_canvas_surface_resolver(move(canvas_surface_resolver))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +56,34 @@ void CanvasCommandPlayer::play_command(CanvasCommands::DrawBitmap const& command
|
|||
m_painter->draw_bitmap(command.dst_rect, command.frame, command.src_rect, command.scaling_mode, command.filter, command.global_alpha, command.compositing_and_blending_operator);
|
||||
}
|
||||
|
||||
void CanvasCommandPlayer::play_command(CanvasCommands::DrawCanvas const& command)
|
||||
{
|
||||
if (!m_canvas_surface_resolver)
|
||||
return;
|
||||
|
||||
auto* source_surface = m_canvas_surface_resolver(command.source_canvas_id);
|
||||
if (!source_surface)
|
||||
return;
|
||||
|
||||
auto image = source_surface->sk_image_snapshot<sk_sp<SkImage>>();
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
SkPaint paint;
|
||||
if (command.filter.has_value())
|
||||
paint.setImageFilter(to_skia_image_filter(command.filter.value()));
|
||||
paint.setAlpha(static_cast<u8>(command.global_alpha * 255));
|
||||
paint.setBlender(to_skia_blender(command.compositing_and_blending_operator));
|
||||
|
||||
m_surface->canvas().drawImageRect(
|
||||
image.get(),
|
||||
to_skia_rect(command.src_rect),
|
||||
to_skia_rect(command.dst_rect),
|
||||
to_skia_sampling_options(command.scaling_mode),
|
||||
&paint,
|
||||
SkCanvas::kStrict_SrcRectConstraint);
|
||||
}
|
||||
|
||||
void CanvasCommandPlayer::play_command(CanvasCommands::FillPath const& command)
|
||||
{
|
||||
// Shadows are recorded as blurred solid-color fills; everything else goes through the general paint-style overload.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
|
|
@ -20,7 +21,9 @@ class CanvasCommandPlayer {
|
|||
AK_MAKE_NONMOVABLE(CanvasCommandPlayer);
|
||||
|
||||
public:
|
||||
CanvasCommandPlayer(RefPtr<SkiaBackendContext>, IntSize, BitmapFormat, AlphaType);
|
||||
using CanvasSurfaceResolver = Function<PaintingSurface const*(u64)>;
|
||||
|
||||
CanvasCommandPlayer(RefPtr<SkiaBackendContext>, IntSize, BitmapFormat, AlphaType, CanvasSurfaceResolver = {});
|
||||
~CanvasCommandPlayer();
|
||||
|
||||
NonnullRefPtr<PaintingSurface> surface() const;
|
||||
|
|
@ -33,6 +36,7 @@ private:
|
|||
void play_command(CanvasCommands::ClearRect const&);
|
||||
void play_command(CanvasCommands::FillRect const&);
|
||||
void play_command(CanvasCommands::DrawBitmap const&);
|
||||
void play_command(CanvasCommands::DrawCanvas const&);
|
||||
void play_command(CanvasCommands::FillPath const&);
|
||||
void play_command(CanvasCommands::StrokePath const&);
|
||||
void play_command(CanvasCommands::SetTransform const&);
|
||||
|
|
@ -45,6 +49,7 @@ private:
|
|||
|
||||
NonnullRefPtr<PaintingSurface> m_surface;
|
||||
NonnullOwnPtr<PainterSkia> m_painter;
|
||||
CanvasSurfaceResolver m_canvas_surface_resolver;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,10 +175,7 @@ WebIDL::ExceptionOr<void> CanvasRenderingContext2D::draw_image_internal(CanvasIm
|
|||
if (usability == CanvasImageSourceUsability::Bad)
|
||||
return {};
|
||||
|
||||
auto frame = canvas_image_source_frame(image);
|
||||
if (!frame.has_value())
|
||||
return {};
|
||||
auto source_bitmap_rect = frame->rect();
|
||||
auto source_bitmap_rect = Gfx::IntRect { {}, canvas_image_source_dimensions(image) };
|
||||
|
||||
// 4. Establish the source and destination rectangles as follows:
|
||||
// If not specified, the dw and dh arguments must default to the values of sw and sh, interpreted such that one CSS pixel in the image is treated as one unit in the output bitmap's coordinate space.
|
||||
|
|
@ -230,6 +227,34 @@ WebIDL::ExceptionOr<void> CanvasRenderingContext2D::draw_image_internal(CanvasIm
|
|||
}
|
||||
|
||||
if (auto* canvas_command_list = this->canvas_command_list()) {
|
||||
if (auto const* source_canvas = image.get_pointer<GC::Ref<HTMLCanvasElement>>()) {
|
||||
(*source_canvas)->ensure_backing_storage();
|
||||
(*source_canvas)->prepare_for_compositing();
|
||||
if (auto source_canvas_id = (*source_canvas)->canvas_id(); source_canvas_id.has_value()) {
|
||||
canvas_command_list->append(Gfx::CanvasCommands::DrawCanvas {
|
||||
.source_canvas_id = source_canvas_id->value(),
|
||||
.dst_rect = destination_rect,
|
||||
.src_rect = source_rect.to_rounded<int>(),
|
||||
.scaling_mode = scaling_mode,
|
||||
.filter = drawing_state().filter,
|
||||
.global_alpha = drawing_state().global_alpha,
|
||||
.compositing_and_blending_operator = drawing_state().current_compositing_and_blending_operator,
|
||||
});
|
||||
did_draw(destination_rect);
|
||||
flush_recorded_commands();
|
||||
|
||||
// 7. If image is not origin-clean, then set the CanvasRenderingContext2D's origin-clean flag to false.
|
||||
if (image_is_not_origin_clean(image))
|
||||
m_origin_clean = false;
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
auto frame = canvas_image_source_frame(image);
|
||||
if (!frame.has_value())
|
||||
return {};
|
||||
|
||||
canvas_command_list->append(Gfx::CanvasCommands::DrawBitmap {
|
||||
.frame = *frame,
|
||||
.dst_rect = destination_rect,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ OwnPtr<Gfx::CanvasCommandPlayer> CanvasHost::create_2d_command_player(Gfx::IntSi
|
|||
return nullptr;
|
||||
|
||||
auto format = alpha ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
|
||||
auto player = make<Gfx::CanvasCommandPlayer>(m_skia_backend_context, size, format, Gfx::AlphaType::Premultiplied);
|
||||
auto player = make<Gfx::CanvasCommandPlayer>(m_skia_backend_context, size, format, Gfx::AlphaType::Premultiplied, [this](u64 canvas_id) -> Gfx::PaintingSurface const* {
|
||||
return m_canvas_surface_registry.canvas_surface(Web::Painting::CanvasId { canvas_id });
|
||||
});
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#the-canvas-settings:concept-canvas-alpha
|
||||
// "Thus, the bitmap of such a context starts off as opaque black instead of transparent black"
|
||||
|
|
|
|||
Loading…
Reference in a new issue