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..
This commit is contained in:
Aliaksandr Kalenik 2026-06-15 02:31:19 +02:00 committed by Alexander Kalenik
parent d858c7219b
commit 3ded5bdcdf
5 changed files with 243 additions and 3 deletions

View file

@ -60,6 +60,10 @@ if (APPLE)
list(APPEND SOURCES MetalContext.mm)
endif()
if (HAS_DIRECTX)
list(APPEND SOURCES Direct3DContext.cpp)
endif()
if (HAS_VULKAN)
list(APPEND SOURCES VulkanContext.cpp VulkanImage.cpp)
endif()
@ -96,6 +100,10 @@ if (APPLE)
target_link_libraries(LibCore PUBLIC "-framework Accelerate")
endif()
if (HAS_DIRECTX)
target_link_libraries(LibGfx PRIVATE d3d12 dxgi d3dcompiler)
endif()
if (HAS_VULKAN)
target_link_libraries(LibCore PUBLIC Vulkan::Vulkan Vulkan::Headers)

View file

@ -0,0 +1,105 @@
/*
* Copyright (c) 2026, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Error.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/RefPtr.h>
#include <LibGfx/Direct3DContext.h>
#ifdef USE_DIRECTX
# include <AK/Windows.h>
# include <d3d12.h>
# include <dxgi1_4.h>
// NOTE: Not using the newer winrt that supersedes wrl as that uses exceptions for error handling.
# include <wrl/client.h>
namespace Gfx {
using namespace Microsoft::WRL;
struct Direct3DContext::Impl {
Impl(ComPtr<IDXGIAdapter1> adapter, ComPtr<ID3D12Device> device, ComPtr<ID3D12CommandQueue> queue)
: adapter(move(adapter))
, device(move(device))
, queue(move(queue))
{
}
ComPtr<IDXGIAdapter1> adapter;
ComPtr<ID3D12Device> device;
ComPtr<ID3D12CommandQueue> queue;
};
Direct3DContext::Direct3DContext(NonnullOwnPtr<Impl> impl)
: m_impl(move(impl))
{
}
Direct3DContext::~Direct3DContext() = default;
IDXGIAdapter1* Direct3DContext::adapter() const
{
return m_impl->adapter.Get();
}
ID3D12Device* Direct3DContext::device() const
{
return m_impl->device.Get();
}
ID3D12CommandQueue* Direct3DContext::queue() const
{
return m_impl->queue.Get();
}
static ErrorOr<ComPtr<IDXGIAdapter1>> get_hardware_adapter(IDXGIFactory4& factory)
{
for (UINT adapter_index = 0;; ++adapter_index) {
ComPtr<IDXGIAdapter1> adapter;
auto hr = factory.EnumAdapters1(adapter_index, &adapter);
if (hr == DXGI_ERROR_NOT_FOUND)
break;
if (FAILED(hr))
return Error::from_windows_error(hr);
if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), nullptr)))
return move(adapter);
}
return Error::from_string_literal("No Direct3D 12 adapter found");
}
ErrorOr<NonnullRefPtr<Direct3DContext>> create_direct3d_context()
{
ComPtr<IDXGIFactory4> factory;
auto hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
if (FAILED(hr))
return Error::from_windows_error(hr);
auto adapter = TRY(get_hardware_adapter(*factory.Get()));
ComPtr<ID3D12Device> device;
hr = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device));
if (FAILED(hr))
return Error::from_windows_error(hr);
D3D12_COMMAND_QUEUE_DESC queue_description {};
queue_description.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queue_description.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
ComPtr<ID3D12CommandQueue> queue;
hr = device->CreateCommandQueue(&queue_description, IID_PPV_ARGS(&queue));
if (FAILED(hr))
return Error::from_windows_error(hr);
return adopt_ref(*new Direct3DContext(make<Direct3DContext::Impl>(move(adapter), move(device), move(queue))));
}
}
#endif

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2026, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#ifdef USE_DIRECTX
# include <AK/Error.h>
# include <AK/NonnullOwnPtr.h>
# include <AK/NonnullRefPtr.h>
# include <AK/RefCounted.h>
struct ID3D12CommandQueue;
struct ID3D12Device;
struct IDXGIAdapter1;
namespace Gfx {
class Direct3DContext final : public RefCounted<Direct3DContext> {
public:
~Direct3DContext();
IDXGIAdapter1* adapter() const;
ID3D12Device* device() const;
ID3D12CommandQueue* queue() const;
private:
struct Impl;
explicit Direct3DContext(NonnullOwnPtr<Impl>);
NonnullOwnPtr<Impl> m_impl;
friend ErrorOr<NonnullRefPtr<Direct3DContext>> create_direct3d_context();
};
ErrorOr<NonnullRefPtr<Direct3DContext>> create_direct3d_context();
}
#endif

View file

@ -9,11 +9,19 @@
#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>
@ -29,7 +37,7 @@
namespace Gfx {
#if defined(AK_OS_MACOS) || USE_VULKAN
#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);
@ -44,7 +52,7 @@ static auto& main_thread_context()
return *context;
}
#if defined(AK_OS_MACOS) || USE_VULKAN
#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);
@ -127,7 +135,14 @@ RefPtr<SkiaBackendContext> SkiaBackendContext::create_independent_gpu_backend()
if (!metal_context)
return {};
return create_metal_context(*metal_context);
#elif USE_VULKAN
#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());
@ -145,6 +160,65 @@ 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);

View file

@ -10,6 +10,10 @@
#include <AK/Function.h>
#include <AK/Noncopyable.h>
#ifdef USE_DIRECTX
# include <LibGfx/Direct3DContext.h>
#endif
#ifdef USE_VULKAN
# include <LibGfx/VulkanContext.h>
#endif
@ -24,6 +28,7 @@ class SkSurface;
namespace Gfx {
struct VulkanContext;
class Direct3DContext;
class MetalContext;
class SkiaBackendContext : public AtomicRefCounted<SkiaBackendContext> {
@ -31,6 +36,10 @@ class SkiaBackendContext : public AtomicRefCounted<SkiaBackendContext> {
AK_MAKE_NONMOVABLE(SkiaBackendContext);
public:
#ifdef USE_DIRECTX
static RefPtr<SkiaBackendContext> create_direct3d_context(NonnullRefPtr<Direct3DContext>);
#endif
#ifdef USE_VULKAN
static RefPtr<SkiaBackendContext> create_vulkan_context(const VulkanContext& vulkan_context);
#endif