From e713de115c394480b7d57653fe7b472db0dc7ce3 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Wed, 3 Jul 2024 19:23:27 +0200 Subject: [PATCH] LibWeb+LibCore: Use Vulkan backend for Skia on Linux Skia now uses GPU-accelerated painting on Linux if Vulkan is available. Most of the performance gain is currently negated by reading the GPU backend back into RAM to pass it to the Browser process. In the future, this could be improved by sharing GPU-allocated memory across the Browser and WebContent processes. --- Meta/CMake/vulkan.cmake | 7 + Userland/Libraries/LibCore/CMakeLists.txt | 11 ++ Userland/Libraries/LibCore/VulkanContext.cpp | 125 ++++++++++++++++++ Userland/Libraries/LibCore/VulkanContext.h | 29 ++++ Userland/Libraries/LibWeb/CMakeLists.txt | 1 + .../LibWeb/HTML/TraversableNavigable.cpp | 22 +++ .../LibWeb/HTML/TraversableNavigable.h | 7 +- .../LibWeb/Painting/DisplayListPlayerSkia.cpp | 83 ++++++++++++ .../LibWeb/Painting/DisplayListPlayerSkia.h | 9 ++ vcpkg.json | 5 +- 10 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 Meta/CMake/vulkan.cmake create mode 100644 Userland/Libraries/LibCore/VulkanContext.cpp create mode 100644 Userland/Libraries/LibCore/VulkanContext.h 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"