diff --git a/packages/skia/android/cpp/jni/JniWebGPUView.cpp b/packages/skia/android/cpp/jni/JniWebGPUView.cpp index 99d3ce242b..215a92bc58 100644 --- a/packages/skia/android/cpp/jni/JniWebGPUView.cpp +++ b/packages/skia/android/cpp/jni/JniWebGPUView.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #ifdef SK_GRAPHITE #include "webgpu/webgpu_cpp.h" @@ -7,16 +9,42 @@ #include "rnskia/RNDawnContext.h" #endif +#if __ANDROID_API__ >= 28 +#include + +static void applyColorSpace(JNIEnv *env, ANativeWindow *window, + jstring jColorSpace) { + int32_t dataSpace = ADATASPACE_SRGB; + if (jColorSpace != nullptr) { + const char *cs = env->GetStringUTFChars(jColorSpace, nullptr); + if (strcmp(cs, "display-p3") == 0) { + dataSpace = ADATASPACE_DISPLAY_P3; + } else if (strcmp(cs, "bt2020-hlg") == 0) { + dataSpace = ADATASPACE_BT2020_HLG; + } else if (strcmp(cs, "bt2020-pq") == 0) { + dataSpace = ADATASPACE_BT2020_PQ; + } + env->ReleaseStringUTFChars(jColorSpace, cs); + } + ANativeWindow_setBuffersDataSpace(window, dataSpace); +} +#endif + extern "C" JNIEXPORT void JNICALL Java_com_shopify_reactnative_skia_WebGPUView_onSurfaceCreate( JNIEnv *env, jobject thiz, jobject jSurface, jint contextId, jfloat width, - jfloat height) { + jfloat height, jstring jColorSpace) { #ifdef SK_GRAPHITE auto window = ANativeWindow_fromSurface(env, jSurface); auto ®istry = rnwgpu::SurfaceRegistry::getInstance(); auto &dawnContext = RNSkia::DawnContext::getInstance(); auto gpu = dawnContext.getWGPUInstance(); +#if __ANDROID_API__ >= 28 + // Set color space on the native window + applyColorSpace(env, window, jColorSpace); +#endif + // Create surface from ANativeWindow wgpu::SurfaceSourceAndroidNativeWindow androidSurfaceDesc; androidSurfaceDesc.window = window; diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/WebGPUView.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/WebGPUView.java index b8f75d3ca2..454c441ce2 100644 --- a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/WebGPUView.java +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/WebGPUView.java @@ -11,6 +11,7 @@ public class WebGPUView extends ReactViewGroup implements WebGPUViewAPI { private int mContextId; private boolean mTransparent = false; + private String mColorSpace = null; private View mView = null; WebGPUView(Context context) { @@ -21,6 +22,10 @@ public void setContextId(int contextId) { mContextId = contextId; } + public void setColorSpace(String value) { + mColorSpace = value; + } + public void setTransparent(boolean value) { Context ctx = getContext(); if (value != mTransparent || mView == null) { @@ -50,7 +55,7 @@ public void surfaceCreated(Surface surface) { float density = getResources().getDisplayMetrics().density; float width = getWidth() / density; float height = getHeight() / density; - onSurfaceCreate(surface, mContextId, width, height); + onSurfaceCreate(surface, mContextId, width, height, mColorSpace); } @Override @@ -76,7 +81,8 @@ private native void onSurfaceCreate( Surface surface, int contextId, float width, - float height + float height, + String colorSpace ); @DoNotStrip diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/WebGPUViewManager.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/WebGPUViewManager.java index 3065299e2b..8c671bf469 100644 --- a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/WebGPUViewManager.java +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/WebGPUViewManager.java @@ -50,6 +50,12 @@ public void setContextId(WebGPUView view, int value) { view.setContextId(value); } + @Override + @ReactProp(name = "colorSpace") + public void setColorSpace(WebGPUView view, @Nullable String value) { + view.setColorSpace(value); + } + @Override public void onDropViewInstance(@NonNull ReactViewGroup view) { super.onDropViewInstance(view); diff --git a/packages/skia/apple/WebGPUMetalView.h b/packages/skia/apple/WebGPUMetalView.h index c17a710611..0547304333 100644 --- a/packages/skia/apple/WebGPUMetalView.h +++ b/packages/skia/apple/WebGPUMetalView.h @@ -5,6 +5,7 @@ @interface WebGPUMetalView : RNSkPlatformView @property (nonatomic, strong) NSNumber *contextId; +@property (nonatomic, strong) NSString *colorSpace; - (void)configure; - (void)update; diff --git a/packages/skia/apple/WebGPUMetalView.mm b/packages/skia/apple/WebGPUMetalView.mm index b7404646fc..37ccc699c4 100644 --- a/packages/skia/apple/WebGPUMetalView.mm +++ b/packages/skia/apple/WebGPUMetalView.mm @@ -12,6 +12,44 @@ @implementation WebGPUMetalView { BOOL _isConfigured; } +- (void)applyColorSpace { + CAMetalLayer *metalLayer = (CAMetalLayer *)self.layer; + CGColorSpaceRef cs = NULL; + BOOL needsEDR = NO; + if ([_colorSpace isEqualToString:@"display-p3"]) { + cs = CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3); + } else if ([_colorSpace isEqualToString:@"bt2020-hlg"]) { + cs = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2100_HLG); + needsEDR = YES; + } else if ([_colorSpace isEqualToString:@"bt2020-pq"]) { + cs = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2100_PQ); + needsEDR = YES; + } else { + cs = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + } + metalLayer.colorspace = cs; + CGColorSpaceRelease(cs); +#if !TARGET_OS_OSX + metalLayer.wantsExtendedDynamicRangeContent = needsEDR; +#endif + + // Set surface format — HDR needs RGBA16Float + auto ®istry = rnwgpu::SurfaceRegistry::getInstance(); + auto surfaceInfo = registry.getSurfaceInfo([_contextId intValue]); + if (surfaceInfo) { + surfaceInfo->setFormatOverride(needsEDR + ? wgpu::TextureFormat::RGBA16Float + : wgpu::TextureFormat::Undefined); + } +} + +- (void)setColorSpace:(NSString *)colorSpace { + _colorSpace = colorSpace; + if (_isConfigured) { + [self applyColorSpace]; + } +} + #if !TARGET_OS_OSX + (Class)layerClass { return [CAMetalLayer class]; @@ -45,6 +83,9 @@ - (void)configure { .getSurfaceInfoOrCreate([_contextId intValue], gpu, size.width, size.height) ->switchToOnscreen(nativeSurface, surface); + + _isConfigured = YES; + [self applyColorSpace]; } - (void)update { diff --git a/packages/skia/apple/WebGPUView.mm b/packages/skia/apple/WebGPUView.mm index 04f99e2c62..1b2684ec6d 100644 --- a/packages/skia/apple/WebGPUView.mm +++ b/packages/skia/apple/WebGPUView.mm @@ -58,9 +58,19 @@ - (void)updateProps:(const Props::Shared &)props WebGPUMetalView *metalView = [WebGPUMetalView new]; self.contentView = metalView; [metalView setContextId:@(newViewProps.contextId)]; + if (!newViewProps.colorSpace.empty()) { + [metalView setColorSpace:[NSString stringWithUTF8String:newViewProps.colorSpace.c_str()]]; + } [metalView configure]; } + if (newViewProps.colorSpace != oldViewProps.colorSpace) { + NSString *cs = newViewProps.colorSpace.empty() + ? nil + : [NSString stringWithUTF8String:newViewProps.colorSpace.c_str()]; + [(WebGPUMetalView *)self.contentView setColorSpace:cs]; + } + [super updateProps:props oldProps:oldProps]; } diff --git a/packages/skia/cpp/rnskia/RNDawnContext.h b/packages/skia/cpp/rnskia/RNDawnContext.h index 7d56ca22cf..ed82cf09c4 100644 --- a/packages/skia/cpp/rnskia/RNDawnContext.h +++ b/packages/skia/cpp/rnskia/RNDawnContext.h @@ -188,6 +188,27 @@ class DawnContext { // Get the wgpu::Device for WebGPU bindings wgpu::Device getWGPUDevice() { return backendContext.fDevice; } + // Create a secondary Dawn device from the same adapter. + // Has its own command queue and does NOT enable ImplicitDeviceSynchronization, + // so it won't contend with the primary rendering device's mutex. + // Safe for concurrent GPU work (e.g. ML inference) alongside Skia rendering. + wgpu::Device createSecondaryDevice() { + auto adapter = DawnUtils::getMatchedAdapter(instance.get()); + + std::vector features = { + wgpu::FeatureName::BufferMapExtendedUsages, +#ifdef __APPLE__ + wgpu::FeatureName::SharedTextureMemoryIOSurface, + wgpu::FeatureName::DawnMultiPlanarFormats, + // Note: SharedFenceMTLSharedEvent intentionally NOT enabled — it causes + // EndAccess to encode fence signals that crash with "uncommitted encoder". + // IOSurface data is already written by the camera before we read it. +#endif + }; + + return DawnUtils::requestDevice(adapter, features, false); + } + // Create an SkImage from a WebGPU texture // The texture must have TextureBinding usage sk_sp MakeImageFromTexture(wgpu::Texture texture, int width, diff --git a/packages/skia/cpp/rnskia/RNDawnUtils.h b/packages/skia/cpp/rnskia/RNDawnUtils.h index 8bfdc2ff08..8ae490cf8b 100644 --- a/packages/skia/cpp/rnskia/RNDawnUtils.h +++ b/packages/skia/cpp/rnskia/RNDawnUtils.h @@ -21,35 +21,21 @@ static const wgpu::TextureFormat PreferredTextureFormat = wgpu::TextureFormat::RGBA8Unorm; #endif -inline skgpu::graphite::DawnBackendContext -createDawnBackendContext(dawn::native::Instance *instance) { - - auto useTintIR = false; - static constexpr const char *kToggles[] = { -#if !defined(SK_DEBUG) - "skip_validation", -#endif - "disable_lazy_clear_for_mapped_at_creation_buffer", - "allow_unsafe_apis", - "use_user_defined_labels_in_backend", - "disable_robustness", - "use_tint_ir", - }; - wgpu::DawnTogglesDescriptor togglesDesc; - togglesDesc.enabledToggleCount = std::size(kToggles) - (useTintIR ? 0 : 1); - togglesDesc.enabledToggles = kToggles; - - dawn::native::Adapter matchedAdaptor; - - wgpu::RequestAdapterOptions options; +// Find the best matching GPU adapter for the current platform. +// Sorts by adapter type (DiscreteGPU > IntegratedGPU > CPU) and selects the +// first adapter matching the platform backend (Metal on Apple, Vulkan on +// Android). +inline dawn::native::Adapter +getMatchedAdapter(dawn::native::Instance *instance) { #ifdef __APPLE__ constexpr auto kDefaultBackendType = wgpu::BackendType::Metal; #elif __ANDROID__ constexpr auto kDefaultBackendType = wgpu::BackendType::Vulkan; #endif + + wgpu::RequestAdapterOptions options; options.backendType = kDefaultBackendType; options.featureLevel = wgpu::FeatureLevel::Core; - options.nextInChain = &togglesDesc; std::vector adapters = instance->EnumerateAdapters(&options); @@ -57,8 +43,6 @@ createDawnBackendContext(dawn::native::Instance *instance) { throw std::runtime_error("No matching adapter found"); } - // Sort adapters by adapterType(DiscreteGPU, IntegratedGPU, CPU) and - // backendType(Metal, Vulkan, OpenGL, OpenGLES, WebGPU). std::sort(adapters.begin(), adapters.end(), [](dawn::native::Adapter a, dawn::native::Adapter b) { wgpu::Adapter wgpuA = a.Get(); @@ -76,16 +60,93 @@ createDawnBackendContext(dawn::native::Instance *instance) { wgpu::AdapterInfo props; wgpuAdapter.GetInfo(&props); if (kDefaultBackendType == props.backendType) { - matchedAdaptor = adapter; - break; + return adapter; } } - if (!matchedAdaptor) { - throw std::runtime_error("No matching adapter found"); + throw std::runtime_error("No matching adapter found"); +} + +// Create a Dawn device from the given adapter with the requested features. +// Features not supported by the adapter are silently skipped. +// Set fatalOnDeviceLost=true for primary rendering devices (SK_ABORT on loss), +// false for secondary/compute devices (log only). +inline wgpu::Device +requestDevice(dawn::native::Adapter &nativeAdapter, + const std::vector &requestedFeatures, + bool fatalOnDeviceLost = true) { + wgpu::Adapter adapter = nativeAdapter.Get(); + + // Filter to only features the adapter supports + std::vector features; + for (auto feature : requestedFeatures) { + if (adapter.HasFeature(feature)) { + features.push_back(feature); + } } - wgpu::Adapter adapter = matchedAdaptor.Get(); + static constexpr const char *kToggles[] = { +#if !defined(SK_DEBUG) + "skip_validation", +#endif + "disable_lazy_clear_for_mapped_at_creation_buffer", + "allow_unsafe_apis", + "use_user_defined_labels_in_backend", + "disable_robustness", + }; + wgpu::DawnTogglesDescriptor togglesDesc; + togglesDesc.enabledToggleCount = std::size(kToggles); + togglesDesc.enabledToggles = kToggles; + + wgpu::DeviceDescriptor desc; + desc.requiredFeatureCount = features.size(); + desc.requiredFeatures = features.data(); + desc.nextInChain = &togglesDesc; + + if (fatalOnDeviceLost) { + desc.SetDeviceLostCallback( + wgpu::CallbackMode::AllowSpontaneous, + [](const wgpu::Device &, wgpu::DeviceLostReason reason, + wgpu::StringView message) { + if (reason != wgpu::DeviceLostReason::Destroyed) { + SK_ABORT("Device lost: %.*s\n", + static_cast(message.length), message.data); + } + }); + desc.SetUncapturedErrorCallback( + [](const wgpu::Device &, wgpu::ErrorType, + wgpu::StringView message) { + SkDebugf("Device error: %.*s\n", + static_cast(message.length), message.data); + }); + } else { + desc.SetDeviceLostCallback( + wgpu::CallbackMode::AllowSpontaneous, + [](const wgpu::Device &, wgpu::DeviceLostReason reason, + wgpu::StringView message) { + if (reason != wgpu::DeviceLostReason::Destroyed) { + RNSkia::RNSkLogger::logToConsole( + "Device lost: %.*s", + static_cast(message.length), message.data); + } + }); + desc.SetUncapturedErrorCallback( + [](const wgpu::Device &, wgpu::ErrorType, + wgpu::StringView message) { + RNSkia::RNSkLogger::logToConsole( + "Device error: %.*s", + static_cast(message.length), message.data); + }); + } + + return wgpu::Device::Acquire(nativeAdapter.CreateDevice(&desc)); +} + +inline skgpu::graphite::DawnBackendContext +createDawnBackendContext(dawn::native::Instance *instance) { + + auto matchedAdapter = getMatchedAdapter(instance); + wgpu::Adapter adapter = matchedAdapter.Get(); // Log selected adapter info wgpu::AdapterInfo adapterInfo; @@ -130,93 +191,34 @@ createDawnBackendContext(dawn::native::Instance *instance) { "Selected Dawn adapter - Backend: %s, Device: %s, Description: %s", backendName.c_str(), deviceName.c_str(), description.c_str()); - std::vector features; - if (adapter.HasFeature(wgpu::FeatureName::MSAARenderToSingleSampled)) { - features.push_back(wgpu::FeatureName::MSAARenderToSingleSampled); - } - if (adapter.HasFeature(wgpu::FeatureName::TransientAttachments)) { - features.push_back(wgpu::FeatureName::TransientAttachments); - } - if (adapter.HasFeature(wgpu::FeatureName::Unorm16TextureFormats)) { - features.push_back(wgpu::FeatureName::Unorm16TextureFormats); - } - if (adapter.HasFeature(wgpu::FeatureName::DualSourceBlending)) { - features.push_back(wgpu::FeatureName::DualSourceBlending); - } - if (adapter.HasFeature(wgpu::FeatureName::FramebufferFetch)) { - features.push_back(wgpu::FeatureName::FramebufferFetch); - } - if (adapter.HasFeature(wgpu::FeatureName::BufferMapExtendedUsages)) { - features.push_back(wgpu::FeatureName::BufferMapExtendedUsages); - } - if (adapter.HasFeature(wgpu::FeatureName::TextureCompressionETC2)) { - features.push_back(wgpu::FeatureName::TextureCompressionETC2); - } - if (adapter.HasFeature(wgpu::FeatureName::TextureCompressionBC)) { - features.push_back(wgpu::FeatureName::TextureCompressionBC); - } - if (adapter.HasFeature(wgpu::FeatureName::R8UnormStorage)) { - features.push_back(wgpu::FeatureName::R8UnormStorage); - } - if (adapter.HasFeature(wgpu::FeatureName::DawnLoadResolveTexture)) { - features.push_back(wgpu::FeatureName::DawnLoadResolveTexture); - } - if (adapter.HasFeature(wgpu::FeatureName::DawnPartialLoadResolveTexture)) { - features.push_back(wgpu::FeatureName::DawnPartialLoadResolveTexture); - } - if (adapter.HasFeature(wgpu::FeatureName::TimestampQuery)) { - features.push_back(wgpu::FeatureName::TimestampQuery); - } - if (adapter.HasFeature(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment)) { - features.push_back(wgpu::FeatureName::DawnTexelCopyBufferRowAlignment); - } - if (adapter.HasFeature(wgpu::FeatureName::ImplicitDeviceSynchronization)) { - features.push_back(wgpu::FeatureName::ImplicitDeviceSynchronization); - } + // Primary device features — request everything Skia/Graphite can use + std::vector features = { + wgpu::FeatureName::MSAARenderToSingleSampled, + wgpu::FeatureName::TransientAttachments, + wgpu::FeatureName::Unorm16TextureFormats, + wgpu::FeatureName::DualSourceBlending, + wgpu::FeatureName::FramebufferFetch, + wgpu::FeatureName::BufferMapExtendedUsages, + wgpu::FeatureName::TextureCompressionETC2, + wgpu::FeatureName::TextureCompressionBC, + wgpu::FeatureName::R8UnormStorage, + wgpu::FeatureName::DawnLoadResolveTexture, + wgpu::FeatureName::DawnPartialLoadResolveTexture, + wgpu::FeatureName::TimestampQuery, + wgpu::FeatureName::DawnTexelCopyBufferRowAlignment, + wgpu::FeatureName::ImplicitDeviceSynchronization, #ifdef __APPLE__ - if (adapter.HasFeature(wgpu::FeatureName::SharedTextureMemoryIOSurface)) { - features.push_back(wgpu::FeatureName::SharedTextureMemoryIOSurface); - } - if (adapter.HasFeature(wgpu::FeatureName::DawnMultiPlanarFormats)) { - features.push_back(wgpu::FeatureName::DawnMultiPlanarFormats); - } - if (adapter.HasFeature(wgpu::FeatureName::MultiPlanarFormatP010)) { - features.push_back(wgpu::FeatureName::MultiPlanarFormatP010); - } - if (adapter.HasFeature(wgpu::FeatureName::MultiPlanarFormatP210)) { - features.push_back(wgpu::FeatureName::MultiPlanarFormatP210); - } - if (adapter.HasFeature(wgpu::FeatureName::MultiPlanarFormatExtendedUsages)) { - features.push_back(wgpu::FeatureName::MultiPlanarFormatExtendedUsages); - } + wgpu::FeatureName::SharedTextureMemoryIOSurface, + wgpu::FeatureName::DawnMultiPlanarFormats, + wgpu::FeatureName::MultiPlanarFormatP010, + wgpu::FeatureName::MultiPlanarFormatP210, + wgpu::FeatureName::MultiPlanarFormatExtendedUsages, #else - if (adapter.HasFeature( - wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer)) { - features.push_back(wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer); - } + wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer, #endif + }; - wgpu::DeviceDescriptor desc; - desc.requiredFeatureCount = features.size(); - desc.requiredFeatures = features.data(); - desc.nextInChain = &togglesDesc; - desc.SetDeviceLostCallback( - wgpu::CallbackMode::AllowSpontaneous, - [](const wgpu::Device &, wgpu::DeviceLostReason reason, - wgpu::StringView message) { - if (reason != wgpu::DeviceLostReason::Destroyed) { - SK_ABORT("Device lost: %.*s\n", static_cast(message.length), - message.data); - } - }); - desc.SetUncapturedErrorCallback( - [](const wgpu::Device &, wgpu::ErrorType, wgpu::StringView message) { - SkDebugf("Device error: %.*s\n", static_cast(message.length), - message.data); - }); - - wgpu::Device device = - wgpu::Device::Acquire(matchedAdaptor.CreateDevice(&desc)); + wgpu::Device device = requestDevice(matchedAdapter, features, true); SkASSERT(device); skgpu::graphite::DawnBackendContext backendContext; @@ -227,4 +229,4 @@ createDawnBackendContext(dawn::native::Instance *instance) { return backendContext; } -} // namespace DawnUtils \ No newline at end of file +} // namespace DawnUtils diff --git a/packages/skia/cpp/rnwgpu/SurfaceRegistry.h b/packages/skia/cpp/rnwgpu/SurfaceRegistry.h index e41de864a6..db8313faac 100644 --- a/packages/skia/cpp/rnwgpu/SurfaceRegistry.h +++ b/packages/skia/cpp/rnwgpu/SurfaceRegistry.h @@ -34,9 +34,22 @@ class SurfaceInfo { _configure(); } + void setFormatOverride(wgpu::TextureFormat format) { + std::unique_lock lock(_mutex); + _formatOverride = format; + // If already configured, reconfigure now + if (config.device) { + config.format = format; + _configure(); + } + } + void configure(wgpu::SurfaceConfiguration &newConfig) { std::unique_lock lock(_mutex); config = newConfig; + if (_formatOverride != wgpu::TextureFormat::Undefined) { + config.format = _formatOverride; + } config.width = width; config.height = height; config.presentMode = wgpu::PresentMode::Fifo; @@ -150,6 +163,11 @@ class SurfaceInfo { return config.device; } + wgpu::TextureFormat getFormatOverride() { + std::shared_lock lock(_mutex); + return _formatOverride; + } + private: void _configure() { if (surface) { @@ -172,6 +190,7 @@ class SurfaceInfo { wgpu::Texture texture = nullptr; wgpu::Instance gpu; wgpu::SurfaceConfiguration config; + wgpu::TextureFormat _formatOverride = wgpu::TextureFormat::Undefined; int width; int height; }; diff --git a/packages/skia/cpp/rnwgpu/api/GPUCanvasContext.cpp b/packages/skia/cpp/rnwgpu/api/GPUCanvasContext.cpp index 217d8445d3..e59e94769c 100644 --- a/packages/skia/cpp/rnwgpu/api/GPUCanvasContext.cpp +++ b/packages/skia/cpp/rnwgpu/api/GPUCanvasContext.cpp @@ -28,6 +28,13 @@ void GPUCanvasContext::configure( !conv(surfaceConfiguration.format, configuration->format)) { throw std::runtime_error("Error with SurfaceConfiguration"); } + // Check for format override from native colorSpace prop + { + auto overrideFormat = _surfaceInfo->getFormatOverride(); + if (overrideFormat != wgpu::TextureFormat::Undefined) { + surfaceConfiguration.format = overrideFormat; + } + } #ifdef __APPLE__ surfaceConfiguration.alphaMode = configuration->alphaMode; diff --git a/packages/skia/src/specs/WebGPUViewNativeComponent.ts b/packages/skia/src/specs/WebGPUViewNativeComponent.ts index 7a601a307b..3ecd74c9ab 100644 --- a/packages/skia/src/specs/WebGPUViewNativeComponent.ts +++ b/packages/skia/src/specs/WebGPUViewNativeComponent.ts @@ -5,6 +5,7 @@ import type { ViewProps } from "react-native"; export interface NativeProps extends ViewProps { contextId: Int32; transparent: boolean; + colorSpace?: string; } // eslint-disable-next-line import/no-default-export diff --git a/packages/skia/src/views/WebGPUCanvas.tsx b/packages/skia/src/views/WebGPUCanvas.tsx index 1456b4e088..a7bee3eebe 100644 --- a/packages/skia/src/views/WebGPUCanvas.tsx +++ b/packages/skia/src/views/WebGPUCanvas.tsx @@ -44,11 +44,13 @@ export interface WebGPUCanvasRef { interface WebGPUCanvasProps extends ViewProps { transparent?: boolean; + colorSpace?: "srgb" | "display-p3" | "bt2020-hlg" | "bt2020-pq"; ref?: React.Ref; } export const WebGPUCanvas = ({ transparent, + colorSpace, ref, ...props }: WebGPUCanvasProps) => { @@ -103,6 +105,7 @@ export const WebGPUCanvas = ({ style={{ flex: 1 }} contextId={contextId} transparent={!!transparent} + colorSpace={colorSpace} /> );