fix(drm): skip the single-plane retry when driver images had one plane
Record the plane count of the last exported images; a refused one-plane driver image goes straight to linear. TestExportTargetsPlanes now reads each multi-plane target back, checks plane offsets and skips without a multi-plane modifier. Clamp the plane count once in exportTarget and log the exported target at Debug.
- commit
- cca9f0846ca9e9374be9097ed7d4f4fb66fdfce0
- author
- brice · · 2026-10-04 20:13 UTC
- parent
- 5491ddd
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +92 -24
M internal/adapters/drm/commit_test.go +39 -2
| @@ -120,14 +120,23 @@ func TestShowImagesTestsBeforeModeset(t *testing.T) { | |||
| 120 | 120 | o.cursor = nil | |
| 121 | 121 | r := portsmocks.NewMockRenderer(t) | |
| 122 | 122 | buf := func() ports.DMABuf { | |
| 123 | + | var b ports.DMABuf | |
| 124 | + | for range 2 { | |
| 125 | + | f, w, _ := os.Pipe() | |
| 126 | + | w.Close() | |
| 127 | + | b.Planes = append(b.Planes, ports.DMABufPlane{File: f}) | |
| 128 | + | } | |
| 129 | + | return b | |
| 130 | + | } | |
| 131 | + | linear := func() ports.DMABuf { | |
| 123 | 132 | f, w, _ := os.Pipe() | |
| 124 | 133 | w.Close() | |
| 125 | 134 | return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}} | |
| 126 | 135 | } | |
| 127 | 136 | r.EXPECT().SetHDR(float64(0)).Return().Maybe() | |
| 128 | 137 | r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | |
| 129 | - | r.EXPECT().ExportTargets(2, []uint64(nil), true).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | |
| 130 | - | r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | |
| 138 | + | r.EXPECT().ExportTargets(2, []uint64(nil), true).Return([]ports.DMABuf{linear(), linear()}, nil).Once() | |
| 139 | + | r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{linear(), linear()}, nil).Once() | |
| 131 | 140 | r.EXPECT().UseTarget(mock.Anything).Return() | |
| 132 | 141 | r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil) | |
| 133 | 142 | k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil) | |
| @@ -194,6 +203,34 @@ func TestShowImagesRetriesSinglePlaneBeforeLinear(t *testing.T) { | |||
| 194 | 203 | } | |
| 195 | 204 | } | |
| 196 | 205 | ||
| 206 | + | // Refused driver images that already had one plane are not retried as | |
| 207 | + | // single-plane images: the next export is linear. | |
| 208 | + | func TestShowImagesSkipsSinglePlaneWhenDriverImagesHadOnePlane(t *testing.T) { | |
| 209 | + | o, k, commits := testOutput(t, unix.EINVAL) | |
| 210 | + | o.cursor = nil | |
| 211 | + | buf := func() ports.DMABuf { | |
| 212 | + | f, w, _ := os.Pipe() | |
| 213 | + | w.Close() | |
| 214 | + | return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}} | |
| 215 | + | } | |
| 216 | + | r := portsmocks.NewMockRenderer(t) | |
| 217 | + | r.EXPECT().SetHDR(float64(0)).Return().Maybe() | |
| 218 | + | r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | |
| 219 | + | r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | |
| 220 | + | r.EXPECT().UseTarget(mock.Anything).Return() | |
| 221 | + | r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil) | |
| 222 | + | k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil) | |
| 223 | + | k.EXPECT().rmFB(mock.Anything).Return(nil) | |
| 224 | + | k.EXPECT().createBlob(mock.Anything).Return(99, nil) | |
| 225 | + | k.EXPECT().destroyBlob(mock.Anything).Return(nil) | |
| 226 | + | if err := o.showImages(r, imagesDriver, nil); err != nil { | |
| 227 | + | t.Fatal(err) | |
| 228 | + | } | |
| 229 | + | if o.kind != imagesLinear || len(*commits) != 3 { | |
| 230 | + | t.Fatalf("kind %d, %d commits", o.kind, len(*commits)) | |
| 231 | + | } | |
| 232 | + | } | |
| 233 | + | ||
| 197 | 234 | // Enabling an output whose images were not made yet (started switched | |
| 198 | 235 | // away) tests them and modesets once. | |
| 199 | 236 | func TestEnableAfterStartAwayModesetsOnce(t *testing.T) { | |
| @@ -120,14 +120,23 @@ func TestShowImagesTestsBeforeModeset(t *testing.T) { | |||||
| 120 | o.cursor = nil | 120 | o.cursor = nil | ||
| 121 | r := portsmocks.NewMockRenderer(t) | 121 | r := portsmocks.NewMockRenderer(t) | ||
| 122 | buf := func() ports.DMABuf { | 122 | buf := func() ports.DMABuf { | ||
| 123 | + | var b ports.DMABuf | |||
| 124 | + | for range 2 { | |||
| 125 | + | f, w, _ := os.Pipe() | |||
| 126 | + | w.Close() | |||
| 127 | + | b.Planes = append(b.Planes, ports.DMABufPlane{File: f}) | |||
| 128 | + | } | |||
| 129 | + | return b | |||
| 130 | + | } | |||
| 131 | + | linear := func() ports.DMABuf { | |||
| 123 | f, w, _ := os.Pipe() | 132 | f, w, _ := os.Pipe() | ||
| 124 | w.Close() | 133 | w.Close() | ||
| 125 | return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}} | 134 | return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}} | ||
| 126 | } | 135 | } | ||
| 127 | r.EXPECT().SetHDR(float64(0)).Return().Maybe() | 136 | r.EXPECT().SetHDR(float64(0)).Return().Maybe() | ||
| 128 | r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | 137 | r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | ||
| 129 | - | r.EXPECT().ExportTargets(2, []uint64(nil), true).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | 138 | + | r.EXPECT().ExportTargets(2, []uint64(nil), true).Return([]ports.DMABuf{linear(), linear()}, nil).Once() |
| 130 | - | r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | 139 | + | r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{linear(), linear()}, nil).Once() |
| 131 | r.EXPECT().UseTarget(mock.Anything).Return() | 140 | r.EXPECT().UseTarget(mock.Anything).Return() | ||
| 132 | r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil) | 141 | r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil) | ||
| 133 | k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil) | 142 | k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil) | ||
| @@ -194,6 +203,34 @@ func TestShowImagesRetriesSinglePlaneBeforeLinear(t *testing.T) { | |||||
| 194 | } | 203 | } | ||
| 195 | } | 204 | } | ||
| 196 | 205 | ||||
| 206 | + | // Refused driver images that already had one plane are not retried as | |||
| 207 | + | // single-plane images: the next export is linear. | |||
| 208 | + | func TestShowImagesSkipsSinglePlaneWhenDriverImagesHadOnePlane(t *testing.T) { | |||
| 209 | + | o, k, commits := testOutput(t, unix.EINVAL) | |||
| 210 | + | o.cursor = nil | |||
| 211 | + | buf := func() ports.DMABuf { | |||
| 212 | + | f, w, _ := os.Pipe() | |||
| 213 | + | w.Close() | |||
| 214 | + | return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}} | |||
| 215 | + | } | |||
| 216 | + | r := portsmocks.NewMockRenderer(t) | |||
| 217 | + | r.EXPECT().SetHDR(float64(0)).Return().Maybe() | |||
| 218 | + | r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | |||
| 219 | + | r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | |||
| 220 | + | r.EXPECT().UseTarget(mock.Anything).Return() | |||
| 221 | + | r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil) | |||
| 222 | + | k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil) | |||
| 223 | + | k.EXPECT().rmFB(mock.Anything).Return(nil) | |||
| 224 | + | k.EXPECT().createBlob(mock.Anything).Return(99, nil) | |||
| 225 | + | k.EXPECT().destroyBlob(mock.Anything).Return(nil) | |||
| 226 | + | if err := o.showImages(r, imagesDriver, nil); err != nil { | |||
| 227 | + | t.Fatal(err) | |||
| 228 | + | } | |||
| 229 | + | if o.kind != imagesLinear || len(*commits) != 3 { | |||
| 230 | + | t.Fatalf("kind %d, %d commits", o.kind, len(*commits)) | |||
| 231 | + | } | |||
| 232 | + | } | |||
| 233 | + | ||||
| 197 | // Enabling an output whose images were not made yet (started switched | 234 | // Enabling an output whose images were not made yet (started switched | ||
| 198 | // away) tests them and modesets once. | 235 | // away) tests them and modesets once. | ||
| 199 | func TestEnableAfterStartAwayModesetsOnce(t *testing.T) { | 236 | func TestEnableAfterStartAwayModesetsOnce(t *testing.T) { | ||
M internal/adapters/drm/images.go +7 -0
| @@ -104,6 +104,11 @@ func (o *Output) showImageKind(r ports.Renderer, kind imageKind, cause error) er | |||
| 104 | 104 | o.log.Warn().Err(err).Str("connector", o.conn.name).Int("kind", int(got)).Msg("modeset refused the output images") | |
| 105 | 105 | o.freeImages() | |
| 106 | 106 | kind, cause = got+1, err | |
| 107 | + | if kind == imagesSinglePlane && o.planes == 1 { | |
| 108 | + | // The refused images already had one plane: single-plane | |
| 109 | + | // images would be the same ones. | |
| 110 | + | kind = imagesLinear | |
| 111 | + | } | |
| 107 | 112 | } | |
| 108 | 113 | } | |
| 109 | 114 | ||
| @@ -154,6 +159,7 @@ func (o *Output) exportImages(r ports.Renderer, mods []uint64, singlePlane bool) | |||
| 154 | 159 | if err != nil { | |
| 155 | 160 | return err | |
| 156 | 161 | } | |
| 162 | + | o.planes = 0 | |
| 157 | 163 | for i := range bufs { | |
| 158 | 164 | if len(bufs[i].Planes) == 0 || len(bufs[i].Planes) > maxFBPlanes { | |
| 159 | 165 | err = errors.Join(err, fmt.Errorf("output image has %d planes", len(bufs[i].Planes))) | |
| @@ -194,6 +200,7 @@ func (o *Output) exportImages(r ports.Renderer, mods []uint64, singlePlane bool) | |||
| 194 | 200 | _, _ = r.ExportTargets(0, nil, false) | |
| 195 | 201 | return err | |
| 196 | 202 | } | |
| 203 | + | o.planes = len(bufs[0].Planes) | |
| 197 | 204 | o.log.Info().Str("connector", o.conn.name).Uint64("modifier", bufs[0].Modifier).Int("planes", len(bufs[0].Planes)).Msg("zero-copy output") | |
| 198 | 205 | return nil | |
| 199 | 206 | } | |
| @@ -104,6 +104,11 @@ func (o *Output) showImageKind(r ports.Renderer, kind imageKind, cause error) er | |||||
| 104 | o.log.Warn().Err(err).Str("connector", o.conn.name).Int("kind", int(got)).Msg("modeset refused the output images") | 104 | o.log.Warn().Err(err).Str("connector", o.conn.name).Int("kind", int(got)).Msg("modeset refused the output images") | ||
| 105 | o.freeImages() | 105 | o.freeImages() | ||
| 106 | kind, cause = got+1, err | 106 | kind, cause = got+1, err | ||
| 107 | + | if kind == imagesSinglePlane && o.planes == 1 { | |||
| 108 | + | // The refused images already had one plane: single-plane | |||
| 109 | + | // images would be the same ones. | |||
| 110 | + | kind = imagesLinear | |||
| 111 | + | } | |||
| 107 | } | 112 | } | ||
| 108 | } | 113 | } | ||
| 109 | 114 | ||||
| @@ -154,6 +159,7 @@ func (o *Output) exportImages(r ports.Renderer, mods []uint64, singlePlane bool) | |||||
| 154 | if err != nil { | 159 | if err != nil { | ||
| 155 | return err | 160 | return err | ||
| 156 | } | 161 | } | ||
| 162 | + | o.planes = 0 | |||
| 157 | for i := range bufs { | 163 | for i := range bufs { | ||
| 158 | if len(bufs[i].Planes) == 0 || len(bufs[i].Planes) > maxFBPlanes { | 164 | if len(bufs[i].Planes) == 0 || len(bufs[i].Planes) > maxFBPlanes { | ||
| 159 | err = errors.Join(err, fmt.Errorf("output image has %d planes", len(bufs[i].Planes))) | 165 | err = errors.Join(err, fmt.Errorf("output image has %d planes", len(bufs[i].Planes))) | ||
| @@ -194,6 +200,7 @@ func (o *Output) exportImages(r ports.Renderer, mods []uint64, singlePlane bool) | |||||
| 194 | _, _ = r.ExportTargets(0, nil, false) | 200 | _, _ = r.ExportTargets(0, nil, false) | ||
| 195 | return err | 201 | return err | ||
| 196 | } | 202 | } | ||
| 203 | + | o.planes = len(bufs[0].Planes) | |||
| 197 | o.log.Info().Str("connector", o.conn.name).Uint64("modifier", bufs[0].Modifier).Int("planes", len(bufs[0].Planes)).Msg("zero-copy output") | 204 | o.log.Info().Str("connector", o.conn.name).Uint64("modifier", bufs[0].Modifier).Int("planes", len(bufs[0].Planes)).Msg("zero-copy output") | ||
| 198 | return nil | 205 | return nil | ||
| 199 | } | 206 | } | ||
M internal/adapters/drm/output.go +2 -0
| @@ -176,6 +176,8 @@ type Output struct { | |||
| 176 | 176 | protectNotBefore time.Time | |
| 177 | 177 | // kind is how the images were made. | |
| 178 | 178 | kind imageKind | |
| 179 | + | // planes is the memory plane count of the last exported images. | |
| 180 | + | planes int | |
| 179 | 181 | // formats receives the direct scanout formats after each modeset; | |
| 180 | 182 | // sampled and device are what they are built from (Want). | |
| 181 | 183 | formats chan<- ports.OutputFormats | |
| @@ -176,6 +176,8 @@ type Output struct { | |||||
| 176 | protectNotBefore time.Time | 176 | protectNotBefore time.Time | ||
| 177 | // kind is how the images were made. | 177 | // kind is how the images were made. | ||
| 178 | kind imageKind | 178 | kind imageKind | ||
| 179 | + | // planes is the memory plane count of the last exported images. | |||
| 180 | + | planes int | |||
| 179 | // formats receives the direct scanout formats after each modeset; | 181 | // formats receives the direct scanout formats after each modeset; | ||
| 180 | // sampled and device are what they are built from (Want). | 182 | // sampled and device are what they are built from (Want). | ||
| 181 | formats chan<- ports.OutputFormats | 183 | formats chan<- ports.OutputFormats | ||
M internal/adapters/vulkan/targets.go +3 -3
| @@ -255,9 +255,9 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |||
| 255 | 255 | if err := checked("vkGetImageDrmFormatModifierPropertiesEXT", d.GetImageDrmFormatModifierPropertiesEXT(r.device, t.image, &props)); err != nil { | |
| 256 | 256 | return nil, ports.DMABuf{}, err | |
| 257 | 257 | } | |
| 258 | - | n := max(r.modPlanes[props.DrmFormatModifier], 1) | |
| 258 | + | n := min(max(r.modPlanes[props.DrmFormatModifier], 1), maxModifierPlanes) | |
| 259 | 259 | var layouts [maxModifierPlanes]vk.SubresourceLayout | |
| 260 | - | for i := range min(n, maxModifierPlanes) { | |
| 260 | + | for i := range n { | |
| 261 | 261 | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT << i} | |
| 262 | 262 | d.GetImageSubresourceLayout(r.device, t.image, &sub, &layouts[i]) | |
| 263 | 263 | } | |
| @@ -291,7 +291,7 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |||
| 291 | 291 | } | |
| 292 | 292 | planes = append(planes, ports.DMABufPlane{File: os.NewFile(uintptr(dup), "neferwl-target"), Offset: uint32(layouts[i].Offset), Stride: uint32(layouts[i].RowPitch)}) | |
| 293 | 293 | } | |
| 294 | - | r.log.Info().Uint64("modifier", props.DrmFormatModifier).Uint32("planes", n).Msg("render target exported") | |
| 294 | + | r.log.Debug().Uint64("modifier", props.DrmFormatModifier).Uint32("planes", n).Msg("render target exported") | |
| 295 | 295 | buf := ports.DMABuf{Width: r.width, Height: r.height, Format: fourcc, Modifier: props.DrmFormatModifier, Planes: planes} | |
| 296 | 296 | ok = true | |
| 297 | 297 | return t, buf, nil | |
| @@ -255,9 +255,9 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |||||
| 255 | 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 { | ||
| 256 | return nil, ports.DMABuf{}, err | 256 | return nil, ports.DMABuf{}, err | ||
| 257 | } | 257 | } | ||
| 258 | - | n := max(r.modPlanes[props.DrmFormatModifier], 1) | 258 | + | n := min(max(r.modPlanes[props.DrmFormatModifier], 1), maxModifierPlanes) |
| 259 | var layouts [maxModifierPlanes]vk.SubresourceLayout | 259 | var layouts [maxModifierPlanes]vk.SubresourceLayout | ||
| 260 | - | for i := range min(n, maxModifierPlanes) { | 260 | + | for i := range n { |
| 261 | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT << i} | 261 | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT << i} | ||
| 262 | d.GetImageSubresourceLayout(r.device, t.image, &sub, &layouts[i]) | 262 | d.GetImageSubresourceLayout(r.device, t.image, &sub, &layouts[i]) | ||
| 263 | } | 263 | } | ||
| @@ -291,7 +291,7 @@ func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |||||
| 291 | } | 291 | } | ||
| 292 | planes = append(planes, ports.DMABufPlane{File: os.NewFile(uintptr(dup), "neferwl-target"), Offset: uint32(layouts[i].Offset), Stride: uint32(layouts[i].RowPitch)}) | 292 | planes = append(planes, ports.DMABufPlane{File: os.NewFile(uintptr(dup), "neferwl-target"), Offset: uint32(layouts[i].Offset), Stride: uint32(layouts[i].RowPitch)}) | ||
| 293 | } | 293 | } | ||
| 294 | - | r.log.Info().Uint64("modifier", props.DrmFormatModifier).Uint32("planes", n).Msg("render target exported") | 294 | + | r.log.Debug().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} | 295 | buf := ports.DMABuf{Width: r.width, Height: r.height, Format: fourcc, Modifier: props.DrmFormatModifier, Planes: planes} | ||
| 296 | ok = true | 296 | ok = true | ||
| 297 | return t, buf, nil | 297 | return t, buf, nil | ||
M internal/adapters/vulkan/targets_test.go +41 -19
| @@ -209,25 +209,44 @@ func TestFilterModifiersKeepsMultiPlane(t *testing.T) { | |||
| 209 | 209 | } | |
| 210 | 210 | ||
| 211 | 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. | |
| 212 | + | // (DCC modifiers have two to four), each with a layout, and drawing into | |
| 213 | + | // each target works: the frames read back through the producing renderer. | |
| 214 | + | // A multi-plane image cannot be read back through a viewer import, which | |
| 215 | + | // only imports one-plane buffers. | |
| 214 | 216 | func TestExportTargetsPlanes(t *testing.T) { | |
| 215 | 217 | r, err := New(64, 32) | |
| 216 | 218 | if err != nil { | |
| 217 | 219 | t.Skipf("Vulkan unavailable: %v", err) | |
| 218 | 220 | } | |
| 219 | 221 | 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() { | |
| 222 | + | closeAll := func(bufs []ports.DMABuf) { | |
| 225 | 223 | for _, b := range bufs { | |
| 226 | 224 | for _, p := range b.Planes { | |
| 227 | 225 | p.File.Close() | |
| 228 | 226 | } | |
| 229 | 227 | } | |
| 230 | - | }() | |
| 228 | + | } | |
| 229 | + | // Single-plane export never returns a multi-plane modifier. | |
| 230 | + | one, err := r.ExportTargets(1, nil, true) | |
| 231 | + | if err != nil { | |
| 232 | + | t.Skipf("no single-plane targets: %v", err) | |
| 233 | + | } | |
| 234 | + | if len(one[0].Planes) != 1 || r.modPlanes[one[0].Modifier] > 1 { | |
| 235 | + | t.Fatalf("single-plane export has %d planes (modifier %#x)", len(one[0].Planes), one[0].Modifier) | |
| 236 | + | } | |
| 237 | + | closeAll(one) | |
| 238 | + | multi := false | |
| 239 | + | for _, m := range r.renderMods { | |
| 240 | + | multi = multi || r.modPlanes[m] > 1 | |
| 241 | + | } | |
| 242 | + | if !multi { | |
| 243 | + | t.Skip("device has no multi-plane target modifier") | |
| 244 | + | } | |
| 245 | + | bufs, err := r.ExportTargets(2, nil, false) | |
| 246 | + | if err != nil { | |
| 247 | + | t.Skipf("no exportable targets: %v", err) | |
| 248 | + | } | |
| 249 | + | defer closeAll(bufs) | |
| 231 | 250 | for _, b := range bufs { | |
| 232 | 251 | want := max(r.modPlanes[b.Modifier], 1) | |
| 233 | 252 | if uint32(len(b.Planes)) != want { | |
| @@ -238,6 +257,10 @@ func TestExportTargetsPlanes(t *testing.T) { | |||
| 238 | 257 | if p.File == nil || p.Stride == 0 { | |
| 239 | 258 | t.Fatalf("modifier %#x plane %d: %+v", b.Modifier, i, p) | |
| 240 | 259 | } | |
| 260 | + | // The planes share one memory object: planes 1.. lie after plane 0. | |
| 261 | + | if i > 0 && p.Offset == 0 { | |
| 262 | + | t.Fatalf("modifier %#x plane %d at offset 0", b.Modifier, i) | |
| 263 | + | } | |
| 241 | 264 | if seen[int(p.File.Fd())] { | |
| 242 | 265 | t.Fatalf("modifier %#x: plane %d shares a file", b.Modifier, i) | |
| 243 | 266 | } | |
| @@ -245,17 +268,16 @@ func TestExportTargetsPlanes(t *testing.T) { | |||
| 245 | 268 | } | |
| 246 | 269 | t.Logf("modifier %#x planes %d", b.Modifier, len(b.Planes)) | |
| 247 | 270 | } | |
| 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() | |
| 271 | + | for i, tc := range []struct { | |
| 272 | + | bg string | |
| 273 | + | want color.RGBA | |
| 274 | + | }{{"#ff0000", color.RGBA{255, 0, 0, 255}}, {"#0000ff", color.RGBA{0, 0, 255, 255}}} { | |
| 275 | + | r.UseTarget(i) | |
| 276 | + | if err := render(r, ports.Scene{Background: tc.bg}, nil); err != nil { | |
| 277 | + | t.Fatal(err) | |
| 278 | + | } | |
| 279 | + | if got := readPixels(t, r).At(3, 3); got != tc.want { | |
| 280 | + | t.Fatalf("target %d (modifier %#x): read back %v, want %v", i, bufs[i].Modifier, got, tc.want) | |
| 256 | 281 | } | |
| 257 | - | }() | |
| 258 | - | if len(one[0].Planes) != 1 { | |
| 259 | - | t.Fatalf("single-plane export has %d planes", len(one[0].Planes)) | |
| 260 | 282 | } | |
| 261 | 283 | } | |
| @@ -209,25 +209,44 @@ func TestFilterModifiersKeepsMultiPlane(t *testing.T) { | |||||
| 209 | } | 209 | } | ||
| 210 | 210 | ||||
| 211 | // Every exported target carries one file per memory plane of its modifier | 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 | 212 | + | // (DCC modifiers have two to four), each with a layout, and drawing into |
| 213 | - | // an independent descriptor of the shared memory. | 213 | + | // each target works: the frames read back through the producing renderer. |
| 214 | + | // A multi-plane image cannot be read back through a viewer import, which | |||
| 215 | + | // only imports one-plane buffers. | |||
| 214 | func TestExportTargetsPlanes(t *testing.T) { | 216 | func TestExportTargetsPlanes(t *testing.T) { | ||
| 215 | r, err := New(64, 32) | 217 | r, err := New(64, 32) | ||
| 216 | if err != nil { | 218 | if err != nil { | ||
| 217 | t.Skipf("Vulkan unavailable: %v", err) | 219 | t.Skipf("Vulkan unavailable: %v", err) | ||
| 218 | } | 220 | } | ||
| 219 | defer r.Close() | 221 | defer r.Close() | ||
| 220 | - | bufs, err := r.ExportTargets(2, nil, false) | 222 | + | closeAll := func(bufs []ports.DMABuf) { |
| 221 | - | if err != nil { | |||
| 222 | - | t.Skipf("no exportable targets: %v", err) | |||
| 223 | - | } | |||
| 224 | - | defer func() { | |||
| 225 | for _, b := range bufs { | 223 | for _, b := range bufs { | ||
| 226 | for _, p := range b.Planes { | 224 | for _, p := range b.Planes { | ||
| 227 | p.File.Close() | 225 | p.File.Close() | ||
| 228 | } | 226 | } | ||
| 229 | } | 227 | } | ||
| 230 | - | }() | 228 | + | } |
| 229 | + | // Single-plane export never returns a multi-plane modifier. | |||
| 230 | + | one, err := r.ExportTargets(1, nil, true) | |||
| 231 | + | if err != nil { | |||
| 232 | + | t.Skipf("no single-plane targets: %v", err) | |||
| 233 | + | } | |||
| 234 | + | if len(one[0].Planes) != 1 || r.modPlanes[one[0].Modifier] > 1 { | |||
| 235 | + | t.Fatalf("single-plane export has %d planes (modifier %#x)", len(one[0].Planes), one[0].Modifier) | |||
| 236 | + | } | |||
| 237 | + | closeAll(one) | |||
| 238 | + | multi := false | |||
| 239 | + | for _, m := range r.renderMods { | |||
| 240 | + | multi = multi || r.modPlanes[m] > 1 | |||
| 241 | + | } | |||
| 242 | + | if !multi { | |||
| 243 | + | t.Skip("device has no multi-plane target modifier") | |||
| 244 | + | } | |||
| 245 | + | bufs, err := r.ExportTargets(2, nil, false) | |||
| 246 | + | if err != nil { | |||
| 247 | + | t.Skipf("no exportable targets: %v", err) | |||
| 248 | + | } | |||
| 249 | + | defer closeAll(bufs) | |||
| 231 | for _, b := range bufs { | 250 | for _, b := range bufs { | ||
| 232 | want := max(r.modPlanes[b.Modifier], 1) | 251 | want := max(r.modPlanes[b.Modifier], 1) | ||
| 233 | if uint32(len(b.Planes)) != want { | 252 | if uint32(len(b.Planes)) != want { | ||
| @@ -238,6 +257,10 @@ func TestExportTargetsPlanes(t *testing.T) { | |||||
| 238 | if p.File == nil || p.Stride == 0 { | 257 | if p.File == nil || p.Stride == 0 { | ||
| 239 | t.Fatalf("modifier %#x plane %d: %+v", b.Modifier, i, p) | 258 | t.Fatalf("modifier %#x plane %d: %+v", b.Modifier, i, p) | ||
| 240 | } | 259 | } | ||
| 260 | + | // The planes share one memory object: planes 1.. lie after plane 0. | |||
| 261 | + | if i > 0 && p.Offset == 0 { | |||
| 262 | + | t.Fatalf("modifier %#x plane %d at offset 0", b.Modifier, i) | |||
| 263 | + | } | |||
| 241 | if seen[int(p.File.Fd())] { | 264 | if seen[int(p.File.Fd())] { | ||
| 242 | t.Fatalf("modifier %#x: plane %d shares a file", b.Modifier, i) | 265 | t.Fatalf("modifier %#x: plane %d shares a file", b.Modifier, i) | ||
| 243 | } | 266 | } | ||
| @@ -245,17 +268,16 @@ func TestExportTargetsPlanes(t *testing.T) { | |||||
| 245 | } | 268 | } | ||
| 246 | t.Logf("modifier %#x planes %d", b.Modifier, len(b.Planes)) | 269 | t.Logf("modifier %#x planes %d", b.Modifier, len(b.Planes)) | ||
| 247 | } | 270 | } | ||
| 248 | - | // Single-plane export never returns a multi-plane modifier. | 271 | + | for i, tc := range []struct { |
| 249 | - | one, err := r.ExportTargets(1, nil, true) | 272 | + | bg string |
| 250 | - | if err != nil { | 273 | + | want color.RGBA |
| 251 | - | t.Skipf("no single-plane targets: %v", err) | 274 | + | }{{"#ff0000", color.RGBA{255, 0, 0, 255}}, {"#0000ff", color.RGBA{0, 0, 255, 255}}} { |
| 252 | - | } | 275 | + | r.UseTarget(i) |
| 253 | - | defer func() { | 276 | + | if err := render(r, ports.Scene{Background: tc.bg}, nil); err != nil { |
| 254 | - | for _, p := range one[0].Planes { | 277 | + | t.Fatal(err) |
| 255 | - | p.File.Close() | 278 | + | } |
| 279 | + | if got := readPixels(t, r).At(3, 3); got != tc.want { | |||
| 280 | + | t.Fatalf("target %d (modifier %#x): read back %v, want %v", i, bufs[i].Modifier, got, tc.want) | |||
| 256 | } | 281 | } | ||
| 257 | - | }() | |||
| 258 | - | if len(one[0].Planes) != 1 { | |||
| 259 | - | t.Fatalf("single-plane export has %d planes", len(one[0].Planes)) | |||
| 260 | } | 282 | } | ||
| 261 | } | 283 | } | ||