ladybird/Libraries/LibGfx/SkiaBackendContext.cpp
Aliaksandr Kalenik 3ded5bdcdf LibGfx: Add a Direct3D Skia backend
With Windows backend selection fixed to Direct3D, LibGfx needs a native
GPU context that can feed Skia directly. Add a Direct3DContext helper
that owns the DXGI adapter, D3D12 device, and direct command queue, then
pass that state to Skia through GrDirectContext::MakeDirect3D..
2026-06-15 04:50:29 +02:00

353 lines
12 KiB
C++

/*
* Copyright (c) 2024-2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NeverDestroyed.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/RefPtr.h>
#include <AK/Time.h>
#include <LibGfx/Bitmap.h>
#ifdef USE_DIRECTX
# include <LibGfx/Direct3DContext.h>
#endif
#include <LibGfx/SkiaBackendContext.h>
#include <core/SkSurface.h>
#include <gpu/ganesh/GrDirectContext.h>
#ifdef USE_DIRECTX
# include <AK/Windows.h>
# include <gpu/ganesh/d3d/GrD3DBackendContext.h>
#endif
#ifdef USE_VULKAN
# include <gpu/ganesh/vk/GrVkDirectContext.h>
# include <gpu/vk/VulkanBackendContext.h>
# include <gpu/vk/VulkanExtensions.h>
#endif
#ifdef AK_OS_MACOS
# include <gpu/ganesh/GrBackendSurface.h>
# include <gpu/ganesh/mtl/GrMtlBackendContext.h>
# include <gpu/ganesh/mtl/GrMtlBackendSurface.h>
# include <gpu/ganesh/mtl/GrMtlDirectContext.h>
#endif
namespace Gfx {
#if defined(AK_OS_MACOS) || defined(USE_DIRECTX) || defined(USE_VULKAN)
static constexpr size_t skia_resource_cache_limit = 256 * MiB;
#endif
static constexpr auto skia_deferred_cleanup_interval = AK::Duration::from_seconds(1);
static constexpr auto skia_aggressive_cleanup_interval = AK::Duration::from_seconds(5);
static constexpr auto skia_deferred_cleanup_resource_age = std::chrono::seconds(5);
static constexpr auto skia_resource_cache_high_watermark = 384 * MiB;
static constexpr auto skia_resource_cache_critical_watermark = 512 * MiB;
static auto& main_thread_context()
{
static NeverDestroyed<RefPtr<SkiaBackendContext>> context;
return *context;
}
#if defined(AK_OS_MACOS) || defined(USE_DIRECTX) || defined(USE_VULKAN)
static void invoke_async_flush_callback(void* context)
{
auto* callback = static_cast<Function<void()>*>(context);
auto callback_to_invoke = move(*callback);
delete callback;
callback_to_invoke();
}
static void flush_and_submit_async_to_context(GrDirectContext& context, SkSurface* surface, Function<void()>&& callback)
{
GrFlushInfo flush_info {};
flush_info.fFinishedProc = invoke_async_flush_callback;
flush_info.fFinishedContext = new Function<void()>(move(callback));
context.flush(surface, SkSurfaces::BackendSurfaceAccess::kPresent, flush_info);
VERIFY(context.submit(GrSyncCpu::kNo));
}
#endif
void SkiaBackendContext::check_async_work_completion()
{
if (auto* context = sk_context())
context->checkAsyncWorkCompletion();
}
void SkiaBackendContext::flush_and_submit(SkSurface* surface)
{
flush_and_submit_impl(surface);
perform_post_flush_cleanup();
}
void SkiaBackendContext::flush_and_submit_async(SkSurface* surface, Function<void()>&& callback)
{
flush_and_submit_async_impl(surface, move(callback));
perform_post_flush_cleanup();
}
void SkiaBackendContext::perform_post_flush_cleanup()
{
auto* context = sk_context();
if (!context)
return;
static thread_local Optional<MonotonicTime> s_last_deferred_cleanup;
static thread_local Optional<MonotonicTime> s_last_aggressive_cleanup;
auto const now = MonotonicTime::now();
if (s_last_deferred_cleanup.has_value() && now - *s_last_deferred_cleanup < skia_deferred_cleanup_interval)
return;
s_last_deferred_cleanup = now;
context->performDeferredCleanup(skia_deferred_cleanup_resource_age);
size_t resource_bytes = 0;
context->getResourceCacheUsage(nullptr, &resource_bytes);
if (resource_bytes < skia_resource_cache_high_watermark)
return;
if (s_last_aggressive_cleanup.has_value() && now - *s_last_aggressive_cleanup < skia_aggressive_cleanup_interval)
return;
s_last_aggressive_cleanup = now;
context->performDeferredCleanup(std::chrono::milliseconds(0));
context->getResourceCacheUsage(nullptr, &resource_bytes);
if (resource_bytes >= skia_resource_cache_critical_watermark)
context->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
}
void SkiaBackendContext::initialize_gpu_backend()
{
VERIFY(!main_thread_context());
main_thread_context() = create_independent_gpu_backend();
}
RefPtr<SkiaBackendContext> SkiaBackendContext::create_independent_gpu_backend()
{
#ifdef AK_OS_MACOS
auto metal_context = get_metal_context();
if (!metal_context)
return {};
return create_metal_context(*metal_context);
#elif defined(USE_DIRECTX)
auto maybe_direct3d_context = Gfx::create_direct3d_context();
if (maybe_direct3d_context.is_error()) {
dbgln("Direct3D 12 context creation failed: {}", maybe_direct3d_context.error());
return {};
}
return create_direct3d_context(maybe_direct3d_context.release_value());
#elif defined(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();
return create_vulkan_context(vulkan_context);
#else
return {};
#endif
}
RefPtr<SkiaBackendContext> SkiaBackendContext::the_main_thread_context()
{
return main_thread_context();
}
#ifdef USE_DIRECTX
class SkiaDirect3DBackendContext final : public SkiaBackendContext {
AK_MAKE_NONCOPYABLE(SkiaDirect3DBackendContext);
AK_MAKE_NONMOVABLE(SkiaDirect3DBackendContext);
public:
SkiaDirect3DBackendContext(sk_sp<GrDirectContext> context, NonnullRefPtr<Direct3DContext> direct3d_context)
: m_context(move(context))
, m_direct3d_context(move(direct3d_context))
{
}
~SkiaDirect3DBackendContext() override
{
m_context.reset();
}
void flush_and_submit_impl(SkSurface* surface) override
{
GrFlushInfo const flush_info {};
m_context->flush(surface, SkSurfaces::BackendSurfaceAccess::kPresent, flush_info);
m_context->submit(GrSyncCpu::kYes);
}
void flush_and_submit_async_impl(SkSurface* surface, Function<void()>&& callback) override
{
flush_and_submit_async_to_context(*m_context, surface, move(callback));
}
GrDirectContext* sk_context() const override { return m_context.get(); }
VulkanContext const& vulkan_context() override { VERIFY_NOT_REACHED(); }
MetalContext& metal_context() override { VERIFY_NOT_REACHED(); }
private:
sk_sp<GrDirectContext> m_context;
NonnullRefPtr<Direct3DContext> m_direct3d_context;
};
RefPtr<SkiaBackendContext> SkiaBackendContext::create_direct3d_context(NonnullRefPtr<Direct3DContext> direct3d_context)
{
GrD3DBackendContext backend_context;
backend_context.fAdapter.retain(direct3d_context->adapter());
backend_context.fDevice.retain(direct3d_context->device());
backend_context.fQueue.retain(direct3d_context->queue());
backend_context.fProtectedContext = GrProtected::kNo;
auto context = GrDirectContext::MakeDirect3D(backend_context);
if (!context) {
dbgln("Skia Direct3D context creation failed");
return {};
}
context->setResourceCacheLimit(skia_resource_cache_limit);
return adopt_ref(*new SkiaDirect3DBackendContext(move(context), move(direct3d_context)));
}
#endif
#ifdef USE_VULKAN
class SkiaVulkanBackendContext final : public SkiaBackendContext {
AK_MAKE_NONCOPYABLE(SkiaVulkanBackendContext);
AK_MAKE_NONMOVABLE(SkiaVulkanBackendContext);
public:
SkiaVulkanBackendContext(sk_sp<GrDirectContext> context, VulkanContext const& vulkan_context, NonnullOwnPtr<skgpu::VulkanExtensions> extensions)
: m_context(move(context))
, m_extensions(move(extensions))
, m_vulkan_context(vulkan_context)
{
}
~SkiaVulkanBackendContext() override
{
m_context.reset();
# ifdef USE_VULKAN_DMABUF_IMAGES
if (m_vulkan_context.command_pool != VK_NULL_HANDLE)
vkDestroyCommandPool(m_vulkan_context.logical_device, m_vulkan_context.command_pool, nullptr);
# endif
if (m_vulkan_context.logical_device != VK_NULL_HANDLE)
vkDestroyDevice(m_vulkan_context.logical_device, nullptr);
if (m_vulkan_context.instance != VK_NULL_HANDLE)
vkDestroyInstance(m_vulkan_context.instance, nullptr);
}
void flush_and_submit_impl(SkSurface* surface) override
{
GrFlushInfo const flush_info {};
m_context->flush(surface, SkSurfaces::BackendSurfaceAccess::kPresent, flush_info);
m_context->submit(GrSyncCpu::kYes);
}
void flush_and_submit_async_impl(SkSurface* surface, Function<void()>&& callback) override
{
flush_and_submit_async_to_context(*m_context, surface, move(callback));
}
skgpu::VulkanExtensions const* extensions() const { return m_extensions.ptr(); }
GrDirectContext* sk_context() const override { return m_context.get(); }
VulkanContext const& vulkan_context() override { return m_vulkan_context; }
MetalContext& metal_context() override { VERIFY_NOT_REACHED(); }
private:
sk_sp<GrDirectContext> m_context;
NonnullOwnPtr<skgpu::VulkanExtensions> m_extensions;
VulkanContext const m_vulkan_context;
};
RefPtr<SkiaBackendContext> SkiaBackendContext::create_vulkan_context(VulkanContext const& vulkan_context)
{
skgpu::VulkanBackendContext backend_context;
backend_context.fInstance = vulkan_context.instance;
backend_context.fDevice = vulkan_context.logical_device;
backend_context.fQueue = vulkan_context.graphics_queue;
backend_context.fGraphicsQueueIndex = vulkan_context.graphics_queue_family;
backend_context.fPhysicalDevice = vulkan_context.physical_device;
backend_context.fMaxAPIVersion = vulkan_context.api_version;
backend_context.fGetProc = [](char const* proc_name, VkInstance instance, VkDevice device) {
if (device != VK_NULL_HANDLE) {
return vkGetDeviceProcAddr(device, proc_name);
}
return vkGetInstanceProcAddr(instance, proc_name);
};
auto extensions = make<skgpu::VulkanExtensions>();
backend_context.fVkExtensions = extensions.ptr();
sk_sp<GrDirectContext> ctx = GrDirectContexts::MakeVulkan(backend_context);
VERIFY(ctx);
ctx->setResourceCacheLimit(skia_resource_cache_limit);
return adopt_ref(*new SkiaVulkanBackendContext(ctx, vulkan_context, move(extensions)));
}
#endif
#ifdef AK_OS_MACOS
class SkiaMetalBackendContext final : public SkiaBackendContext {
AK_MAKE_NONCOPYABLE(SkiaMetalBackendContext);
AK_MAKE_NONMOVABLE(SkiaMetalBackendContext);
public:
SkiaMetalBackendContext(sk_sp<GrDirectContext> context, NonnullRefPtr<MetalContext> metal_context)
: m_context(move(context))
, m_metal_context(move(metal_context))
{
}
~SkiaMetalBackendContext() override
{
m_context.reset();
}
void flush_and_submit_impl(SkSurface* surface) override
{
GrFlushInfo const flush_info {};
m_context->flush(surface, SkSurfaces::BackendSurfaceAccess::kPresent, flush_info);
m_context->submit(GrSyncCpu::kYes);
}
void flush_and_submit_async_impl(SkSurface* surface, Function<void()>&& callback) override
{
flush_and_submit_async_to_context(*m_context, surface, move(callback));
}
GrDirectContext* sk_context() const override { return m_context.get(); }
VulkanContext const& vulkan_context() override { VERIFY_NOT_REACHED(); }
MetalContext& metal_context() override { return m_metal_context; }
private:
sk_sp<GrDirectContext> m_context;
NonnullRefPtr<MetalContext> m_metal_context;
};
RefPtr<SkiaBackendContext> SkiaBackendContext::create_metal_context(NonnullRefPtr<MetalContext> metal_context)
{
GrMtlBackendContext backend_context;
backend_context.fDevice.retain(metal_context->device());
backend_context.fQueue.retain(metal_context->queue());
sk_sp<GrDirectContext> ctx = GrDirectContexts::MakeMetal(backend_context);
VERIFY(ctx);
ctx->setResourceCacheLimit(skia_resource_cache_limit);
return adopt_ref(*new SkiaMetalBackendContext(move(ctx), move(metal_context)));
}
#endif
}