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
53 changes: 47 additions & 6 deletions include/c/sk_graphite.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ 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;
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;
typedef struct sk_graphite_shader_error_handler_t sk_graphite_shader_error_handler_t;

// Backend identification

Expand All @@ -39,6 +40,39 @@ typedef enum {
// 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);

// Shader compile error callback.
//
// Fires when Graphite hands driver-source shader text (MSL for Metal, SPIR-V/GLSL
// for Vulkan, WGSL for Dawn) to the driver's compiler and the driver rejects it.
// `shader` is the source text; `errors` is the driver's compile error message.
// `shaderWasCached` is Skia's hint about whether this pipeline was already
// present in its internal pipeline cache — typically false on first-time
// failures. Both string pointers are valid only for the duration of the call.
//
// When null, Skia uses its default handler (SkDebugf + assert) — which is
// often invisible on iOS/tvOS. Set this to capture the failing shader text
// for diagnostics (see #4555 for the Graphite-Metal-simulator gradient case
// this callback was added for).
typedef void (*sk_graphite_shader_error_handler_proc)(
void* userData,
const char* shader,
const char* errors,
bool shaderWasCached);

// Build a bridge object that routes Graphite's shader-compile errors to the
// caller's proc + userData. Ownership: the returned handle is caller-owned;
// pass it to sk_graphite_context_options_t.fShaderErrorHandler and, after the
// associated Context has been destroyed, free it with
// sk_graphite_shader_error_handler_delete. Skia's ContextOptions holds a raw
// non-owning pointer to the bridge, so it must outlive the Context — do NOT
// delete the handle before the Context is deleted.
SK_C_API sk_graphite_shader_error_handler_t* sk_graphite_shader_error_handler_new(
sk_graphite_shader_error_handler_proc proc,
void* userData);

SK_C_API void sk_graphite_shader_error_handler_delete(
sk_graphite_shader_error_handler_t* handler);

// ContextOptions (POD, value type)

typedef struct {
Expand All @@ -47,6 +81,13 @@ typedef struct {
int64_t fGpuBudgetInBytes; // -1 to use Skia's default
bool fRequireOrderedRecordings;
bool fSetBackendLabels;

// Optional shader-compile error handler (see _new / _delete above).
// Nullable. When non-null, the caller retains ownership: the Context does
// not free the handle on destruction, mirroring Skia's raw non-owning
// storage. Free the handle with sk_graphite_shader_error_handler_delete
// AFTER the Context has been deleted.
sk_graphite_shader_error_handler_t* fShaderErrorHandler;
} sk_graphite_context_options_t;

SK_C_API void sk_graphite_context_options_init_defaults(sk_graphite_context_options_t* out);
Expand Down
51 changes: 51 additions & 0 deletions src/c/sk_graphite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "include/core/SkImage.h"
#include "include/core/SkSurface.h"
#include "include/gpu/ShaderErrorHandler.h"
#include "include/gpu/graphite/ContextOptions.h"
#include "include/gpu/graphite/GraphiteTypes.h"
#include "include/gpu/graphite/Image.h"
Expand Down Expand Up @@ -64,6 +65,7 @@ extern "C" SK_C_API void sk_graphite_context_options_init_defaults(sk_graphite_c
out->fGpuBudgetInBytes = static_cast<int64_t>(defaults.fGpuBudgetInBytes);
out->fRequireOrderedRecordings = defaults.fRequireOrderedRecordings;
out->fSetBackendLabels = defaults.fSetBackendLabels;
out->fShaderErrorHandler = nullptr;
}

// Translate the public sample-count integer to the SampleCount enum.
Expand Down Expand Up @@ -144,6 +146,49 @@ extern "C" SK_C_API sk_image_t* sk_graphite_image_make_texture(
return ToImage(out.release());
}

// ShaderErrorHandler bridge — routes Graphite's "shader failed to compile"
// hook to a plain C function pointer + userData. Owned by the caller via the
// opaque sk_graphite_shader_error_handler_t handle (see _new / _delete below);
// Skia's ContextOptions holds a raw non-owning pointer to it, so the bridge
// must outlive the Context that references it.
namespace {
class FfiShaderErrorHandler final : public skgpu::ShaderErrorHandler {
public:
FfiShaderErrorHandler(sk_graphite_shader_error_handler_proc proc, void* userData)
: fProc(proc), fUserData(userData) {}

void compileError(const char* shader, const char* errors, bool shaderWasCached) override {
if (fProc) fProc(fUserData, shader, errors, shaderWasCached);
}

private:
sk_graphite_shader_error_handler_proc fProc;
void* fUserData;
};
} // namespace

// Opaque handle: caller-owned bridge object. Cast-only alias so callers
// can pass it through the C ABI while sk_graphite.cpp treats it as a real
// FfiShaderErrorHandler*.
struct sk_graphite_shader_error_handler_t;
static FfiShaderErrorHandler* AsBridge(sk_graphite_shader_error_handler_t* h) {
return reinterpret_cast<FfiShaderErrorHandler*>(h);
}

extern "C" SK_C_API sk_graphite_shader_error_handler_t* sk_graphite_shader_error_handler_new(
sk_graphite_shader_error_handler_proc proc, void* userData)
{
if (!proc) return nullptr;
return reinterpret_cast<sk_graphite_shader_error_handler_t*>(
new FfiShaderErrorHandler(proc, userData));
}

extern "C" SK_C_API void sk_graphite_shader_error_handler_delete(
sk_graphite_shader_error_handler_t* handler)
{
delete AsBridge(handler);
}

// 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
Expand All @@ -166,6 +211,12 @@ bool sk_graphite_make_context_options(const sk_graphite_context_options_t* opts,
}
out->fRequireOrderedRecordings = opts->fRequireOrderedRecordings;
out->fSetBackendLabels = opts->fSetBackendLabels;
// Install shader-compile diagnostic if the caller supplied one. The bridge
// is caller-owned (via sk_graphite_shader_error_handler_new); Skia's
// ContextOptions stores it as a raw non-owning pointer, so the caller must
// keep it alive for the Context's lifetime and free it (via _delete) after
// the Context is destroyed.
out->fShaderErrorHandler = AsBridge(opts->fShaderErrorHandler);
return true;
}

Expand Down
9 changes: 9 additions & 0 deletions src/gpu/graphite/mtl/MtlCaps.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include "src/gpu/graphite/mtl/MtlCaps.h"

#include <TargetConditionals.h>

#include "include/core/SkTextureCompressionType.h"
#include "include/gpu/graphite/TextureInfo.h"
#include "include/gpu/graphite/mtl/MtlGraphiteTypes.h"
Expand Down Expand Up @@ -143,10 +145,17 @@
// Dual source blending requires Metal 1.2, but our minimum requirements ensure 2.2
shaderCaps->fDualSourceBlendingSupport = true;
shaderCaps->fVectorClampMinMaxSupport = !isIntel;
// The iOS/tvOS simulator advertises an Apple GPU family but its Metal implementation
// cannot read from a render target in a fragment shader: the MSL compiles, but
// newRenderPipelineStateWithDescriptor fails with CompilerError Code=2 "reading from a
// rendertarget is not supported" (mono/SkiaSharp#4555). Keep FB fetch off there so dst
// reads fall back to DstReadStrategy::kTextureCopy, which the simulator handles fine.
#if !TARGET_OS_SIMULATOR
if (this->isApple()) {
shaderCaps->fFBFetchSupport = true;
shaderCaps->fFBFetchColorName = "sk_LastFragColor";
}
#endif
}


