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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
4 changes: 4 additions & 0 deletions gn/core.gni
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
285 changes: 285 additions & 0 deletions include/c/sk_graphite.h
Original file line number Diff line number Diff line change
@@ -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
50 changes: 50 additions & 0 deletions include/c/sk_graphite_dawn.h
Original file line number Diff line number Diff line change
@@ -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
Loading