From 47505e39323148315b5b939ed075edae6845c8f3 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Sun, 10 May 2026 20:29:54 +0000 Subject: [PATCH 01/14] mono/skia: add C-API shim for skgpu::graphite::* Adds an sk_graphite_* C ABI living inside :core so SkiaSharp's P/Invoke layer can drive Graphite without binding directly to C++. Mirrors the existing sk_gr_context_* / gr_* shim shape: include/c/sk_graphite.h -- core: context, recorder, recording, surface, image, backend texture, texture info, async readback, ImageProvider hook. include/c/sk_graphite_vulkan.h -- Vulkan-only entry points include/c/sk_graphite_metal.h -- Metal-only entry points include/c/sk_graphite_dawn.h -- Dawn-only entry points src/c/sk_graphite{,_vulkan,_metal,_dawn}.cpp The implementations are wrapped in `#if defined(SK_GRAPHITE)` etc., with no-op stubs in the else branch so the C ABI surface is consistent whether or not the backend is built. BUILD.gn / gn/core.gni: the :graphite target's all_dependent_configs apply SK_GRAPHITE/SK_DAWN to dependents but not to :core itself. The new shim cpp files live in :core, so add those defines explicitly when the respective skia_enable_* args are set; otherwise the bodies collapse to the stubs and never link the real symbols. Co-Authored-By: Claude Opus 4.7 (1M context) --- BUILD.gn | 11 + gn/core.gni | 4 + include/c/sk_graphite.h | 293 +++++++++++++++++ include/c/sk_graphite_dawn.h | 43 +++ include/c/sk_graphite_metal.h | 49 +++ include/c/sk_graphite_vulkan.h | 84 +++++ src/c/sk_graphite.cpp | 565 +++++++++++++++++++++++++++++++++ src/c/sk_graphite_dawn.cpp | 70 ++++ src/c/sk_graphite_metal.cpp | 75 +++++ src/c/sk_graphite_vulkan.cpp | 145 +++++++++ 10 files changed, 1339 insertions(+) create mode 100644 include/c/sk_graphite.h create mode 100644 include/c/sk_graphite_dawn.h create mode 100644 include/c/sk_graphite_metal.h create mode 100644 include/c/sk_graphite_vulkan.h create mode 100644 src/c/sk_graphite.cpp create mode 100644 src/c/sk_graphite_dawn.cpp create mode 100644 src/c/sk_graphite_metal.cpp create mode 100644 src/c/sk_graphite_vulkan.cpp diff --git a/BUILD.gn b/BUILD.gn index 4775bb9572c1..fabb4bc9e483 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1824,6 +1824,17 @@ skia_source_set("core") { if (skia_use_metal) { defines += [ "SK_METAL" ] } + # mono/skia: same reasoning as the GR_* defines above — :graphite's + # all_dependent_configs apply SK_GRAPHITE/SK_DAWN to dependents but not to + # :core itself. The new src/c/sk_graphite*.cpp shims live in :core, so they + # need these defines to compile their bodies (otherwise they collapse to + # the !SK_GRAPHITE / !SK_DAWN stubs and never link the real symbols). + if (skia_enable_graphite) { + defines += [ "SK_GRAPHITE" ] + } + if (skia_use_dawn) { + defines += [ "SK_DAWN" ] + } # These deps don't belong to :core, but they are currently needed to support (de)serialization. public_deps = [ diff --git a/gn/core.gni b/gn/core.gni index c034f58a9b1c..f8d0685cba8c 100644 --- a/gn/core.gni +++ b/gn/core.gni @@ -878,6 +878,10 @@ skia_core_sources += [ "$_src/c/sk_types_priv.h", "$_src/c/sk_vertices.cpp", "$_src/c/gr_context.cpp", + "$_src/c/sk_graphite.cpp", + "$_src/c/sk_graphite_dawn.cpp", + "$_src/c/sk_graphite_metal.cpp", + "$_src/c/sk_graphite_vulkan.cpp", "$_src/c/sk_linker.cpp", "$_src/c/skottie_animation.cpp", "$_src/c/skresources_resource_provider.cpp", diff --git a/include/c/sk_graphite.h b/include/c/sk_graphite.h new file mode 100644 index 000000000000..19d8a07e2ba2 --- /dev/null +++ b/include/c/sk_graphite.h @@ -0,0 +1,293 @@ +/* + * 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 sk_graphite_DEFINED +#define sk_graphite_DEFINED + +#include "include/c/sk_types.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +// Opaque handles + +typedef struct sk_graphite_context_t sk_graphite_context_t; +typedef struct sk_graphite_recorder_t sk_graphite_recorder_t; +typedef struct sk_graphite_recording_t sk_graphite_recording_t; +typedef struct sk_graphite_backend_texture_t sk_graphite_backend_texture_t; +typedef struct sk_graphite_texture_info_t sk_graphite_texture_info_t; +typedef struct sk_graphite_image_provider_t sk_graphite_image_provider_t; + +// Backend identification + +typedef enum { + DAWN_SK_GRAPHITE_BACKEND = 0, + METAL_SK_GRAPHITE_BACKEND = 1, + VULKAN_SK_GRAPHITE_BACKEND = 2, +} sk_graphite_backend_t; + +// Returns true if the requested backend was compiled into this build of +// libSkiaSharp. Safe to call before any context is created and on any backend. +SK_C_API bool sk_graphite_backend_is_available(sk_graphite_backend_t backend); + +// ContextOptions (POD, value type) + +typedef struct { + bool fDisableDriverCorrectnessWorkarounds; + int32_t fInternalMultisampleCount; // valid: 1, 2, 4, 8, 16 + int64_t fGpuBudgetInBytes; // -1 to use Skia's default + bool fRequireOrderedRecordings; + bool fSetBackendLabels; +} sk_graphite_context_options_t; + +SK_C_API void sk_graphite_context_options_init_defaults(sk_graphite_context_options_t* out); + +// Submission control + +typedef enum { + NO_SK_GRAPHITE_SYNC_TO_CPU = 0, + YES_SK_GRAPHITE_SYNC_TO_CPU = 1, +} sk_graphite_sync_to_cpu_t; + +typedef enum { + NO_SK_GRAPHITE_MARK_FRAME_BOUNDARY = 0, + YES_SK_GRAPHITE_MARK_FRAME_BOUNDARY = 1, +} sk_graphite_mark_frame_boundary_t; + +typedef struct { + sk_graphite_sync_to_cpu_t fSync; + sk_graphite_mark_frame_boundary_t fMarkBoundary; + uint64_t fFrameID; +} sk_graphite_submit_info_t; + +// Recording insert / status + +typedef enum { + SUCCESS_SK_GRAPHITE_INSERT_STATUS = 0, + INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS = 1, + PROMISE_INSTANTIATION_FAILED_SK_GRAPHITE_INSERT_STATUS = 2, + ADD_COMMANDS_FAILED_SK_GRAPHITE_INSERT_STATUS = 3, + ASYNC_SHADER_COMPILES_FAILED_SK_GRAPHITE_INSERT_STATUS = 4, + OUT_OF_ORDER_RECORDING_SK_GRAPHITE_INSERT_STATUS = 5, +} sk_graphite_insert_status_t; + +typedef struct { + sk_graphite_recording_t* fRecording; // non-null + sk_surface_t* fTargetSurface; // nullable; for deferred canvas targets + int32_t fTargetTranslationX; + int32_t fTargetTranslationY; + sk_irect_t fTargetClip; +} sk_graphite_insert_recording_info_t; + +// Release callback for caller-owned backend textures + +typedef void (*sk_graphite_release_proc_t)(void* releaseContext); + +// Context + +SK_C_API void sk_graphite_context_delete(sk_graphite_context_t* context); +SK_C_API sk_graphite_backend_t sk_graphite_context_get_backend(const sk_graphite_context_t* context); +SK_C_API bool sk_graphite_context_is_device_lost(const sk_graphite_context_t* context); +SK_C_API int32_t sk_graphite_context_get_max_texture_size(const sk_graphite_context_t* context); +SK_C_API bool sk_graphite_context_supports_protected_content(const sk_graphite_context_t* context); +SK_C_API int64_t sk_graphite_context_get_current_budgeted_bytes(const sk_graphite_context_t* context); +SK_C_API int64_t sk_graphite_context_get_max_budgeted_bytes(const sk_graphite_context_t* context); +SK_C_API void sk_graphite_context_set_max_budgeted_bytes(sk_graphite_context_t* context, int64_t bytes); +SK_C_API void sk_graphite_context_free_gpu_resources(sk_graphite_context_t* context); +SK_C_API void sk_graphite_context_perform_deferred_cleanup(sk_graphite_context_t* context, int64_t milliseconds); + +// Recorder vending. recorderBudgetBytes < 0 uses the context default. +SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder(sk_graphite_context_t* context, int64_t recorderBudgetBytes); + +// Variant that attaches an ImageProvider to the new recorder. Ownership of +// the provider transfers to the recorder on success — caller must NOT call +// sk_graphite_image_provider_delete on it afterwards. Pass null to behave +// identically to sk_graphite_context_make_recorder. The provider can also +// be sourced from sk_graphite_image_provider_new. +SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder_with_image_provider( + sk_graphite_context_t* context, + int64_t recorderBudgetBytes, + sk_graphite_image_provider_t* imageProvider); + +// Insert / submit. insert returns a status — non-success is not an error path. +SK_C_API sk_graphite_insert_status_t sk_graphite_context_insert_recording(sk_graphite_context_t* context, const sk_graphite_insert_recording_info_t* info); +SK_C_API bool sk_graphite_context_submit(sk_graphite_context_t* context, const sk_graphite_submit_info_t* info /* nullable -> defaults */); + +// Recorder + +SK_C_API void sk_graphite_recorder_delete(sk_graphite_recorder_t* recorder); +SK_C_API sk_graphite_backend_t sk_graphite_recorder_get_backend(const sk_graphite_recorder_t* recorder); +SK_C_API int32_t sk_graphite_recorder_get_max_texture_size(const sk_graphite_recorder_t* recorder); +// Snap: returns null if no recording has been recorded since the last snap(). +SK_C_API sk_graphite_recording_t* sk_graphite_recorder_snap(sk_graphite_recorder_t* recorder); + +// Recording + +SK_C_API void sk_graphite_recording_delete(sk_graphite_recording_t* recording); + +// Surface factories (Graphite-backed) + +SK_C_API sk_surface_t* sk_graphite_surface_make_render_target( + sk_graphite_recorder_t* recorder, + const sk_imageinfo_t* info, + int32_t mipmapped, // 0 = no, 1 = yes + const sk_surfaceprops_t* props /* nullable */); + +// Wrap a caller-allocated GPU texture (described by an opaque +// sk_graphite_backend_texture_t) as an SkSurface so that drawing into it +// writes pixels into the original GPU resource. Caller retains ownership of +// the underlying GPU object — the wrapper does NOT free it on disposal. +// +// The release callback (if provided) fires when the wrapping surface is +// destroyed, giving the caller a hook to release the wrapped resource. +SK_C_API sk_surface_t* sk_graphite_surface_wrap_backend_texture( + sk_graphite_recorder_t* recorder, + const sk_graphite_backend_texture_t* backendTexture, + sk_colortype_t colorType, + sk_colorspace_t* colorSpace /* nullable */, + const sk_surfaceprops_t* props /* nullable */, + sk_graphite_release_proc_t releaseProc /* nullable */, + void* releaseContext); + +// Wrap a Graphite-allocated or caller-allocated GPU texture as an SkImage so +// it can be sampled by other draw operations (paint sources, image filters, +// shaders). Mirrors SkImages::WrapTexture in upstream Skia. +SK_C_API sk_image_t* sk_graphite_image_wrap_texture( + sk_graphite_recorder_t* recorder, + const sk_graphite_backend_texture_t* backendTexture, + sk_colortype_t colorType, + sk_alphatype_t alphaType, + sk_colorspace_t* colorSpace /* nullable */, + sk_graphite_release_proc_t releaseProc /* nullable */, + void* releaseContext); + +// Recorder-allocated BackendTexture: lets Skia allocate a fresh GPU texture +// matching the supplied TextureInfo. Caller owns the wrapper; pair with +// sk_graphite_recorder_delete_backend_texture or sk_graphite_context_delete_backend_texture +// (the recorder/context releases the underlying GPU resource). +SK_C_API sk_graphite_backend_texture_t* sk_graphite_recorder_create_backend_texture( + sk_graphite_recorder_t* recorder, + int32_t width, + int32_t height, + const sk_graphite_texture_info_t* info); + +SK_C_API void sk_graphite_recorder_delete_backend_texture( + sk_graphite_recorder_t* recorder, + const sk_graphite_backend_texture_t* tex); + +SK_C_API void sk_graphite_context_delete_backend_texture( + sk_graphite_context_t* context, + const sk_graphite_backend_texture_t* tex); + +// BackendTexture handle — opaque heap wrapper around skgpu::graphite::BackendTexture +// (which is a value type in C++; we heap-alloc to keep ABI stable across Skia revs). + +SK_C_API void sk_graphite_backend_texture_delete (sk_graphite_backend_texture_t* tex); +SK_C_API bool sk_graphite_backend_texture_is_valid (const sk_graphite_backend_texture_t* tex); +SK_C_API sk_graphite_backend_t sk_graphite_backend_texture_get_backend (const sk_graphite_backend_texture_t* tex); +SK_C_API void sk_graphite_backend_texture_get_dimensions(const sk_graphite_backend_texture_t* tex, int32_t* outWidth, int32_t* outHeight); + +// TextureInfo handle — describes a backend's texture format/sample/mipmap state +// without referring to a concrete GPU resource. + +SK_C_API void sk_graphite_texture_info_delete (sk_graphite_texture_info_t* info); +SK_C_API bool sk_graphite_texture_info_is_valid (const sk_graphite_texture_info_t* info); +SK_C_API sk_graphite_backend_t sk_graphite_texture_info_get_backend (const sk_graphite_texture_info_t* info); +SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_graphite_texture_info_t* info); +SK_C_API int32_t sk_graphite_texture_info_get_mipmapped (const sk_graphite_texture_info_t* info); // 0 = no, 1 = yes + +// Asynchronous CPU readback — direct pass-through of upstream Skia's +// Context::asyncRescaleAndReadPixels. The legacy sk_surface_read_pixels does +// NOT work on Graphite-backed surfaces in production builds (Skia gates the +// implementation on GPU_TEST_UTILS — see src/gpu/graphite/Device.cpp). +// +// The callback is invoked exactly once. The result pointer is non-owning and +// is only valid for the duration of the callback invocation. A null result +// means failure (rect out of bounds, lost device, etc.). +// +// Drive completion by calling sk_graphite_context_check_async_work_completion +// from the same thread that owns the context. The callback fires on the +// thread that calls checkAsyncWorkCompletion. + +typedef struct sk_graphite_async_read_result_t sk_graphite_async_read_result_t; + +typedef enum { + SRC_SK_GRAPHITE_RESCALE_GAMMA = 0, + LINEAR_SK_GRAPHITE_RESCALE_GAMMA = 1, +} sk_graphite_rescale_gamma_t; + +typedef enum { + NEAREST_SK_GRAPHITE_RESCALE_MODE = 0, + REPEATED_LINEAR_SK_GRAPHITE_RESCALE_MODE = 1, + REPEATED_CUBIC_SK_GRAPHITE_RESCALE_MODE = 2, +} sk_graphite_rescale_mode_t; + +typedef void (*sk_graphite_async_read_pixels_proc_t)( + void* callbackContext, + const sk_graphite_async_read_result_t* result /* non-owning, valid only during callback */); + +SK_C_API void sk_graphite_context_async_rescale_and_read_pixels_surface( + sk_graphite_context_t* context, + const sk_surface_t* surface, + const sk_imageinfo_t* dstInfo, + const sk_irect_t* srcRect, + sk_graphite_rescale_gamma_t rescaleGamma, + sk_graphite_rescale_mode_t rescaleMode, + sk_graphite_async_read_pixels_proc_t callback, + void* callbackContext); + +SK_C_API void sk_graphite_context_check_async_work_completion(sk_graphite_context_t* context); + +// AsyncReadResult accessors — call only inside the async-read callback. +SK_C_API int32_t sk_graphite_async_read_result_get_count (const sk_graphite_async_read_result_t* result); +SK_C_API const void* sk_graphite_async_read_result_get_data (const sk_graphite_async_read_result_t* result, int32_t planeIndex); +SK_C_API size_t sk_graphite_async_read_result_get_row_bytes(const sk_graphite_async_read_result_t* result, int32_t planeIndex); + +// ImageProvider — bridge so a managed (or any external) caller can satisfy +// Graphite's "convert non-Graphite SkImage to Graphite-backed" hook without +// owning a C++ subclass of skgpu::graphite::ImageProvider. +// +// The callback fires on the recorder's owning thread. Return a Graphite-backed +// SkImage (e.g. obtained via sk_graphite_image_make_texture) that preserves +// the original image's dimensions and alpha type. Returning null causes the +// triggering draw to be dropped — same as if no provider were set. +// +// Ownership: the returned sk_image_t* is consumed by Skia; the bridge takes +// the +1 reference and decrements after Skia is done. Do NOT call sk_image_unref +// on it from your callback — return it directly. + +typedef sk_image_t* (*sk_graphite_image_provider_proc_t)( + void* userData, + sk_graphite_recorder_t* recorder, + const sk_image_t* image, + int32_t mipmapped /* 0 = no, 1 = yes */); + +// Build an ImageProvider that dispatches to the given callback. Caller retains +// ownership of the returned wrapper until it is passed to a Context via +// sk_graphite_context_options_t::fImageProvider; ownership transfers on +// successful Context creation. +SK_C_API sk_graphite_image_provider_t* sk_graphite_image_provider_new( + sk_graphite_image_provider_proc_t proc, + void* userData); + +// Free an unused provider (e.g. when CreateVulkan returned null and the caller +// needs to clean up). Safe on null. Calling this AFTER the provider has been +// installed in a Context is undefined — the Context owns it from that point. +SK_C_API void sk_graphite_image_provider_delete(sk_graphite_image_provider_t* provider); + +// Upload a raster (CPU-backed) SkImage to a Graphite-backed texture. This is +// the same operation Skia's SkImages::TextureFromImage performs; exposed so a +// C# implementation of sk_graphite_image_provider_proc_t can do the actual +// conversion the hook is asked for. Returns null if the recorder is null or +// the upload failed. +SK_C_API sk_image_t* sk_graphite_image_make_texture( + sk_graphite_recorder_t* recorder, + const sk_image_t* image, + int32_t mipmapped /* 0 = no, 1 = yes */); + +SK_C_PLUS_PLUS_END_GUARD + +#endif // sk_graphite_DEFINED diff --git a/include/c/sk_graphite_dawn.h b/include/c/sk_graphite_dawn.h new file mode 100644 index 000000000000..4cab2339be20 --- /dev/null +++ b/include/c/sk_graphite_dawn.h @@ -0,0 +1,43 @@ +/* + * 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 sk_graphite_dawn_DEFINED +#define sk_graphite_dawn_DEFINED + +#include "include/c/sk_types.h" +#include "include/c/sk_graphite.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +// Opaque heap wrapper around skgpu::graphite::DawnBackendContext. +typedef struct sk_graphite_dawn_backend_context_t sk_graphite_dawn_backend_context_t; + +// Init struct: caller-owned WGPUInstance/Device/Queue raw handles. The shim +// AddRef's each at construction; the wrapper owns those references and +// releases them on delete. +// +// fNonYielding: when non-zero, no DawnTickFunction is installed (Skia's +// "non-yielding context" mode). Required when running over Emscripten without +// -s ASYNCIFY. Native Dawn callers should leave it 0 — the shim installs +// DawnNativeProcessEventsFunction by default. +typedef struct { + void* fInstance; // WGPUInstance + void* fDevice; // WGPUDevice + void* fQueue; // WGPUQueue + int32_t fNonYielding; // 0 = install default tick fn; 1 = no tick fn (Emscripten) +} sk_graphite_dawn_backend_context_init_t; + +SK_C_API sk_graphite_dawn_backend_context_t* sk_graphite_dawn_backend_context_new(const sk_graphite_dawn_backend_context_init_t* init); +SK_C_API void sk_graphite_dawn_backend_context_delete(sk_graphite_dawn_backend_context_t* bc); + +SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( + const sk_graphite_dawn_backend_context_t* bc, + const sk_graphite_context_options_t* opts /* nullable -> defaults */); + +SK_C_PLUS_PLUS_END_GUARD + +#endif // sk_graphite_dawn_DEFINED diff --git a/include/c/sk_graphite_metal.h b/include/c/sk_graphite_metal.h new file mode 100644 index 000000000000..32d5c887a111 --- /dev/null +++ b/include/c/sk_graphite_metal.h @@ -0,0 +1,49 @@ +/* + * 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 sk_graphite_metal_DEFINED +#define sk_graphite_metal_DEFINED + +#include "include/c/sk_types.h" +#include "include/c/sk_graphite.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +// Opaque heap wrapper around skgpu::graphite::MtlBackendContext. The shim +// CFRetain's the device/queue handles passed in via the init struct, so the +// caller's references are not consumed. +typedef struct sk_graphite_mtl_backend_context_t sk_graphite_mtl_backend_context_t; + +// Init struct: caller-owned id + id. Both are +// passed as opaque void* to keep the C API ObjC-free; the shim treats them +// as CFTypeRef and CFRetain's during MakeMetal. +typedef struct { + void* fDevice; // id (CFRetain-able) + void* fQueue; // id (CFRetain-able) +} sk_graphite_mtl_backend_context_init_t; + +SK_C_API sk_graphite_mtl_backend_context_t* sk_graphite_mtl_backend_context_new(const sk_graphite_mtl_backend_context_init_t* init); +SK_C_API void sk_graphite_mtl_backend_context_delete(sk_graphite_mtl_backend_context_t* bc); + +// Build a Graphite Context for the Metal backend. Returns null on failure +// (Metal not built into libSkiaSharp, init invalid, or device rejected). +SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal( + const sk_graphite_mtl_backend_context_t* bc, + const sk_graphite_context_options_t* opts /* nullable -> defaults */); + +// Wrap an externally-allocated id as a Graphite BackendTexture. +// The wrapper does NOT retain the texture — Skia's contract is that the +// caller keeps the texture alive for the BackendTexture's lifetime. (See +// upstream comment in MtlGraphiteTypes_cpp.h: "The BackendTexture will not +// call retain or release on the passed in CFTypeRef.") +SK_C_API sk_graphite_backend_texture_t* sk_graphite_mtl_backend_texture_new( + int32_t width, int32_t height, + void* mtlTexture); // id + +SK_C_PLUS_PLUS_END_GUARD + +#endif // sk_graphite_metal_DEFINED diff --git a/include/c/sk_graphite_vulkan.h b/include/c/sk_graphite_vulkan.h new file mode 100644 index 000000000000..fcda00f51ca0 --- /dev/null +++ b/include/c/sk_graphite_vulkan.h @@ -0,0 +1,84 @@ +/* + * 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 sk_graphite_vulkan_DEFINED +#define sk_graphite_vulkan_DEFINED + +#include "include/c/sk_types.h" +#include "include/c/sk_graphite.h" + +SK_C_PLUS_PLUS_BEGIN_GUARD + +// Opaque handle wrapping the C++ skgpu::VulkanBackendContext value. +// Heap-allocated so that callers can pass it across the FFI without worrying +// about layout differences in the C++ struct between Skia revisions. +typedef struct sk_graphite_vk_backend_context_t sk_graphite_vk_backend_context_t; + +// Function-pointer getter signature: identical in shape to skgpu::VulkanGetProc. +// userData is the fGetProcUserData passed via the init struct. +typedef VKAPI_ATTR void (VKAPI_CALL *sk_graphite_vk_func_ptr)(void); +typedef sk_graphite_vk_func_ptr (*sk_graphite_vk_get_proc_t)(void* userData, const char* name, vk_instance_t* instance, vk_device_t* device); + +typedef struct { + vk_instance_t* fInstance; + vk_physical_device_t* fPhysicalDevice; + vk_device_t* fDevice; + vk_queue_t* fQueue; + uint32_t fGraphicsQueueIndex; + uint32_t fMaxAPIVersion; + sk_graphite_vk_get_proc_t fGetProc; + void* fGetProcUserData; + bool fProtectedContext; +} sk_graphite_vk_backend_context_init_t; + +// Lifetime: caller owns; pair with sk_graphite_vk_backend_context_delete. +SK_C_API sk_graphite_vk_backend_context_t* sk_graphite_vk_backend_context_new(const sk_graphite_vk_backend_context_init_t* init); +SK_C_API void sk_graphite_vk_backend_context_delete(sk_graphite_vk_backend_context_t* bc); + +// Build a Graphite Context for the Vulkan backend. +// Returns null on failure (Vulkan not built in, init invalid, or device rejected). +SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( + const sk_graphite_vk_backend_context_t* bc, + const sk_graphite_context_options_t* opts /* nullable -> defaults */); + +// Vulkan-specific TextureInfo (POD). Field values are passed straight into +// skgpu::graphite::VulkanTextureInfo. YCbCr conversion not exposed in v1 — +// add a separate _ycbcr_t struct + factory if needed. +typedef struct { + int32_t fSampleCount; // 1, 2, 4, 8, or 16 + int32_t fMipmapped; // 0 = no, 1 = yes + uint32_t fFlags; // VkImageCreateFlags + int32_t fFormat; // VkFormat (e.g. VK_FORMAT_R8G8B8A8_UNORM) + int32_t fImageTiling; // VkImageTiling + uint32_t fImageUsageFlags; // VkImageUsageFlags + int32_t fSharingMode; // VkSharingMode + uint32_t fAspectMask; // VkImageAspectFlags +} sk_graphite_vk_texture_info_t; + +// Build a backend-erased TextureInfo from Vulkan-specific fields. +// Caller owns the returned handle; pair with sk_graphite_texture_info_delete. +SK_C_API sk_graphite_texture_info_t* sk_graphite_vk_texture_info_new( + const sk_graphite_vk_texture_info_t* info); + +// Wrap an externally-allocated VkImage as a Graphite BackendTexture. The +// VkImage's memory must already be bound; the wrapper does NOT bind memory. +// Caller retains ownership of the VkImage — sk_graphite_backend_texture_delete +// only frees the wrapper, not the VkImage. +// +// imageLayout is the layout the image is in when handed to Skia (typically +// VK_IMAGE_LAYOUT_UNDEFINED for a fresh allocation). +SK_C_API sk_graphite_backend_texture_t* sk_graphite_vk_backend_texture_new( + int32_t width, + int32_t height, + const sk_graphite_vk_texture_info_t* info, + int32_t imageLayout, // VkImageLayout + uint32_t queueFamilyIndex, + void* vkImage); // VkImage + +SK_C_PLUS_PLUS_END_GUARD + +#endif // sk_graphite_vulkan_DEFINED diff --git a/src/c/sk_graphite.cpp b/src/c/sk_graphite.cpp new file mode 100644 index 000000000000..82cafe7da0ff --- /dev/null +++ b/src/c/sk_graphite.cpp @@ -0,0 +1,565 @@ +/* + * 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/sk_graphite.h" + +#include "include/core/SkTypes.h" // pulls SK_GRAPHITE / SK_VULKAN / SK_METAL / SK_DAWN + +#if defined(SK_GRAPHITE) + +#include "include/core/SkImage.h" +#include "include/core/SkSurface.h" +#include "include/gpu/graphite/BackendTexture.h" +#include "include/gpu/graphite/Context.h" +#include "include/gpu/graphite/ContextOptions.h" +#include "include/gpu/graphite/GraphiteTypes.h" +#include "include/gpu/graphite/Image.h" +#include "include/gpu/graphite/ImageProvider.h" +#include "include/gpu/graphite/Recorder.h" +#include "include/gpu/graphite/Recording.h" +#include "include/gpu/graphite/Surface.h" +#include "include/gpu/graphite/TextureInfo.h" + +#include "src/c/sk_types_priv.h" + +#include +#include +#include + +namespace gr = skgpu::graphite; + +// reinterpret_cast helpers between opaque C handles and C++ types +static inline gr::Context* AsGraphiteContext(sk_graphite_context_t* h) { return reinterpret_cast(h); } +static inline const gr::Context* AsGraphiteContext(const sk_graphite_context_t* h) { return reinterpret_cast(h); } +static inline sk_graphite_context_t* ToGraphiteContext(gr::Context* p) { return reinterpret_cast(p); } + +static inline gr::Recorder* AsGraphiteRecorder(sk_graphite_recorder_t* h) { return reinterpret_cast(h); } +static inline const gr::Recorder* AsGraphiteRecorder(const sk_graphite_recorder_t* h) { return reinterpret_cast(h); } +static inline sk_graphite_recorder_t* ToGraphiteRecorder(gr::Recorder* p) { return reinterpret_cast(p); } + +static inline gr::Recording* AsGraphiteRecording(sk_graphite_recording_t* h) { return reinterpret_cast(h); } +static inline sk_graphite_recording_t* ToGraphiteRecording(gr::Recording* p) { return reinterpret_cast(p); } + +static inline gr::BackendTexture* AsGraphiteBackendTexture(sk_graphite_backend_texture_t* h) { return reinterpret_cast(h); } +static inline const gr::BackendTexture* AsGraphiteBackendTexture(const sk_graphite_backend_texture_t* h) { return reinterpret_cast(h); } +static inline sk_graphite_backend_texture_t* ToGraphiteBackendTexture(gr::BackendTexture* p) { return reinterpret_cast(p); } + +static inline gr::TextureInfo* AsGraphiteTextureInfo(sk_graphite_texture_info_t* h) { return reinterpret_cast(h); } +static inline const gr::TextureInfo* AsGraphiteTextureInfo(const sk_graphite_texture_info_t* h) { return reinterpret_cast(h); } +static inline sk_graphite_texture_info_t* ToGraphiteTextureInfo(gr::TextureInfo* p) { return reinterpret_cast(p); } + +// Backend availability — runtime-queried, per-backend compile-time gated. +extern "C" SK_C_API bool sk_graphite_backend_is_available(sk_graphite_backend_t backend) { + switch (backend) { + case VULKAN_SK_GRAPHITE_BACKEND: +#if defined(SK_VULKAN) + return true; +#else + return false; +#endif + case METAL_SK_GRAPHITE_BACKEND: +#if defined(SK_METAL) + return true; +#else + return false; +#endif + case DAWN_SK_GRAPHITE_BACKEND: +#if defined(SK_DAWN) + return true; +#else + return false; +#endif + } + return false; +} + +// ContextOptions + +extern "C" SK_C_API void sk_graphite_context_options_init_defaults(sk_graphite_context_options_t* out) { + if (!out) return; + gr::ContextOptions defaults; + out->fDisableDriverCorrectnessWorkarounds = defaults.fDisableDriverCorrectnessWorkarounds; + out->fInternalMultisampleCount = static_cast(defaults.fInternalMultisampleCount); + out->fGpuBudgetInBytes = static_cast(defaults.fGpuBudgetInBytes); + out->fRequireOrderedRecordings = defaults.fRequireOrderedRecordings; + out->fSetBackendLabels = defaults.fSetBackendLabels; +} + +// Translate the public sample-count integer to the SampleCount enum. +static gr::SampleCount ToGraphiteSampleCount(int32_t count) { + switch (count) { + case 1: return gr::SampleCount::k1; + case 2: return gr::SampleCount::k2; + case 4: return gr::SampleCount::k4; + case 8: return gr::SampleCount::k8; + case 16: return gr::SampleCount::k16; + } + return gr::SampleCount::k1; +} + +// ImageProvider bridge — routes Graphite's "I have a non-Graphite SkImage, +// please convert it" hook to a function pointer + userData supplied by the +// managed (or any C) caller. Held by sk_sp inside ContextOptions; owned by +// the Context after ContextFactory::Make* succeeds. +namespace { +class FfiImageProvider : public gr::ImageProvider { +public: + FfiImageProvider(sk_graphite_image_provider_proc_t proc, void* userData) + : fProc(proc), fUserData(userData) {} + + sk_sp findOrCreate(gr::Recorder* recorder, + const SkImage* image, + SkImage::RequiredProperties props) override { + if (!fProc || !recorder || !image) return nullptr; + // The bridge consumes a +1 reference: the callback returns a fresh + // sk_image_t* (heap-owned by Skia's sk_sp machinery). We adopt it + // into an sk_sp so it is decremented exactly once when this scope — + // or whoever Skia hands the result to next — releases it. + sk_image_t* raw = fProc( + fUserData, + ToGraphiteRecorder(recorder), + ToImage(const_cast(image)), // const-correctness shim; Skia's hook is non-const + props.fMipmapped ? 1 : 0); + return sk_sp(AsImage(raw)); + } + +private: + sk_graphite_image_provider_proc_t fProc; + void* fUserData; +}; +} // namespace + +// Heap wrapper around sk_sp so the C ABI can hand out a +// stable pointer. We keep the sp as a member rather than naked-AddRef so the +// destructor releases the underlying ref-count exactly once when the wrapper +// is freed via sk_graphite_image_provider_delete. +struct sk_graphite_image_provider_t { + sk_sp sp; +}; + +extern "C" SK_C_API sk_graphite_image_provider_t* sk_graphite_image_provider_new( + sk_graphite_image_provider_proc_t proc, void* userData) +{ + if (!proc) return nullptr; + auto* w = new sk_graphite_image_provider_t; + w->sp = sk_make_sp(proc, userData); + return w; +} + +extern "C" SK_C_API void sk_graphite_image_provider_delete(sk_graphite_image_provider_t* w) { + delete w; +} + +extern "C" SK_C_API sk_image_t* sk_graphite_image_make_texture( + sk_graphite_recorder_t* recorder, + const sk_image_t* image, + int32_t mipmapped) +{ + if (!recorder || !image) return nullptr; + SkImage::RequiredProperties props; + props.fMipmapped = (mipmapped != 0); + auto out = SkImages::TextureFromImage(AsGraphiteRecorder(recorder), AsImage(image), props); + return ToImage(out.release()); +} + +// Public helper used by per-backend factories in sibling translation units. +gr::ContextOptions sk_graphite_make_context_options(const sk_graphite_context_options_t* opts) { + gr::ContextOptions out; + if (opts) { + out.fDisableDriverCorrectnessWorkarounds = opts->fDisableDriverCorrectnessWorkarounds; + out.fInternalMultisampleCount = ToGraphiteSampleCount(opts->fInternalMultisampleCount); + if (opts->fGpuBudgetInBytes >= 0) { + out.fGpuBudgetInBytes = static_cast(opts->fGpuBudgetInBytes); + } + out.fRequireOrderedRecordings = opts->fRequireOrderedRecordings; + out.fSetBackendLabels = opts->fSetBackendLabels; + } + return out; +} + +// Context + +extern "C" SK_C_API void sk_graphite_context_delete(sk_graphite_context_t* h) { + delete AsGraphiteContext(h); +} + +extern "C" SK_C_API sk_graphite_backend_t sk_graphite_context_get_backend(const sk_graphite_context_t* h) { + if (!h) return VULKAN_SK_GRAPHITE_BACKEND; // arbitrary; caller misused the API + switch (AsGraphiteContext(h)->backend()) { + case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; + default: return VULKAN_SK_GRAPHITE_BACKEND; + } +} + +extern "C" SK_C_API bool sk_graphite_context_is_device_lost(const sk_graphite_context_t* h) { return h ? AsGraphiteContext(h)->isDeviceLost() : true; } +extern "C" SK_C_API int32_t sk_graphite_context_get_max_texture_size(const sk_graphite_context_t* h) { return h ? AsGraphiteContext(h)->maxTextureSize() : 0; } +extern "C" SK_C_API bool sk_graphite_context_supports_protected_content(const sk_graphite_context_t* h) { return h ? AsGraphiteContext(h)->supportsProtectedContent() : false; } +extern "C" SK_C_API int64_t sk_graphite_context_get_current_budgeted_bytes(const sk_graphite_context_t* h) { return h ? static_cast(AsGraphiteContext(h)->currentBudgetedBytes()) : 0; } +extern "C" SK_C_API int64_t sk_graphite_context_get_max_budgeted_bytes(const sk_graphite_context_t* h) { return h ? static_cast(AsGraphiteContext(h)->maxBudgetedBytes()) : 0; } +extern "C" SK_C_API void sk_graphite_context_set_max_budgeted_bytes(sk_graphite_context_t* h, int64_t b) { if (h && b >= 0) AsGraphiteContext(h)->setMaxBudgetedBytes(static_cast(b)); } +extern "C" SK_C_API void sk_graphite_context_free_gpu_resources(sk_graphite_context_t* h) { if (h) AsGraphiteContext(h)->freeGpuResources(); } + +extern "C" SK_C_API void sk_graphite_context_perform_deferred_cleanup(sk_graphite_context_t* h, int64_t milliseconds) { + if (h && milliseconds >= 0) { + AsGraphiteContext(h)->performDeferredCleanup(std::chrono::milliseconds(milliseconds)); + } +} + +extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder(sk_graphite_context_t* h, int64_t recorderBudgetBytes) { + return sk_graphite_context_make_recorder_with_image_provider(h, recorderBudgetBytes, nullptr); +} + +extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder_with_image_provider( + sk_graphite_context_t* h, int64_t recorderBudgetBytes, sk_graphite_image_provider_t* imageProvider) +{ + if (!h) return nullptr; + gr::RecorderOptions opts; + if (recorderBudgetBytes >= 0) { + opts.fGpuBudgetInBytes = static_cast(recorderBudgetBytes); + } + if (imageProvider) { + // The recorder takes its own ref on the underlying ImageProvider via the sp copy. + // Caller's wrapper sp keeps its own ref until sk_graphite_image_provider_delete is + // invoked — typically right after this call returns. + opts.fImageProvider = imageProvider->sp; + } + auto recorder = AsGraphiteContext(h)->makeRecorder(opts); + return ToGraphiteRecorder(recorder.release()); +} + +static sk_graphite_insert_status_t ToInsertStatus(gr::InsertStatus::V v) { + switch (v) { + case gr::InsertStatus::kSuccess: return SUCCESS_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kInvalidRecording: return INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kPromiseImageInstantiationFailed: return PROMISE_INSTANTIATION_FAILED_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kAddCommandsFailed: return ADD_COMMANDS_FAILED_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kAsyncShaderCompilesFailed: return ASYNC_SHADER_COMPILES_FAILED_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kOutOfOrderRecording: return OUT_OF_ORDER_RECORDING_SK_GRAPHITE_INSERT_STATUS; + } + return INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS; +} + +extern "C" SK_C_API sk_graphite_insert_status_t sk_graphite_context_insert_recording(sk_graphite_context_t* h, const sk_graphite_insert_recording_info_t* info) { + if (!h || !info || !info->fRecording) { + return INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS; + } + gr::InsertRecordingInfo iri; + iri.fRecording = AsGraphiteRecording(info->fRecording); + iri.fTargetSurface = AsSurface(info->fTargetSurface); + iri.fTargetTranslation.fX = info->fTargetTranslationX; + iri.fTargetTranslation.fY = info->fTargetTranslationY; + iri.fTargetClip = *AsIRect(&info->fTargetClip); + auto status = AsGraphiteContext(h)->insertRecording(iri); + return ToInsertStatus(status); +} + +extern "C" SK_C_API bool sk_graphite_context_submit(sk_graphite_context_t* h, const sk_graphite_submit_info_t* info) { + if (!h) return false; + gr::SubmitInfo si; + if (info) { + si.fSync = (info->fSync == YES_SK_GRAPHITE_SYNC_TO_CPU) ? gr::SyncToCpu::kYes : gr::SyncToCpu::kNo; + si.fMarkBoundary = (info->fMarkBoundary == YES_SK_GRAPHITE_MARK_FRAME_BOUNDARY) ? gr::MarkFrameBoundary::kYes : gr::MarkFrameBoundary::kNo; + si.fFrameID = info->fFrameID; + } + return AsGraphiteContext(h)->submit(si); +} + +// Recorder + +extern "C" SK_C_API void sk_graphite_recorder_delete(sk_graphite_recorder_t* h) { + delete AsGraphiteRecorder(h); +} + +extern "C" SK_C_API sk_graphite_backend_t sk_graphite_recorder_get_backend(const sk_graphite_recorder_t* h) { + if (!h) return VULKAN_SK_GRAPHITE_BACKEND; + switch (AsGraphiteRecorder(h)->backend()) { + case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; + default: return VULKAN_SK_GRAPHITE_BACKEND; + } +} + +extern "C" SK_C_API int32_t sk_graphite_recorder_get_max_texture_size(const sk_graphite_recorder_t* h) { + return h ? AsGraphiteRecorder(h)->maxTextureSize() : 0; +} + +extern "C" SK_C_API sk_graphite_recording_t* sk_graphite_recorder_snap(sk_graphite_recorder_t* h) { + if (!h) return nullptr; + auto recording = AsGraphiteRecorder(h)->snap(); + return ToGraphiteRecording(recording.release()); +} + +// Recording + +extern "C" SK_C_API void sk_graphite_recording_delete(sk_graphite_recording_t* h) { + delete AsGraphiteRecording(h); +} + +// Surface (Graphite-backed) + +extern "C" SK_C_API sk_surface_t* sk_graphite_surface_make_render_target( + sk_graphite_recorder_t* recorder, + const sk_imageinfo_t* cinfo, + int32_t mipmapped, + const sk_surfaceprops_t* props) +{ + if (!recorder || !cinfo) return nullptr; + SkImageInfo info = AsImageInfo(cinfo); + skgpu::Mipmapped mips = (mipmapped != 0) ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; + auto surface = SkSurfaces::RenderTarget(AsGraphiteRecorder(recorder), info, mips, AsSurfaceProps(props)); + return ToSurface(surface.release()); +} + +extern "C" SK_C_API sk_surface_t* sk_graphite_surface_wrap_backend_texture( + sk_graphite_recorder_t* recorder, + const sk_graphite_backend_texture_t* backendTexture, + sk_colortype_t colorType, + sk_colorspace_t* colorSpace, + const sk_surfaceprops_t* props, + sk_graphite_release_proc_t releaseProc, + void* releaseContext) +{ + if (!recorder || !backendTexture) return nullptr; + auto surface = SkSurfaces::WrapBackendTexture( + AsGraphiteRecorder(recorder), + *AsGraphiteBackendTexture(backendTexture), + static_cast(colorType), + sk_ref_sp(AsColorSpace(colorSpace)), + AsSurfaceProps(props), + reinterpret_cast(releaseProc), + releaseContext); + return ToSurface(surface.release()); +} + +extern "C" SK_C_API sk_image_t* sk_graphite_image_wrap_texture( + sk_graphite_recorder_t* recorder, + const sk_graphite_backend_texture_t* backendTexture, + sk_colortype_t colorType, + sk_alphatype_t alphaType, + sk_colorspace_t* colorSpace, + sk_graphite_release_proc_t releaseProc, + void* releaseContext) +{ + if (!recorder || !backendTexture) return nullptr; + auto image = SkImages::WrapTexture( + AsGraphiteRecorder(recorder), + *AsGraphiteBackendTexture(backendTexture), + static_cast(colorType), + static_cast(alphaType), + sk_ref_sp(AsColorSpace(colorSpace)), + reinterpret_cast(releaseProc), + releaseContext); + return ToImage(image.release()); +} + +extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_recorder_create_backend_texture( + sk_graphite_recorder_t* recorder, + int32_t width, int32_t height, + const sk_graphite_texture_info_t* info) +{ + if (!recorder || !info || width <= 0 || height <= 0) return nullptr; + auto bt = AsGraphiteRecorder(recorder)->createBackendTexture( + SkISize::Make(width, height), + *AsGraphiteTextureInfo(info)); + if (!bt.isValid()) return nullptr; + return ToGraphiteBackendTexture(new gr::BackendTexture(bt)); +} + +extern "C" SK_C_API void sk_graphite_recorder_delete_backend_texture( + sk_graphite_recorder_t* recorder, + const sk_graphite_backend_texture_t* tex) +{ + if (recorder && tex) + AsGraphiteRecorder(recorder)->deleteBackendTexture(*AsGraphiteBackendTexture(tex)); +} + +extern "C" SK_C_API void sk_graphite_context_delete_backend_texture( + sk_graphite_context_t* context, + const sk_graphite_backend_texture_t* tex) +{ + if (context && tex) + AsGraphiteContext(context)->deleteBackendTexture(*AsGraphiteBackendTexture(tex)); +} + +// BackendTexture handle: heap wrapper around the C++ value type. + +extern "C" SK_C_API void sk_graphite_backend_texture_delete(sk_graphite_backend_texture_t* h) { + delete AsGraphiteBackendTexture(h); +} +extern "C" SK_C_API bool sk_graphite_backend_texture_is_valid(const sk_graphite_backend_texture_t* h) { + return h ? AsGraphiteBackendTexture(h)->isValid() : false; +} +extern "C" SK_C_API sk_graphite_backend_t sk_graphite_backend_texture_get_backend(const sk_graphite_backend_texture_t* h) { + if (!h) return VULKAN_SK_GRAPHITE_BACKEND; + switch (AsGraphiteBackendTexture(h)->backend()) { + case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; + default: return VULKAN_SK_GRAPHITE_BACKEND; + } +} +extern "C" SK_C_API void sk_graphite_backend_texture_get_dimensions(const sk_graphite_backend_texture_t* h, int32_t* outW, int32_t* outH) { + if (!h || !outW || !outH) return; + auto d = AsGraphiteBackendTexture(h)->dimensions(); + *outW = d.fWidth; + *outH = d.fHeight; +} + +// TextureInfo handle. + +extern "C" SK_C_API void sk_graphite_texture_info_delete(sk_graphite_texture_info_t* h) { + delete AsGraphiteTextureInfo(h); +} +extern "C" SK_C_API bool sk_graphite_texture_info_is_valid(const sk_graphite_texture_info_t* h) { + return h ? AsGraphiteTextureInfo(h)->isValid() : false; +} +extern "C" SK_C_API sk_graphite_backend_t sk_graphite_texture_info_get_backend(const sk_graphite_texture_info_t* h) { + if (!h) return VULKAN_SK_GRAPHITE_BACKEND; + switch (AsGraphiteTextureInfo(h)->backend()) { + case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; + default: return VULKAN_SK_GRAPHITE_BACKEND; + } +} +extern "C" SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_graphite_texture_info_t* h) { + if (!h) return 1; + switch (AsGraphiteTextureInfo(h)->sampleCount()) { + case gr::SampleCount::k1: return 1; + case gr::SampleCount::k2: return 2; + case gr::SampleCount::k4: return 4; + case gr::SampleCount::k8: return 8; + case gr::SampleCount::k16: return 16; + } + return 1; +} +extern "C" SK_C_API int32_t sk_graphite_texture_info_get_mipmapped(const sk_graphite_texture_info_t* h) { + return (h && AsGraphiteTextureInfo(h)->mipmapped() == skgpu::Mipmapped::kYes) ? 1 : 0; +} + +// Async readback — pass-through wrapping of Context::asyncRescaleAndReadPixels. +// The C user provides a function-pointer callback + context void*. Our adapter +// receives the unique_ptr from Skia, hands the user a non-owning +// pointer to it, and lets the unique_ptr destruct when the callback returns. +namespace { +struct AsyncReadCallbackBridge { + sk_graphite_async_read_pixels_proc_t userCallback; + void* userContext; +}; + +void asyncReadCallbackAdapter(SkImage::ReadPixelsContext rawBridge, + std::unique_ptr result) { + auto* bridge = static_cast(rawBridge); + bridge->userCallback( + bridge->userContext, + reinterpret_cast(result.get())); + delete bridge; + // unique_ptr destroys result here as it goes out of scope — pointer the user + // saw is now invalid. Documented in sk_graphite.h. +} +} // namespace + +extern "C" SK_C_API void sk_graphite_context_async_rescale_and_read_pixels_surface( + sk_graphite_context_t* h, + const sk_surface_t* surface, + const sk_imageinfo_t* cdstInfo, + const sk_irect_t* csrcRect, + sk_graphite_rescale_gamma_t rescaleGamma, + sk_graphite_rescale_mode_t rescaleMode, + sk_graphite_async_read_pixels_proc_t callback, + void* callbackContext) +{ + if (!h || !surface || !cdstInfo || !csrcRect || !callback) { + if (callback) callback(callbackContext, nullptr); // signal failure synchronously + return; + } + auto* bridge = new AsyncReadCallbackBridge{callback, callbackContext}; + AsGraphiteContext(h)->asyncRescaleAndReadPixels( + AsSurface(surface), + AsImageInfo(cdstInfo), + *AsIRect(csrcRect), + static_cast(rescaleGamma), + static_cast(rescaleMode), + asyncReadCallbackAdapter, + bridge); +} + +extern "C" SK_C_API void sk_graphite_context_check_async_work_completion(sk_graphite_context_t* h) { + if (h) AsGraphiteContext(h)->checkAsyncWorkCompletion(); +} + +// AsyncReadResult accessors. The opaque sk_graphite_async_read_result_t* maps +// directly to const SkImage::AsyncReadResult*. +static inline const SkImage::AsyncReadResult* AsAsyncReadResult(const sk_graphite_async_read_result_t* h) { + return reinterpret_cast(h); +} + +extern "C" SK_C_API int32_t sk_graphite_async_read_result_get_count(const sk_graphite_async_read_result_t* h) { + return h ? AsAsyncReadResult(h)->count() : 0; +} + +extern "C" SK_C_API const void* sk_graphite_async_read_result_get_data(const sk_graphite_async_read_result_t* h, int32_t planeIndex) { + if (!h || planeIndex < 0 || planeIndex >= AsAsyncReadResult(h)->count()) return nullptr; + return AsAsyncReadResult(h)->data(planeIndex); +} + +extern "C" SK_C_API size_t sk_graphite_async_read_result_get_row_bytes(const sk_graphite_async_read_result_t* h, int32_t planeIndex) { + if (!h || planeIndex < 0 || planeIndex >= AsAsyncReadResult(h)->count()) return 0; + return AsAsyncReadResult(h)->rowBytes(planeIndex); +} + +#else // !SK_GRAPHITE — stubs so the C ABI surface is consistent regardless of build config + +extern "C" SK_C_API bool sk_graphite_backend_is_available(sk_graphite_backend_t) { return false; } +extern "C" SK_C_API void sk_graphite_context_options_init_defaults(sk_graphite_context_options_t*) {} +extern "C" SK_C_API void sk_graphite_context_delete(sk_graphite_context_t*) {} +extern "C" SK_C_API sk_graphite_backend_t sk_graphite_context_get_backend(const sk_graphite_context_t*) { return VULKAN_SK_GRAPHITE_BACKEND; } +extern "C" SK_C_API bool sk_graphite_context_is_device_lost(const sk_graphite_context_t*) { return true; } +extern "C" SK_C_API int32_t sk_graphite_context_get_max_texture_size(const sk_graphite_context_t*) { return 0; } +extern "C" SK_C_API bool sk_graphite_context_supports_protected_content(const sk_graphite_context_t*) { return false; } +extern "C" SK_C_API int64_t sk_graphite_context_get_current_budgeted_bytes(const sk_graphite_context_t*) { return 0; } +extern "C" SK_C_API int64_t sk_graphite_context_get_max_budgeted_bytes(const sk_graphite_context_t*) { return 0; } +extern "C" SK_C_API void sk_graphite_context_set_max_budgeted_bytes(sk_graphite_context_t*, int64_t) {} +extern "C" SK_C_API void sk_graphite_context_free_gpu_resources(sk_graphite_context_t*) {} +extern "C" SK_C_API void sk_graphite_context_perform_deferred_cleanup(sk_graphite_context_t*, int64_t) {} +extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder(sk_graphite_context_t*, int64_t) { return nullptr; } +extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder_with_image_provider(sk_graphite_context_t*, int64_t, sk_graphite_image_provider_t*) { return nullptr; } +extern "C" SK_C_API sk_graphite_insert_status_t sk_graphite_context_insert_recording(sk_graphite_context_t*, const sk_graphite_insert_recording_info_t*) { return INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS; } +extern "C" SK_C_API bool sk_graphite_context_submit(sk_graphite_context_t*, const sk_graphite_submit_info_t*) { return false; } +extern "C" SK_C_API void sk_graphite_recorder_delete(sk_graphite_recorder_t*) {} +extern "C" SK_C_API sk_graphite_backend_t sk_graphite_recorder_get_backend(const sk_graphite_recorder_t*) { return VULKAN_SK_GRAPHITE_BACKEND; } +extern "C" SK_C_API int32_t sk_graphite_recorder_get_max_texture_size(const sk_graphite_recorder_t*) { return 0; } +extern "C" SK_C_API sk_graphite_recording_t* sk_graphite_recorder_snap(sk_graphite_recorder_t*) { return nullptr; } +extern "C" SK_C_API void sk_graphite_recording_delete(sk_graphite_recording_t*) {} +extern "C" SK_C_API sk_surface_t* sk_graphite_surface_make_render_target(sk_graphite_recorder_t*, const sk_imageinfo_t*, int32_t, const sk_surfaceprops_t*) { return nullptr; } +extern "C" SK_C_API void sk_graphite_context_async_rescale_and_read_pixels_surface(sk_graphite_context_t*, const sk_surface_t*, const sk_imageinfo_t*, const sk_irect_t*, sk_graphite_rescale_gamma_t, sk_graphite_rescale_mode_t, sk_graphite_async_read_pixels_proc_t cb, void* ctx) { if (cb) cb(ctx, nullptr); } +extern "C" SK_C_API void sk_graphite_context_check_async_work_completion(sk_graphite_context_t*) {} +extern "C" SK_C_API int32_t sk_graphite_async_read_result_get_count(const sk_graphite_async_read_result_t*) { return 0; } +extern "C" SK_C_API const void* sk_graphite_async_read_result_get_data(const sk_graphite_async_read_result_t*, int32_t) { return nullptr; } +extern "C" SK_C_API size_t sk_graphite_async_read_result_get_row_bytes(const sk_graphite_async_read_result_t*, int32_t) { return 0; } +extern "C" SK_C_API sk_surface_t* sk_graphite_surface_wrap_backend_texture(sk_graphite_recorder_t*, const sk_graphite_backend_texture_t*, sk_colortype_t, sk_colorspace_t*, const sk_surfaceprops_t*, sk_graphite_release_proc_t, void*) { return nullptr; } +extern "C" SK_C_API void sk_graphite_backend_texture_delete(sk_graphite_backend_texture_t*) {} +extern "C" SK_C_API bool sk_graphite_backend_texture_is_valid(const sk_graphite_backend_texture_t*) { return false; } +extern "C" SK_C_API sk_graphite_backend_t sk_graphite_backend_texture_get_backend(const sk_graphite_backend_texture_t*) { return VULKAN_SK_GRAPHITE_BACKEND; } +extern "C" SK_C_API void sk_graphite_backend_texture_get_dimensions(const sk_graphite_backend_texture_t*, int32_t*, int32_t*) {} +extern "C" SK_C_API void sk_graphite_texture_info_delete(sk_graphite_texture_info_t*) {} +extern "C" SK_C_API bool sk_graphite_texture_info_is_valid(const sk_graphite_texture_info_t*) { return false; } +extern "C" SK_C_API sk_graphite_backend_t sk_graphite_texture_info_get_backend(const sk_graphite_texture_info_t*) { return VULKAN_SK_GRAPHITE_BACKEND; } +extern "C" SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_graphite_texture_info_t*) { return 1; } +extern "C" SK_C_API int32_t sk_graphite_texture_info_get_mipmapped(const sk_graphite_texture_info_t*) { return 0; } +extern "C" SK_C_API sk_image_t* sk_graphite_image_wrap_texture(sk_graphite_recorder_t*, const sk_graphite_backend_texture_t*, sk_colortype_t, sk_alphatype_t, sk_colorspace_t*, sk_graphite_release_proc_t, void*) { return nullptr; } +extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_recorder_create_backend_texture(sk_graphite_recorder_t*, int32_t, int32_t, const sk_graphite_texture_info_t*) { return nullptr; } +extern "C" SK_C_API void sk_graphite_recorder_delete_backend_texture(sk_graphite_recorder_t*, const sk_graphite_backend_texture_t*) {} +extern "C" SK_C_API void sk_graphite_context_delete_backend_texture(sk_graphite_context_t*, const sk_graphite_backend_texture_t*) {} +extern "C" SK_C_API sk_graphite_image_provider_t* sk_graphite_image_provider_new(sk_graphite_image_provider_proc_t, void*) { return nullptr; } +extern "C" SK_C_API void sk_graphite_image_provider_delete(sk_graphite_image_provider_t*) {} +extern "C" SK_C_API sk_image_t* sk_graphite_image_make_texture(sk_graphite_recorder_t*, const sk_image_t*, int32_t) { return nullptr; } + +#endif // SK_GRAPHITE diff --git a/src/c/sk_graphite_dawn.cpp b/src/c/sk_graphite_dawn.cpp new file mode 100644 index 000000000000..1b6a59af221e --- /dev/null +++ b/src/c/sk_graphite_dawn.cpp @@ -0,0 +1,70 @@ +/* + * 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/sk_graphite_dawn.h" + +#include "include/core/SkTypes.h" + +#if defined(SK_GRAPHITE) && defined(SK_DAWN) + +#include "include/gpu/graphite/Context.h" +#include "include/gpu/graphite/ContextOptions.h" +#include "include/gpu/graphite/dawn/DawnBackendContext.h" + +#include "src/c/sk_types_priv.h" + +#include + +namespace gr = skgpu::graphite; + +// Forward-declared in sk_graphite.cpp; reused here for option translation. +extern gr::ContextOptions sk_graphite_make_context_options(const sk_graphite_context_options_t* opts); + +// Heap-allocated wrapper holding a DawnBackendContext value. The wgpu::Instance/ +// Device/Queue smart pointers AddRef on construction-from-raw and Release on +// destruction, so caller's references remain unaffected by our wrapper's +// lifetime. +struct sk_graphite_dawn_backend_context_t { + gr::DawnBackendContext dbc; +}; + +extern "C" SK_C_API sk_graphite_dawn_backend_context_t* sk_graphite_dawn_backend_context_new(const sk_graphite_dawn_backend_context_init_t* init) { + if (!init || !init->fInstance || !init->fDevice || !init->fQueue) return nullptr; + auto* bc = new sk_graphite_dawn_backend_context_t; + // wgpu::Instance/Device/Queue construct-from-raw with AddRef semantics + // (the wgpu C++ wrappers' default constructor takes a raw C handle and + // adds a reference unless explicitly told to acquire). + bc->dbc.fInstance = wgpu::Instance(static_cast(init->fInstance)); + bc->dbc.fDevice = wgpu::Device (static_cast (init->fDevice)); + bc->dbc.fQueue = wgpu::Queue (static_cast (init->fQueue)); + if (init->fNonYielding) { + bc->dbc.fTick = nullptr; + } + // else: leave the default (DawnNativeProcessEventsFunction on non-Emscripten). + return bc; +} + +extern "C" SK_C_API void sk_graphite_dawn_backend_context_delete(sk_graphite_dawn_backend_context_t* bc) { + delete bc; // wgpu::* destructors call Release +} + +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( + const sk_graphite_dawn_backend_context_t* bc, + const sk_graphite_context_options_t* opts) +{ + if (!bc) return nullptr; + auto context = gr::ContextFactory::MakeDawn(bc->dbc, sk_graphite_make_context_options(opts)); + return reinterpret_cast(context.release()); +} + +#else // !(SK_GRAPHITE && SK_DAWN) + +extern "C" SK_C_API sk_graphite_dawn_backend_context_t* sk_graphite_dawn_backend_context_new(const sk_graphite_dawn_backend_context_init_t*) { return nullptr; } +extern "C" SK_C_API void sk_graphite_dawn_backend_context_delete(sk_graphite_dawn_backend_context_t*) {} +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn(const sk_graphite_dawn_backend_context_t*, const sk_graphite_context_options_t*) { return nullptr; } + +#endif // SK_GRAPHITE && SK_DAWN diff --git a/src/c/sk_graphite_metal.cpp b/src/c/sk_graphite_metal.cpp new file mode 100644 index 000000000000..78b7b6a8d0d5 --- /dev/null +++ b/src/c/sk_graphite_metal.cpp @@ -0,0 +1,75 @@ +/* + * 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/sk_graphite_metal.h" + +#include "include/core/SkTypes.h" + +#if defined(SK_GRAPHITE) && defined(SK_METAL) + +#include "include/gpu/graphite/BackendTexture.h" +#include "include/gpu/graphite/Context.h" +#include "include/gpu/graphite/ContextOptions.h" +#include "include/gpu/graphite/mtl/MtlBackendContext.h" +#include "include/gpu/graphite/mtl/MtlGraphiteTypes_cpp.h" +#include "include/ports/SkCFObject.h" + +#include "src/c/sk_types_priv.h" + +#include + +namespace gr = skgpu::graphite; + +// Forward-declared in sk_graphite.cpp; reused here to share option translation. +extern gr::ContextOptions sk_graphite_make_context_options(const sk_graphite_context_options_t* opts); + +// Heap-allocated wrapper holding an MtlBackendContext value. The wrapper +// CFRetains the caller-supplied device/queue at construction (via sk_ret_cfp) +// and CFReleases them on delete (via sk_cfp's destructor). +struct sk_graphite_mtl_backend_context_t { + gr::MtlBackendContext mbc; +}; + +extern "C" SK_C_API sk_graphite_mtl_backend_context_t* sk_graphite_mtl_backend_context_new(const sk_graphite_mtl_backend_context_init_t* init) { + if (!init || !init->fDevice || !init->fQueue) return nullptr; + auto* bc = new sk_graphite_mtl_backend_context_t; + bc->mbc.fDevice = sk_ret_cfp(static_cast(init->fDevice)); + bc->mbc.fQueue = sk_ret_cfp(static_cast(init->fQueue)); + return bc; +} + +extern "C" SK_C_API void sk_graphite_mtl_backend_context_delete(sk_graphite_mtl_backend_context_t* bc) { + delete bc; // sk_cfp destructors call CFRelease +} + +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal( + const sk_graphite_mtl_backend_context_t* bc, + const sk_graphite_context_options_t* opts) +{ + if (!bc) return nullptr; + auto context = gr::ContextFactory::MakeMetal(bc->mbc, sk_graphite_make_context_options(opts)); + return reinterpret_cast(context.release()); +} + +extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_mtl_backend_texture_new( + int32_t width, int32_t height, void* mtlTexture) +{ + if (width <= 0 || height <= 0 || !mtlTexture) return nullptr; + auto bt = gr::BackendTextures::MakeMetal(SkISize::Make(width, height), + static_cast(mtlTexture)); + auto* heap = new gr::BackendTexture(bt); + return reinterpret_cast(heap); +} + +#else // !(SK_GRAPHITE && SK_METAL) + +extern "C" SK_C_API sk_graphite_mtl_backend_context_t* sk_graphite_mtl_backend_context_new(const sk_graphite_mtl_backend_context_init_t*) { return nullptr; } +extern "C" SK_C_API void sk_graphite_mtl_backend_context_delete(sk_graphite_mtl_backend_context_t*) {} +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal(const sk_graphite_mtl_backend_context_t*, const sk_graphite_context_options_t*) { return nullptr; } +extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_mtl_backend_texture_new(int32_t, int32_t, void*) { return nullptr; } + +#endif // SK_GRAPHITE && SK_METAL diff --git a/src/c/sk_graphite_vulkan.cpp b/src/c/sk_graphite_vulkan.cpp new file mode 100644 index 000000000000..998d85a918f4 --- /dev/null +++ b/src/c/sk_graphite_vulkan.cpp @@ -0,0 +1,145 @@ +/* + * 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/sk_graphite_vulkan.h" + +#include "include/core/SkTypes.h" + +#if defined(SK_GRAPHITE) && defined(SK_VULKAN) + +#include "include/gpu/GpuTypes.h" +#include "include/gpu/graphite/BackendTexture.h" +#include "include/gpu/graphite/Context.h" +#include "include/gpu/graphite/ContextOptions.h" +#include "include/gpu/graphite/TextureInfo.h" +#include "include/gpu/graphite/vk/VulkanGraphiteContext.h" +#include "include/gpu/graphite/vk/VulkanGraphiteTypes.h" +#include "include/gpu/vk/VulkanBackendContext.h" +#include "include/gpu/vk/VulkanMemoryAllocator.h" + +#include "src/c/sk_types_priv.h" +#include "src/gpu/GpuTypesPriv.h" +#include "src/gpu/vk/vulkanmemoryallocator/VulkanMemoryAllocatorPriv.h" + +#include + +namespace gr = skgpu::graphite; + +// Forward-declared in sk_graphite.cpp; reused here to share option translation. +extern gr::ContextOptions sk_graphite_make_context_options(const sk_graphite_context_options_t* opts); + +// Heap-allocated wrapper holding a copy of the init struct so that the lambda +// captured by the resulting skgpu::VulkanBackendContext::fGetProc has stable +// pointers to fGetProc / fGetProcUserData regardless of caller stack lifetime. +struct sk_graphite_vk_backend_context_t { + sk_graphite_vk_backend_context_init_t init; +}; + +extern "C" SK_C_API sk_graphite_vk_backend_context_t* sk_graphite_vk_backend_context_new(const sk_graphite_vk_backend_context_init_t* init) { + if (!init) return nullptr; + auto* bc = new sk_graphite_vk_backend_context_t{*init}; + return bc; +} + +extern "C" SK_C_API void sk_graphite_vk_backend_context_delete(sk_graphite_vk_backend_context_t* bc) { + delete bc; +} + +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( + const sk_graphite_vk_backend_context_t* bc, + const sk_graphite_context_options_t* opts) +{ + if (!bc) return nullptr; + const auto& init = bc->init; + + skgpu::VulkanBackendContext vkbc; + vkbc.fInstance = reinterpret_cast(init.fInstance); + vkbc.fPhysicalDevice = reinterpret_cast(init.fPhysicalDevice); + vkbc.fDevice = reinterpret_cast(init.fDevice); + vkbc.fQueue = reinterpret_cast(init.fQueue); + vkbc.fGraphicsQueueIndex = init.fGraphicsQueueIndex; + vkbc.fMaxAPIVersion = init.fMaxAPIVersion; + vkbc.fProtectedContext = init.fProtectedContext ? skgpu::Protected::kYes : skgpu::Protected::kNo; + + if (init.fGetProc) { + // Capture by value so the lambda is independent of the wrapper's lifetime. + sk_graphite_vk_get_proc_t getProc = init.fGetProc; + void* userData = init.fGetProcUserData; + vkbc.fGetProc = [getProc, userData](const char* name, VkInstance instance, VkDevice device) -> PFN_vkVoidFunction { + return reinterpret_cast( + getProc(userData, name, reinterpret_cast(instance), reinterpret_cast(device))); + }; + } + + // 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. + if (!vkbc.fMemoryAllocator) { + vkbc.fMemoryAllocator = skgpu::VulkanMemoryAllocators::Make(vkbc, skgpu::ThreadSafe::kNo); + if (!vkbc.fMemoryAllocator) { + return nullptr; + } + } + + auto context = gr::ContextFactory::MakeVulkan(vkbc, sk_graphite_make_context_options(opts)); + return reinterpret_cast(context.release()); +} + +// Vulkan TextureInfo + BackendTexture factories. + +namespace { +gr::VulkanTextureInfo MakeNativeVkTextureInfo(const sk_graphite_vk_texture_info_t& info) { + auto sampleBits = static_cast(info.fSampleCount > 0 ? info.fSampleCount : 1); + auto mipmapped = info.fMipmapped != 0 ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; + return gr::VulkanTextureInfo( + sampleBits, + mipmapped, + static_cast(info.fFlags), + static_cast(info.fFormat), + static_cast(info.fImageTiling), + static_cast(info.fImageUsageFlags), + static_cast(info.fSharingMode), + static_cast(info.fAspectMask), + skgpu::VulkanYcbcrConversionInfo{}); +} +} // namespace + +extern "C" SK_C_API sk_graphite_texture_info_t* sk_graphite_vk_texture_info_new(const sk_graphite_vk_texture_info_t* info) { + if (!info) return nullptr; + auto* ti = new gr::TextureInfo(gr::TextureInfos::MakeVulkan(MakeNativeVkTextureInfo(*info))); + return reinterpret_cast(ti); +} + +extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_vk_backend_texture_new( + int32_t width, int32_t height, + const sk_graphite_vk_texture_info_t* info, + int32_t imageLayout, + uint32_t queueFamilyIndex, + void* vkImage) +{ + if (!info || width <= 0 || height <= 0) return nullptr; + auto vkti = MakeNativeVkTextureInfo(*info); + auto bt = gr::BackendTextures::MakeVulkan( + SkISize::Make(width, height), + vkti, + static_cast(imageLayout), + queueFamilyIndex, + reinterpret_cast(vkImage), + skgpu::VulkanAlloc{}); + auto* heap = new gr::BackendTexture(bt); + return reinterpret_cast(heap); +} + +#else // !(SK_GRAPHITE && SK_VULKAN) + +extern "C" SK_C_API sk_graphite_vk_backend_context_t* sk_graphite_vk_backend_context_new(const sk_graphite_vk_backend_context_init_t*) { return nullptr; } +extern "C" SK_C_API void sk_graphite_vk_backend_context_delete(sk_graphite_vk_backend_context_t*) {} +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan(const sk_graphite_vk_backend_context_t*, const sk_graphite_context_options_t*) { return nullptr; } +extern "C" SK_C_API sk_graphite_texture_info_t* sk_graphite_vk_texture_info_new(const sk_graphite_vk_texture_info_t*) { return nullptr; } +extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_vk_backend_texture_new(int32_t, int32_t, const sk_graphite_vk_texture_info_t*, int32_t, uint32_t, void*) { return nullptr; } + +#endif // SK_GRAPHITE && SK_VULKAN From 126959f786261f656fb08e200c485ca7b7b02c10 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Sun, 10 May 2026 22:33:15 +0000 Subject: [PATCH 02/14] mono/skia: anchor sk_graphite_* symbols in SkiaKeeper macOS builds libSkiaSharp.dylib in two steps -- GN/ninja produces libskia.a, then xcodebuild compiles src/xamarin/*.cpp and links the dylib against libskia.a via -lskia. The macOS linker pulls object files from libskia.a lazily: only those that resolve undefined symbol references in the linkage roots get included. SkiaKeeper.c is the existing anchor file -- it holds one void* reference per C-API module so the linker keeps the whole object file alive in the final dylib. The new sk_graphite_*.cpp shim files weren't on the anchor list, so libskia.a contained them but the dylib didn't export their symbols. Add one anchor per shim cpp. The per-backend cpps have stub bodies in their !SK_GRAPHITE / !SK_VULKAN / !SK_METAL / !SK_DAWN branches, so anchoring is safe when those backends are disabled at build time. --- src/xamarin/SkiaKeeper.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/xamarin/SkiaKeeper.c b/src/xamarin/SkiaKeeper.c index f90ac430c6f6..91c047e851ce 100644 --- a/src/xamarin/SkiaKeeper.c +++ b/src/xamarin/SkiaKeeper.c @@ -50,6 +50,16 @@ #include "include/c/sksg_invalidation_controller.h" #include "include/c/skresources_resource_provider.h" +// Graphite — guarded so non-Graphite builds don't need the headers, +// but each shim cpp keeps a no-op !SK_GRAPHITE branch so anchoring +// the symbol is safe regardless. macOS in particular needs this +// because :skia is linked via `-lskia` and the linker drops object +// files whose externs aren't referenced by something in the dylib. +#include "include/c/sk_graphite.h" +#include "include/c/sk_graphite_dawn.h" +#include "include/c/sk_graphite_metal.h" +#include "include/c/sk_graphite_vulkan.h" + // Xamarin #include "include/xamarin/sk_managedstream.h" @@ -104,6 +114,16 @@ void** KeepSkiaCSymbols (void) (void*)sksg_invalidation_controller_new, (void*)skresources_resource_provider_ref, + // Graphite — one symbol per shim cpp, so the linker pulls in the + // whole object file (and its sibling symbols) when libskia.a is + // consumed via -lskia. The per-backend cpps export stubs in their + // !SK_GRAPHITE / !SK_BACKEND branches, so anchoring is safe even + // when a backend is disabled. + (void*)sk_graphite_backend_is_available, + (void*)sk_graphite_dawn_backend_context_new, + (void*)sk_graphite_mtl_backend_context_new, + (void*)sk_graphite_vk_backend_context_new, + // Xamarin (void*)sk_compatpaint_new_with_font, (void*)sk_managedstream_new, From bf5e889cb99ad254e9ad9e77f30271fc6dd60da7 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Mon, 11 May 2026 04:56:02 +0000 Subject: [PATCH 03/14] mono/skia: Graphite-on-WebGPU patches for Emscripten Threads Skia's Graphite/Dawn backend through Emscripten's bundled WebGPU shim (-sUSE_WEBGPU=1) so it links cleanly into libSkiaSharp.a under is_canvaskit=true. Five surgical edits: * include/c/sk_graphite_dawn.h + src/c/sk_graphite_dawn.cpp -- new sk_graphite_dawn_backend_texture_new C entry point that wraps a caller-supplied WGPUTexture (e.g. canvas.getContext('webgpu') .getCurrentTexture()) via BackendTextures::MakeDawn. The wrapper does not retain or release the WGPUTexture; any SkSurface/SkImage that wraps it will retain for its own lifetime. * src/gpu/graphite/dawn/DawnCaps.cpp -- guard YCbCr/biplanar texture formats (R8BG8Biplanar420Unorm, R10X6BG10X6Biplanar420Unorm, R8BG8A8Triplanar420Unorm, OpaqueYCbCrAndroid, DualSourceBlending, TextureFormatsTier1) under #if !defined(__EMSCRIPTEN__). These wgpu::TextureFormat enum values do not exist in Emscripten's bundled webgpu_cpp.h and would fail to compile. * src/gpu/graphite/dawn/DawnBuffer.cpp -- replace SKGPU_LOG(...) macro with explicit SKGPU_LOG_D / SKGPU_LOG_E; Emscripten's preprocessor rejects the macro form in this position. * third_party/dawn/BUILD.gn -- skip the native Dawn CMake build AND the libdawn_combined.a link step when is_canvaskit=true. On Emscripten the wgpu_* symbols are provided by -sUSE_WEBGPU=1 at the consumer's final link step; building native Dawn for WASM is both unnecessary and breaks (the Dawn CMake build assumes desktop toolchains). Build prerequisite (out-of-tree): externals/dawn requires one extra #include in third_party/externals/dawn/include/tint/tint.h -- 'src/tint/api/common/bindings.h'. Dawn isn't a git submodule of skia (it is checked out via DEPS), so we cannot land that patch here. Apply it separately if a fresh DEPS sync is needed and Dawn pre-m146. Co-Authored-By: Claude Opus 4.7 (1M context) --- include/c/sk_graphite_dawn.h | 12 ++++++++++++ src/c/sk_graphite_dawn.cpp | 10 ++++++++++ src/gpu/graphite/dawn/DawnBuffer.cpp | 6 +++++- src/gpu/graphite/dawn/DawnCaps.cpp | 12 +++++++++++- third_party/dawn/BUILD.gn | 13 +++++++++++-- 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/include/c/sk_graphite_dawn.h b/include/c/sk_graphite_dawn.h index 4cab2339be20..72df10175ee5 100644 --- a/include/c/sk_graphite_dawn.h +++ b/include/c/sk_graphite_dawn.h @@ -38,6 +38,18 @@ SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( const sk_graphite_dawn_backend_context_t* bc, const sk_graphite_context_options_t* opts /* nullable -> defaults */); +// Wrap an externally-allocated WGPUTexture as a Graphite BackendTexture. The +// shim queries width/height/format/usage directly from the texture, so this +// is the path for WebGPU swap-chain textures (canvas.getContext('webgpu') +// .getCurrentTexture()). +// +// Lifetime: the BackendTexture does NOT retain or release the WGPUTexture — +// caller keeps it alive for the wrapper's lifetime. Any SkSurface/SkImage that +// wraps this BackendTexture *will* retain it for its own lifetime, so caller +// can drop their reference once the Surface is built. +SK_C_API sk_graphite_backend_texture_t* sk_graphite_dawn_backend_texture_new( + void* wgpuTexture); // WGPUTexture + SK_C_PLUS_PLUS_END_GUARD #endif // sk_graphite_dawn_DEFINED diff --git a/src/c/sk_graphite_dawn.cpp b/src/c/sk_graphite_dawn.cpp index 1b6a59af221e..3585283a1175 100644 --- a/src/c/sk_graphite_dawn.cpp +++ b/src/c/sk_graphite_dawn.cpp @@ -11,9 +11,11 @@ #if defined(SK_GRAPHITE) && defined(SK_DAWN) +#include "include/gpu/graphite/BackendTexture.h" #include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/ContextOptions.h" #include "include/gpu/graphite/dawn/DawnBackendContext.h" +#include "include/gpu/graphite/dawn/DawnGraphiteTypes.h" #include "src/c/sk_types_priv.h" @@ -61,10 +63,18 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( return reinterpret_cast(context.release()); } +extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_dawn_backend_texture_new(void* wgpuTexture) { + if (!wgpuTexture) return nullptr; + auto bt = gr::BackendTextures::MakeDawn(static_cast(wgpuTexture)); + if (!bt.isValid()) return nullptr; + return reinterpret_cast(new gr::BackendTexture(bt)); +} + #else // !(SK_GRAPHITE && SK_DAWN) extern "C" SK_C_API sk_graphite_dawn_backend_context_t* sk_graphite_dawn_backend_context_new(const sk_graphite_dawn_backend_context_init_t*) { return nullptr; } extern "C" SK_C_API void sk_graphite_dawn_backend_context_delete(sk_graphite_dawn_backend_context_t*) {} extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn(const sk_graphite_dawn_backend_context_t*, const sk_graphite_context_options_t*) { return nullptr; } +extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_dawn_backend_texture_new(void*) { return nullptr; } #endif // SK_GRAPHITE && SK_DAWN diff --git a/src/gpu/graphite/dawn/DawnBuffer.cpp b/src/gpu/graphite/dawn/DawnBuffer.cpp index e39052346cf1..db811d70029e 100644 --- a/src/gpu/graphite/dawn/DawnBuffer.cpp +++ b/src/gpu/graphite/dawn/DawnBuffer.cpp @@ -55,7 +55,11 @@ void log_map_error(WGPUBufferMapAsyncStatus status, const char*) { statusStr = ""; break; } - SKGPU_LOG(priority, "Buffer async map failed with status %s.", statusStr); + if (priority == SkLogPriority::kDebug) { + SKGPU_LOG_D("Buffer async map failed with status %s.", statusStr); + } else { + SKGPU_LOG_E("Buffer async map failed with status %s.", statusStr); + } } #else diff --git a/src/gpu/graphite/dawn/DawnCaps.cpp b/src/gpu/graphite/dawn/DawnCaps.cpp index 2bc9d757df8a..90da3a9ae909 100644 --- a/src/gpu/graphite/dawn/DawnCaps.cpp +++ b/src/gpu/graphite/dawn/DawnCaps.cpp @@ -82,10 +82,10 @@ static constexpr wgpu::TextureFormat kFormats[] = { wgpu::TextureFormat::ETC2RGBA8Unorm, wgpu::TextureFormat::ETC2RGBA8UnormSrgb, +#if !defined(__EMSCRIPTEN__) wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm, -#if !defined(__EMSCRIPTEN__) wgpu::TextureFormat::OpaqueYCbCrAndroid, #endif @@ -501,9 +501,13 @@ void DawnCaps::initShaderCaps(const wgpu::Device& device) { // WGSL supports shader derivatives in the fragment shader shaderCaps->fShaderDerivativeSupport = true; +#if !defined(__EMSCRIPTEN__) + // DualSourceBlending is a Dawn-extension feature not exposed by Emscripten's + // bundled webgpu.h; gate it the same way as FramebufferFetch below. if (device.HasFeature(wgpu::FeatureName::DualSourceBlending)) { shaderCaps->fDualSourceBlendingSupport = true; } +#endif #if !defined(__EMSCRIPTEN__) if (device.HasFeature(wgpu::FeatureName::FramebufferFetch)) { shaderCaps->fFBFetchSupport = true; @@ -550,9 +554,15 @@ void DawnCaps::initFormatTable(const wgpu::Device& device) { { info = &fFormatTable[GetFormatIndex(wgpu::TextureFormat::R8Unorm)]; info->fFlags = FormatInfo::kAllFlags; +#if defined(__EMSCRIPTEN__) + // TextureFormatsTier1 is a Dawn-extension feature not exposed by + // Emscripten's bundled webgpu.h. Conservatively drop the storage flag. + info->fFlags &= ~FormatInfo::kStorage_Flag; +#else if (!device.HasFeature(wgpu::FeatureName::TextureFormatsTier1)) { info->fFlags &= ~FormatInfo::kStorage_Flag; } +#endif info->fColorTypeInfoCount = 3; info->fColorTypeInfos = std::make_unique(info->fColorTypeInfoCount); int ctIdx = 0; diff --git a/third_party/dawn/BUILD.gn b/third_party/dawn/BUILD.gn index 128fac065075..b2dcfcf68627 100644 --- a/third_party/dawn/BUILD.gn +++ b/third_party/dawn/BUILD.gn @@ -154,7 +154,12 @@ config("dawn_api_config") { } else { libs = [] } - libs += [ "$root_out_dir/$_dawn_lib_name" ] + # On WASM/canvaskit builds we let Emscripten's bundled webgpu.h resolve the + # wgpu_* symbols via -sUSE_WEBGPU=1 at the final link. There's no + # libdawn_combined.a produced (see below), so don't reference it as a lib. + if (!is_canvaskit) { + libs += [ "$root_out_dir/$_dawn_lib_name" ] + } # https://dawn.googlesource.com/dawn/+/647c352f57aa8a4c2666258455158a36c0c385af/src/dawn/native/BUILD.gn#645 if (is_mac || is_ios) { @@ -291,7 +296,11 @@ action("dawn_cmake") { } group("dawn") { - public_deps = [ ":dawn_cmake" ] + if (!is_canvaskit) { + # canvaskit (incl. our WASM-Graphite build) does NOT build native Dawn + # because Emscripten's webgpu_cpp.h + -sUSE_WEBGPU=1 provide the wgpu API. + public_deps = [ ":dawn_cmake" ] + } public_configs = [ ":dawn_api_config" ] } From 70150e1f13236f5c359a8ea07f77377a3c9f204a Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Wed, 13 May 2026 15:14:56 +0000 Subject: [PATCH 04/14] mono/skia: use DEF_MAP_WITH_NS for sk_graphite_* cast helpers The five Graphite C-API types (Context, Recorder, Recording, BackendTexture, TextureInfo) were hand-rolled with three or four overload-incomplete static-inline reinterpret_cast helpers per type in sk_graphite.cpp. Move them into sk_types_priv.h under the same DEF_MAP_WITH_NS pattern that already handles GrDirectContext / VulkanYcbcrConversionInfo / etc. Each invocation generates 8 const/non-const ref+ptr As/To overloads, gated by SK_GRAPHITE so non-Graphite builds skip both the includes and the helpers. The per-backend cpps (sk_graphite_vulkan/metal/dawn) now use the generated ToGraphiteContext / ToGraphiteBackendTexture / ToGraphiteTextureInfo instead of raw reinterpret_cast. Net: -10 source lines, consistent convention with the rest of the C-API map declarations. --- src/c/sk_graphite.cpp | 28 +++------------------------- src/c/sk_graphite_dawn.cpp | 8 ++++---- src/c/sk_graphite_metal.cpp | 8 ++++---- src/c/sk_graphite_vulkan.cpp | 11 +++++------ src/c/sk_types_priv.h | 13 +++++++++++++ 5 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/c/sk_graphite.cpp b/src/c/sk_graphite.cpp index 82cafe7da0ff..8ee9425afd46 100644 --- a/src/c/sk_graphite.cpp +++ b/src/c/sk_graphite.cpp @@ -13,17 +13,15 @@ #include "include/core/SkImage.h" #include "include/core/SkSurface.h" -#include "include/gpu/graphite/BackendTexture.h" -#include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/ContextOptions.h" #include "include/gpu/graphite/GraphiteTypes.h" #include "include/gpu/graphite/Image.h" #include "include/gpu/graphite/ImageProvider.h" -#include "include/gpu/graphite/Recorder.h" -#include "include/gpu/graphite/Recording.h" #include "include/gpu/graphite/Surface.h" -#include "include/gpu/graphite/TextureInfo.h" +// Pulls in the Context/Recorder/Recording/BackendTexture/TextureInfo headers +// and the matching As/To helpers via the DEF_MAP_WITH_NS block guarded by +// SK_GRAPHITE in sk_types_priv.h. #include "src/c/sk_types_priv.h" #include @@ -32,26 +30,6 @@ namespace gr = skgpu::graphite; -// reinterpret_cast helpers between opaque C handles and C++ types -static inline gr::Context* AsGraphiteContext(sk_graphite_context_t* h) { return reinterpret_cast(h); } -static inline const gr::Context* AsGraphiteContext(const sk_graphite_context_t* h) { return reinterpret_cast(h); } -static inline sk_graphite_context_t* ToGraphiteContext(gr::Context* p) { return reinterpret_cast(p); } - -static inline gr::Recorder* AsGraphiteRecorder(sk_graphite_recorder_t* h) { return reinterpret_cast(h); } -static inline const gr::Recorder* AsGraphiteRecorder(const sk_graphite_recorder_t* h) { return reinterpret_cast(h); } -static inline sk_graphite_recorder_t* ToGraphiteRecorder(gr::Recorder* p) { return reinterpret_cast(p); } - -static inline gr::Recording* AsGraphiteRecording(sk_graphite_recording_t* h) { return reinterpret_cast(h); } -static inline sk_graphite_recording_t* ToGraphiteRecording(gr::Recording* p) { return reinterpret_cast(p); } - -static inline gr::BackendTexture* AsGraphiteBackendTexture(sk_graphite_backend_texture_t* h) { return reinterpret_cast(h); } -static inline const gr::BackendTexture* AsGraphiteBackendTexture(const sk_graphite_backend_texture_t* h) { return reinterpret_cast(h); } -static inline sk_graphite_backend_texture_t* ToGraphiteBackendTexture(gr::BackendTexture* p) { return reinterpret_cast(p); } - -static inline gr::TextureInfo* AsGraphiteTextureInfo(sk_graphite_texture_info_t* h) { return reinterpret_cast(h); } -static inline const gr::TextureInfo* AsGraphiteTextureInfo(const sk_graphite_texture_info_t* h) { return reinterpret_cast(h); } -static inline sk_graphite_texture_info_t* ToGraphiteTextureInfo(gr::TextureInfo* p) { return reinterpret_cast(p); } - // Backend availability — runtime-queried, per-backend compile-time gated. extern "C" SK_C_API bool sk_graphite_backend_is_available(sk_graphite_backend_t backend) { switch (backend) { diff --git a/src/c/sk_graphite_dawn.cpp b/src/c/sk_graphite_dawn.cpp index 3585283a1175..9a5c52c026af 100644 --- a/src/c/sk_graphite_dawn.cpp +++ b/src/c/sk_graphite_dawn.cpp @@ -11,12 +11,12 @@ #if defined(SK_GRAPHITE) && defined(SK_DAWN) -#include "include/gpu/graphite/BackendTexture.h" -#include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/ContextOptions.h" #include "include/gpu/graphite/dawn/DawnBackendContext.h" #include "include/gpu/graphite/dawn/DawnGraphiteTypes.h" +// Pulls in Context/BackendTexture + the matching As/To helpers via the +// SK_GRAPHITE block. #include "src/c/sk_types_priv.h" #include @@ -60,14 +60,14 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( { if (!bc) return nullptr; auto context = gr::ContextFactory::MakeDawn(bc->dbc, sk_graphite_make_context_options(opts)); - return reinterpret_cast(context.release()); + return ToGraphiteContext(context.release()); } extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_dawn_backend_texture_new(void* wgpuTexture) { if (!wgpuTexture) return nullptr; auto bt = gr::BackendTextures::MakeDawn(static_cast(wgpuTexture)); if (!bt.isValid()) return nullptr; - return reinterpret_cast(new gr::BackendTexture(bt)); + return ToGraphiteBackendTexture(new gr::BackendTexture(bt)); } #else // !(SK_GRAPHITE && SK_DAWN) diff --git a/src/c/sk_graphite_metal.cpp b/src/c/sk_graphite_metal.cpp index 78b7b6a8d0d5..bec779556bd9 100644 --- a/src/c/sk_graphite_metal.cpp +++ b/src/c/sk_graphite_metal.cpp @@ -11,13 +11,13 @@ #if defined(SK_GRAPHITE) && defined(SK_METAL) -#include "include/gpu/graphite/BackendTexture.h" -#include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/ContextOptions.h" #include "include/gpu/graphite/mtl/MtlBackendContext.h" #include "include/gpu/graphite/mtl/MtlGraphiteTypes_cpp.h" #include "include/ports/SkCFObject.h" +// Pulls in Context/BackendTexture + the matching As/To helpers via the +// SK_GRAPHITE block. #include "src/c/sk_types_priv.h" #include @@ -52,7 +52,7 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal( { if (!bc) return nullptr; auto context = gr::ContextFactory::MakeMetal(bc->mbc, sk_graphite_make_context_options(opts)); - return reinterpret_cast(context.release()); + return ToGraphiteContext(context.release()); } extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_mtl_backend_texture_new( @@ -62,7 +62,7 @@ extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_mtl_backend_textu auto bt = gr::BackendTextures::MakeMetal(SkISize::Make(width, height), static_cast(mtlTexture)); auto* heap = new gr::BackendTexture(bt); - return reinterpret_cast(heap); + return ToGraphiteBackendTexture(heap); } #else // !(SK_GRAPHITE && SK_METAL) diff --git a/src/c/sk_graphite_vulkan.cpp b/src/c/sk_graphite_vulkan.cpp index 998d85a918f4..50201fb16664 100644 --- a/src/c/sk_graphite_vulkan.cpp +++ b/src/c/sk_graphite_vulkan.cpp @@ -12,15 +12,14 @@ #if defined(SK_GRAPHITE) && defined(SK_VULKAN) #include "include/gpu/GpuTypes.h" -#include "include/gpu/graphite/BackendTexture.h" -#include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/ContextOptions.h" -#include "include/gpu/graphite/TextureInfo.h" #include "include/gpu/graphite/vk/VulkanGraphiteContext.h" #include "include/gpu/graphite/vk/VulkanGraphiteTypes.h" #include "include/gpu/vk/VulkanBackendContext.h" #include "include/gpu/vk/VulkanMemoryAllocator.h" +// Pulls in Context/BackendTexture/TextureInfo + the matching As/To helpers +// via the SK_GRAPHITE block. #include "src/c/sk_types_priv.h" #include "src/gpu/GpuTypesPriv.h" #include "src/gpu/vk/vulkanmemoryallocator/VulkanMemoryAllocatorPriv.h" @@ -86,7 +85,7 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( } auto context = gr::ContextFactory::MakeVulkan(vkbc, sk_graphite_make_context_options(opts)); - return reinterpret_cast(context.release()); + return ToGraphiteContext(context.release()); } // Vulkan TextureInfo + BackendTexture factories. @@ -111,7 +110,7 @@ gr::VulkanTextureInfo MakeNativeVkTextureInfo(const sk_graphite_vk_texture_info_ extern "C" SK_C_API sk_graphite_texture_info_t* sk_graphite_vk_texture_info_new(const sk_graphite_vk_texture_info_t* info) { if (!info) return nullptr; auto* ti = new gr::TextureInfo(gr::TextureInfos::MakeVulkan(MakeNativeVkTextureInfo(*info))); - return reinterpret_cast(ti); + return ToGraphiteTextureInfo(ti); } extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_vk_backend_texture_new( @@ -131,7 +130,7 @@ extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_vk_backend_textur reinterpret_cast(vkImage), skgpu::VulkanAlloc{}); auto* heap = new gr::BackendTexture(bt); - return reinterpret_cast(heap); + return ToGraphiteBackendTexture(heap); } #else // !(SK_GRAPHITE && SK_VULKAN) diff --git a/src/c/sk_types_priv.h b/src/c/sk_types_priv.h index 5126becaceba..6e44a06bc744 100644 --- a/src/c/sk_types_priv.h +++ b/src/c/sk_types_priv.h @@ -197,6 +197,19 @@ DEF_STRUCT_MAP(GrD3DTextureResourceInfo, gr_d3d_textureresourceinfo_t, GrD3DText #endif // SK_DIRECT3D #endif // SK_GANESH +#if defined(SK_GRAPHITE) +#include "include/gpu/graphite/BackendTexture.h" +#include "include/gpu/graphite/Context.h" +#include "include/gpu/graphite/Recorder.h" +#include "include/gpu/graphite/Recording.h" +#include "include/gpu/graphite/TextureInfo.h" +DEF_MAP_WITH_NS(skgpu::graphite, Context, sk_graphite_context_t, GraphiteContext) +DEF_MAP_WITH_NS(skgpu::graphite, Recorder, sk_graphite_recorder_t, GraphiteRecorder) +DEF_MAP_WITH_NS(skgpu::graphite, Recording, sk_graphite_recording_t, GraphiteRecording) +DEF_MAP_WITH_NS(skgpu::graphite, BackendTexture, sk_graphite_backend_texture_t, GraphiteBackendTexture) +DEF_MAP_WITH_NS(skgpu::graphite, TextureInfo, sk_graphite_texture_info_t, GraphiteTextureInfo) +#endif // SK_GRAPHITE + #include "include/effects/SkRuntimeEffect.h" DEF_MAP(SkRuntimeEffect::Uniform, sk_runtimeeffect_uniform_t, RuntimeEffectUniform) DEF_MAP(SkRuntimeEffect::Child, sk_runtimeeffect_child_t, RuntimeEffectChild) From 49deb73cb00549900f7611528656c7caf5618a2e Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Wed, 13 May 2026 15:15:04 +0000 Subject: [PATCH 05/14] mono/skia: clarify DawnBuffer SKGPU_LOG fix rationale Replace the misleading comment ("SKGPU_LOG unavailable on Emscripten") with the actual reason: SKGPU_LOG expands via SKIA_LOG to `if constexpr (priority <= ...)`, which requires a compile-time constant. The call site passes a runtime variable set by the switch above, so the original code fails to compile. Only surfaces in WASM because the function is gated by __EMSCRIPTEN__; upstream doesn't build Graphite-on-WASM, so the bug sits there without anyone noticing. --- src/gpu/graphite/dawn/DawnBuffer.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gpu/graphite/dawn/DawnBuffer.cpp b/src/gpu/graphite/dawn/DawnBuffer.cpp index db811d70029e..86a870c12232 100644 --- a/src/gpu/graphite/dawn/DawnBuffer.cpp +++ b/src/gpu/graphite/dawn/DawnBuffer.cpp @@ -55,6 +55,15 @@ void log_map_error(WGPUBufferMapAsyncStatus status, const char*) { statusStr = ""; break; } + // mono/skia: SKGPU_LOG(priority, ...) expands (via SKIA_LOG) to + // `if constexpr (priority <= ...)`, which requires `priority` to be a + // compile-time constant. The runtime variable set by the switch above + // fails that constraint, so the original `SKGPU_LOG(priority, ...)` + // call doesn't compile. Branch on the runtime value and dispatch to + // the fixed-level macros (SKGPU_LOG_D / _E), each of which has a + // constant priority baked in. Only surfaces in WASM because this whole + // function is gated by __EMSCRIPTEN__; upstream doesn't build + // Graphite-on-WASM, so the bug never trips there. if (priority == SkLogPriority::kDebug) { SKGPU_LOG_D("Buffer async map failed with status %s.", statusStr); } else { From d0193ae31bc2ef06c362a1d5f9b12d4ba8cd1224 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Wed, 13 May 2026 15:15:16 +0000 Subject: [PATCH 06/14] mono/skia: tidy marker placement on local in-tree edits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * DawnCaps.cpp + third_party/dawn/BUILD.gn — hoist explanatory comments above the preprocessor gate and prefix each with "mono/skia:" so grep surfaces every local upstream patch. * SkiaKeeper.c — drop the "mono/skia:" prefix from the Graphite comments. The whole src/xamarin/ directory is mono-only territory, so the marker adds nothing. --- src/gpu/graphite/dawn/DawnCaps.cpp | 11 +++++++---- src/xamarin/SkiaKeeper.c | 10 +++++----- third_party/dawn/BUILD.gn | 10 +++++----- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/gpu/graphite/dawn/DawnCaps.cpp b/src/gpu/graphite/dawn/DawnCaps.cpp index 90da3a9ae909..64dff6f10c0d 100644 --- a/src/gpu/graphite/dawn/DawnCaps.cpp +++ b/src/gpu/graphite/dawn/DawnCaps.cpp @@ -82,6 +82,9 @@ static constexpr wgpu::TextureFormat kFormats[] = { wgpu::TextureFormat::ETC2RGBA8Unorm, wgpu::TextureFormat::ETC2RGBA8UnormSrgb, +// mono/skia: R8BG8Biplanar420Unorm + R10X6BG10X6Biplanar420Unorm are +// Dawn-extension formats not exposed by Emscripten's bundled webgpu.h; +// widen the existing __EMSCRIPTEN__ gate to cover them too. #if !defined(__EMSCRIPTEN__) wgpu::TextureFormat::R8BG8Biplanar420Unorm, wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm, @@ -501,9 +504,9 @@ void DawnCaps::initShaderCaps(const wgpu::Device& device) { // WGSL supports shader derivatives in the fragment shader shaderCaps->fShaderDerivativeSupport = true; +// mono/skia: DualSourceBlending is a Dawn-extension feature not exposed by +// Emscripten's bundled webgpu.h; gate it the same way as FramebufferFetch below. #if !defined(__EMSCRIPTEN__) - // DualSourceBlending is a Dawn-extension feature not exposed by Emscripten's - // bundled webgpu.h; gate it the same way as FramebufferFetch below. if (device.HasFeature(wgpu::FeatureName::DualSourceBlending)) { shaderCaps->fDualSourceBlendingSupport = true; } @@ -554,9 +557,9 @@ void DawnCaps::initFormatTable(const wgpu::Device& device) { { info = &fFormatTable[GetFormatIndex(wgpu::TextureFormat::R8Unorm)]; info->fFlags = FormatInfo::kAllFlags; +// mono/skia: TextureFormatsTier1 is a Dawn-extension feature not exposed by +// Emscripten's bundled webgpu.h. Conservatively drop the storage flag on WASM. #if defined(__EMSCRIPTEN__) - // TextureFormatsTier1 is a Dawn-extension feature not exposed by - // Emscripten's bundled webgpu.h. Conservatively drop the storage flag. info->fFlags &= ~FormatInfo::kStorage_Flag; #else if (!device.HasFeature(wgpu::FeatureName::TextureFormatsTier1)) { diff --git a/src/xamarin/SkiaKeeper.c b/src/xamarin/SkiaKeeper.c index 91c047e851ce..e88e991a7291 100644 --- a/src/xamarin/SkiaKeeper.c +++ b/src/xamarin/SkiaKeeper.c @@ -50,11 +50,11 @@ #include "include/c/sksg_invalidation_controller.h" #include "include/c/skresources_resource_provider.h" -// Graphite — guarded so non-Graphite builds don't need the headers, -// but each shim cpp keeps a no-op !SK_GRAPHITE branch so anchoring -// the symbol is safe regardless. macOS in particular needs this -// because :skia is linked via `-lskia` and the linker drops object -// files whose externs aren't referenced by something in the dylib. +// Graphite. Each shim cpp keeps a no-op !SK_GRAPHITE branch so anchoring +// the symbol is safe regardless of which backend is enabled. macOS in +// particular needs this because :skia is linked via `-lskia` and the +// linker drops object files whose externs aren't referenced by something +// in the dylib. #include "include/c/sk_graphite.h" #include "include/c/sk_graphite_dawn.h" #include "include/c/sk_graphite_metal.h" diff --git a/third_party/dawn/BUILD.gn b/third_party/dawn/BUILD.gn index b2dcfcf68627..793e7980bd4b 100644 --- a/third_party/dawn/BUILD.gn +++ b/third_party/dawn/BUILD.gn @@ -154,9 +154,9 @@ config("dawn_api_config") { } else { libs = [] } - # On WASM/canvaskit builds we let Emscripten's bundled webgpu.h resolve the - # wgpu_* symbols via -sUSE_WEBGPU=1 at the final link. There's no - # libdawn_combined.a produced (see below), so don't reference it as a lib. + # mono/skia: on WASM/canvaskit builds we let Emscripten's bundled webgpu.h + # resolve the wgpu_* symbols via -sUSE_WEBGPU=1 at the final link. There's + # no libdawn_combined.a produced (see below), so don't reference it as a lib. if (!is_canvaskit) { libs += [ "$root_out_dir/$_dawn_lib_name" ] } @@ -296,9 +296,9 @@ action("dawn_cmake") { } group("dawn") { + # mono/skia: canvaskit (incl. our WASM-Graphite build) does NOT build native + # Dawn because Emscripten's webgpu_cpp.h + -sUSE_WEBGPU=1 provide the wgpu API. if (!is_canvaskit) { - # canvaskit (incl. our WASM-Graphite build) does NOT build native Dawn - # because Emscripten's webgpu_cpp.h + -sUSE_WEBGPU=1 provide the wgpu API. public_deps = [ ":dawn_cmake" ] } From 0bf2e34efcdf725189e9bc059990c22686f26d38 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Wed, 13 May 2026 15:26:35 +0000 Subject: [PATCH 07/14] mono/skia: drop the redundant SkiaKeeper Graphite comment The previous block explained what the anchors are doing, but the file-level convention (one anchor per .cpp to keep the linker from pruning archive members) already covers that. The Graphite includes and four anchors below speak for themselves. --- src/xamarin/SkiaKeeper.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/xamarin/SkiaKeeper.c b/src/xamarin/SkiaKeeper.c index e88e991a7291..ee97836f1477 100644 --- a/src/xamarin/SkiaKeeper.c +++ b/src/xamarin/SkiaKeeper.c @@ -50,11 +50,6 @@ #include "include/c/sksg_invalidation_controller.h" #include "include/c/skresources_resource_provider.h" -// Graphite. Each shim cpp keeps a no-op !SK_GRAPHITE branch so anchoring -// the symbol is safe regardless of which backend is enabled. macOS in -// particular needs this because :skia is linked via `-lskia` and the -// linker drops object files whose externs aren't referenced by something -// in the dylib. #include "include/c/sk_graphite.h" #include "include/c/sk_graphite_dawn.h" #include "include/c/sk_graphite_metal.h" From 2f9fc47b667703144a6803de071137d5b253c695 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Wed, 13 May 2026 16:41:30 +0000 Subject: [PATCH 08/14] mono/skia: tighten enum mappings in the Graphite C shim Three related fixes to remove silent fallback returns from the sk_graphite_* C shim: * Closed-enum exhaustiveness. ToInsertStatus and the sampleCount() getter on sk_graphite_texture_info handled every Skia value but still ended in a `return X;` fallback. Drop the unreachable default values; end the switches with SkUNREACHABLE so the compiler can enforce exhaustiveness via -Wswitch-enum and any future Skia milestone that grows the enum produces a compile error here instead of a runtime mis-mapping. * Open-enum honesty for BackendApi. skgpu::BackendApi has kMock and kUnsupported values we don't expose on the public C ABI. The four *_get_backend functions used to silently map them to kVulkan, which is a lie. Add UNKNOWN_SK_GRAPHITE_BACKEND = -1 to the C enum, list kMock/kUnsupported explicitly, drop the `default:` blanket case, and end with SkUNREACHABLE. Null-handle early returns also use UNKNOWN now rather than guessing Vulkan. * Bool-returning sample-count validator. ToGraphiteSampleCount used to silently clamp invalid input to k1, which means the caller had no way to know they had passed a bad value. Switch to a bool-returning out-parameter form and propagate the failure up through sk_graphite_make_context_options (also bool-returning out param) and the per-backend ContextFactory factories (sk_graphite_context_make_{vulkan,metal,dawn}), which now return nullptr when the C options struct is invalid. The managed binding validates options up front and throws ArgumentException with a clear message; the null path is a defense-in-depth backstop. --- include/c/sk_graphite.h | 14 +++-- src/c/sk_graphite.cpp | 112 +++++++++++++++++++++-------------- src/c/sk_graphite_dawn.cpp | 7 ++- src/c/sk_graphite_metal.cpp | 9 ++- src/c/sk_graphite_vulkan.cpp | 9 ++- 5 files changed, 94 insertions(+), 57 deletions(-) diff --git a/include/c/sk_graphite.h b/include/c/sk_graphite.h index 19d8a07e2ba2..cb049e862552 100644 --- a/include/c/sk_graphite.h +++ b/include/c/sk_graphite.h @@ -24,9 +24,15 @@ typedef struct sk_graphite_image_provider_t sk_graphite_image_provider_t; // Backend identification typedef enum { - DAWN_SK_GRAPHITE_BACKEND = 0, - METAL_SK_GRAPHITE_BACKEND = 1, - VULKAN_SK_GRAPHITE_BACKEND = 2, + DAWN_SK_GRAPHITE_BACKEND = 0, + METAL_SK_GRAPHITE_BACKEND = 1, + VULKAN_SK_GRAPHITE_BACKEND = 2, + // skgpu::BackendApi has values we don't expose on the public C ABI + // (kMock, kUnsupported). Returned by *_get_backend when the underlying + // Skia object holds one of those — callers shouldn't normally see this + // because the per-backend factories only produce kVulkan/kMetal/kDawn, + // but it exists for forward-compat with future Skia milestones. + UNKNOWN_SK_GRAPHITE_BACKEND = -1, } sk_graphite_backend_t; // Returns true if the requested backend was compiled into this build of @@ -37,7 +43,7 @@ SK_C_API bool sk_graphite_backend_is_available(sk_graphite_backend_t backend); typedef struct { bool fDisableDriverCorrectnessWorkarounds; - int32_t fInternalMultisampleCount; // valid: 1, 2, 4, 8, 16 + int32_t fInternalMultisampleCount; // valid: 0 (use Skia default), 1, 2, 4, 8, 16 int64_t fGpuBudgetInBytes; // -1 to use Skia's default bool fRequireOrderedRecordings; bool fSetBackendLabels; diff --git a/src/c/sk_graphite.cpp b/src/c/sk_graphite.cpp index 8ee9425afd46..47e3de80996a 100644 --- a/src/c/sk_graphite.cpp +++ b/src/c/sk_graphite.cpp @@ -68,15 +68,18 @@ extern "C" SK_C_API void sk_graphite_context_options_init_defaults(sk_graphite_c } // Translate the public sample-count integer to the SampleCount enum. -static gr::SampleCount ToGraphiteSampleCount(int32_t count) { +// Returns true on success; *out is left unchanged on failure so the caller +// can supply a default or surface the validation error. Used by +// sk_graphite_make_context_options below, which propagates the bool up. +static bool ToGraphiteSampleCount(int32_t count, gr::SampleCount* out) { switch (count) { - case 1: return gr::SampleCount::k1; - case 2: return gr::SampleCount::k2; - case 4: return gr::SampleCount::k4; - case 8: return gr::SampleCount::k8; - case 16: return gr::SampleCount::k16; + case 1: *out = gr::SampleCount::k1; return true; + case 2: *out = gr::SampleCount::k2; return true; + case 4: *out = gr::SampleCount::k4; return true; + case 8: *out = gr::SampleCount::k8; return true; + case 16: *out = gr::SampleCount::k16; return true; } - return gr::SampleCount::k1; + return false; } // ImageProvider bridge — routes Graphite's "I have a non-Graphite SkImage, @@ -145,18 +148,29 @@ extern "C" SK_C_API sk_image_t* sk_graphite_image_make_texture( } // Public helper used by per-backend factories in sibling translation units. -gr::ContextOptions sk_graphite_make_context_options(const sk_graphite_context_options_t* opts) { - gr::ContextOptions out; - if (opts) { - out.fDisableDriverCorrectnessWorkarounds = opts->fDisableDriverCorrectnessWorkarounds; - out.fInternalMultisampleCount = ToGraphiteSampleCount(opts->fInternalMultisampleCount); - if (opts->fGpuBudgetInBytes >= 0) { - out.fGpuBudgetInBytes = static_cast(opts->fGpuBudgetInBytes); - } - out.fRequireOrderedRecordings = opts->fRequireOrderedRecordings; - out.fSetBackendLabels = opts->fSetBackendLabels; +// Translate the C-ABI options struct to a Skia ContextOptions. Returns false +// if any field carries an invalid value (currently: only fInternalMultisampleCount +// being something other than 1/2/4/8/16). On failure *out is left in a +// well-defined "Skia defaults" state and the per-backend factory should bail +// with nullptr so the managed caller can surface ArgumentException. +bool sk_graphite_make_context_options(const sk_graphite_context_options_t* opts, gr::ContextOptions* out) { + if (!out) return false; + *out = gr::ContextOptions{}; + if (!opts) return true; // null = use Skia defaults + // fInternalMultisampleCount: 0 means "leave Skia's default in place", same + // shape as the fGpuBudgetInBytes < 0 sentinel below. Lets a caller pass + // default-constructed C-side opts without tripping the validator. + if (opts->fInternalMultisampleCount != 0 && + !ToGraphiteSampleCount(opts->fInternalMultisampleCount, &out->fInternalMultisampleCount)) { + return false; + } + out->fDisableDriverCorrectnessWorkarounds = opts->fDisableDriverCorrectnessWorkarounds; + if (opts->fGpuBudgetInBytes >= 0) { + out->fGpuBudgetInBytes = static_cast(opts->fGpuBudgetInBytes); } - return out; + out->fRequireOrderedRecordings = opts->fRequireOrderedRecordings; + out->fSetBackendLabels = opts->fSetBackendLabels; + return true; } // Context @@ -166,13 +180,15 @@ extern "C" SK_C_API void sk_graphite_context_delete(sk_graphite_context_t* h) { } extern "C" SK_C_API sk_graphite_backend_t sk_graphite_context_get_backend(const sk_graphite_context_t* h) { - if (!h) return VULKAN_SK_GRAPHITE_BACKEND; // arbitrary; caller misused the API + if (!h) return UNKNOWN_SK_GRAPHITE_BACKEND; switch (AsGraphiteContext(h)->backend()) { - case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; - case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; - case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; - default: return VULKAN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMock: + case skgpu::BackendApi::kUnsupported: return UNKNOWN_SK_GRAPHITE_BACKEND; } + SkUNREACHABLE; } extern "C" SK_C_API bool sk_graphite_context_is_device_lost(const sk_graphite_context_t* h) { return h ? AsGraphiteContext(h)->isDeviceLost() : true; } @@ -213,14 +229,14 @@ extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder_wi static sk_graphite_insert_status_t ToInsertStatus(gr::InsertStatus::V v) { switch (v) { - case gr::InsertStatus::kSuccess: return SUCCESS_SK_GRAPHITE_INSERT_STATUS; - case gr::InsertStatus::kInvalidRecording: return INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kSuccess: return SUCCESS_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kInvalidRecording: return INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS; case gr::InsertStatus::kPromiseImageInstantiationFailed: return PROMISE_INSTANTIATION_FAILED_SK_GRAPHITE_INSERT_STATUS; - case gr::InsertStatus::kAddCommandsFailed: return ADD_COMMANDS_FAILED_SK_GRAPHITE_INSERT_STATUS; - case gr::InsertStatus::kAsyncShaderCompilesFailed: return ASYNC_SHADER_COMPILES_FAILED_SK_GRAPHITE_INSERT_STATUS; - case gr::InsertStatus::kOutOfOrderRecording: return OUT_OF_ORDER_RECORDING_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kAddCommandsFailed: return ADD_COMMANDS_FAILED_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kAsyncShaderCompilesFailed: return ASYNC_SHADER_COMPILES_FAILED_SK_GRAPHITE_INSERT_STATUS; + case gr::InsertStatus::kOutOfOrderRecording: return OUT_OF_ORDER_RECORDING_SK_GRAPHITE_INSERT_STATUS; } - return INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS; + SkUNREACHABLE; } extern "C" SK_C_API sk_graphite_insert_status_t sk_graphite_context_insert_recording(sk_graphite_context_t* h, const sk_graphite_insert_recording_info_t* info) { @@ -255,13 +271,15 @@ extern "C" SK_C_API void sk_graphite_recorder_delete(sk_graphite_recorder_t* h) } extern "C" SK_C_API sk_graphite_backend_t sk_graphite_recorder_get_backend(const sk_graphite_recorder_t* h) { - if (!h) return VULKAN_SK_GRAPHITE_BACKEND; + if (!h) return UNKNOWN_SK_GRAPHITE_BACKEND; switch (AsGraphiteRecorder(h)->backend()) { - case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; - case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; - case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; - default: return VULKAN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMock: + case skgpu::BackendApi::kUnsupported: return UNKNOWN_SK_GRAPHITE_BACKEND; } + SkUNREACHABLE; } extern "C" SK_C_API int32_t sk_graphite_recorder_get_max_texture_size(const sk_graphite_recorder_t* h) { @@ -375,13 +393,15 @@ extern "C" SK_C_API bool sk_graphite_backend_texture_is_valid(const sk_graphite_ return h ? AsGraphiteBackendTexture(h)->isValid() : false; } extern "C" SK_C_API sk_graphite_backend_t sk_graphite_backend_texture_get_backend(const sk_graphite_backend_texture_t* h) { - if (!h) return VULKAN_SK_GRAPHITE_BACKEND; + if (!h) return UNKNOWN_SK_GRAPHITE_BACKEND; switch (AsGraphiteBackendTexture(h)->backend()) { - case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; - case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; - case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; - default: return VULKAN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMock: + case skgpu::BackendApi::kUnsupported: return UNKNOWN_SK_GRAPHITE_BACKEND; } + SkUNREACHABLE; } extern "C" SK_C_API void sk_graphite_backend_texture_get_dimensions(const sk_graphite_backend_texture_t* h, int32_t* outW, int32_t* outH) { if (!h || !outW || !outH) return; @@ -399,13 +419,15 @@ extern "C" SK_C_API bool sk_graphite_texture_info_is_valid(const sk_graphite_tex return h ? AsGraphiteTextureInfo(h)->isValid() : false; } extern "C" SK_C_API sk_graphite_backend_t sk_graphite_texture_info_get_backend(const sk_graphite_texture_info_t* h) { - if (!h) return VULKAN_SK_GRAPHITE_BACKEND; + if (!h) return UNKNOWN_SK_GRAPHITE_BACKEND; switch (AsGraphiteTextureInfo(h)->backend()) { - case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; - case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; - case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; - default: return VULKAN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kVulkan: return VULKAN_SK_GRAPHITE_BACKEND; + case skgpu::BackendApi::kMock: + case skgpu::BackendApi::kUnsupported: return UNKNOWN_SK_GRAPHITE_BACKEND; } + SkUNREACHABLE; } extern "C" SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_graphite_texture_info_t* h) { if (!h) return 1; @@ -416,7 +438,7 @@ extern "C" SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_g case gr::SampleCount::k8: return 8; case gr::SampleCount::k16: return 16; } - return 1; + SkUNREACHABLE; } extern "C" SK_C_API int32_t sk_graphite_texture_info_get_mipmapped(const sk_graphite_texture_info_t* h) { return (h && AsGraphiteTextureInfo(h)->mipmapped() == skgpu::Mipmapped::kYes) ? 1 : 0; diff --git a/src/c/sk_graphite_dawn.cpp b/src/c/sk_graphite_dawn.cpp index 9a5c52c026af..83b699da7c45 100644 --- a/src/c/sk_graphite_dawn.cpp +++ b/src/c/sk_graphite_dawn.cpp @@ -24,7 +24,8 @@ namespace gr = skgpu::graphite; // Forward-declared in sk_graphite.cpp; reused here for option translation. -extern gr::ContextOptions sk_graphite_make_context_options(const sk_graphite_context_options_t* opts); +// Returns false if opts carries an invalid value (e.g. out-of-range sample count). +extern bool sk_graphite_make_context_options(const sk_graphite_context_options_t* opts, gr::ContextOptions* out); // Heap-allocated wrapper holding a DawnBackendContext value. The wgpu::Instance/ // Device/Queue smart pointers AddRef on construction-from-raw and Release on @@ -59,7 +60,9 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( const sk_graphite_context_options_t* opts) { if (!bc) return nullptr; - auto context = gr::ContextFactory::MakeDawn(bc->dbc, sk_graphite_make_context_options(opts)); + gr::ContextOptions gopts; + if (!sk_graphite_make_context_options(opts, &gopts)) return nullptr; + auto context = gr::ContextFactory::MakeDawn(bc->dbc, gopts); return ToGraphiteContext(context.release()); } diff --git a/src/c/sk_graphite_metal.cpp b/src/c/sk_graphite_metal.cpp index bec779556bd9..78339013959c 100644 --- a/src/c/sk_graphite_metal.cpp +++ b/src/c/sk_graphite_metal.cpp @@ -24,8 +24,9 @@ namespace gr = skgpu::graphite; -// Forward-declared in sk_graphite.cpp; reused here to share option translation. -extern gr::ContextOptions sk_graphite_make_context_options(const sk_graphite_context_options_t* opts); +// Forward-declared in sk_graphite.cpp; reused here for option translation. +// Returns false if opts carries an invalid value (e.g. out-of-range sample count). +extern bool sk_graphite_make_context_options(const sk_graphite_context_options_t* opts, gr::ContextOptions* out); // Heap-allocated wrapper holding an MtlBackendContext value. The wrapper // CFRetains the caller-supplied device/queue at construction (via sk_ret_cfp) @@ -51,7 +52,9 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal( const sk_graphite_context_options_t* opts) { if (!bc) return nullptr; - auto context = gr::ContextFactory::MakeMetal(bc->mbc, sk_graphite_make_context_options(opts)); + gr::ContextOptions gopts; + if (!sk_graphite_make_context_options(opts, &gopts)) return nullptr; + auto context = gr::ContextFactory::MakeMetal(bc->mbc, gopts); return ToGraphiteContext(context.release()); } diff --git a/src/c/sk_graphite_vulkan.cpp b/src/c/sk_graphite_vulkan.cpp index 50201fb16664..ca0a77e788d2 100644 --- a/src/c/sk_graphite_vulkan.cpp +++ b/src/c/sk_graphite_vulkan.cpp @@ -28,8 +28,9 @@ namespace gr = skgpu::graphite; -// Forward-declared in sk_graphite.cpp; reused here to share option translation. -extern gr::ContextOptions sk_graphite_make_context_options(const sk_graphite_context_options_t* opts); +// Forward-declared in sk_graphite.cpp; reused here for option translation. +// Returns false if opts carries an invalid value (e.g. out-of-range sample count). +extern bool sk_graphite_make_context_options(const sk_graphite_context_options_t* opts, gr::ContextOptions* out); // Heap-allocated wrapper holding a copy of the init struct so that the lambda // captured by the resulting skgpu::VulkanBackendContext::fGetProc has stable @@ -84,7 +85,9 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( } } - auto context = gr::ContextFactory::MakeVulkan(vkbc, sk_graphite_make_context_options(opts)); + gr::ContextOptions gopts; + if (!sk_graphite_make_context_options(opts, &gopts)) return nullptr; + auto context = gr::ContextFactory::MakeVulkan(vkbc, gopts); return ToGraphiteContext(context.release()); } From b3a94c23ee8e32236bb81842f727ccb3448cd85f Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Wed, 13 May 2026 16:56:09 +0000 Subject: [PATCH 09/14] mono/skia: drop defensive null/range checks from the Graphite C shim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shim was double-checking input that the managed binding already controls: null handles, null info pointers, negative budgets, zero or negative widths, out-of-range plane indices. Move that work to managed where the failure mode is a proper ArgumentException with a diagnostic message, and let the C shim be a straight 1-to-1 mapping to skgpu::graphite::*. Kept in the shim: * if (!opts) return true; in sk_graphite_make_context_options — a documented "null = use Skia defaults" semantic. * fGpuBudgetInBytes >= 0 and recorderBudgetBytes >= 0 sentinels — documented "negative = use Skia default" behaviour mirroring the C struct comment. * if (!bt.isValid()) return nullptr; — validates Skia's own return value, not a defensive null check. * Vulkan memory-allocator fallback in sk_graphite_context_make_vulkan — real setup logic, not a guard. * FfiImageProvider's findOrCreate boundary nulls — Skia is calling into us through a C++ vtable; we can't trust the inputs. --- src/c/sk_graphite.cpp | 62 ++++++++++-------------------------- src/c/sk_graphite_dawn.cpp | 3 -- src/c/sk_graphite_metal.cpp | 3 -- src/c/sk_graphite_vulkan.cpp | 4 --- 4 files changed, 16 insertions(+), 56 deletions(-) diff --git a/src/c/sk_graphite.cpp b/src/c/sk_graphite.cpp index 47e3de80996a..650027f91dd6 100644 --- a/src/c/sk_graphite.cpp +++ b/src/c/sk_graphite.cpp @@ -58,7 +58,6 @@ extern "C" SK_C_API bool sk_graphite_backend_is_available(sk_graphite_backend_t // ContextOptions extern "C" SK_C_API void sk_graphite_context_options_init_defaults(sk_graphite_context_options_t* out) { - if (!out) return; gr::ContextOptions defaults; out->fDisableDriverCorrectnessWorkarounds = defaults.fDisableDriverCorrectnessWorkarounds; out->fInternalMultisampleCount = static_cast(defaults.fInternalMultisampleCount); @@ -125,7 +124,6 @@ struct sk_graphite_image_provider_t { extern "C" SK_C_API sk_graphite_image_provider_t* sk_graphite_image_provider_new( sk_graphite_image_provider_proc_t proc, void* userData) { - if (!proc) return nullptr; auto* w = new sk_graphite_image_provider_t; w->sp = sk_make_sp(proc, userData); return w; @@ -140,7 +138,6 @@ extern "C" SK_C_API sk_image_t* sk_graphite_image_make_texture( const sk_image_t* image, int32_t mipmapped) { - if (!recorder || !image) return nullptr; SkImage::RequiredProperties props; props.fMipmapped = (mipmapped != 0); auto out = SkImages::TextureFromImage(AsGraphiteRecorder(recorder), AsImage(image), props); @@ -154,7 +151,6 @@ extern "C" SK_C_API sk_image_t* sk_graphite_image_make_texture( // well-defined "Skia defaults" state and the per-backend factory should bail // with nullptr so the managed caller can surface ArgumentException. bool sk_graphite_make_context_options(const sk_graphite_context_options_t* opts, gr::ContextOptions* out) { - if (!out) return false; *out = gr::ContextOptions{}; if (!opts) return true; // null = use Skia defaults // fInternalMultisampleCount: 0 means "leave Skia's default in place", same @@ -180,7 +176,6 @@ extern "C" SK_C_API void sk_graphite_context_delete(sk_graphite_context_t* h) { } extern "C" SK_C_API sk_graphite_backend_t sk_graphite_context_get_backend(const sk_graphite_context_t* h) { - if (!h) return UNKNOWN_SK_GRAPHITE_BACKEND; switch (AsGraphiteContext(h)->backend()) { case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; @@ -191,18 +186,16 @@ extern "C" SK_C_API sk_graphite_backend_t sk_graphite_context_get_backend(const SkUNREACHABLE; } -extern "C" SK_C_API bool sk_graphite_context_is_device_lost(const sk_graphite_context_t* h) { return h ? AsGraphiteContext(h)->isDeviceLost() : true; } -extern "C" SK_C_API int32_t sk_graphite_context_get_max_texture_size(const sk_graphite_context_t* h) { return h ? AsGraphiteContext(h)->maxTextureSize() : 0; } -extern "C" SK_C_API bool sk_graphite_context_supports_protected_content(const sk_graphite_context_t* h) { return h ? AsGraphiteContext(h)->supportsProtectedContent() : false; } -extern "C" SK_C_API int64_t sk_graphite_context_get_current_budgeted_bytes(const sk_graphite_context_t* h) { return h ? static_cast(AsGraphiteContext(h)->currentBudgetedBytes()) : 0; } -extern "C" SK_C_API int64_t sk_graphite_context_get_max_budgeted_bytes(const sk_graphite_context_t* h) { return h ? static_cast(AsGraphiteContext(h)->maxBudgetedBytes()) : 0; } -extern "C" SK_C_API void sk_graphite_context_set_max_budgeted_bytes(sk_graphite_context_t* h, int64_t b) { if (h && b >= 0) AsGraphiteContext(h)->setMaxBudgetedBytes(static_cast(b)); } -extern "C" SK_C_API void sk_graphite_context_free_gpu_resources(sk_graphite_context_t* h) { if (h) AsGraphiteContext(h)->freeGpuResources(); } +extern "C" SK_C_API bool sk_graphite_context_is_device_lost(const sk_graphite_context_t* h) { return AsGraphiteContext(h)->isDeviceLost(); } +extern "C" SK_C_API int32_t sk_graphite_context_get_max_texture_size(const sk_graphite_context_t* h) { return AsGraphiteContext(h)->maxTextureSize(); } +extern "C" SK_C_API bool sk_graphite_context_supports_protected_content(const sk_graphite_context_t* h) { return AsGraphiteContext(h)->supportsProtectedContent(); } +extern "C" SK_C_API int64_t sk_graphite_context_get_current_budgeted_bytes(const sk_graphite_context_t* h) { return static_cast(AsGraphiteContext(h)->currentBudgetedBytes()); } +extern "C" SK_C_API int64_t sk_graphite_context_get_max_budgeted_bytes(const sk_graphite_context_t* h) { return static_cast(AsGraphiteContext(h)->maxBudgetedBytes()); } +extern "C" SK_C_API void sk_graphite_context_set_max_budgeted_bytes(sk_graphite_context_t* h, int64_t b) { AsGraphiteContext(h)->setMaxBudgetedBytes(static_cast(b)); } +extern "C" SK_C_API void sk_graphite_context_free_gpu_resources(sk_graphite_context_t* h) { AsGraphiteContext(h)->freeGpuResources(); } extern "C" SK_C_API void sk_graphite_context_perform_deferred_cleanup(sk_graphite_context_t* h, int64_t milliseconds) { - if (h && milliseconds >= 0) { - AsGraphiteContext(h)->performDeferredCleanup(std::chrono::milliseconds(milliseconds)); - } + AsGraphiteContext(h)->performDeferredCleanup(std::chrono::milliseconds(milliseconds)); } extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder(sk_graphite_context_t* h, int64_t recorderBudgetBytes) { @@ -212,7 +205,6 @@ extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder(sk extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder_with_image_provider( sk_graphite_context_t* h, int64_t recorderBudgetBytes, sk_graphite_image_provider_t* imageProvider) { - if (!h) return nullptr; gr::RecorderOptions opts; if (recorderBudgetBytes >= 0) { opts.fGpuBudgetInBytes = static_cast(recorderBudgetBytes); @@ -240,9 +232,6 @@ static sk_graphite_insert_status_t ToInsertStatus(gr::InsertStatus::V v) { } extern "C" SK_C_API sk_graphite_insert_status_t sk_graphite_context_insert_recording(sk_graphite_context_t* h, const sk_graphite_insert_recording_info_t* info) { - if (!h || !info || !info->fRecording) { - return INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS; - } gr::InsertRecordingInfo iri; iri.fRecording = AsGraphiteRecording(info->fRecording); iri.fTargetSurface = AsSurface(info->fTargetSurface); @@ -254,7 +243,6 @@ extern "C" SK_C_API sk_graphite_insert_status_t sk_graphite_context_insert_recor } extern "C" SK_C_API bool sk_graphite_context_submit(sk_graphite_context_t* h, const sk_graphite_submit_info_t* info) { - if (!h) return false; gr::SubmitInfo si; if (info) { si.fSync = (info->fSync == YES_SK_GRAPHITE_SYNC_TO_CPU) ? gr::SyncToCpu::kYes : gr::SyncToCpu::kNo; @@ -271,7 +259,6 @@ extern "C" SK_C_API void sk_graphite_recorder_delete(sk_graphite_recorder_t* h) } extern "C" SK_C_API sk_graphite_backend_t sk_graphite_recorder_get_backend(const sk_graphite_recorder_t* h) { - if (!h) return UNKNOWN_SK_GRAPHITE_BACKEND; switch (AsGraphiteRecorder(h)->backend()) { case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; @@ -283,11 +270,10 @@ extern "C" SK_C_API sk_graphite_backend_t sk_graphite_recorder_get_backend(const } extern "C" SK_C_API int32_t sk_graphite_recorder_get_max_texture_size(const sk_graphite_recorder_t* h) { - return h ? AsGraphiteRecorder(h)->maxTextureSize() : 0; + return AsGraphiteRecorder(h)->maxTextureSize(); } extern "C" SK_C_API sk_graphite_recording_t* sk_graphite_recorder_snap(sk_graphite_recorder_t* h) { - if (!h) return nullptr; auto recording = AsGraphiteRecorder(h)->snap(); return ToGraphiteRecording(recording.release()); } @@ -306,7 +292,6 @@ extern "C" SK_C_API sk_surface_t* sk_graphite_surface_make_render_target( int32_t mipmapped, const sk_surfaceprops_t* props) { - if (!recorder || !cinfo) return nullptr; SkImageInfo info = AsImageInfo(cinfo); skgpu::Mipmapped mips = (mipmapped != 0) ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; auto surface = SkSurfaces::RenderTarget(AsGraphiteRecorder(recorder), info, mips, AsSurfaceProps(props)); @@ -322,7 +307,6 @@ extern "C" SK_C_API sk_surface_t* sk_graphite_surface_wrap_backend_texture( sk_graphite_release_proc_t releaseProc, void* releaseContext) { - if (!recorder || !backendTexture) return nullptr; auto surface = SkSurfaces::WrapBackendTexture( AsGraphiteRecorder(recorder), *AsGraphiteBackendTexture(backendTexture), @@ -343,7 +327,6 @@ extern "C" SK_C_API sk_image_t* sk_graphite_image_wrap_texture( sk_graphite_release_proc_t releaseProc, void* releaseContext) { - if (!recorder || !backendTexture) return nullptr; auto image = SkImages::WrapTexture( AsGraphiteRecorder(recorder), *AsGraphiteBackendTexture(backendTexture), @@ -360,7 +343,6 @@ extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_recorder_create_b int32_t width, int32_t height, const sk_graphite_texture_info_t* info) { - if (!recorder || !info || width <= 0 || height <= 0) return nullptr; auto bt = AsGraphiteRecorder(recorder)->createBackendTexture( SkISize::Make(width, height), *AsGraphiteTextureInfo(info)); @@ -372,16 +354,14 @@ extern "C" SK_C_API void sk_graphite_recorder_delete_backend_texture( sk_graphite_recorder_t* recorder, const sk_graphite_backend_texture_t* tex) { - if (recorder && tex) - AsGraphiteRecorder(recorder)->deleteBackendTexture(*AsGraphiteBackendTexture(tex)); + AsGraphiteRecorder(recorder)->deleteBackendTexture(*AsGraphiteBackendTexture(tex)); } extern "C" SK_C_API void sk_graphite_context_delete_backend_texture( sk_graphite_context_t* context, const sk_graphite_backend_texture_t* tex) { - if (context && tex) - AsGraphiteContext(context)->deleteBackendTexture(*AsGraphiteBackendTexture(tex)); + AsGraphiteContext(context)->deleteBackendTexture(*AsGraphiteBackendTexture(tex)); } // BackendTexture handle: heap wrapper around the C++ value type. @@ -390,10 +370,9 @@ extern "C" SK_C_API void sk_graphite_backend_texture_delete(sk_graphite_backend_ delete AsGraphiteBackendTexture(h); } extern "C" SK_C_API bool sk_graphite_backend_texture_is_valid(const sk_graphite_backend_texture_t* h) { - return h ? AsGraphiteBackendTexture(h)->isValid() : false; + return AsGraphiteBackendTexture(h)->isValid(); } extern "C" SK_C_API sk_graphite_backend_t sk_graphite_backend_texture_get_backend(const sk_graphite_backend_texture_t* h) { - if (!h) return UNKNOWN_SK_GRAPHITE_BACKEND; switch (AsGraphiteBackendTexture(h)->backend()) { case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; @@ -404,7 +383,6 @@ extern "C" SK_C_API sk_graphite_backend_t sk_graphite_backend_texture_get_backen SkUNREACHABLE; } extern "C" SK_C_API void sk_graphite_backend_texture_get_dimensions(const sk_graphite_backend_texture_t* h, int32_t* outW, int32_t* outH) { - if (!h || !outW || !outH) return; auto d = AsGraphiteBackendTexture(h)->dimensions(); *outW = d.fWidth; *outH = d.fHeight; @@ -416,10 +394,9 @@ extern "C" SK_C_API void sk_graphite_texture_info_delete(sk_graphite_texture_inf delete AsGraphiteTextureInfo(h); } extern "C" SK_C_API bool sk_graphite_texture_info_is_valid(const sk_graphite_texture_info_t* h) { - return h ? AsGraphiteTextureInfo(h)->isValid() : false; + return AsGraphiteTextureInfo(h)->isValid(); } extern "C" SK_C_API sk_graphite_backend_t sk_graphite_texture_info_get_backend(const sk_graphite_texture_info_t* h) { - if (!h) return UNKNOWN_SK_GRAPHITE_BACKEND; switch (AsGraphiteTextureInfo(h)->backend()) { case skgpu::BackendApi::kDawn: return DAWN_SK_GRAPHITE_BACKEND; case skgpu::BackendApi::kMetal: return METAL_SK_GRAPHITE_BACKEND; @@ -430,7 +407,6 @@ extern "C" SK_C_API sk_graphite_backend_t sk_graphite_texture_info_get_backend(c SkUNREACHABLE; } extern "C" SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_graphite_texture_info_t* h) { - if (!h) return 1; switch (AsGraphiteTextureInfo(h)->sampleCount()) { case gr::SampleCount::k1: return 1; case gr::SampleCount::k2: return 2; @@ -441,7 +417,7 @@ extern "C" SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_g SkUNREACHABLE; } extern "C" SK_C_API int32_t sk_graphite_texture_info_get_mipmapped(const sk_graphite_texture_info_t* h) { - return (h && AsGraphiteTextureInfo(h)->mipmapped() == skgpu::Mipmapped::kYes) ? 1 : 0; + return (AsGraphiteTextureInfo(h)->mipmapped() == skgpu::Mipmapped::kYes) ? 1 : 0; } // Async readback — pass-through wrapping of Context::asyncRescaleAndReadPixels. @@ -476,10 +452,6 @@ extern "C" SK_C_API void sk_graphite_context_async_rescale_and_read_pixels_surfa sk_graphite_async_read_pixels_proc_t callback, void* callbackContext) { - if (!h || !surface || !cdstInfo || !csrcRect || !callback) { - if (callback) callback(callbackContext, nullptr); // signal failure synchronously - return; - } auto* bridge = new AsyncReadCallbackBridge{callback, callbackContext}; AsGraphiteContext(h)->asyncRescaleAndReadPixels( AsSurface(surface), @@ -492,7 +464,7 @@ extern "C" SK_C_API void sk_graphite_context_async_rescale_and_read_pixels_surfa } extern "C" SK_C_API void sk_graphite_context_check_async_work_completion(sk_graphite_context_t* h) { - if (h) AsGraphiteContext(h)->checkAsyncWorkCompletion(); + AsGraphiteContext(h)->checkAsyncWorkCompletion(); } // AsyncReadResult accessors. The opaque sk_graphite_async_read_result_t* maps @@ -502,16 +474,14 @@ static inline const SkImage::AsyncReadResult* AsAsyncReadResult(const sk_graphit } extern "C" SK_C_API int32_t sk_graphite_async_read_result_get_count(const sk_graphite_async_read_result_t* h) { - return h ? AsAsyncReadResult(h)->count() : 0; + return AsAsyncReadResult(h)->count(); } extern "C" SK_C_API const void* sk_graphite_async_read_result_get_data(const sk_graphite_async_read_result_t* h, int32_t planeIndex) { - if (!h || planeIndex < 0 || planeIndex >= AsAsyncReadResult(h)->count()) return nullptr; return AsAsyncReadResult(h)->data(planeIndex); } extern "C" SK_C_API size_t sk_graphite_async_read_result_get_row_bytes(const sk_graphite_async_read_result_t* h, int32_t planeIndex) { - if (!h || planeIndex < 0 || planeIndex >= AsAsyncReadResult(h)->count()) return 0; return AsAsyncReadResult(h)->rowBytes(planeIndex); } diff --git a/src/c/sk_graphite_dawn.cpp b/src/c/sk_graphite_dawn.cpp index 83b699da7c45..0fcb13ceebf3 100644 --- a/src/c/sk_graphite_dawn.cpp +++ b/src/c/sk_graphite_dawn.cpp @@ -36,7 +36,6 @@ struct sk_graphite_dawn_backend_context_t { }; extern "C" SK_C_API sk_graphite_dawn_backend_context_t* sk_graphite_dawn_backend_context_new(const sk_graphite_dawn_backend_context_init_t* init) { - if (!init || !init->fInstance || !init->fDevice || !init->fQueue) return nullptr; auto* bc = new sk_graphite_dawn_backend_context_t; // wgpu::Instance/Device/Queue construct-from-raw with AddRef semantics // (the wgpu C++ wrappers' default constructor takes a raw C handle and @@ -59,7 +58,6 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( const sk_graphite_dawn_backend_context_t* bc, const sk_graphite_context_options_t* opts) { - if (!bc) return nullptr; gr::ContextOptions gopts; if (!sk_graphite_make_context_options(opts, &gopts)) return nullptr; auto context = gr::ContextFactory::MakeDawn(bc->dbc, gopts); @@ -67,7 +65,6 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( } extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_dawn_backend_texture_new(void* wgpuTexture) { - if (!wgpuTexture) return nullptr; auto bt = gr::BackendTextures::MakeDawn(static_cast(wgpuTexture)); if (!bt.isValid()) return nullptr; return ToGraphiteBackendTexture(new gr::BackendTexture(bt)); diff --git a/src/c/sk_graphite_metal.cpp b/src/c/sk_graphite_metal.cpp index 78339013959c..05329ded619d 100644 --- a/src/c/sk_graphite_metal.cpp +++ b/src/c/sk_graphite_metal.cpp @@ -36,7 +36,6 @@ struct sk_graphite_mtl_backend_context_t { }; extern "C" SK_C_API sk_graphite_mtl_backend_context_t* sk_graphite_mtl_backend_context_new(const sk_graphite_mtl_backend_context_init_t* init) { - if (!init || !init->fDevice || !init->fQueue) return nullptr; auto* bc = new sk_graphite_mtl_backend_context_t; bc->mbc.fDevice = sk_ret_cfp(static_cast(init->fDevice)); bc->mbc.fQueue = sk_ret_cfp(static_cast(init->fQueue)); @@ -51,7 +50,6 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal( const sk_graphite_mtl_backend_context_t* bc, const sk_graphite_context_options_t* opts) { - if (!bc) return nullptr; gr::ContextOptions gopts; if (!sk_graphite_make_context_options(opts, &gopts)) return nullptr; auto context = gr::ContextFactory::MakeMetal(bc->mbc, gopts); @@ -61,7 +59,6 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal( extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_mtl_backend_texture_new( int32_t width, int32_t height, void* mtlTexture) { - if (width <= 0 || height <= 0 || !mtlTexture) return nullptr; auto bt = gr::BackendTextures::MakeMetal(SkISize::Make(width, height), static_cast(mtlTexture)); auto* heap = new gr::BackendTexture(bt); diff --git a/src/c/sk_graphite_vulkan.cpp b/src/c/sk_graphite_vulkan.cpp index ca0a77e788d2..4dbb0c0954d9 100644 --- a/src/c/sk_graphite_vulkan.cpp +++ b/src/c/sk_graphite_vulkan.cpp @@ -40,7 +40,6 @@ struct sk_graphite_vk_backend_context_t { }; extern "C" SK_C_API sk_graphite_vk_backend_context_t* sk_graphite_vk_backend_context_new(const sk_graphite_vk_backend_context_init_t* init) { - if (!init) return nullptr; auto* bc = new sk_graphite_vk_backend_context_t{*init}; return bc; } @@ -53,7 +52,6 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( const sk_graphite_vk_backend_context_t* bc, const sk_graphite_context_options_t* opts) { - if (!bc) return nullptr; const auto& init = bc->init; skgpu::VulkanBackendContext vkbc; @@ -111,7 +109,6 @@ gr::VulkanTextureInfo MakeNativeVkTextureInfo(const sk_graphite_vk_texture_info_ } // namespace extern "C" SK_C_API sk_graphite_texture_info_t* sk_graphite_vk_texture_info_new(const sk_graphite_vk_texture_info_t* info) { - if (!info) return nullptr; auto* ti = new gr::TextureInfo(gr::TextureInfos::MakeVulkan(MakeNativeVkTextureInfo(*info))); return ToGraphiteTextureInfo(ti); } @@ -123,7 +120,6 @@ extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_vk_backend_textur uint32_t queueFamilyIndex, void* vkImage) { - if (!info || width <= 0 || height <= 0) return nullptr; auto vkti = MakeNativeVkTextureInfo(*info); auto bt = gr::BackendTextures::MakeVulkan( SkISize::Make(width, height), From 561686adc3143ac686251963fa1cace369d6e47b Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Wed, 13 May 2026 17:15:24 +0000 Subject: [PATCH 10/14] mono/skia: fold sk_graphite_context_make_recorder into the one entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plain make_recorder entry was a forwarder to the _with_image_provider variant with a null third argument. Two C entry points, two C# P/Invokes, two SkiaApi declarations, two stub branches in the !SK_GRAPHITE block — all for what's a single C++ call. Collapse to one entry that takes the optional image-provider directly; callers that don't want one pass null. --- include/c/sk_graphite.h | 12 ++++-------- src/c/sk_graphite.cpp | 9 ++------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/include/c/sk_graphite.h b/include/c/sk_graphite.h index cb049e862552..b7b541b1e606 100644 --- a/include/c/sk_graphite.h +++ b/include/c/sk_graphite.h @@ -106,14 +106,10 @@ SK_C_API void sk_graphite_context_free_gpu_resources(sk SK_C_API void sk_graphite_context_perform_deferred_cleanup(sk_graphite_context_t* context, int64_t milliseconds); // Recorder vending. recorderBudgetBytes < 0 uses the context default. -SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder(sk_graphite_context_t* context, int64_t recorderBudgetBytes); - -// Variant that attaches an ImageProvider to the new recorder. Ownership of -// the provider transfers to the recorder on success — caller must NOT call -// sk_graphite_image_provider_delete on it afterwards. Pass null to behave -// identically to sk_graphite_context_make_recorder. The provider can also -// be sourced from sk_graphite_image_provider_new. -SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder_with_image_provider( +// imageProvider may be null. When non-null, ownership of the wrapper transfers +// to the recorder on success — caller must NOT call sk_graphite_image_provider_delete +// on it afterwards. The provider is constructed via sk_graphite_image_provider_new. +SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder( sk_graphite_context_t* context, int64_t recorderBudgetBytes, sk_graphite_image_provider_t* imageProvider); diff --git a/src/c/sk_graphite.cpp b/src/c/sk_graphite.cpp index 650027f91dd6..520edb01b89d 100644 --- a/src/c/sk_graphite.cpp +++ b/src/c/sk_graphite.cpp @@ -198,11 +198,7 @@ extern "C" SK_C_API void sk_graphite_context_perform_deferred_cleanup(sk_graphit AsGraphiteContext(h)->performDeferredCleanup(std::chrono::milliseconds(milliseconds)); } -extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder(sk_graphite_context_t* h, int64_t recorderBudgetBytes) { - return sk_graphite_context_make_recorder_with_image_provider(h, recorderBudgetBytes, nullptr); -} - -extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder_with_image_provider( +extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder( sk_graphite_context_t* h, int64_t recorderBudgetBytes, sk_graphite_image_provider_t* imageProvider) { gr::RecorderOptions opts; @@ -499,8 +495,7 @@ extern "C" SK_C_API int64_t sk_graphite_context_get_max_bu extern "C" SK_C_API void sk_graphite_context_set_max_budgeted_bytes(sk_graphite_context_t*, int64_t) {} extern "C" SK_C_API void sk_graphite_context_free_gpu_resources(sk_graphite_context_t*) {} extern "C" SK_C_API void sk_graphite_context_perform_deferred_cleanup(sk_graphite_context_t*, int64_t) {} -extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder(sk_graphite_context_t*, int64_t) { return nullptr; } -extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder_with_image_provider(sk_graphite_context_t*, int64_t, sk_graphite_image_provider_t*) { return nullptr; } +extern "C" SK_C_API sk_graphite_recorder_t* sk_graphite_context_make_recorder(sk_graphite_context_t*, int64_t, sk_graphite_image_provider_t*) { return nullptr; } extern "C" SK_C_API sk_graphite_insert_status_t sk_graphite_context_insert_recording(sk_graphite_context_t*, const sk_graphite_insert_recording_info_t*) { return INVALID_RECORDING_SK_GRAPHITE_INSERT_STATUS; } extern "C" SK_C_API bool sk_graphite_context_submit(sk_graphite_context_t*, const sk_graphite_submit_info_t*) { return false; } extern "C" SK_C_API void sk_graphite_recorder_delete(sk_graphite_recorder_t*) {} From bafe1e4d5683734a422b1f541886b7d8120743e5 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Wed, 13 May 2026 20:59:24 +0000 Subject: [PATCH 11/14] mono/skia: use C bool, not int32_t, for the mipmap and non-yielding flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five sites in the Graphite C ABI were carrying a binary flag as int32_t with a "0 = no, 1 = yes" comment instead of the C bool type used elsewhere in the same headers (e.g. sk_graphite_backend_is_- available, sk_graphite_context_is_device_lost, fProtectedContext): * sk_graphite_image_make_texture(... mipmapped) * sk_graphite_surface_make_render_target(... mipmapped, ...) * sk_graphite_image_provider_proc_t callback (mipmapped) * sk_graphite_texture_info_get_mipmapped return * sk_graphite_vk_texture_info_t::fMipmapped * sk_graphite_dawn_backend_context_init_t::fNonYielding Switch them all to bool to match the rest of the surface and drop the "0 = no, 1 = yes" comments. Managed side gains a slightly nicer shape (no `? 1 : 0` / `!= 0` adapters around every call), and the binary nature of each flag is now self-documenting from the type. The two-state SubmitInfo enums (fSync, fMarkBoundary) are kept as distinct enums on purpose — the named YES_/NO_ constants read more clearly at call sites than bare bools and mirror Skia's enum class. --- include/c/sk_graphite.h | 8 ++++---- include/c/sk_graphite_dawn.h | 6 +++--- include/c/sk_graphite_vulkan.h | 2 +- src/c/sk_graphite.cpp | 18 +++++++++--------- src/c/sk_graphite_vulkan.cpp | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/c/sk_graphite.h b/include/c/sk_graphite.h index b7b541b1e606..5c3d14d3e7bf 100644 --- a/include/c/sk_graphite.h +++ b/include/c/sk_graphite.h @@ -135,7 +135,7 @@ SK_C_API void sk_graphite_recording_delete(sk_graphite_ SK_C_API sk_surface_t* sk_graphite_surface_make_render_target( sk_graphite_recorder_t* recorder, const sk_imageinfo_t* info, - int32_t mipmapped, // 0 = no, 1 = yes + bool mipmapped, const sk_surfaceprops_t* props /* nullable */); // Wrap a caller-allocated GPU texture (described by an opaque @@ -199,7 +199,7 @@ SK_C_API void sk_graphite_texture_info_delete ( SK_C_API bool sk_graphite_texture_info_is_valid (const sk_graphite_texture_info_t* info); SK_C_API sk_graphite_backend_t sk_graphite_texture_info_get_backend (const sk_graphite_texture_info_t* info); SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_graphite_texture_info_t* info); -SK_C_API int32_t sk_graphite_texture_info_get_mipmapped (const sk_graphite_texture_info_t* info); // 0 = no, 1 = yes +SK_C_API bool sk_graphite_texture_info_get_mipmapped (const sk_graphite_texture_info_t* info); // Asynchronous CPU readback — direct pass-through of upstream Skia's // Context::asyncRescaleAndReadPixels. The legacy sk_surface_read_pixels does @@ -265,7 +265,7 @@ typedef sk_image_t* (*sk_graphite_image_provider_proc_t)( void* userData, sk_graphite_recorder_t* recorder, const sk_image_t* image, - int32_t mipmapped /* 0 = no, 1 = yes */); + bool mipmapped); // Build an ImageProvider that dispatches to the given callback. Caller retains // ownership of the returned wrapper until it is passed to a Context via @@ -288,7 +288,7 @@ SK_C_API void sk_graphite_image_provider_delete(sk_graphite_image_provider_t* pr SK_C_API sk_image_t* sk_graphite_image_make_texture( sk_graphite_recorder_t* recorder, const sk_image_t* image, - int32_t mipmapped /* 0 = no, 1 = yes */); + bool mipmapped); SK_C_PLUS_PLUS_END_GUARD diff --git a/include/c/sk_graphite_dawn.h b/include/c/sk_graphite_dawn.h index 72df10175ee5..472e2d0d6ae1 100644 --- a/include/c/sk_graphite_dawn.h +++ b/include/c/sk_graphite_dawn.h @@ -20,15 +20,15 @@ typedef struct sk_graphite_dawn_backend_context_t sk_graphite_dawn_backend_conte // AddRef's each at construction; the wrapper owns those references and // releases them on delete. // -// fNonYielding: when non-zero, no DawnTickFunction is installed (Skia's +// fNonYielding: when true, no DawnTickFunction is installed (Skia's // "non-yielding context" mode). Required when running over Emscripten without -// -s ASYNCIFY. Native Dawn callers should leave it 0 — the shim installs +// -s ASYNCIFY. Native Dawn callers should leave it false — the shim installs // DawnNativeProcessEventsFunction by default. typedef struct { void* fInstance; // WGPUInstance void* fDevice; // WGPUDevice void* fQueue; // WGPUQueue - int32_t fNonYielding; // 0 = install default tick fn; 1 = no tick fn (Emscripten) + bool fNonYielding; } sk_graphite_dawn_backend_context_init_t; SK_C_API sk_graphite_dawn_backend_context_t* sk_graphite_dawn_backend_context_new(const sk_graphite_dawn_backend_context_init_t* init); diff --git a/include/c/sk_graphite_vulkan.h b/include/c/sk_graphite_vulkan.h index fcda00f51ca0..8854073a6851 100644 --- a/include/c/sk_graphite_vulkan.h +++ b/include/c/sk_graphite_vulkan.h @@ -50,7 +50,7 @@ SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( // add a separate _ycbcr_t struct + factory if needed. typedef struct { int32_t fSampleCount; // 1, 2, 4, 8, or 16 - int32_t fMipmapped; // 0 = no, 1 = yes + bool fMipmapped; uint32_t fFlags; // VkImageCreateFlags int32_t fFormat; // VkFormat (e.g. VK_FORMAT_R8G8B8A8_UNORM) int32_t fImageTiling; // VkImageTiling diff --git a/src/c/sk_graphite.cpp b/src/c/sk_graphite.cpp index 520edb01b89d..eb6f1fef4d3c 100644 --- a/src/c/sk_graphite.cpp +++ b/src/c/sk_graphite.cpp @@ -136,10 +136,10 @@ extern "C" SK_C_API void sk_graphite_image_provider_delete(sk_graphite_image_pro extern "C" SK_C_API sk_image_t* sk_graphite_image_make_texture( sk_graphite_recorder_t* recorder, const sk_image_t* image, - int32_t mipmapped) + bool mipmapped) { SkImage::RequiredProperties props; - props.fMipmapped = (mipmapped != 0); + props.fMipmapped = mipmapped; auto out = SkImages::TextureFromImage(AsGraphiteRecorder(recorder), AsImage(image), props); return ToImage(out.release()); } @@ -285,11 +285,11 @@ extern "C" SK_C_API void sk_graphite_recording_delete(sk_graphite_recording_t* h extern "C" SK_C_API sk_surface_t* sk_graphite_surface_make_render_target( sk_graphite_recorder_t* recorder, const sk_imageinfo_t* cinfo, - int32_t mipmapped, + bool mipmapped, const sk_surfaceprops_t* props) { SkImageInfo info = AsImageInfo(cinfo); - skgpu::Mipmapped mips = (mipmapped != 0) ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; + skgpu::Mipmapped mips = mipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; auto surface = SkSurfaces::RenderTarget(AsGraphiteRecorder(recorder), info, mips, AsSurfaceProps(props)); return ToSurface(surface.release()); } @@ -412,8 +412,8 @@ extern "C" SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_g } SkUNREACHABLE; } -extern "C" SK_C_API int32_t sk_graphite_texture_info_get_mipmapped(const sk_graphite_texture_info_t* h) { - return (AsGraphiteTextureInfo(h)->mipmapped() == skgpu::Mipmapped::kYes) ? 1 : 0; +extern "C" SK_C_API bool sk_graphite_texture_info_get_mipmapped(const sk_graphite_texture_info_t* h) { + return AsGraphiteTextureInfo(h)->mipmapped() == skgpu::Mipmapped::kYes; } // Async readback — pass-through wrapping of Context::asyncRescaleAndReadPixels. @@ -503,7 +503,7 @@ extern "C" SK_C_API sk_graphite_backend_t sk_graphite_recorder_get_backe extern "C" SK_C_API int32_t sk_graphite_recorder_get_max_texture_size(const sk_graphite_recorder_t*) { return 0; } extern "C" SK_C_API sk_graphite_recording_t* sk_graphite_recorder_snap(sk_graphite_recorder_t*) { return nullptr; } extern "C" SK_C_API void sk_graphite_recording_delete(sk_graphite_recording_t*) {} -extern "C" SK_C_API sk_surface_t* sk_graphite_surface_make_render_target(sk_graphite_recorder_t*, const sk_imageinfo_t*, int32_t, const sk_surfaceprops_t*) { return nullptr; } +extern "C" SK_C_API sk_surface_t* sk_graphite_surface_make_render_target(sk_graphite_recorder_t*, const sk_imageinfo_t*, bool, const sk_surfaceprops_t*) { return nullptr; } extern "C" SK_C_API void sk_graphite_context_async_rescale_and_read_pixels_surface(sk_graphite_context_t*, const sk_surface_t*, const sk_imageinfo_t*, const sk_irect_t*, sk_graphite_rescale_gamma_t, sk_graphite_rescale_mode_t, sk_graphite_async_read_pixels_proc_t cb, void* ctx) { if (cb) cb(ctx, nullptr); } extern "C" SK_C_API void sk_graphite_context_check_async_work_completion(sk_graphite_context_t*) {} extern "C" SK_C_API int32_t sk_graphite_async_read_result_get_count(const sk_graphite_async_read_result_t*) { return 0; } @@ -518,13 +518,13 @@ extern "C" SK_C_API void sk_graphite_texture_info_delet extern "C" SK_C_API bool sk_graphite_texture_info_is_valid(const sk_graphite_texture_info_t*) { return false; } extern "C" SK_C_API sk_graphite_backend_t sk_graphite_texture_info_get_backend(const sk_graphite_texture_info_t*) { return VULKAN_SK_GRAPHITE_BACKEND; } extern "C" SK_C_API int32_t sk_graphite_texture_info_get_sample_count(const sk_graphite_texture_info_t*) { return 1; } -extern "C" SK_C_API int32_t sk_graphite_texture_info_get_mipmapped(const sk_graphite_texture_info_t*) { return 0; } +extern "C" SK_C_API bool sk_graphite_texture_info_get_mipmapped(const sk_graphite_texture_info_t*) { return false; } extern "C" SK_C_API sk_image_t* sk_graphite_image_wrap_texture(sk_graphite_recorder_t*, const sk_graphite_backend_texture_t*, sk_colortype_t, sk_alphatype_t, sk_colorspace_t*, sk_graphite_release_proc_t, void*) { return nullptr; } extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_recorder_create_backend_texture(sk_graphite_recorder_t*, int32_t, int32_t, const sk_graphite_texture_info_t*) { return nullptr; } extern "C" SK_C_API void sk_graphite_recorder_delete_backend_texture(sk_graphite_recorder_t*, const sk_graphite_backend_texture_t*) {} extern "C" SK_C_API void sk_graphite_context_delete_backend_texture(sk_graphite_context_t*, const sk_graphite_backend_texture_t*) {} extern "C" SK_C_API sk_graphite_image_provider_t* sk_graphite_image_provider_new(sk_graphite_image_provider_proc_t, void*) { return nullptr; } extern "C" SK_C_API void sk_graphite_image_provider_delete(sk_graphite_image_provider_t*) {} -extern "C" SK_C_API sk_image_t* sk_graphite_image_make_texture(sk_graphite_recorder_t*, const sk_image_t*, int32_t) { return nullptr; } +extern "C" SK_C_API sk_image_t* sk_graphite_image_make_texture(sk_graphite_recorder_t*, const sk_image_t*, bool) { return nullptr; } #endif // SK_GRAPHITE diff --git a/src/c/sk_graphite_vulkan.cpp b/src/c/sk_graphite_vulkan.cpp index 4dbb0c0954d9..2e517fde5ee3 100644 --- a/src/c/sk_graphite_vulkan.cpp +++ b/src/c/sk_graphite_vulkan.cpp @@ -94,7 +94,7 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( namespace { gr::VulkanTextureInfo MakeNativeVkTextureInfo(const sk_graphite_vk_texture_info_t& info) { auto sampleBits = static_cast(info.fSampleCount > 0 ? info.fSampleCount : 1); - auto mipmapped = info.fMipmapped != 0 ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; + auto mipmapped = info.fMipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; return gr::VulkanTextureInfo( sampleBits, mipmapped, From 1d99dc41f10037c4b29340b9f7509f1bccf05034 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Wed, 13 May 2026 21:12:04 +0000 Subject: [PATCH 12/14] mono/skia: drop the sync/mark-boundary enums in favor of bool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both sk_graphite_sync_to_cpu_t and sk_graphite_mark_frame_boundary_t were two-value enums shaped exactly like a bool. Mirroring Skia's `enum class SyncToCpu : bool` / `MarkFrameBoundary : bool` shape in the C ABI never paid off — at call sites the YES_/NO_ constants are just ceremony around a true/false. Drop both enums and make the fields in sk_graphite_submit_info_t plain `bool`. Same translation to Skia's enum class happens in sk_graphite_context_submit, only now the input is a clean boolean instead of an enum-equals-named-constant expression. --- include/c/sk_graphite.h | 16 +++------------- src/c/sk_graphite.cpp | 4 ++-- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/include/c/sk_graphite.h b/include/c/sk_graphite.h index 5c3d14d3e7bf..b33292a2d6ba 100644 --- a/include/c/sk_graphite.h +++ b/include/c/sk_graphite.h @@ -53,20 +53,10 @@ SK_C_API void sk_graphite_context_options_init_defaults(sk_graphite_context_opti // Submission control -typedef enum { - NO_SK_GRAPHITE_SYNC_TO_CPU = 0, - YES_SK_GRAPHITE_SYNC_TO_CPU = 1, -} sk_graphite_sync_to_cpu_t; - -typedef enum { - NO_SK_GRAPHITE_MARK_FRAME_BOUNDARY = 0, - YES_SK_GRAPHITE_MARK_FRAME_BOUNDARY = 1, -} sk_graphite_mark_frame_boundary_t; - typedef struct { - sk_graphite_sync_to_cpu_t fSync; - sk_graphite_mark_frame_boundary_t fMarkBoundary; - uint64_t fFrameID; + bool fSync; + bool fMarkBoundary; + uint64_t fFrameID; } sk_graphite_submit_info_t; // Recording insert / status diff --git a/src/c/sk_graphite.cpp b/src/c/sk_graphite.cpp index eb6f1fef4d3c..a71f1aacad53 100644 --- a/src/c/sk_graphite.cpp +++ b/src/c/sk_graphite.cpp @@ -241,8 +241,8 @@ extern "C" SK_C_API sk_graphite_insert_status_t sk_graphite_context_insert_recor extern "C" SK_C_API bool sk_graphite_context_submit(sk_graphite_context_t* h, const sk_graphite_submit_info_t* info) { gr::SubmitInfo si; if (info) { - si.fSync = (info->fSync == YES_SK_GRAPHITE_SYNC_TO_CPU) ? gr::SyncToCpu::kYes : gr::SyncToCpu::kNo; - si.fMarkBoundary = (info->fMarkBoundary == YES_SK_GRAPHITE_MARK_FRAME_BOUNDARY) ? gr::MarkFrameBoundary::kYes : gr::MarkFrameBoundary::kNo; + si.fSync = info->fSync ? gr::SyncToCpu::kYes : gr::SyncToCpu::kNo; + si.fMarkBoundary = info->fMarkBoundary ? gr::MarkFrameBoundary::kYes : gr::MarkFrameBoundary::kNo; si.fFrameID = info->fFrameID; } return AsGraphiteContext(h)->submit(si); From 3aae65dcee7f38e70d041fad0b91c07e32a91993 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Thu, 14 May 2026 17:16:44 +0000 Subject: [PATCH 13/14] mono/skia: collapse backend-context wrappers into the make_* entry points Drop the opaque sk_graphite_{vk,mtl,dawn}_backend_context_t handles plus their paired _new / _delete functions. sk_graphite_context_make_* now take const _init_t* directly. The CFRetain (Metal) and AddRef (Dawn) work that the wrappers used to do moves inline into _make_*, bracketed by the ContextFactory::Make* call so locals release on scope exit whether Skia took its own refs or not. The Vulkan dispatch lambda was already capturing fGetProc + fGetProcUserData by value, so the wrapper was purely cosmetic on that path. Net: 6 fewer exported symbols, ~60 lines removed, one consistent managed lifecycle across all three backends. Co-Authored-By: Claude Opus 4.7 (1M context) --- include/c/sk_graphite_dawn.h | 13 ++++------ include/c/sk_graphite_metal.h | 14 ++++------- include/c/sk_graphite_vulkan.h | 14 ++++------- src/c/sk_graphite_dawn.cpp | 44 ++++++++++++---------------------- src/c/sk_graphite_metal.cpp | 34 +++++++++----------------- src/c/sk_graphite_vulkan.cpp | 26 +++----------------- src/xamarin/SkiaKeeper.c | 6 ++--- 7 files changed, 44 insertions(+), 107 deletions(-) diff --git a/include/c/sk_graphite_dawn.h b/include/c/sk_graphite_dawn.h index 472e2d0d6ae1..e4a6980e24ec 100644 --- a/include/c/sk_graphite_dawn.h +++ b/include/c/sk_graphite_dawn.h @@ -13,12 +13,10 @@ SK_C_PLUS_PLUS_BEGIN_GUARD -// Opaque heap wrapper around skgpu::graphite::DawnBackendContext. -typedef struct sk_graphite_dawn_backend_context_t sk_graphite_dawn_backend_context_t; - // Init struct: caller-owned WGPUInstance/Device/Queue raw handles. The shim -// AddRef's each at construction; the wrapper owns those references and -// releases them on delete. +// AddRef's each during sk_graphite_context_make_dawn; Skia takes its own refs +// on success, so the caller may drop their references as soon as that call +// returns. // // fNonYielding: when true, no DawnTickFunction is installed (Skia's // "non-yielding context" mode). Required when running over Emscripten without @@ -31,11 +29,8 @@ typedef struct { bool fNonYielding; } sk_graphite_dawn_backend_context_init_t; -SK_C_API sk_graphite_dawn_backend_context_t* sk_graphite_dawn_backend_context_new(const sk_graphite_dawn_backend_context_init_t* init); -SK_C_API void sk_graphite_dawn_backend_context_delete(sk_graphite_dawn_backend_context_t* bc); - SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( - const sk_graphite_dawn_backend_context_t* bc, + const sk_graphite_dawn_backend_context_init_t* init, const sk_graphite_context_options_t* opts /* nullable -> defaults */); // Wrap an externally-allocated WGPUTexture as a Graphite BackendTexture. The diff --git a/include/c/sk_graphite_metal.h b/include/c/sk_graphite_metal.h index 32d5c887a111..b687dcb3ebea 100644 --- a/include/c/sk_graphite_metal.h +++ b/include/c/sk_graphite_metal.h @@ -13,26 +13,20 @@ SK_C_PLUS_PLUS_BEGIN_GUARD -// Opaque heap wrapper around skgpu::graphite::MtlBackendContext. The shim -// CFRetain's the device/queue handles passed in via the init struct, so the -// caller's references are not consumed. -typedef struct sk_graphite_mtl_backend_context_t sk_graphite_mtl_backend_context_t; - // Init struct: caller-owned id + id. Both are // passed as opaque void* to keep the C API ObjC-free; the shim treats them -// as CFTypeRef and CFRetain's during MakeMetal. +// as CFTypeRef and CFRetain's during MakeMetal. Skia takes its own retains +// on success, so the caller may drop their references as soon as +// sk_graphite_context_make_metal returns. typedef struct { void* fDevice; // id (CFRetain-able) void* fQueue; // id (CFRetain-able) } sk_graphite_mtl_backend_context_init_t; -SK_C_API sk_graphite_mtl_backend_context_t* sk_graphite_mtl_backend_context_new(const sk_graphite_mtl_backend_context_init_t* init); -SK_C_API void sk_graphite_mtl_backend_context_delete(sk_graphite_mtl_backend_context_t* bc); - // Build a Graphite Context for the Metal backend. Returns null on failure // (Metal not built into libSkiaSharp, init invalid, or device rejected). SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal( - const sk_graphite_mtl_backend_context_t* bc, + const sk_graphite_mtl_backend_context_init_t* init, const sk_graphite_context_options_t* opts /* nullable -> defaults */); // Wrap an externally-allocated id as a Graphite BackendTexture. diff --git a/include/c/sk_graphite_vulkan.h b/include/c/sk_graphite_vulkan.h index 8854073a6851..b41d3d082390 100644 --- a/include/c/sk_graphite_vulkan.h +++ b/include/c/sk_graphite_vulkan.h @@ -13,11 +13,6 @@ SK_C_PLUS_PLUS_BEGIN_GUARD -// Opaque handle wrapping the C++ skgpu::VulkanBackendContext value. -// Heap-allocated so that callers can pass it across the FFI without worrying -// about layout differences in the C++ struct between Skia revisions. -typedef struct sk_graphite_vk_backend_context_t sk_graphite_vk_backend_context_t; - // Function-pointer getter signature: identical in shape to skgpu::VulkanGetProc. // userData is the fGetProcUserData passed via the init struct. typedef VKAPI_ATTR void (VKAPI_CALL *sk_graphite_vk_func_ptr)(void); @@ -35,14 +30,13 @@ typedef struct { bool fProtectedContext; } sk_graphite_vk_backend_context_init_t; -// Lifetime: caller owns; pair with sk_graphite_vk_backend_context_delete. -SK_C_API sk_graphite_vk_backend_context_t* sk_graphite_vk_backend_context_new(const sk_graphite_vk_backend_context_init_t* init); -SK_C_API void sk_graphite_vk_backend_context_delete(sk_graphite_vk_backend_context_t* bc); - // Build a Graphite Context for the Vulkan backend. // Returns null on failure (Vulkan not built in, init invalid, or device rejected). +// The dispatch lambda captures fGetProc + fGetProcUserData by value, so the +// caller may free the init struct as soon as this returns. Passed by value to +// mirror gr_direct_context_make_vulkan; the marshaller copies the bytes. SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( - const sk_graphite_vk_backend_context_t* bc, + const sk_graphite_vk_backend_context_init_t init, const sk_graphite_context_options_t* opts /* nullable -> defaults */); // Vulkan-specific TextureInfo (POD). Field values are passed straight into diff --git a/src/c/sk_graphite_dawn.cpp b/src/c/sk_graphite_dawn.cpp index 0fcb13ceebf3..912767431665 100644 --- a/src/c/sk_graphite_dawn.cpp +++ b/src/c/sk_graphite_dawn.cpp @@ -27,40 +27,28 @@ namespace gr = skgpu::graphite; // Returns false if opts carries an invalid value (e.g. out-of-range sample count). extern bool sk_graphite_make_context_options(const sk_graphite_context_options_t* opts, gr::ContextOptions* out); -// Heap-allocated wrapper holding a DawnBackendContext value. The wgpu::Instance/ -// Device/Queue smart pointers AddRef on construction-from-raw and Release on -// destruction, so caller's references remain unaffected by our wrapper's -// lifetime. -struct sk_graphite_dawn_backend_context_t { - gr::DawnBackendContext dbc; -}; - -extern "C" SK_C_API sk_graphite_dawn_backend_context_t* sk_graphite_dawn_backend_context_new(const sk_graphite_dawn_backend_context_init_t* init) { - auto* bc = new sk_graphite_dawn_backend_context_t; +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( + const sk_graphite_dawn_backend_context_init_t* init, + const sk_graphite_context_options_t* opts) +{ // wgpu::Instance/Device/Queue construct-from-raw with AddRef semantics // (the wgpu C++ wrappers' default constructor takes a raw C handle and - // adds a reference unless explicitly told to acquire). - bc->dbc.fInstance = wgpu::Instance(static_cast(init->fInstance)); - bc->dbc.fDevice = wgpu::Device (static_cast (init->fDevice)); - bc->dbc.fQueue = wgpu::Queue (static_cast (init->fQueue)); + // adds a reference unless explicitly told to acquire). The local dbc + // holds those refs for the duration of this call; on success MakeDawn + // takes its own refs into the resulting Context, so the local releases + // at scope-exit are correct whether MakeDawn succeeded or failed. + gr::DawnBackendContext dbc; + dbc.fInstance = wgpu::Instance(static_cast(init->fInstance)); + dbc.fDevice = wgpu::Device (static_cast (init->fDevice)); + dbc.fQueue = wgpu::Queue (static_cast (init->fQueue)); if (init->fNonYielding) { - bc->dbc.fTick = nullptr; + dbc.fTick = nullptr; } // else: leave the default (DawnNativeProcessEventsFunction on non-Emscripten). - return bc; -} - -extern "C" SK_C_API void sk_graphite_dawn_backend_context_delete(sk_graphite_dawn_backend_context_t* bc) { - delete bc; // wgpu::* destructors call Release -} -extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn( - const sk_graphite_dawn_backend_context_t* bc, - const sk_graphite_context_options_t* opts) -{ gr::ContextOptions gopts; if (!sk_graphite_make_context_options(opts, &gopts)) return nullptr; - auto context = gr::ContextFactory::MakeDawn(bc->dbc, gopts); + auto context = gr::ContextFactory::MakeDawn(dbc, gopts); return ToGraphiteContext(context.release()); } @@ -72,9 +60,7 @@ extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_dawn_backend_text #else // !(SK_GRAPHITE && SK_DAWN) -extern "C" SK_C_API sk_graphite_dawn_backend_context_t* sk_graphite_dawn_backend_context_new(const sk_graphite_dawn_backend_context_init_t*) { return nullptr; } -extern "C" SK_C_API void sk_graphite_dawn_backend_context_delete(sk_graphite_dawn_backend_context_t*) {} -extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn(const sk_graphite_dawn_backend_context_t*, const sk_graphite_context_options_t*) { return nullptr; } +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_dawn(const sk_graphite_dawn_backend_context_init_t*, const sk_graphite_context_options_t*) { return nullptr; } extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_dawn_backend_texture_new(void*) { return nullptr; } #endif // SK_GRAPHITE && SK_DAWN diff --git a/src/c/sk_graphite_metal.cpp b/src/c/sk_graphite_metal.cpp index 05329ded619d..bedcdc78190d 100644 --- a/src/c/sk_graphite_metal.cpp +++ b/src/c/sk_graphite_metal.cpp @@ -28,31 +28,21 @@ namespace gr = skgpu::graphite; // Returns false if opts carries an invalid value (e.g. out-of-range sample count). extern bool sk_graphite_make_context_options(const sk_graphite_context_options_t* opts, gr::ContextOptions* out); -// Heap-allocated wrapper holding an MtlBackendContext value. The wrapper -// CFRetains the caller-supplied device/queue at construction (via sk_ret_cfp) -// and CFReleases them on delete (via sk_cfp's destructor). -struct sk_graphite_mtl_backend_context_t { - gr::MtlBackendContext mbc; -}; - -extern "C" SK_C_API sk_graphite_mtl_backend_context_t* sk_graphite_mtl_backend_context_new(const sk_graphite_mtl_backend_context_init_t* init) { - auto* bc = new sk_graphite_mtl_backend_context_t; - bc->mbc.fDevice = sk_ret_cfp(static_cast(init->fDevice)); - bc->mbc.fQueue = sk_ret_cfp(static_cast(init->fQueue)); - return bc; -} - -extern "C" SK_C_API void sk_graphite_mtl_backend_context_delete(sk_graphite_mtl_backend_context_t* bc) { - delete bc; // sk_cfp destructors call CFRelease -} - extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal( - const sk_graphite_mtl_backend_context_t* bc, + const sk_graphite_mtl_backend_context_init_t* init, const sk_graphite_context_options_t* opts) { + // sk_ret_cfp CFRetains the caller's handles. The local mbc holds the + // retains for the duration of this call; on success MakeMetal takes its + // own retains into the resulting Context, so the local releases at + // scope-exit are correct whether MakeMetal succeeded or failed. + gr::MtlBackendContext mbc; + mbc.fDevice = sk_ret_cfp(static_cast(init->fDevice)); + mbc.fQueue = sk_ret_cfp(static_cast(init->fQueue)); + gr::ContextOptions gopts; if (!sk_graphite_make_context_options(opts, &gopts)) return nullptr; - auto context = gr::ContextFactory::MakeMetal(bc->mbc, gopts); + auto context = gr::ContextFactory::MakeMetal(mbc, gopts); return ToGraphiteContext(context.release()); } @@ -67,9 +57,7 @@ extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_mtl_backend_textu #else // !(SK_GRAPHITE && SK_METAL) -extern "C" SK_C_API sk_graphite_mtl_backend_context_t* sk_graphite_mtl_backend_context_new(const sk_graphite_mtl_backend_context_init_t*) { return nullptr; } -extern "C" SK_C_API void sk_graphite_mtl_backend_context_delete(sk_graphite_mtl_backend_context_t*) {} -extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal(const sk_graphite_mtl_backend_context_t*, const sk_graphite_context_options_t*) { return nullptr; } +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal(const sk_graphite_mtl_backend_context_init_t*, const sk_graphite_context_options_t*) { return nullptr; } extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_mtl_backend_texture_new(int32_t, int32_t, void*) { return nullptr; } #endif // SK_GRAPHITE && SK_METAL diff --git a/src/c/sk_graphite_vulkan.cpp b/src/c/sk_graphite_vulkan.cpp index 2e517fde5ee3..e4f40da5d5f0 100644 --- a/src/c/sk_graphite_vulkan.cpp +++ b/src/c/sk_graphite_vulkan.cpp @@ -32,28 +32,10 @@ namespace gr = skgpu::graphite; // Returns false if opts carries an invalid value (e.g. out-of-range sample count). extern bool sk_graphite_make_context_options(const sk_graphite_context_options_t* opts, gr::ContextOptions* out); -// Heap-allocated wrapper holding a copy of the init struct so that the lambda -// captured by the resulting skgpu::VulkanBackendContext::fGetProc has stable -// pointers to fGetProc / fGetProcUserData regardless of caller stack lifetime. -struct sk_graphite_vk_backend_context_t { - sk_graphite_vk_backend_context_init_t init; -}; - -extern "C" SK_C_API sk_graphite_vk_backend_context_t* sk_graphite_vk_backend_context_new(const sk_graphite_vk_backend_context_init_t* init) { - auto* bc = new sk_graphite_vk_backend_context_t{*init}; - return bc; -} - -extern "C" SK_C_API void sk_graphite_vk_backend_context_delete(sk_graphite_vk_backend_context_t* bc) { - delete bc; -} - extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( - const sk_graphite_vk_backend_context_t* bc, + const sk_graphite_vk_backend_context_init_t init, const sk_graphite_context_options_t* opts) { - const auto& init = bc->init; - skgpu::VulkanBackendContext vkbc; vkbc.fInstance = reinterpret_cast(init.fInstance); vkbc.fPhysicalDevice = reinterpret_cast(init.fPhysicalDevice); @@ -64,7 +46,7 @@ extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( vkbc.fProtectedContext = init.fProtectedContext ? skgpu::Protected::kYes : skgpu::Protected::kNo; if (init.fGetProc) { - // Capture by value so the lambda is independent of the wrapper's lifetime. + // Capture by value so the lambda is independent of the init struct's lifetime. sk_graphite_vk_get_proc_t getProc = init.fGetProc; void* userData = init.fGetProcUserData; vkbc.fGetProc = [getProc, userData](const char* name, VkInstance instance, VkDevice device) -> PFN_vkVoidFunction { @@ -134,9 +116,7 @@ extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_vk_backend_textur #else // !(SK_GRAPHITE && SK_VULKAN) -extern "C" SK_C_API sk_graphite_vk_backend_context_t* sk_graphite_vk_backend_context_new(const sk_graphite_vk_backend_context_init_t*) { return nullptr; } -extern "C" SK_C_API void sk_graphite_vk_backend_context_delete(sk_graphite_vk_backend_context_t*) {} -extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan(const sk_graphite_vk_backend_context_t*, const sk_graphite_context_options_t*) { return nullptr; } +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan(const sk_graphite_vk_backend_context_init_t, const sk_graphite_context_options_t*) { return nullptr; } extern "C" SK_C_API sk_graphite_texture_info_t* sk_graphite_vk_texture_info_new(const sk_graphite_vk_texture_info_t*) { return nullptr; } extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_vk_backend_texture_new(int32_t, int32_t, const sk_graphite_vk_texture_info_t*, int32_t, uint32_t, void*) { return nullptr; } diff --git a/src/xamarin/SkiaKeeper.c b/src/xamarin/SkiaKeeper.c index ee97836f1477..b274fba6e5d1 100644 --- a/src/xamarin/SkiaKeeper.c +++ b/src/xamarin/SkiaKeeper.c @@ -115,9 +115,9 @@ void** KeepSkiaCSymbols (void) // !SK_GRAPHITE / !SK_BACKEND branches, so anchoring is safe even // when a backend is disabled. (void*)sk_graphite_backend_is_available, - (void*)sk_graphite_dawn_backend_context_new, - (void*)sk_graphite_mtl_backend_context_new, - (void*)sk_graphite_vk_backend_context_new, + (void*)sk_graphite_context_make_dawn, + (void*)sk_graphite_context_make_metal, + (void*)sk_graphite_context_make_vulkan, // Xamarin (void*)sk_compatpaint_new_with_font, From ff920394e83abd1e69d4f2c6202550699298bb83 Mon Sep 17 00:00:00 2001 From: Ramez Gerges Date: Mon, 25 May 2026 15:30:39 +0000 Subject: [PATCH 14/14] mono/skia: include sk_graphite.h in sk_types_priv.h under SK_GRAPHITE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DEF_MAP_WITH_NS helpers added in 70150e1f13 reference the C-side typedefs sk_graphite_context_t/recorder_t/recording_t/backend_texture_t /texture_info_t. They live in include/c/sk_graphite.h, but sk_types_priv.h only pulled in the C++ skgpu::graphite::* headers — so any translation unit that includes sk_types_priv.h without first including sk_graphite.h fails to compile when SK_GRAPHITE is defined. Xamarin TUs (sk_managedtracememorydump.cpp, sk_compatpaint.cpp, etc.) are the affected callers and broke the WASM build (the first target that defines SK_GRAPHITE while compiling them). Native builds masked the issue because they don't currently set skia_enable_graphite=true. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/c/sk_types_priv.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/c/sk_types_priv.h b/src/c/sk_types_priv.h index 6e44a06bc744..800f34ccf395 100644 --- a/src/c/sk_types_priv.h +++ b/src/c/sk_types_priv.h @@ -198,6 +198,7 @@ DEF_STRUCT_MAP(GrD3DTextureResourceInfo, gr_d3d_textureresourceinfo_t, GrD3DText #endif // SK_GANESH #if defined(SK_GRAPHITE) +#include "include/c/sk_graphite.h" #include "include/gpu/graphite/BackendTexture.h" #include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/Recorder.h"