2026-06-15 14:29:43 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <Compositor/CanvasHost.h>
|
|
|
|
|
#include <Compositor/HostWebGLContext.h>
|
|
|
|
|
#include <LibGfx/Bitmap.h>
|
|
|
|
|
#include <LibGfx/CanvasCommandPlayer.h>
|
|
|
|
|
#include <LibGfx/PaintingSurface.h>
|
|
|
|
|
#include <LibGfx/SkiaBackendContext.h>
|
|
|
|
|
#include <LibWeb/Painting/CanvasSurfaceRegistry.h>
|
|
|
|
|
|
|
|
|
|
namespace Compositor {
|
|
|
|
|
|
|
|
|
|
CanvasHost::CanvasHost(RefPtr<Gfx::SkiaBackendContext> skia_backend_context, Web::Painting::CanvasSurfaceRegistry& canvas_surface_registry)
|
|
|
|
|
: m_skia_backend_context(move(skia_backend_context))
|
|
|
|
|
, m_canvas_surface_registry(canvas_surface_registry)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CanvasHost::~CanvasHost()
|
|
|
|
|
{
|
|
|
|
|
for (auto canvas_id : m_contexts.keys())
|
|
|
|
|
m_canvas_surface_registry.remove_canvas_surface(canvas_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OwnPtr<Gfx::CanvasCommandPlayer> CanvasHost::create_2d_command_player(Gfx::IntSize size, bool alpha)
|
|
|
|
|
{
|
|
|
|
|
if (size.is_empty() || static_cast<i64>(size.width()) * static_cast<i64>(size.height()) > Gfx::max_canvas_area)
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
auto format = alpha ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
|
2026-06-16 01:02:08 -03:00
|
|
|
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 });
|
|
|
|
|
});
|
2026-06-15 14:29:43 -03:00
|
|
|
|
|
|
|
|
// 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"
|
|
|
|
|
// AD-HOC: Skia hands out a fully transparent surface by default; only clear when alpha is disabled.
|
|
|
|
|
if (!alpha)
|
|
|
|
|
player->clear(Gfx::Color::Black);
|
|
|
|
|
|
|
|
|
|
return player;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-18 18:12:08 -03:00
|
|
|
static void copy_surface_contents(Gfx::PaintingSurface& source, Gfx::PaintingSurface& destination)
|
2026-06-15 14:29:43 -03:00
|
|
|
{
|
2026-06-18 18:12:08 -03:00
|
|
|
destination.copy_from_surface(source);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static NonnullRefPtr<Gfx::PaintingSurface> create_presented_canvas_surface(Gfx::PaintingSurface& source)
|
|
|
|
|
{
|
|
|
|
|
auto surface = Gfx::PaintingSurface::create_with_size(
|
|
|
|
|
source.size(),
|
|
|
|
|
Gfx::BitmapFormat::BGRA8888,
|
|
|
|
|
Gfx::AlphaType::Premultiplied,
|
|
|
|
|
source.skia_backend_context());
|
|
|
|
|
copy_surface_contents(source, *surface);
|
|
|
|
|
return surface;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CanvasHost::Canvas2DContext& CanvasHost::as_2d(Context& context)
|
|
|
|
|
{
|
|
|
|
|
auto* canvas_context = context.get_pointer<Canvas2DContext>();
|
|
|
|
|
VERIFY(canvas_context);
|
|
|
|
|
return *canvas_context;
|
2026-06-15 14:29:43 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HostWebGLContext& CanvasHost::as_webgl(Context& context)
|
|
|
|
|
{
|
|
|
|
|
auto* webgl_context = context.get_pointer<WebGLContext>();
|
|
|
|
|
VERIFY(webgl_context);
|
|
|
|
|
return **webgl_context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Optional<Web::Painting::CanvasId> CanvasHost::create_2d_context(Gfx::IntSize size, bool alpha)
|
|
|
|
|
{
|
2026-06-18 18:12:08 -03:00
|
|
|
auto command_player = create_2d_command_player(size, alpha);
|
|
|
|
|
if (!command_player)
|
2026-06-15 14:29:43 -03:00
|
|
|
return {};
|
|
|
|
|
|
2026-06-18 18:12:08 -03:00
|
|
|
auto presented_surface = create_presented_canvas_surface(command_player->surface());
|
|
|
|
|
auto canvas_id = m_canvas_surface_registry.create_canvas_surface(presented_surface);
|
|
|
|
|
Canvas2DContext context {
|
|
|
|
|
.command_player = command_player.release_nonnull(),
|
|
|
|
|
.presented_surface = move(presented_surface),
|
|
|
|
|
};
|
|
|
|
|
m_contexts.set(canvas_id, move(context));
|
2026-06-15 14:29:43 -03:00
|
|
|
return canvas_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CanvasHost::CreateWebGLContextResult CanvasHost::create_webgl_context(Web::WebGL::WebGLVersion version, Gfx::IntSize size, bool depth, bool stencil, bool antialias)
|
|
|
|
|
{
|
|
|
|
|
if (!m_skia_backend_context)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
auto context = HostWebGLContext::create(*m_skia_backend_context, version, { .depth = depth, .stencil = stencil, .antialias = antialias }, size);
|
|
|
|
|
if (!context)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
auto canvas_id = m_canvas_surface_registry.allocate_canvas_id();
|
|
|
|
|
auto supported_extensions = context->gl_context().get_supported_opengl_extensions();
|
|
|
|
|
m_contexts.set(canvas_id, context.release_nonnull());
|
|
|
|
|
return { .success = true, .canvas_id = canvas_id, .supported_extensions = move(supported_extensions) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CanvasHost::destroy_context(Web::Painting::CanvasId canvas_id)
|
|
|
|
|
{
|
|
|
|
|
m_contexts.remove(canvas_id);
|
|
|
|
|
m_canvas_surface_registry.remove_canvas_surface(canvas_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CanvasHost::has_context(Web::Painting::CanvasId canvas_id) const
|
|
|
|
|
{
|
|
|
|
|
return m_contexts.contains(canvas_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CanvasHost::Context* CanvasHost::context(Web::Painting::CanvasId canvas_id)
|
|
|
|
|
{
|
|
|
|
|
auto it = m_contexts.find(canvas_id);
|
|
|
|
|
if (it == m_contexts.end())
|
|
|
|
|
return nullptr;
|
|
|
|
|
return &it->value;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-18 18:12:08 -03:00
|
|
|
void CanvasHost::present_canvas_2d_context(Web::Painting::CanvasId canvas_id, Canvas2DContext& context)
|
|
|
|
|
{
|
|
|
|
|
copy_surface_contents(context.command_player->surface(), context.presented_surface);
|
|
|
|
|
m_canvas_surface_registry.set_canvas_surface(canvas_id, context.presented_surface);
|
|
|
|
|
context.has_uncommitted_commands = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CanvasHost::execute_canvas_2d_commands(Web::Painting::CanvasId canvas_id, Gfx::CanvasCommandList const& commands, bool commit)
|
2026-06-15 14:29:43 -03:00
|
|
|
{
|
|
|
|
|
auto* context = this->context(canvas_id);
|
|
|
|
|
VERIFY(context);
|
2026-06-18 18:12:08 -03:00
|
|
|
|
|
|
|
|
auto& canvas_context = as_2d(*context);
|
|
|
|
|
if (!commands.is_empty()) {
|
|
|
|
|
canvas_context.command_player->play(commands);
|
|
|
|
|
canvas_context.has_uncommitted_commands = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (commit && canvas_context.has_uncommitted_commands)
|
|
|
|
|
present_canvas_2d_context(canvas_id, canvas_context);
|
2026-06-15 14:29:43 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CanvasHost::execute_webgl_commands(Web::Painting::CanvasId canvas_id, ByteBuffer const& commands, Vector<Gfx::DecodedImageFrame> const& bitmaps)
|
|
|
|
|
{
|
|
|
|
|
auto* context = this->context(canvas_id);
|
|
|
|
|
VERIFY(context);
|
|
|
|
|
auto& webgl_context = as_webgl(*context);
|
|
|
|
|
MUST(webgl_context.execute_commands(commands, bitmaps));
|
|
|
|
|
if (auto surface = webgl_context.surface())
|
|
|
|
|
m_canvas_surface_registry.set_canvas_surface(canvas_id, surface.release_nonnull());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ErrorOr<ByteBuffer> CanvasHost::execute_webgl_sync_call(Web::Painting::CanvasId canvas_id, ByteBuffer request)
|
|
|
|
|
{
|
|
|
|
|
auto* context = this->context(canvas_id);
|
|
|
|
|
VERIFY(context);
|
|
|
|
|
return as_webgl(*context).execute_sync_call(request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Web::WebGL::ReadPixelsResult CanvasHost::webgl_read_pixels_robust_angle(Web::Painting::CanvasId canvas_id, Web::WebGL::GLint x, Web::WebGL::GLint y, Web::WebGL::GLsizei width, Web::WebGL::GLsizei height, Web::WebGL::GLenum format, Web::WebGL::GLenum type, Web::WebGL::GLsizei buf_size, Core::AnonymousBuffer pixels)
|
|
|
|
|
{
|
|
|
|
|
auto* context = this->context(canvas_id);
|
|
|
|
|
VERIFY(context);
|
|
|
|
|
return as_webgl(*context).read_pixels_robust_angle(x, y, width, height, format, type, buf_size, move(pixels));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CanvasHost::webgl_read_buffer_sub_data(Web::Painting::CanvasId canvas_id, Web::WebGL::GLenum target, Web::WebGL::GLintptr offset, Web::WebGL::GLintptr size, Core::AnonymousBuffer data)
|
|
|
|
|
{
|
|
|
|
|
auto* context = this->context(canvas_id);
|
|
|
|
|
VERIFY(context);
|
|
|
|
|
as_webgl(*context).read_buffer_sub_data(target, offset, size, move(data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CanvasHost::present_webgl_canvas(Web::Painting::CanvasId canvas_id, bool preserve_drawing_buffer)
|
|
|
|
|
{
|
|
|
|
|
auto* context = this->context(canvas_id);
|
|
|
|
|
VERIFY(context);
|
|
|
|
|
|
|
|
|
|
auto surface = MUST(as_webgl(*context).prepare_for_compositing(preserve_drawing_buffer));
|
|
|
|
|
m_canvas_surface_registry.set_canvas_surface(canvas_id, move(surface));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Gfx::ShareableBitmap read_back_surface(Gfx::PaintingSurface& surface, Gfx::IntRect rect)
|
|
|
|
|
{
|
|
|
|
|
auto clipped_rect = rect.intersected(surface.rect());
|
|
|
|
|
if (clipped_rect.is_empty())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
auto bitmap_or_error = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, clipped_rect.size());
|
|
|
|
|
if (bitmap_or_error.is_error())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
auto bitmap = bitmap_or_error.release_value();
|
|
|
|
|
surface.flush();
|
|
|
|
|
surface.read_into_bitmap(*bitmap, clipped_rect.location());
|
|
|
|
|
return Gfx::ShareableBitmap { move(bitmap), Gfx::ShareableBitmap::ConstructWithKnownGoodBitmap };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gfx::ShareableBitmap CanvasHost::read_back_pixels(Web::Painting::CanvasId canvas_id, Gfx::IntRect rect)
|
|
|
|
|
{
|
|
|
|
|
auto* context = this->context(canvas_id);
|
|
|
|
|
if (!context)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
return context->visit(
|
2026-06-18 18:12:08 -03:00
|
|
|
[rect](Canvas2DContext& canvas_context) {
|
|
|
|
|
return read_back_surface(canvas_context.command_player->surface(), rect);
|
2026-06-15 14:29:43 -03:00
|
|
|
},
|
|
|
|
|
[rect](WebGLContext& webgl_context) {
|
|
|
|
|
return webgl_context->read_back_drawing_buffer(rect);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|