diff --git a/Meta/CMake/vulkan.cmake b/Meta/CMake/vulkan.cmake new file mode 100644 index 0000000000..be853d8f2b --- /dev/null +++ b/Meta/CMake/vulkan.cmake @@ -0,0 +1,7 @@ +if (NOT APPLE) + find_package(Vulkan QUIET) + if (Vulkan_FOUND) + set(HAS_VULKAN ON CACHE BOOL "" FORCE) + add_compile_definitions(USE_VULKAN=1) + endif() +endif() diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt index f32f618fd6..6b3464c9da 100644 --- a/Userland/Libraries/LibCore/CMakeLists.txt +++ b/Userland/Libraries/LibCore/CMakeLists.txt @@ -1,3 +1,5 @@ +include(vulkan) + # These are the minimal set of sources needed to build the code generators. We separate them to allow # LibCore to depend on generated sources. set(SOURCES @@ -79,6 +81,11 @@ else() ) endif() +if (HAS_VULKAN) + include_directories(${Vulkan_INCLUDE_DIR}) + list(APPEND SOURCES VulkanContext.cpp) +endif() + if (APPLE OR CMAKE_SYSTEM_NAME STREQUAL "GNU") list(APPEND SOURCES MachPort.cpp) endif() @@ -103,3 +110,7 @@ endif() if (ANDROID) target_link_libraries(LibCore PRIVATE log) endif() + +if (HAS_VULKAN) + target_link_libraries(LibCore PUBLIC ${Vulkan_LIBRARIES}) +endif() diff --git a/Userland/Libraries/LibCore/VulkanContext.cpp b/Userland/Libraries/LibCore/VulkanContext.cpp new file mode 100644 index 0000000000..85393e30ba --- /dev/null +++ b/Userland/Libraries/LibCore/VulkanContext.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Core { + +ErrorOr create_instance(uint32_t api_version) +{ + VkInstance instance; + + VkApplicationInfo app_info {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pApplicationName = "Ladybird"; + app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + app_info.pEngineName = nullptr; + app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0); + app_info.apiVersion = api_version; + + VkInstanceCreateInfo create_info {}; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + create_info.pApplicationInfo = &app_info; + + if (vkCreateInstance(&create_info, nullptr, &instance) != VK_SUCCESS) { + return Error::from_string_view("Application instance creation failed"sv); + } + + return instance; +} + +ErrorOr pick_physical_device(VkInstance instance) +{ + uint32_t device_count = 0; + vkEnumeratePhysicalDevices(instance, &device_count, nullptr); + + if (device_count == 0) + return Error::from_string_view("Can't find any physical devices available"sv); + + Vector devices; + devices.resize(device_count); + vkEnumeratePhysicalDevices(instance, &device_count, devices.data()); + + VkPhysicalDevice picked_device = VK_NULL_HANDLE; + // Pick discrete GPU or the first device in the list + for (auto const& device : devices) { + if (picked_device == VK_NULL_HANDLE) + picked_device = device; + + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties(device, &device_properties); + if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + picked_device = device; + } + + if (picked_device != VK_NULL_HANDLE) + return picked_device; + + VERIFY_NOT_REACHED(); +} + +ErrorOr create_logical_device(VkPhysicalDevice physical_device) +{ + VkDevice device; + + uint32_t queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr); + Vector queue_families; + queue_families.resize(queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families.data()); + + int graphics_queue_family_index = -1; + for (int i = 0; i < static_cast(queue_families.size()); i++) { + if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + graphics_queue_family_index = i; + break; + } + } + + VkDeviceQueueCreateInfo queue_create_info {}; + queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info.queueFamilyIndex = graphics_queue_family_index; + queue_create_info.queueCount = 1; + + float const queue_priority = 1.0f; + queue_create_info.pQueuePriorities = &queue_priority; + + VkPhysicalDeviceFeatures deviceFeatures {}; + + VkDeviceCreateInfo create_device_info {}; + create_device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + create_device_info.pQueueCreateInfos = &queue_create_info; + create_device_info.queueCreateInfoCount = 1; + create_device_info.pEnabledFeatures = &deviceFeatures; + + if (vkCreateDevice(physical_device, &create_device_info, nullptr, &device) != VK_SUCCESS) { + return Error::from_string_view("Logical device creation failed"sv); + } + + return device; +} + +ErrorOr create_vulkan_context() +{ + uint32_t const api_version = VK_API_VERSION_1_0; + auto* instance = TRY(create_instance(api_version)); + auto* physical_device = TRY(pick_physical_device(instance)); + auto* logical_device = TRY(create_logical_device(physical_device)); + + VkQueue graphics_queue; + vkGetDeviceQueue(logical_device, 0, 0, &graphics_queue); + + return VulkanContext { + .api_version = api_version, + .instance = instance, + .physical_device = physical_device, + .logical_device = logical_device, + .graphics_queue = graphics_queue, + }; +} + +} diff --git a/Userland/Libraries/LibCore/VulkanContext.h b/Userland/Libraries/LibCore/VulkanContext.h new file mode 100644 index 0000000000..04fdfd344c --- /dev/null +++ b/Userland/Libraries/LibCore/VulkanContext.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#if !defined(USE_VULKAN) +static_assert(false, "This file must only be used when Vulkan is available"); +#endif + +#include +#include +#include + +namespace Core { + +struct VulkanContext { + uint32_t api_version { VK_API_VERSION_1_0 }; + VkInstance instance { VK_NULL_HANDLE }; + VkPhysicalDevice physical_device { VK_NULL_HANDLE }; + VkDevice logical_device { VK_NULL_HANDLE }; + VkQueue graphics_queue { VK_NULL_HANDLE }; +}; + +ErrorOr create_vulkan_context(); + +} diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 39acd4bedb..2749007a96 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -1,4 +1,5 @@ include(libweb_generators) +include(vulkan) set(SOURCES Animations/Animatable.cpp diff --git a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp index 2d1f703561..d5dd24cd51 100644 --- a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp @@ -35,6 +35,19 @@ TraversableNavigable::TraversableNavigable(JS::NonnullGCPtr page) m_skia_backend_context = Painting::DisplayListPlayerSkia::create_metal_context(*m_metal_context); } #endif + +#ifdef USE_VULKAN + auto display_list_player_type = page->client().display_list_player_type(); + if (display_list_player_type == DisplayListPlayerType::Skia) { + auto maybe_vulkan_context = Core::create_vulkan_context(); + if (!maybe_vulkan_context.is_error()) { + auto vulkan_context = maybe_vulkan_context.release_value(); + m_skia_backend_context = Painting::DisplayListPlayerSkia::create_vulkan_context(vulkan_context); + } else { + dbgln("Vulkan context creation failed: {}", maybe_vulkan_context.error()); + } + } +#endif } TraversableNavigable::~TraversableNavigable() = default; @@ -1201,6 +1214,15 @@ void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting:: return; } #endif + +#ifdef USE_VULKAN + if (m_skia_backend_context) { + Painting::DisplayListPlayerSkia player(*m_skia_backend_context, target.bitmap()); + display_list.execute(player); + return; + } +#endif + Painting::DisplayListPlayerSkia player(target.bitmap()); display_list.execute(player); } else { diff --git a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h index 6072474b7e..0b427fcc40 100644 --- a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h +++ b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.h @@ -19,6 +19,10 @@ # include #endif +#ifdef USE_VULKAN +# include +#endif + namespace Web::HTML { // https://html.spec.whatwg.org/multipage/document-sequences.html#traversable-navigable @@ -130,8 +134,9 @@ private: String m_window_handle; -#ifdef AK_OS_MACOS OwnPtr m_skia_backend_context; + +#ifdef AK_OS_MACOS OwnPtr m_metal_context; #endif }; diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index 8035032dcb..e80bcef098 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -26,6 +26,13 @@ #include #include +#ifdef USE_VULKAN +# include +# include +# include +# include +#endif + #ifdef AK_OS_MACOS # define FixedPoint FixedPointMacOS # define Duration DurationMacOS @@ -47,10 +54,86 @@ public: { } + void read_into_bitmap(Gfx::Bitmap& bitmap) + { + auto image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), kBGRA_8888_SkColorType, kPremul_SkAlphaType); + SkPixmap pixmap(image_info, bitmap.begin(), bitmap.pitch()); + m_surface->readPixels(pixmap, 0, 0); + } + private: sk_sp m_surface; }; +#ifdef USE_VULKAN +class SkiaVulkanBackendContext final : public SkiaBackendContext { + AK_MAKE_NONCOPYABLE(SkiaVulkanBackendContext); + AK_MAKE_NONMOVABLE(SkiaVulkanBackendContext); + +public: + SkiaVulkanBackendContext(sk_sp context, NonnullOwnPtr extensions) + : m_context(move(context)) + , m_extensions(move(extensions)) + { + } + + ~SkiaVulkanBackendContext() override {}; + + void flush_and_submit() override + { + m_context->flush(); + m_context->submit(GrSyncCpu::kYes); + } + + sk_sp create_surface(int width, int height) + { + auto image_info = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType); + return SkSurfaces::RenderTarget(m_context.get(), skgpu::Budgeted::kYes, image_info); + } + + skgpu::VulkanExtensions const* extensions() const { return m_extensions.ptr(); } + +private: + sk_sp m_context; + NonnullOwnPtr m_extensions; +}; + +OwnPtr DisplayListPlayerSkia::create_vulkan_context(Core::VulkanContext& vulkan_context) +{ + GrVkBackendContext backend_context; + + backend_context.fInstance = vulkan_context.instance; + backend_context.fDevice = vulkan_context.logical_device; + backend_context.fQueue = vulkan_context.graphics_queue; + 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(); + backend_context.fVkExtensions = extensions.ptr(); + + sk_sp ctx = GrDirectContexts::MakeVulkan(backend_context); + VERIFY(ctx); + return make(ctx, move(extensions)); +} + +DisplayListPlayerSkia::DisplayListPlayerSkia(SkiaBackendContext& context, Gfx::Bitmap& bitmap) +{ + VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888); + auto surface = static_cast(context).create_surface(bitmap.width(), bitmap.height()); + m_surface = make(surface); + m_flush_context = [&bitmap, &surface = m_surface, &context] { + context.flush_and_submit(); + surface->read_into_bitmap(bitmap); + }; +} +#endif + #ifdef AK_OS_MACOS class SkiaMetalBackendContext final : public SkiaBackendContext { AK_MAKE_NONCOPYABLE(SkiaMetalBackendContext); diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h index c49d6f9102..965d3c0c8f 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h @@ -14,6 +14,10 @@ # include #endif +#ifdef USE_VULKAN +# include +#endif + namespace Web::Painting { class SkiaBackendContext { @@ -70,6 +74,11 @@ public: DisplayListPlayerSkia(Gfx::Bitmap&); +#ifdef USE_VULKAN + static OwnPtr create_vulkan_context(Core::VulkanContext&); + DisplayListPlayerSkia(SkiaBackendContext&, Gfx::Bitmap&); +#endif + #ifdef AK_OS_MACOS static OwnPtr create_metal_context(Core::MetalContext const&); DisplayListPlayerSkia(SkiaBackendContext&, Core::MetalTexture&); diff --git a/vcpkg.json b/vcpkg.json index 50b7396a6b..1253f5f22b 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -22,7 +22,10 @@ }, { "name": "skia", - "platform": "linux | freebsd | openbsd" + "platform": "linux | freebsd | openbsd", + "features": [ + "vulkan" + ] }, "sqlite3", "woff2"