/* * Copyright (c) 2024-2026, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #ifdef USE_DIRECTX # include #endif #include #include #include #ifdef USE_DIRECTX # include # include #endif #ifdef USE_VULKAN # include # include # include #endif #ifdef AK_OS_MACOS # include # include # include # include #endif namespace Gfx { #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); 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> context; return *context; } #if defined(AK_OS_MACOS) || defined(USE_DIRECTX) || defined(USE_VULKAN) static void invoke_async_flush_callback(void* context) { auto* callback = static_cast*>(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&& callback) { GrFlushInfo flush_info {}; flush_info.fFinishedProc = invoke_async_flush_callback; flush_info.fFinishedContext = new Function(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&& 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 s_last_deferred_cleanup; static thread_local Optional 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::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 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()); return {}; } auto vulkan_context = maybe_vulkan_context.release_value(); return create_vulkan_context(vulkan_context); #else return {}; #endif } 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); AK_MAKE_NONMOVABLE(SkiaVulkanBackendContext); public: SkiaVulkanBackendContext(sk_sp context, VulkanContext const& vulkan_context, NonnullOwnPtr 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&& 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 m_context; NonnullOwnPtr m_extensions; VulkanContext const m_vulkan_context; }; RefPtr 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(); backend_context.fVkExtensions = extensions.ptr(); sk_sp 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 context, NonnullRefPtr 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&& 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 m_context; NonnullRefPtr m_metal_context; }; RefPtr SkiaBackendContext::create_metal_context(NonnullRefPtr metal_context) { GrMtlBackendContext backend_context; backend_context.fDevice.retain(metal_context->device()); backend_context.fQueue.retain(metal_context->queue()); sk_sp ctx = GrDirectContexts::MakeMetal(backend_context); VERIFY(ctx); ctx->setResourceCacheLimit(skia_resource_cache_limit); return adopt_ref(*new SkiaMetalBackendContext(move(ctx), move(metal_context))); } #endif }