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..b33292a2d6ba --- /dev/null +++ b/include/c/sk_graphite.h @@ -0,0 +1,285 @@ +/* + * 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, + // 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 +// 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: 0 (use Skia default), 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 struct { + bool fSync; + bool 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. +// 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); + +// 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, + bool mipmapped, + 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 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 +// 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, + 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 +// 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, + bool mipmapped); + +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..e4a6980e24ec --- /dev/null +++ b/include/c/sk_graphite_dawn.h @@ -0,0 +1,50 @@ +/* + * 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 + +// Init struct: caller-owned WGPUInstance/Device/Queue raw handles. The shim +// 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 +// -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 + bool fNonYielding; +} sk_graphite_dawn_backend_context_init_t; + +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 /* 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/include/c/sk_graphite_metal.h b/include/c/sk_graphite_metal.h new file mode 100644 index 000000000000..b687dcb3ebea --- /dev/null +++ b/include/c/sk_graphite_metal.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_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 + +// 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. 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; + +// 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_init_t* init, + 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..b41d3d082390 --- /dev/null +++ b/include/c/sk_graphite_vulkan.h @@ -0,0 +1,78 @@ +/* + * 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 + +// 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; + +// 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_init_t init, + 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 + bool fMipmapped; + 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..a71f1aacad53 --- /dev/null +++ b/src/c/sk_graphite.cpp @@ -0,0 +1,530 @@ +/* + * 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/ContextOptions.h" +#include "include/gpu/graphite/GraphiteTypes.h" +#include "include/gpu/graphite/Image.h" +#include "include/gpu/graphite/ImageProvider.h" +#include "include/gpu/graphite/Surface.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 +#include +#include + +namespace gr = skgpu::graphite; + +// 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) { + 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. +// 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: *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 false; +} + +// 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) +{ + 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, + bool mipmapped) +{ + SkImage::RequiredProperties props; + props.fMipmapped = mipmapped; + auto out = SkImages::TextureFromImage(AsGraphiteRecorder(recorder), AsImage(image), props); + return ToImage(out.release()); +} + +// Public helper used by per-backend factories in sibling translation units. +// 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) { + *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); + } + out->fRequireOrderedRecordings = opts->fRequireOrderedRecordings; + out->fSetBackendLabels = opts->fSetBackendLabels; + return true; +} + +// 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) { + 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; + 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 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) { + 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, sk_graphite_image_provider_t* imageProvider) +{ + 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; + } + 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) { + 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) { + gr::SubmitInfo si; + if (info) { + 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); +} + +// 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) { + 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; + 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) { + return AsGraphiteRecorder(h)->maxTextureSize(); +} + +extern "C" SK_C_API sk_graphite_recording_t* sk_graphite_recorder_snap(sk_graphite_recorder_t* h) { + 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, + bool mipmapped, + const sk_surfaceprops_t* props) +{ + SkImageInfo info = AsImageInfo(cinfo); + skgpu::Mipmapped mips = mipmapped ? 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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) +{ + 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 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) { + 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; + 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) { + 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 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) { + 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; + 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) { + 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; + } + SkUNREACHABLE; +} +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. +// 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) +{ + 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) { + 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 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) { + 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) { + 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, 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*, 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; } +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 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*, bool) { 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..912767431665 --- /dev/null +++ b/src/c/sk_graphite_dawn.cpp @@ -0,0 +1,66 @@ +/* + * 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/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 + +namespace gr = skgpu::graphite; + +// 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); + +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). 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) { + dbc.fTick = nullptr; + } + // else: leave the default (DawnNativeProcessEventsFunction on non-Emscripten). + + gr::ContextOptions gopts; + if (!sk_graphite_make_context_options(opts, &gopts)) return nullptr; + auto context = gr::ContextFactory::MakeDawn(dbc, gopts); + return ToGraphiteContext(context.release()); +} + +extern "C" SK_C_API sk_graphite_backend_texture_t* sk_graphite_dawn_backend_texture_new(void* wgpuTexture) { + auto bt = gr::BackendTextures::MakeDawn(static_cast(wgpuTexture)); + if (!bt.isValid()) return nullptr; + return ToGraphiteBackendTexture(new gr::BackendTexture(bt)); +} + +#else // !(SK_GRAPHITE && SK_DAWN) + +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 new file mode 100644 index 000000000000..bedcdc78190d --- /dev/null +++ b/src/c/sk_graphite_metal.cpp @@ -0,0 +1,63 @@ +/* + * 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/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 + +namespace gr = skgpu::graphite; + +// 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); + +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_metal( + 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(mbc, gopts); + return ToGraphiteContext(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) +{ + auto bt = gr::BackendTextures::MakeMetal(SkISize::Make(width, height), + static_cast(mtlTexture)); + auto* heap = new gr::BackendTexture(bt); + return ToGraphiteBackendTexture(heap); +} + +#else // !(SK_GRAPHITE && SK_METAL) + +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 new file mode 100644 index 000000000000..e4f40da5d5f0 --- /dev/null +++ b/src/c/sk_graphite_vulkan.cpp @@ -0,0 +1,123 @@ +/* + * 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/ContextOptions.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" + +#include + +namespace gr = skgpu::graphite; + +// 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); + +extern "C" SK_C_API sk_graphite_context_t* sk_graphite_context_make_vulkan( + const sk_graphite_vk_backend_context_init_t init, + const sk_graphite_context_options_t* opts) +{ + 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 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 { + 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; + } + } + + gr::ContextOptions gopts; + if (!sk_graphite_make_context_options(opts, &gopts)) return nullptr; + auto context = gr::ContextFactory::MakeVulkan(vkbc, gopts); + return ToGraphiteContext(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 ? 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) { + auto* ti = new gr::TextureInfo(gr::TextureInfos::MakeVulkan(MakeNativeVkTextureInfo(*info))); + return ToGraphiteTextureInfo(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) +{ + 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 ToGraphiteBackendTexture(heap); +} + +#else // !(SK_GRAPHITE && SK_VULKAN) + +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; } + +#endif // SK_GRAPHITE && SK_VULKAN diff --git a/src/c/sk_types_priv.h b/src/c/sk_types_priv.h index 5126becaceba..800f34ccf395 100644 --- a/src/c/sk_types_priv.h +++ b/src/c/sk_types_priv.h @@ -197,6 +197,20 @@ DEF_STRUCT_MAP(GrD3DTextureResourceInfo, gr_d3d_textureresourceinfo_t, GrD3DText #endif // SK_DIRECT3D #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" +#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) diff --git a/src/gpu/graphite/dawn/DawnBuffer.cpp b/src/gpu/graphite/dawn/DawnBuffer.cpp index e39052346cf1..86a870c12232 100644 --- a/src/gpu/graphite/dawn/DawnBuffer.cpp +++ b/src/gpu/graphite/dawn/DawnBuffer.cpp @@ -55,7 +55,20 @@ void log_map_error(WGPUBufferMapAsyncStatus status, const char*) { statusStr = ""; break; } - SKGPU_LOG(priority, "Buffer async map failed with status %s.", statusStr); + // 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 { + 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..64dff6f10c0d 100644 --- a/src/gpu/graphite/dawn/DawnCaps.cpp +++ b/src/gpu/graphite/dawn/DawnCaps.cpp @@ -82,10 +82,13 @@ 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, -#if !defined(__EMSCRIPTEN__) wgpu::TextureFormat::OpaqueYCbCrAndroid, #endif @@ -501,9 +504,13 @@ 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__) if (device.HasFeature(wgpu::FeatureName::DualSourceBlending)) { shaderCaps->fDualSourceBlendingSupport = true; } +#endif #if !defined(__EMSCRIPTEN__) if (device.HasFeature(wgpu::FeatureName::FramebufferFetch)) { shaderCaps->fFBFetchSupport = true; @@ -550,9 +557,15 @@ 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__) + 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/src/xamarin/SkiaKeeper.c b/src/xamarin/SkiaKeeper.c index f90ac430c6f6..b274fba6e5d1 100644 --- a/src/xamarin/SkiaKeeper.c +++ b/src/xamarin/SkiaKeeper.c @@ -50,6 +50,11 @@ #include "include/c/sksg_invalidation_controller.h" #include "include/c/skresources_resource_provider.h" +#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 +109,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_context_make_dawn, + (void*)sk_graphite_context_make_metal, + (void*)sk_graphite_context_make_vulkan, + // Xamarin (void*)sk_compatpaint_new_with_font, (void*)sk_managedstream_new, diff --git a/third_party/dawn/BUILD.gn b/third_party/dawn/BUILD.gn index 128fac065075..793e7980bd4b 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" ] + # 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" ] + } # 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" ] + # 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) { + public_deps = [ ":dawn_cmake" ] + } public_configs = [ ":dawn_api_config" ] }