Expand Down
11 changes: 11 additions & 0 deletions src/gpu/graphite/mtl/MtlGraphicsPipeline.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include "src/gpu/graphite/mtl/MtlGraphicsPipeline.h"

#include "include/gpu/ShaderErrorHandler.h"

#include "include/gpu/graphite/TextureInfo.h"
#include "include/gpu/graphite/mtl/MtlGraphiteTypes.h"
#include "include/private/SkLog.h"
Expand Down Expand Up @@ -428,6 +430,15 @@ static MTLBlendOperation blend_equation_to_mtl_blend_op(skgpu::BlendEquation equ
error:&error]);
if (!pso) {
SKIA_LOG_E("Render pipeline creation failure:\n%s", error.debugDescription.UTF8String);
// A shader can pass the MSL front-end compile yet still be rejected when the render
// pipeline state is created (e.g. framebuffer fetch on the iOS simulator, which fails
// here with "reading from a rendertarget is not supported" — mono/SkiaSharp#4555).
// Route the driver's error through the ShaderErrorHandler so it reaches the same
// diagnostic hook as MSL compile failures instead of only SkDebugf.
std::string diag = "newRenderPipelineStateWithDescriptor failed: ";
diag += error.debugDescription.UTF8String ? error.debugDescription.UTF8String : "(no error)";
sharedContext->caps()->shaderErrorHandler()->compileError(
label.c_str(), diag.c_str(), /*shaderWasCached=*/false);
return nullptr;
}

Expand Down