Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gn/core.gni
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,7 @@ skia_core_sources += [
"$_src/c/sk_types_priv.h",
"$_src/c/sk_vertices.cpp",
"$_src/c/gr_context.cpp",
"$_src/c/gr_vk_device_lost.cpp",
"$_src/c/sk_graphite.cpp",
"$_src/c/sk_graphite_dawn.cpp",
"$_src/c/sk_graphite_metal.cpp",
Expand Down
69 changes: 69 additions & 0 deletions include/c/gr_vk_device_lost.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2026 Microsoft Corporation. All rights reserved.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/

#ifndef gr_vk_device_lost_DEFINED
#define gr_vk_device_lost_DEFINED

#include "include/c/sk_types.h"

SK_C_PLUS_PLUS_BEGIN_GUARD

// Single fault-address record from VK_EXT_device_fault. Fields mirror
// VkDeviceFaultAddressInfoEXT verbatim (see vulkan_core.h). Empty when the
// extension is not enabled or the driver reports no address info.
typedef struct {
int32_t fAddressType; // VkDeviceFaultAddressTypeEXT
uint64_t fReportedAddress; // VkDeviceAddress
uint64_t fAddressPrecision; // VkDeviceSize
} gr_vk_device_fault_address_info_t;

// Single vendor-specific fault record from VK_EXT_device_fault. Mirrors
// VkDeviceFaultVendorInfoEXT (see vulkan_core.h). The description buffer is
// fixed-size (VK_MAX_DESCRIPTION_SIZE = 256) and NUL-terminated by the driver.
typedef struct {
char fDescription[256]; // VK_MAX_DESCRIPTION_SIZE
uint64_t fVendorFaultCode;
uint64_t fVendorFaultData;
} gr_vk_device_fault_vendor_info_t;

// Everything Skia's VulkanDeviceLostProc hands the caller, marshalled into a
// C-ABI-friendly struct. All pointers are valid only for the duration of the
// callback — copy anything you want to keep before returning. Vectors that were
// empty on the C++ side land here as (nullptr, 0).
typedef struct {
const char* fDescription; // NUL-terminated
const gr_vk_device_fault_address_info_t* fAddressInfos;
int32_t fAddressInfoCount;
const gr_vk_device_fault_vendor_info_t* fVendorInfos;
int32_t fVendorInfoCount;
const void* fVendorBinaryData; // raw bytes
size_t fVendorBinaryDataSize;
} gr_vk_device_lost_info_t;

// Fires when Skia detects VK_ERROR_DEVICE_LOST. `info` is valid only during the
// call; copy anything you need to keep. When VK_EXT_device_fault is not enabled
// the address/vendor arrays and binary data are empty; the description is still
// populated with Skia's message.
typedef void (*gr_vk_device_lost_proc)(void* userData, const gr_vk_device_lost_info_t* info);

// Build a bridge object that routes Skia's device-lost callback to the caller's
// proc + userData. Ownership: the returned handle is caller-owned; pass it to
// gr_vk_backendcontext_t.fDeviceLostHandler / sk_graphite_vk_backend_context_init_t
// .fDeviceLostHandler and, after the associated Context has been destroyed, free
// it with gr_vk_device_lost_handler_delete. Skia stores the callback pointer
// non-owning inside the Context, so the bridge must outlive the Context — do NOT
// delete the handle before the Context is deleted.
SK_C_API gr_vk_device_lost_handler_t* gr_vk_device_lost_handler_new(
gr_vk_device_lost_proc proc,
void* userData);

SK_C_API void gr_vk_device_lost_handler_delete(
gr_vk_device_lost_handler_t* handler);

SK_C_PLUS_PLUS_END_GUARD

#endif // gr_vk_device_lost_DEFINED
4 changes: 4 additions & 0 deletions include/c/sk_graphite_vulkan.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ typedef struct {
sk_graphite_vk_get_proc fGetProc;
void* fGetProcUserData;
bool fProtectedContext;
// Optional device-lost handler (see gr_vk_device_lost.h). Nullable. Caller-owned;
// must outlive the Context and be freed via gr_vk_device_lost_handler_delete
// AFTER SKGraphiteContext destruction.
gr_vk_device_lost_handler_t* fDeviceLostHandler;
} sk_graphite_vk_backend_context_init_t;

// Build a Graphite Context for the Vulkan backend.
Expand Down
9 changes: 9 additions & 0 deletions include/c/sk_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,10 @@ typedef struct gr_vk_memory_allocator_t gr_vk_memory_allocator_t;
typedef VKAPI_ATTR void (VKAPI_CALL *gr_vk_func_ptr)(void);
typedef gr_vk_func_ptr (*gr_vk_get_proc)(void* ctx, const char* name, vk_instance_t* instance, vk_device_t* device);

// Vulkan device-lost callback handle. See gr_vk_device_lost.h for the type
// definition + _new / _delete lifecycle.
typedef struct gr_vk_device_lost_handler_t gr_vk_device_lost_handler_t;

typedef struct {
vk_instance_t* fInstance;
vk_physical_device_t* fPhysicalDevice;
Expand All @@ -699,6 +703,11 @@ typedef struct {
gr_vk_get_proc fGetProc;
void* fGetProcUserData;
bool fProtectedContext;
// Optional device-lost handler (see gr_vk_device_lost.h). Nullable. When non-null,
// the caller retains ownership: the Context does not free the handle on destruction,
// mirroring Skia's raw non-owning storage of the callback. Free the handle with
// gr_vk_device_lost_handler_delete AFTER the Context has been deleted.
gr_vk_device_lost_handler_t* fDeviceLostHandler;
} gr_vk_backendcontext_t;

typedef intptr_t gr_vk_backendmemory_t;
Expand Down
92 changes: 92 additions & 0 deletions src/c/gr_vk_device_lost.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2026 Microsoft Corporation. All rights reserved.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/

#include "include/c/gr_vk_device_lost.h"

#include "include/core/SkTypes.h" // pulls SK_VULKAN

#if defined(SK_VULKAN)

#include "include/gpu/vk/VulkanTypes.h"

#include <string>
#include <vector>

// Bridge that adapts Skia's beefy VulkanDeviceLostProc (std::string,
// std::vector<VkDeviceFaultAddressInfoEXT>, std::vector<VkDeviceFaultVendorInfoEXT>,
// std::vector<std::byte>) to a plain C callback that takes only the description.
// The v1 signature intentionally drops the fault-detail vectors — see
// gr_vk_device_lost.h for rationale.
struct gr_vk_device_lost_handler_t {
gr_vk_device_lost_proc fProc;
void* fUserData;
};

// Static thunk used as skgpu::VulkanBackendContext.fDeviceLostProc.
// `userData` there is a gr_vk_device_lost_handler_t* — installed by the shims
// that build a skgpu::VulkanBackendContext (AsGrVkBackendContext for Ganesh,
// sk_graphite_context_make_vulkan for Graphite). Stateless so it decays to a
// plain function pointer (skgpu::VulkanDeviceLostProc is a fn ptr, not a
// std::function).
//
// Marshalling: VkDeviceFaultAddressInfoEXT / VkDeviceFaultVendorInfoEXT are
// standard-layout POD in Vulkan.h, and our gr_vk_device_fault_*_info_t mirror
// their layout field-for-field, so we can memcpy the vector storage into caller-
// visible arrays without a per-element loop. std::byte is layout-compatible with
// unsigned char, so vendorBinaryData maps 1:1. Skia guarantees the vectors and
// description remain valid for the duration of this call, so pointing the info
// struct at their storage is safe — no allocation, no copies.
extern "C" void gr_vk_device_lost_thunk(skgpu::VulkanDeviceLostContext userData,
const std::string& description,
const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
const std::vector<std::byte>& vendorBinaryData)
{
auto* handler = static_cast<gr_vk_device_lost_handler_t*>(userData);
if (!handler || !handler->fProc) return;

static_assert(sizeof(gr_vk_device_fault_address_info_t) == sizeof(VkDeviceFaultAddressInfoEXT),
"gr_vk_device_fault_address_info_t must match VkDeviceFaultAddressInfoEXT layout");
static_assert(sizeof(gr_vk_device_fault_vendor_info_t) == sizeof(VkDeviceFaultVendorInfoEXT),
"gr_vk_device_fault_vendor_info_t must match VkDeviceFaultVendorInfoEXT layout");

gr_vk_device_lost_info_t info;
info.fDescription = description.c_str();
info.fAddressInfos = reinterpret_cast<const gr_vk_device_fault_address_info_t*>(
addressInfos.data());
info.fAddressInfoCount = static_cast<int32_t>(addressInfos.size());
info.fVendorInfos = reinterpret_cast<const gr_vk_device_fault_vendor_info_t*>(
vendorInfos.data());
info.fVendorInfoCount = static_cast<int32_t>(vendorInfos.size());
info.fVendorBinaryData = vendorBinaryData.data();
info.fVendorBinaryDataSize = vendorBinaryData.size();

handler->fProc(handler->fUserData, &info);
}

extern "C" SK_C_API gr_vk_device_lost_handler_t* gr_vk_device_lost_handler_new(
gr_vk_device_lost_proc proc, void* userData)
{
if (!proc) return nullptr;
auto* handler = new gr_vk_device_lost_handler_t;
handler->fProc = proc;
handler->fUserData = userData;
return handler;
}

extern "C" SK_C_API void gr_vk_device_lost_handler_delete(gr_vk_device_lost_handler_t* handler)
{
delete handler;
}

#else // !SK_VULKAN

extern "C" SK_C_API gr_vk_device_lost_handler_t* gr_vk_device_lost_handler_new(
gr_vk_device_lost_proc, void*) { return nullptr; }
extern "C" SK_C_API void gr_vk_device_lost_handler_delete(gr_vk_device_lost_handler_t*) {}

#endif // SK_VULKAN
19 changes: 19 additions & 0 deletions src/c/sk_graphite_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
#include "include/gpu/graphite/vk/VulkanGraphiteTypes.h"
#include "include/gpu/vk/VulkanBackendContext.h"
#include "include/gpu/vk/VulkanMemoryAllocator.h"
#include "include/gpu/vk/VulkanTypes.h" // skgpu::VulkanDeviceLostContext for gr_vk_device_lost_thunk
#include "include/c/gr_vk_device_lost.h" // gr_vk_device_lost_handler_t
#include <string> // std::string in gr_vk_device_lost_thunk signature
#include <vector> // std::vector<> in gr_vk_device_lost_thunk signature

// Defined in gr_vk_device_lost.cpp — adapts Skia's std::string+vectors callback to
// the plain C proc stored in the handle.
extern "C" void gr_vk_device_lost_thunk(skgpu::VulkanDeviceLostContext userData,
const std::string& description,
const std::vector<VkDeviceFaultAddressInfoEXT>&,
const std::vector<VkDeviceFaultVendorInfoEXT>&,
const std::vector<std::byte>&);

// Pulls in Context/BackendTexture/TextureInfo + the matching As/To helpers
// via the SK_GRAPHITE block.
Expand Down Expand Up @@ -55,6 +67,13 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan(
};
}

// Device-lost bridge: caller-owned handle; Skia stores it non-owning, so the
// caller frees via gr_vk_device_lost_handler_delete after the Context dies.
if (init.fDeviceLostHandler) {
vkbc.fDeviceLostContext = init.fDeviceLostHandler;
vkbc.fDeviceLostProc = gr_vk_device_lost_thunk;
}

// Graphite's Vulkan path does NOT auto-create a memory allocator (unlike Ganesh's
// GrVkGpu); the caller must supply one. Until we expose that as part of the C API
// (deferred to a follow-up), build the default VMA-backed allocator here.
Expand Down
18 changes: 18 additions & 0 deletions src/c/sk_types_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
# include "include/gpu/ganesh/vk/GrVkTypes.h"
# include "include/gpu/vk/VulkanBackendContext.h"
# include "include/gpu/vk/VulkanExtensions.h"
# include "include/gpu/vk/VulkanTypes.h" // for skgpu::VulkanDeviceLostContext used by gr_vk_device_lost_thunk
# include <string> // std::string in gr_vk_device_lost_thunk signature
# include <vector> // std::vector<> in gr_vk_device_lost_thunk signature
# define SK_ONLY_VULKAN(...) SK_FIRST_ARG(__VA_ARGS__)
# else
# define SK_ONLY_VULKAN(...) SK_SKIP_ARG(__VA_ARGS__)
Expand Down Expand Up @@ -459,6 +462,14 @@ DEF_MAP(VkPhysicalDeviceFeatures2, vk_physical_device_features_2_t, VkPhysicalDe
DEF_MAP_WITH_NS(skgpu, VulkanMemoryAllocator, gr_vk_memory_allocator_t, GrVkMemoryAllocator);
DEF_MAP_WITH_NS(skgpu, VulkanExtensions, gr_vk_extensions_t, GrVkExtensions)

// Declared in src/c/gr_vk_device_lost.cpp — adapts Skia's beefy device-lost callback
// (std::string + fault-detail vectors) to the plain C proc stored in the handle.
extern "C" void gr_vk_device_lost_thunk(skgpu::VulkanDeviceLostContext userData,
const std::string& description,
const std::vector<VkDeviceFaultAddressInfoEXT>&,
const std::vector<VkDeviceFaultVendorInfoEXT>&,
const std::vector<std::byte>&);

static inline skgpu::VulkanBackendContext AsGrVkBackendContext(const gr_vk_backendcontext_t* context) {
skgpu::VulkanBackendContext ctx;
ctx.fInstance = AsVkInstance(context->fInstance);
Expand All @@ -477,6 +488,13 @@ static inline skgpu::VulkanBackendContext AsGrVkBackendContext(const gr_vk_backe
};
}
ctx.fProtectedContext = context->fProtectedContext ? skgpu::Protected::kYes : skgpu::Protected::kNo;
// Device-lost bridge: caller-owned handle. Skia stores fDeviceLostContext raw
// non-owning, so the handle must outlive the Context and be freed by the caller
// via gr_vk_device_lost_handler_delete afterwards.
if (context->fDeviceLostHandler != nullptr) {
ctx.fDeviceLostContext = context->fDeviceLostHandler;
ctx.fDeviceLostProc = gr_vk_device_lost_thunk;
}
return ctx;
}

Expand Down