SkiaWGPURenderer::render_to_texture produces washed-out colors with sRGB texture formats (e.g. Rgba8UnormSrgb)
## 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 条评论