ITADN

SkiaWGPURenderer::render_to_texture produces washed-out colors with sRGB texture formats (e.g. Rgba8UnormSrgb)

#12458Closedtilladam 创建于 2026-07-13
T
tilladamcommented
## Bug Description `SkiaWGPURenderer::render_to_texture()` renders visibly wrong (too bright / washed-out) colors when the caller-provided target texture has an **sRGB** pixel format such as `wgpu::TextureFormat::Rgba8UnormSrgb`. Content, layout, antialiasing, and text are all correct — only color values are wrong. Non-sRGB formats (`Rgba8Unorm`, `Bgra8Unorm`) render correctly. The doc comment on `render_to_texture` currently lists `Rgba8UnormSrgb` as a supported format ("`Rgba8Unorm` and `Rgba8UnormSrgb` are supported on all backends"), so callers reasonably pick it — but the result is wrong, with no error. **Root cause.** Slint's renderers write *display-ready sRGB* byte values (the software renderer stores them literally, with no `SkColorSpace`). When the render target's pixel format is sRGB, the GPU's fixed-function attachment applies an **sRGB encode on store**, so those already-sRGB bytes are encoded a second time. `wrap_metal_texture` (`internal/renderers/skia/wgpu_29_surface/metal.rs`) wraps the texture with `color_space: None`, so Skia does not decode-to-linear first — the extra encode is uncompensated. The exact amount is one sRGB encode. Reading raw texture bytes (`copy_texture_to_buffer`) for a dark navy `#090f18` = RGB `(9, 15, 24)`: | Target format | Raw bytes read back | Correct? | |-------------------|---------------------|----------| | `Rgba8Unorm` | `(9, 15, 24)` | ✅ literal | | `Bgra8Unorm` | `(9, 15, 24)` | ✅ literal | | `Rgba8UnormSrgb` | `(53, 69, 86)` | ❌ = `sRGB_encode((9,15,24))` | (Mid-tones show it most: `#808080` = 128 → `188` on the sRGB target.) The effect is invisible on pure `0`/`255` primaries because sRGB encode is identity at the endpoints, which is why it can go unnoticed with saturated test colors. **Expected:** rendering into any "supported" target format yields the same colors the software renderer produces (the literal sRGB bytes). **Actual:** sRGB target formats silently apply an extra sRGB encode. Note the **windowed** WGPU-Skia path already avoids this: `WGPUSurface::new_with_surface` (`wgpu_29_surface.rs:60-69`) deliberately filters the swapchain to non-sRGB formats (`matches!(f, Rgba8Unorm | Bgra8Unorm)`). The offscreen `render_to_texture` path does not carry that same invariant. Related theme: [Discussion #4988 "Color Management"](https://github.com/slint-ui/slint/discussions/4988) (Slint renderers' incomplete/inconsistent sRGB handling). This report is a specific, reproducible instance of it on the Skia-WGPU offscreen path. ### Note on fixing at the wrapper level I tried making the Metal wrapper color-manage the sRGB target instead of rejecting it, and it does not work through the public `skia-safe` (0.97.2) surface API on Metal: | `SkColorType` | `SkColorSpace` | raw bytes | result | |---|---|---|---| | `SRGBA8888` | `None` | `(53,69,86)` | wrong (current) | | `SRGBA8888` | `sRGB` | `(53,69,86)` | still wrong (color space has no effect) | | `RGBA8888` | any | — | `wrap_backend_render_target` returns `None` (Skia rejects a non-sRGB color type against an sRGB `MTLPixelFormat`) | So on Metal, an sRGB target can't be made correct at the wrap site; the reliable fix is to render into a non-sRGB target/view. ## Reproducible Code Slint component (any opaque dark background reproduces it): ```slint export component Demo inherits Window { background: #090f18; } ``` Rust harness (essence — render to an offscreen texture and read raw bytes): ```rust // texture: wgpu Rgba8UnormSrgb, RENDER_ATTACHMENT | COPY_SRC renderer.render_to_texture(&texture)?; // SkiaWGPURenderer // copy_texture_to_buffer(...) + map + read: // center pixel reads (53, 69, 86) instead of (9, 15, 24) ``` ## Environment Details - **Slint Version:** current `master` (the `SkiaWGPURenderer` offscreen path from #8979) - **Platform/OS:** macOS (Apple Silicon), Metal backend - **Programming Language:** Rust - **Backend/Renderer:** Skia + WGPU (`unstable-wgpu-29`), offscreen `render_to_texture` - **Not verified on:** Vulkan / DX12 (backend selection is cfg-gated to the host platform, so those paths don't compile on macOS). The mechanism (sRGB pixel format ⇒ hardware encode-on-store) is general to sRGB render targets, so the same symptom is expected there, but I could not test it. ## Product Impact Hit while rendering a Slint UI to a GPU texture for compositing into another engine (the UI is rendered offscreen via `render_to_texture` and sampled elsewhere). Colors were uniformly washed out until the texture format was switched to non-sRGB. Inconvenience rather than a blocker once the cause is known — but silent, and the docs point the wrong way.
关闭于 20 天前 0 条评论