detached a13ea2f

feat(vulkan): export multi-plane DCC render targets

probeModifiers keeps modifiers with 1 to 4 memory planes and records the
counts; exportTarget reads every plane layout and gives each plane its own
file. ExportTargets gains a singlePlane argument (ports.Renderer) that
drops multi-plane modifiers, and logs the chosen modifier and plane count.
commit
a13ea2fd003a5271407ed47753493fb11a508525
author
brice · · 2026-10-04 20:02 UTC
parent
4cbd8f8
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
26 files · +248 -87
M internal/adapters/drm/amdgpu_rules_test.go +1 -1
@@ -420,7 +420,7 @@ func TestRunProtectedActivationRetriesWithoutStoppingOrEarlyProof(t *testing.T)
420420 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
421421 }
422422 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
423- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
423+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
424424 r.EXPECT().UseTarget(mock.Anything).Return()
425425 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
426426 r.EXPECT().Close().Return().Once()
@@ -420,7 +420,7 @@ func TestRunProtectedActivationRetriesWithoutStoppingOrEarlyProof(t *testing.T)
420 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}420 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
421 }421 }
422 r.EXPECT().SetHDR(float64(0)).Return().Maybe()422 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
423- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()423+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
424 r.EXPECT().UseTarget(mock.Anything).Return()424 r.EXPECT().UseTarget(mock.Anything).Return()
425 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)425 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
426 r.EXPECT().Close().Return().Once()426 r.EXPECT().Close().Return().Once()
M internal/adapters/drm/capture_test.go +3 -3
@@ -339,7 +339,7 @@ func TestRunChildHoldLiftedWhileDisplayCommitStalls(t *testing.T) {
339339 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
340340 }
341341 display.EXPECT().SetHDR(float64(0)).Return().Maybe()
342- display.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
342+ display.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
343343 display.EXPECT().UseTarget(mock.Anything).Return()
344344 display.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
345345 display.EXPECT().Close().Return().Once()
@@ -447,7 +447,7 @@ func TestRunCaptureWaitsForItsIndicatorScene(t *testing.T) {
447447 }
448448 r := portsmocks.NewMockRenderer(t)
449449 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
450- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
450+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
451451 r.EXPECT().UseTarget(mock.Anything).Return()
452452 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
453453 switch {
@@ -543,7 +543,7 @@ func TestRunCaptureWithoutIndicatorFailsAfterTheBoundedWait(t *testing.T) {
543543 }
544544 r := portsmocks.NewMockRenderer(t)
545545 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
546- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
546+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
547547 r.EXPECT().UseTarget(mock.Anything).Return()
548548 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
549549 r.EXPECT().Close().Return().Once()
@@ -339,7 +339,7 @@ func TestRunChildHoldLiftedWhileDisplayCommitStalls(t *testing.T) {
339 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}339 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
340 }340 }
341 display.EXPECT().SetHDR(float64(0)).Return().Maybe()341 display.EXPECT().SetHDR(float64(0)).Return().Maybe()
342- display.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()342+ display.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
343 display.EXPECT().UseTarget(mock.Anything).Return()343 display.EXPECT().UseTarget(mock.Anything).Return()
344 display.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)344 display.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
345 display.EXPECT().Close().Return().Once()345 display.EXPECT().Close().Return().Once()
@@ -447,7 +447,7 @@ func TestRunCaptureWaitsForItsIndicatorScene(t *testing.T) {
447 }447 }
448 r := portsmocks.NewMockRenderer(t)448 r := portsmocks.NewMockRenderer(t)
449 r.EXPECT().SetHDR(float64(0)).Return().Maybe()449 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
450- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()450+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
451 r.EXPECT().UseTarget(mock.Anything).Return()451 r.EXPECT().UseTarget(mock.Anything).Return()
452 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {452 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
453 switch {453 switch {
@@ -543,7 +543,7 @@ func TestRunCaptureWithoutIndicatorFailsAfterTheBoundedWait(t *testing.T) {
543 }543 }
544 r := portsmocks.NewMockRenderer(t)544 r := portsmocks.NewMockRenderer(t)
545 r.EXPECT().SetHDR(float64(0)).Return().Maybe()545 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
546- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()546+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
547 r.EXPECT().UseTarget(mock.Anything).Return()547 r.EXPECT().UseTarget(mock.Anything).Return()
548 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)548 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
549 r.EXPECT().Close().Return().Once()549 r.EXPECT().Close().Return().Once()
M internal/adapters/drm/commit_test.go +5 -5
@@ -123,9 +123,9 @@ func TestShowImagesTestsBeforeModeset(t *testing.T) {
123123 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
124124 }
125125 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
126- r.EXPECT().ExportTargets(2, []uint64(nil)).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
126+ r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
127127 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
128- r.EXPECT().ExportTargets(2, []uint64{0}).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
128+ r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
129129 r.EXPECT().UseTarget(mock.Anything).Return()
130130 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
131131 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
@@ -161,7 +161,7 @@ func TestEnableAfterStartAwayModesetsOnce(t *testing.T) {
161161 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
162162 }
163163 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
164- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
164+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
165165 r.EXPECT().UseTarget(mock.Anything).Return()
166166 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
167167 r.EXPECT().Close().Return().Once()
@@ -262,7 +262,7 @@ func TestRunReportsSeenAfterCursorCommit(t *testing.T) {
262262 r.EXPECT().CursorBuffers(64).Return([2]ports.DMABuf{buf(), buf()}, nil).Once()
263263 r.EXPECT().WriteCursor(mock.Anything, mock.Anything, 1, 1).Return(nil)
264264 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
265- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
265+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
266266 r.EXPECT().UseTarget(mock.Anything).Return()
267267 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
268268 r.EXPECT().Close().Return().Once()
@@ -355,7 +355,7 @@ func TestRunClosesFrameFences(t *testing.T) {
355355 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
356356 }
357357 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
358- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
358+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
359359 r.EXPECT().UseTarget(mock.Anything).Return()
360360 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
361361 f, w, _ := os.Pipe()
@@ -123,9 +123,9 @@ func TestShowImagesTestsBeforeModeset(t *testing.T) {
123 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}123 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
124 }124 }
125 r.EXPECT().SetHDR(float64(0)).Return().Maybe()125 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
126- r.EXPECT().ExportTargets(2, []uint64(nil)).Return([]ports.DMABuf{buf(), buf()}, nil).Once()126+ r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
127 r.EXPECT().SetHDR(float64(0)).Return().Maybe()127 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
128- r.EXPECT().ExportTargets(2, []uint64{0}).Return([]ports.DMABuf{buf(), buf()}, nil).Once()128+ r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
129 r.EXPECT().UseTarget(mock.Anything).Return()129 r.EXPECT().UseTarget(mock.Anything).Return()
130 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)130 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
131 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)131 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
@@ -161,7 +161,7 @@ func TestEnableAfterStartAwayModesetsOnce(t *testing.T) {
161 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}161 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
162 }162 }
163 r.EXPECT().SetHDR(float64(0)).Return().Maybe()163 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
164- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()164+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
165 r.EXPECT().UseTarget(mock.Anything).Return()165 r.EXPECT().UseTarget(mock.Anything).Return()
166 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)166 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
167 r.EXPECT().Close().Return().Once()167 r.EXPECT().Close().Return().Once()
@@ -262,7 +262,7 @@ func TestRunReportsSeenAfterCursorCommit(t *testing.T) {
262 r.EXPECT().CursorBuffers(64).Return([2]ports.DMABuf{buf(), buf()}, nil).Once()262 r.EXPECT().CursorBuffers(64).Return([2]ports.DMABuf{buf(), buf()}, nil).Once()
263 r.EXPECT().WriteCursor(mock.Anything, mock.Anything, 1, 1).Return(nil)263 r.EXPECT().WriteCursor(mock.Anything, mock.Anything, 1, 1).Return(nil)
264 r.EXPECT().SetHDR(float64(0)).Return().Maybe()264 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
265- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()265+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
266 r.EXPECT().UseTarget(mock.Anything).Return()266 r.EXPECT().UseTarget(mock.Anything).Return()
267 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)267 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
268 r.EXPECT().Close().Return().Once()268 r.EXPECT().Close().Return().Once()
@@ -355,7 +355,7 @@ func TestRunClosesFrameFences(t *testing.T) {
355 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}355 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
356 }356 }
357 r.EXPECT().SetHDR(float64(0)).Return().Maybe()357 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
358- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()358+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
359 r.EXPECT().UseTarget(mock.Anything).Return()359 r.EXPECT().UseTarget(mock.Anything).Return()
360 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {360 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
361 f, w, _ := os.Pipe()361 f, w, _ := os.Pipe()
M internal/adapters/drm/hdr_test.go +6 -6
@@ -88,9 +88,9 @@ func TestHDRTestCommitFallbackReexportsSDR(t *testing.T) {
8888 var calls []string
8989 r.EXPECT().SetHDR(float64(203)).Run(func(float64) { calls = append(calls, "hdr") }).Return().Once()
9090 r.EXPECT().SetHDR(float64(0)).Run(func(float64) { calls = append(calls, "sdr") }).Return().Once()
91- r.EXPECT().ExportTargets(2, []uint64{0}).Return([]ports.DMABuf{buf(), buf()}, nil).Twice()
92- r.EXPECT().ExportTargets(0, []uint64(nil)).Run(func(int, []uint64) { calls = append(calls, "drop") }).Return(nil, nil).Once()
93- r.EXPECT().ExportTargets(2, []uint64(nil)).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
91+ r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Twice()
92+ r.EXPECT().ExportTargets(0, []uint64(nil), false).Run(func(int, []uint64, bool) { calls = append(calls, "drop") }).Return(nil, nil).Once()
93+ r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
9494 r.EXPECT().UseTarget(mock.Anything).Return()
9595 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
9696 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil)
@@ -160,7 +160,7 @@ func TestHDRSuccessfulModeset(t *testing.T) {
160160 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
161161 }
162162 r.EXPECT().SetHDR(float64(ports.DefaultSDRBrightness)).Return().Once()
163- r.EXPECT().ExportTargets(2, []uint64{19}).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
163+ r.EXPECT().ExportTargets(2, []uint64{19}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
164164 r.EXPECT().UseTarget(mock.Anything).Return()
165165 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
166166 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil).Twice()
@@ -201,7 +201,7 @@ func TestHDRBlobReusedAcrossImageSetup(t *testing.T) {
201201 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
202202 }
203203 r.EXPECT().SetHDR(float64(ports.DefaultSDRBrightness)).Return().Twice()
204- r.EXPECT().ExportTargets(2, []uint64{19}).RunAndReturn(func(int, []uint64) ([]ports.DMABuf, error) { return []ports.DMABuf{buf(), buf()}, nil }).Twice()
204+ r.EXPECT().ExportTargets(2, []uint64{19}, false).RunAndReturn(func(int, []uint64, bool) ([]ports.DMABuf, error) { return []ports.DMABuf{buf(), buf()}, nil }).Twice()
205205 r.EXPECT().UseTarget(mock.Anything).Return()
206206 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
207207 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil)
@@ -251,7 +251,7 @@ func TestHDRBlobReplacementWaitsForCommit(t *testing.T) {
251251 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
252252 }
253253 r.EXPECT().SetHDR(float64(ports.DefaultSDRBrightness)).Return().Twice()
254- r.EXPECT().ExportTargets(2, []uint64{19}).RunAndReturn(func(int, []uint64) ([]ports.DMABuf, error) { return []ports.DMABuf{buf(), buf()}, nil }).Twice()
254+ r.EXPECT().ExportTargets(2, []uint64{19}, false).RunAndReturn(func(int, []uint64, bool) ([]ports.DMABuf, error) { return []ports.DMABuf{buf(), buf()}, nil }).Twice()
255255 r.EXPECT().UseTarget(mock.Anything).Return()
256256 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
257257 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil)
@@ -88,9 +88,9 @@ func TestHDRTestCommitFallbackReexportsSDR(t *testing.T) {
88 var calls []string88 var calls []string
89 r.EXPECT().SetHDR(float64(203)).Run(func(float64) { calls = append(calls, "hdr") }).Return().Once()89 r.EXPECT().SetHDR(float64(203)).Run(func(float64) { calls = append(calls, "hdr") }).Return().Once()
90 r.EXPECT().SetHDR(float64(0)).Run(func(float64) { calls = append(calls, "sdr") }).Return().Once()90 r.EXPECT().SetHDR(float64(0)).Run(func(float64) { calls = append(calls, "sdr") }).Return().Once()
91- r.EXPECT().ExportTargets(2, []uint64{0}).Return([]ports.DMABuf{buf(), buf()}, nil).Twice()91+ r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Twice()
92- r.EXPECT().ExportTargets(0, []uint64(nil)).Run(func(int, []uint64) { calls = append(calls, "drop") }).Return(nil, nil).Once()92+ r.EXPECT().ExportTargets(0, []uint64(nil), false).Run(func(int, []uint64, bool) { calls = append(calls, "drop") }).Return(nil, nil).Once()
93- r.EXPECT().ExportTargets(2, []uint64(nil)).Return([]ports.DMABuf{buf(), buf()}, nil).Once()93+ r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
94 r.EXPECT().UseTarget(mock.Anything).Return()94 r.EXPECT().UseTarget(mock.Anything).Return()
95 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)95 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
96 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil)96 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil)
@@ -160,7 +160,7 @@ func TestHDRSuccessfulModeset(t *testing.T) {
160 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}160 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
161 }161 }
162 r.EXPECT().SetHDR(float64(ports.DefaultSDRBrightness)).Return().Once()162 r.EXPECT().SetHDR(float64(ports.DefaultSDRBrightness)).Return().Once()
163- r.EXPECT().ExportTargets(2, []uint64{19}).Return([]ports.DMABuf{buf(), buf()}, nil).Once()163+ r.EXPECT().ExportTargets(2, []uint64{19}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
164 r.EXPECT().UseTarget(mock.Anything).Return()164 r.EXPECT().UseTarget(mock.Anything).Return()
165 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)165 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
166 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil).Twice()166 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil).Twice()
@@ -201,7 +201,7 @@ func TestHDRBlobReusedAcrossImageSetup(t *testing.T) {
201 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}201 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
202 }202 }
203 r.EXPECT().SetHDR(float64(ports.DefaultSDRBrightness)).Return().Twice()203 r.EXPECT().SetHDR(float64(ports.DefaultSDRBrightness)).Return().Twice()
204- r.EXPECT().ExportTargets(2, []uint64{19}).RunAndReturn(func(int, []uint64) ([]ports.DMABuf, error) { return []ports.DMABuf{buf(), buf()}, nil }).Twice()204+ r.EXPECT().ExportTargets(2, []uint64{19}, false).RunAndReturn(func(int, []uint64, bool) ([]ports.DMABuf, error) { return []ports.DMABuf{buf(), buf()}, nil }).Twice()
205 r.EXPECT().UseTarget(mock.Anything).Return()205 r.EXPECT().UseTarget(mock.Anything).Return()
206 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)206 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
207 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil)207 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil)
@@ -251,7 +251,7 @@ func TestHDRBlobReplacementWaitsForCommit(t *testing.T) {
251 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}251 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
252 }252 }
253 r.EXPECT().SetHDR(float64(ports.DefaultSDRBrightness)).Return().Twice()253 r.EXPECT().SetHDR(float64(ports.DefaultSDRBrightness)).Return().Twice()
254- r.EXPECT().ExportTargets(2, []uint64{19}).RunAndReturn(func(int, []uint64) ([]ports.DMABuf, error) { return []ports.DMABuf{buf(), buf()}, nil }).Twice()254+ r.EXPECT().ExportTargets(2, []uint64{19}, false).RunAndReturn(func(int, []uint64, bool) ([]ports.DMABuf, error) { return []ports.DMABuf{buf(), buf()}, nil }).Twice()
255 r.EXPECT().UseTarget(mock.Anything).Return()255 r.EXPECT().UseTarget(mock.Anything).Return()
256 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)256 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
257 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil)257 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil)
M internal/adapters/drm/images.go +3 -3
@@ -57,7 +57,7 @@ func (o *Output) showImages(r ports.Renderer, kind imageKind, cause error) error
5757 o.hdr.on = false
5858 o.freeImages()
5959 r.SetHDR(0)
60- _, _ = r.ExportTargets(0, nil)
60+ _, _ = r.ExportTargets(0, nil, false)
6161 // A failed test commit can leave the old HDR mode on screen;
6262 // retain its blob until the SDR modeset succeeds or Close restores it.
6363 } else {
@@ -141,7 +141,7 @@ func (o *Output) setupImages(r ports.Renderer, kind imageKind, cause error) (ima
141141
142142 // exportImages makes the renderer's exported targets the output images.
143143 func (o *Output) exportImages(r ports.Renderer, mods []uint64) error {
144- bufs, err := r.ExportTargets(len(o.fbs), mods)
144+ bufs, err := r.ExportTargets(len(o.fbs), mods, false)
145145 if err != nil {
146146 return err
147147 }
@@ -175,7 +175,7 @@ func (o *Output) exportImages(r ports.Renderer, mods []uint64) error {
175175 }
176176 if err != nil {
177177 o.freeImages()
178- _, _ = r.ExportTargets(0, nil)
178+ _, _ = r.ExportTargets(0, nil, false)
179179 return err
180180 }
181181 o.log.Info().Str("connector", o.conn.name).Uint64("modifier", bufs[0].Modifier).Msg("zero-copy output")
@@ -57,7 +57,7 @@ func (o *Output) showImages(r ports.Renderer, kind imageKind, cause error) error
57 o.hdr.on = false57 o.hdr.on = false
58 o.freeImages()58 o.freeImages()
59 r.SetHDR(0)59 r.SetHDR(0)
60- _, _ = r.ExportTargets(0, nil)60+ _, _ = r.ExportTargets(0, nil, false)
61 // A failed test commit can leave the old HDR mode on screen;61 // A failed test commit can leave the old HDR mode on screen;
62 // retain its blob until the SDR modeset succeeds or Close restores it.62 // retain its blob until the SDR modeset succeeds or Close restores it.
63 } else {63 } else {
@@ -141,7 +141,7 @@ func (o *Output) setupImages(r ports.Renderer, kind imageKind, cause error) (ima
141 141
142 // exportImages makes the renderer's exported targets the output images.142 // exportImages makes the renderer's exported targets the output images.
143 func (o *Output) exportImages(r ports.Renderer, mods []uint64) error {143 func (o *Output) exportImages(r ports.Renderer, mods []uint64) error {
144- bufs, err := r.ExportTargets(len(o.fbs), mods)144+ bufs, err := r.ExportTargets(len(o.fbs), mods, false)
145 if err != nil {145 if err != nil {
146 return err146 return err
147 }147 }
@@ -175,7 +175,7 @@ func (o *Output) exportImages(r ports.Renderer, mods []uint64) error {
175 }175 }
176 if err != nil {176 if err != nil {
177 o.freeImages()177 o.freeImages()
178- _, _ = r.ExportTargets(0, nil)178+ _, _ = r.ExportTargets(0, nil, false)
179 return err179 return err
180 }180 }
181 o.log.Info().Str("connector", o.conn.name).Uint64("modifier", bufs[0].Modifier).Msg("zero-copy output")181 o.log.Info().Str("connector", o.conn.name).Uint64("modifier", bufs[0].Modifier).Msg("zero-copy output")
M internal/adapters/drm/output_test.go +5 -5
@@ -565,7 +565,7 @@ func TestRunReportsSeenAfterFlip(t *testing.T) {
565565 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
566566 }
567567 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
568- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
568+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
569569 r.EXPECT().UseTarget(mock.Anything).Return()
570570 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
571571 f, w, _ := os.Pipe()
@@ -651,7 +651,7 @@ func TestRunComposedFullscreenKeepsVRR(t *testing.T) {
651651 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
652652 }
653653 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
654- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
654+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
655655 r.EXPECT().UseTarget(mock.Anything).Return()
656656 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
657657 r.EXPECT().Close().Return().Once()
@@ -790,7 +790,7 @@ func TestCaptureRenderFailureClosesPendingRequest(t *testing.T) {
790790 t.Cleanup(func() { _ = f.Close() })
791791 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
792792 }
793- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{makeBuf(), makeBuf()}, nil).Once()
793+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{makeBuf(), makeBuf()}, nil).Once()
794794 r.EXPECT().Render(ports.Scene{Background: "#000000"}, mock.Anything).Return(nil, nil).Twice()
795795 r.EXPECT().Render(mock.MatchedBy(func(s ports.Scene) bool { return s.Background != "#000000" }), mock.Anything).Return(nil, boom).Once()
796796 r.EXPECT().Close().Return().Once()
@@ -852,7 +852,7 @@ func TestCaptureForcesDRMComposition(t *testing.T) {
852852 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
853853 }
854854 r.EXPECT().SetHDR(float64(0)).Return().Maybe() // SDR output
855- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{makeBuf(), makeBuf()}, nil).Once()
855+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{makeBuf(), makeBuf()}, nil).Once()
856856 r.EXPECT().UseTarget(mock.Anything).Return()
857857 rendered := make(chan ports.Scene, 3)
858858 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
@@ -968,7 +968,7 @@ func startStampRun(t *testing.T) *stampRun {
968968 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
969969 }
970970 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
971- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
971+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
972972 r.EXPECT().UseTarget(mock.Anything).Return()
973973 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
974974 r.EXPECT().Close().Return().Once()
@@ -565,7 +565,7 @@ func TestRunReportsSeenAfterFlip(t *testing.T) {
565 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}565 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
566 }566 }
567 r.EXPECT().SetHDR(float64(0)).Return().Maybe()567 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
568- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()568+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
569 r.EXPECT().UseTarget(mock.Anything).Return()569 r.EXPECT().UseTarget(mock.Anything).Return()
570 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {570 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
571 f, w, _ := os.Pipe()571 f, w, _ := os.Pipe()
@@ -651,7 +651,7 @@ func TestRunComposedFullscreenKeepsVRR(t *testing.T) {
651 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}651 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
652 }652 }
653 r.EXPECT().SetHDR(float64(0)).Return().Maybe()653 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
654- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()654+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
655 r.EXPECT().UseTarget(mock.Anything).Return()655 r.EXPECT().UseTarget(mock.Anything).Return()
656 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)656 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
657 r.EXPECT().Close().Return().Once()657 r.EXPECT().Close().Return().Once()
@@ -790,7 +790,7 @@ func TestCaptureRenderFailureClosesPendingRequest(t *testing.T) {
790 t.Cleanup(func() { _ = f.Close() })790 t.Cleanup(func() { _ = f.Close() })
791 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}791 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
792 }792 }
793- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{makeBuf(), makeBuf()}, nil).Once()793+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{makeBuf(), makeBuf()}, nil).Once()
794 r.EXPECT().Render(ports.Scene{Background: "#000000"}, mock.Anything).Return(nil, nil).Twice()794 r.EXPECT().Render(ports.Scene{Background: "#000000"}, mock.Anything).Return(nil, nil).Twice()
795 r.EXPECT().Render(mock.MatchedBy(func(s ports.Scene) bool { return s.Background != "#000000" }), mock.Anything).Return(nil, boom).Once()795 r.EXPECT().Render(mock.MatchedBy(func(s ports.Scene) bool { return s.Background != "#000000" }), mock.Anything).Return(nil, boom).Once()
796 r.EXPECT().Close().Return().Once()796 r.EXPECT().Close().Return().Once()
@@ -852,7 +852,7 @@ func TestCaptureForcesDRMComposition(t *testing.T) {
852 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}852 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
853 }853 }
854 r.EXPECT().SetHDR(float64(0)).Return().Maybe() // SDR output854 r.EXPECT().SetHDR(float64(0)).Return().Maybe() // SDR output
855- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{makeBuf(), makeBuf()}, nil).Once()855+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{makeBuf(), makeBuf()}, nil).Once()
856 r.EXPECT().UseTarget(mock.Anything).Return()856 r.EXPECT().UseTarget(mock.Anything).Return()
857 rendered := make(chan ports.Scene, 3)857 rendered := make(chan ports.Scene, 3)
858 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {858 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
@@ -968,7 +968,7 @@ func startStampRun(t *testing.T) *stampRun {
968 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}968 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
969 }969 }
970 r.EXPECT().SetHDR(float64(0)).Return().Maybe()970 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
971- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()971+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
972 r.EXPECT().UseTarget(mock.Anything).Return()972 r.EXPECT().UseTarget(mock.Anything).Return()
973 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)973 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
974 r.EXPECT().Close().Return().Once()974 r.EXPECT().Close().Return().Once()
M internal/adapters/drm/power_test.go +2 -2
@@ -25,7 +25,7 @@ func TestRunOutputPower(t *testing.T) {
2525 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
2626 }
2727 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
28- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
28+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
2929 r.EXPECT().UseTarget(mock.Anything).Return()
3030 renders := make(chan struct{}, 8)
3131 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
@@ -133,7 +133,7 @@ func TestRunOutputStartOff(t *testing.T) {
133133 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
134134 }
135135 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
136- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
136+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
137137 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()
138138 renders := make(chan struct{}, 8)
139139 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
@@ -25,7 +25,7 @@ func TestRunOutputPower(t *testing.T) {
25 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}25 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
26 }26 }
27 r.EXPECT().SetHDR(float64(0)).Return().Maybe()27 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
28- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()28+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
29 r.EXPECT().UseTarget(mock.Anything).Return()29 r.EXPECT().UseTarget(mock.Anything).Return()
30 renders := make(chan struct{}, 8)30 renders := make(chan struct{}, 8)
31 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {31 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
@@ -133,7 +133,7 @@ func TestRunOutputStartOff(t *testing.T) {
133 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}133 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
134 }134 }
135 r.EXPECT().SetHDR(float64(0)).Return().Maybe()135 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
136- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()136+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
137 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()137 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()
138 renders := make(chan struct{}, 8)138 renders := make(chan struct{}, 8)
139 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {139 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
M internal/adapters/drm/protection_retry_test.go +2 -2
@@ -32,7 +32,7 @@ func runProtectedStartup(t *testing.T, o *Output, k *mockkms, renders func() (*o
3232 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
3333 }
3434 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
35- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
35+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
3636 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()
3737 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
3838 return renders()
@@ -266,7 +266,7 @@ func TestProtectedShowImagesHDRKeepsHDRForKMSErrors(t *testing.T) {
266266 r.EXPECT().SetHDR(float64(203)).Return().Once()
267267 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()
268268 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil).Maybe()
269- r.EXPECT().ExportTargets(2, []uint64{0}).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
269+ r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
270270 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil).Maybe()
271271 k.EXPECT().createBlob(mock.Anything).Return(321, nil).Maybe()
272272 k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
@@ -32,7 +32,7 @@ func runProtectedStartup(t *testing.T, o *Output, k *mockkms, renders func() (*o
32 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}32 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
33 }33 }
34 r.EXPECT().SetHDR(float64(0)).Return().Maybe()34 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
35- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()35+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
36 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()36 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()
37 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {37 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
38 return renders()38 return renders()
@@ -266,7 +266,7 @@ func TestProtectedShowImagesHDRKeepsHDRForKMSErrors(t *testing.T) {
266 r.EXPECT().SetHDR(float64(203)).Return().Once()266 r.EXPECT().SetHDR(float64(203)).Return().Once()
267 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()267 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()
268 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil).Maybe()268 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil).Maybe()
269- r.EXPECT().ExportTargets(2, []uint64{0}).Return([]ports.DMABuf{buf(), buf()}, nil).Once()269+ r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
270 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil).Maybe()270 k.EXPECT().addFB(mock.Anything, uint32(fourccXR30)).Return(70, nil).Maybe()
271 k.EXPECT().createBlob(mock.Anything).Return(321, nil).Maybe()271 k.EXPECT().createBlob(mock.Anything).Return(321, nil).Maybe()
272 k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()272 k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
M internal/adapters/drm/security_recovery_test.go +1 -1
@@ -31,7 +31,7 @@ func TestSecurityStuckRecoveryInvalidatesBeforeFreshProof(t *testing.T) {
3131 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
3232 }
3333 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
34- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
34+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
3535 r.EXPECT().UseTarget(mock.Anything).Return()
3636 fence, signal := protectionFence(t, false)
3737 var renderedOnce atomic.Bool
@@ -31,7 +31,7 @@ func TestSecurityStuckRecoveryInvalidatesBeforeFreshProof(t *testing.T) {
31 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}31 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
32 }32 }
33 r.EXPECT().SetHDR(float64(0)).Return().Maybe()33 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
34- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()34+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
35 r.EXPECT().UseTarget(mock.Anything).Return()35 r.EXPECT().UseTarget(mock.Anything).Return()
36 fence, signal := protectionFence(t, false)36 fence, signal := protectionFence(t, false)
37 var renderedOnce atomic.Bool37 var renderedOnce atomic.Bool
M internal/adapters/drm/security_run_test.go +1 -1
@@ -32,7 +32,7 @@ func TestSecurityRunWakePendingResumeAndOff(t *testing.T) {
3232 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
3333 }
3434 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
35- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
35+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
3636 r.EXPECT().UseTarget(mock.Anything).Return()
3737 black := make(chan struct{}, 8)
3838 desktop := make(chan struct{}, 2)
@@ -32,7 +32,7 @@ func TestSecurityRunWakePendingResumeAndOff(t *testing.T) {
32 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}32 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
33 }33 }
34 r.EXPECT().SetHDR(float64(0)).Return().Maybe()34 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
35- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()35+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
36 r.EXPECT().UseTarget(mock.Anything).Return()36 r.EXPECT().UseTarget(mock.Anything).Return()
37 black := make(chan struct{}, 8)37 black := make(chan struct{}, 8)
38 desktop := make(chan struct{}, 2)38 desktop := make(chan struct{}, 2)
M internal/adapters/drm/security_startup_test.go +3 -3
@@ -28,7 +28,7 @@ func TestSecurityProtectedStartupBlackProofAndCaptureRejection(t *testing.T) {
2828 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
2929 }
3030 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
31- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
31+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
3232 r.EXPECT().UseTarget(mock.Anything).Return()
3333 r.EXPECT().Render(ports.Scene{Background: "#000000"}, map[ports.WindowID]ports.SurfaceContent(nil)).Return(nil, nil).Times(3)
3434 r.EXPECT().Close().Return().Once()
@@ -94,8 +94,8 @@ func TestSecurityStartupClearWaitUsesRunContext(t *testing.T) {
9494 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
9595 }
9696 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
97- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
98- r.EXPECT().ExportTargets(0, []uint64(nil)).Return(nil, nil).Once()
97+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
98+ r.EXPECT().ExportTargets(0, []uint64(nil), false).Return(nil, nil).Once()
9999 r.EXPECT().UseTarget(0).Return().Once()
100100 fence, _ := protectionFence(t, false)
101101 ctx, cancel := context.WithCancel(context.Background())
@@ -28,7 +28,7 @@ func TestSecurityProtectedStartupBlackProofAndCaptureRejection(t *testing.T) {
28 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}28 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
29 }29 }
30 r.EXPECT().SetHDR(float64(0)).Return().Maybe()30 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
31- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()31+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
32 r.EXPECT().UseTarget(mock.Anything).Return()32 r.EXPECT().UseTarget(mock.Anything).Return()
33 r.EXPECT().Render(ports.Scene{Background: "#000000"}, map[ports.WindowID]ports.SurfaceContent(nil)).Return(nil, nil).Times(3)33 r.EXPECT().Render(ports.Scene{Background: "#000000"}, map[ports.WindowID]ports.SurfaceContent(nil)).Return(nil, nil).Times(3)
34 r.EXPECT().Close().Return().Once()34 r.EXPECT().Close().Return().Once()
@@ -94,8 +94,8 @@ func TestSecurityStartupClearWaitUsesRunContext(t *testing.T) {
94 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}94 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
95 }95 }
96 r.EXPECT().SetHDR(float64(0)).Return().Maybe()96 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
97- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()97+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
98- r.EXPECT().ExportTargets(0, []uint64(nil)).Return(nil, nil).Once()98+ r.EXPECT().ExportTargets(0, []uint64(nil), false).Return(nil, nil).Once()
99 r.EXPECT().UseTarget(0).Return().Once()99 r.EXPECT().UseTarget(0).Return().Once()
100 fence, _ := protectionFence(t, false)100 fence, _ := protectionFence(t, false)
101 ctx, cancel := context.WithCancel(context.Background())101 ctx, cancel := context.WithCancel(context.Background())
M internal/adapters/drm/stuck_test.go +1 -1
@@ -52,7 +52,7 @@ func startStuck(t *testing.T, render func() *os.File, errs ...error) *stuckRun {
5252 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
5353 }
5454 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
55- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
55+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
5656 r.EXPECT().UseTarget(mock.Anything).Return()
5757 var started atomic.Bool
5858 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
@@ -52,7 +52,7 @@ func startStuck(t *testing.T, render func() *os.File, errs ...error) *stuckRun {
52 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}52 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
53 }53 }
54 r.EXPECT().SetHDR(float64(0)).Return().Maybe()54 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
55- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once()55+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
56 r.EXPECT().UseTarget(mock.Anything).Return()56 r.EXPECT().UseTarget(mock.Anything).Return()
57 var started atomic.Bool57 var started atomic.Bool
58 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {58 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
M internal/adapters/drm/vrr_flip_gap_test.go +1 -1
@@ -92,7 +92,7 @@ func TestRunWaitsVRRFlipGap(t *testing.T) {
9292 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
9393 }
9494 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
95- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
95+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
9696 r.EXPECT().UseTarget(mock.Anything).Return()
9797 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
9898 r.EXPECT().Close().Return().Once()
@@ -92,7 +92,7 @@ func TestRunWaitsVRRFlipGap(t *testing.T) {
92 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}92 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
93 }93 }
94 r.EXPECT().SetHDR(float64(0)).Return().Maybe()94 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
95- r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()95+ r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
96 r.EXPECT().UseTarget(mock.Anything).Return()96 r.EXPECT().UseTarget(mock.Anything).Return()
97 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)97 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
98 r.EXPECT().Close().Return().Once()98 r.EXPECT().Close().Return().Once()
M internal/adapters/headless/output.go +2 -2
@@ -66,10 +66,10 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents
6666 var confirmed *ports.OutputHDR
6767 if opts.HDR {
6868 r.SetHDR(203)
69- if bufs, err := r.ExportTargets(1, nil); err != nil {
69+ if bufs, err := r.ExportTargets(1, nil, false); err != nil {
7070 opts.Log.Warn().Err(err).Str("output", opts.Name).Msg("virtual HDR unavailable; falling back to SDR")
7171 r.SetHDR(0)
72- _, _ = r.ExportTargets(0, nil)
72+ _, _ = r.ExportTargets(0, nil, false)
7373 } else {
7474 for _, b := range bufs {
7575 for _, p := range b.Planes {
@@ -66,10 +66,10 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents
66 var confirmed *ports.OutputHDR66 var confirmed *ports.OutputHDR
67 if opts.HDR {67 if opts.HDR {
68 r.SetHDR(203)68 r.SetHDR(203)
69- if bufs, err := r.ExportTargets(1, nil); err != nil {69+ if bufs, err := r.ExportTargets(1, nil, false); err != nil {
70 opts.Log.Warn().Err(err).Str("output", opts.Name).Msg("virtual HDR unavailable; falling back to SDR")70 opts.Log.Warn().Err(err).Str("output", opts.Name).Msg("virtual HDR unavailable; falling back to SDR")
71 r.SetHDR(0)71 r.SetHDR(0)
72- _, _ = r.ExportTargets(0, nil)72+ _, _ = r.ExportTargets(0, nil, false)
73 } else {73 } else {
74 for _, b := range bufs {74 for _, b := range bufs {
75 for _, p := range b.Planes {75 for _, p := range b.Planes {
M internal/adapters/headless/output_test.go +4 -4
@@ -340,7 +340,7 @@ func TestHeadlessRawHDRScreenshot(t *testing.T) {
340340 }
341341 r := portsmocks.NewMockRenderer(t)
342342 r.EXPECT().SetHDR(float64(203)).Return().Once()
343- r.EXPECT().ExportTargets(1, []uint64(nil)).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()
343+ r.EXPECT().ExportTargets(1, []uint64(nil), false).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()
344344 rendered := make(chan struct{}, 4)
345345 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
346346 rendered <- struct{}{}
@@ -413,16 +413,16 @@ func TestHeadlessHDRFormatsConfirmed(t *testing.T) {
413413 if err != nil {
414414 t.Fatal(err)
415415 }
416- r.EXPECT().ExportTargets(1, []uint64(nil)).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()
416+ r.EXPECT().ExportTargets(1, []uint64(nil), false).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()
417417 t.Cleanup(func() {
418418 if _, err := f.Stat(); err == nil {
419419 t.Error("exported fd not closed")
420420 }
421421 })
422422 } else {
423- r.EXPECT().ExportTargets(1, []uint64(nil)).Return(nil, errors.New("no compatible target")).Once()
423+ r.EXPECT().ExportTargets(1, []uint64(nil), false).Return(nil, errors.New("no compatible target")).Once()
424424 r.EXPECT().SetHDR(float64(0)).Return().Once()
425- r.EXPECT().ExportTargets(0, []uint64(nil)).Return(nil, nil).Once()
425+ r.EXPECT().ExportTargets(0, []uint64(nil), false).Return(nil, nil).Once()
426426 }
427427 r.EXPECT().Close().Return().Once()
428428 formats := make(chan ports.OutputFormats, 1)
@@ -340,7 +340,7 @@ func TestHeadlessRawHDRScreenshot(t *testing.T) {
340 }340 }
341 r := portsmocks.NewMockRenderer(t)341 r := portsmocks.NewMockRenderer(t)
342 r.EXPECT().SetHDR(float64(203)).Return().Once()342 r.EXPECT().SetHDR(float64(203)).Return().Once()
343- r.EXPECT().ExportTargets(1, []uint64(nil)).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()343+ r.EXPECT().ExportTargets(1, []uint64(nil), false).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()
344 rendered := make(chan struct{}, 4)344 rendered := make(chan struct{}, 4)
345 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {345 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
346 rendered <- struct{}{}346 rendered <- struct{}{}
@@ -413,16 +413,16 @@ func TestHeadlessHDRFormatsConfirmed(t *testing.T) {
413 if err != nil {413 if err != nil {
414 t.Fatal(err)414 t.Fatal(err)
415 }415 }
416- r.EXPECT().ExportTargets(1, []uint64(nil)).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()416+ r.EXPECT().ExportTargets(1, []uint64(nil), false).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()
417 t.Cleanup(func() {417 t.Cleanup(func() {
418 if _, err := f.Stat(); err == nil {418 if _, err := f.Stat(); err == nil {
419 t.Error("exported fd not closed")419 t.Error("exported fd not closed")
420 }420 }
421 })421 })
422 } else {422 } else {
423- r.EXPECT().ExportTargets(1, []uint64(nil)).Return(nil, errors.New("no compatible target")).Once()423+ r.EXPECT().ExportTargets(1, []uint64(nil), false).Return(nil, errors.New("no compatible target")).Once()
424 r.EXPECT().SetHDR(float64(0)).Return().Once()424 r.EXPECT().SetHDR(float64(0)).Return().Once()
425- r.EXPECT().ExportTargets(0, []uint64(nil)).Return(nil, nil).Once()425+ r.EXPECT().ExportTargets(0, []uint64(nil), false).Return(nil, nil).Once()
426 }426 }
427 r.EXPECT().Close().Return().Once()427 r.EXPECT().Close().Return().Once()
428 formats := make(chan ports.OutputFormats, 1)428 formats := make(chan ports.OutputFormats, 1)
M internal/adapters/headless/security_test.go +1 -1
@@ -267,7 +267,7 @@ func TestSecurityRawHDRSuppressed(t *testing.T) {
267267 }
268268 r := portsmocks.NewMockRenderer(t)
269269 r.EXPECT().SetHDR(float64(203)).Return().Once()
270- r.EXPECT().ExportTargets(1, []uint64(nil)).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()
270+ r.EXPECT().ExportTargets(1, []uint64(nil), false).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()
271271 rendered := make(chan ports.Scene, 4)
272272 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
273273 rendered <- s
@@ -267,7 +267,7 @@ func TestSecurityRawHDRSuppressed(t *testing.T) {
267 }267 }
268 r := portsmocks.NewMockRenderer(t)268 r := portsmocks.NewMockRenderer(t)
269 r.EXPECT().SetHDR(float64(203)).Return().Once()269 r.EXPECT().SetHDR(float64(203)).Return().Once()
270- r.EXPECT().ExportTargets(1, []uint64(nil)).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()270+ r.EXPECT().ExportTargets(1, []uint64(nil), false).Return([]ports.DMABuf{{Planes: []ports.DMABufPlane{{File: f}}}}, nil).Once()
271 rendered := make(chan ports.Scene, 4)271 rendered := make(chan ports.Scene, 4)
272 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {272 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
273 rendered <- s273 rendered <- s
M internal/adapters/vulkan/capture_test.go +2 -2
@@ -529,7 +529,7 @@ func TestCaptureSurvivesHDRToggle(t *testing.T) {
529529 hdrShot := readPixels(t, r).RGBAAt(3, 3)
530530 leased := captureWait(t, r) // HDR capture held across the toggle
531531 r.SetHDR(0)
532- if _, err := r.ExportTargets(0, nil); err != nil {
532+ if _, err := r.ExportTargets(0, nil, false); err != nil {
533533 t.Fatal(err)
534534 }
535535 scene.Seq++
@@ -556,7 +556,7 @@ func TestCaptureSurvivesHDRToggle(t *testing.T) {
556556 }
557557 // Back to HDR: the capture pass and slots are reused.
558558 r.SetHDR(203)
559- if _, err := r.ExportTargets(1, r.hdrMods); err != nil {
559+ if _, err := r.ExportTargets(1, r.hdrMods, false); err != nil {
560560 t.Skipf("no HDR target: %v", err)
561561 }
562562 scene.Seq++
@@ -529,7 +529,7 @@ func TestCaptureSurvivesHDRToggle(t *testing.T) {
529 hdrShot := readPixels(t, r).RGBAAt(3, 3)529 hdrShot := readPixels(t, r).RGBAAt(3, 3)
530 leased := captureWait(t, r) // HDR capture held across the toggle530 leased := captureWait(t, r) // HDR capture held across the toggle
531 r.SetHDR(0)531 r.SetHDR(0)
532- if _, err := r.ExportTargets(0, nil); err != nil {532+ if _, err := r.ExportTargets(0, nil, false); err != nil {
533 t.Fatal(err)533 t.Fatal(err)
534 }534 }
535 scene.Seq++535 scene.Seq++
@@ -556,7 +556,7 @@ func TestCaptureSurvivesHDRToggle(t *testing.T) {
556 }556 }
557 // Back to HDR: the capture pass and slots are reused.557 // Back to HDR: the capture pass and slots are reused.
558 r.SetHDR(203)558 r.SetHDR(203)
559- if _, err := r.ExportTargets(1, r.hdrMods); err != nil {559+ if _, err := r.ExportTargets(1, r.hdrMods, false); err != nil {
560 t.Skipf("no HDR target: %v", err)560 t.Skipf("no HDR target: %v", err)
561 }561 }
562 scene.Seq++562 scene.Seq++
M internal/adapters/vulkan/damage_test.go +1 -1
@@ -50,7 +50,7 @@ func damageMatchesFullRedraw(t *testing.T, tr, out ports.BufferTransform) {
5050 return r
5151 }
5252 damaged, full := newR(), newR()
53- bufsD, err := damaged.ExportTargets(2, nil)
53+ bufsD, err := damaged.ExportTargets(2, nil, false)
5454 if err != nil {
5555 t.Skipf("no exportable targets: %v", err)
5656 }
@@ -50,7 +50,7 @@ func damageMatchesFullRedraw(t *testing.T, tr, out ports.BufferTransform) {
50 return r50 return r
51 }51 }
52 damaged, full := newR(), newR()52 damaged, full := newR(), newR()
53- bufsD, err := damaged.ExportTargets(2, nil)53+ bufsD, err := damaged.ExportTargets(2, nil, false)
54 if err != nil {54 if err != nil {
55 t.Skipf("no exportable targets: %v", err)55 t.Skipf("no exportable targets: %v", err)
56 }56 }
M internal/adapters/vulkan/hdr_composition_test.go +3 -3
@@ -27,7 +27,7 @@ func hdrTestRenderer(t *testing.T) *Renderer {
2727 if len(r.hdrMods) == 0 {
2828 t.Skip("no HDR transfer-src modifier")
2929 }
30- bufs, err := r.ExportTargets(1, r.hdrMods)
30+ bufs, err := r.ExportTargets(1, r.hdrMods, false)
3131 if err != nil {
3232 t.Skipf("no HDR target: %v", err)
3333 }
@@ -116,7 +116,7 @@ func TestHDRPixels(t *testing.T) {
116116 defer plain.Close()
117117 plain.SetHDR(203)
118118 plain.SetVirtualOutput(true)
119- bufs, err := plain.ExportTargets(1, nil)
119+ bufs, err := plain.ExportTargets(1, nil, false)
120120 if err != nil {
121121 t.Skipf("no HDR target: %v", err)
122122 }
@@ -149,7 +149,7 @@ func TestHDRPixelsReadsRenderedTarget(t *testing.T) {
149149 t.Fatal(err)
150150 }
151151 // New targets replace the one the frame drew into: nothing to read.
152- bufs, err := r.ExportTargets(2, r.hdrMods)
152+ bufs, err := r.ExportTargets(2, r.hdrMods, false)
153153 if err != nil {
154154 t.Skipf("no two HDR targets: %v", err)
155155 }
@@ -27,7 +27,7 @@ func hdrTestRenderer(t *testing.T) *Renderer {
27 if len(r.hdrMods) == 0 {27 if len(r.hdrMods) == 0 {
28 t.Skip("no HDR transfer-src modifier")28 t.Skip("no HDR transfer-src modifier")
29 }29 }
30- bufs, err := r.ExportTargets(1, r.hdrMods)30+ bufs, err := r.ExportTargets(1, r.hdrMods, false)
31 if err != nil {31 if err != nil {
32 t.Skipf("no HDR target: %v", err)32 t.Skipf("no HDR target: %v", err)
33 }33 }
@@ -116,7 +116,7 @@ func TestHDRPixels(t *testing.T) {
116 defer plain.Close()116 defer plain.Close()
117 plain.SetHDR(203)117 plain.SetHDR(203)
118 plain.SetVirtualOutput(true)118 plain.SetVirtualOutput(true)
119- bufs, err := plain.ExportTargets(1, nil)119+ bufs, err := plain.ExportTargets(1, nil, false)
120 if err != nil {120 if err != nil {
121 t.Skipf("no HDR target: %v", err)121 t.Skipf("no HDR target: %v", err)
122 }122 }
@@ -149,7 +149,7 @@ func TestHDRPixelsReadsRenderedTarget(t *testing.T) {
149 t.Fatal(err)149 t.Fatal(err)
150 }150 }
151 // New targets replace the one the frame drew into: nothing to read.151 // New targets replace the one the frame drew into: nothing to read.
152- bufs, err := r.ExportTargets(2, r.hdrMods)152+ bufs, err := r.ExportTargets(2, r.hdrMods, false)
153 if err != nil {153 if err != nil {
154 t.Skipf("no two HDR targets: %v", err)154 t.Skipf("no two HDR targets: %v", err)
155 }155 }
M internal/adapters/vulkan/renderer.go +10 -0
@@ -10,6 +10,7 @@ import (
1010 "unsafe"
1111
1212 "github.com/bnema/neferwl/internal/ports"
13+ "github.com/bnema/zerowrap"
1314
1415 vk "github.com/bnema/purego-vulkan/vulkan"
1516 )
@@ -35,6 +36,11 @@ type Renderer struct {
3536 hdrNits float64
3637 hdr hdrPass
3738 hdrError error
39+ // modPlanes is the memory plane count of each probed target modifier
40+ // (SDR and HDR): DCC modifiers have more than one.
41+ modPlanes map[uint64]uint32
42+ // log reports the chosen target modifier; the zero value discards.
43+ log zerowrap.Logger
3844 // hdrReadback makes exported HDR targets transfer sources, for GPU tests
3945 // and headless --screenshot-raw (SetHDRReadback); never DRM.
4046 hdrReadback bool
@@ -340,6 +346,10 @@ func (r *Renderer) TakeRedrawn() int {
340346 return n
341347 }
342348
349+// SetLogger sets the logger (with its component field) for render target
350+// events. Not on the port; the zero logger discards.
351+func (r *Renderer) SetLogger(l zerowrap.Logger) { r.log = l }
352+
343353 // QueuePriority is the global priority the queue got: realtime, high or
344354 // default.
345355 func (r *Renderer) QueuePriority() string { return r.queuePriority }
@@ -10,6 +10,7 @@ import (
10 "unsafe"10 "unsafe"
11 11
12 "github.com/bnema/neferwl/internal/ports"12 "github.com/bnema/neferwl/internal/ports"
13+ "github.com/bnema/zerowrap"
13 14
14 vk "github.com/bnema/purego-vulkan/vulkan"15 vk "github.com/bnema/purego-vulkan/vulkan"
15 )16 )
@@ -35,6 +36,11 @@ type Renderer struct {
35 hdrNits float6436 hdrNits float64
36 hdr hdrPass37 hdr hdrPass
37 hdrError error38 hdrError error
39+ // modPlanes is the memory plane count of each probed target modifier
40+ // (SDR and HDR): DCC modifiers have more than one.
41+ modPlanes map[uint64]uint32
42+ // log reports the chosen target modifier; the zero value discards.
43+ log zerowrap.Logger
38 // hdrReadback makes exported HDR targets transfer sources, for GPU tests44 // hdrReadback makes exported HDR targets transfer sources, for GPU tests
39 // and headless --screenshot-raw (SetHDRReadback); never DRM.45 // and headless --screenshot-raw (SetHDRReadback); never DRM.
40 hdrReadback bool46 hdrReadback bool
@@ -340,6 +346,10 @@ func (r *Renderer) TakeRedrawn() int {
340 return n346 return n
341 }347 }
342 348
349+// SetLogger sets the logger (with its component field) for render target
350+// events. Not on the port; the zero logger discards.
351+func (r *Renderer) SetLogger(l zerowrap.Logger) { r.log = l }
352+
343 // QueuePriority is the global priority the queue got: realtime, high or353 // QueuePriority is the global priority the queue got: realtime, high or
344 // default.354 // default.
345 func (r *Renderer) QueuePriority() string { return r.queuePriority }355 func (r *Renderer) QueuePriority() string { return r.queuePriority }
M internal/adapters/vulkan/targets.go +68 -18
@@ -3,12 +3,14 @@ package vulkan
33 import (
44 "errors"
55 "fmt"
6+ "maps"
67 "os"
78 "slices"
89 "unsafe"
910
1011 "github.com/bnema/neferwl/internal/ports"
1112 vk "github.com/bnema/purego-vulkan/vulkan"
13+ "golang.org/x/sys/unix"
1214 )
1315
1416 // Render targets (ADR 014). A DRM output scans out images the renderer
@@ -44,7 +46,9 @@ const fourccXR30 = 'X' | 'R'<<8 | '3'<<16 | '0'<<24
4446
4547 // ExportTargets allocates n exported images of the output size, replacing
4648 // the previous ones; n = 0 only drops them (back to the internal image).
47-func (r *Renderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMABuf, error) {
49+// singlePlane keeps only one-plane modifiers (no DCC metadata planes), for
50+// displays that refuse the multi-plane framebuffers of the others.
51+func (r *Renderer) ExportTargets(n int, modifiers []uint64, singlePlane bool) ([]ports.DMABuf, error) {
4852 r.dropTargets()
4953 if n > 0 && r.hdrNits > 0 && r.hdrReadback && r.physical != 0 {
5054 // Readback targets (GPU tests, headless --screenshot-raw) are also
@@ -60,7 +64,7 @@ func (r *Renderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMABuf, err
6064 if !r.dd.HasGetMemoryFdKHR() || !r.dd.HasGetImageDrmFormatModifierPropertiesEXT() || len(r.dmabuf.Formats) == 0 {
6165 return nil, errors.New("device cannot export dmabufs")
6266 }
63- mods := r.exportModifiers(modifiers)
67+ mods := r.exportModifiers(modifiers, singlePlane)
6468 if len(mods) == 0 {
6569 if r.hdrNits == 0 {
6670 return nil, errors.New("no XRGB8888 modifier both the device and the display accept")
@@ -108,14 +112,18 @@ func (r *Renderer) target() *target {
108112
109113 // exportModifiers intersects the selected signal format modifiers with
110114 // those supported by the display. HDR never assumes an unspecified list,
111-// except for a virtual output (no display) or a test readback.
112-func (r *Renderer) exportModifiers(display []uint64) []uint64 {
115+// except for a virtual output (no display) or a test readback. singlePlane
116+// drops the modifiers with more than one memory plane.
117+func (r *Renderer) exportModifiers(display []uint64, singlePlane bool) []uint64 {
113118 var out []uint64
114119 available := r.renderMods
115120 if r.hdrNits > 0 {
116121 available = r.hdrMods
117122 }
118123 for _, m := range available {
124+ if singlePlane && r.modPlanes[m] > 1 {
125+ continue
126+ }
119127 if (len(display) == 0 && (r.hdrNits == 0 || r.hdrReadback || r.virtual)) || slices.Contains(display, m) {
120128 out = append(out, m)
121129 }
@@ -123,14 +131,15 @@ func (r *Renderer) exportModifiers(display []uint64) []uint64 {
123131 return out
124132 }
125133
126-// probeRenderModifiers lists single-plane, exportable SDR and HDR targets.
134+// probeRenderModifiers lists exportable SDR and HDR targets.
127135 func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) {
128136 r.renderMods = r.probeModifiers(physical, vk.FormatB8g8r8a8Unorm)
129137 r.hdrMods = r.probeModifiers(physical, vk.FormatA2r10g10b10UnormPack32)
130138 }
131139
132-// probeModifiers lists the single-plane, exportable modifiers of format that
133-// support the target usage. HDR targets need transfer-src only when
140+// probeModifiers lists the exportable modifiers of format (one to four memory
141+// planes: DCC adds metadata planes) that support the target usage, and
142+// records their plane counts in modPlanes. HDR targets need transfer-src only when
134143 // hdrReadback is set (GPU tests, headless --screenshot-raw; never DRM).
135144 func (r *Renderer) probeModifiers(physical vk.PhysicalDevice, format vk.Format) []uint64 {
136145 list := vk.DrmFormatModifierPropertiesListEXT{SType: vk.StructureTypeDRMFormatModifierPropertiesListEXT}
@@ -148,17 +157,37 @@ func (r *Renderer) probeModifiers(physical vk.PhysicalDevice, format vk.Format)
148157 } else if r.hdrReadback {
149158 need |= formatFeatureTransferSrc
150159 }
151- var result []uint64
152160 // SDR targets are composed and read back; HDR targets are color
153161 // attachments, and transfer sources only with hdrReadback.
154- for _, m := range mods[:list.DrmFormatModifierCount] {
155- if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, format, m.DrmFormatModifier) {
156- result = append(result, m.DrmFormatModifier)
157- }
162+ result, planes := filterModifiers(mods[:list.DrmFormatModifierCount], need, func(m uint64) bool { return r.exportable(physical, format, m) })
163+ if r.modPlanes == nil {
164+ r.modPlanes = make(map[uint64]uint32)
158165 }
166+ // A modifier has the same plane count for every format (DCC planes
167+ // come from the modifier), so SDR and HDR share the map.
168+ maps.Copy(r.modPlanes, planes)
159169 return result
160170 }
161171
172+// maxModifierPlanes is the DRM limit of planes in a framebuffer.
173+const maxModifierPlanes = 4
174+
175+// filterModifiers keeps the modifiers with 1..4 memory planes whose tiling
176+// features include need and that pass exportable, and returns each kept
177+// modifier's plane count.
178+func filterModifiers(mods []vk.DrmFormatModifierPropertiesEXT, need vk.FormatFeatureFlags, exportable func(uint64) bool) ([]uint64, map[uint64]uint32) {
179+ var result []uint64
180+ planes := make(map[uint64]uint32, len(mods))
181+ for _, m := range mods {
182+ if m.DrmFormatModifierPlaneCount < 1 || m.DrmFormatModifierPlaneCount > maxModifierPlanes || m.DrmFormatModifierTilingFeatures&need != need || !exportable(m.DrmFormatModifier) {
183+ continue
184+ }
185+ result = append(result, m.DrmFormatModifier)
186+ planes[m.DrmFormatModifier] = m.DrmFormatModifierPlaneCount
187+ }
188+ return result, planes
189+}
190+
162191 func (r *Renderer) exportable(physical vk.PhysicalDevice, format vk.Format, modifier uint64) bool {
163192 mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive}
164193 ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
@@ -226,9 +255,12 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
226255 if err := checked("vkGetImageDrmFormatModifierPropertiesEXT", d.GetImageDrmFormatModifierPropertiesEXT(r.device, t.image, &props)); err != nil {
227256 return nil, ports.DMABuf{}, err
228257 }
229- var layout vk.SubresourceLayout
230- sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT}
231- d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout)
258+ n := max(r.modPlanes[props.DrmFormatModifier], 1)
259+ var layouts [maxModifierPlanes]vk.SubresourceLayout
260+ for i := range min(n, maxModifierPlanes) {
261+ sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT << i}
262+ d.GetImageSubresourceLayout(r.device, t.image, &sub, &layouts[i])
263+ }
232264 t.view, err = r.imageView(t.image, format)
233265 if err != nil {
234266 return nil, ports.DMABuf{}, err
@@ -238,11 +270,29 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
238270 if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil {
239271 return nil, ports.DMABuf{}, err
240272 }
241- f := os.NewFile(uintptr(fd), "neferwl-target")
242- if f == nil {
273+ // Every plane owns its file: the planes share the one memory object,
274+ // so planes 1.. are duplicates of the exported fd.
275+ planes := make([]ports.DMABufPlane, 0, n)
276+ closePlanes := func() {
277+ for _, p := range planes {
278+ _ = p.File.Close()
279+ }
280+ }
281+ first := os.NewFile(uintptr(fd), "neferwl-target")
282+ if first == nil {
243283 return nil, ports.DMABuf{}, fmt.Errorf("invalid exported fd %d", fd)
244284 }
245- buf := ports.DMABuf{Width: r.width, Height: r.height, Format: fourcc, Modifier: props.DrmFormatModifier, Planes: []ports.DMABufPlane{{File: f, Offset: uint32(layout.Offset), Stride: uint32(layout.RowPitch)}}}
285+ planes = append(planes, ports.DMABufPlane{File: first, Offset: uint32(layouts[0].Offset), Stride: uint32(layouts[0].RowPitch)})
286+ for i := uint32(1); i < n; i++ {
287+ dup, err := unix.FcntlInt(first.Fd(), unix.F_DUPFD_CLOEXEC, 0)
288+ if err != nil {
289+ closePlanes()
290+ return nil, ports.DMABuf{}, fmt.Errorf("duplicate exported fd for plane %d: %w", i, err)
291+ }
292+ planes = append(planes, ports.DMABufPlane{File: os.NewFile(uintptr(dup), "neferwl-target"), Offset: uint32(layouts[i].Offset), Stride: uint32(layouts[i].RowPitch)})
293+ }
294+ r.log.Info().Uint64("modifier", props.DrmFormatModifier).Uint32("planes", n).Msg("render target exported")
295+ buf := ports.DMABuf{Width: r.width, Height: r.height, Format: fourcc, Modifier: props.DrmFormatModifier, Planes: planes}
246296 ok = true
247297 return t, buf, nil
248298 }
@@ -3,12 +3,14 @@ package vulkan
3 import (3 import (
4 "errors"4 "errors"
5 "fmt"5 "fmt"
6+ "maps"
6 "os"7 "os"
7 "slices"8 "slices"
8 "unsafe"9 "unsafe"
9 10
10 "github.com/bnema/neferwl/internal/ports"11 "github.com/bnema/neferwl/internal/ports"
11 vk "github.com/bnema/purego-vulkan/vulkan"12 vk "github.com/bnema/purego-vulkan/vulkan"
13+ "golang.org/x/sys/unix"
12 )14 )
13 15
14 // Render targets (ADR 014). A DRM output scans out images the renderer16 // Render targets (ADR 014). A DRM output scans out images the renderer
@@ -44,7 +46,9 @@ const fourccXR30 = 'X' | 'R'<<8 | '3'<<16 | '0'<<24
44 46
45 // ExportTargets allocates n exported images of the output size, replacing47 // ExportTargets allocates n exported images of the output size, replacing
46 // the previous ones; n = 0 only drops them (back to the internal image).48 // the previous ones; n = 0 only drops them (back to the internal image).
47-func (r *Renderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMABuf, error) {49+// singlePlane keeps only one-plane modifiers (no DCC metadata planes), for
50+// displays that refuse the multi-plane framebuffers of the others.
51+func (r *Renderer) ExportTargets(n int, modifiers []uint64, singlePlane bool) ([]ports.DMABuf, error) {
48 r.dropTargets()52 r.dropTargets()
49 if n > 0 && r.hdrNits > 0 && r.hdrReadback && r.physical != 0 {53 if n > 0 && r.hdrNits > 0 && r.hdrReadback && r.physical != 0 {
50 // Readback targets (GPU tests, headless --screenshot-raw) are also54 // Readback targets (GPU tests, headless --screenshot-raw) are also
@@ -60,7 +64,7 @@ func (r *Renderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMABuf, err
60 if !r.dd.HasGetMemoryFdKHR() || !r.dd.HasGetImageDrmFormatModifierPropertiesEXT() || len(r.dmabuf.Formats) == 0 {64 if !r.dd.HasGetMemoryFdKHR() || !r.dd.HasGetImageDrmFormatModifierPropertiesEXT() || len(r.dmabuf.Formats) == 0 {
61 return nil, errors.New("device cannot export dmabufs")65 return nil, errors.New("device cannot export dmabufs")
62 }66 }
63- mods := r.exportModifiers(modifiers)67+ mods := r.exportModifiers(modifiers, singlePlane)
64 if len(mods) == 0 {68 if len(mods) == 0 {
65 if r.hdrNits == 0 {69 if r.hdrNits == 0 {
66 return nil, errors.New("no XRGB8888 modifier both the device and the display accept")70 return nil, errors.New("no XRGB8888 modifier both the device and the display accept")
@@ -108,14 +112,18 @@ func (r *Renderer) target() *target {
108 112
109 // exportModifiers intersects the selected signal format modifiers with113 // exportModifiers intersects the selected signal format modifiers with
110 // those supported by the display. HDR never assumes an unspecified list,114 // those supported by the display. HDR never assumes an unspecified list,
111-// except for a virtual output (no display) or a test readback.115+// except for a virtual output (no display) or a test readback. singlePlane
112-func (r *Renderer) exportModifiers(display []uint64) []uint64 {116+// drops the modifiers with more than one memory plane.
117+func (r *Renderer) exportModifiers(display []uint64, singlePlane bool) []uint64 {
113 var out []uint64118 var out []uint64
114 available := r.renderMods119 available := r.renderMods
115 if r.hdrNits > 0 {120 if r.hdrNits > 0 {
116 available = r.hdrMods121 available = r.hdrMods
117 }122 }
118 for _, m := range available {123 for _, m := range available {
124+ if singlePlane && r.modPlanes[m] > 1 {
125+ continue
126+ }
119 if (len(display) == 0 && (r.hdrNits == 0 || r.hdrReadback || r.virtual)) || slices.Contains(display, m) {127 if (len(display) == 0 && (r.hdrNits == 0 || r.hdrReadback || r.virtual)) || slices.Contains(display, m) {
120 out = append(out, m)128 out = append(out, m)
121 }129 }
@@ -123,14 +131,15 @@ func (r *Renderer) exportModifiers(display []uint64) []uint64 {
123 return out131 return out
124 }132 }
125 133
126-// probeRenderModifiers lists single-plane, exportable SDR and HDR targets.134+// probeRenderModifiers lists exportable SDR and HDR targets.
127 func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) {135 func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) {
128 r.renderMods = r.probeModifiers(physical, vk.FormatB8g8r8a8Unorm)136 r.renderMods = r.probeModifiers(physical, vk.FormatB8g8r8a8Unorm)
129 r.hdrMods = r.probeModifiers(physical, vk.FormatA2r10g10b10UnormPack32)137 r.hdrMods = r.probeModifiers(physical, vk.FormatA2r10g10b10UnormPack32)
130 }138 }
131 139
132-// probeModifiers lists the single-plane, exportable modifiers of format that140+// probeModifiers lists the exportable modifiers of format (one to four memory
133-// support the target usage. HDR targets need transfer-src only when141+// planes: DCC adds metadata planes) that support the target usage, and
142+// records their plane counts in modPlanes. HDR targets need transfer-src only when
134 // hdrReadback is set (GPU tests, headless --screenshot-raw; never DRM).143 // hdrReadback is set (GPU tests, headless --screenshot-raw; never DRM).
135 func (r *Renderer) probeModifiers(physical vk.PhysicalDevice, format vk.Format) []uint64 {144 func (r *Renderer) probeModifiers(physical vk.PhysicalDevice, format vk.Format) []uint64 {
136 list := vk.DrmFormatModifierPropertiesListEXT{SType: vk.StructureTypeDRMFormatModifierPropertiesListEXT}145 list := vk.DrmFormatModifierPropertiesListEXT{SType: vk.StructureTypeDRMFormatModifierPropertiesListEXT}
@@ -148,17 +157,37 @@ func (r *Renderer) probeModifiers(physical vk.PhysicalDevice, format vk.Format)
148 } else if r.hdrReadback {157 } else if r.hdrReadback {
149 need |= formatFeatureTransferSrc158 need |= formatFeatureTransferSrc
150 }159 }
151- var result []uint64
152 // SDR targets are composed and read back; HDR targets are color160 // SDR targets are composed and read back; HDR targets are color
153 // attachments, and transfer sources only with hdrReadback.161 // attachments, and transfer sources only with hdrReadback.
154- for _, m := range mods[:list.DrmFormatModifierCount] {162+ result, planes := filterModifiers(mods[:list.DrmFormatModifierCount], need, func(m uint64) bool { return r.exportable(physical, format, m) })
155- if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, format, m.DrmFormatModifier) {163+ if r.modPlanes == nil {
156- result = append(result, m.DrmFormatModifier)164+ r.modPlanes = make(map[uint64]uint32)
157- }
158 }165 }
166+ // A modifier has the same plane count for every format (DCC planes
167+ // come from the modifier), so SDR and HDR share the map.
168+ maps.Copy(r.modPlanes, planes)
159 return result169 return result
160 }170 }
161 171
172+// maxModifierPlanes is the DRM limit of planes in a framebuffer.
173+const maxModifierPlanes = 4
174+
175+// filterModifiers keeps the modifiers with 1..4 memory planes whose tiling
176+// features include need and that pass exportable, and returns each kept
177+// modifier's plane count.
178+func filterModifiers(mods []vk.DrmFormatModifierPropertiesEXT, need vk.FormatFeatureFlags, exportable func(uint64) bool) ([]uint64, map[uint64]uint32) {
179+ var result []uint64
180+ planes := make(map[uint64]uint32, len(mods))
181+ for _, m := range mods {
182+ if m.DrmFormatModifierPlaneCount < 1 || m.DrmFormatModifierPlaneCount > maxModifierPlanes || m.DrmFormatModifierTilingFeatures&need != need || !exportable(m.DrmFormatModifier) {
183+ continue
184+ }
185+ result = append(result, m.DrmFormatModifier)
186+ planes[m.DrmFormatModifier] = m.DrmFormatModifierPlaneCount
187+ }
188+ return result, planes
189+}
190+
162 func (r *Renderer) exportable(physical vk.PhysicalDevice, format vk.Format, modifier uint64) bool {191 func (r *Renderer) exportable(physical vk.PhysicalDevice, format vk.Format, modifier uint64) bool {
163 mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive}192 mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive}
164 ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}193 ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
@@ -226,9 +255,12 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
226 if err := checked("vkGetImageDrmFormatModifierPropertiesEXT", d.GetImageDrmFormatModifierPropertiesEXT(r.device, t.image, &props)); err != nil {255 if err := checked("vkGetImageDrmFormatModifierPropertiesEXT", d.GetImageDrmFormatModifierPropertiesEXT(r.device, t.image, &props)); err != nil {
227 return nil, ports.DMABuf{}, err256 return nil, ports.DMABuf{}, err
228 }257 }
229- var layout vk.SubresourceLayout258+ n := max(r.modPlanes[props.DrmFormatModifier], 1)
230- sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT}259+ var layouts [maxModifierPlanes]vk.SubresourceLayout
231- d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout)260+ for i := range min(n, maxModifierPlanes) {
261+ sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT << i}
262+ d.GetImageSubresourceLayout(r.device, t.image, &sub, &layouts[i])
263+ }
232 t.view, err = r.imageView(t.image, format)264 t.view, err = r.imageView(t.image, format)
233 if err != nil {265 if err != nil {
234 return nil, ports.DMABuf{}, err266 return nil, ports.DMABuf{}, err
@@ -238,11 +270,29 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
238 if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil {270 if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil {
239 return nil, ports.DMABuf{}, err271 return nil, ports.DMABuf{}, err
240 }272 }
241- f := os.NewFile(uintptr(fd), "neferwl-target")273+ // Every plane owns its file: the planes share the one memory object,
242- if f == nil {274+ // so planes 1.. are duplicates of the exported fd.
275+ planes := make([]ports.DMABufPlane, 0, n)
276+ closePlanes := func() {
277+ for _, p := range planes {
278+ _ = p.File.Close()
279+ }
280+ }
281+ first := os.NewFile(uintptr(fd), "neferwl-target")
282+ if first == nil {
243 return nil, ports.DMABuf{}, fmt.Errorf("invalid exported fd %d", fd)283 return nil, ports.DMABuf{}, fmt.Errorf("invalid exported fd %d", fd)
244 }284 }
245- buf := ports.DMABuf{Width: r.width, Height: r.height, Format: fourcc, Modifier: props.DrmFormatModifier, Planes: []ports.DMABufPlane{{File: f, Offset: uint32(layout.Offset), Stride: uint32(layout.RowPitch)}}}285+ planes = append(planes, ports.DMABufPlane{File: first, Offset: uint32(layouts[0].Offset), Stride: uint32(layouts[0].RowPitch)})
286+ for i := uint32(1); i < n; i++ {
287+ dup, err := unix.FcntlInt(first.Fd(), unix.F_DUPFD_CLOEXEC, 0)
288+ if err != nil {
289+ closePlanes()
290+ return nil, ports.DMABuf{}, fmt.Errorf("duplicate exported fd for plane %d: %w", i, err)
291+ }
292+ planes = append(planes, ports.DMABufPlane{File: os.NewFile(uintptr(dup), "neferwl-target"), Offset: uint32(layouts[i].Offset), Stride: uint32(layouts[i].RowPitch)})
293+ }
294+ r.log.Info().Uint64("modifier", props.DrmFormatModifier).Uint32("planes", n).Msg("render target exported")
295+ buf := ports.DMABuf{Width: r.width, Height: r.height, Format: fourcc, Modifier: props.DrmFormatModifier, Planes: planes}
246 ok = true296 ok = true
247 return t, buf, nil297 return t, buf, nil
248 }298 }
M internal/adapters/vulkan/targets_test.go +98 -6
@@ -3,6 +3,7 @@ package vulkan
33 import (
44 "image/color"
55 "math"
6+ "slices"
67 "testing"
78
89 "github.com/bnema/neferwl/internal/ports"
@@ -11,20 +12,24 @@ import (
1112
1213 // Frames drawn into exported targets reach their dmabufs: another renderer
1314 // importing a target (as KMS scans it out) sees the frame, and readback
14-// reads the target drawn last.
15+// reads the target drawn last. The viewer imports one-plane buffers only,
16+// so the targets are single-plane here; TestExportTargetsPlanes covers the
17+// multi-plane (DCC) ones.
1518 func TestExportTargets(t *testing.T) {
1619 r, err := New(64, 32)
1720 if err != nil {
1821 t.Skipf("Vulkan unavailable: %v", err)
1922 }
2023 defer r.Close()
21- bufs, err := r.ExportTargets(2, nil)
24+ bufs, err := r.ExportTargets(2, nil, true)
2225 if err != nil {
2326 t.Skipf("no exportable targets: %v", err)
2427 }
2528 defer func() {
2629 for _, b := range bufs {
27- b.Planes[0].File.Close()
30+ for _, p := range b.Planes {
31+ p.File.Close()
32+ }
2833 }
2934 }()
3035 if len(bufs) != 2 || bufs[0].Width != 64 || bufs[0].Format != fourccXRGB || bufs[0].Planes[0].Stride < 64*4 {
@@ -83,7 +88,7 @@ func TestHDRExportTarget(t *testing.T) {
8388 if len(r.hdrMods) == 0 {
8489 t.Skip("no HDR modifier supporting transfer-src")
8590 }
86- bufs, err := r.ExportTargets(1, r.hdrMods)
91+ bufs, err := r.ExportTargets(1, r.hdrMods, false)
8792 if err != nil {
8893 t.Skipf("no HDR-exportable target: %v", err)
8994 }
@@ -140,12 +145,12 @@ func TestVirtualHDRExportsWithoutDisplayList(t *testing.T) {
140145 t.Skip("no exportable HDR GPU")
141146 }
142147 r.SetHDR(203)
143- if bufs, err := r.ExportTargets(1, nil); err == nil {
148+ if bufs, err := r.ExportTargets(1, nil, false); err == nil {
144149 bufs[0].Planes[0].File.Close()
145150 t.Fatal("HDR export without a display list succeeded on a non-virtual output")
146151 }
147152 r.SetVirtualOutput(true)
148- bufs, err := r.ExportTargets(1, nil)
153+ bufs, err := r.ExportTargets(1, nil, false)
149154 if err != nil {
150155 t.Fatalf("virtual HDR export: %v", err)
151156 }
@@ -167,3 +172,90 @@ func checkHDRPixel(t *testing.T, r *Renderer, rgb [3]float64) {
167172 }
168173 }
169174 }
175+
176+// Targets may use DCC modifiers: one to four memory planes are kept and
177+// their counts recorded; singlePlane drops the multi-plane ones.
178+func TestFilterModifiersKeepsMultiPlane(t *testing.T) {
179+ need := vk.FormatFeatureFlags(vk.FormatFeatureColorAttachmentBit)
180+ mods := []vk.DrmFormatModifierPropertiesEXT{
181+ {DrmFormatModifier: 0, DrmFormatModifierPlaneCount: 1, DrmFormatModifierTilingFeatures: need},
182+ {DrmFormatModifier: 10, DrmFormatModifierPlaneCount: 3, DrmFormatModifierTilingFeatures: need},
183+ {DrmFormatModifier: 11, DrmFormatModifierPlaneCount: 4, DrmFormatModifierTilingFeatures: need},
184+ {DrmFormatModifier: 12, DrmFormatModifierPlaneCount: 5, DrmFormatModifierTilingFeatures: need},
185+ {DrmFormatModifier: 13, DrmFormatModifierPlaneCount: 0, DrmFormatModifierTilingFeatures: need},
186+ {DrmFormatModifier: 14, DrmFormatModifierPlaneCount: 2},
187+ {DrmFormatModifier: 15, DrmFormatModifierPlaneCount: 2, DrmFormatModifierTilingFeatures: need},
188+ }
189+ got, planes := filterModifiers(mods, need, func(m uint64) bool { return m != 15 })
190+ if !slices.Equal(got, []uint64{0, 10, 11}) {
191+ t.Fatalf("kept %v", got)
192+ }
193+ if planes[0] != 1 || planes[10] != 3 || planes[11] != 4 || len(planes) != 3 {
194+ t.Fatalf("planes %v", planes)
195+ }
196+ r := &Renderer{renderMods: got, modPlanes: planes}
197+ if all := r.exportModifiers(nil, false); !slices.Equal(all, []uint64{0, 10, 11}) {
198+ t.Fatalf("multi-plane export %v", all)
199+ }
200+ if one := r.exportModifiers(nil, true); !slices.Equal(one, []uint64{0}) {
201+ t.Fatalf("single-plane export %v", one)
202+ }
203+ if one := r.exportModifiers([]uint64{10, 11}, true); len(one) != 0 {
204+ t.Fatalf("single-plane with only DCC offered: %v", one)
205+ }
206+ if two := r.exportModifiers([]uint64{10, 0}, false); !slices.Equal(two, []uint64{0, 10}) {
207+ t.Fatalf("display intersection %v", two)
208+ }
209+}
210+
211+// Every exported target carries one file per memory plane of its modifier
212+// (DCC modifiers have two to four), each with a layout, and each file is
213+// an independent descriptor of the shared memory.
214+func TestExportTargetsPlanes(t *testing.T) {
215+ r, err := New(64, 32)
216+ if err != nil {
217+ t.Skipf("Vulkan unavailable: %v", err)
218+ }
219+ defer r.Close()
220+ bufs, err := r.ExportTargets(2, nil, false)
221+ if err != nil {
222+ t.Skipf("no exportable targets: %v", err)
223+ }
224+ defer func() {
225+ for _, b := range bufs {
226+ for _, p := range b.Planes {
227+ p.File.Close()
228+ }
229+ }
230+ }()
231+ for _, b := range bufs {
232+ want := max(r.modPlanes[b.Modifier], 1)
233+ if uint32(len(b.Planes)) != want {
234+ t.Fatalf("modifier %#x: %d plane files, want %d", b.Modifier, len(b.Planes), want)
235+ }
236+ seen := map[int]bool{}
237+ for i, p := range b.Planes {
238+ if p.File == nil || p.Stride == 0 {
239+ t.Fatalf("modifier %#x plane %d: %+v", b.Modifier, i, p)
240+ }
241+ if seen[int(p.File.Fd())] {
242+ t.Fatalf("modifier %#x: plane %d shares a file", b.Modifier, i)
243+ }
244+ seen[int(p.File.Fd())] = true
245+ }
246+ t.Logf("modifier %#x planes %d", b.Modifier, len(b.Planes))
247+ }
248+ // Single-plane export never returns a multi-plane modifier.
249+ one, err := r.ExportTargets(1, nil, true)
250+ if err != nil {
251+ t.Skipf("no single-plane targets: %v", err)
252+ }
253+ defer func() {
254+ for _, p := range one[0].Planes {
255+ p.File.Close()
256+ }
257+ }()
258+ if len(one[0].Planes) != 1 {
259+ t.Fatalf("single-plane export has %d planes", len(one[0].Planes))
260+ }
261+}
@@ -3,6 +3,7 @@ package vulkan
3 import (3 import (
4 "image/color"4 "image/color"
5 "math"5 "math"
6+ "slices"
6 "testing"7 "testing"
7 8
8 "github.com/bnema/neferwl/internal/ports"9 "github.com/bnema/neferwl/internal/ports"
@@ -11,20 +12,24 @@ import (
11 12
12 // Frames drawn into exported targets reach their dmabufs: another renderer13 // Frames drawn into exported targets reach their dmabufs: another renderer
13 // importing a target (as KMS scans it out) sees the frame, and readback14 // importing a target (as KMS scans it out) sees the frame, and readback
14-// reads the target drawn last.15+// reads the target drawn last. The viewer imports one-plane buffers only,
16+// so the targets are single-plane here; TestExportTargetsPlanes covers the
17+// multi-plane (DCC) ones.
15 func TestExportTargets(t *testing.T) {18 func TestExportTargets(t *testing.T) {
16 r, err := New(64, 32)19 r, err := New(64, 32)
17 if err != nil {20 if err != nil {
18 t.Skipf("Vulkan unavailable: %v", err)21 t.Skipf("Vulkan unavailable: %v", err)
19 }22 }
20 defer r.Close()23 defer r.Close()
21- bufs, err := r.ExportTargets(2, nil)24+ bufs, err := r.ExportTargets(2, nil, true)
22 if err != nil {25 if err != nil {
23 t.Skipf("no exportable targets: %v", err)26 t.Skipf("no exportable targets: %v", err)
24 }27 }
25 defer func() {28 defer func() {
26 for _, b := range bufs {29 for _, b := range bufs {
27- b.Planes[0].File.Close()30+ for _, p := range b.Planes {
31+ p.File.Close()
32+ }
28 }33 }
29 }()34 }()
30 if len(bufs) != 2 || bufs[0].Width != 64 || bufs[0].Format != fourccXRGB || bufs[0].Planes[0].Stride < 64*4 {35 if len(bufs) != 2 || bufs[0].Width != 64 || bufs[0].Format != fourccXRGB || bufs[0].Planes[0].Stride < 64*4 {
@@ -83,7 +88,7 @@ func TestHDRExportTarget(t *testing.T) {
83 if len(r.hdrMods) == 0 {88 if len(r.hdrMods) == 0 {
84 t.Skip("no HDR modifier supporting transfer-src")89 t.Skip("no HDR modifier supporting transfer-src")
85 }90 }
86- bufs, err := r.ExportTargets(1, r.hdrMods)91+ bufs, err := r.ExportTargets(1, r.hdrMods, false)
87 if err != nil {92 if err != nil {
88 t.Skipf("no HDR-exportable target: %v", err)93 t.Skipf("no HDR-exportable target: %v", err)
89 }94 }
@@ -140,12 +145,12 @@ func TestVirtualHDRExportsWithoutDisplayList(t *testing.T) {
140 t.Skip("no exportable HDR GPU")145 t.Skip("no exportable HDR GPU")
141 }146 }
142 r.SetHDR(203)147 r.SetHDR(203)
143- if bufs, err := r.ExportTargets(1, nil); err == nil {148+ if bufs, err := r.ExportTargets(1, nil, false); err == nil {
144 bufs[0].Planes[0].File.Close()149 bufs[0].Planes[0].File.Close()
145 t.Fatal("HDR export without a display list succeeded on a non-virtual output")150 t.Fatal("HDR export without a display list succeeded on a non-virtual output")
146 }151 }
147 r.SetVirtualOutput(true)152 r.SetVirtualOutput(true)
148- bufs, err := r.ExportTargets(1, nil)153+ bufs, err := r.ExportTargets(1, nil, false)
149 if err != nil {154 if err != nil {
150 t.Fatalf("virtual HDR export: %v", err)155 t.Fatalf("virtual HDR export: %v", err)
151 }156 }
@@ -167,3 +172,90 @@ func checkHDRPixel(t *testing.T, r *Renderer, rgb [3]float64) {
167 }172 }
168 }173 }
169 }174 }
175+
176+// Targets may use DCC modifiers: one to four memory planes are kept and
177+// their counts recorded; singlePlane drops the multi-plane ones.
178+func TestFilterModifiersKeepsMultiPlane(t *testing.T) {
179+ need := vk.FormatFeatureFlags(vk.FormatFeatureColorAttachmentBit)
180+ mods := []vk.DrmFormatModifierPropertiesEXT{
181+ {DrmFormatModifier: 0, DrmFormatModifierPlaneCount: 1, DrmFormatModifierTilingFeatures: need},
182+ {DrmFormatModifier: 10, DrmFormatModifierPlaneCount: 3, DrmFormatModifierTilingFeatures: need},
183+ {DrmFormatModifier: 11, DrmFormatModifierPlaneCount: 4, DrmFormatModifierTilingFeatures: need},
184+ {DrmFormatModifier: 12, DrmFormatModifierPlaneCount: 5, DrmFormatModifierTilingFeatures: need},
185+ {DrmFormatModifier: 13, DrmFormatModifierPlaneCount: 0, DrmFormatModifierTilingFeatures: need},
186+ {DrmFormatModifier: 14, DrmFormatModifierPlaneCount: 2},
187+ {DrmFormatModifier: 15, DrmFormatModifierPlaneCount: 2, DrmFormatModifierTilingFeatures: need},
188+ }
189+ got, planes := filterModifiers(mods, need, func(m uint64) bool { return m != 15 })
190+ if !slices.Equal(got, []uint64{0, 10, 11}) {
191+ t.Fatalf("kept %v", got)
192+ }
193+ if planes[0] != 1 || planes[10] != 3 || planes[11] != 4 || len(planes) != 3 {
194+ t.Fatalf("planes %v", planes)
195+ }
196+ r := &Renderer{renderMods: got, modPlanes: planes}
197+ if all := r.exportModifiers(nil, false); !slices.Equal(all, []uint64{0, 10, 11}) {
198+ t.Fatalf("multi-plane export %v", all)
199+ }
200+ if one := r.exportModifiers(nil, true); !slices.Equal(one, []uint64{0}) {
201+ t.Fatalf("single-plane export %v", one)
202+ }
203+ if one := r.exportModifiers([]uint64{10, 11}, true); len(one) != 0 {
204+ t.Fatalf("single-plane with only DCC offered: %v", one)
205+ }
206+ if two := r.exportModifiers([]uint64{10, 0}, false); !slices.Equal(two, []uint64{0, 10}) {
207+ t.Fatalf("display intersection %v", two)
208+ }
209+}
210+
211+// Every exported target carries one file per memory plane of its modifier
212+// (DCC modifiers have two to four), each with a layout, and each file is
213+// an independent descriptor of the shared memory.
214+func TestExportTargetsPlanes(t *testing.T) {
215+ r, err := New(64, 32)
216+ if err != nil {
217+ t.Skipf("Vulkan unavailable: %v", err)
218+ }
219+ defer r.Close()
220+ bufs, err := r.ExportTargets(2, nil, false)
221+ if err != nil {
222+ t.Skipf("no exportable targets: %v", err)
223+ }
224+ defer func() {
225+ for _, b := range bufs {
226+ for _, p := range b.Planes {
227+ p.File.Close()
228+ }
229+ }
230+ }()
231+ for _, b := range bufs {
232+ want := max(r.modPlanes[b.Modifier], 1)
233+ if uint32(len(b.Planes)) != want {
234+ t.Fatalf("modifier %#x: %d plane files, want %d", b.Modifier, len(b.Planes), want)
235+ }
236+ seen := map[int]bool{}
237+ for i, p := range b.Planes {
238+ if p.File == nil || p.Stride == 0 {
239+ t.Fatalf("modifier %#x plane %d: %+v", b.Modifier, i, p)
240+ }
241+ if seen[int(p.File.Fd())] {
242+ t.Fatalf("modifier %#x: plane %d shares a file", b.Modifier, i)
243+ }
244+ seen[int(p.File.Fd())] = true
245+ }
246+ t.Logf("modifier %#x planes %d", b.Modifier, len(b.Planes))
247+ }
248+ // Single-plane export never returns a multi-plane modifier.
249+ one, err := r.ExportTargets(1, nil, true)
250+ if err != nil {
251+ t.Skipf("no single-plane targets: %v", err)
252+ }
253+ defer func() {
254+ for _, p := range one[0].Planes {
255+ p.File.Close()
256+ }
257+ }()
258+ if len(one[0].Planes) != 1 {
259+ t.Fatalf("single-plane export has %d planes", len(one[0].Planes))
260+ }
261+}
M internal/app/app.go +1 -0
@@ -294,6 +294,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent)
294294 if err != nil {
295295 return nil, err
296296 }
297+ r.SetLogger(renderLog)
297298 renderLog.Info().Str("queue_priority", r.QueuePriority()).Msg("vulkan queue")
298299 return r, nil
299300 }
@@ -294,6 +294,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent)
294 if err != nil {294 if err != nil {
295 return nil, err295 return nil, err
296 }296 }
297+ r.SetLogger(renderLog)
297 renderLog.Info().Str("queue_priority", r.QueuePriority()).Msg("vulkan queue")298 renderLog.Info().Str("queue_priority", r.QueuePriority()).Msg("vulkan queue")
298 return r, nil299 return r, nil
299 }300 }
M internal/app/headless_test.go +2 -2
@@ -25,7 +25,7 @@ func TestVirtualRendererExportsHDRTargets(t *testing.T) {
2525 }
2626 defer r.Close()
2727 // A device that cannot export at all says nothing about the wiring.
28- sdr, err := r.ExportTargets(1, nil)
28+ sdr, err := r.ExportTargets(1, nil, false)
2929 if err != nil {
3030 t.Skipf("no exportable target: %v", err)
3131 }
@@ -35,7 +35,7 @@ func TestVirtualRendererExportsHDRTargets(t *testing.T) {
3535 }
3636 }
3737 r.SetHDR(203)
38- bufs, err := r.ExportTargets(1, nil)
38+ bufs, err := r.ExportTargets(1, nil, false)
3939 if err != nil {
4040 t.Fatalf("virtual HDR export: %v", err)
4141 }
@@ -25,7 +25,7 @@ func TestVirtualRendererExportsHDRTargets(t *testing.T) {
25 }25 }
26 defer r.Close()26 defer r.Close()
27 // A device that cannot export at all says nothing about the wiring.27 // A device that cannot export at all says nothing about the wiring.
28- sdr, err := r.ExportTargets(1, nil)28+ sdr, err := r.ExportTargets(1, nil, false)
29 if err != nil {29 if err != nil {
30 t.Skipf("no exportable target: %v", err)30 t.Skipf("no exportable target: %v", err)
31 }31 }
@@ -35,7 +35,7 @@ func TestVirtualRendererExportsHDRTargets(t *testing.T) {
35 }35 }
36 }36 }
37 r.SetHDR(203)37 r.SetHDR(203)
38- bufs, err := r.ExportTargets(1, nil)38+ bufs, err := r.ExportTargets(1, nil, false)
39 if err != nil {39 if err != nil {
40 t.Fatalf("virtual HDR export: %v", err)40 t.Fatalf("virtual HDR export: %v", err)
41 }41 }
M internal/mocks/ports/devices_mock.go +18 -12
@@ -385,8 +385,8 @@ func (_c *MockRenderer_EndCapture_Call) RunAndReturn(run func(captureFrame ports
385385 }
386386
387387 // ExportTargets provides a mock function for the type MockRenderer
388-func (_mock *MockRenderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMABuf, error) {
389- ret := _mock.Called(n, modifiers)
388+func (_mock *MockRenderer) ExportTargets(n int, modifiers []uint64, singlePlane bool) ([]ports.DMABuf, error) {
389+ ret := _mock.Called(n, modifiers, singlePlane)
390390
391391 if len(ret) == 0 {
392392 panic("no return value specified for ExportTargets")
@@ -394,18 +394,18 @@ func (_mock *MockRenderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMA
394394
395395 var r0 []ports.DMABuf
396396 var r1 error
397- if returnFunc, ok := ret.Get(0).(func(int, []uint64) ([]ports.DMABuf, error)); ok {
398- return returnFunc(n, modifiers)
397+ if returnFunc, ok := ret.Get(0).(func(int, []uint64, bool) ([]ports.DMABuf, error)); ok {
398+ return returnFunc(n, modifiers, singlePlane)
399399 }
400- if returnFunc, ok := ret.Get(0).(func(int, []uint64) []ports.DMABuf); ok {
401- r0 = returnFunc(n, modifiers)
400+ if returnFunc, ok := ret.Get(0).(func(int, []uint64, bool) []ports.DMABuf); ok {
401+ r0 = returnFunc(n, modifiers, singlePlane)
402402 } else {
403403 if ret.Get(0) != nil {
404404 r0 = ret.Get(0).([]ports.DMABuf)
405405 }
406406 }
407- if returnFunc, ok := ret.Get(1).(func(int, []uint64) error); ok {
408- r1 = returnFunc(n, modifiers)
407+ if returnFunc, ok := ret.Get(1).(func(int, []uint64, bool) error); ok {
408+ r1 = returnFunc(n, modifiers, singlePlane)
409409 } else {
410410 r1 = ret.Error(1)
411411 }
@@ -420,11 +420,12 @@ type MockRenderer_ExportTargets_Call struct {
420420 // ExportTargets is a helper method to define mock.On call
421421 // - n int
422422 // - modifiers []uint64
423-func (_e *MockRenderer_Expecter) ExportTargets(n any, modifiers any) *MockRenderer_ExportTargets_Call {
424- return &MockRenderer_ExportTargets_Call{Call: _e.mock.On("ExportTargets", n, modifiers)}
423+// - singlePlane bool
424+func (_e *MockRenderer_Expecter) ExportTargets(n any, modifiers any, singlePlane any) *MockRenderer_ExportTargets_Call {
425+ return &MockRenderer_ExportTargets_Call{Call: _e.mock.On("ExportTargets", n, modifiers, singlePlane)}
425426 }
426427
427-func (_c *MockRenderer_ExportTargets_Call) Run(run func(n int, modifiers []uint64)) *MockRenderer_ExportTargets_Call {
428+func (_c *MockRenderer_ExportTargets_Call) Run(run func(n int, modifiers []uint64, singlePlane bool)) *MockRenderer_ExportTargets_Call {
428429 _c.Call.Run(func(args mock.Arguments) {
429430 var arg0 int
430431 if args[0] != nil {
@@ -434,9 +435,14 @@ func (_c *MockRenderer_ExportTargets_Call) Run(run func(n int, modifiers []uint6
434435 if args[1] != nil {
435436 arg1 = args[1].([]uint64)
436437 }
438+ var arg2 bool
439+ if args[2] != nil {
440+ arg2 = args[2].(bool)
441+ }
437442 run(
438443 arg0,
439444 arg1,
445+ arg2,
440446 )
441447 })
442448 return _c
@@ -447,7 +453,7 @@ func (_c *MockRenderer_ExportTargets_Call) Return(dMABufs []ports.DMABuf, err er
447453 return _c
448454 }
449455
450-func (_c *MockRenderer_ExportTargets_Call) RunAndReturn(run func(n int, modifiers []uint64) ([]ports.DMABuf, error)) *MockRenderer_ExportTargets_Call {
456+func (_c *MockRenderer_ExportTargets_Call) RunAndReturn(run func(n int, modifiers []uint64, singlePlane bool) ([]ports.DMABuf, error)) *MockRenderer_ExportTargets_Call {
451457 _c.Call.Return(run)
452458 return _c
453459 }
@@ -385,8 +385,8 @@ func (_c *MockRenderer_EndCapture_Call) RunAndReturn(run func(captureFrame ports
385 }385 }
386 386
387 // ExportTargets provides a mock function for the type MockRenderer387 // ExportTargets provides a mock function for the type MockRenderer
388-func (_mock *MockRenderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMABuf, error) {388+func (_mock *MockRenderer) ExportTargets(n int, modifiers []uint64, singlePlane bool) ([]ports.DMABuf, error) {
389- ret := _mock.Called(n, modifiers)389+ ret := _mock.Called(n, modifiers, singlePlane)
390 390
391 if len(ret) == 0 {391 if len(ret) == 0 {
392 panic("no return value specified for ExportTargets")392 panic("no return value specified for ExportTargets")
@@ -394,18 +394,18 @@ func (_mock *MockRenderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMA
394 394
395 var r0 []ports.DMABuf395 var r0 []ports.DMABuf
396 var r1 error396 var r1 error
397- if returnFunc, ok := ret.Get(0).(func(int, []uint64) ([]ports.DMABuf, error)); ok {397+ if returnFunc, ok := ret.Get(0).(func(int, []uint64, bool) ([]ports.DMABuf, error)); ok {
398- return returnFunc(n, modifiers)398+ return returnFunc(n, modifiers, singlePlane)
399 }399 }
400- if returnFunc, ok := ret.Get(0).(func(int, []uint64) []ports.DMABuf); ok {400+ if returnFunc, ok := ret.Get(0).(func(int, []uint64, bool) []ports.DMABuf); ok {
401- r0 = returnFunc(n, modifiers)401+ r0 = returnFunc(n, modifiers, singlePlane)
402 } else {402 } else {
403 if ret.Get(0) != nil {403 if ret.Get(0) != nil {
404 r0 = ret.Get(0).([]ports.DMABuf)404 r0 = ret.Get(0).([]ports.DMABuf)
405 }405 }
406 }406 }
407- if returnFunc, ok := ret.Get(1).(func(int, []uint64) error); ok {407+ if returnFunc, ok := ret.Get(1).(func(int, []uint64, bool) error); ok {
408- r1 = returnFunc(n, modifiers)408+ r1 = returnFunc(n, modifiers, singlePlane)
409 } else {409 } else {
410 r1 = ret.Error(1)410 r1 = ret.Error(1)
411 }411 }
@@ -420,11 +420,12 @@ type MockRenderer_ExportTargets_Call struct {
420 // ExportTargets is a helper method to define mock.On call420 // ExportTargets is a helper method to define mock.On call
421 // - n int421 // - n int
422 // - modifiers []uint64422 // - modifiers []uint64
423-func (_e *MockRenderer_Expecter) ExportTargets(n any, modifiers any) *MockRenderer_ExportTargets_Call {423+// - singlePlane bool
424- return &MockRenderer_ExportTargets_Call{Call: _e.mock.On("ExportTargets", n, modifiers)}424+func (_e *MockRenderer_Expecter) ExportTargets(n any, modifiers any, singlePlane any) *MockRenderer_ExportTargets_Call {
425+ return &MockRenderer_ExportTargets_Call{Call: _e.mock.On("ExportTargets", n, modifiers, singlePlane)}
425 }426 }
426 427
427-func (_c *MockRenderer_ExportTargets_Call) Run(run func(n int, modifiers []uint64)) *MockRenderer_ExportTargets_Call {428+func (_c *MockRenderer_ExportTargets_Call) Run(run func(n int, modifiers []uint64, singlePlane bool)) *MockRenderer_ExportTargets_Call {
428 _c.Call.Run(func(args mock.Arguments) {429 _c.Call.Run(func(args mock.Arguments) {
429 var arg0 int430 var arg0 int
430 if args[0] != nil {431 if args[0] != nil {
@@ -434,9 +435,14 @@ func (_c *MockRenderer_ExportTargets_Call) Run(run func(n int, modifiers []uint6
434 if args[1] != nil {435 if args[1] != nil {
435 arg1 = args[1].([]uint64)436 arg1 = args[1].([]uint64)
436 }437 }
438+ var arg2 bool
439+ if args[2] != nil {
440+ arg2 = args[2].(bool)
441+ }
437 run(442 run(
438 arg0,443 arg0,
439 arg1,444 arg1,
445+ arg2,
440 )446 )
441 })447 })
442 return _c448 return _c
@@ -447,7 +453,7 @@ func (_c *MockRenderer_ExportTargets_Call) Return(dMABufs []ports.DMABuf, err er
447 return _c453 return _c
448 }454 }
449 455
450-func (_c *MockRenderer_ExportTargets_Call) RunAndReturn(run func(n int, modifiers []uint64) ([]ports.DMABuf, error)) *MockRenderer_ExportTargets_Call {456+func (_c *MockRenderer_ExportTargets_Call) RunAndReturn(run func(n int, modifiers []uint64, singlePlane bool) ([]ports.DMABuf, error)) *MockRenderer_ExportTargets_Call {
451 _c.Call.Return(run)457 _c.Call.Return(run)
452 return _c458 return _c
453 }459 }
M internal/ports/devices.go +4 -2
@@ -47,9 +47,11 @@ type Renderer interface {
4747 // ExportTargets allocates n images of the renderer's size that the
4848 // display can scan out, with one of the given XRGB8888 (SDR) or
4949 // XRGB2101010 (HDR) modifiers (none: any the device exports),
50- // and returns them as dmabufs.
50+ // and returns them as dmabufs; a modifier may have several memory
51+ // planes (DCC metadata), each plane owning its file. singlePlane
52+ // restricts the choice to one-plane modifiers.
5153 // n = 0 drops the targets.
52- ExportTargets(n int, modifiers []uint64) ([]DMABuf, error)
54+ ExportTargets(n int, modifiers []uint64, singlePlane bool) ([]DMABuf, error)
5355 // UseTarget selects the exported image the next Render draws into.
5456 UseTarget(i int)
5557 // CursorBuffers allocates two linear ARGB8888 images of size×size the
@@ -47,9 +47,11 @@ type Renderer interface {
47 // ExportTargets allocates n images of the renderer's size that the47 // ExportTargets allocates n images of the renderer's size that the
48 // display can scan out, with one of the given XRGB8888 (SDR) or48 // display can scan out, with one of the given XRGB8888 (SDR) or
49 // XRGB2101010 (HDR) modifiers (none: any the device exports),49 // XRGB2101010 (HDR) modifiers (none: any the device exports),
50- // and returns them as dmabufs.50+ // and returns them as dmabufs; a modifier may have several memory
51+ // planes (DCC metadata), each plane owning its file. singlePlane
52+ // restricts the choice to one-plane modifiers.
51 // n = 0 drops the targets.53 // n = 0 drops the targets.
52- ExportTargets(n int, modifiers []uint64) ([]DMABuf, error)54+ ExportTargets(n int, modifiers []uint64, singlePlane bool) ([]DMABuf, error)
53 // UseTarget selects the exported image the next Render draws into.55 // UseTarget selects the exported image the next Render draws into.
54 UseTarget(i int)56 UseTarget(i int)
55 // CursorBuffers allocates two linear ARGB8888 images of size×size the57 // CursorBuffers allocates two linear ARGB8888 images of size×size the

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 23

Tags 10

↑↓ moveenter openesc close