From 1b65e0e4d6b4cd6719de17e38f568ff9ad218bfa Mon Sep 17 00:00:00 2001 From: Cody Mullins <1738479+codymullins@users.noreply.github.com> Date: Tue, 16 Jun 2026 19:17:33 -0400 Subject: [PATCH] feat(gpu): safe, discoverable surface format selection Expose surface-format support so a consumer can ask for a wide-gamut or HDR format and fall back safely, and turn an unsupported format into a managed error instead of a wgpu process abort. - WindowSurfaceCapabilities.Supports(TextureFormat) and ChooseFormat, mirroring the present-mode and alpha-mode helpers. - WindowSurface.Configure validates the requested format against the surface's capabilities, the way it already does for the alpha mode. Addresses #26 --- src/Skyline.Gpu/WindowSurface.cs | 10 ++++++ src/Skyline.Gpu/WindowSurfaceCapabilities.cs | 33 ++++++++++++++++++++ tests/Skyline.WindowedTests/Program.cs | 22 +++++++++++++ 3 files changed, 65 insertions(+) diff --git a/src/Skyline.Gpu/WindowSurface.cs b/src/Skyline.Gpu/WindowSurface.cs index ed6c85b..1faed39 100644 --- a/src/Skyline.Gpu/WindowSurface.cs +++ b/src/Skyline.Gpu/WindowSurface.cs @@ -92,6 +92,16 @@ public void Configure(int pixelWidth, int pixelHeight) $"Supported: {string.Join(", ", Capabilities.AlphaModes.ToArray())}. Use Auto or a supported mode."); } + // An unsupported format aborts wgpu the same way, so reject a + // wide-gamut or HDR format the surface cannot present with a managed + // error. Pick one safely with Capabilities.ChooseFormat. + if (!Capabilities.Supports(_options.Format)) + { + throw new InvalidOperationException( + $"surface format {_options.Format} is not supported by this surface. " + + $"Supported: {string.Join(", ", Capabilities.Formats.ToArray())}. Use Capabilities.ChooseFormat(...)."); + } + var w = Math.Max(1, pixelWidth); var h = Math.Max(1, pixelHeight); var config = new SurfaceConfiguration diff --git a/src/Skyline.Gpu/WindowSurfaceCapabilities.cs b/src/Skyline.Gpu/WindowSurfaceCapabilities.cs index 20f0610..1d14527 100644 --- a/src/Skyline.Gpu/WindowSurfaceCapabilities.cs +++ b/src/Skyline.Gpu/WindowSurfaceCapabilities.cs @@ -29,6 +29,19 @@ internal WindowSurfaceCapabilities(TextureFormat[] formats, PresentMode[] presen public ReadOnlySpan AlphaModes => _alphaModes; + public bool Supports(TextureFormat format) + { + foreach (var f in _formats) + { + if (f == format) + { + return true; + } + } + + return false; + } + public bool Supports(PresentMode mode) { // At most four entries — a scan beats any lookup structure. @@ -72,4 +85,24 @@ public PresentMode ChoosePresentMode(params ReadOnlySpan preferred) return PresentMode.Fifo; } + + /// + /// The first of this surface supports, or the + /// surface's own preferred format ([0]) when none + /// match. Use it to ask for a wide-gamut or high-dynamic-range format — + /// such as Rgba16Float — with a safe fallback, then pass the result as + /// . + /// + public TextureFormat ChooseFormat(params ReadOnlySpan preferred) + { + foreach (var format in preferred) + { + if (Supports(format)) + { + return format; + } + } + + return _formats[0]; + } } diff --git a/tests/Skyline.WindowedTests/Program.cs b/tests/Skyline.WindowedTests/Program.cs index 8694c73..934783e 100644 --- a/tests/Skyline.WindowedTests/Program.cs +++ b/tests/Skyline.WindowedTests/Program.cs @@ -127,6 +127,28 @@ void Check(bool condition, string what) Check(alphaOkGpu.Surface.PixelSize.Width > 0, "Configure accepts a supported alpha mode"); } + // Surface format selection: report support, choose with a fallback, and + // reject an unsupported format with a managed error instead of a wgpu abort. + Check(caps.Supports(surface.Format), "the configured surface format is supported"); + var chosenFormat = caps.ChooseFormat(TextureFormat.Rgba16float, caps.Formats[0]); + Check(caps.Supports(chosenFormat), "ChooseFormat returns a supported format"); + Check(caps.ChooseFormat((TextureFormat)0x6FFFFFFF) == caps.Formats[0], "ChooseFormat falls back to the preferred format"); + + var unsupportedFormat = TextureFormat.R8Unorm; + var foundUnsupportedFormat = false; + foreach (var f in new[] { TextureFormat.R8Unorm, TextureFormat.RG8Unorm, TextureFormat.R16float, TextureFormat.R32float }) + { + if (!caps.Supports(f)) { unsupportedFormat = f; foundUnsupportedFormat = true; break; } + } + Check(foundUnsupportedFormat && !caps.Supports(unsupportedFormat), "the surface rejects at least one single-channel format"); + using (var fmtGpu = GpuContext.Create(win.Surface!, surfaceOptions: new WindowSurfaceOptions { Format = unsupportedFormat })) + { + var fmtThrew = false; + try { fmtGpu.Surface!.Configure(frame.PixelWidth, frame.PixelHeight); } + catch (InvalidOperationException) { fmtThrew = true; } + Check(fmtThrew, "Configure throws on an unsupported format"); + } + surface.PresentMode = caps.ChoosePresentMode(PresentMode.Fifo); Check(surface.PresentMode == PresentMode.Fifo, "PresentMode property holds the choice"); Check(surface.Format == TextureFormat.Bgra8Unorm, "Format reflects options");