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.
This commit is contained in:
Aliaksandr Kalenik 2024-07-03 19:23:27 +02:00 committed by Andreas Kling
parent 70db2bff92
commit e713de115c
Notes: sideshowbarker 2024-07-17 04:09:56 +09:00
10 changed files with 297 additions and 2 deletions

7
Meta/CMake/vulkan.cmake Normal file
View file

@ -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()

View file

@ -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()

View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Vector.h>
#include <LibCore/VulkanContext.h>
namespace Core {
ErrorOr<VkInstance> 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<VkPhysicalDevice> 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<VkPhysicalDevice> 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<VkDevice> create_logical_device(VkPhysicalDevice physical_device)
{
VkDevice device;
uint32_t queue_family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr);
Vector<VkQueueFamilyProperties> 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<int>(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<VulkanContext> 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,
};
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* 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 <AK/Forward.h>
#include <AK/Function.h>
#include <vulkan/vulkan.h>
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<VulkanContext> create_vulkan_context();
}

View file

@ -1,4 +1,5 @@
include(libweb_generators)
include(vulkan)
set(SOURCES
Animations/Animatable.cpp

View file

@ -35,6 +35,19 @@ TraversableNavigable::TraversableNavigable(JS::NonnullGCPtr<Page> 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 {

View file

@ -19,6 +19,10 @@
# include <LibCore/MetalContext.h>
#endif
#ifdef USE_VULKAN
# include <LibCore/VulkanContext.h>
#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<Web::Painting::SkiaBackendContext> m_skia_backend_context;
#ifdef AK_OS_MACOS
OwnPtr<Core::MetalContext> m_metal_context;
#endif
};

View file

@ -26,6 +26,13 @@
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
#include <LibWeb/Painting/ShadowPainting.h>
#ifdef USE_VULKAN
# include <gpu/ganesh/vk/GrVkDirectContext.h>
# include <gpu/vk/GrVkBackendContext.h>
# include <gpu/vk/VulkanBackendContext.h>
# include <gpu/vk/VulkanExtensions.h>
#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<SkSurface> m_surface;
};
#ifdef USE_VULKAN
class SkiaVulkanBackendContext final : public SkiaBackendContext {
AK_MAKE_NONCOPYABLE(SkiaVulkanBackendContext);
AK_MAKE_NONMOVABLE(SkiaVulkanBackendContext);
public:
SkiaVulkanBackendContext(sk_sp<GrDirectContext> context, NonnullOwnPtr<skgpu::VulkanExtensions> 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<SkSurface> 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<GrDirectContext> m_context;
NonnullOwnPtr<skgpu::VulkanExtensions> m_extensions;
};
OwnPtr<SkiaBackendContext> 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<skgpu::VulkanExtensions>();
backend_context.fVkExtensions = extensions.ptr();
sk_sp<GrDirectContext> ctx = GrDirectContexts::MakeVulkan(backend_context);
VERIFY(ctx);
return make<SkiaVulkanBackendContext>(ctx, move(extensions));
}
DisplayListPlayerSkia::DisplayListPlayerSkia(SkiaBackendContext& context, Gfx::Bitmap& bitmap)
{
VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888);
auto surface = static_cast<SkiaVulkanBackendContext&>(context).create_surface(bitmap.width(), bitmap.height());
m_surface = make<SkiaSurface>(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);

View file

@ -14,6 +14,10 @@
# include <LibCore/MetalContext.h>
#endif
#ifdef USE_VULKAN
# include <LibCore/VulkanContext.h>
#endif
namespace Web::Painting {
class SkiaBackendContext {
@ -70,6 +74,11 @@ public:
DisplayListPlayerSkia(Gfx::Bitmap&);
#ifdef USE_VULKAN
static OwnPtr<SkiaBackendContext> create_vulkan_context(Core::VulkanContext&);
DisplayListPlayerSkia(SkiaBackendContext&, Gfx::Bitmap&);
#endif
#ifdef AK_OS_MACOS
static OwnPtr<SkiaBackendContext> create_metal_context(Core::MetalContext const&);
DisplayListPlayerSkia(SkiaBackendContext&, Core::MetalTexture&);

View file

@ -22,7 +22,10 @@
},
{
"name": "skia",
"platform": "linux | freebsd | openbsd"
"platform": "linux | freebsd | openbsd",
"features": [
"vulkan"
]
},
"sqlite3",
"woff2"