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:
parent
d858c7219b
commit
3ded5bdcdf
5 changed files with 243 additions and 3 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
105
Libraries/LibGfx/Direct3DContext.cpp
Normal file
105
Libraries/LibGfx/Direct3DContext.cpp
Normal 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
|
||||
44
Libraries/LibGfx/Direct3DContext.h
Normal file
44
Libraries/LibGfx/Direct3DContext.h
Normal 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
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue