LibGfx+LibWeb: Move Skia backend context to process level singleton

Previously, this was attached to the traversable navigable. Using a
singleton instead allows us to use the context for detached documents.
This commit is contained in:
Tim Ledbetter
2026-03-16 23:55:40 +00:00
committed by Jelle Raaijmakers
parent d4cf537d58
commit 26389363ad
Notes: github-actions[bot] 2026-03-19 12:36:24 +00:00
18 changed files with 90 additions and 77 deletions

View File

@@ -94,12 +94,13 @@ NonnullRefPtr<PaintingSurface> PaintingSurface::create_from_vkimage(NonnullRefPt
}
#endif
NonnullRefPtr<PaintingSurface> PaintingSurface::create_with_size(RefPtr<SkiaBackendContext> context, IntSize size, BitmapFormat color_type, AlphaType alpha_type)
NonnullRefPtr<PaintingSurface> PaintingSurface::create_with_size(IntSize size, BitmapFormat color_type, AlphaType alpha_type)
{
auto sk_color_type = to_skia_color_type(color_type);
auto sk_alpha_type = to_skia_alpha_type(color_type, alpha_type);
auto image_info = SkImageInfo::Make(size.width(), size.height(), sk_color_type, sk_alpha_type, SkColorSpace::MakeSRGB());
auto context = SkiaBackendContext::the();
if (context) {
context->lock();
auto surface = SkSurfaces::RenderTarget(context->sk_context(), skgpu::Budgeted::kNo, image_info);

View File

@@ -32,7 +32,7 @@ public:
Function<void(PaintingSurface&)> on_flush;
static NonnullRefPtr<PaintingSurface> create_with_size(RefPtr<SkiaBackendContext> context, IntSize size, BitmapFormat color_type, AlphaType alpha_type);
static NonnullRefPtr<PaintingSurface> create_with_size(IntSize size, BitmapFormat color_type, AlphaType alpha_type);
static NonnullRefPtr<PaintingSurface> wrap_bitmap(Bitmap&);
#ifdef AK_OS_MACOS

View File

@@ -27,6 +27,31 @@
namespace Gfx {
static RefPtr<SkiaBackendContext> s_the;
void SkiaBackendContext::initialize_gpu_backend()
{
VERIFY(!s_the);
#ifdef AK_OS_MACOS
auto metal_context = get_metal_context();
s_the = create_metal_context(*metal_context);
#elif USE_VULKAN
auto maybe_vulkan_context = Gfx::create_vulkan_context();
if (maybe_vulkan_context.is_error()) {
dbgln("Vulkan context creation failed: {}", maybe_vulkan_context.error());
return;
}
auto vulkan_context = maybe_vulkan_context.release_value();
s_the = create_vulkan_context(vulkan_context);
#endif
}
RefPtr<SkiaBackendContext> SkiaBackendContext::the()
{
return s_the;
}
#ifdef USE_VULKAN
class SkiaVulkanBackendContext final : public SkiaBackendContext {
AK_MAKE_NONCOPYABLE(SkiaVulkanBackendContext);

View File

@@ -39,6 +39,9 @@ public:
static RefPtr<SkiaBackendContext> create_metal_context(NonnullRefPtr<MetalContext>);
#endif
static void initialize_gpu_backend();
static RefPtr<SkiaBackendContext> the();
SkiaBackendContext() { }
virtual ~SkiaBackendContext() { }

View File

@@ -34,7 +34,6 @@
#include <LibWeb/HTML/ImageRequest.h>
#include <LibWeb/HTML/Path2D.h>
#include <LibWeb/HTML/TextMetrics.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Painting/Paintable.h>
@@ -248,8 +247,7 @@ void CanvasRenderingContext2D::allocate_painting_surface_if_needed()
auto color_type = m_context_attributes.alpha ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
auto skia_backend_context = canvas_element().navigable()->traversable_navigable()->skia_backend_context();
m_surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, canvas_element().bitmap_size_for_canvas(), color_type, Gfx::AlphaType::Premultiplied);
m_surface = Gfx::PaintingSurface::create_with_size(canvas_element().bitmap_size_for_canvas(), color_type, Gfx::AlphaType::Premultiplied);
m_painter = nullptr;
// https://html.spec.whatwg.org/multipage/canvas.html#the-canvas-settings:concept-canvas-alpha

View File

@@ -23,7 +23,6 @@
#include <LibWeb/HTML/HTMLCanvasElement.h>
#include <LibWeb/HTML/Numbers.h>
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/Layout/CanvasBox.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
@@ -307,14 +306,13 @@ Gfx::IntSize HTMLCanvasElement::bitmap_size_for_canvas(size_t minimum_width, siz
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-todataurl
String HTMLCanvasElement::to_data_url(StringView type, JS::Value js_quality)
{
// It is possible the canvas doesn't have a associated bitmap so create one
// It is possible the canvas doesn't have an associated bitmap so create one
allocate_painting_surface_if_needed();
auto surface = this->surface();
auto size = bitmap_size_for_canvas();
if (!surface && !size.is_empty()) {
// If the context is not initialized yet, we need to allocate transparent surface for serialization
auto skia_backend_context = navigable()->traversable_navigable()->skia_backend_context();
surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
surface = Gfx::PaintingSurface::create_with_size(size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
}
// FIXME: 1. If this canvas element's bitmap's origin-clean flag is set to false, then throw a "SecurityError" DOMException.
@@ -391,8 +389,7 @@ RefPtr<Gfx::Bitmap> HTMLCanvasElement::get_bitmap_from_surface()
auto surface = this->surface();
if (auto const size = bitmap_size_for_canvas(); !surface && !size.is_empty()) {
// If the context is not initialized yet, we need to allocate transparent surface for serialization
auto const skia_backend_context = navigable()->traversable_navigable()->skia_backend_context();
surface = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
surface = Gfx::PaintingSurface::create_with_size(size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
}
RefPtr<Gfx::Bitmap> bitmap;

View File

@@ -145,28 +145,6 @@ bool Navigable::is_ancestor_of(GC::Ref<Navigable> other) const
return false;
}
static RefPtr<Gfx::SkiaBackendContext> g_cached_skia_backend_context;
static RefPtr<Gfx::SkiaBackendContext> get_skia_backend_context()
{
if (!g_cached_skia_backend_context) {
#ifdef AK_OS_MACOS
auto metal_context = Gfx::get_metal_context();
g_cached_skia_backend_context = Gfx::SkiaBackendContext::create_metal_context(*metal_context);
#elif USE_VULKAN
auto maybe_vulkan_context = Gfx::create_vulkan_context();
if (maybe_vulkan_context.is_error()) {
dbgln("Vulkan context creation failed: {}", maybe_vulkan_context.error());
return {};
}
auto vulkan_context = maybe_vulkan_context.release_value();
g_cached_skia_backend_context = Gfx::SkiaBackendContext::create_vulkan_context(vulkan_context);
#endif
}
return g_cached_skia_backend_context;
}
Navigable::Navigable(GC::Ref<Page> page, bool is_svg_page)
: m_page(page)
, m_event_handler({}, *this)
@@ -179,19 +157,9 @@ 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) {
OwnPtr<Painting::DisplayListPlayerSkia> skia_player;
if (display_list_player_type == DisplayListPlayerType::SkiaGPUIfAvailable) {
skia_player = make<Painting::DisplayListPlayerSkia>(m_skia_backend_context);
} else {
skia_player = make<Painting::DisplayListPlayerSkia>();
}
m_rendering_thread.set_skia_player(move(skia_player));
auto display_list_player_type = page->client().display_list_player_type();
m_rendering_thread.set_skia_player(make<Painting::DisplayListPlayerSkia>());
m_rendering_thread.start(display_list_player_type);
}
}
@@ -2911,11 +2879,6 @@ void Navigable::render_screenshot(Gfx::PaintingSurface& painting_surface, PaintC
m_rendering_thread.request_screenshot(painting_surface, move(callback));
}
RefPtr<Gfx::SkiaBackendContext> Navigable::skia_backend_context() const
{
return m_skia_backend_context;
}
GC::Ref<WebIDL::Promise> Navigable::scroll_viewport_by_delta(CSSPixelPoint delta)
{
auto vv = active_document()->visual_viewport();

View File

@@ -212,8 +212,6 @@ public:
[[nodiscard]] bool has_inclusive_ancestor_with_visibility_hidden() const;
RefPtr<Gfx::SkiaBackendContext> skia_backend_context() const;
RenderingThread& rendering_thread() { return m_rendering_thread; }
void set_pending_set_browser_zoom_request(bool value) { m_pending_set_browser_zoom_request = value; }
@@ -291,7 +289,6 @@ private:
bool m_pending_set_browser_zoom_request { false };
bool m_should_show_line_box_borders { false };
GC::Ref<Painting::BackingStoreManager> m_backing_store_manager;
RefPtr<Gfx::SkiaBackendContext> m_skia_backend_context;
RenderingThread m_rendering_thread;
};

View File

@@ -7,6 +7,7 @@
#include <LibCore/Timer.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/PaintingSurface.h>
#include <LibGfx/SkiaBackendContext.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/Painting/BackingStoreManager.h>
#include <WebContent/PageClient.h>
@@ -50,7 +51,7 @@ void BackingStoreManager::restart_resize_timer()
void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size)
{
auto skia_backend_context = m_navigable->skia_backend_context();
auto skia_backend_context = Gfx::SkiaBackendContext::the();
RefPtr<Gfx::PaintingSurface> front_store;
RefPtr<Gfx::PaintingSurface> back_store;
@@ -119,11 +120,11 @@ void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size)
#ifdef USE_VULKAN
if (skia_backend_context) {
front_store = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
front_store = Gfx::PaintingSurface::create_with_size(size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
front_store->on_flush = [front_bitmap](auto& surface) {
surface.read_into_bitmap(*front_bitmap);
};
back_store = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
back_store = Gfx::PaintingSurface::create_with_size(size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
back_store->on_flush = [back_bitmap](auto& surface) {
surface.read_into_bitmap(*back_bitmap);
};

View File

@@ -28,6 +28,7 @@
#include <LibGfx/Font/Font.h>
#include <LibGfx/PainterSkia.h>
#include <LibGfx/PathSkia.h>
#include <LibGfx/SkiaBackendContext.h>
#include <LibGfx/SkiaUtils.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
@@ -35,11 +36,6 @@
namespace Web::Painting {
DisplayListPlayerSkia::DisplayListPlayerSkia(RefPtr<Gfx::SkiaBackendContext> context)
: m_context(context)
{
}
DisplayListPlayerSkia::DisplayListPlayerSkia()
{
}
@@ -97,8 +93,8 @@ static SkM44 to_skia_matrix4x4(Gfx::FloatMatrix4x4 const& matrix)
void DisplayListPlayerSkia::flush()
{
if (m_context)
m_context->flush_and_submit(&surface().sk_surface());
if (auto context = Gfx::SkiaBackendContext::the())
context->flush_and_submit(&surface().sk_surface());
surface().flush();
}
@@ -143,7 +139,7 @@ void DisplayListPlayerSkia::draw_external_content(DrawExternalContent const& com
auto bitmap = command.source->current_bitmap();
if (!bitmap)
return;
if (m_context && !bitmap->ensure_sk_image(*m_context))
if (Gfx::SkiaBackendContext::the() && !bitmap->ensure_sk_image(*Gfx::SkiaBackendContext::the()))
return;
auto dst_rect = to_skia_rect(command.dst_rect);
SkRect src_rect = SkRect::MakeIWH(bitmap->width(), bitmap->height());
@@ -155,7 +151,7 @@ void DisplayListPlayerSkia::draw_external_content(DrawExternalContent const& com
void DisplayListPlayerSkia::draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const& command)
{
if (m_context && !command.bitmap->ensure_sk_image(*m_context))
if (Gfx::SkiaBackendContext::the() && !command.bitmap->ensure_sk_image(*Gfx::SkiaBackendContext::the()))
return;
auto dst_rect = to_skia_rect(command.dst_rect);
@@ -171,7 +167,7 @@ void DisplayListPlayerSkia::draw_scaled_immutable_bitmap(DrawScaledImmutableBitm
void DisplayListPlayerSkia::draw_repeated_immutable_bitmap(DrawRepeatedImmutableBitmap const& command)
{
if (m_context && !command.bitmap->ensure_sk_image(*m_context))
if (Gfx::SkiaBackendContext::the() && !command.bitmap->ensure_sk_image(*Gfx::SkiaBackendContext::the()))
return;
SkMatrix matrix;
@@ -488,7 +484,7 @@ SkPaint DisplayListPlayerSkia::paint_style_to_skia_paint(Painting::SVGPaintServe
if (tile_size.is_empty())
return {};
auto tile_surface = Gfx::PaintingSurface::create_with_size(m_context, tile_size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
auto tile_surface = Gfx::PaintingSurface::create_with_size(tile_size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
execute_display_list_into_surface(*pattern->tile_display_list(), *tile_surface);

View File

@@ -6,7 +6,6 @@
#pragma once
#include <LibGfx/SkiaBackendContext.h>
#include <LibWeb/Painting/DisplayList.h>
#include <LibWeb/Painting/DisplayListCommand.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
@@ -18,7 +17,6 @@ namespace Web::Painting {
class DisplayListPlayerSkia final : public DisplayListPlayer {
public:
DisplayListPlayerSkia(RefPtr<Gfx::SkiaBackendContext>);
DisplayListPlayerSkia();
~DisplayListPlayerSkia();
@@ -59,8 +57,6 @@ private:
bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override;
SkPaint paint_style_to_skia_paint(SVGPaintServerPaintStyle const&, Gfx::FloatRect const& bounding_rect);
RefPtr<Gfx::SkiaBackendContext> m_context;
};
}

View File

@@ -118,7 +118,7 @@ RefPtr<Gfx::PaintingSurface> SVGDecodedImageData::render_to_surface(Gfx::IntSize
if (m_cached_rendered_surfaces.size() > 10)
m_cached_rendered_surfaces.remove(m_cached_rendered_surfaces.begin());
auto surface = Gfx::PaintingSurface::create_with_size(m_document->navigable()->skia_backend_context(), size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
auto surface = Gfx::PaintingSurface::create_with_size(size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied);
auto display_list = record_display_list(size);
if (!display_list)
return nullptr;

View File

@@ -6,12 +6,12 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/SkiaBackendContext.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/WebGL2RenderingContextPrototype.h>
#include <LibWeb/HTML/HTMLCanvasElement.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/Infra/Strings.h>
#include <LibWeb/Painting/Paintable.h>
#include <LibWeb/WebGL/EventNames.h>
@@ -34,7 +34,7 @@ JS::ThrowCompletionOr<GC::Ptr<WebGL2RenderingContext>> WebGL2RenderingContext::c
// We should be coming here from getContext being called on a wrapped <canvas> element.
auto context_attributes = TRY(convert_value_to_context_attributes_dictionary(canvas_element.vm(), options));
auto skia_backend_context = canvas_element.navigable()->traversable_navigable()->skia_backend_context();
auto skia_backend_context = Gfx::SkiaBackendContext::the();
if (!skia_backend_context) {
fire_webgl_context_creation_error(canvas_element);
return GC::Ptr<WebGL2RenderingContext> { nullptr };

View File

@@ -5,12 +5,12 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/SkiaBackendContext.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/WebGLRenderingContextPrototype.h>
#include <LibWeb/HTML/HTMLCanvasElement.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/Infra/Strings.h>
#include <LibWeb/Painting/Paintable.h>
#include <LibWeb/WebGL/EventNames.h>
@@ -50,7 +50,7 @@ JS::ThrowCompletionOr<GC::Ptr<WebGLRenderingContext>> WebGLRenderingContext::cre
// We should be coming here from getContext being called on a wrapped <canvas> element.
auto context_attributes = TRY(convert_value_to_context_attributes_dictionary(canvas_element.vm(), options));
auto skia_backend_context = canvas_element.navigable()->traversable_navigable()->skia_backend_context();
auto skia_backend_context = Gfx::SkiaBackendContext::the();
if (!skia_backend_context) {
fire_webgl_context_creation_error(canvas_element);
return GC::Ptr<WebGLRenderingContext> { nullptr };

View File

@@ -15,6 +15,7 @@
#include <LibCrypto/OpenSSLForward.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/PathFontProvider.h>
#include <LibGfx/SkiaBackendContext.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibIPC/TransportHandle.h>
#include <LibJS/Bytecode/Interpreter.h>
@@ -198,7 +199,12 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
Web::set_browser_process_executable_path(executable_path);
// Always use the CPU backend for tests, as the GPU backend is not deterministic
WebContent::PageClient::set_use_skia_painter(force_cpu_painting ? WebContent::PageClient::UseSkiaPainter::CPUBackend : WebContent::PageClient::UseSkiaPainter::GPUBackendIfAvailable);
if (force_cpu_painting) {
WebContent::PageClient::set_use_skia_painter(WebContent::PageClient::UseSkiaPainter::CPUBackend);
} else {
Gfx::SkiaBackendContext::initialize_gpu_backend();
WebContent::PageClient::set_use_skia_painter(WebContent::PageClient::UseSkiaPainter::GPUBackendIfAvailable);
}
WebContent::PageClient::set_is_headless(is_headless);

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<script>
var doc = document.implementation.createHTMLDocument("");
var canvas = doc.createElement("canvas");
doc.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, 50, 50);
</script>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<body>
<script>
var doc = document.implementation.createHTMLDocument('');
var canvas = doc.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
doc.body.appendChild(canvas);
canvas.toDataURL();
</script>
</body>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<body>
<script>
var doc = document.implementation.createHTMLDocument('');
var canvas = doc.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
doc.body.appendChild(canvas);
canvas.getContext('webgl');
</script>
</body>