ladybird/Libraries/LibGfx/SkiaBackendContext.cpp
Andreas Kling 164ed80244 Meta: Enable exit-time destructor warnings for libraries
Enable -Wexit-time-destructors for all in-tree library targets and
update process-lifetime library statics so they no longer register
exit-time destructors. Long-lived caches, lookup tables, singleton
registries, and generated constants now use NeverDestroyed or leaked
references where the data is intended to live until process exit.

Update LibWeb, LibLine, and the binding generators so regenerated
sources follow the same rule instead of reintroducing destructed
statics.
2026-06-04 19:20:49 +02:00

279 lines
9.3 KiB
C++

/*
* Copyright (c) 2024-2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NeverDestroyed.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/RefPtr.h>
#include <AK/Time.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/SkiaBackendContext.h>
#include <core/SkSurface.h>
#include <gpu/ganesh/GrDirectContext.h>
#ifdef USE_VULKAN
# include <gpu/ganesh/vk/GrVkDirectContext.h>
# include <gpu/vk/VulkanBackendContext.h>
# include <gpu/vk/VulkanExtensions.h>
#endif
#ifdef AK_OS_MACOS
# include <gpu/ganesh/GrBackendSurface.h>
# include <gpu/ganesh/mtl/GrMtlBackendContext.h>
# include <gpu/ganesh/mtl/GrMtlBackendSurface.h>
# include <gpu/ganesh/mtl/GrMtlDirectContext.h>
#endif
namespace Gfx {
#if defined(AK_OS_MACOS) || 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);
static constexpr auto skia_aggressive_cleanup_interval = AK::Duration::from_seconds(5);
static constexpr auto skia_deferred_cleanup_resource_age = std::chrono::seconds(5);
static constexpr auto skia_resource_cache_high_watermark = 384 * MiB;
static constexpr auto skia_resource_cache_critical_watermark = 512 * MiB;
static auto& main_thread_context()
{
static NeverDestroyed<RefPtr<SkiaBackendContext>> context;
return *context;
}
#if defined(AK_OS_MACOS) || USE_VULKAN
static void invoke_async_flush_callback(void* context)
{
auto* callback = static_cast<Function<void()>*>(context);
auto callback_to_invoke = move(*callback);
delete callback;
callback_to_invoke();
}
static void flush_and_submit_async_to_context(GrDirectContext& context, SkSurface* surface, Function<void()>&& callback)
{
GrFlushInfo flush_info {};
flush_info.fFinishedProc = invoke_async_flush_callback;
flush_info.fFinishedContext = new Function<void()>(move(callback));
context.flush(surface, SkSurfaces::BackendSurfaceAccess::kPresent, flush_info);
VERIFY(context.submit(GrSyncCpu::kNo));
}
#endif
void SkiaBackendContext::check_async_work_completion()
{
if (auto* context = sk_context())
context->checkAsyncWorkCompletion();
}
void SkiaBackendContext::flush_and_submit(SkSurface* surface)
{
flush_and_submit_impl(surface);
perform_post_flush_cleanup();
}
void SkiaBackendContext::flush_and_submit_async(SkSurface* surface, Function<void()>&& callback)
{
flush_and_submit_async_impl(surface, move(callback));
perform_post_flush_cleanup();
}
void SkiaBackendContext::perform_post_flush_cleanup()
{
auto* context = sk_context();
if (!context)
return;
static thread_local Optional<MonotonicTime> s_last_deferred_cleanup;
static thread_local Optional<MonotonicTime> s_last_aggressive_cleanup;
auto const now = MonotonicTime::now();
if (s_last_deferred_cleanup.has_value() && now - *s_last_deferred_cleanup < skia_deferred_cleanup_interval)
return;
s_last_deferred_cleanup = now;
context->performDeferredCleanup(skia_deferred_cleanup_resource_age);
size_t resource_bytes = 0;
context->getResourceCacheUsage(nullptr, &resource_bytes);
if (resource_bytes < skia_resource_cache_high_watermark)
return;
if (s_last_aggressive_cleanup.has_value() && now - *s_last_aggressive_cleanup < skia_aggressive_cleanup_interval)
return;
s_last_aggressive_cleanup = now;
context->performDeferredCleanup(std::chrono::milliseconds(0));
context->getResourceCacheUsage(nullptr, &resource_bytes);
if (resource_bytes >= skia_resource_cache_critical_watermark)
context->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
}
void SkiaBackendContext::initialize_gpu_backend()
{
VERIFY(!main_thread_context());
main_thread_context() = create_independent_gpu_backend();
}
RefPtr<SkiaBackendContext> SkiaBackendContext::create_independent_gpu_backend()
{
#ifdef AK_OS_MACOS
auto metal_context = get_metal_context();
if (!metal_context)
return {};
return create_metal_context(*metal_context);
#elif 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());
return {};
}
auto vulkan_context = maybe_vulkan_context.release_value();
return create_vulkan_context(vulkan_context);
#else
return {};
#endif
}
RefPtr<SkiaBackendContext> SkiaBackendContext::the_main_thread_context()
{
return main_thread_context();
}
#ifdef USE_VULKAN
class SkiaVulkanBackendContext final : public SkiaBackendContext {
AK_MAKE_NONCOPYABLE(SkiaVulkanBackendContext);
AK_MAKE_NONMOVABLE(SkiaVulkanBackendContext);
public:
SkiaVulkanBackendContext(sk_sp<GrDirectContext> context, VulkanContext const& vulkan_context, NonnullOwnPtr<skgpu::VulkanExtensions> extensions)
: m_context(move(context))
, m_extensions(move(extensions))
, m_vulkan_context(vulkan_context)
{
}
~SkiaVulkanBackendContext() override
{
m_context.reset();
# ifdef USE_VULKAN_DMABUF_IMAGES
if (m_vulkan_context.command_pool != VK_NULL_HANDLE)
vkDestroyCommandPool(m_vulkan_context.logical_device, m_vulkan_context.command_pool, nullptr);
# endif
if (m_vulkan_context.logical_device != VK_NULL_HANDLE)
vkDestroyDevice(m_vulkan_context.logical_device, nullptr);
if (m_vulkan_context.instance != VK_NULL_HANDLE)
vkDestroyInstance(m_vulkan_context.instance, nullptr);
}
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));
}
skgpu::VulkanExtensions const* extensions() const { return m_extensions.ptr(); }
GrDirectContext* sk_context() const override { return m_context.get(); }
VulkanContext const& vulkan_context() override { return m_vulkan_context; }
MetalContext& metal_context() override { VERIFY_NOT_REACHED(); }
private:
sk_sp<GrDirectContext> m_context;
NonnullOwnPtr<skgpu::VulkanExtensions> m_extensions;
VulkanContext const m_vulkan_context;
};
RefPtr<SkiaBackendContext> SkiaBackendContext::create_vulkan_context(VulkanContext const& vulkan_context)
{
skgpu::VulkanBackendContext backend_context;
backend_context.fInstance = vulkan_context.instance;
backend_context.fDevice = vulkan_context.logical_device;
backend_context.fQueue = vulkan_context.graphics_queue;
backend_context.fGraphicsQueueIndex = vulkan_context.graphics_queue_family;
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);
ctx->setResourceCacheLimit(skia_resource_cache_limit);
return adopt_ref(*new SkiaVulkanBackendContext(ctx, vulkan_context, move(extensions)));
}
#endif
#ifdef AK_OS_MACOS
class SkiaMetalBackendContext final : public SkiaBackendContext {
AK_MAKE_NONCOPYABLE(SkiaMetalBackendContext);
AK_MAKE_NONMOVABLE(SkiaMetalBackendContext);
public:
SkiaMetalBackendContext(sk_sp<GrDirectContext> context, NonnullRefPtr<MetalContext> metal_context)
: m_context(move(context))
, m_metal_context(move(metal_context))
{
}
~SkiaMetalBackendContext() 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 { return m_metal_context; }
private:
sk_sp<GrDirectContext> m_context;
NonnullRefPtr<MetalContext> m_metal_context;
};
RefPtr<SkiaBackendContext> SkiaBackendContext::create_metal_context(NonnullRefPtr<MetalContext> metal_context)
{
GrMtlBackendContext backend_context;
backend_context.fDevice.retain(metal_context->device());
backend_context.fQueue.retain(metal_context->queue());
sk_sp<GrDirectContext> ctx = GrDirectContexts::MakeMetal(backend_context);
VERIFY(ctx);
ctx->setResourceCacheLimit(skia_resource_cache_limit);
return adopt_ref(*new SkiaMetalBackendContext(move(ctx), move(metal_context)));
}
#endif
}