detached 8545a93

Merge branch 'fix/hdr-sdr-plane' into feat/xwayland-builtin

commit
8545a931eb985e9d2b21ac3c100b92f2d52e1d10
author
brice · · 2026-10-22 18:34 UTC
merge of
8342bf4 + 7f7b429 · diff against the first parent
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
checks
1/1
refs
feat/xwayland-builtin
2 files · +41 -0
M internal/adapters/drm/colorop_match.go +11 -0
@@ -4,6 +4,7 @@ import (
44 "encoding/binary"
55 "fmt"
66 "math"
7+ "os"
78 "slices"
89 "strings"
910
@@ -244,6 +245,13 @@ func ctmBytes(m [3][3]float64) []byte {
244245 return b
245246 }
246247
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+
247255 // planeColor decides how plane (with pipeline, nil: none) shows a client
248256 // buffer of format whose content is c, on an output in HDR10 mode when hdrOn.
249257 // 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
267275 return colorBypass, "hdr_format"
268276 }
269277 return colorBypass, ""
278+ case !sdrPlanePipeline:
279+ // Unverified on hardware: the owner saw oversaturated colours.
280+ return colorBypass, "hdr_sdr_content"
270281 case pipeline == nil || !opaque || !isSDRColor(c):
271282 // The kernel applies the pipeline to premultiplied pixels: exact
272283 // 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 b245 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 client255 // 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 encoding257 // 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: exact282 // 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+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close