diff --git a/Libraries/LibGfx/CMakeLists.txt b/Libraries/LibGfx/CMakeLists.txt index f1d1c8105a..852a590cdc 100644 --- a/Libraries/LibGfx/CMakeLists.txt +++ b/Libraries/LibGfx/CMakeLists.txt @@ -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) diff --git a/Libraries/LibGfx/Direct3DContext.cpp b/Libraries/LibGfx/Direct3DContext.cpp new file mode 100644 index 0000000000..db3a51d028 --- /dev/null +++ b/Libraries/LibGfx/Direct3DContext.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2026, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +#ifdef USE_DIRECTX + +# include +# include +# include + +// NOTE: Not using the newer winrt that supersedes wrl as that uses exceptions for error handling. +# include + +namespace Gfx { + +using namespace Microsoft::WRL; + +struct Direct3DContext::Impl { + Impl(ComPtr adapter, ComPtr device, ComPtr queue) + : adapter(move(adapter)) + , device(move(device)) + , queue(move(queue)) + { + } + + ComPtr adapter; + ComPtr device; + ComPtr queue; +}; + +Direct3DContext::Direct3DContext(NonnullOwnPtr 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> get_hardware_adapter(IDXGIFactory4& factory) +{ + for (UINT adapter_index = 0;; ++adapter_index) { + ComPtr 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> create_direct3d_context() +{ + ComPtr 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 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 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(move(adapter), move(device), move(queue)))); +} + +} + +#endif diff --git a/Libraries/LibGfx/Direct3DContext.h b/Libraries/LibGfx/Direct3DContext.h new file mode 100644 index 0000000000..5a21bdc89e --- /dev/null +++ b/Libraries/LibGfx/Direct3DContext.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2026, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#ifdef USE_DIRECTX + +# include +# include +# include +# include + +struct ID3D12CommandQueue; +struct ID3D12Device; +struct IDXGIAdapter1; + +namespace Gfx { + +class Direct3DContext final : public RefCounted { +public: + ~Direct3DContext(); + + IDXGIAdapter1* adapter() const; + ID3D12Device* device() const; + ID3D12CommandQueue* queue() const; + +private: + struct Impl; + + explicit Direct3DContext(NonnullOwnPtr); + + NonnullOwnPtr m_impl; + + friend ErrorOr> create_direct3d_context(); +}; + +ErrorOr> create_direct3d_context(); + +} + +#endif diff --git a/Libraries/LibGfx/SkiaBackendContext.cpp b/Libraries/LibGfx/SkiaBackendContext.cpp index 8e75f8aefa..5ff0a785d8 100644 --- a/Libraries/LibGfx/SkiaBackendContext.cpp +++ b/Libraries/LibGfx/SkiaBackendContext.cpp @@ -9,11 +9,19 @@ #include #include #include +#ifdef USE_DIRECTX +# include +#endif #include #include #include +#ifdef USE_DIRECTX +# include +# include +#endif + #ifdef USE_VULKAN # include # include @@ -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*>(context); @@ -127,7 +135,14 @@ RefPtr 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::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 context, NonnullRefPtr 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&& 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 m_context; + NonnullRefPtr m_direct3d_context; +}; + +RefPtr SkiaBackendContext::create_direct3d_context(NonnullRefPtr 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); diff --git a/Libraries/LibGfx/SkiaBackendContext.h b/Libraries/LibGfx/SkiaBackendContext.h index 2855f02fcf..41301587b0 100644 --- a/Libraries/LibGfx/SkiaBackendContext.h +++ b/Libraries/LibGfx/SkiaBackendContext.h @@ -10,6 +10,10 @@ #include #include +#ifdef USE_DIRECTX +# include +#endif + #ifdef USE_VULKAN # include #endif @@ -24,6 +28,7 @@ class SkSurface; namespace Gfx { struct VulkanContext; +class Direct3DContext; class MetalContext; class SkiaBackendContext : public AtomicRefCounted { @@ -31,6 +36,10 @@ class SkiaBackendContext : public AtomicRefCounted { AK_MAKE_NONMOVABLE(SkiaBackendContext); public: +#ifdef USE_DIRECTX + static RefPtr create_direct3d_context(NonnullRefPtr); +#endif + #ifdef USE_VULKAN static RefPtr create_vulkan_context(const VulkanContext& vulkan_context); #endif