Merge branch 'fix/hdr-sdr-plane' into feat/xwayland-builtin
2 files · +41 -0
M internal/adapters/drm/colorop_match.go +11 -0
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "encoding/binary" | |
| 5 | 5 | "fmt" | |
| 6 | 6 | "math" | |
| 7 | + | "os" | |
| 7 | 8 | "slices" | |
| 8 | 9 | "strings" | |
| 9 | 10 | ||
| @@ -244,6 +245,13 @@ func ctmBytes(m [3][3]float64) []byte { | |||
| 244 | 245 | return b | |
| 245 | 246 | } | |
| 246 | 247 | ||
| 248 | + | // sdrPlanePipeline lets sRGB buffers on an HDR output scan out through the | |
| 249 | + | // plane colour pipeline. Off by default: on amdgpu (DP-2, 5120x2160) that path | |
| 250 | + | // looked oversaturated against the composed one and its cause is not yet | |
| 251 | + | // established; the buffers are composed ("hdr_sdr_content"). Set | |
| 252 | + | // NEFERWL_DRM_SDR_PLANE=1 to test the pipeline path. | |
| 253 | + | var sdrPlanePipeline = os.Getenv("NEFERWL_DRM_SDR_PLANE") == "1" | |
| 254 | + | ||
| 247 | 255 | // planeColor decides how plane (with pipeline, nil: none) shows a client | |
| 248 | 256 | // buffer of format whose content is c, on an output in HDR10 mode when hdrOn. | |
| 249 | 257 | // opaque is the surface's Opaque. A buffer already in the output's encoding | |
| @@ -267,6 +275,9 @@ func planeColor(c ports.SurfaceColor, format uint32, opaque, hdrOn bool, pipelin | |||
| 267 | 275 | return colorBypass, "hdr_format" | |
| 268 | 276 | } | |
| 269 | 277 | return colorBypass, "" | |
| 278 | + | case !sdrPlanePipeline: | |
| 279 | + | // Unverified on hardware: the owner saw oversaturated colours. | |
| 280 | + | return colorBypass, "hdr_sdr_content" | |
| 270 | 281 | case pipeline == nil || !opaque || !isSDRColor(c): | |
| 271 | 282 | // The kernel applies the pipeline to premultiplied pixels: exact | |
| 272 | 283 | // only without alpha. | |
| @@ -4,6 +4,7 @@ import ( | |||||
| 4 | "encoding/binary" | 4 | "encoding/binary" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | "math" | 6 | "math" | ||
| 7 | + | "os" | |||
| 7 | "slices" | 8 | "slices" | ||
| 8 | "strings" | 9 | "strings" | ||
| 9 | 10 | ||||
| @@ -244,6 +245,13 @@ func ctmBytes(m [3][3]float64) []byte { | |||||
| 244 | return b | 245 | return b | ||
| 245 | } | 246 | } | ||
| 246 | 247 | ||||
| 248 | + | // sdrPlanePipeline lets sRGB buffers on an HDR output scan out through the | |||
| 249 | + | // plane colour pipeline. Off by default: on amdgpu (DP-2, 5120x2160) that path | |||
| 250 | + | // looked oversaturated against the composed one and its cause is not yet | |||
| 251 | + | // established; the buffers are composed ("hdr_sdr_content"). Set | |||
| 252 | + | // NEFERWL_DRM_SDR_PLANE=1 to test the pipeline path. | |||
| 253 | + | var sdrPlanePipeline = os.Getenv("NEFERWL_DRM_SDR_PLANE") == "1" | |||
| 254 | + | ||||
| 247 | // planeColor decides how plane (with pipeline, nil: none) shows a client | 255 | // planeColor decides how plane (with pipeline, nil: none) shows a client | ||
| 248 | // buffer of format whose content is c, on an output in HDR10 mode when hdrOn. | 256 | // buffer of format whose content is c, on an output in HDR10 mode when hdrOn. | ||
| 249 | // opaque is the surface's Opaque. A buffer already in the output's encoding | 257 | // opaque is the surface's Opaque. A buffer already in the output's encoding | ||
| @@ -267,6 +275,9 @@ func planeColor(c ports.SurfaceColor, format uint32, opaque, hdrOn bool, pipelin | |||||
| 267 | return colorBypass, "hdr_format" | 275 | return colorBypass, "hdr_format" | ||
| 268 | } | 276 | } | ||
| 269 | return colorBypass, "" | 277 | return colorBypass, "" | ||
| 278 | + | case !sdrPlanePipeline: | |||
| 279 | + | // Unverified on hardware: the owner saw oversaturated colours. | |||
| 280 | + | return colorBypass, "hdr_sdr_content" | |||
| 270 | case pipeline == nil || !opaque || !isSDRColor(c): | 281 | case pipeline == nil || !opaque || !isSDRColor(c): | ||
| 271 | // The kernel applies the pipeline to premultiplied pixels: exact | 282 | // The kernel applies the pipeline to premultiplied pixels: exact | ||
| 272 | // only without alpha. | 283 | // only without alpha. | ||
A internal/adapters/drm/sdrplane_test.go +30 -0
| @@ -0,0 +1,30 @@ | |||
| 1 | + | package drm | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "encoding/binary" | |
| 5 | + | "testing" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/neferwl/internal/ports" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func init() { sdrPlanePipeline = true } // the pipeline tests exercise the path itself | |
| 11 | + | ||
| 12 | + | // Golden bytes: row-major 3x4, little-endian S31.32 sign-magnitude, zero offset column. | |
| 13 | + | func TestCTMBlobGolden(t *testing.T) { | |
| 14 | + | b := ctmBytes([3][3]float64{{1, -0.5, 0}, {0, 1, 0}, {0, 0, 1}}) | |
| 15 | + | want := []uint64{1 << 32, 1<<63 | 1<<31, 0, 0, 0, 1 << 32, 0, 0, 0, 0, 1 << 32, 0} | |
| 16 | + | for i, w := range want { | |
| 17 | + | if g := binary.LittleEndian.Uint64(b[i*8:]); g != w { | |
| 18 | + | t.Errorf("word %d = %#x, want %#x", i, g, w) | |
| 19 | + | } | |
| 20 | + | } | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | func TestPlaneColorSwitchOff(t *testing.T) { | |
| 24 | + | sdrPlanePipeline = false | |
| 25 | + | defer func() { sdrPlanePipeline = true }() | |
| 26 | + | m, why := planeColor(ports.SurfaceColor{}, fourccXRGB, true, true, &colorPipeline{}) | |
| 27 | + | if m != colorBypass || why != "hdr_sdr_content" { | |
| 28 | + | t.Fatalf("got %d %q", m, why) | |
| 29 | + | } | |
| 30 | + | } | |
| @@ -0,0 +1,30 @@ | |||||
| 1 | + | package drm | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "encoding/binary" | |||
| 5 | + | "testing" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/neferwl/internal/ports" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func init() { sdrPlanePipeline = true } // the pipeline tests exercise the path itself | |||
| 11 | + | ||||
| 12 | + | // Golden bytes: row-major 3x4, little-endian S31.32 sign-magnitude, zero offset column. | |||
| 13 | + | func TestCTMBlobGolden(t *testing.T) { | |||
| 14 | + | b := ctmBytes([3][3]float64{{1, -0.5, 0}, {0, 1, 0}, {0, 0, 1}}) | |||
| 15 | + | want := []uint64{1 << 32, 1<<63 | 1<<31, 0, 0, 0, 1 << 32, 0, 0, 0, 0, 1 << 32, 0} | |||
| 16 | + | for i, w := range want { | |||
| 17 | + | if g := binary.LittleEndian.Uint64(b[i*8:]); g != w { | |||
| 18 | + | t.Errorf("word %d = %#x, want %#x", i, g, w) | |||
| 19 | + | } | |||
| 20 | + | } | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | func TestPlaneColorSwitchOff(t *testing.T) { | |||
| 24 | + | sdrPlanePipeline = false | |||
| 25 | + | defer func() { sdrPlanePipeline = true }() | |||
| 26 | + | m, why := planeColor(ports.SurfaceColor{}, fourccXRGB, true, true, &colorPipeline{}) | |||
| 27 | + | if m != colorBypass || why != "hdr_sdr_content" { | |||
| 28 | + | t.Fatalf("got %d %q", m, why) | |||
| 29 | + | } | |||
| 30 | + | } | |||