fix(drm): compare 3D LUT sizes only when both colour pipelines have one
2 files · +43 -28
M internal/adapters/drm/colorop.go +5 -4
| @@ -279,15 +279,16 @@ func (o *Output) setupColor() { | |||
| 279 | 279 | Uint32("lut3d_size", o.lut3dSize()).Msg("plane colour objects") | |
| 280 | 280 | } | |
| 281 | 281 | ||
| 282 | - | // alignLUT3DSizes drops the overlay's pipeline when its 3D LUT size differs | |
| 283 | - | // from the primary's: the output has one identity LUT blob, sized for the | |
| 284 | - | // primary. SDR content on the overlay is then composed. | |
| 282 | + | // alignLUT3DSizes drops the overlay's pipeline when both planes have a 3D | |
| 283 | + | // LUT of different sizes: the output has one identity LUT blob, sized for | |
| 284 | + | // the primary. A pipeline without a 3D LUT ignores the blob. SDR content on | |
| 285 | + | // the overlay is then composed. | |
| 285 | 286 | func (o *Output) alignLUT3DSizes() { | |
| 286 | 287 | p, ov := o.primary.pipeline, (*colorPipeline)(nil) | |
| 287 | 288 | if o.overlay != nil { | |
| 288 | 289 | ov = o.overlay.pipeline | |
| 289 | 290 | } | |
| 290 | - | if p == nil || ov == nil || p.lut3dSize == ov.lut3dSize { | |
| 291 | + | if p == nil || ov == nil || p.lut3d.op == 0 || ov.lut3d.op == 0 || p.lut3dSize == ov.lut3dSize { | |
| 291 | 292 | return | |
| 292 | 293 | } | |
| 293 | 294 | o.log.Warn().Str("connector", o.conn.name).Uint32("primary", p.lut3dSize).Uint32("overlay", ov.lut3dSize). | |
| @@ -279,15 +279,16 @@ func (o *Output) setupColor() { | |||||
| 279 | Uint32("lut3d_size", o.lut3dSize()).Msg("plane colour objects") | 279 | Uint32("lut3d_size", o.lut3dSize()).Msg("plane colour objects") | ||
| 280 | } | 280 | } | ||
| 281 | 281 | ||||
| 282 | - | // alignLUT3DSizes drops the overlay's pipeline when its 3D LUT size differs | 282 | + | // alignLUT3DSizes drops the overlay's pipeline when both planes have a 3D |
| 283 | - | // from the primary's: the output has one identity LUT blob, sized for the | 283 | + | // LUT of different sizes: the output has one identity LUT blob, sized for |
| 284 | - | // primary. SDR content on the overlay is then composed. | 284 | + | // the primary. A pipeline without a 3D LUT ignores the blob. SDR content on |
| 285 | + | // the overlay is then composed. | |||
| 285 | func (o *Output) alignLUT3DSizes() { | 286 | func (o *Output) alignLUT3DSizes() { | ||
| 286 | p, ov := o.primary.pipeline, (*colorPipeline)(nil) | 287 | p, ov := o.primary.pipeline, (*colorPipeline)(nil) | ||
| 287 | if o.overlay != nil { | 288 | if o.overlay != nil { | ||
| 288 | ov = o.overlay.pipeline | 289 | ov = o.overlay.pipeline | ||
| 289 | } | 290 | } | ||
| 290 | - | if p == nil || ov == nil || p.lut3dSize == ov.lut3dSize { | 291 | + | if p == nil || ov == nil || p.lut3d.op == 0 || ov.lut3d.op == 0 || p.lut3dSize == ov.lut3dSize { |
| 291 | return | 292 | return | ||
| 292 | } | 293 | } | ||
| 293 | o.log.Warn().Str("connector", o.conn.name).Uint32("primary", p.lut3dSize).Uint32("overlay", ov.lut3dSize). | 294 | o.log.Warn().Str("connector", o.conn.name).Uint32("primary", p.lut3dSize).Uint32("overlay", ov.lut3dSize). | ||
M internal/adapters/drm/colorop_test.go +38 -24
| @@ -884,30 +884,44 @@ func TestSetupColor(t *testing.T) { | |||
| 884 | 884 | t.Fatalf("blobs %d %d", o.ctmBlob, o.lut3dBlob) | |
| 885 | 885 | } | |
| 886 | 886 | }) | |
| 887 | - | t.Run("an overlay 3D LUT of another size turns the overlay pipeline off", func(t *testing.T) { | |
| 888 | - | o, k, _ := testOutput(t) | |
| 889 | - | props := maps0(planeProps) | |
| 890 | - | props["zpos"] = pZpos | |
| 891 | - | o.primary.props = props | |
| 892 | - | colorPlane(k, o.primary, pColorPipe, opBase, amdgpuChain) | |
| 893 | - | ov := &plane{id: tOverlay, typ: planeOverlay, props: props, zposValue: 2, formats: []ports.DMABufFormat{{Format: fourccXRGB}}} | |
| 894 | - | other := slices.Clone(amdgpuChain) | |
| 895 | - | other[5] = opSpec{typ: "3D LUT", size: 9} | |
| 896 | - | colorPlane(k, ov, pColorPipeOverlay, opBase+20, other) | |
| 897 | - | o.stray = []*plane{ov} | |
| 898 | - | o.pickOverlay() | |
| 899 | - | o.hdr.on = true | |
| 900 | - | o.readColor() | |
| 901 | - | if o.overlay == nil || o.overlay.pipeline == nil || o.primary.pipeline == nil { | |
| 902 | - | t.Fatal("pipelines not matched") | |
| 903 | - | } | |
| 904 | - | o.hdr.settings = HDRSettings{Enabled: true, SDRBrightness: 203} | |
| 905 | - | o.hdr.cap.Capable = true | |
| 906 | - | k.EXPECT().createBlob(mock.MatchedBy(func(b []byte) bool { return slices.Equal(b, ctmBytes(bt709ToBT2020)) })).Return(uint32(61), nil).Once() | |
| 907 | - | k.EXPECT().createBlob(mock.MatchedBy(func(b []byte) bool { return slices.Equal(b, lut3dIdentityBytes(17)) })).Return(uint32(62), nil).Once() | |
| 908 | - | o.setupColor() | |
| 909 | - | if o.primary.pipeline == nil || o.overlay.pipeline != nil || o.lut3dBlob != 62 { | |
| 910 | - | t.Fatalf("primary %v overlay %v lut %d", o.primary.pipeline != nil, o.overlay.pipeline != nil, o.lut3dBlob) | |
| 887 | + | t.Run("3D LUT sizes of the primary and overlay pipelines", func(t *testing.T) { | |
| 888 | + | noLUT := []opSpec{opSRGB, opMult, opCTM, opPQ} | |
| 889 | + | small := slices.Clone(amdgpuChain) | |
| 890 | + | small[5] = opSpec{typ: "3D LUT", size: 9} | |
| 891 | + | for _, tc := range []struct { | |
| 892 | + | name string | |
| 893 | + | primary, overlay []opSpec | |
| 894 | + | lut int // size of the identity LUT blob, 0: none | |
| 895 | + | overlayKept bool | |
| 896 | + | }{ | |
| 897 | + | {"different sizes drop the overlay pipeline", amdgpuChain, small, 17, false}, | |
| 898 | + | {"a primary without 3D LUT keeps both", noLUT, amdgpuChain, 17, true}, | |
| 899 | + | {"an overlay without 3D LUT keeps both", amdgpuChain, noLUT, 17, true}, | |
| 900 | + | } { | |
| 901 | + | t.Run(tc.name, func(t *testing.T) { | |
| 902 | + | o, k, _ := testOutput(t) | |
| 903 | + | props := maps0(planeProps) | |
| 904 | + | props["zpos"] = pZpos | |
| 905 | + | o.primary.props = props | |
| 906 | + | colorPlane(k, o.primary, pColorPipe, opBase, tc.primary) | |
| 907 | + | ov := &plane{id: tOverlay, typ: planeOverlay, props: props, zposValue: 2, formats: []ports.DMABufFormat{{Format: fourccXRGB}}} | |
| 908 | + | colorPlane(k, ov, pColorPipeOverlay, opBase+20, tc.overlay) | |
| 909 | + | o.stray = []*plane{ov} | |
| 910 | + | o.pickOverlay() | |
| 911 | + | o.hdr.on = true | |
| 912 | + | o.readColor() | |
| 913 | + | if o.overlay == nil || o.overlay.pipeline == nil || o.primary.pipeline == nil { | |
| 914 | + | t.Fatal("pipelines not matched") | |
| 915 | + | } | |
| 916 | + | o.hdr.settings = HDRSettings{Enabled: true, SDRBrightness: 203} | |
| 917 | + | o.hdr.cap.Capable = true | |
| 918 | + | k.EXPECT().createBlob(mock.MatchedBy(func(b []byte) bool { return slices.Equal(b, ctmBytes(bt709ToBT2020)) })).Return(uint32(61), nil).Once() | |
| 919 | + | k.EXPECT().createBlob(mock.MatchedBy(func(b []byte) bool { return slices.Equal(b, lut3dIdentityBytes(uint32(tc.lut))) })).Return(uint32(62), nil).Once() | |
| 920 | + | o.setupColor() | |
| 921 | + | if o.primary.pipeline == nil || (o.overlay.pipeline != nil) != tc.overlayKept || o.lut3dBlob != 62 { | |
| 922 | + | t.Fatalf("primary %v overlay %v lut %d", o.primary.pipeline != nil, o.overlay.pipeline != nil, o.lut3dBlob) | |
| 923 | + | } | |
| 924 | + | }) | |
| 911 | 925 | } | |
| 912 | 926 | }) | |
| 913 | 927 | t.Run("3D LUT blob failure turns the pipelines off", func(t *testing.T) { | |
| @@ -884,30 +884,44 @@ func TestSetupColor(t *testing.T) { | |||||
| 884 | t.Fatalf("blobs %d %d", o.ctmBlob, o.lut3dBlob) | 884 | t.Fatalf("blobs %d %d", o.ctmBlob, o.lut3dBlob) | ||
| 885 | } | 885 | } | ||
| 886 | }) | 886 | }) | ||
| 887 | - | t.Run("an overlay 3D LUT of another size turns the overlay pipeline off", func(t *testing.T) { | 887 | + | t.Run("3D LUT sizes of the primary and overlay pipelines", func(t *testing.T) { |
| 888 | - | o, k, _ := testOutput(t) | 888 | + | noLUT := []opSpec{opSRGB, opMult, opCTM, opPQ} |
| 889 | - | props := maps0(planeProps) | 889 | + | small := slices.Clone(amdgpuChain) |
| 890 | - | props["zpos"] = pZpos | 890 | + | small[5] = opSpec{typ: "3D LUT", size: 9} |
| 891 | - | o.primary.props = props | 891 | + | for _, tc := range []struct { |
| 892 | - | colorPlane(k, o.primary, pColorPipe, opBase, amdgpuChain) | 892 | + | name string |
| 893 | - | ov := &plane{id: tOverlay, typ: planeOverlay, props: props, zposValue: 2, formats: []ports.DMABufFormat{{Format: fourccXRGB}}} | 893 | + | primary, overlay []opSpec |
| 894 | - | other := slices.Clone(amdgpuChain) | 894 | + | lut int // size of the identity LUT blob, 0: none |
| 895 | - | other[5] = opSpec{typ: "3D LUT", size: 9} | 895 | + | overlayKept bool |
| 896 | - | colorPlane(k, ov, pColorPipeOverlay, opBase+20, other) | 896 | + | }{ |
| 897 | - | o.stray = []*plane{ov} | 897 | + | {"different sizes drop the overlay pipeline", amdgpuChain, small, 17, false}, |
| 898 | - | o.pickOverlay() | 898 | + | {"a primary without 3D LUT keeps both", noLUT, amdgpuChain, 17, true}, |
| 899 | - | o.hdr.on = true | 899 | + | {"an overlay without 3D LUT keeps both", amdgpuChain, noLUT, 17, true}, |
| 900 | - | o.readColor() | 900 | + | } { |
| 901 | - | if o.overlay == nil || o.overlay.pipeline == nil || o.primary.pipeline == nil { | 901 | + | t.Run(tc.name, func(t *testing.T) { |
| 902 | - | t.Fatal("pipelines not matched") | 902 | + | o, k, _ := testOutput(t) |
| 903 | - | } | 903 | + | props := maps0(planeProps) |
| 904 | - | o.hdr.settings = HDRSettings{Enabled: true, SDRBrightness: 203} | 904 | + | props["zpos"] = pZpos |
| 905 | - | o.hdr.cap.Capable = true | 905 | + | o.primary.props = props |
| 906 | - | k.EXPECT().createBlob(mock.MatchedBy(func(b []byte) bool { return slices.Equal(b, ctmBytes(bt709ToBT2020)) })).Return(uint32(61), nil).Once() | 906 | + | colorPlane(k, o.primary, pColorPipe, opBase, tc.primary) |
| 907 | - | k.EXPECT().createBlob(mock.MatchedBy(func(b []byte) bool { return slices.Equal(b, lut3dIdentityBytes(17)) })).Return(uint32(62), nil).Once() | 907 | + | ov := &plane{id: tOverlay, typ: planeOverlay, props: props, zposValue: 2, formats: []ports.DMABufFormat{{Format: fourccXRGB}}} |
| 908 | - | o.setupColor() | 908 | + | colorPlane(k, ov, pColorPipeOverlay, opBase+20, tc.overlay) |
| 909 | - | if o.primary.pipeline == nil || o.overlay.pipeline != nil || o.lut3dBlob != 62 { | 909 | + | o.stray = []*plane{ov} |
| 910 | - | t.Fatalf("primary %v overlay %v lut %d", o.primary.pipeline != nil, o.overlay.pipeline != nil, o.lut3dBlob) | 910 | + | o.pickOverlay() |
| 911 | + | o.hdr.on = true | |||
| 912 | + | o.readColor() | |||
| 913 | + | if o.overlay == nil || o.overlay.pipeline == nil || o.primary.pipeline == nil { | |||
| 914 | + | t.Fatal("pipelines not matched") | |||
| 915 | + | } | |||
| 916 | + | o.hdr.settings = HDRSettings{Enabled: true, SDRBrightness: 203} | |||
| 917 | + | o.hdr.cap.Capable = true | |||
| 918 | + | k.EXPECT().createBlob(mock.MatchedBy(func(b []byte) bool { return slices.Equal(b, ctmBytes(bt709ToBT2020)) })).Return(uint32(61), nil).Once() | |||
| 919 | + | k.EXPECT().createBlob(mock.MatchedBy(func(b []byte) bool { return slices.Equal(b, lut3dIdentityBytes(uint32(tc.lut))) })).Return(uint32(62), nil).Once() | |||
| 920 | + | o.setupColor() | |||
| 921 | + | if o.primary.pipeline == nil || (o.overlay.pipeline != nil) != tc.overlayKept || o.lut3dBlob != 62 { | |||
| 922 | + | t.Fatalf("primary %v overlay %v lut %d", o.primary.pipeline != nil, o.overlay.pipeline != nil, o.lut3dBlob) | |||
| 923 | + | } | |||
| 924 | + | }) | |||
| 911 | } | 925 | } | ||
| 912 | }) | 926 | }) | ||
| 913 | t.Run("3D LUT blob failure turns the pipelines off", func(t *testing.T) { | 927 | t.Run("3D LUT blob failure turns the pipelines off", func(t *testing.T) { | ||