feat(vulkan): compose every frame on the GPU with one pipeline and return its fence as a sync file
One graphics pipeline draws fills, sampled dmabufs and wl_shm pixels as quads with push constants in a single rendering pass. Scaled content filters linearly, 1:1 content stays pixel exact. wl_shm surfaces are copied once per content into persistently mapped, double-buffered GPU buffers the shader reads directly; the staging buffer, CPU resample and fill strips are gone. Frames go through a ring of two command buffer slots and Render exports the frame semaphore as a sync file, so no CPU wait remains before the flip; objects a frame read are freed only after it completed. Dmabuf formats are advertised on sampled-image support.
- commit
- 717ee76b8fbc355795fda2ff8c2503fd1836cc2c
- author
- brice · · 2026-09-26 11:33 UTC
- parent
- d129f9d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
19 files · +1247 -732
M internal/adapters/drm/commit_test.go +94 -0
| @@ -314,3 +314,97 @@ func TestRunReportsSeenAfterCursorCommit(t *testing.T) { | |||
| 314 | 314 | t.Fatal(err) | |
| 315 | 315 | } | |
| 316 | 316 | } | |
| 317 | + | ||
| 318 | + | // Frame fences are closed on every path: flipped, EBUSY (re-rendered | |
| 319 | + | // after the event) and lost master (re-rendered after the modeset). The | |
| 320 | + | // fd count is unchanged after many frames. | |
| 321 | + | func TestRunClosesFrameFences(t *testing.T) { | |
| 322 | + | fds := func() int { | |
| 323 | + | ents, err := os.ReadDir("/proc/self/fd") | |
| 324 | + | if err != nil { | |
| 325 | + | t.Skip(err) | |
| 326 | + | } | |
| 327 | + | return len(ents) | |
| 328 | + | } | |
| 329 | + | const rounds = 30 | |
| 330 | + | // Commit results: modeset test and modeset, then per round | |
| 331 | + | // EBUSY + flip, or lost master + modeset + flip, or a plain flip. | |
| 332 | + | errs := []error{nil, nil} | |
| 333 | + | for i := range rounds { | |
| 334 | + | switch i % 3 { | |
| 335 | + | case 0: | |
| 336 | + | errs = append(errs, unix.EBUSY, nil) | |
| 337 | + | case 1: | |
| 338 | + | errs = append(errs, unix.EACCES, nil, nil) | |
| 339 | + | default: | |
| 340 | + | errs = append(errs, nil) | |
| 341 | + | } | |
| 342 | + | } | |
| 343 | + | o, k, commits, commitMu := testOutputMu(t, errs...) | |
| 344 | + | o.cursor, o.tearing, o.fbs = nil, false, [2]uint32{} | |
| 345 | + | flips := make(chan flipEvent, 1) | |
| 346 | + | o.flipped = flips | |
| 347 | + | r := portsmocks.NewMockRenderer(t) | |
| 348 | + | buf := func() ports.DMABuf { | |
| 349 | + | f, w, _ := os.Pipe() | |
| 350 | + | w.Close() | |
| 351 | + | return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}} | |
| 352 | + | } | |
| 353 | + | r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | |
| 354 | + | r.EXPECT().UseTarget(mock.Anything).Return() | |
| 355 | + | r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) { | |
| 356 | + | f, w, _ := os.Pipe() | |
| 357 | + | _, _ = w.Write([]byte{1}) | |
| 358 | + | w.Close() | |
| 359 | + | return f, nil | |
| 360 | + | }) | |
| 361 | + | r.EXPECT().Close().Return().Once() | |
| 362 | + | k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice() | |
| 363 | + | k.EXPECT().createBlob(mock.Anything).Return(99, nil) | |
| 364 | + | k.EXPECT().destroyBlob(mock.Anything).Return(nil) | |
| 365 | + | k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe() | |
| 366 | + | before := fds() | |
| 367 | + | active := make(chan bool, 1) | |
| 368 | + | scenes := make(chan ports.Scene, 1) | |
| 369 | + | presented := make(chan ports.OutputPresented, 4*rounds) | |
| 370 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 371 | + | done := make(chan error, 1) | |
| 372 | + | go func() { | |
| 373 | + | done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, active, scenes, nil, nil, presented) | |
| 374 | + | }() | |
| 375 | + | count := func() int { | |
| 376 | + | commitMu.Lock() | |
| 377 | + | defer commitMu.Unlock() | |
| 378 | + | return len(*commits) | |
| 379 | + | } | |
| 380 | + | last := func() commitRec { | |
| 381 | + | commitMu.Lock() | |
| 382 | + | defer commitMu.Unlock() | |
| 383 | + | return (*commits)[len(*commits)-1] | |
| 384 | + | } | |
| 385 | + | waitFor(t, func() bool { return count() == 2 }) | |
| 386 | + | for i := range rounds { | |
| 387 | + | n := count() | |
| 388 | + | scenes <- ports.Scene{Seq: uint64(i)} | |
| 389 | + | switch i % 3 { | |
| 390 | + | case 0: | |
| 391 | + | waitFor(t, func() bool { return count() == n+1 }) | |
| 392 | + | flips <- flipEvent{crtc: tCrtc} // ends the EBUSY wait | |
| 393 | + | waitFor(t, func() bool { return count() == n+2 }) | |
| 394 | + | case 1: | |
| 395 | + | waitFor(t, func() bool { return count() == n+1 }) | |
| 396 | + | active <- true | |
| 397 | + | waitFor(t, func() bool { return count() == n+3 }) | |
| 398 | + | default: | |
| 399 | + | waitFor(t, func() bool { return count() == n+1 }) | |
| 400 | + | } | |
| 401 | + | flips <- eventOf(last()) | |
| 402 | + | } | |
| 403 | + | cancel() | |
| 404 | + | if err := <-done; err != nil { | |
| 405 | + | t.Fatal(err) | |
| 406 | + | } | |
| 407 | + | if after := fds(); after > before { | |
| 408 | + | t.Fatalf("fds %d -> %d", before, after) | |
| 409 | + | } | |
| 410 | + | } | |
| @@ -314,3 +314,97 @@ func TestRunReportsSeenAfterCursorCommit(t *testing.T) { | |||||
| 314 | t.Fatal(err) | 314 | t.Fatal(err) | ||
| 315 | } | 315 | } | ||
| 316 | } | 316 | } | ||
| 317 | + | ||||
| 318 | + | // Frame fences are closed on every path: flipped, EBUSY (re-rendered | |||
| 319 | + | // after the event) and lost master (re-rendered after the modeset). The | |||
| 320 | + | // fd count is unchanged after many frames. | |||
| 321 | + | func TestRunClosesFrameFences(t *testing.T) { | |||
| 322 | + | fds := func() int { | |||
| 323 | + | ents, err := os.ReadDir("/proc/self/fd") | |||
| 324 | + | if err != nil { | |||
| 325 | + | t.Skip(err) | |||
| 326 | + | } | |||
| 327 | + | return len(ents) | |||
| 328 | + | } | |||
| 329 | + | const rounds = 30 | |||
| 330 | + | // Commit results: modeset test and modeset, then per round | |||
| 331 | + | // EBUSY + flip, or lost master + modeset + flip, or a plain flip. | |||
| 332 | + | errs := []error{nil, nil} | |||
| 333 | + | for i := range rounds { | |||
| 334 | + | switch i % 3 { | |||
| 335 | + | case 0: | |||
| 336 | + | errs = append(errs, unix.EBUSY, nil) | |||
| 337 | + | case 1: | |||
| 338 | + | errs = append(errs, unix.EACCES, nil, nil) | |||
| 339 | + | default: | |||
| 340 | + | errs = append(errs, nil) | |||
| 341 | + | } | |||
| 342 | + | } | |||
| 343 | + | o, k, commits, commitMu := testOutputMu(t, errs...) | |||
| 344 | + | o.cursor, o.tearing, o.fbs = nil, false, [2]uint32{} | |||
| 345 | + | flips := make(chan flipEvent, 1) | |||
| 346 | + | o.flipped = flips | |||
| 347 | + | r := portsmocks.NewMockRenderer(t) | |||
| 348 | + | buf := func() ports.DMABuf { | |||
| 349 | + | f, w, _ := os.Pipe() | |||
| 350 | + | w.Close() | |||
| 351 | + | return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}} | |||
| 352 | + | } | |||
| 353 | + | r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{buf(), buf()}, nil).Once() | |||
| 354 | + | r.EXPECT().UseTarget(mock.Anything).Return() | |||
| 355 | + | r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) { | |||
| 356 | + | f, w, _ := os.Pipe() | |||
| 357 | + | _, _ = w.Write([]byte{1}) | |||
| 358 | + | w.Close() | |||
| 359 | + | return f, nil | |||
| 360 | + | }) | |||
| 361 | + | r.EXPECT().Close().Return().Once() | |||
| 362 | + | k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice() | |||
| 363 | + | k.EXPECT().createBlob(mock.Anything).Return(99, nil) | |||
| 364 | + | k.EXPECT().destroyBlob(mock.Anything).Return(nil) | |||
| 365 | + | k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe() | |||
| 366 | + | before := fds() | |||
| 367 | + | active := make(chan bool, 1) | |||
| 368 | + | scenes := make(chan ports.Scene, 1) | |||
| 369 | + | presented := make(chan ports.OutputPresented, 4*rounds) | |||
| 370 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 371 | + | done := make(chan error, 1) | |||
| 372 | + | go func() { | |||
| 373 | + | done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, active, scenes, nil, nil, presented) | |||
| 374 | + | }() | |||
| 375 | + | count := func() int { | |||
| 376 | + | commitMu.Lock() | |||
| 377 | + | defer commitMu.Unlock() | |||
| 378 | + | return len(*commits) | |||
| 379 | + | } | |||
| 380 | + | last := func() commitRec { | |||
| 381 | + | commitMu.Lock() | |||
| 382 | + | defer commitMu.Unlock() | |||
| 383 | + | return (*commits)[len(*commits)-1] | |||
| 384 | + | } | |||
| 385 | + | waitFor(t, func() bool { return count() == 2 }) | |||
| 386 | + | for i := range rounds { | |||
| 387 | + | n := count() | |||
| 388 | + | scenes <- ports.Scene{Seq: uint64(i)} | |||
| 389 | + | switch i % 3 { | |||
| 390 | + | case 0: | |||
| 391 | + | waitFor(t, func() bool { return count() == n+1 }) | |||
| 392 | + | flips <- flipEvent{crtc: tCrtc} // ends the EBUSY wait | |||
| 393 | + | waitFor(t, func() bool { return count() == n+2 }) | |||
| 394 | + | case 1: | |||
| 395 | + | waitFor(t, func() bool { return count() == n+1 }) | |||
| 396 | + | active <- true | |||
| 397 | + | waitFor(t, func() bool { return count() == n+3 }) | |||
| 398 | + | default: | |||
| 399 | + | waitFor(t, func() bool { return count() == n+1 }) | |||
| 400 | + | } | |||
| 401 | + | flips <- eventOf(last()) | |||
| 402 | + | } | |||
| 403 | + | cancel() | |||
| 404 | + | if err := <-done; err != nil { | |||
| 405 | + | t.Fatal(err) | |||
| 406 | + | } | |||
| 407 | + | if after := fds(); after > before { | |||
| 408 | + | t.Fatalf("fds %d -> %d", before, after) | |||
| 409 | + | } | |||
| 410 | + | } | |||
D internal/adapters/vulkan/blend.go +0 -190
| @@ -1,190 +0,0 @@ | |||
| 1 | - | package vulkan | |
| 2 | - | ||
| 3 | - | import ( | |
| 4 | - | _ "embed" | |
| 5 | - | "encoding/binary" | |
| 6 | - | "unsafe" | |
| 7 | - | ||
| 8 | - | vk "github.com/bnema/purego-vulkan/vulkan" | |
| 9 | - | ) | |
| 10 | - | ||
| 11 | - | // Translucent client pixels are blended over what is below them; opaque | |
| 12 | - | // ones keep the plain copy. The blend pipeline draws one quad per upload | |
| 13 | - | // and its fragment shader reads the staged pixels straight from the | |
| 14 | - | // staging buffer: no texture, no sampler. Wayland buffers are | |
| 15 | - | // premultiplied, hence ONE, ONE_MINUS_SRC_ALPHA. | |
| 16 | - | // | |
| 17 | - | // Limits: dmabuf buffers are still blitted, alpha ignored (blending them | |
| 18 | - | // needs a sampled-image variant). The opaque region is not honoured, so | |
| 19 | - | // an ARGB buffer blends even where it is opaque. | |
| 20 | - | ||
| 21 | - | //go:generate glslc -O --target-env=vulkan1.3 shaders/blend.vert -o shaders/blend.vert.spv | |
| 22 | - | //go:generate glslc -O --target-env=vulkan1.3 shaders/blend.frag -o shaders/blend.frag.spv | |
| 23 | - | ||
| 24 | - | var ( | |
| 25 | - | //go:embed shaders/blend.vert.spv | |
| 26 | - | blendVert []byte | |
| 27 | - | //go:embed shaders/blend.frag.spv | |
| 28 | - | blendFrag []byte | |
| 29 | - | ) | |
| 30 | - | ||
| 31 | - | // drawSize is one entry of the draw table (shaders: struct Draw). | |
| 32 | - | const drawSize = 32 | |
| 33 | - | ||
| 34 | - | // blendPipeline is the graphics state for blended uploads. | |
| 35 | - | type blendPipeline struct { | |
| 36 | - | setLayout vk.DescriptorSetLayout | |
| 37 | - | layout vk.PipelineLayout | |
| 38 | - | pipeline vk.Pipeline | |
| 39 | - | pool vk.DescriptorPool | |
| 40 | - | set vk.DescriptorSet | |
| 41 | - | } | |
| 42 | - | ||
| 43 | - | func (r *Renderer) createBlend() error { | |
| 44 | - | d, b := r.dd, &r.blend | |
| 45 | - | bindings := []vk.DescriptorSetLayoutBinding{ | |
| 46 | - | {Binding: 0, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}, | |
| 47 | - | {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageVertexBit | vk.ShaderStageFragmentBit}, | |
| 48 | - | } | |
| 49 | - | sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 2, Bindings: &bindings[0]} | |
| 50 | - | if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &b.setLayout)); err != nil { | |
| 51 | - | return err | |
| 52 | - | } | |
| 53 | - | pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &b.setLayout} | |
| 54 | - | if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &b.layout)); err != nil { | |
| 55 | - | return err | |
| 56 | - | } | |
| 57 | - | size := vk.DescriptorPoolSize{Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: 2} | |
| 58 | - | dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &size} | |
| 59 | - | if err := checked("vkCreateDescriptorPool", d.CreateDescriptorPool(r.device, &dpi, nil, &b.pool)); err != nil { | |
| 60 | - | return err | |
| 61 | - | } | |
| 62 | - | dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: b.pool, DescriptorSetCount: 1, SetLayouts: &b.setLayout} | |
| 63 | - | if err := checked("vkAllocateDescriptorSets", d.AllocateDescriptorSets(r.device, &dai, &b.set)); err != nil { | |
| 64 | - | return err | |
| 65 | - | } | |
| 66 | - | vert, err := r.shaderModule(blendVert) | |
| 67 | - | if err != nil { | |
| 68 | - | return err | |
| 69 | - | } | |
| 70 | - | defer d.DestroyShaderModule(r.device, vert, nil) | |
| 71 | - | frag, err := r.shaderModule(blendFrag) | |
| 72 | - | if err != nil { | |
| 73 | - | return err | |
| 74 | - | } | |
| 75 | - | defer d.DestroyShaderModule(r.device, frag, nil) | |
| 76 | - | main := []byte("main\x00") | |
| 77 | - | stages := []vk.PipelineShaderStageCreateInfo{ | |
| 78 | - | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]}, | |
| 79 | - | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0]}, | |
| 80 | - | } | |
| 81 | - | vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo} | |
| 82 | - | assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList} | |
| 83 | - | // The target size is fixed for the renderer's life: static viewport. | |
| 84 | - | viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1} | |
| 85 | - | scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}} | |
| 86 | - | viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor} | |
| 87 | - | raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1} | |
| 88 | - | multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit} | |
| 89 | - | attachment := vk.PipelineColorBlendAttachmentState{ | |
| 90 | - | BlendEnable: 1, | |
| 91 | - | SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd, | |
| 92 | - | SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd, | |
| 93 | - | ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit, | |
| 94 | - | } | |
| 95 | - | blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment} | |
| 96 | - | format := vk.Format(vk.FormatB8g8r8a8Unorm) | |
| 97 | - | rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format} | |
| 98 | - | gpi := vk.GraphicsPipelineCreateInfo{ | |
| 99 | - | SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering), | |
| 100 | - | StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly, | |
| 101 | - | ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample, | |
| 102 | - | ColorBlendState: &blendState, Layout: b.layout, BasePipelineIndex: -1, | |
| 103 | - | } | |
| 104 | - | return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, &b.pipeline)) | |
| 105 | - | } | |
| 106 | - | ||
| 107 | - | func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) { | |
| 108 | - | // SPIR-V is read as words: copy into a uint32-aligned slice. | |
| 109 | - | words := make([]uint32, len(code)/4) | |
| 110 | - | for i := range words { | |
| 111 | - | words[i] = binary.LittleEndian.Uint32(code[i*4:]) | |
| 112 | - | } | |
| 113 | - | info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]} | |
| 114 | - | var m vk.ShaderModule | |
| 115 | - | return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m)) | |
| 116 | - | } | |
| 117 | - | ||
| 118 | - | func (r *Renderer) destroyBlend() { | |
| 119 | - | d, b := r.dd, &r.blend | |
| 120 | - | if b.pipeline != 0 { | |
| 121 | - | d.DestroyPipeline(r.device, b.pipeline, nil) | |
| 122 | - | } | |
| 123 | - | if b.pool != 0 { | |
| 124 | - | d.DestroyDescriptorPool(r.device, b.pool, nil) | |
| 125 | - | } | |
| 126 | - | if b.layout != 0 { | |
| 127 | - | d.DestroyPipelineLayout(r.device, b.layout, nil) | |
| 128 | - | } | |
| 129 | - | if b.setLayout != 0 { | |
| 130 | - | d.DestroyDescriptorSetLayout(r.device, b.setLayout, nil) | |
| 131 | - | } | |
| 132 | - | *b = blendPipeline{} | |
| 133 | - | } | |
| 134 | - | ||
| 135 | - | // bindStaging points the set at the staging buffer: pixels blended | |
| 136 | - | // pixel bytes from 0, and the draw table of n entries at tableOffset. | |
| 137 | - | func (r *Renderer) bindStaging(pixels, tableOffset, n int) { | |
| 138 | - | infos := []vk.DescriptorBufferInfo{ | |
| 139 | - | {Buffer: r.staging, Range: vk.DeviceSize(max(pixels, 4))}, | |
| 140 | - | {Buffer: r.staging, Offset: vk.DeviceSize(tableOffset), Range: vk.DeviceSize(n * drawSize)}, | |
| 141 | - | } | |
| 142 | - | writes := []vk.WriteDescriptorSet{ | |
| 143 | - | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[0]}, | |
| 144 | - | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 1, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[1]}, | |
| 145 | - | } | |
| 146 | - | r.dd.UpdateDescriptorSets(r.device, 2, &writes[0], 0, nil) | |
| 147 | - | } | |
| 148 | - | ||
| 149 | - | // putDraw writes draw entry i of table. | |
| 150 | - | func (r *Renderer) putDraw(table []byte, i int, u *upload) { | |
| 151 | - | e := table[i*drawSize : (i+1)*drawSize] | |
| 152 | - | for j, v := range []uint32{ | |
| 153 | - | uint32(u.rect.Min.X), uint32(u.rect.Min.Y), uint32(u.rect.Max.X), uint32(u.rect.Max.Y), | |
| 154 | - | uint32(u.offset / 4), uint32(u.rect.Dx()), uint32(r.width), uint32(r.height), | |
| 155 | - | } { | |
| 156 | - | binary.LittleEndian.PutUint32(e[j*4:], v) | |
| 157 | - | } | |
| 158 | - | } | |
| 159 | - | ||
| 160 | - | // drawBlended draws uploads[first:last], all blended, into view: one | |
| 161 | - | // instanced draw. The image is TRANSFER_DST before and after. | |
| 162 | - | func (r *Renderer) drawBlended(image vk.Image, view vk.ImageView, first, last int) { | |
| 163 | - | d := r.dd | |
| 164 | - | rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | |
| 165 | - | b := vk.ImageMemoryBarrier{ | |
| 166 | - | SType: vk.StructureTypeImageMemoryBarrier, Image: image, SubresourceRange: rangeInfo, | |
| 167 | - | SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessColorAttachmentReadBit | vk.AccessColorAttachmentWriteBit, | |
| 168 | - | OldLayout: vk.ImageLayoutTransferDstOptimal, NewLayout: vk.ImageLayoutColorAttachmentOptimal, | |
| 169 | - | } | |
| 170 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, &b) | |
| 171 | - | attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpLoad, StoreOp: vk.AttachmentStoreOpStore} | |
| 172 | - | info := vk.RenderingInfo{SType: vk.StructureTypeRenderingInfo, RenderArea: vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment} | |
| 173 | - | d.CmdBeginRendering(r.command, &info) | |
| 174 | - | d.CmdBindPipeline(r.command, vk.PipelineBindPointGraphics, r.blend.pipeline) | |
| 175 | - | d.CmdBindDescriptorSets(r.command, vk.PipelineBindPointGraphics, r.blend.layout, 0, 1, &r.blend.set, 0, nil) | |
| 176 | - | d.CmdDraw(r.command, 6, uint32(last-first), 0, uint32(first)) | |
| 177 | - | d.CmdEndRendering(r.command) | |
| 178 | - | b.SrcAccessMask, b.DstAccessMask = vk.AccessColorAttachmentWriteBit, vk.AccessTransferWriteBit|vk.AccessTransferReadBit | |
| 179 | - | b.OldLayout, b.NewLayout = vk.ImageLayoutColorAttachmentOptimal, vk.ImageLayoutTransferDstOptimal | |
| 180 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |
| 181 | - | } | |
| 182 | - | ||
| 183 | - | // createView makes the color attachment view of a target image. | |
| 184 | - | func (r *Renderer) createView(t *target) error { | |
| 185 | - | info := vk.ImageViewCreateInfo{ | |
| 186 | - | SType: vk.StructureTypeImageViewCreateInfo, Image: t.image, ViewType: vk.ImageViewType2d, Format: vk.FormatB8g8r8a8Unorm, | |
| 187 | - | SubresourceRange: vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}, | |
| 188 | - | } | |
| 189 | - | return checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &t.view)) | |
| 190 | - | } | |
| @@ -1,190 +0,0 @@ | |||||
| 1 | - | package vulkan | |||
| 2 | - | ||||
| 3 | - | import ( | |||
| 4 | - | _ "embed" | |||
| 5 | - | "encoding/binary" | |||
| 6 | - | "unsafe" | |||
| 7 | - | ||||
| 8 | - | vk "github.com/bnema/purego-vulkan/vulkan" | |||
| 9 | - | ) | |||
| 10 | - | ||||
| 11 | - | // Translucent client pixels are blended over what is below them; opaque | |||
| 12 | - | // ones keep the plain copy. The blend pipeline draws one quad per upload | |||
| 13 | - | // and its fragment shader reads the staged pixels straight from the | |||
| 14 | - | // staging buffer: no texture, no sampler. Wayland buffers are | |||
| 15 | - | // premultiplied, hence ONE, ONE_MINUS_SRC_ALPHA. | |||
| 16 | - | // | |||
| 17 | - | // Limits: dmabuf buffers are still blitted, alpha ignored (blending them | |||
| 18 | - | // needs a sampled-image variant). The opaque region is not honoured, so | |||
| 19 | - | // an ARGB buffer blends even where it is opaque. | |||
| 20 | - | ||||
| 21 | - | //go:generate glslc -O --target-env=vulkan1.3 shaders/blend.vert -o shaders/blend.vert.spv | |||
| 22 | - | //go:generate glslc -O --target-env=vulkan1.3 shaders/blend.frag -o shaders/blend.frag.spv | |||
| 23 | - | ||||
| 24 | - | var ( | |||
| 25 | - | //go:embed shaders/blend.vert.spv | |||
| 26 | - | blendVert []byte | |||
| 27 | - | //go:embed shaders/blend.frag.spv | |||
| 28 | - | blendFrag []byte | |||
| 29 | - | ) | |||
| 30 | - | ||||
| 31 | - | // drawSize is one entry of the draw table (shaders: struct Draw). | |||
| 32 | - | const drawSize = 32 | |||
| 33 | - | ||||
| 34 | - | // blendPipeline is the graphics state for blended uploads. | |||
| 35 | - | type blendPipeline struct { | |||
| 36 | - | setLayout vk.DescriptorSetLayout | |||
| 37 | - | layout vk.PipelineLayout | |||
| 38 | - | pipeline vk.Pipeline | |||
| 39 | - | pool vk.DescriptorPool | |||
| 40 | - | set vk.DescriptorSet | |||
| 41 | - | } | |||
| 42 | - | ||||
| 43 | - | func (r *Renderer) createBlend() error { | |||
| 44 | - | d, b := r.dd, &r.blend | |||
| 45 | - | bindings := []vk.DescriptorSetLayoutBinding{ | |||
| 46 | - | {Binding: 0, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}, | |||
| 47 | - | {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageVertexBit | vk.ShaderStageFragmentBit}, | |||
| 48 | - | } | |||
| 49 | - | sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 2, Bindings: &bindings[0]} | |||
| 50 | - | if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &b.setLayout)); err != nil { | |||
| 51 | - | return err | |||
| 52 | - | } | |||
| 53 | - | pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &b.setLayout} | |||
| 54 | - | if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &b.layout)); err != nil { | |||
| 55 | - | return err | |||
| 56 | - | } | |||
| 57 | - | size := vk.DescriptorPoolSize{Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: 2} | |||
| 58 | - | dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &size} | |||
| 59 | - | if err := checked("vkCreateDescriptorPool", d.CreateDescriptorPool(r.device, &dpi, nil, &b.pool)); err != nil { | |||
| 60 | - | return err | |||
| 61 | - | } | |||
| 62 | - | dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: b.pool, DescriptorSetCount: 1, SetLayouts: &b.setLayout} | |||
| 63 | - | if err := checked("vkAllocateDescriptorSets", d.AllocateDescriptorSets(r.device, &dai, &b.set)); err != nil { | |||
| 64 | - | return err | |||
| 65 | - | } | |||
| 66 | - | vert, err := r.shaderModule(blendVert) | |||
| 67 | - | if err != nil { | |||
| 68 | - | return err | |||
| 69 | - | } | |||
| 70 | - | defer d.DestroyShaderModule(r.device, vert, nil) | |||
| 71 | - | frag, err := r.shaderModule(blendFrag) | |||
| 72 | - | if err != nil { | |||
| 73 | - | return err | |||
| 74 | - | } | |||
| 75 | - | defer d.DestroyShaderModule(r.device, frag, nil) | |||
| 76 | - | main := []byte("main\x00") | |||
| 77 | - | stages := []vk.PipelineShaderStageCreateInfo{ | |||
| 78 | - | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]}, | |||
| 79 | - | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0]}, | |||
| 80 | - | } | |||
| 81 | - | vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo} | |||
| 82 | - | assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList} | |||
| 83 | - | // The target size is fixed for the renderer's life: static viewport. | |||
| 84 | - | viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1} | |||
| 85 | - | scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}} | |||
| 86 | - | viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor} | |||
| 87 | - | raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1} | |||
| 88 | - | multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit} | |||
| 89 | - | attachment := vk.PipelineColorBlendAttachmentState{ | |||
| 90 | - | BlendEnable: 1, | |||
| 91 | - | SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd, | |||
| 92 | - | SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd, | |||
| 93 | - | ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit, | |||
| 94 | - | } | |||
| 95 | - | blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment} | |||
| 96 | - | format := vk.Format(vk.FormatB8g8r8a8Unorm) | |||
| 97 | - | rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format} | |||
| 98 | - | gpi := vk.GraphicsPipelineCreateInfo{ | |||
| 99 | - | SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering), | |||
| 100 | - | StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly, | |||
| 101 | - | ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample, | |||
| 102 | - | ColorBlendState: &blendState, Layout: b.layout, BasePipelineIndex: -1, | |||
| 103 | - | } | |||
| 104 | - | return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, &b.pipeline)) | |||
| 105 | - | } | |||
| 106 | - | ||||
| 107 | - | func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) { | |||
| 108 | - | // SPIR-V is read as words: copy into a uint32-aligned slice. | |||
| 109 | - | words := make([]uint32, len(code)/4) | |||
| 110 | - | for i := range words { | |||
| 111 | - | words[i] = binary.LittleEndian.Uint32(code[i*4:]) | |||
| 112 | - | } | |||
| 113 | - | info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]} | |||
| 114 | - | var m vk.ShaderModule | |||
| 115 | - | return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m)) | |||
| 116 | - | } | |||
| 117 | - | ||||
| 118 | - | func (r *Renderer) destroyBlend() { | |||
| 119 | - | d, b := r.dd, &r.blend | |||
| 120 | - | if b.pipeline != 0 { | |||
| 121 | - | d.DestroyPipeline(r.device, b.pipeline, nil) | |||
| 122 | - | } | |||
| 123 | - | if b.pool != 0 { | |||
| 124 | - | d.DestroyDescriptorPool(r.device, b.pool, nil) | |||
| 125 | - | } | |||
| 126 | - | if b.layout != 0 { | |||
| 127 | - | d.DestroyPipelineLayout(r.device, b.layout, nil) | |||
| 128 | - | } | |||
| 129 | - | if b.setLayout != 0 { | |||
| 130 | - | d.DestroyDescriptorSetLayout(r.device, b.setLayout, nil) | |||
| 131 | - | } | |||
| 132 | - | *b = blendPipeline{} | |||
| 133 | - | } | |||
| 134 | - | ||||
| 135 | - | // bindStaging points the set at the staging buffer: pixels blended | |||
| 136 | - | // pixel bytes from 0, and the draw table of n entries at tableOffset. | |||
| 137 | - | func (r *Renderer) bindStaging(pixels, tableOffset, n int) { | |||
| 138 | - | infos := []vk.DescriptorBufferInfo{ | |||
| 139 | - | {Buffer: r.staging, Range: vk.DeviceSize(max(pixels, 4))}, | |||
| 140 | - | {Buffer: r.staging, Offset: vk.DeviceSize(tableOffset), Range: vk.DeviceSize(n * drawSize)}, | |||
| 141 | - | } | |||
| 142 | - | writes := []vk.WriteDescriptorSet{ | |||
| 143 | - | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[0]}, | |||
| 144 | - | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: r.blend.set, DstBinding: 1, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &infos[1]}, | |||
| 145 | - | } | |||
| 146 | - | r.dd.UpdateDescriptorSets(r.device, 2, &writes[0], 0, nil) | |||
| 147 | - | } | |||
| 148 | - | ||||
| 149 | - | // putDraw writes draw entry i of table. | |||
| 150 | - | func (r *Renderer) putDraw(table []byte, i int, u *upload) { | |||
| 151 | - | e := table[i*drawSize : (i+1)*drawSize] | |||
| 152 | - | for j, v := range []uint32{ | |||
| 153 | - | uint32(u.rect.Min.X), uint32(u.rect.Min.Y), uint32(u.rect.Max.X), uint32(u.rect.Max.Y), | |||
| 154 | - | uint32(u.offset / 4), uint32(u.rect.Dx()), uint32(r.width), uint32(r.height), | |||
| 155 | - | } { | |||
| 156 | - | binary.LittleEndian.PutUint32(e[j*4:], v) | |||
| 157 | - | } | |||
| 158 | - | } | |||
| 159 | - | ||||
| 160 | - | // drawBlended draws uploads[first:last], all blended, into view: one | |||
| 161 | - | // instanced draw. The image is TRANSFER_DST before and after. | |||
| 162 | - | func (r *Renderer) drawBlended(image vk.Image, view vk.ImageView, first, last int) { | |||
| 163 | - | d := r.dd | |||
| 164 | - | rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | |||
| 165 | - | b := vk.ImageMemoryBarrier{ | |||
| 166 | - | SType: vk.StructureTypeImageMemoryBarrier, Image: image, SubresourceRange: rangeInfo, | |||
| 167 | - | SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessColorAttachmentReadBit | vk.AccessColorAttachmentWriteBit, | |||
| 168 | - | OldLayout: vk.ImageLayoutTransferDstOptimal, NewLayout: vk.ImageLayoutColorAttachmentOptimal, | |||
| 169 | - | } | |||
| 170 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 171 | - | attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpLoad, StoreOp: vk.AttachmentStoreOpStore} | |||
| 172 | - | info := vk.RenderingInfo{SType: vk.StructureTypeRenderingInfo, RenderArea: vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment} | |||
| 173 | - | d.CmdBeginRendering(r.command, &info) | |||
| 174 | - | d.CmdBindPipeline(r.command, vk.PipelineBindPointGraphics, r.blend.pipeline) | |||
| 175 | - | d.CmdBindDescriptorSets(r.command, vk.PipelineBindPointGraphics, r.blend.layout, 0, 1, &r.blend.set, 0, nil) | |||
| 176 | - | d.CmdDraw(r.command, 6, uint32(last-first), 0, uint32(first)) | |||
| 177 | - | d.CmdEndRendering(r.command) | |||
| 178 | - | b.SrcAccessMask, b.DstAccessMask = vk.AccessColorAttachmentWriteBit, vk.AccessTransferWriteBit|vk.AccessTransferReadBit | |||
| 179 | - | b.OldLayout, b.NewLayout = vk.ImageLayoutColorAttachmentOptimal, vk.ImageLayoutTransferDstOptimal | |||
| 180 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 181 | - | } | |||
| 182 | - | ||||
| 183 | - | // createView makes the color attachment view of a target image. | |||
| 184 | - | func (r *Renderer) createView(t *target) error { | |||
| 185 | - | info := vk.ImageViewCreateInfo{ | |||
| 186 | - | SType: vk.StructureTypeImageViewCreateInfo, Image: t.image, ViewType: vk.ImageViewType2d, Format: vk.FormatB8g8r8a8Unorm, | |||
| 187 | - | SubresourceRange: vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}, | |||
| 188 | - | } | |||
| 189 | - | return checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &t.view)) | |||
| 190 | - | } | |||
A internal/adapters/vulkan/compose.go +305 -0
| @@ -0,0 +1,305 @@ | |||
| 1 | + | package vulkan | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | _ "embed" | |
| 5 | + | "encoding/binary" | |
| 6 | + | "image" | |
| 7 | + | "math" | |
| 8 | + | "unsafe" | |
| 9 | + | ||
| 10 | + | vk "github.com/bnema/purego-vulkan/vulkan" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // Composition is one graphics pipeline drawing every piece of a frame as | |
| 14 | + | // a quad, in paint order, inside one dynamic rendering pass: solid fills, | |
| 15 | + | // client dmabufs sampled as images, and client wl_shm pixels read from a | |
| 16 | + | // GPU buffer (shm.go). Push constants carry each draw; the descriptor set | |
| 17 | + | // holds its image and buffer. Client pixels are premultiplied, hence the | |
| 18 | + | // blend ONE, ONE_MINUS_SRC_ALPHA; opaque draws force alpha to 1. | |
| 19 | + | ||
| 20 | + | //go:generate glslc -O --target-env=vulkan1.3 shaders/compose.vert -o shaders/compose.vert.spv | |
| 21 | + | //go:generate glslc -O --target-env=vulkan1.3 shaders/compose.frag -o shaders/compose.frag.spv | |
| 22 | + | ||
| 23 | + | var ( | |
| 24 | + | //go:embed shaders/compose.vert.spv | |
| 25 | + | composeVert []byte | |
| 26 | + | //go:embed shaders/compose.frag.spv | |
| 27 | + | composeFrag []byte | |
| 28 | + | ) | |
| 29 | + | ||
| 30 | + | // Draw modes and flags (shaders: modeSolid, modeImage, flag*). | |
| 31 | + | const ( | |
| 32 | + | modeSolid = 0 | |
| 33 | + | modeImage = 1 | |
| 34 | + | modeBuffer = 2 | |
| 35 | + | ||
| 36 | + | flagOpaque = 1 | |
| 37 | + | flagExact = 2 | |
| 38 | + | ) | |
| 39 | + | ||
| 40 | + | // pushConstants is struct Draw of the shaders (std430 push constant block). | |
| 41 | + | type pushConstants struct { | |
| 42 | + | rect [4]int32 | |
| 43 | + | mapv [4]float32 | |
| 44 | + | color [4]float32 | |
| 45 | + | buf [4]uint32 | |
| 46 | + | misc [4]uint32 | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | // draw is one quad of a frame. | |
| 50 | + | type draw struct { | |
| 51 | + | pc pushConstants | |
| 52 | + | set vk.DescriptorSet | |
| 53 | + | im *imported // sampled client dmabuf, for ownership barriers | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | // composer is the pipeline state and the objects every draw can bind. | |
| 57 | + | type composer struct { | |
| 58 | + | setLayout vk.DescriptorSetLayout | |
| 59 | + | layout vk.PipelineLayout | |
| 60 | + | pipeline vk.Pipeline | |
| 61 | + | sampler vk.Sampler | |
| 62 | + | // Placeholders for the binding a draw does not use: every binding | |
| 63 | + | // the shader declares must be valid. | |
| 64 | + | dummyImage vk.Image | |
| 65 | + | dummyMemory vk.DeviceMemory | |
| 66 | + | dummyView vk.ImageView | |
| 67 | + | dummyBuffer gpuBuffer | |
| 68 | + | dummyPool vk.DescriptorPool | |
| 69 | + | dummySet vk.DescriptorSet | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | func (r *Renderer) createComposer() error { | |
| 73 | + | d, c := r.dd, &r.compose | |
| 74 | + | bindings := []vk.DescriptorSetLayoutBinding{ | |
| 75 | + | {Binding: 0, DescriptorType: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}, | |
| 76 | + | {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}, | |
| 77 | + | } | |
| 78 | + | sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 2, Bindings: &bindings[0]} | |
| 79 | + | if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &c.setLayout)); err != nil { | |
| 80 | + | return err | |
| 81 | + | } | |
| 82 | + | pcr := vk.PushConstantRange{StageFlags: vk.ShaderStageVertexBit | vk.ShaderStageFragmentBit, Size: uint32(unsafe.Sizeof(pushConstants{}))} | |
| 83 | + | pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &c.setLayout, PushConstantRangeCount: 1, PushConstantRanges: &pcr} | |
| 84 | + | if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &c.layout)); err != nil { | |
| 85 | + | return err | |
| 86 | + | } | |
| 87 | + | si := vk.SamplerCreateInfo{SType: vk.StructureTypeSamplerCreateInfo, MagFilter: vk.FilterLinear, MinFilter: vk.FilterLinear, AddressModeU: vk.SamplerAddressModeClampToEdge, AddressModeV: vk.SamplerAddressModeClampToEdge, AddressModeW: vk.SamplerAddressModeClampToEdge} | |
| 88 | + | if err := checked("vkCreateSampler", d.CreateSampler(r.device, &si, nil, &c.sampler)); err != nil { | |
| 89 | + | return err | |
| 90 | + | } | |
| 91 | + | if err := r.createPipeline(); err != nil { | |
| 92 | + | return err | |
| 93 | + | } | |
| 94 | + | return r.createDummies() | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | func (r *Renderer) createPipeline() error { | |
| 98 | + | d, c := r.dd, &r.compose | |
| 99 | + | vert, err := r.shaderModule(composeVert) | |
| 100 | + | if err != nil { | |
| 101 | + | return err | |
| 102 | + | } | |
| 103 | + | defer d.DestroyShaderModule(r.device, vert, nil) | |
| 104 | + | frag, err := r.shaderModule(composeFrag) | |
| 105 | + | if err != nil { | |
| 106 | + | return err | |
| 107 | + | } | |
| 108 | + | defer d.DestroyShaderModule(r.device, frag, nil) | |
| 109 | + | main := []byte("main\x00") | |
| 110 | + | stages := []vk.PipelineShaderStageCreateInfo{ | |
| 111 | + | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]}, | |
| 112 | + | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0]}, | |
| 113 | + | } | |
| 114 | + | vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo} | |
| 115 | + | assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList} | |
| 116 | + | // The target size is fixed for the renderer's life: static viewport. | |
| 117 | + | viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1} | |
| 118 | + | scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}} | |
| 119 | + | viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor} | |
| 120 | + | raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1} | |
| 121 | + | multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit} | |
| 122 | + | attachment := vk.PipelineColorBlendAttachmentState{ | |
| 123 | + | BlendEnable: 1, | |
| 124 | + | SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd, | |
| 125 | + | SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd, | |
| 126 | + | ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit, | |
| 127 | + | } | |
| 128 | + | blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment} | |
| 129 | + | format := vk.Format(vk.FormatB8g8r8a8Unorm) | |
| 130 | + | rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format} | |
| 131 | + | gpi := vk.GraphicsPipelineCreateInfo{ | |
| 132 | + | SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering), | |
| 133 | + | StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly, | |
| 134 | + | ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample, | |
| 135 | + | ColorBlendState: &blendState, Layout: c.layout, BasePipelineIndex: -1, | |
| 136 | + | } | |
| 137 | + | return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, &c.pipeline)) | |
| 138 | + | } | |
| 139 | + | ||
| 140 | + | // createDummies makes the placeholder image (1×1, shader-read layout) and | |
| 141 | + | // buffer, and the set fills bind. | |
| 142 | + | func (r *Renderer) createDummies() error { | |
| 143 | + | d, c := r.dd, &r.compose | |
| 144 | + | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: vk.Extent3D{Width: 1, Height: 1, Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: vk.ImageUsageSampledBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | |
| 145 | + | if err := checked("vkCreateImage(dummy)", d.CreateImage(r.device, &ii, nil, &c.dummyImage)); err != nil { | |
| 146 | + | return err | |
| 147 | + | } | |
| 148 | + | var req vk.MemoryRequirements | |
| 149 | + | d.GetImageMemoryRequirements(r.device, c.dummyImage, &req) | |
| 150 | + | kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit) | |
| 151 | + | if err != nil { | |
| 152 | + | return err | |
| 153 | + | } | |
| 154 | + | alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind} | |
| 155 | + | if err := checked("vkAllocateMemory(dummy)", d.AllocateMemory(r.device, &alloc, nil, &c.dummyMemory)); err != nil { | |
| 156 | + | return err | |
| 157 | + | } | |
| 158 | + | if err := checked("vkBindImageMemory(dummy)", d.BindImageMemory(r.device, c.dummyImage, c.dummyMemory, 0)); err != nil { | |
| 159 | + | return err | |
| 160 | + | } | |
| 161 | + | if c.dummyView, err = r.imageView(c.dummyImage, vk.FormatB8g8r8a8Unorm); err != nil { | |
| 162 | + | return err | |
| 163 | + | } | |
| 164 | + | if err := r.newGPUBuffer(&c.dummyBuffer, 16); err != nil { | |
| 165 | + | return err | |
| 166 | + | } | |
| 167 | + | if c.dummyPool, c.dummySet, err = r.newSet(c.dummyView, c.dummyBuffer.buffer); err != nil { | |
| 168 | + | return err | |
| 169 | + | } | |
| 170 | + | // Move the placeholder image to the layout every draw declares. | |
| 171 | + | return r.oneShot(func(cmd vk.CommandBuffer) { | |
| 172 | + | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, OldLayout: vk.ImageLayoutUndefined, NewLayout: vk.ImageLayoutShaderReadOnlyOptimal, DstAccessMask: vk.AccessShaderReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: c.dummyImage, SubresourceRange: colorRange} | |
| 173 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageFragmentShaderBit, 0, 0, nil, 0, nil, 1, &b) | |
| 174 | + | }) | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | // newSet allocates a descriptor set from its own pool (the bindings lack | |
| 178 | + | // vkFreeDescriptorSets: the pool is destroyed with the object). | |
| 179 | + | func (r *Renderer) newSet(view vk.ImageView, buffer vk.Buffer) (vk.DescriptorPool, vk.DescriptorSet, error) { | |
| 180 | + | d := r.dd | |
| 181 | + | sizes := []vk.DescriptorPoolSize{{Type: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1}, {Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1}} | |
| 182 | + | dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 2, PoolSizes: &sizes[0]} | |
| 183 | + | var pool vk.DescriptorPool | |
| 184 | + | if err := checked("vkCreateDescriptorPool", d.CreateDescriptorPool(r.device, &dpi, nil, &pool)); err != nil { | |
| 185 | + | return 0, 0, err | |
| 186 | + | } | |
| 187 | + | var set vk.DescriptorSet | |
| 188 | + | dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: pool, DescriptorSetCount: 1, SetLayouts: &r.compose.setLayout} | |
| 189 | + | if err := checked("vkAllocateDescriptorSets", d.AllocateDescriptorSets(r.device, &dai, &set)); err != nil { | |
| 190 | + | d.DestroyDescriptorPool(r.device, pool, nil) | |
| 191 | + | return 0, 0, err | |
| 192 | + | } | |
| 193 | + | img := vk.DescriptorImageInfo{Sampler: r.compose.sampler, ImageView: view, ImageLayout: vk.ImageLayoutShaderReadOnlyOptimal} | |
| 194 | + | buf := vk.DescriptorBufferInfo{Buffer: buffer, Range: wholeSize} | |
| 195 | + | writes := []vk.WriteDescriptorSet{ | |
| 196 | + | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeCombinedImageSampler, ImageInfo: &img}, | |
| 197 | + | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: set, DstBinding: 1, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &buf}, | |
| 198 | + | } | |
| 199 | + | d.UpdateDescriptorSets(r.device, 2, &writes[0], 0, nil) | |
| 200 | + | return pool, set, nil | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | func (r *Renderer) destroyComposer() { | |
| 204 | + | d, c := r.dd, &r.compose | |
| 205 | + | if c.dummyPool != 0 { | |
| 206 | + | d.DestroyDescriptorPool(r.device, c.dummyPool, nil) | |
| 207 | + | } | |
| 208 | + | r.freeGPUBuffer(&c.dummyBuffer) | |
| 209 | + | if c.dummyView != 0 { | |
| 210 | + | d.DestroyImageView(r.device, c.dummyView, nil) | |
| 211 | + | } | |
| 212 | + | if c.dummyImage != 0 { | |
| 213 | + | d.DestroyImage(r.device, c.dummyImage, nil) | |
| 214 | + | } | |
| 215 | + | if c.dummyMemory != 0 { | |
| 216 | + | d.FreeMemory(r.device, c.dummyMemory, nil) | |
| 217 | + | } | |
| 218 | + | if c.sampler != 0 { | |
| 219 | + | d.DestroySampler(r.device, c.sampler, nil) | |
| 220 | + | } | |
| 221 | + | if c.pipeline != 0 { | |
| 222 | + | d.DestroyPipeline(r.device, c.pipeline, nil) | |
| 223 | + | } | |
| 224 | + | if c.layout != 0 { | |
| 225 | + | d.DestroyPipelineLayout(r.device, c.layout, nil) | |
| 226 | + | } | |
| 227 | + | if c.setLayout != 0 { | |
| 228 | + | d.DestroyDescriptorSetLayout(r.device, c.setLayout, nil) | |
| 229 | + | } | |
| 230 | + | *c = composer{} | |
| 231 | + | } | |
| 232 | + | ||
| 233 | + | func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) { | |
| 234 | + | // SPIR-V is read as words: copy into a uint32-aligned slice. | |
| 235 | + | words := make([]uint32, len(code)/4) | |
| 236 | + | for i := range words { | |
| 237 | + | words[i] = binary.LittleEndian.Uint32(code[i*4:]) | |
| 238 | + | } | |
| 239 | + | info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]} | |
| 240 | + | var m vk.ShaderModule | |
| 241 | + | return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m)) | |
| 242 | + | } | |
| 243 | + | ||
| 244 | + | var colorRange = vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | |
| 245 | + | ||
| 246 | + | // imageView makes a 2D color view of an image. | |
| 247 | + | func (r *Renderer) imageView(img vk.Image, format vk.Format) (vk.ImageView, error) { | |
| 248 | + | info := vk.ImageViewCreateInfo{SType: vk.StructureTypeImageViewCreateInfo, Image: img, ViewType: vk.ImageViewType2d, Format: format, SubresourceRange: colorRange} | |
| 249 | + | var v vk.ImageView | |
| 250 | + | return v, checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &v)) | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // createView makes the color attachment view of a target image. | |
| 254 | + | func (r *Renderer) createView(t *target) (err error) { | |
| 255 | + | t.view, err = r.imageView(t.image, vk.FormatB8g8r8a8Unorm) | |
| 256 | + | return err | |
| 257 | + | } | |
| 258 | + | ||
| 259 | + | // fillDraw is a solid rect of c. | |
| 260 | + | func (r *Renderer) fillDraw(rect image.Rectangle, c [3]uint8) draw { | |
| 261 | + | dr := draw{set: r.compose.dummySet} | |
| 262 | + | dr.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)} | |
| 263 | + | dr.pc.color = [4]float32{float32(c[0]) / 255, float32(c[1]) / 255, float32(c[2]) / 255, 1} | |
| 264 | + | dr.pc.misc = [4]uint32{modeSolid, 0, uint32(r.width), uint32(r.height)} | |
| 265 | + | return dr | |
| 266 | + | } | |
| 267 | + | ||
| 268 | + | // contentDraw draws the visible rect of a w×h buffer mapped onto full | |
| 269 | + | // (target pixels). exact: the buffer is drawn at its size. | |
| 270 | + | func (r *Renderer) contentDraw(rect, full image.Rectangle, w, h int, mode uint32, opaque bool) draw { | |
| 271 | + | var dr draw | |
| 272 | + | sx, sy := float32(w)/float32(full.Dx()), float32(h)/float32(full.Dy()) | |
| 273 | + | dr.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)} | |
| 274 | + | dr.pc.mapv = [4]float32{-float32(full.Min.X) * sx, -float32(full.Min.Y) * sy, sx, sy} | |
| 275 | + | flags := uint32(0) | |
| 276 | + | if opaque { | |
| 277 | + | flags |= flagOpaque | |
| 278 | + | } | |
| 279 | + | if full.Dx() == w && full.Dy() == h { | |
| 280 | + | flags |= flagExact | |
| 281 | + | } | |
| 282 | + | dr.pc.misc = [4]uint32{mode, flags, uint32(r.width), uint32(r.height)} | |
| 283 | + | return dr | |
| 284 | + | } | |
| 285 | + | ||
| 286 | + | // recordDraws draws ds in one rendering pass that clears view to bg. | |
| 287 | + | func (r *Renderer) recordDraws(cmd vk.CommandBuffer, view vk.ImageView, bg [3]uint8, ds []draw) { | |
| 288 | + | d, c := r.dd, &r.compose | |
| 289 | + | clear := vk.ClearValue{math.Float32bits(float32(bg[0]) / 255), math.Float32bits(float32(bg[1]) / 255), math.Float32bits(float32(bg[2]) / 255), math.Float32bits(1)} | |
| 290 | + | attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpClear, StoreOp: vk.AttachmentStoreOpStore, ClearValue: clear} | |
| 291 | + | info := vk.RenderingInfo{SType: vk.StructureTypeRenderingInfo, RenderArea: vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment} | |
| 292 | + | d.CmdBeginRendering(cmd, &info) | |
| 293 | + | d.CmdBindPipeline(cmd, vk.PipelineBindPointGraphics, c.pipeline) | |
| 294 | + | var bound vk.DescriptorSet | |
| 295 | + | for i := range ds { | |
| 296 | + | dr := &ds[i] | |
| 297 | + | if dr.set != bound { | |
| 298 | + | d.CmdBindDescriptorSets(cmd, vk.PipelineBindPointGraphics, c.layout, 0, 1, &dr.set, 0, nil) | |
| 299 | + | bound = dr.set | |
| 300 | + | } | |
| 301 | + | d.CmdPushConstants(cmd, c.layout, vk.ShaderStageVertexBit|vk.ShaderStageFragmentBit, 0, uint32(unsafe.Sizeof(dr.pc)), unsafe.Pointer(&dr.pc)) | |
| 302 | + | d.CmdDraw(cmd, 6, 1, 0, 0) | |
| 303 | + | } | |
| 304 | + | d.CmdEndRendering(cmd) | |
| 305 | + | } | |
| @@ -0,0 +1,305 @@ | |||||
| 1 | + | package vulkan | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | _ "embed" | |||
| 5 | + | "encoding/binary" | |||
| 6 | + | "image" | |||
| 7 | + | "math" | |||
| 8 | + | "unsafe" | |||
| 9 | + | ||||
| 10 | + | vk "github.com/bnema/purego-vulkan/vulkan" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // Composition is one graphics pipeline drawing every piece of a frame as | |||
| 14 | + | // a quad, in paint order, inside one dynamic rendering pass: solid fills, | |||
| 15 | + | // client dmabufs sampled as images, and client wl_shm pixels read from a | |||
| 16 | + | // GPU buffer (shm.go). Push constants carry each draw; the descriptor set | |||
| 17 | + | // holds its image and buffer. Client pixels are premultiplied, hence the | |||
| 18 | + | // blend ONE, ONE_MINUS_SRC_ALPHA; opaque draws force alpha to 1. | |||
| 19 | + | ||||
| 20 | + | //go:generate glslc -O --target-env=vulkan1.3 shaders/compose.vert -o shaders/compose.vert.spv | |||
| 21 | + | //go:generate glslc -O --target-env=vulkan1.3 shaders/compose.frag -o shaders/compose.frag.spv | |||
| 22 | + | ||||
| 23 | + | var ( | |||
| 24 | + | //go:embed shaders/compose.vert.spv | |||
| 25 | + | composeVert []byte | |||
| 26 | + | //go:embed shaders/compose.frag.spv | |||
| 27 | + | composeFrag []byte | |||
| 28 | + | ) | |||
| 29 | + | ||||
| 30 | + | // Draw modes and flags (shaders: modeSolid, modeImage, flag*). | |||
| 31 | + | const ( | |||
| 32 | + | modeSolid = 0 | |||
| 33 | + | modeImage = 1 | |||
| 34 | + | modeBuffer = 2 | |||
| 35 | + | ||||
| 36 | + | flagOpaque = 1 | |||
| 37 | + | flagExact = 2 | |||
| 38 | + | ) | |||
| 39 | + | ||||
| 40 | + | // pushConstants is struct Draw of the shaders (std430 push constant block). | |||
| 41 | + | type pushConstants struct { | |||
| 42 | + | rect [4]int32 | |||
| 43 | + | mapv [4]float32 | |||
| 44 | + | color [4]float32 | |||
| 45 | + | buf [4]uint32 | |||
| 46 | + | misc [4]uint32 | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | // draw is one quad of a frame. | |||
| 50 | + | type draw struct { | |||
| 51 | + | pc pushConstants | |||
| 52 | + | set vk.DescriptorSet | |||
| 53 | + | im *imported // sampled client dmabuf, for ownership barriers | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | // composer is the pipeline state and the objects every draw can bind. | |||
| 57 | + | type composer struct { | |||
| 58 | + | setLayout vk.DescriptorSetLayout | |||
| 59 | + | layout vk.PipelineLayout | |||
| 60 | + | pipeline vk.Pipeline | |||
| 61 | + | sampler vk.Sampler | |||
| 62 | + | // Placeholders for the binding a draw does not use: every binding | |||
| 63 | + | // the shader declares must be valid. | |||
| 64 | + | dummyImage vk.Image | |||
| 65 | + | dummyMemory vk.DeviceMemory | |||
| 66 | + | dummyView vk.ImageView | |||
| 67 | + | dummyBuffer gpuBuffer | |||
| 68 | + | dummyPool vk.DescriptorPool | |||
| 69 | + | dummySet vk.DescriptorSet | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | func (r *Renderer) createComposer() error { | |||
| 73 | + | d, c := r.dd, &r.compose | |||
| 74 | + | bindings := []vk.DescriptorSetLayoutBinding{ | |||
| 75 | + | {Binding: 0, DescriptorType: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}, | |||
| 76 | + | {Binding: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1, StageFlags: vk.ShaderStageFragmentBit}, | |||
| 77 | + | } | |||
| 78 | + | sli := vk.DescriptorSetLayoutCreateInfo{SType: vk.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 2, Bindings: &bindings[0]} | |||
| 79 | + | if err := checked("vkCreateDescriptorSetLayout", d.CreateDescriptorSetLayout(r.device, &sli, nil, &c.setLayout)); err != nil { | |||
| 80 | + | return err | |||
| 81 | + | } | |||
| 82 | + | pcr := vk.PushConstantRange{StageFlags: vk.ShaderStageVertexBit | vk.ShaderStageFragmentBit, Size: uint32(unsafe.Sizeof(pushConstants{}))} | |||
| 83 | + | pli := vk.PipelineLayoutCreateInfo{SType: vk.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &c.setLayout, PushConstantRangeCount: 1, PushConstantRanges: &pcr} | |||
| 84 | + | if err := checked("vkCreatePipelineLayout", d.CreatePipelineLayout(r.device, &pli, nil, &c.layout)); err != nil { | |||
| 85 | + | return err | |||
| 86 | + | } | |||
| 87 | + | si := vk.SamplerCreateInfo{SType: vk.StructureTypeSamplerCreateInfo, MagFilter: vk.FilterLinear, MinFilter: vk.FilterLinear, AddressModeU: vk.SamplerAddressModeClampToEdge, AddressModeV: vk.SamplerAddressModeClampToEdge, AddressModeW: vk.SamplerAddressModeClampToEdge} | |||
| 88 | + | if err := checked("vkCreateSampler", d.CreateSampler(r.device, &si, nil, &c.sampler)); err != nil { | |||
| 89 | + | return err | |||
| 90 | + | } | |||
| 91 | + | if err := r.createPipeline(); err != nil { | |||
| 92 | + | return err | |||
| 93 | + | } | |||
| 94 | + | return r.createDummies() | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | func (r *Renderer) createPipeline() error { | |||
| 98 | + | d, c := r.dd, &r.compose | |||
| 99 | + | vert, err := r.shaderModule(composeVert) | |||
| 100 | + | if err != nil { | |||
| 101 | + | return err | |||
| 102 | + | } | |||
| 103 | + | defer d.DestroyShaderModule(r.device, vert, nil) | |||
| 104 | + | frag, err := r.shaderModule(composeFrag) | |||
| 105 | + | if err != nil { | |||
| 106 | + | return err | |||
| 107 | + | } | |||
| 108 | + | defer d.DestroyShaderModule(r.device, frag, nil) | |||
| 109 | + | main := []byte("main\x00") | |||
| 110 | + | stages := []vk.PipelineShaderStageCreateInfo{ | |||
| 111 | + | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageVertexBit, Module: vert, Name: &main[0]}, | |||
| 112 | + | {SType: vk.StructureTypePipelineShaderStageCreateInfo, Stage: vk.ShaderStageFragmentBit, Module: frag, Name: &main[0]}, | |||
| 113 | + | } | |||
| 114 | + | vertexInput := vk.PipelineVertexInputStateCreateInfo{SType: vk.StructureTypePipelineVertexInputStateCreateInfo} | |||
| 115 | + | assembly := vk.PipelineInputAssemblyStateCreateInfo{SType: vk.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vk.PrimitiveTopologyTriangleList} | |||
| 116 | + | // The target size is fixed for the renderer's life: static viewport. | |||
| 117 | + | viewport := vk.Viewport{Width: float32(r.width), Height: float32(r.height), MaxDepth: 1} | |||
| 118 | + | scissor := vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}} | |||
| 119 | + | viewportState := vk.PipelineViewportStateCreateInfo{SType: vk.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &viewport, ScissorCount: 1, Scissors: &scissor} | |||
| 120 | + | raster := vk.PipelineRasterizationStateCreateInfo{SType: vk.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vk.PolygonModeFill, CullMode: vk.CullModeNone, FrontFace: vk.FrontFaceCounterClockwise, LineWidth: 1} | |||
| 121 | + | multisample := vk.PipelineMultisampleStateCreateInfo{SType: vk.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vk.SampleCount1Bit} | |||
| 122 | + | attachment := vk.PipelineColorBlendAttachmentState{ | |||
| 123 | + | BlendEnable: 1, | |||
| 124 | + | SrcColorBlendFactor: vk.BlendFactorOne, DstColorBlendFactor: vk.BlendFactorOneMinusSrcAlpha, ColorBlendOp: vk.BlendOpAdd, | |||
| 125 | + | SrcAlphaBlendFactor: vk.BlendFactorOne, DstAlphaBlendFactor: vk.BlendFactorOneMinusSrcAlpha, AlphaBlendOp: vk.BlendOpAdd, | |||
| 126 | + | ColorWriteMask: vk.ColorComponentRBit | vk.ColorComponentGBit | vk.ColorComponentBBit | vk.ColorComponentABit, | |||
| 127 | + | } | |||
| 128 | + | blendState := vk.PipelineColorBlendStateCreateInfo{SType: vk.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &attachment} | |||
| 129 | + | format := vk.Format(vk.FormatB8g8r8a8Unorm) | |||
| 130 | + | rendering := vk.PipelineRenderingCreateInfo{SType: vk.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format} | |||
| 131 | + | gpi := vk.GraphicsPipelineCreateInfo{ | |||
| 132 | + | SType: vk.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rendering), | |||
| 133 | + | StageCount: 2, Stages: &stages[0], VertexInputState: &vertexInput, InputAssemblyState: &assembly, | |||
| 134 | + | ViewportState: &viewportState, RasterizationState: &raster, MultisampleState: &multisample, | |||
| 135 | + | ColorBlendState: &blendState, Layout: c.layout, BasePipelineIndex: -1, | |||
| 136 | + | } | |||
| 137 | + | return checked("vkCreateGraphicsPipelines", d.CreateGraphicsPipelines(r.device, 0, 1, &gpi, nil, &c.pipeline)) | |||
| 138 | + | } | |||
| 139 | + | ||||
| 140 | + | // createDummies makes the placeholder image (1×1, shader-read layout) and | |||
| 141 | + | // buffer, and the set fills bind. | |||
| 142 | + | func (r *Renderer) createDummies() error { | |||
| 143 | + | d, c := r.dd, &r.compose | |||
| 144 | + | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: vk.Extent3D{Width: 1, Height: 1, Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: vk.ImageUsageSampledBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | |||
| 145 | + | if err := checked("vkCreateImage(dummy)", d.CreateImage(r.device, &ii, nil, &c.dummyImage)); err != nil { | |||
| 146 | + | return err | |||
| 147 | + | } | |||
| 148 | + | var req vk.MemoryRequirements | |||
| 149 | + | d.GetImageMemoryRequirements(r.device, c.dummyImage, &req) | |||
| 150 | + | kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit) | |||
| 151 | + | if err != nil { | |||
| 152 | + | return err | |||
| 153 | + | } | |||
| 154 | + | alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind} | |||
| 155 | + | if err := checked("vkAllocateMemory(dummy)", d.AllocateMemory(r.device, &alloc, nil, &c.dummyMemory)); err != nil { | |||
| 156 | + | return err | |||
| 157 | + | } | |||
| 158 | + | if err := checked("vkBindImageMemory(dummy)", d.BindImageMemory(r.device, c.dummyImage, c.dummyMemory, 0)); err != nil { | |||
| 159 | + | return err | |||
| 160 | + | } | |||
| 161 | + | if c.dummyView, err = r.imageView(c.dummyImage, vk.FormatB8g8r8a8Unorm); err != nil { | |||
| 162 | + | return err | |||
| 163 | + | } | |||
| 164 | + | if err := r.newGPUBuffer(&c.dummyBuffer, 16); err != nil { | |||
| 165 | + | return err | |||
| 166 | + | } | |||
| 167 | + | if c.dummyPool, c.dummySet, err = r.newSet(c.dummyView, c.dummyBuffer.buffer); err != nil { | |||
| 168 | + | return err | |||
| 169 | + | } | |||
| 170 | + | // Move the placeholder image to the layout every draw declares. | |||
| 171 | + | return r.oneShot(func(cmd vk.CommandBuffer) { | |||
| 172 | + | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, OldLayout: vk.ImageLayoutUndefined, NewLayout: vk.ImageLayoutShaderReadOnlyOptimal, DstAccessMask: vk.AccessShaderReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: c.dummyImage, SubresourceRange: colorRange} | |||
| 173 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageFragmentShaderBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 174 | + | }) | |||
| 175 | + | } | |||
| 176 | + | ||||
| 177 | + | // newSet allocates a descriptor set from its own pool (the bindings lack | |||
| 178 | + | // vkFreeDescriptorSets: the pool is destroyed with the object). | |||
| 179 | + | func (r *Renderer) newSet(view vk.ImageView, buffer vk.Buffer) (vk.DescriptorPool, vk.DescriptorSet, error) { | |||
| 180 | + | d := r.dd | |||
| 181 | + | sizes := []vk.DescriptorPoolSize{{Type: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 1}, {Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: 1}} | |||
| 182 | + | dpi := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 2, PoolSizes: &sizes[0]} | |||
| 183 | + | var pool vk.DescriptorPool | |||
| 184 | + | if err := checked("vkCreateDescriptorPool", d.CreateDescriptorPool(r.device, &dpi, nil, &pool)); err != nil { | |||
| 185 | + | return 0, 0, err | |||
| 186 | + | } | |||
| 187 | + | var set vk.DescriptorSet | |||
| 188 | + | dai := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: pool, DescriptorSetCount: 1, SetLayouts: &r.compose.setLayout} | |||
| 189 | + | if err := checked("vkAllocateDescriptorSets", d.AllocateDescriptorSets(r.device, &dai, &set)); err != nil { | |||
| 190 | + | d.DestroyDescriptorPool(r.device, pool, nil) | |||
| 191 | + | return 0, 0, err | |||
| 192 | + | } | |||
| 193 | + | img := vk.DescriptorImageInfo{Sampler: r.compose.sampler, ImageView: view, ImageLayout: vk.ImageLayoutShaderReadOnlyOptimal} | |||
| 194 | + | buf := vk.DescriptorBufferInfo{Buffer: buffer, Range: wholeSize} | |||
| 195 | + | writes := []vk.WriteDescriptorSet{ | |||
| 196 | + | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: set, DstBinding: 0, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeCombinedImageSampler, ImageInfo: &img}, | |||
| 197 | + | {SType: vk.StructureTypeWriteDescriptorSet, DstSet: set, DstBinding: 1, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &buf}, | |||
| 198 | + | } | |||
| 199 | + | d.UpdateDescriptorSets(r.device, 2, &writes[0], 0, nil) | |||
| 200 | + | return pool, set, nil | |||
| 201 | + | } | |||
| 202 | + | ||||
| 203 | + | func (r *Renderer) destroyComposer() { | |||
| 204 | + | d, c := r.dd, &r.compose | |||
| 205 | + | if c.dummyPool != 0 { | |||
| 206 | + | d.DestroyDescriptorPool(r.device, c.dummyPool, nil) | |||
| 207 | + | } | |||
| 208 | + | r.freeGPUBuffer(&c.dummyBuffer) | |||
| 209 | + | if c.dummyView != 0 { | |||
| 210 | + | d.DestroyImageView(r.device, c.dummyView, nil) | |||
| 211 | + | } | |||
| 212 | + | if c.dummyImage != 0 { | |||
| 213 | + | d.DestroyImage(r.device, c.dummyImage, nil) | |||
| 214 | + | } | |||
| 215 | + | if c.dummyMemory != 0 { | |||
| 216 | + | d.FreeMemory(r.device, c.dummyMemory, nil) | |||
| 217 | + | } | |||
| 218 | + | if c.sampler != 0 { | |||
| 219 | + | d.DestroySampler(r.device, c.sampler, nil) | |||
| 220 | + | } | |||
| 221 | + | if c.pipeline != 0 { | |||
| 222 | + | d.DestroyPipeline(r.device, c.pipeline, nil) | |||
| 223 | + | } | |||
| 224 | + | if c.layout != 0 { | |||
| 225 | + | d.DestroyPipelineLayout(r.device, c.layout, nil) | |||
| 226 | + | } | |||
| 227 | + | if c.setLayout != 0 { | |||
| 228 | + | d.DestroyDescriptorSetLayout(r.device, c.setLayout, nil) | |||
| 229 | + | } | |||
| 230 | + | *c = composer{} | |||
| 231 | + | } | |||
| 232 | + | ||||
| 233 | + | func (r *Renderer) shaderModule(code []byte) (vk.ShaderModule, error) { | |||
| 234 | + | // SPIR-V is read as words: copy into a uint32-aligned slice. | |||
| 235 | + | words := make([]uint32, len(code)/4) | |||
| 236 | + | for i := range words { | |||
| 237 | + | words[i] = binary.LittleEndian.Uint32(code[i*4:]) | |||
| 238 | + | } | |||
| 239 | + | info := vk.ShaderModuleCreateInfo{SType: vk.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(words) * 4), Code: &words[0]} | |||
| 240 | + | var m vk.ShaderModule | |||
| 241 | + | return m, checked("vkCreateShaderModule", r.dd.CreateShaderModule(r.device, &info, nil, &m)) | |||
| 242 | + | } | |||
| 243 | + | ||||
| 244 | + | var colorRange = vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | |||
| 245 | + | ||||
| 246 | + | // imageView makes a 2D color view of an image. | |||
| 247 | + | func (r *Renderer) imageView(img vk.Image, format vk.Format) (vk.ImageView, error) { | |||
| 248 | + | info := vk.ImageViewCreateInfo{SType: vk.StructureTypeImageViewCreateInfo, Image: img, ViewType: vk.ImageViewType2d, Format: format, SubresourceRange: colorRange} | |||
| 249 | + | var v vk.ImageView | |||
| 250 | + | return v, checked("vkCreateImageView", r.dd.CreateImageView(r.device, &info, nil, &v)) | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | // createView makes the color attachment view of a target image. | |||
| 254 | + | func (r *Renderer) createView(t *target) (err error) { | |||
| 255 | + | t.view, err = r.imageView(t.image, vk.FormatB8g8r8a8Unorm) | |||
| 256 | + | return err | |||
| 257 | + | } | |||
| 258 | + | ||||
| 259 | + | // fillDraw is a solid rect of c. | |||
| 260 | + | func (r *Renderer) fillDraw(rect image.Rectangle, c [3]uint8) draw { | |||
| 261 | + | dr := draw{set: r.compose.dummySet} | |||
| 262 | + | dr.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)} | |||
| 263 | + | dr.pc.color = [4]float32{float32(c[0]) / 255, float32(c[1]) / 255, float32(c[2]) / 255, 1} | |||
| 264 | + | dr.pc.misc = [4]uint32{modeSolid, 0, uint32(r.width), uint32(r.height)} | |||
| 265 | + | return dr | |||
| 266 | + | } | |||
| 267 | + | ||||
| 268 | + | // contentDraw draws the visible rect of a w×h buffer mapped onto full | |||
| 269 | + | // (target pixels). exact: the buffer is drawn at its size. | |||
| 270 | + | func (r *Renderer) contentDraw(rect, full image.Rectangle, w, h int, mode uint32, opaque bool) draw { | |||
| 271 | + | var dr draw | |||
| 272 | + | sx, sy := float32(w)/float32(full.Dx()), float32(h)/float32(full.Dy()) | |||
| 273 | + | dr.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)} | |||
| 274 | + | dr.pc.mapv = [4]float32{-float32(full.Min.X) * sx, -float32(full.Min.Y) * sy, sx, sy} | |||
| 275 | + | flags := uint32(0) | |||
| 276 | + | if opaque { | |||
| 277 | + | flags |= flagOpaque | |||
| 278 | + | } | |||
| 279 | + | if full.Dx() == w && full.Dy() == h { | |||
| 280 | + | flags |= flagExact | |||
| 281 | + | } | |||
| 282 | + | dr.pc.misc = [4]uint32{mode, flags, uint32(r.width), uint32(r.height)} | |||
| 283 | + | return dr | |||
| 284 | + | } | |||
| 285 | + | ||||
| 286 | + | // recordDraws draws ds in one rendering pass that clears view to bg. | |||
| 287 | + | func (r *Renderer) recordDraws(cmd vk.CommandBuffer, view vk.ImageView, bg [3]uint8, ds []draw) { | |||
| 288 | + | d, c := r.dd, &r.compose | |||
| 289 | + | clear := vk.ClearValue{math.Float32bits(float32(bg[0]) / 255), math.Float32bits(float32(bg[1]) / 255), math.Float32bits(float32(bg[2]) / 255), math.Float32bits(1)} | |||
| 290 | + | attachment := vk.RenderingAttachmentInfo{SType: vk.StructureTypeRenderingAttachmentInfo, ImageView: view, ImageLayout: vk.ImageLayoutColorAttachmentOptimal, LoadOp: vk.AttachmentLoadOpClear, StoreOp: vk.AttachmentStoreOpStore, ClearValue: clear} | |||
| 291 | + | info := vk.RenderingInfo{SType: vk.StructureTypeRenderingInfo, RenderArea: vk.Rect2D{Extent: vk.Extent2D{Width: uint32(r.width), Height: uint32(r.height)}}, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment} | |||
| 292 | + | d.CmdBeginRendering(cmd, &info) | |||
| 293 | + | d.CmdBindPipeline(cmd, vk.PipelineBindPointGraphics, c.pipeline) | |||
| 294 | + | var bound vk.DescriptorSet | |||
| 295 | + | for i := range ds { | |||
| 296 | + | dr := &ds[i] | |||
| 297 | + | if dr.set != bound { | |||
| 298 | + | d.CmdBindDescriptorSets(cmd, vk.PipelineBindPointGraphics, c.layout, 0, 1, &dr.set, 0, nil) | |||
| 299 | + | bound = dr.set | |||
| 300 | + | } | |||
| 301 | + | d.CmdPushConstants(cmd, c.layout, vk.ShaderStageVertexBit|vk.ShaderStageFragmentBit, 0, uint32(unsafe.Sizeof(dr.pc)), unsafe.Pointer(&dr.pc)) | |||
| 302 | + | d.CmdDraw(cmd, 6, 1, 0, 0) | |||
| 303 | + | } | |||
| 304 | + | d.CmdEndRendering(cmd) | |||
| 305 | + | } | |||
A internal/adapters/vulkan/compose_test.go +138 -0
| @@ -0,0 +1,138 @@ | |||
| 1 | + | package vulkan | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "image/color" | |
| 6 | + | "slices" | |
| 7 | + | "testing" | |
| 8 | + | "time" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/nefertty/internal/adapters/syncfile" | |
| 11 | + | "github.com/bnema/nefertty/internal/ports" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // near reports whether two colors are within tol per channel. | |
| 15 | + | func near(a, b color.RGBA, tol int) bool { | |
| 16 | + | d := func(x, y uint8) bool { v := int(x) - int(y); return v >= -tol && v <= tol } | |
| 17 | + | return d(a.R, b.R) && d(a.G, b.G) && d(a.B, b.B) | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | // Scaled wl_shm pixels filter linearly: halfway between a black and a | |
| 21 | + | // white texel is grey, not either one (nearest neighbour). | |
| 22 | + | func TestRendererScaledSHMFiltersLinearly(t *testing.T) { | |
| 23 | + | r, err := New(64, 16) | |
| 24 | + | if err != nil { | |
| 25 | + | t.Skipf("Vulkan unavailable: %v", err) | |
| 26 | + | } | |
| 27 | + | defer r.Close() | |
| 28 | + | // 2×1 buffer: black, white; drawn 64×16, texel centres at x=16, x=48. | |
| 29 | + | px := []byte{0, 0, 0, 255, 255, 255, 255, 255} | |
| 30 | + | c := shmContent(t, 2, 1, 8, px) | |
| 31 | + | c.LogicalW, c.LogicalH = 64, 16 | |
| 32 | + | scene := ports.Scene{Background: "#ff0000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 16}, Borderless: true}}} | |
| 33 | + | if _, err := r.Render(scene, map[ports.WindowID]ports.SurfaceContent{1: *c}); err != nil { | |
| 34 | + | t.Fatal(err) | |
| 35 | + | } | |
| 36 | + | got := r.Pixels().RGBAAt(32, 8) | |
| 37 | + | if !near(got, color.RGBA{128, 128, 128, 255}, 8) { | |
| 38 | + | t.Fatalf("midpoint %v, want grey", got) | |
| 39 | + | } | |
| 40 | + | if got := r.Pixels().RGBAAt(2, 8); !near(got, color.RGBA{0, 0, 0, 255}, 2) { | |
| 41 | + | t.Fatalf("left edge %v, want black (clamped)", got) | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | // Scaled dmabufs are sampled with the linear sampler. | |
| 46 | + | func TestRendererScaledDMABufFiltersLinearly(t *testing.T) { | |
| 47 | + | r, err := New(128, 4) | |
| 48 | + | if err != nil { | |
| 49 | + | t.Skipf("Vulkan unavailable: %v", err) | |
| 50 | + | } | |
| 51 | + | defer r.Close() | |
| 52 | + | linear := ports.DMABufFormat{Format: fourcc('A', 'R', '2', '4'), Modifier: 0} | |
| 53 | + | if !slices.Contains(r.DMABuf().Formats, linear) { | |
| 54 | + | t.Skipf("linear ARGB8888 not importable") | |
| 55 | + | } | |
| 56 | + | // 64×1: left half black, right half white, drawn twice as wide. | |
| 57 | + | f := udmabuf(t, 64, 1, func(x, _ int) [4]byte { | |
| 58 | + | if x < 32 { | |
| 59 | + | return [4]byte{0, 0, 0, 255} | |
| 60 | + | } | |
| 61 | + | return [4]byte{255, 255, 255, 255} | |
| 62 | + | }) | |
| 63 | + | buf := &ports.DMABuf{ID: 3, Width: 64, Height: 1, Format: linear.Format, Planes: []ports.DMABufPlane{{File: f, Stride: 256}}} | |
| 64 | + | scene := ports.Scene{Background: "#ff0000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 128, H: 4}, Borderless: true}}} | |
| 65 | + | c := ports.SurfaceContent{ID: 1, Width: 64, Height: 1, LogicalW: 128, LogicalH: 4, DMABuf: buf} | |
| 66 | + | if _, err := r.Render(scene, map[ports.WindowID]ports.SurfaceContent{1: c}); err != nil { | |
| 67 | + | t.Fatal(err) | |
| 68 | + | } | |
| 69 | + | // Target x=64 (centre 64.5) maps to buffer 32.25: between texel 31 | |
| 70 | + | // (black, centre 31.5) and 32 (white, centre 32.5), 75% white. | |
| 71 | + | if got := r.Pixels().RGBAAt(64, 2); !near(got, color.RGBA{191, 191, 191, 255}, 10) { | |
| 72 | + | t.Fatalf("boundary %v, want a linear mix", got) | |
| 73 | + | } | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | // Render returns a sync file that becomes readable when the GPU is done. | |
| 77 | + | func TestRendererReturnsDoneFence(t *testing.T) { | |
| 78 | + | r, err := New(32, 32) | |
| 79 | + | if err != nil { | |
| 80 | + | t.Skipf("Vulkan unavailable: %v", err) | |
| 81 | + | } | |
| 82 | + | defer r.Close() | |
| 83 | + | if !r.syncFD { | |
| 84 | + | t.Skip("device cannot export sync files") | |
| 85 | + | } | |
| 86 | + | done, err := r.Render(ports.Scene{Background: "#102030"}, nil) | |
| 87 | + | if err != nil { | |
| 88 | + | t.Fatal(err) | |
| 89 | + | } | |
| 90 | + | if done == nil { | |
| 91 | + | t.Fatal("no done fence") | |
| 92 | + | } | |
| 93 | + | defer done.Close() | |
| 94 | + | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | |
| 95 | + | defer cancel() | |
| 96 | + | if err := syncfile.Wait(ctx, done); err != nil { | |
| 97 | + | t.Fatal(err) | |
| 98 | + | } | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | // An unchanged wl_shm window is not copied again; a new content is. | |
| 102 | + | func TestRendererSHMCopiedOncePerContent(t *testing.T) { | |
| 103 | + | r, err := New(32, 32) | |
| 104 | + | if err != nil { | |
| 105 | + | t.Skipf("Vulkan unavailable: %v", err) | |
| 106 | + | } | |
| 107 | + | defer r.Close() | |
| 108 | + | c := solidContent(t, 16, 16, color.RGBA{1, 2, 3, 255}) | |
| 109 | + | c.ID, c.Seq = 1, 1 | |
| 110 | + | scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 16, H: 16}, Borderless: true}}} | |
| 111 | + | contents := map[ports.WindowID]ports.SurfaceContent{1: c} | |
| 112 | + | render := func() { | |
| 113 | + | t.Helper() | |
| 114 | + | done, err := r.Render(scene, contents) | |
| 115 | + | if done != nil { | |
| 116 | + | done.Close() | |
| 117 | + | } | |
| 118 | + | if err != nil { | |
| 119 | + | t.Fatal(err) | |
| 120 | + | } | |
| 121 | + | } | |
| 122 | + | render() | |
| 123 | + | if r.copied != 16*16*4 { | |
| 124 | + | t.Fatalf("first frame copied %d", r.copied) | |
| 125 | + | } | |
| 126 | + | for range 3 { | |
| 127 | + | render() | |
| 128 | + | } | |
| 129 | + | if r.copied != 16*16*4 { | |
| 130 | + | t.Fatalf("unchanged content copied again: %d", r.copied) | |
| 131 | + | } | |
| 132 | + | c.Seq = 2 | |
| 133 | + | contents[1] = c | |
| 134 | + | render() | |
| 135 | + | if r.copied != 2*16*16*4 { | |
| 136 | + | t.Fatalf("new content copied %d", r.copied) | |
| 137 | + | } | |
| 138 | + | } | |
| @@ -0,0 +1,138 @@ | |||||
| 1 | + | package vulkan | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "image/color" | |||
| 6 | + | "slices" | |||
| 7 | + | "testing" | |||
| 8 | + | "time" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/nefertty/internal/adapters/syncfile" | |||
| 11 | + | "github.com/bnema/nefertty/internal/ports" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // near reports whether two colors are within tol per channel. | |||
| 15 | + | func near(a, b color.RGBA, tol int) bool { | |||
| 16 | + | d := func(x, y uint8) bool { v := int(x) - int(y); return v >= -tol && v <= tol } | |||
| 17 | + | return d(a.R, b.R) && d(a.G, b.G) && d(a.B, b.B) | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | // Scaled wl_shm pixels filter linearly: halfway between a black and a | |||
| 21 | + | // white texel is grey, not either one (nearest neighbour). | |||
| 22 | + | func TestRendererScaledSHMFiltersLinearly(t *testing.T) { | |||
| 23 | + | r, err := New(64, 16) | |||
| 24 | + | if err != nil { | |||
| 25 | + | t.Skipf("Vulkan unavailable: %v", err) | |||
| 26 | + | } | |||
| 27 | + | defer r.Close() | |||
| 28 | + | // 2×1 buffer: black, white; drawn 64×16, texel centres at x=16, x=48. | |||
| 29 | + | px := []byte{0, 0, 0, 255, 255, 255, 255, 255} | |||
| 30 | + | c := shmContent(t, 2, 1, 8, px) | |||
| 31 | + | c.LogicalW, c.LogicalH = 64, 16 | |||
| 32 | + | scene := ports.Scene{Background: "#ff0000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 16}, Borderless: true}}} | |||
| 33 | + | if _, err := r.Render(scene, map[ports.WindowID]ports.SurfaceContent{1: *c}); err != nil { | |||
| 34 | + | t.Fatal(err) | |||
| 35 | + | } | |||
| 36 | + | got := r.Pixels().RGBAAt(32, 8) | |||
| 37 | + | if !near(got, color.RGBA{128, 128, 128, 255}, 8) { | |||
| 38 | + | t.Fatalf("midpoint %v, want grey", got) | |||
| 39 | + | } | |||
| 40 | + | if got := r.Pixels().RGBAAt(2, 8); !near(got, color.RGBA{0, 0, 0, 255}, 2) { | |||
| 41 | + | t.Fatalf("left edge %v, want black (clamped)", got) | |||
| 42 | + | } | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | // Scaled dmabufs are sampled with the linear sampler. | |||
| 46 | + | func TestRendererScaledDMABufFiltersLinearly(t *testing.T) { | |||
| 47 | + | r, err := New(128, 4) | |||
| 48 | + | if err != nil { | |||
| 49 | + | t.Skipf("Vulkan unavailable: %v", err) | |||
| 50 | + | } | |||
| 51 | + | defer r.Close() | |||
| 52 | + | linear := ports.DMABufFormat{Format: fourcc('A', 'R', '2', '4'), Modifier: 0} | |||
| 53 | + | if !slices.Contains(r.DMABuf().Formats, linear) { | |||
| 54 | + | t.Skipf("linear ARGB8888 not importable") | |||
| 55 | + | } | |||
| 56 | + | // 64×1: left half black, right half white, drawn twice as wide. | |||
| 57 | + | f := udmabuf(t, 64, 1, func(x, _ int) [4]byte { | |||
| 58 | + | if x < 32 { | |||
| 59 | + | return [4]byte{0, 0, 0, 255} | |||
| 60 | + | } | |||
| 61 | + | return [4]byte{255, 255, 255, 255} | |||
| 62 | + | }) | |||
| 63 | + | buf := &ports.DMABuf{ID: 3, Width: 64, Height: 1, Format: linear.Format, Planes: []ports.DMABufPlane{{File: f, Stride: 256}}} | |||
| 64 | + | scene := ports.Scene{Background: "#ff0000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 128, H: 4}, Borderless: true}}} | |||
| 65 | + | c := ports.SurfaceContent{ID: 1, Width: 64, Height: 1, LogicalW: 128, LogicalH: 4, DMABuf: buf} | |||
| 66 | + | if _, err := r.Render(scene, map[ports.WindowID]ports.SurfaceContent{1: c}); err != nil { | |||
| 67 | + | t.Fatal(err) | |||
| 68 | + | } | |||
| 69 | + | // Target x=64 (centre 64.5) maps to buffer 32.25: between texel 31 | |||
| 70 | + | // (black, centre 31.5) and 32 (white, centre 32.5), 75% white. | |||
| 71 | + | if got := r.Pixels().RGBAAt(64, 2); !near(got, color.RGBA{191, 191, 191, 255}, 10) { | |||
| 72 | + | t.Fatalf("boundary %v, want a linear mix", got) | |||
| 73 | + | } | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | // Render returns a sync file that becomes readable when the GPU is done. | |||
| 77 | + | func TestRendererReturnsDoneFence(t *testing.T) { | |||
| 78 | + | r, err := New(32, 32) | |||
| 79 | + | if err != nil { | |||
| 80 | + | t.Skipf("Vulkan unavailable: %v", err) | |||
| 81 | + | } | |||
| 82 | + | defer r.Close() | |||
| 83 | + | if !r.syncFD { | |||
| 84 | + | t.Skip("device cannot export sync files") | |||
| 85 | + | } | |||
| 86 | + | done, err := r.Render(ports.Scene{Background: "#102030"}, nil) | |||
| 87 | + | if err != nil { | |||
| 88 | + | t.Fatal(err) | |||
| 89 | + | } | |||
| 90 | + | if done == nil { | |||
| 91 | + | t.Fatal("no done fence") | |||
| 92 | + | } | |||
| 93 | + | defer done.Close() | |||
| 94 | + | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | |||
| 95 | + | defer cancel() | |||
| 96 | + | if err := syncfile.Wait(ctx, done); err != nil { | |||
| 97 | + | t.Fatal(err) | |||
| 98 | + | } | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | // An unchanged wl_shm window is not copied again; a new content is. | |||
| 102 | + | func TestRendererSHMCopiedOncePerContent(t *testing.T) { | |||
| 103 | + | r, err := New(32, 32) | |||
| 104 | + | if err != nil { | |||
| 105 | + | t.Skipf("Vulkan unavailable: %v", err) | |||
| 106 | + | } | |||
| 107 | + | defer r.Close() | |||
| 108 | + | c := solidContent(t, 16, 16, color.RGBA{1, 2, 3, 255}) | |||
| 109 | + | c.ID, c.Seq = 1, 1 | |||
| 110 | + | scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 16, H: 16}, Borderless: true}}} | |||
| 111 | + | contents := map[ports.WindowID]ports.SurfaceContent{1: c} | |||
| 112 | + | render := func() { | |||
| 113 | + | t.Helper() | |||
| 114 | + | done, err := r.Render(scene, contents) | |||
| 115 | + | if done != nil { | |||
| 116 | + | done.Close() | |||
| 117 | + | } | |||
| 118 | + | if err != nil { | |||
| 119 | + | t.Fatal(err) | |||
| 120 | + | } | |||
| 121 | + | } | |||
| 122 | + | render() | |||
| 123 | + | if r.copied != 16*16*4 { | |||
| 124 | + | t.Fatalf("first frame copied %d", r.copied) | |||
| 125 | + | } | |||
| 126 | + | for range 3 { | |||
| 127 | + | render() | |||
| 128 | + | } | |||
| 129 | + | if r.copied != 16*16*4 { | |||
| 130 | + | t.Fatalf("unchanged content copied again: %d", r.copied) | |||
| 131 | + | } | |||
| 132 | + | c.Seq = 2 | |||
| 133 | + | contents[1] = c | |||
| 134 | + | render() | |||
| 135 | + | if r.copied != 2*16*16*4 { | |||
| 136 | + | t.Fatalf("new content copied %d", r.copied) | |||
| 137 | + | } | |||
| 138 | + | } | |||
M internal/adapters/vulkan/cursor.go +8 -4
| @@ -29,7 +29,7 @@ const fourccARGB = 'A' | 'R'<<8 | '2'<<16 | '4'<<24 | |||
| 29 | 29 | func (r *Renderer) CursorBuffers(size int) ([2]ports.DMABuf, error) { | |
| 30 | 30 | var out [2]ports.DMABuf | |
| 31 | 31 | r.dropCursors() | |
| 32 | - | if r.dd.GetMemoryFdKHR == nil || size <= 0 || size > 512 { | |
| 32 | + | if r.dd.GetMemoryFdKHR == nil || len(r.dmabuf.Formats) == 0 || size <= 0 || size > 512 { | |
| 33 | 33 | return out, errors.New("device cannot export cursor images") | |
| 34 | 34 | } | |
| 35 | 35 | for i := range r.cursors { | |
| @@ -55,8 +55,12 @@ func (r *Renderer) cursorImage(size int) (*cursorImage, ports.DMABuf, error) { | |||
| 55 | 55 | r.freeCursor(c) | |
| 56 | 56 | } | |
| 57 | 57 | }() | |
| 58 | - | external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | |
| 59 | - | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: vk.Extent3D{Width: uint32(size), Height: uint32(size), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingLinear, Usage: vk.ImageUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutPreinitialized} | |
| 58 | + | // Exported images take an explicit DRM modifier: linear, as cursor | |
| 59 | + | // planes scan out. The GPU never touches them (the CPU writes, KMS reads). | |
| 60 | + | linear := uint64(0) | |
| 61 | + | list := vk.ImageDrmFormatModifierListCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierListCreateInfoEXT, DrmFormatModifierCount: 1, DrmFormatModifiers: &linear} | |
| 62 | + | external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&list), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | |
| 63 | + | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: vk.Extent3D{Width: uint32(size), Height: uint32(size), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | |
| 60 | 64 | if err := checked("vkCreateImage(cursor)", d.CreateImage(r.device, &ii, nil, &c.image)); err != nil { | |
| 61 | 65 | return nil, ports.DMABuf{}, err | |
| 62 | 66 | } | |
| @@ -76,7 +80,7 @@ func (r *Renderer) cursorImage(size int) (*cursorImage, ports.DMABuf, error) { | |||
| 76 | 80 | return nil, ports.DMABuf{}, err | |
| 77 | 81 | } | |
| 78 | 82 | var layout vk.SubresourceLayout | |
| 79 | - | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectColorBit} | |
| 83 | + | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT} | |
| 80 | 84 | d.GetImageSubresourceLayout(r.device, c.image, &sub, &layout) | |
| 81 | 85 | c.pitch = int(layout.RowPitch) | |
| 82 | 86 | // Cursor planes scan out packed rows from the start of the buffer. | |
| @@ -29,7 +29,7 @@ const fourccARGB = 'A' | 'R'<<8 | '2'<<16 | '4'<<24 | |||||
| 29 | func (r *Renderer) CursorBuffers(size int) ([2]ports.DMABuf, error) { | 29 | func (r *Renderer) CursorBuffers(size int) ([2]ports.DMABuf, error) { | ||
| 30 | var out [2]ports.DMABuf | 30 | var out [2]ports.DMABuf | ||
| 31 | r.dropCursors() | 31 | r.dropCursors() | ||
| 32 | - | if r.dd.GetMemoryFdKHR == nil || size <= 0 || size > 512 { | 32 | + | if r.dd.GetMemoryFdKHR == nil || len(r.dmabuf.Formats) == 0 || size <= 0 || size > 512 { |
| 33 | return out, errors.New("device cannot export cursor images") | 33 | return out, errors.New("device cannot export cursor images") | ||
| 34 | } | 34 | } | ||
| 35 | for i := range r.cursors { | 35 | for i := range r.cursors { | ||
| @@ -55,8 +55,12 @@ func (r *Renderer) cursorImage(size int) (*cursorImage, ports.DMABuf, error) { | |||||
| 55 | r.freeCursor(c) | 55 | r.freeCursor(c) | ||
| 56 | } | 56 | } | ||
| 57 | }() | 57 | }() | ||
| 58 | - | external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | 58 | + | // Exported images take an explicit DRM modifier: linear, as cursor |
| 59 | - | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: vk.Extent3D{Width: uint32(size), Height: uint32(size), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingLinear, Usage: vk.ImageUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutPreinitialized} | 59 | + | // planes scan out. The GPU never touches them (the CPU writes, KMS reads). |
| 60 | + | linear := uint64(0) | |||
| 61 | + | list := vk.ImageDrmFormatModifierListCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierListCreateInfoEXT, DrmFormatModifierCount: 1, DrmFormatModifiers: &linear} | |||
| 62 | + | external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&list), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | |||
| 63 | + | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: vk.Extent3D{Width: uint32(size), Height: uint32(size), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | |||
| 60 | if err := checked("vkCreateImage(cursor)", d.CreateImage(r.device, &ii, nil, &c.image)); err != nil { | 64 | if err := checked("vkCreateImage(cursor)", d.CreateImage(r.device, &ii, nil, &c.image)); err != nil { | ||
| 61 | return nil, ports.DMABuf{}, err | 65 | return nil, ports.DMABuf{}, err | ||
| 62 | } | 66 | } | ||
| @@ -76,7 +80,7 @@ func (r *Renderer) cursorImage(size int) (*cursorImage, ports.DMABuf, error) { | |||||
| 76 | return nil, ports.DMABuf{}, err | 80 | return nil, ports.DMABuf{}, err | ||
| 77 | } | 81 | } | ||
| 78 | var layout vk.SubresourceLayout | 82 | var layout vk.SubresourceLayout | ||
| 79 | - | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectColorBit} | 83 | + | sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT} |
| 80 | d.GetImageSubresourceLayout(r.device, c.image, &sub, &layout) | 84 | d.GetImageSubresourceLayout(r.device, c.image, &sub, &layout) | ||
| 81 | c.pitch = int(layout.RowPitch) | 85 | c.pitch = int(layout.RowPitch) | ||
| 82 | // Cursor planes scan out packed rows from the start of the buffer. | 86 | // Cursor planes scan out packed rows from the start of the buffer. | ||
M internal/adapters/vulkan/dmabuf.go +28 -11
| @@ -13,7 +13,7 @@ import ( | |||
| 13 | 13 | ||
| 14 | 14 | // dmabuf import (linux-dmabuf clients): each client buffer becomes a | |
| 15 | 15 | // VkImage bound to its imported memory, cached by buffer ID while the | |
| 16 | - | // client keeps attaching it, and blitted into the output image. Sync is | |
| 16 | + | // client keeps attaching it, and sampled by the compose shader. Sync is | |
| 17 | 17 | // implicit: before sampling, the renderer waits on the fences the client's | |
| 18 | 18 | // driver attached to the buffer (DMA_BUF_IOCTL_EXPORT_SYNC_FILE), imported | |
| 19 | 19 | // as a semaphore. | |
| @@ -86,7 +86,7 @@ func cstring(b []byte) string { | |||
| 86 | 86 | } | |
| 87 | 87 | ||
| 88 | 88 | // probeDMABuf lists the fourcc/modifier pairs the device imports as | |
| 89 | - | // single-plane blit sources, and the render node. | |
| 89 | + | // single-plane sampled images with linear filtering, and the render node. | |
| 90 | 90 | func (r *Renderer) probeDMABuf(physical vk.PhysicalDevice) ports.DMABufSupport { | |
| 91 | 91 | var sup ports.DMABufSupport | |
| 92 | 92 | drm := vk.PhysicalDeviceDrmPropertiesEXT{SType: vk.StructureTypePhysicalDeviceDRMPropertiesEXT} | |
| @@ -107,8 +107,9 @@ func (r *Renderer) probeDMABuf(physical vk.PhysicalDevice) ports.DMABufSupport { | |||
| 107 | 107 | r.id.GetPhysicalDeviceFormatProperties2(physical, f.format, &fp) | |
| 108 | 108 | for _, m := range mods[:list.DrmFormatModifierCount] { | |
| 109 | 109 | // Multi-plane modifiers (compression metadata) are left to a | |
| 110 | - | // later step; the blit needs a source. | |
| 111 | - | if m.DrmFormatModifierPlaneCount != 1 || m.DrmFormatModifierTilingFeatures&vk.FormatFeatureBlitSrcBit == 0 { | |
| 110 | + | // later step. | |
| 111 | + | need := vk.FormatFeatureFlags(vk.FormatFeatureSampledImageBit | vk.FormatFeatureSampledImageFilterLinearBit) | |
| 112 | + | if m.DrmFormatModifierPlaneCount != 1 || m.DrmFormatModifierTilingFeatures&need != need { | |
| 112 | 113 | continue | |
| 113 | 114 | } | |
| 114 | 115 | if r.importable(physical, f.format, m.DrmFormatModifier) { | |
| @@ -124,7 +125,7 @@ func (r *Renderer) probeDMABuf(physical vk.PhysicalDevice) ports.DMABufSupport { | |||
| 124 | 125 | func (r *Renderer) importable(physical vk.PhysicalDevice, format vk.Format, modifier uint64) bool { | |
| 125 | 126 | mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive} | |
| 126 | 127 | ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | |
| 127 | - | info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Format: format, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageTransferSrcBit} | |
| 128 | + | info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Format: format, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageSampledBit} | |
| 128 | 129 | extOut := vk.ExternalImageFormatProperties{SType: vk.StructureTypeExternalImageFormatProperties} | |
| 129 | 130 | out := vk.ImageFormatProperties2{SType: vk.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&extOut)} | |
| 130 | 131 | if r.id.GetPhysicalDeviceImageFormatProperties2(physical, &info, &out) != vk.Success { | |
| @@ -136,10 +137,14 @@ func (r *Renderer) importable(physical vk.PhysicalDevice, format vk.Format, modi | |||
| 136 | 137 | // DMABuf reports the formats this renderer imports. | |
| 137 | 138 | func (r *Renderer) DMABuf() ports.DMABufSupport { return r.dmabuf } | |
| 138 | 139 | ||
| 139 | - | // imported is a client dmabuf bound to a VkImage. | |
| 140 | + | // imported is a client dmabuf bound to a VkImage, with the view and set | |
| 141 | + | // the compose shader samples it through. | |
| 140 | 142 | type imported struct { | |
| 141 | 143 | image vk.Image | |
| 142 | 144 | memory vk.DeviceMemory | |
| 145 | + | view vk.ImageView | |
| 146 | + | pool vk.DescriptorPool | |
| 147 | + | set vk.DescriptorSet | |
| 143 | 148 | fd int // our duplicate of plane 0, for implicit sync | |
| 144 | 149 | // last is the frame that drew it, for eviction. | |
| 145 | 150 | last uint64 | |
| @@ -177,7 +182,7 @@ func (r *Renderer) importDMABuf(b *ports.DMABuf) (*imported, error) { | |||
| 177 | 182 | layout := vk.SubresourceLayout{Offset: vk.DeviceSize(b.Planes[0].Offset), RowPitch: vk.DeviceSize(b.Planes[0].Stride)} | |
| 178 | 183 | explicit := vk.ImageDrmFormatModifierExplicitCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierExplicitCreateInfoEXT, DrmFormatModifier: b.Modifier, DrmFormatModifierPlaneCount: 1, PlaneLayouts: &layout} | |
| 179 | 184 | external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&explicit), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | |
| 180 | - | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: format, Extent: vk.Extent3D{Width: uint32(b.Width), Height: uint32(b.Height), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | |
| 185 | + | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: format, Extent: vk.Extent3D{Width: uint32(b.Width), Height: uint32(b.Height), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageSampledBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | |
| 181 | 186 | if err := checked("vkCreateImage(dmabuf)", d.CreateImage(r.device, &ii, nil, &im.image)); err != nil { | |
| 182 | 187 | return nil, err | |
| 183 | 188 | } | |
| @@ -206,6 +211,12 @@ func (r *Renderer) importDMABuf(b *ports.DMABuf) (*imported, error) { | |||
| 206 | 211 | if err := checked("vkBindImageMemory(dmabuf)", d.BindImageMemory(r.device, im.image, im.memory, 0)); err != nil { | |
| 207 | 212 | return nil, err | |
| 208 | 213 | } | |
| 214 | + | if im.view, err = r.imageView(im.image, format); err != nil { | |
| 215 | + | return nil, err | |
| 216 | + | } | |
| 217 | + | if im.pool, im.set, err = r.newSet(im.view, r.compose.dummyBuffer.buffer); err != nil { | |
| 218 | + | return nil, err | |
| 219 | + | } | |
| 209 | 220 | ok = true | |
| 210 | 221 | r.imports[b.ID] = im | |
| 211 | 222 | return im, nil | |
| @@ -229,6 +240,12 @@ func dupFile(f *os.File) (int, error) { | |||
| 229 | 240 | } | |
| 230 | 241 | ||
| 231 | 242 | func (r *Renderer) release(im *imported) { | |
| 243 | + | if im.pool != 0 { | |
| 244 | + | r.dd.DestroyDescriptorPool(r.device, im.pool, nil) | |
| 245 | + | } | |
| 246 | + | if im.view != 0 { | |
| 247 | + | r.dd.DestroyImageView(r.device, im.view, nil) | |
| 248 | + | } | |
| 232 | 249 | if im.image != 0 { | |
| 233 | 250 | r.dd.DestroyImage(r.device, im.image, nil) | |
| 234 | 251 | } | |
| @@ -240,13 +257,13 @@ func (r *Renderer) release(im *imported) { | |||
| 240 | 257 | } | |
| 241 | 258 | } | |
| 242 | 259 | ||
| 243 | - | // dropUnused frees imports not drawn for importTTL frames: the client moved | |
| 244 | - | // on to other buffers or went away. Called after a frame completes. | |
| 260 | + | // dropUnused frees imports not drawn for importTTL frames (the client | |
| 261 | + | // moved on to other buffers or went away), once the last frame that drew | |
| 262 | + | // them completed. | |
| 245 | 263 | func (r *Renderer) dropUnused() { | |
| 246 | - | r.frame++ | |
| 247 | 264 | for id, im := range r.imports { | |
| 248 | 265 | if r.frame-im.last > importTTL { | |
| 249 | - | r.release(im) | |
| 266 | + | r.retire(im.last, func() { r.release(im) }) | |
| 250 | 267 | delete(r.imports, id) | |
| 251 | 268 | } | |
| 252 | 269 | } | |
| @@ -13,7 +13,7 @@ import ( | |||||
| 13 | 13 | ||||
| 14 | // dmabuf import (linux-dmabuf clients): each client buffer becomes a | 14 | // dmabuf import (linux-dmabuf clients): each client buffer becomes a | ||
| 15 | // VkImage bound to its imported memory, cached by buffer ID while the | 15 | // VkImage bound to its imported memory, cached by buffer ID while the | ||
| 16 | - | // client keeps attaching it, and blitted into the output image. Sync is | 16 | + | // client keeps attaching it, and sampled by the compose shader. Sync is |
| 17 | // implicit: before sampling, the renderer waits on the fences the client's | 17 | // implicit: before sampling, the renderer waits on the fences the client's | ||
| 18 | // driver attached to the buffer (DMA_BUF_IOCTL_EXPORT_SYNC_FILE), imported | 18 | // driver attached to the buffer (DMA_BUF_IOCTL_EXPORT_SYNC_FILE), imported | ||
| 19 | // as a semaphore. | 19 | // as a semaphore. | ||
| @@ -86,7 +86,7 @@ func cstring(b []byte) string { | |||||
| 86 | } | 86 | } | ||
| 87 | 87 | ||||
| 88 | // probeDMABuf lists the fourcc/modifier pairs the device imports as | 88 | // probeDMABuf lists the fourcc/modifier pairs the device imports as | ||
| 89 | - | // single-plane blit sources, and the render node. | 89 | + | // single-plane sampled images with linear filtering, and the render node. |
| 90 | func (r *Renderer) probeDMABuf(physical vk.PhysicalDevice) ports.DMABufSupport { | 90 | func (r *Renderer) probeDMABuf(physical vk.PhysicalDevice) ports.DMABufSupport { | ||
| 91 | var sup ports.DMABufSupport | 91 | var sup ports.DMABufSupport | ||
| 92 | drm := vk.PhysicalDeviceDrmPropertiesEXT{SType: vk.StructureTypePhysicalDeviceDRMPropertiesEXT} | 92 | drm := vk.PhysicalDeviceDrmPropertiesEXT{SType: vk.StructureTypePhysicalDeviceDRMPropertiesEXT} | ||
| @@ -107,8 +107,9 @@ func (r *Renderer) probeDMABuf(physical vk.PhysicalDevice) ports.DMABufSupport { | |||||
| 107 | r.id.GetPhysicalDeviceFormatProperties2(physical, f.format, &fp) | 107 | r.id.GetPhysicalDeviceFormatProperties2(physical, f.format, &fp) | ||
| 108 | for _, m := range mods[:list.DrmFormatModifierCount] { | 108 | for _, m := range mods[:list.DrmFormatModifierCount] { | ||
| 109 | // Multi-plane modifiers (compression metadata) are left to a | 109 | // Multi-plane modifiers (compression metadata) are left to a | ||
| 110 | - | // later step; the blit needs a source. | 110 | + | // later step. |
| 111 | - | if m.DrmFormatModifierPlaneCount != 1 || m.DrmFormatModifierTilingFeatures&vk.FormatFeatureBlitSrcBit == 0 { | 111 | + | need := vk.FormatFeatureFlags(vk.FormatFeatureSampledImageBit | vk.FormatFeatureSampledImageFilterLinearBit) |
| 112 | + | if m.DrmFormatModifierPlaneCount != 1 || m.DrmFormatModifierTilingFeatures&need != need { | |||
| 112 | continue | 113 | continue | ||
| 113 | } | 114 | } | ||
| 114 | if r.importable(physical, f.format, m.DrmFormatModifier) { | 115 | if r.importable(physical, f.format, m.DrmFormatModifier) { | ||
| @@ -124,7 +125,7 @@ func (r *Renderer) probeDMABuf(physical vk.PhysicalDevice) ports.DMABufSupport { | |||||
| 124 | func (r *Renderer) importable(physical vk.PhysicalDevice, format vk.Format, modifier uint64) bool { | 125 | func (r *Renderer) importable(physical vk.PhysicalDevice, format vk.Format, modifier uint64) bool { | ||
| 125 | mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive} | 126 | mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive} | ||
| 126 | ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | 127 | ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | ||
| 127 | - | info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Format: format, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageTransferSrcBit} | 128 | + | info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Format: format, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageSampledBit} |
| 128 | extOut := vk.ExternalImageFormatProperties{SType: vk.StructureTypeExternalImageFormatProperties} | 129 | extOut := vk.ExternalImageFormatProperties{SType: vk.StructureTypeExternalImageFormatProperties} | ||
| 129 | out := vk.ImageFormatProperties2{SType: vk.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&extOut)} | 130 | out := vk.ImageFormatProperties2{SType: vk.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&extOut)} | ||
| 130 | if r.id.GetPhysicalDeviceImageFormatProperties2(physical, &info, &out) != vk.Success { | 131 | if r.id.GetPhysicalDeviceImageFormatProperties2(physical, &info, &out) != vk.Success { | ||
| @@ -136,10 +137,14 @@ func (r *Renderer) importable(physical vk.PhysicalDevice, format vk.Format, modi | |||||
| 136 | // DMABuf reports the formats this renderer imports. | 137 | // DMABuf reports the formats this renderer imports. | ||
| 137 | func (r *Renderer) DMABuf() ports.DMABufSupport { return r.dmabuf } | 138 | func (r *Renderer) DMABuf() ports.DMABufSupport { return r.dmabuf } | ||
| 138 | 139 | ||||
| 139 | - | // imported is a client dmabuf bound to a VkImage. | 140 | + | // imported is a client dmabuf bound to a VkImage, with the view and set |
| 141 | + | // the compose shader samples it through. | |||
| 140 | type imported struct { | 142 | type imported struct { | ||
| 141 | image vk.Image | 143 | image vk.Image | ||
| 142 | memory vk.DeviceMemory | 144 | memory vk.DeviceMemory | ||
| 145 | + | view vk.ImageView | |||
| 146 | + | pool vk.DescriptorPool | |||
| 147 | + | set vk.DescriptorSet | |||
| 143 | fd int // our duplicate of plane 0, for implicit sync | 148 | fd int // our duplicate of plane 0, for implicit sync | ||
| 144 | // last is the frame that drew it, for eviction. | 149 | // last is the frame that drew it, for eviction. | ||
| 145 | last uint64 | 150 | last uint64 | ||
| @@ -177,7 +182,7 @@ func (r *Renderer) importDMABuf(b *ports.DMABuf) (*imported, error) { | |||||
| 177 | layout := vk.SubresourceLayout{Offset: vk.DeviceSize(b.Planes[0].Offset), RowPitch: vk.DeviceSize(b.Planes[0].Stride)} | 182 | layout := vk.SubresourceLayout{Offset: vk.DeviceSize(b.Planes[0].Offset), RowPitch: vk.DeviceSize(b.Planes[0].Stride)} | ||
| 178 | explicit := vk.ImageDrmFormatModifierExplicitCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierExplicitCreateInfoEXT, DrmFormatModifier: b.Modifier, DrmFormatModifierPlaneCount: 1, PlaneLayouts: &layout} | 183 | explicit := vk.ImageDrmFormatModifierExplicitCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierExplicitCreateInfoEXT, DrmFormatModifier: b.Modifier, DrmFormatModifierPlaneCount: 1, PlaneLayouts: &layout} | ||
| 179 | external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&explicit), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | 184 | external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&explicit), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT} | ||
| 180 | - | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: format, Extent: vk.Extent3D{Width: uint32(b.Width), Height: uint32(b.Height), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} | 185 | + | ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: format, Extent: vk.Extent3D{Width: uint32(b.Width), Height: uint32(b.Height), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageSampledBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined} |
| 181 | if err := checked("vkCreateImage(dmabuf)", d.CreateImage(r.device, &ii, nil, &im.image)); err != nil { | 186 | if err := checked("vkCreateImage(dmabuf)", d.CreateImage(r.device, &ii, nil, &im.image)); err != nil { | ||
| 182 | return nil, err | 187 | return nil, err | ||
| 183 | } | 188 | } | ||
| @@ -206,6 +211,12 @@ func (r *Renderer) importDMABuf(b *ports.DMABuf) (*imported, error) { | |||||
| 206 | if err := checked("vkBindImageMemory(dmabuf)", d.BindImageMemory(r.device, im.image, im.memory, 0)); err != nil { | 211 | if err := checked("vkBindImageMemory(dmabuf)", d.BindImageMemory(r.device, im.image, im.memory, 0)); err != nil { | ||
| 207 | return nil, err | 212 | return nil, err | ||
| 208 | } | 213 | } | ||
| 214 | + | if im.view, err = r.imageView(im.image, format); err != nil { | |||
| 215 | + | return nil, err | |||
| 216 | + | } | |||
| 217 | + | if im.pool, im.set, err = r.newSet(im.view, r.compose.dummyBuffer.buffer); err != nil { | |||
| 218 | + | return nil, err | |||
| 219 | + | } | |||
| 209 | ok = true | 220 | ok = true | ||
| 210 | r.imports[b.ID] = im | 221 | r.imports[b.ID] = im | ||
| 211 | return im, nil | 222 | return im, nil | ||
| @@ -229,6 +240,12 @@ func dupFile(f *os.File) (int, error) { | |||||
| 229 | } | 240 | } | ||
| 230 | 241 | ||||
| 231 | func (r *Renderer) release(im *imported) { | 242 | func (r *Renderer) release(im *imported) { | ||
| 243 | + | if im.pool != 0 { | |||
| 244 | + | r.dd.DestroyDescriptorPool(r.device, im.pool, nil) | |||
| 245 | + | } | |||
| 246 | + | if im.view != 0 { | |||
| 247 | + | r.dd.DestroyImageView(r.device, im.view, nil) | |||
| 248 | + | } | |||
| 232 | if im.image != 0 { | 249 | if im.image != 0 { | ||
| 233 | r.dd.DestroyImage(r.device, im.image, nil) | 250 | r.dd.DestroyImage(r.device, im.image, nil) | ||
| 234 | } | 251 | } | ||
| @@ -240,13 +257,13 @@ func (r *Renderer) release(im *imported) { | |||||
| 240 | } | 257 | } | ||
| 241 | } | 258 | } | ||
| 242 | 259 | ||||
| 243 | - | // dropUnused frees imports not drawn for importTTL frames: the client moved | 260 | + | // dropUnused frees imports not drawn for importTTL frames (the client |
| 244 | - | // on to other buffers or went away. Called after a frame completes. | 261 | + | // moved on to other buffers or went away), once the last frame that drew |
| 262 | + | // them completed. | |||
| 245 | func (r *Renderer) dropUnused() { | 263 | func (r *Renderer) dropUnused() { | ||
| 246 | - | r.frame++ | |||
| 247 | for id, im := range r.imports { | 264 | for id, im := range r.imports { | ||
| 248 | if r.frame-im.last > importTTL { | 265 | if r.frame-im.last > importTTL { | ||
| 249 | - | r.release(im) | 266 | + | r.retire(im.last, func() { r.release(im) }) |
| 250 | delete(r.imports, id) | 267 | delete(r.imports, id) | ||
| 251 | } | 268 | } | ||
| 252 | } | 269 | } | ||
A internal/adapters/vulkan/frame.go +313 -0
| @@ -0,0 +1,313 @@ | |||
| 1 | + | package vulkan | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "errors" | |
| 5 | + | "fmt" | |
| 6 | + | "math" | |
| 7 | + | "os" | |
| 8 | + | "runtime" | |
| 9 | + | "slices" | |
| 10 | + | "unsafe" | |
| 11 | + | ||
| 12 | + | "github.com/bnema/nefertty/internal/ports" | |
| 13 | + | vk "github.com/bnema/purego-vulkan/vulkan" | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // Frames are recorded into a ring of slots, one per frame in flight. The | |
| 17 | + | // CPU never waits for a frame it just submitted: Render returns a sync | |
| 18 | + | // file the display waits on (KMS IN_FENCE_FD). A slot's fence is waited | |
| 19 | + | // only when the slot is reused, and objects a frame read (imports, GPU | |
| 20 | + | // buffers, client semaphores) are freed only once that frame completed. | |
| 21 | + | ||
| 22 | + | // frameSlots is how many frames may be in flight: one per output target. | |
| 23 | + | const frameSlots = 2 | |
| 24 | + | ||
| 25 | + | // frameSlot is the command buffer and sync objects of one frame in flight. | |
| 26 | + | type frameSlot struct { | |
| 27 | + | cmd vk.CommandBuffer | |
| 28 | + | fence vk.Fence | |
| 29 | + | // done is signalled with the frame and exported as its sync file. | |
| 30 | + | done vk.Semaphore | |
| 31 | + | frame uint64 // the frame recorded last, 0 before any | |
| 32 | + | busy bool // submitted, fence not yet seen signalled | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | // deferredFree runs once frame completed. | |
| 36 | + | type deferredFree struct { | |
| 37 | + | frame uint64 | |
| 38 | + | free func() | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | func (r *Renderer) createSlots() error { | |
| 42 | + | d := r.dd | |
| 43 | + | cmds := make([]vk.CommandBuffer, frameSlots) | |
| 44 | + | ai := vk.CommandBufferAllocateInfo{SType: vk.StructureTypeCommandBufferAllocateInfo, CommandPool: r.pool, Level: vk.CommandBufferLevelPrimary, CommandBufferCount: frameSlots} | |
| 45 | + | if err := checked("vkAllocateCommandBuffers", d.AllocateCommandBuffers(r.device, &ai, &cmds[0])); err != nil { | |
| 46 | + | return err | |
| 47 | + | } | |
| 48 | + | for i := range r.slots { | |
| 49 | + | s := &r.slots[i] | |
| 50 | + | s.cmd = cmds[i] | |
| 51 | + | fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo} | |
| 52 | + | if err := checked("vkCreateFence", d.CreateFence(r.device, &fi, nil, &s.fence)); err != nil { | |
| 53 | + | return err | |
| 54 | + | } | |
| 55 | + | si := vk.SemaphoreCreateInfo{SType: vk.StructureTypeSemaphoreCreateInfo} | |
| 56 | + | export := vk.ExportSemaphoreCreateInfo{SType: vk.StructureTypeExportSemaphoreCreateInfo, HandleTypes: vk.ExternalSemaphoreHandleTypeSyncFDBit} | |
| 57 | + | if r.syncFD { | |
| 58 | + | si.Next = unsafe.Pointer(&export) | |
| 59 | + | } | |
| 60 | + | if err := checked("vkCreateSemaphore", d.CreateSemaphore(r.device, &si, nil, &s.done)); err != nil { | |
| 61 | + | return err | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | return nil | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | func (r *Renderer) destroySlots() { | |
| 68 | + | d := r.dd | |
| 69 | + | for i := range r.slots { | |
| 70 | + | s := &r.slots[i] | |
| 71 | + | if s.fence != 0 { | |
| 72 | + | d.DestroyFence(r.device, s.fence, nil) | |
| 73 | + | } | |
| 74 | + | if s.done != 0 { | |
| 75 | + | d.DestroySemaphore(r.device, s.done, nil) | |
| 76 | + | } | |
| 77 | + | if s.cmd != 0 { | |
| 78 | + | d.FreeCommandBuffers(r.device, r.pool, 1, &s.cmd) | |
| 79 | + | } | |
| 80 | + | *s = frameSlot{} | |
| 81 | + | } | |
| 82 | + | } | |
| 83 | + | ||
| 84 | + | // waitFrame blocks until frame n completed. Frames not submitted yet and | |
| 85 | + | // frames known done return at once. Every slot holding a frame up to n | |
| 86 | + | // is waited and reset, so its fence is unsignalled before reuse. | |
| 87 | + | func (r *Renderer) waitFrame(n uint64) error { | |
| 88 | + | n = min(n, r.submitted) | |
| 89 | + | if n <= r.completed { | |
| 90 | + | return nil | |
| 91 | + | } | |
| 92 | + | for i := range r.slots { | |
| 93 | + | s := &r.slots[i] | |
| 94 | + | if !s.busy || s.frame > n { | |
| 95 | + | continue | |
| 96 | + | } | |
| 97 | + | if err := checked("vkWaitForFences", r.dd.WaitForFences(r.device, 1, &s.fence, 1, math.MaxUint64)); err != nil { | |
| 98 | + | return err | |
| 99 | + | } | |
| 100 | + | if err := checked("vkResetFences", r.dd.ResetFences(r.device, 1, &s.fence)); err != nil { | |
| 101 | + | return err | |
| 102 | + | } | |
| 103 | + | s.busy = false | |
| 104 | + | } | |
| 105 | + | r.completed = n | |
| 106 | + | r.runDeferred() | |
| 107 | + | return nil | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | // retire frees an object once the frame that last used it completed. | |
| 111 | + | func (r *Renderer) retire(frame uint64, free func()) { | |
| 112 | + | if frame <= r.completed { | |
| 113 | + | free() | |
| 114 | + | return | |
| 115 | + | } | |
| 116 | + | r.deferred = append(r.deferred, deferredFree{frame: frame, free: free}) | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | func (r *Renderer) runDeferred() { | |
| 120 | + | r.deferred = slices.DeleteFunc(r.deferred, func(f deferredFree) bool { | |
| 121 | + | if f.frame <= r.completed { | |
| 122 | + | f.free() | |
| 123 | + | return true | |
| 124 | + | } | |
| 125 | + | return false | |
| 126 | + | }) | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | // idle records that the device finished every submitted frame. | |
| 130 | + | func (r *Renderer) idle() { | |
| 131 | + | for i := range r.slots { | |
| 132 | + | s := &r.slots[i] | |
| 133 | + | if s.busy { | |
| 134 | + | _ = r.dd.ResetFences(r.device, 1, &s.fence) | |
| 135 | + | s.busy = false | |
| 136 | + | } | |
| 137 | + | } | |
| 138 | + | // Frames recorded but never submitted have no GPU work either. | |
| 139 | + | r.completed = r.frame | |
| 140 | + | r.runDeferred() | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | // Render draws a frame into the current target. done is signalled when | |
| 144 | + | // the GPU finished it; nil when the device cannot export a sync file, | |
| 145 | + | // then the frame is finished on return. | |
| 146 | + | func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) (done *os.File, err error) { | |
| 147 | + | r.frame++ | |
| 148 | + | slot := &r.slots[r.frame%frameSlots] | |
| 149 | + | // The slot's previous frame must be done before its command buffer | |
| 150 | + | // is recorded again. | |
| 151 | + | if slot.busy { | |
| 152 | + | if err := r.waitFrame(slot.frame); err != nil { | |
| 153 | + | return nil, err | |
| 154 | + | } | |
| 155 | + | } | |
| 156 | + | ds := r.draws(s, contents) | |
| 157 | + | r.dropPools() | |
| 158 | + | d := r.dd | |
| 159 | + | cmd := slot.cmd | |
| 160 | + | if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(cmd, 0)); err != nil { | |
| 161 | + | return nil, err | |
| 162 | + | } | |
| 163 | + | begin := vk.CommandBufferBeginInfo{SType: vk.StructureTypeCommandBufferBeginInfo, Flags: vk.CommandBufferUsageOneTimeSubmitBit} | |
| 164 | + | if err := checked("vkBeginCommandBuffer", d.BeginCommandBuffer(cmd, &begin)); err != nil { | |
| 165 | + | return nil, err | |
| 166 | + | } | |
| 167 | + | tg := r.target() | |
| 168 | + | // The whole target is cleared by the pass: its old contents never | |
| 169 | + | // matter (UNDEFINED). | |
| 170 | + | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, DstAccessMask: vk.AccessColorAttachmentWriteBit, OldLayout: vk.ImageLayoutUndefined, NewLayout: vk.ImageLayoutColorAttachmentOptimal, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: tg.image, SubresourceRange: colorRange} | |
| 171 | + | if tg.exported { | |
| 172 | + | // Acquire from the display (foreign queue family). | |
| 173 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |
| 174 | + | } | |
| 175 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit|vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, &b) | |
| 176 | + | // Client buffers come from the foreign queue family (their driver) | |
| 177 | + | // and go back to it after the frame, so the next frame acquires them. | |
| 178 | + | var dmas []*imported | |
| 179 | + | for _, dr := range ds { | |
| 180 | + | if dr.im != nil && !slices.Contains(dmas, dr.im) { | |
| 181 | + | dmas = append(dmas, dr.im) | |
| 182 | + | dr.im.last = r.frame | |
| 183 | + | } | |
| 184 | + | } | |
| 185 | + | r.ownership(cmd, dmas, true) | |
| 186 | + | r.recordDraws(cmd, tg.view, parseColor(s.Background), ds) | |
| 187 | + | r.ownership(cmd, dmas, false) | |
| 188 | + | b.SrcAccessMask, b.OldLayout = vk.AccessColorAttachmentWriteBit, vk.ImageLayoutColorAttachmentOptimal | |
| 189 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = queueFamilyIgnored, queueFamilyIgnored | |
| 190 | + | if tg.exported { | |
| 191 | + | // Release to the display: KMS scans it out after the fence. | |
| 192 | + | b.NewLayout, b.DstAccessMask = vk.ImageLayoutGeneral, 0 | |
| 193 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |
| 194 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b) | |
| 195 | + | } else { | |
| 196 | + | b.NewLayout, b.DstAccessMask = vk.ImageLayoutTransferSrcOptimal, vk.AccessTransferReadBit | |
| 197 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |
| 198 | + | } | |
| 199 | + | if err := checked("vkEndCommandBuffer", d.EndCommandBuffer(cmd)); err != nil { | |
| 200 | + | return nil, err | |
| 201 | + | } | |
| 202 | + | // Implicit sync: wait for the client's GPU writes to each buffer. | |
| 203 | + | var waits []vk.Semaphore | |
| 204 | + | for _, im := range dmas { | |
| 205 | + | if sem := r.readFence(im); sem != 0 { | |
| 206 | + | waits = append(waits, sem) | |
| 207 | + | } | |
| 208 | + | } | |
| 209 | + | frame := r.frame | |
| 210 | + | for _, sem := range waits { | |
| 211 | + | r.retire(frame, func() { d.DestroySemaphore(r.device, sem, nil) }) | |
| 212 | + | } | |
| 213 | + | submit := vk.SubmitInfo{SType: vk.StructureTypeSubmitInfo, CommandBufferCount: 1, CommandBuffers: &cmd} | |
| 214 | + | stages := make([]vk.PipelineStageFlags, len(waits)) | |
| 215 | + | if len(waits) > 0 { | |
| 216 | + | for i := range stages { | |
| 217 | + | stages[i] = vk.PipelineStageFragmentShaderBit | |
| 218 | + | } | |
| 219 | + | submit.WaitSemaphoreCount, submit.WaitSemaphores, submit.WaitDstStageMask = uint32(len(waits)), &waits[0], &stages[0] | |
| 220 | + | } | |
| 221 | + | if r.syncFD { | |
| 222 | + | submit.SignalSemaphoreCount, submit.SignalSemaphores = 1, &slot.done | |
| 223 | + | } | |
| 224 | + | if err := checked("vkQueueSubmit", d.QueueSubmit(r.queue, 1, &submit, slot.fence)); err != nil { | |
| 225 | + | return nil, err | |
| 226 | + | } | |
| 227 | + | runtime.KeepAlive(stages) | |
| 228 | + | runtime.KeepAlive(waits) | |
| 229 | + | slot.frame, slot.busy, r.submitted = frame, true, frame | |
| 230 | + | tg.layout = b.NewLayout | |
| 231 | + | r.last, r.readBack = tg, false | |
| 232 | + | r.dropUnused() | |
| 233 | + | r.dropShm() | |
| 234 | + | if !r.syncFD { | |
| 235 | + | return nil, r.waitFrame(frame) | |
| 236 | + | } | |
| 237 | + | var fd int32 | |
| 238 | + | get := vk.SemaphoreGetFdInfoKHR{SType: vk.StructureTypeSemaphoreGetFDInfoKHR, Semaphore: slot.done, HandleType: vk.ExternalSemaphoreHandleTypeSyncFDBit} | |
| 239 | + | if err := checked("vkGetSemaphoreFdKHR", d.GetSemaphoreFdKHR(r.device, &get, &fd)); err != nil { | |
| 240 | + | return nil, errors.Join(err, r.waitFrame(frame)) | |
| 241 | + | } | |
| 242 | + | if fd < 0 { | |
| 243 | + | // Already signalled: the driver may return -1. | |
| 244 | + | return nil, r.waitFrame(frame) | |
| 245 | + | } | |
| 246 | + | f := os.NewFile(uintptr(fd), "nefertty-frame") | |
| 247 | + | if f == nil { | |
| 248 | + | return nil, fmt.Errorf("invalid frame sync file %d", fd) | |
| 249 | + | } | |
| 250 | + | return f, nil | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // ownership moves client images between their driver (foreign queue | |
| 254 | + | // family, GENERAL: their contents survive) and the fragment shader. | |
| 255 | + | func (r *Renderer) ownership(cmd vk.CommandBuffer, dmas []*imported, acquire bool) { | |
| 256 | + | for _, im := range dmas { | |
| 257 | + | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, Image: im.image, SubresourceRange: colorRange} | |
| 258 | + | if acquire { | |
| 259 | + | b.OldLayout, b.NewLayout = vk.ImageLayoutGeneral, vk.ImageLayoutShaderReadOnlyOptimal | |
| 260 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |
| 261 | + | b.DstAccessMask = vk.AccessShaderReadBit | |
| 262 | + | r.dd.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageFragmentShaderBit, 0, 0, nil, 0, nil, 1, &b) | |
| 263 | + | } else { | |
| 264 | + | b.OldLayout, b.NewLayout = vk.ImageLayoutShaderReadOnlyOptimal, vk.ImageLayoutGeneral | |
| 265 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |
| 266 | + | b.SrcAccessMask = vk.AccessShaderReadBit | |
| 267 | + | r.dd.CmdPipelineBarrier(cmd, vk.PipelineStageFragmentShaderBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b) | |
| 268 | + | } | |
| 269 | + | } | |
| 270 | + | } | |
| 271 | + | ||
| 272 | + | // oneShot records and runs commands outside the frame ring and waits for | |
| 273 | + | // them (setup and screenshots only, never per frame). | |
| 274 | + | func (r *Renderer) oneShot(record func(vk.CommandBuffer)) error { | |
| 275 | + | d := r.dd | |
| 276 | + | var cmd vk.CommandBuffer | |
| 277 | + | ai := vk.CommandBufferAllocateInfo{SType: vk.StructureTypeCommandBufferAllocateInfo, CommandPool: r.pool, Level: vk.CommandBufferLevelPrimary, CommandBufferCount: 1} | |
| 278 | + | if err := checked("vkAllocateCommandBuffers", d.AllocateCommandBuffers(r.device, &ai, &cmd)); err != nil { | |
| 279 | + | return err | |
| 280 | + | } | |
| 281 | + | defer d.FreeCommandBuffers(r.device, r.pool, 1, &cmd) | |
| 282 | + | var fence vk.Fence | |
| 283 | + | fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo} | |
| 284 | + | if err := checked("vkCreateFence", d.CreateFence(r.device, &fi, nil, &fence)); err != nil { | |
| 285 | + | return err | |
| 286 | + | } | |
| 287 | + | defer d.DestroyFence(r.device, fence, nil) | |
| 288 | + | begin := vk.CommandBufferBeginInfo{SType: vk.StructureTypeCommandBufferBeginInfo, Flags: vk.CommandBufferUsageOneTimeSubmitBit} | |
| 289 | + | if err := checked("vkBeginCommandBuffer", d.BeginCommandBuffer(cmd, &begin)); err != nil { | |
| 290 | + | return err | |
| 291 | + | } | |
| 292 | + | record(cmd) | |
| 293 | + | if err := checked("vkEndCommandBuffer", d.EndCommandBuffer(cmd)); err != nil { | |
| 294 | + | return err | |
| 295 | + | } | |
| 296 | + | submit := vk.SubmitInfo{SType: vk.StructureTypeSubmitInfo, CommandBufferCount: 1, CommandBuffers: &cmd} | |
| 297 | + | if err := checked("vkQueueSubmit", d.QueueSubmit(r.queue, 1, &submit, fence)); err != nil { | |
| 298 | + | return err | |
| 299 | + | } | |
| 300 | + | return checked("vkWaitForFences", d.WaitForFences(r.device, 1, &fence, 1, math.MaxUint64)) | |
| 301 | + | } | |
| 302 | + | ||
| 303 | + | // Clear draws a background-only frame and waits for it. | |
| 304 | + | func (r *Renderer) Clear(rgb [3]uint8) error { | |
| 305 | + | done, err := r.Render(ports.Scene{Background: fmt.Sprintf("#%02x%02x%02x", rgb[0], rgb[1], rgb[2])}, nil) | |
| 306 | + | if done != nil { | |
| 307 | + | done.Close() | |
| 308 | + | } | |
| 309 | + | if err != nil { | |
| 310 | + | return err | |
| 311 | + | } | |
| 312 | + | return r.waitFrame(r.frame) | |
| 313 | + | } | |
| @@ -0,0 +1,313 @@ | |||||
| 1 | + | package vulkan | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "errors" | |||
| 5 | + | "fmt" | |||
| 6 | + | "math" | |||
| 7 | + | "os" | |||
| 8 | + | "runtime" | |||
| 9 | + | "slices" | |||
| 10 | + | "unsafe" | |||
| 11 | + | ||||
| 12 | + | "github.com/bnema/nefertty/internal/ports" | |||
| 13 | + | vk "github.com/bnema/purego-vulkan/vulkan" | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | // Frames are recorded into a ring of slots, one per frame in flight. The | |||
| 17 | + | // CPU never waits for a frame it just submitted: Render returns a sync | |||
| 18 | + | // file the display waits on (KMS IN_FENCE_FD). A slot's fence is waited | |||
| 19 | + | // only when the slot is reused, and objects a frame read (imports, GPU | |||
| 20 | + | // buffers, client semaphores) are freed only once that frame completed. | |||
| 21 | + | ||||
| 22 | + | // frameSlots is how many frames may be in flight: one per output target. | |||
| 23 | + | const frameSlots = 2 | |||
| 24 | + | ||||
| 25 | + | // frameSlot is the command buffer and sync objects of one frame in flight. | |||
| 26 | + | type frameSlot struct { | |||
| 27 | + | cmd vk.CommandBuffer | |||
| 28 | + | fence vk.Fence | |||
| 29 | + | // done is signalled with the frame and exported as its sync file. | |||
| 30 | + | done vk.Semaphore | |||
| 31 | + | frame uint64 // the frame recorded last, 0 before any | |||
| 32 | + | busy bool // submitted, fence not yet seen signalled | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | // deferredFree runs once frame completed. | |||
| 36 | + | type deferredFree struct { | |||
| 37 | + | frame uint64 | |||
| 38 | + | free func() | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | func (r *Renderer) createSlots() error { | |||
| 42 | + | d := r.dd | |||
| 43 | + | cmds := make([]vk.CommandBuffer, frameSlots) | |||
| 44 | + | ai := vk.CommandBufferAllocateInfo{SType: vk.StructureTypeCommandBufferAllocateInfo, CommandPool: r.pool, Level: vk.CommandBufferLevelPrimary, CommandBufferCount: frameSlots} | |||
| 45 | + | if err := checked("vkAllocateCommandBuffers", d.AllocateCommandBuffers(r.device, &ai, &cmds[0])); err != nil { | |||
| 46 | + | return err | |||
| 47 | + | } | |||
| 48 | + | for i := range r.slots { | |||
| 49 | + | s := &r.slots[i] | |||
| 50 | + | s.cmd = cmds[i] | |||
| 51 | + | fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo} | |||
| 52 | + | if err := checked("vkCreateFence", d.CreateFence(r.device, &fi, nil, &s.fence)); err != nil { | |||
| 53 | + | return err | |||
| 54 | + | } | |||
| 55 | + | si := vk.SemaphoreCreateInfo{SType: vk.StructureTypeSemaphoreCreateInfo} | |||
| 56 | + | export := vk.ExportSemaphoreCreateInfo{SType: vk.StructureTypeExportSemaphoreCreateInfo, HandleTypes: vk.ExternalSemaphoreHandleTypeSyncFDBit} | |||
| 57 | + | if r.syncFD { | |||
| 58 | + | si.Next = unsafe.Pointer(&export) | |||
| 59 | + | } | |||
| 60 | + | if err := checked("vkCreateSemaphore", d.CreateSemaphore(r.device, &si, nil, &s.done)); err != nil { | |||
| 61 | + | return err | |||
| 62 | + | } | |||
| 63 | + | } | |||
| 64 | + | return nil | |||
| 65 | + | } | |||
| 66 | + | ||||
| 67 | + | func (r *Renderer) destroySlots() { | |||
| 68 | + | d := r.dd | |||
| 69 | + | for i := range r.slots { | |||
| 70 | + | s := &r.slots[i] | |||
| 71 | + | if s.fence != 0 { | |||
| 72 | + | d.DestroyFence(r.device, s.fence, nil) | |||
| 73 | + | } | |||
| 74 | + | if s.done != 0 { | |||
| 75 | + | d.DestroySemaphore(r.device, s.done, nil) | |||
| 76 | + | } | |||
| 77 | + | if s.cmd != 0 { | |||
| 78 | + | d.FreeCommandBuffers(r.device, r.pool, 1, &s.cmd) | |||
| 79 | + | } | |||
| 80 | + | *s = frameSlot{} | |||
| 81 | + | } | |||
| 82 | + | } | |||
| 83 | + | ||||
| 84 | + | // waitFrame blocks until frame n completed. Frames not submitted yet and | |||
| 85 | + | // frames known done return at once. Every slot holding a frame up to n | |||
| 86 | + | // is waited and reset, so its fence is unsignalled before reuse. | |||
| 87 | + | func (r *Renderer) waitFrame(n uint64) error { | |||
| 88 | + | n = min(n, r.submitted) | |||
| 89 | + | if n <= r.completed { | |||
| 90 | + | return nil | |||
| 91 | + | } | |||
| 92 | + | for i := range r.slots { | |||
| 93 | + | s := &r.slots[i] | |||
| 94 | + | if !s.busy || s.frame > n { | |||
| 95 | + | continue | |||
| 96 | + | } | |||
| 97 | + | if err := checked("vkWaitForFences", r.dd.WaitForFences(r.device, 1, &s.fence, 1, math.MaxUint64)); err != nil { | |||
| 98 | + | return err | |||
| 99 | + | } | |||
| 100 | + | if err := checked("vkResetFences", r.dd.ResetFences(r.device, 1, &s.fence)); err != nil { | |||
| 101 | + | return err | |||
| 102 | + | } | |||
| 103 | + | s.busy = false | |||
| 104 | + | } | |||
| 105 | + | r.completed = n | |||
| 106 | + | r.runDeferred() | |||
| 107 | + | return nil | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | // retire frees an object once the frame that last used it completed. | |||
| 111 | + | func (r *Renderer) retire(frame uint64, free func()) { | |||
| 112 | + | if frame <= r.completed { | |||
| 113 | + | free() | |||
| 114 | + | return | |||
| 115 | + | } | |||
| 116 | + | r.deferred = append(r.deferred, deferredFree{frame: frame, free: free}) | |||
| 117 | + | } | |||
| 118 | + | ||||
| 119 | + | func (r *Renderer) runDeferred() { | |||
| 120 | + | r.deferred = slices.DeleteFunc(r.deferred, func(f deferredFree) bool { | |||
| 121 | + | if f.frame <= r.completed { | |||
| 122 | + | f.free() | |||
| 123 | + | return true | |||
| 124 | + | } | |||
| 125 | + | return false | |||
| 126 | + | }) | |||
| 127 | + | } | |||
| 128 | + | ||||
| 129 | + | // idle records that the device finished every submitted frame. | |||
| 130 | + | func (r *Renderer) idle() { | |||
| 131 | + | for i := range r.slots { | |||
| 132 | + | s := &r.slots[i] | |||
| 133 | + | if s.busy { | |||
| 134 | + | _ = r.dd.ResetFences(r.device, 1, &s.fence) | |||
| 135 | + | s.busy = false | |||
| 136 | + | } | |||
| 137 | + | } | |||
| 138 | + | // Frames recorded but never submitted have no GPU work either. | |||
| 139 | + | r.completed = r.frame | |||
| 140 | + | r.runDeferred() | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | // Render draws a frame into the current target. done is signalled when | |||
| 144 | + | // the GPU finished it; nil when the device cannot export a sync file, | |||
| 145 | + | // then the frame is finished on return. | |||
| 146 | + | func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) (done *os.File, err error) { | |||
| 147 | + | r.frame++ | |||
| 148 | + | slot := &r.slots[r.frame%frameSlots] | |||
| 149 | + | // The slot's previous frame must be done before its command buffer | |||
| 150 | + | // is recorded again. | |||
| 151 | + | if slot.busy { | |||
| 152 | + | if err := r.waitFrame(slot.frame); err != nil { | |||
| 153 | + | return nil, err | |||
| 154 | + | } | |||
| 155 | + | } | |||
| 156 | + | ds := r.draws(s, contents) | |||
| 157 | + | r.dropPools() | |||
| 158 | + | d := r.dd | |||
| 159 | + | cmd := slot.cmd | |||
| 160 | + | if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(cmd, 0)); err != nil { | |||
| 161 | + | return nil, err | |||
| 162 | + | } | |||
| 163 | + | begin := vk.CommandBufferBeginInfo{SType: vk.StructureTypeCommandBufferBeginInfo, Flags: vk.CommandBufferUsageOneTimeSubmitBit} | |||
| 164 | + | if err := checked("vkBeginCommandBuffer", d.BeginCommandBuffer(cmd, &begin)); err != nil { | |||
| 165 | + | return nil, err | |||
| 166 | + | } | |||
| 167 | + | tg := r.target() | |||
| 168 | + | // The whole target is cleared by the pass: its old contents never | |||
| 169 | + | // matter (UNDEFINED). | |||
| 170 | + | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, DstAccessMask: vk.AccessColorAttachmentWriteBit, OldLayout: vk.ImageLayoutUndefined, NewLayout: vk.ImageLayoutColorAttachmentOptimal, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: tg.image, SubresourceRange: colorRange} | |||
| 171 | + | if tg.exported { | |||
| 172 | + | // Acquire from the display (foreign queue family). | |||
| 173 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |||
| 174 | + | } | |||
| 175 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit|vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageColorAttachmentOutputBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 176 | + | // Client buffers come from the foreign queue family (their driver) | |||
| 177 | + | // and go back to it after the frame, so the next frame acquires them. | |||
| 178 | + | var dmas []*imported | |||
| 179 | + | for _, dr := range ds { | |||
| 180 | + | if dr.im != nil && !slices.Contains(dmas, dr.im) { | |||
| 181 | + | dmas = append(dmas, dr.im) | |||
| 182 | + | dr.im.last = r.frame | |||
| 183 | + | } | |||
| 184 | + | } | |||
| 185 | + | r.ownership(cmd, dmas, true) | |||
| 186 | + | r.recordDraws(cmd, tg.view, parseColor(s.Background), ds) | |||
| 187 | + | r.ownership(cmd, dmas, false) | |||
| 188 | + | b.SrcAccessMask, b.OldLayout = vk.AccessColorAttachmentWriteBit, vk.ImageLayoutColorAttachmentOptimal | |||
| 189 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = queueFamilyIgnored, queueFamilyIgnored | |||
| 190 | + | if tg.exported { | |||
| 191 | + | // Release to the display: KMS scans it out after the fence. | |||
| 192 | + | b.NewLayout, b.DstAccessMask = vk.ImageLayoutGeneral, 0 | |||
| 193 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |||
| 194 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 195 | + | } else { | |||
| 196 | + | b.NewLayout, b.DstAccessMask = vk.ImageLayoutTransferSrcOptimal, vk.AccessTransferReadBit | |||
| 197 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageColorAttachmentOutputBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 198 | + | } | |||
| 199 | + | if err := checked("vkEndCommandBuffer", d.EndCommandBuffer(cmd)); err != nil { | |||
| 200 | + | return nil, err | |||
| 201 | + | } | |||
| 202 | + | // Implicit sync: wait for the client's GPU writes to each buffer. | |||
| 203 | + | var waits []vk.Semaphore | |||
| 204 | + | for _, im := range dmas { | |||
| 205 | + | if sem := r.readFence(im); sem != 0 { | |||
| 206 | + | waits = append(waits, sem) | |||
| 207 | + | } | |||
| 208 | + | } | |||
| 209 | + | frame := r.frame | |||
| 210 | + | for _, sem := range waits { | |||
| 211 | + | r.retire(frame, func() { d.DestroySemaphore(r.device, sem, nil) }) | |||
| 212 | + | } | |||
| 213 | + | submit := vk.SubmitInfo{SType: vk.StructureTypeSubmitInfo, CommandBufferCount: 1, CommandBuffers: &cmd} | |||
| 214 | + | stages := make([]vk.PipelineStageFlags, len(waits)) | |||
| 215 | + | if len(waits) > 0 { | |||
| 216 | + | for i := range stages { | |||
| 217 | + | stages[i] = vk.PipelineStageFragmentShaderBit | |||
| 218 | + | } | |||
| 219 | + | submit.WaitSemaphoreCount, submit.WaitSemaphores, submit.WaitDstStageMask = uint32(len(waits)), &waits[0], &stages[0] | |||
| 220 | + | } | |||
| 221 | + | if r.syncFD { | |||
| 222 | + | submit.SignalSemaphoreCount, submit.SignalSemaphores = 1, &slot.done | |||
| 223 | + | } | |||
| 224 | + | if err := checked("vkQueueSubmit", d.QueueSubmit(r.queue, 1, &submit, slot.fence)); err != nil { | |||
| 225 | + | return nil, err | |||
| 226 | + | } | |||
| 227 | + | runtime.KeepAlive(stages) | |||
| 228 | + | runtime.KeepAlive(waits) | |||
| 229 | + | slot.frame, slot.busy, r.submitted = frame, true, frame | |||
| 230 | + | tg.layout = b.NewLayout | |||
| 231 | + | r.last, r.readBack = tg, false | |||
| 232 | + | r.dropUnused() | |||
| 233 | + | r.dropShm() | |||
| 234 | + | if !r.syncFD { | |||
| 235 | + | return nil, r.waitFrame(frame) | |||
| 236 | + | } | |||
| 237 | + | var fd int32 | |||
| 238 | + | get := vk.SemaphoreGetFdInfoKHR{SType: vk.StructureTypeSemaphoreGetFDInfoKHR, Semaphore: slot.done, HandleType: vk.ExternalSemaphoreHandleTypeSyncFDBit} | |||
| 239 | + | if err := checked("vkGetSemaphoreFdKHR", d.GetSemaphoreFdKHR(r.device, &get, &fd)); err != nil { | |||
| 240 | + | return nil, errors.Join(err, r.waitFrame(frame)) | |||
| 241 | + | } | |||
| 242 | + | if fd < 0 { | |||
| 243 | + | // Already signalled: the driver may return -1. | |||
| 244 | + | return nil, r.waitFrame(frame) | |||
| 245 | + | } | |||
| 246 | + | f := os.NewFile(uintptr(fd), "nefertty-frame") | |||
| 247 | + | if f == nil { | |||
| 248 | + | return nil, fmt.Errorf("invalid frame sync file %d", fd) | |||
| 249 | + | } | |||
| 250 | + | return f, nil | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | // ownership moves client images between their driver (foreign queue | |||
| 254 | + | // family, GENERAL: their contents survive) and the fragment shader. | |||
| 255 | + | func (r *Renderer) ownership(cmd vk.CommandBuffer, dmas []*imported, acquire bool) { | |||
| 256 | + | for _, im := range dmas { | |||
| 257 | + | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, Image: im.image, SubresourceRange: colorRange} | |||
| 258 | + | if acquire { | |||
| 259 | + | b.OldLayout, b.NewLayout = vk.ImageLayoutGeneral, vk.ImageLayoutShaderReadOnlyOptimal | |||
| 260 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |||
| 261 | + | b.DstAccessMask = vk.AccessShaderReadBit | |||
| 262 | + | r.dd.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageFragmentShaderBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 263 | + | } else { | |||
| 264 | + | b.OldLayout, b.NewLayout = vk.ImageLayoutShaderReadOnlyOptimal, vk.ImageLayoutGeneral | |||
| 265 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |||
| 266 | + | b.SrcAccessMask = vk.AccessShaderReadBit | |||
| 267 | + | r.dd.CmdPipelineBarrier(cmd, vk.PipelineStageFragmentShaderBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 268 | + | } | |||
| 269 | + | } | |||
| 270 | + | } | |||
| 271 | + | ||||
| 272 | + | // oneShot records and runs commands outside the frame ring and waits for | |||
| 273 | + | // them (setup and screenshots only, never per frame). | |||
| 274 | + | func (r *Renderer) oneShot(record func(vk.CommandBuffer)) error { | |||
| 275 | + | d := r.dd | |||
| 276 | + | var cmd vk.CommandBuffer | |||
| 277 | + | ai := vk.CommandBufferAllocateInfo{SType: vk.StructureTypeCommandBufferAllocateInfo, CommandPool: r.pool, Level: vk.CommandBufferLevelPrimary, CommandBufferCount: 1} | |||
| 278 | + | if err := checked("vkAllocateCommandBuffers", d.AllocateCommandBuffers(r.device, &ai, &cmd)); err != nil { | |||
| 279 | + | return err | |||
| 280 | + | } | |||
| 281 | + | defer d.FreeCommandBuffers(r.device, r.pool, 1, &cmd) | |||
| 282 | + | var fence vk.Fence | |||
| 283 | + | fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo} | |||
| 284 | + | if err := checked("vkCreateFence", d.CreateFence(r.device, &fi, nil, &fence)); err != nil { | |||
| 285 | + | return err | |||
| 286 | + | } | |||
| 287 | + | defer d.DestroyFence(r.device, fence, nil) | |||
| 288 | + | begin := vk.CommandBufferBeginInfo{SType: vk.StructureTypeCommandBufferBeginInfo, Flags: vk.CommandBufferUsageOneTimeSubmitBit} | |||
| 289 | + | if err := checked("vkBeginCommandBuffer", d.BeginCommandBuffer(cmd, &begin)); err != nil { | |||
| 290 | + | return err | |||
| 291 | + | } | |||
| 292 | + | record(cmd) | |||
| 293 | + | if err := checked("vkEndCommandBuffer", d.EndCommandBuffer(cmd)); err != nil { | |||
| 294 | + | return err | |||
| 295 | + | } | |||
| 296 | + | submit := vk.SubmitInfo{SType: vk.StructureTypeSubmitInfo, CommandBufferCount: 1, CommandBuffers: &cmd} | |||
| 297 | + | if err := checked("vkQueueSubmit", d.QueueSubmit(r.queue, 1, &submit, fence)); err != nil { | |||
| 298 | + | return err | |||
| 299 | + | } | |||
| 300 | + | return checked("vkWaitForFences", d.WaitForFences(r.device, 1, &fence, 1, math.MaxUint64)) | |||
| 301 | + | } | |||
| 302 | + | ||||
| 303 | + | // Clear draws a background-only frame and waits for it. | |||
| 304 | + | func (r *Renderer) Clear(rgb [3]uint8) error { | |||
| 305 | + | done, err := r.Render(ports.Scene{Background: fmt.Sprintf("#%02x%02x%02x", rgb[0], rgb[1], rgb[2])}, nil) | |||
| 306 | + | if done != nil { | |||
| 307 | + | done.Close() | |||
| 308 | + | } | |||
| 309 | + | if err != nil { | |||
| 310 | + | return err | |||
| 311 | + | } | |||
| 312 | + | return r.waitFrame(r.frame) | |||
| 313 | + | } | |||
M internal/adapters/vulkan/renderer.go +96 -460
| @@ -6,7 +6,6 @@ import ( | |||
| 6 | 6 | "math" | |
| 7 | 7 | "os" | |
| 8 | 8 | "runtime" | |
| 9 | - | "slices" | |
| 10 | 9 | "strconv" | |
| 11 | 10 | "unsafe" | |
| 12 | 11 | ||
| @@ -31,43 +30,41 @@ type Renderer struct { | |||
| 31 | 30 | renderMods []uint64 | |
| 32 | 31 | // last is the target of the last frame; readback copies it into | |
| 33 | 32 | // buffer only when Pixels asks (stale until then). | |
| 34 | - | last *target | |
| 35 | - | readBack bool | |
| 36 | - | buffer vk.Buffer | |
| 37 | - | bufferMemory vk.DeviceMemory | |
| 38 | - | staging vk.Buffer | |
| 39 | - | stagingMemory vk.DeviceMemory | |
| 40 | - | stagingMapped unsafe.Pointer | |
| 41 | - | stagingSize int | |
| 42 | - | mapped unsafe.Pointer | |
| 43 | - | pool vk.CommandPool | |
| 44 | - | command vk.CommandBuffer | |
| 45 | - | fence vk.Fence | |
| 46 | - | memory vk.PhysicalDeviceMemoryProperties | |
| 47 | - | family uint32 | |
| 33 | + | last *target | |
| 34 | + | readBack bool | |
| 35 | + | buffer vk.Buffer | |
| 36 | + | bufferMemory vk.DeviceMemory | |
| 37 | + | mapped unsafe.Pointer | |
| 38 | + | pool vk.CommandPool | |
| 39 | + | memory vk.PhysicalDeviceMemoryProperties | |
| 40 | + | family uint32 | |
| 41 | + | // Frames in flight (frame.go): frame is the last frame recorded, | |
| 42 | + | // submitted the last submitted, completed the last known finished. | |
| 43 | + | slots [frameSlots]frameSlot | |
| 44 | + | frame, submitted, completed uint64 | |
| 45 | + | deferred []deferredFree | |
| 46 | + | // syncFD: frames export a sync file (else Render waits for them). | |
| 47 | + | syncFD bool | |
| 48 | 48 | // dmabuf is what clients may send; imports are their buffers by ID. | |
| 49 | 49 | dmabuf ports.DMABufSupport | |
| 50 | 50 | imports map[uint64]*imported | |
| 51 | 51 | // pools are client wl_shm pools mapped by pool ID. | |
| 52 | 52 | pools map[uint64]*mapping | |
| 53 | - | // retired are pool mappings replaced this frame, unmapped after staging. | |
| 53 | + | // retired are pool mappings replaced this frame, unmapped once the | |
| 54 | + | // frame's copies are done. | |
| 54 | 55 | retired [][]byte | |
| 55 | - | frame uint64 | |
| 56 | - | // fillRegions is reused by Render for the bands of solid fills. | |
| 57 | - | fillRegions []vk.BufferImageCopy | |
| 56 | + | // shm are the GPU copies of wl_shm surfaces (shm.go); copied counts | |
| 57 | + | // the bytes copied into them, for tests. | |
| 58 | + | shm map[shmKey]*shmSurface | |
| 59 | + | copied int | |
| 58 | 60 | // cursors are the exported cursor images (CursorBuffers). | |
| 59 | 61 | cursors [2]*cursorImage | |
| 60 | - | // blend draws translucent client pixels (blend.go); align is the | |
| 61 | - | // device's storage buffer offset alignment. | |
| 62 | - | blend blendPipeline | |
| 63 | - | align int | |
| 64 | - | maxRange int // bytes of blended pixels one frame may bind | |
| 62 | + | // compose draws every frame (compose.go); maxRange bounds one | |
| 63 | + | // storage buffer binding. | |
| 64 | + | compose composer | |
| 65 | + | maxRange int | |
| 65 | 66 | } | |
| 66 | 67 | ||
| 67 | - | // fillRows is the height of the strip staged for a solid fill: the CPU | |
| 68 | - | // writes it once, the GPU copies it down the rect. | |
| 69 | - | const fillRows = 64 | |
| 70 | - | ||
| 71 | 68 | func checked(name string, result vk.Result) error { | |
| 72 | 69 | if err := vk.Check(result); err != nil { | |
| 73 | 70 | return fmt.Errorf("%s: %w", name, err) | |
| @@ -79,7 +76,7 @@ func New(width, height int) (r *Renderer, err error) { | |||
| 79 | 76 | if width <= 0 || height <= 0 || width > math.MaxUint32 || height > math.MaxUint32 || uint64(width)*uint64(height) > uint64(math.MaxInt/4) { | |
| 80 | 77 | return nil, fmt.Errorf("invalid renderer dimensions %d x %d", width, height) | |
| 81 | 78 | } | |
| 82 | - | r = &Renderer{width: width, height: height, imports: map[uint64]*imported{}, pools: map[uint64]*mapping{}} | |
| 79 | + | r = &Renderer{width: width, height: height, imports: map[uint64]*imported{}, pools: map[uint64]*mapping{}, shm: map[shmKey]*shmSurface{}} | |
| 83 | 80 | defer func() { | |
| 84 | 81 | if err != nil { | |
| 85 | 82 | r.Close() | |
| @@ -155,7 +152,6 @@ func New(width, height int) (r *Renderer, err error) { | |||
| 155 | 152 | r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory) | |
| 156 | 153 | var properties vk.PhysicalDeviceProperties | |
| 157 | 154 | r.id.GetPhysicalDeviceProperties(physical, &properties) | |
| 158 | - | r.align = int(max(properties.Limits.MinStorageBufferOffsetAlignment, 4)) | |
| 159 | 155 | r.maxRange = int(properties.Limits.MaxStorageBufferRange) | |
| 160 | 156 | r.family = family | |
| 161 | 157 | priority := float32(1) | |
| @@ -189,6 +185,9 @@ func New(width, height int) (r *Renderer, err error) { | |||
| 189 | 185 | r.dmabuf = sup | |
| 190 | 186 | r.probeRenderModifiers(physical) | |
| 191 | 187 | } | |
| 188 | + | // VK_KHR_external_semaphore_fd (enabled above): frames export | |
| 189 | + | // their fence as a sync file. | |
| 190 | + | r.syncFD = r.dd.GetSemaphoreFdKHR != nil | |
| 192 | 191 | } | |
| 193 | 192 | r.dd.GetDeviceQueue(r.device, family, 0, &r.queue) | |
| 194 | 193 | extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1} | |
| @@ -216,7 +215,7 @@ func New(width, height int) (r *Renderer, err error) { | |||
| 216 | 215 | return | |
| 217 | 216 | } | |
| 218 | 217 | r.dd.GetBufferMemoryRequirements(r.device, r.buffer, &req) | |
| 219 | - | // Readback (screenshots, fallback) reads this buffer: uncached memory is ~100x slower. | |
| 218 | + | // Readback (screenshots) reads this buffer: uncached memory is ~100x slower. | |
| 220 | 219 | kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit|vk.MemoryPropertyHostCachedBit) | |
| 221 | 220 | if err != nil { | |
| 222 | 221 | kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit) | |
| @@ -234,44 +233,17 @@ func New(width, height int) (r *Renderer, err error) { | |||
| 234 | 233 | if err = checked("vkMapMemory", r.dd.MapMemory(r.device, r.bufferMemory, 0, size, 0, &r.mapped)); err != nil { | |
| 235 | 234 | return | |
| 236 | 235 | } | |
| 237 | - | // Start with two frame-sized regions; later frames grow staging on demand. | |
| 238 | - | stagingSize := size * 2 | |
| 239 | - | r.stagingSize = int(stagingSize) | |
| 240 | - | stagingInfo := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: stagingSize, Usage: stagingUsage, SharingMode: vk.SharingModeExclusive} | |
| 241 | - | if err = checked("vkCreateBuffer(staging)", r.dd.CreateBuffer(r.device, &stagingInfo, nil, &r.staging)); err != nil { | |
| 242 | - | return | |
| 243 | - | } | |
| 244 | - | r.dd.GetBufferMemoryRequirements(r.device, r.staging, &req) | |
| 245 | - | kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit) | |
| 246 | - | if err != nil { | |
| 247 | - | return | |
| 248 | - | } | |
| 249 | - | alloc = vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind} | |
| 250 | - | if err = checked("vkAllocateMemory(staging)", r.dd.AllocateMemory(r.device, &alloc, nil, &r.stagingMemory)); err != nil { | |
| 251 | - | return | |
| 252 | - | } | |
| 253 | - | if err = checked("vkBindBufferMemory(staging)", r.dd.BindBufferMemory(r.device, r.staging, r.stagingMemory, 0)); err != nil { | |
| 254 | - | return | |
| 255 | - | } | |
| 256 | - | if err = checked("vkMapMemory(staging)", r.dd.MapMemory(r.device, r.stagingMemory, 0, stagingSize, 0, &r.stagingMapped)); err != nil { | |
| 257 | - | return | |
| 258 | - | } | |
| 259 | 236 | pi := vk.CommandPoolCreateInfo{SType: vk.StructureTypeCommandPoolCreateInfo, Flags: vk.CommandPoolCreateResetCommandBufferBit, QueueFamilyIndex: family} | |
| 260 | 237 | if err = checked("vkCreateCommandPool", r.dd.CreateCommandPool(r.device, &pi, nil, &r.pool)); err != nil { | |
| 261 | 238 | return | |
| 262 | 239 | } | |
| 263 | - | ai := vk.CommandBufferAllocateInfo{SType: vk.StructureTypeCommandBufferAllocateInfo, CommandPool: r.pool, Level: vk.CommandBufferLevelPrimary, CommandBufferCount: 1} | |
| 264 | - | if err = checked("vkAllocateCommandBuffers", r.dd.AllocateCommandBuffers(r.device, &ai, &r.command)); err != nil { | |
| 240 | + | if err = r.createSlots(); err != nil { | |
| 265 | 241 | return | |
| 266 | 242 | } | |
| 267 | 243 | if err = r.createView(&r.own); err != nil { | |
| 268 | 244 | return | |
| 269 | 245 | } | |
| 270 | - | if err = r.createBlend(); err != nil { | |
| 271 | - | return | |
| 272 | - | } | |
| 273 | - | fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo} | |
| 274 | - | if err = checked("vkCreateFence", r.dd.CreateFence(r.device, &fi, nil, &r.fence)); err != nil { | |
| 246 | + | if err = r.createComposer(); err != nil { | |
| 275 | 247 | return | |
| 276 | 248 | } | |
| 277 | 249 | runtime.KeepAlive(priority) | |
| @@ -325,116 +297,9 @@ func windowColor(id ports.WindowID) [3]uint8 { | |||
| 325 | 297 | return [3]uint8{uint8(a * 255), uint8(b * 255), uint8(c * 255)} | |
| 326 | 298 | } | |
| 327 | 299 | ||
| 328 | - | // ensureStaging grows the mapped transfer buffer before recording any commands. | |
| 329 | - | func (r *Renderer) ensureStaging(size int) error { | |
| 330 | - | if size <= r.stagingSize { | |
| 331 | - | return nil | |
| 332 | - | } | |
| 333 | - | d := r.dd | |
| 334 | - | capacity := size | |
| 335 | - | info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: stagingUsage, SharingMode: vk.SharingModeExclusive} | |
| 336 | - | var buffer vk.Buffer | |
| 337 | - | if err := checked("vkCreateBuffer(staging)", d.CreateBuffer(r.device, &info, nil, &buffer)); err != nil { | |
| 338 | - | return err | |
| 339 | - | } | |
| 340 | - | var memory vk.DeviceMemory | |
| 341 | - | defer func() { | |
| 342 | - | if buffer != 0 { | |
| 343 | - | d.DestroyBuffer(r.device, buffer, nil) | |
| 344 | - | } | |
| 345 | - | if memory != 0 { | |
| 346 | - | d.FreeMemory(r.device, memory, nil) | |
| 347 | - | } | |
| 348 | - | }() | |
| 349 | - | var req vk.MemoryRequirements | |
| 350 | - | d.GetBufferMemoryRequirements(r.device, buffer, &req) | |
| 351 | - | kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit) | |
| 352 | - | if err != nil { | |
| 353 | - | return err | |
| 354 | - | } | |
| 355 | - | alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind} | |
| 356 | - | if err := checked("vkAllocateMemory(staging)", d.AllocateMemory(r.device, &alloc, nil, &memory)); err != nil { | |
| 357 | - | return err | |
| 358 | - | } | |
| 359 | - | if err := checked("vkBindBufferMemory(staging)", d.BindBufferMemory(r.device, buffer, memory, 0)); err != nil { | |
| 360 | - | return err | |
| 361 | - | } | |
| 362 | - | var mapped unsafe.Pointer | |
| 363 | - | if err := checked("vkMapMemory(staging)", d.MapMemory(r.device, memory, 0, vk.DeviceSize(capacity), 0, &mapped)); err != nil { | |
| 364 | - | return err | |
| 365 | - | } | |
| 366 | - | d.UnmapMemory(r.device, r.stagingMemory) | |
| 367 | - | d.DestroyBuffer(r.device, r.staging, nil) | |
| 368 | - | d.FreeMemory(r.device, r.stagingMemory, nil) | |
| 369 | - | r.staging, r.stagingMemory, r.stagingMapped, r.stagingSize = buffer, memory, mapped, capacity | |
| 370 | - | buffer, memory = 0, 0 | |
| 371 | - | return nil | |
| 372 | - | } | |
| 373 | - | ||
| 374 | - | func (r *Renderer) Clear(rgb [3]uint8) error { | |
| 375 | - | return r.render(ports.Scene{Background: fmt.Sprintf("#%02x%02x%02x", rgb[0], rgb[1], rgb[2])}, nil) | |
| 376 | - | } | |
| 377 | - | ||
| 378 | - | // upload is one piece of a frame: a solid fill, client pixels staged from | |
| 379 | - | // a wl_shm pool, or a dmabuf blitted in place. | |
| 380 | - | type upload struct { | |
| 381 | - | rect image.Rectangle | |
| 382 | - | offset int | |
| 383 | - | pixels []byte | |
| 384 | - | stride int | |
| 385 | - | // dst covers src; they differ in size when the buffer is not drawn at | |
| 386 | - | // the physical size (integer-scale clients, rounding). | |
| 387 | - | dst, src image.Rectangle | |
| 388 | - | color [3]uint8 | |
| 389 | - | opaque bool | |
| 390 | - | // blend draws the staged pixels over the target instead of copying. | |
| 391 | - | blend bool | |
| 392 | - | // dma is a client GPU buffer, blitted instead of copied from staging. | |
| 393 | - | dma *imported | |
| 394 | - | // fill is a solid color: staging holds one strip of rows. | |
| 395 | - | fill bool | |
| 396 | - | } | |
| 397 | - | ||
| 398 | - | // size is the staging bytes of an upload: a strip for a fill. | |
| 399 | - | func (u *upload) size() int { | |
| 400 | - | if u.fill { | |
| 401 | - | return u.rect.Dx() * min(u.rect.Dy(), fillRows) * 4 | |
| 402 | - | } | |
| 403 | - | return u.rect.Dx() * u.rect.Dy() * 4 | |
| 404 | - | } | |
| 405 | - | ||
| 406 | - | // stage copies the visible pixels of a client buffer into dst, resampling | |
| 407 | - | // when the buffer is not drawn at its size. | |
| 408 | - | func (u *upload) stage(dst []byte) { | |
| 409 | - | for row := 0; row < u.rect.Dy(); row++ { | |
| 410 | - | line := dst[row*u.rect.Dx()*4 : (row+1)*u.rect.Dx()*4] | |
| 411 | - | y := u.rect.Min.Y + row | |
| 412 | - | if u.dst.Size() == u.src.Size() { | |
| 413 | - | start := (u.src.Min.Y+y-u.dst.Min.Y)*u.stride + (u.src.Min.X+u.rect.Min.X-u.dst.Min.X)*4 | |
| 414 | - | copy(line, u.pixels[start:start+len(line)]) | |
| 415 | - | } else { | |
| 416 | - | // Nearest-neighbour resample, sampling pixel centres. | |
| 417 | - | sy := u.src.Min.Y + ((y-u.dst.Min.Y)*2+1)*u.src.Dy()/(u.dst.Dy()*2) | |
| 418 | - | src := u.pixels[sy*u.stride:] | |
| 419 | - | for i := 0; i < u.rect.Dx(); i++ { | |
| 420 | - | sx := u.src.Min.X + ((u.rect.Min.X+i-u.dst.Min.X)*2+1)*u.src.Dx()/(u.dst.Dx()*2) | |
| 421 | - | copy(line[i*4:i*4+4], src[sx*4:sx*4+4]) | |
| 422 | - | } | |
| 423 | - | } | |
| 424 | - | if u.opaque { | |
| 425 | - | for i := 3; i < len(line); i += 4 { | |
| 426 | - | line[i] = 255 | |
| 427 | - | } | |
| 428 | - | } | |
| 429 | - | } | |
| 430 | - | } | |
| 431 | - | ||
| 432 | - | // Render draws a frame. It still waits for the GPU; done is nil. | |
| 433 | - | func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) (done *os.File, err error) { | |
| 434 | - | return nil, r.render(s, contents) | |
| 435 | - | } | |
| 436 | - | ||
| 437 | - | func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) error { | |
| 300 | + | // draws walks the scene into quads in paint order, copying new wl_shm | |
| 301 | + | // content into GPU buffers on the way. | |
| 302 | + | func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) []draw { | |
| 438 | 303 | // Scene rects are logical; everything below works in physical pixels. | |
| 439 | 304 | scale := s.Scale | |
| 440 | 305 | if scale <= 0 { | |
| @@ -442,21 +307,20 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 442 | 307 | } | |
| 443 | 308 | phys := func(v int) int { return int(math.Round(float64(v) * scale)) } | |
| 444 | 309 | physRect := func(x, y, w, h int) image.Rectangle { return image.Rect(phys(x), phys(y), phys(x+w), phys(y+h)) } | |
| 445 | - | var uploads []upload | |
| 310 | + | var draws []draw | |
| 446 | 311 | bounds := image.Rect(0, 0, r.width, r.height) | |
| 447 | 312 | add := func(rect image.Rectangle, c [3]uint8) { | |
| 448 | 313 | rect = rect.Intersect(bounds) | |
| 449 | 314 | if rect.Empty() { | |
| 450 | 315 | return | |
| 451 | 316 | } | |
| 452 | - | // A fill stages a strip of up to fillRows rows; the GPU copies the | |
| 453 | - | // strip down the rect. | |
| 454 | - | uploads = append(uploads, upload{rect: rect, color: c, fill: true}) | |
| 317 | + | draws = append(draws, r.fillDraw(rect, c)) | |
| 455 | 318 | } | |
| 456 | - | // addContent maps src (buffer pixels) onto dst (physical pixels). | |
| 457 | - | addContent := func(dst, src image.Rectangle, content *ports.SurfaceContent) { | |
| 319 | + | // addContent draws the part of a buffer mapped onto full (physical | |
| 320 | + | // pixels) that dst shows. key names the surface, seq its content. | |
| 321 | + | addContent := func(dst, full image.Rectangle, content *ports.SurfaceContent, key shmKey, seq uint64) { | |
| 458 | 322 | rect := dst.Intersect(bounds) | |
| 459 | - | if rect.Empty() || src.Empty() { | |
| 323 | + | if rect.Empty() { | |
| 460 | 324 | return | |
| 461 | 325 | } | |
| 462 | 326 | if content.DMABuf != nil { | |
| @@ -465,7 +329,9 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 465 | 329 | // The window shows its background until a buffer imports. | |
| 466 | 330 | return | |
| 467 | 331 | } | |
| 468 | - | uploads = append(uploads, upload{rect: rect, dst: dst, src: src, dma: im}) | |
| 332 | + | dr := r.contentDraw(rect, full, content.Width, content.Height, modeImage, content.Opaque) | |
| 333 | + | dr.set, dr.im = im.set, im | |
| 334 | + | draws = append(draws, dr) | |
| 469 | 335 | return | |
| 470 | 336 | } | |
| 471 | 337 | b := content.SHM | |
| @@ -473,11 +339,19 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 473 | 339 | if err != nil { | |
| 474 | 340 | return | |
| 475 | 341 | } | |
| 476 | - | uploads = append(uploads, upload{rect: rect, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, blend: !content.Opaque, dst: dst, src: src}) | |
| 342 | + | st := shmState{pool: b.Pool, offset: b.Offset, stride: b.Stride, w: content.Width, h: content.Height, seq: seq} | |
| 343 | + | c, err := r.shmCopyFor(key, st, pixels) | |
| 344 | + | if err != nil { | |
| 345 | + | return | |
| 346 | + | } | |
| 347 | + | dr := r.contentDraw(rect, full, content.Width, content.Height, modeBuffer, content.Opaque) | |
| 348 | + | dr.set = c.set | |
| 349 | + | dr.pc.buf = [4]uint32{0, uint32(content.Width), uint32(content.Height), 0} | |
| 350 | + | draws = append(draws, dr) | |
| 477 | 351 | } | |
| 478 | 352 | // drawSurface draws one surface buffer with its origin at (x, y) | |
| 479 | 353 | // logical, clipped to clip (logical). | |
| 480 | - | drawSurface := func(content *ports.SurfaceContent, x, y int, clip image.Rectangle) { | |
| 354 | + | drawSurface := func(content *ports.SurfaceContent, x, y int, clip image.Rectangle, key shmKey, seq uint64) { | |
| 481 | 355 | lw, lh := content.LogicalW, content.LogicalH | |
| 482 | 356 | if lw <= 0 || lh <= 0 { | |
| 483 | 357 | lw, lh = content.Width, content.Height | |
| @@ -499,26 +373,24 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 499 | 373 | if dst.Empty() { | |
| 500 | 374 | return | |
| 501 | 375 | } | |
| 502 | - | // The visible part of dst in buffer pixels. | |
| 503 | - | bx := func(v int) int { return (v - full.Min.X) * content.Width / full.Dx() } | |
| 504 | - | by := func(v int) int { return (v - full.Min.Y) * content.Height / full.Dy() } | |
| 505 | - | src := image.Rect(bx(dst.Min.X), by(dst.Min.Y), bx(dst.Max.X), by(dst.Max.Y)) | |
| 506 | - | addContent(dst, src, content) | |
| 376 | + | addContent(dst, full, content, key, seq) | |
| 507 | 377 | } | |
| 508 | 378 | // place draws a surface tree with its window geometry at (x, y) | |
| 509 | 379 | // logical, clipped to w×h logical: client shadows fall outside. | |
| 510 | - | place := func(content *ports.SurfaceContent, x, y, w, h int) { | |
| 380 | + | // Every surface of the tree is keyed by its window and place in it; | |
| 381 | + | // the tree's Seq changes with any of them. | |
| 382 | + | place := func(id ports.WindowID, content *ports.SurfaceContent, x, y, w, h int) { | |
| 511 | 383 | clip := image.Rect(x, y, x+w, y+h) | |
| 512 | 384 | ox, oy := x-content.Geometry.X, y-content.Geometry.Y | |
| 513 | 385 | for i := range content.Children { | |
| 514 | 386 | if ch := &content.Children[i]; ch.Below { | |
| 515 | - | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | |
| 387 | + | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq) | |
| 516 | 388 | } | |
| 517 | 389 | } | |
| 518 | - | drawSurface(content, ox, oy, clip) | |
| 390 | + | drawSurface(content, ox, oy, clip, shmKey{id, 0}, content.Seq) | |
| 519 | 391 | for i := range content.Children { | |
| 520 | 392 | if ch := &content.Children[i]; !ch.Below { | |
| 521 | - | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | |
| 393 | + | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq) | |
| 522 | 394 | } | |
| 523 | 395 | } | |
| 524 | 396 | } | |
| @@ -534,7 +406,7 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 534 | 406 | continue | |
| 535 | 407 | } | |
| 536 | 408 | content := contents[layer.ID] | |
| 537 | - | place(&content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H) | |
| 409 | + | place(layer.ID, &content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H) | |
| 538 | 410 | } | |
| 539 | 411 | } | |
| 540 | 412 | addLayers(false) | |
| @@ -558,7 +430,7 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 558 | 430 | add(body, windowColor(w.ID)) | |
| 559 | 431 | } else { | |
| 560 | 432 | add(body, parseColor(s.Background)) | |
| 561 | - | place(&content, cx, cy, cw, ch) | |
| 433 | + | place(w.ID, &content, cx, cy, cw, ch) | |
| 562 | 434 | } | |
| 563 | 435 | borderColor := s.Border.Inactive | |
| 564 | 436 | if w.Focused { | |
| @@ -583,225 +455,14 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||
| 583 | 455 | for _, w := range s.Windows { | |
| 584 | 456 | if w.Popup && w.OverLayers == overLayers && !w.Hidden && w.Rect.W > 0 && w.Rect.H > 0 { | |
| 585 | 457 | content := contents[w.ID] | |
| 586 | - | place(&content, w.Rect.X, w.Rect.Y, w.Rect.W, w.Rect.H) | |
| 458 | + | place(w.ID, &content, w.Rect.X, w.Rect.Y, w.Rect.W, w.Rect.H) | |
| 587 | 459 | } | |
| 588 | 460 | } | |
| 589 | 461 | } | |
| 590 | 462 | addPopups(false) | |
| 591 | 463 | addLayers(true) | |
| 592 | 464 | addPopups(true) | |
| 593 | - | // Blended pixels are staged first, so the shaders bind only them, | |
| 594 | - | // within the device's storage buffer range; past it they are copied. | |
| 595 | - | // The draw table follows them, then the other uploads. | |
| 596 | - | blends, used := 0, 0 | |
| 597 | - | for k := range uploads { | |
| 598 | - | u := &uploads[k] | |
| 599 | - | if u.blend && used+u.size() > r.maxRange { | |
| 600 | - | u.blend = false | |
| 601 | - | } | |
| 602 | - | if u.blend { | |
| 603 | - | u.offset = used | |
| 604 | - | used += u.size() | |
| 605 | - | blends++ | |
| 606 | - | } | |
| 607 | - | } | |
| 608 | - | pixelBytes := used | |
| 609 | - | tableOffset := (used + r.align - 1) / r.align * r.align | |
| 610 | - | used = tableOffset + blends*drawSize | |
| 611 | - | for k := range uploads { | |
| 612 | - | if u := &uploads[k]; !u.blend && u.dma == nil { | |
| 613 | - | u.offset = used | |
| 614 | - | used += u.size() | |
| 615 | - | } | |
| 616 | - | } | |
| 617 | - | if err := r.ensureStaging(used); err != nil { | |
| 618 | - | return err | |
| 619 | - | } | |
| 620 | - | data := unsafe.Slice((*byte)(r.stagingMapped), r.stagingSize) | |
| 621 | - | if blends > 0 { | |
| 622 | - | r.bindStaging(pixelBytes, tableOffset, blends) | |
| 623 | - | i := 0 | |
| 624 | - | for k := range uploads { | |
| 625 | - | if uploads[k].blend { | |
| 626 | - | r.putDraw(data[tableOffset:], i, &uploads[k]) | |
| 627 | - | i++ | |
| 628 | - | } | |
| 629 | - | } | |
| 630 | - | } | |
| 631 | - | for _, u := range uploads { | |
| 632 | - | if u.dma != nil { | |
| 633 | - | continue | |
| 634 | - | } | |
| 635 | - | if u.fill { | |
| 636 | - | line := data[u.offset : u.offset+u.rect.Dx()*min(u.rect.Dy(), fillRows)*4] | |
| 637 | - | for i := 0; i < len(line); i += 4 { | |
| 638 | - | line[i], line[i+1], line[i+2], line[i+3] = u.color[2], u.color[1], u.color[0], 255 | |
| 639 | - | } | |
| 640 | - | continue | |
| 641 | - | } | |
| 642 | - | dst := data[u.offset : u.offset+u.rect.Dx()*u.rect.Dy()*4] | |
| 643 | - | // The client may shrink its pool under the mapping: its window | |
| 644 | - | // then shows garbage for this frame, never a crash. | |
| 645 | - | copyGuarded(func() { u.stage(dst) }) | |
| 646 | - | } | |
| 647 | - | rgb := parseColor(s.Background) | |
| 648 | - | d := r.dd | |
| 649 | - | if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil { | |
| 650 | - | return err | |
| 651 | - | } | |
| 652 | - | begin := vk.CommandBufferBeginInfo{SType: vk.StructureTypeCommandBufferBeginInfo} | |
| 653 | - | if err := checked("vkBeginCommandBuffer", d.BeginCommandBuffer(r.command, &begin)); err != nil { | |
| 654 | - | return err | |
| 655 | - | } | |
| 656 | - | rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | |
| 657 | - | tg := r.target() | |
| 658 | - | srcStage := vk.PipelineStageFlags(vk.PipelineStageTopOfPipeBit) | |
| 659 | - | srcAccess := vk.AccessFlags(0) | |
| 660 | - | if tg.layout == vk.ImageLayoutTransferSrcOptimal { | |
| 661 | - | srcStage = vk.PipelineStageTransferBit | |
| 662 | - | srcAccess = vk.AccessTransferReadBit | |
| 663 | - | } | |
| 664 | - | // The whole target is cleared: its old contents never matter. | |
| 665 | - | barrier := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, SrcAccessMask: srcAccess, DstAccessMask: vk.AccessTransferWriteBit, OldLayout: tg.layout, NewLayout: vk.ImageLayoutTransferDstOptimal, Image: tg.image, SubresourceRange: rangeInfo} | |
| 666 | - | if tg.exported { | |
| 667 | - | // Acquire from the display (foreign queue family). | |
| 668 | - | barrier.OldLayout = vk.ImageLayoutUndefined | |
| 669 | - | barrier.SrcQueueFamilyIndex, barrier.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |
| 670 | - | srcStage, barrier.SrcAccessMask = vk.PipelineStageTopOfPipeBit, 0 | |
| 671 | - | } | |
| 672 | - | d.CmdPipelineBarrier(r.command, srcStage, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | |
| 673 | - | barrier.SrcQueueFamilyIndex, barrier.DstQueueFamilyIndex = 0, 0 | |
| 674 | - | // VkClearColorValue is a union; UNORM formats read it as float32. | |
| 675 | - | unorm := func(v uint8) uint32 { return math.Float32bits(float32(v) / 255) } | |
| 676 | - | color := vk.ClearColorValue{unorm(rgb[0]), unorm(rgb[1]), unorm(rgb[2]), math.Float32bits(1)} | |
| 677 | - | d.CmdClearColorImage(r.command, tg.image, vk.ImageLayoutTransferDstOptimal, &color, 1, &rangeInfo) | |
| 678 | - | barrier.SrcAccessMask = vk.AccessTransferWriteBit | |
| 679 | - | barrier.DstAccessMask = vk.AccessTransferWriteBit | |
| 680 | - | barrier.OldLayout = vk.ImageLayoutTransferDstOptimal | |
| 681 | - | barrier.NewLayout = vk.ImageLayoutTransferDstOptimal | |
| 682 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | |
| 683 | - | // Client buffers come from the foreign queue family (their driver) and | |
| 684 | - | // go back to it after the frame, so the next frame acquires them again. | |
| 685 | - | var dmas []*imported | |
| 686 | - | for _, u := range uploads { | |
| 687 | - | if u.dma != nil && !slices.Contains(dmas, u.dma) { | |
| 688 | - | dmas = append(dmas, u.dma) | |
| 689 | - | } | |
| 690 | - | } | |
| 691 | - | ownership := func(acquire bool) { | |
| 692 | - | for _, im := range dmas { | |
| 693 | - | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, Image: im.image, SubresourceRange: rangeInfo} | |
| 694 | - | if acquire { | |
| 695 | - | // GENERAL, never UNDEFINED: the client's contents must survive. | |
| 696 | - | b.OldLayout, b.NewLayout = vk.ImageLayoutGeneral, vk.ImageLayoutTransferSrcOptimal | |
| 697 | - | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |
| 698 | - | b.DstAccessMask = vk.AccessTransferReadBit | |
| 699 | - | } else { | |
| 700 | - | b.OldLayout, b.NewLayout = vk.ImageLayoutTransferSrcOptimal, vk.ImageLayoutGeneral | |
| 701 | - | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |
| 702 | - | b.SrcAccessMask = vk.AccessTransferReadBit | |
| 703 | - | } | |
| 704 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |
| 705 | - | } | |
| 706 | - | } | |
| 707 | - | ownership(true) | |
| 708 | - | drawn := 0 // blended uploads drawn so far: their index in the draw table | |
| 709 | - | for i := 0; i < len(uploads); i++ { | |
| 710 | - | f := uploads[i] | |
| 711 | - | if i > 0 && !f.blend { | |
| 712 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | |
| 713 | - | } | |
| 714 | - | if f.blend { | |
| 715 | - | // Consecutive blended uploads share one draw: blending follows | |
| 716 | - | // primitive order within it. | |
| 717 | - | n := 1 | |
| 718 | - | for i+n < len(uploads) && uploads[i+n].blend { | |
| 719 | - | n++ | |
| 720 | - | } | |
| 721 | - | r.drawBlended(tg.image, tg.view, drawn, drawn+n) | |
| 722 | - | drawn += n | |
| 723 | - | i += n - 1 | |
| 724 | - | continue | |
| 725 | - | } | |
| 726 | - | rect := f.rect | |
| 727 | - | if f.dma != nil { | |
| 728 | - | // Map the visible part of dst back onto the buffer. | |
| 729 | - | sx := func(x int) int32 { return int32(f.src.Min.X + (x-f.dst.Min.X)*f.src.Dx()/f.dst.Dx()) } | |
| 730 | - | sy := func(y int) int32 { return int32(f.src.Min.Y + (y-f.dst.Min.Y)*f.src.Dy()/f.dst.Dy()) } | |
| 731 | - | layers := vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1} | |
| 732 | - | blit := vk.ImageBlit{ | |
| 733 | - | SrcSubresource: layers, | |
| 734 | - | SrcOffsets: [2]vk.Offset3D{{X: sx(rect.Min.X), Y: sy(rect.Min.Y)}, {X: sx(rect.Max.X), Y: sy(rect.Max.Y), Z: 1}}, | |
| 735 | - | DstSubresource: layers, | |
| 736 | - | DstOffsets: [2]vk.Offset3D{{X: int32(rect.Min.X), Y: int32(rect.Min.Y)}, {X: int32(rect.Max.X), Y: int32(rect.Max.Y), Z: 1}}, | |
| 737 | - | } | |
| 738 | - | d.CmdBlitImage(r.command, f.dma.image, vk.ImageLayoutTransferSrcOptimal, tg.image, vk.ImageLayoutTransferDstOptimal, 1, &blit, vk.FilterNearest) | |
| 739 | - | continue | |
| 740 | - | } | |
| 741 | - | layers := vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1} | |
| 742 | - | if f.fill { | |
| 743 | - | // Every band reads the same staged strip. | |
| 744 | - | r.fillRegions = r.fillRegions[:0] | |
| 745 | - | for y := rect.Min.Y; y < rect.Max.Y; y += fillRows { | |
| 746 | - | h := min(fillRows, rect.Max.Y-y) | |
| 747 | - | r.fillRegions = append(r.fillRegions, vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: layers, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(h), Depth: 1}}) | |
| 748 | - | } | |
| 749 | - | d.CmdCopyBufferToImage(r.command, r.staging, tg.image, vk.ImageLayoutTransferDstOptimal, uint32(len(r.fillRegions)), &r.fillRegions[0]) | |
| 750 | - | continue | |
| 751 | - | } | |
| 752 | - | region := vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: layers, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(rect.Min.Y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(rect.Dy()), Depth: 1}} | |
| 753 | - | d.CmdCopyBufferToImage(r.command, r.staging, tg.image, vk.ImageLayoutTransferDstOptimal, 1, ®ion) | |
| 754 | - | } | |
| 755 | - | ownership(false) | |
| 756 | - | barrier.SrcAccessMask = vk.AccessTransferWriteBit | |
| 757 | - | barrier.DstAccessMask = vk.AccessTransferReadBit | |
| 758 | - | barrier.OldLayout = vk.ImageLayoutTransferDstOptimal | |
| 759 | - | barrier.NewLayout = vk.ImageLayoutTransferSrcOptimal | |
| 760 | - | if tg.exported { | |
| 761 | - | // Release to the display: KMS scans it out after the fence. | |
| 762 | - | barrier.NewLayout = vk.ImageLayoutGeneral | |
| 763 | - | barrier.DstAccessMask = 0 | |
| 764 | - | barrier.SrcQueueFamilyIndex, barrier.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |
| 765 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &barrier) | |
| 766 | - | } else { | |
| 767 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | |
| 768 | - | } | |
| 769 | - | if err := checked("vkEndCommandBuffer", d.EndCommandBuffer(r.command)); err != nil { | |
| 770 | - | return err | |
| 771 | - | } | |
| 772 | - | // Implicit sync: wait for the client's GPU writes to each buffer. | |
| 773 | - | var waits []vk.Semaphore | |
| 774 | - | for _, im := range dmas { | |
| 775 | - | if sem := r.readFence(im); sem != 0 { | |
| 776 | - | waits = append(waits, sem) | |
| 777 | - | } | |
| 778 | - | } | |
| 779 | - | defer func() { | |
| 780 | - | for _, sem := range waits { | |
| 781 | - | d.DestroySemaphore(r.device, sem, nil) | |
| 782 | - | } | |
| 783 | - | }() | |
| 784 | - | submit := vk.SubmitInfo{SType: vk.StructureTypeSubmitInfo, CommandBufferCount: 1, CommandBuffers: &r.command} | |
| 785 | - | stages := make([]vk.PipelineStageFlags, len(waits)) | |
| 786 | - | if len(waits) > 0 { | |
| 787 | - | for i := range stages { | |
| 788 | - | stages[i] = vk.PipelineStageTransferBit | |
| 789 | - | } | |
| 790 | - | submit.WaitSemaphoreCount, submit.WaitSemaphores, submit.WaitDstStageMask = uint32(len(waits)), &waits[0], &stages[0] | |
| 791 | - | } | |
| 792 | - | if err := checked("vkQueueSubmit", d.QueueSubmit(r.queue, 1, &submit, r.fence)); err != nil { | |
| 793 | - | return err | |
| 794 | - | } | |
| 795 | - | runtime.KeepAlive(stages) | |
| 796 | - | tg.layout = barrier.NewLayout | |
| 797 | - | r.last, r.readBack = tg, false | |
| 798 | - | // The fence wait is the GPU completion ADR 014 requires before a flip. | |
| 799 | - | if err := checked("vkWaitForFences", d.WaitForFences(r.device, 1, &r.fence, 1, math.MaxUint64)); err != nil { | |
| 800 | - | return err | |
| 801 | - | } | |
| 802 | - | r.dropUnused() | |
| 803 | - | r.dropPools() | |
| 804 | - | return checked("vkResetFences", d.ResetFences(r.device, 1, &r.fence)) | |
| 465 | + | return draws | |
| 805 | 466 | } | |
| 806 | 467 | ||
| 807 | 468 | func (r *Renderer) Pixels() *image.RGBA { | |
| @@ -823,52 +484,44 @@ const ( | |||
| 823 | 484 | wholeSize = ^vk.DeviceSize(0) | |
| 824 | 485 | ) | |
| 825 | 486 | ||
| 826 | - | // readback copies the last frame into the host buffer, once per frame. | |
| 487 | + | // readback copies the last frame into the host buffer, once per frame | |
| 488 | + | // (screenshots only). | |
| 827 | 489 | func (r *Renderer) readback() error { | |
| 828 | 490 | if r.readBack || r.last == nil || r.last.image == 0 { | |
| 829 | 491 | return nil | |
| 830 | 492 | } | |
| 831 | - | d := r.dd | |
| 832 | - | if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil { | |
| 833 | - | return err | |
| 834 | - | } | |
| 835 | - | begin := vk.CommandBufferBeginInfo{SType: vk.StructureTypeCommandBufferBeginInfo} | |
| 836 | - | if err := checked("vkBeginCommandBuffer", d.BeginCommandBuffer(r.command, &begin)); err != nil { | |
| 493 | + | // The copy reads the frame: wait for it, and keep the ring ordered. | |
| 494 | + | if err := r.waitFrame(r.submitted); err != nil { | |
| 837 | 495 | return err | |
| 838 | 496 | } | |
| 839 | 497 | t := r.last | |
| 840 | - | rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | |
| 841 | - | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessTransferReadBit, OldLayout: t.layout, NewLayout: vk.ImageLayoutTransferSrcOptimal, Image: t.image, SubresourceRange: rangeInfo} | |
| 842 | - | if t.exported { | |
| 843 | - | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |
| 844 | - | b.SrcAccessMask = 0 | |
| 845 | - | } | |
| 846 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |
| 847 | - | region := vk.BufferImageCopy{ImageSubresource: vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1}, ImageExtent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}} | |
| 848 | - | d.CmdCopyImageToBuffer(r.command, t.image, vk.ImageLayoutTransferSrcOptimal, r.buffer, 1, ®ion) | |
| 849 | - | // Make the copy visible to host reads of the mapped buffer. | |
| 850 | - | hb := vk.BufferMemoryBarrier{SType: vk.StructureTypeBufferMemoryBarrier, SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessHostReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Buffer: r.buffer, Size: wholeSize} | |
| 851 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageHostBit, 0, 0, nil, 1, &hb, 0, nil) | |
| 852 | - | if t.exported { | |
| 853 | - | b.OldLayout, b.NewLayout = vk.ImageLayoutTransferSrcOptimal, vk.ImageLayoutGeneral | |
| 854 | - | b.SrcAccessMask, b.DstAccessMask = vk.AccessTransferReadBit, 0 | |
| 855 | - | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |
| 856 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b) | |
| 857 | - | } else { | |
| 858 | - | t.layout = vk.ImageLayoutTransferSrcOptimal | |
| 859 | - | } | |
| 860 | - | if err := checked("vkEndCommandBuffer", d.EndCommandBuffer(r.command)); err != nil { | |
| 861 | - | return err | |
| 862 | - | } | |
| 863 | - | submit := vk.SubmitInfo{SType: vk.StructureTypeSubmitInfo, CommandBufferCount: 1, CommandBuffers: &r.command} | |
| 864 | - | if err := checked("vkQueueSubmit", d.QueueSubmit(r.queue, 1, &submit, r.fence)); err != nil { | |
| 498 | + | err := r.oneShot(func(cmd vk.CommandBuffer) { | |
| 499 | + | d := r.dd | |
| 500 | + | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, DstAccessMask: vk.AccessTransferReadBit, OldLayout: t.layout, NewLayout: vk.ImageLayoutTransferSrcOptimal, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: t.image, SubresourceRange: colorRange} | |
| 501 | + | if t.exported { | |
| 502 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |
| 503 | + | } | |
| 504 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |
| 505 | + | region := vk.BufferImageCopy{ImageSubresource: vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1}, ImageExtent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}} | |
| 506 | + | d.CmdCopyImageToBuffer(cmd, t.image, vk.ImageLayoutTransferSrcOptimal, r.buffer, 1, ®ion) | |
| 507 | + | // Make the copy visible to host reads of the mapped buffer. | |
| 508 | + | hb := vk.BufferMemoryBarrier{SType: vk.StructureTypeBufferMemoryBarrier, SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessHostReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Buffer: r.buffer, Size: wholeSize} | |
| 509 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit, vk.PipelineStageHostBit, 0, 0, nil, 1, &hb, 0, nil) | |
| 510 | + | if t.exported { | |
| 511 | + | b.OldLayout, b.NewLayout = vk.ImageLayoutTransferSrcOptimal, vk.ImageLayoutGeneral | |
| 512 | + | b.SrcAccessMask, b.DstAccessMask = vk.AccessTransferReadBit, 0 | |
| 513 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |
| 514 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b) | |
| 515 | + | } | |
| 516 | + | }) | |
| 517 | + | if err != nil { | |
| 865 | 518 | return err | |
| 866 | 519 | } | |
| 867 | - | if err := checked("vkWaitForFences", d.WaitForFences(r.device, 1, &r.fence, 1, math.MaxUint64)); err != nil { | |
| 868 | - | return err | |
| 520 | + | if !t.exported { | |
| 521 | + | t.layout = vk.ImageLayoutTransferSrcOptimal | |
| 869 | 522 | } | |
| 870 | 523 | r.readBack = true | |
| 871 | - | return checked("vkResetFences", d.ResetFences(r.device, 1, &r.fence)) | |
| 524 | + | return nil | |
| 872 | 525 | } | |
| 873 | 526 | ||
| 874 | 527 | func (r *Renderer) Close() { | |
| @@ -879,6 +532,7 @@ func (r *Renderer) Close() { | |||
| 879 | 532 | d := r.dd | |
| 880 | 533 | if r.device != 0 { | |
| 881 | 534 | _ = checked("vkDeviceWaitIdle", d.DeviceWaitIdle(r.device)) | |
| 535 | + | r.idle() | |
| 882 | 536 | } | |
| 883 | 537 | r.dropTargets() | |
| 884 | 538 | r.dropCursors() | |
| @@ -888,30 +542,12 @@ func (r *Renderer) Close() { | |||
| 888 | 542 | } | |
| 889 | 543 | r.frame += importTTL + 1 | |
| 890 | 544 | r.dropPools() | |
| 891 | - | if r.fence != 0 { | |
| 892 | - | d.DestroyFence(r.device, r.fence, nil) | |
| 893 | - | r.fence = 0 | |
| 894 | - | } | |
| 895 | - | if r.command != 0 { | |
| 896 | - | d.FreeCommandBuffers(r.device, r.pool, 1, &r.command) | |
| 897 | - | r.command = 0 | |
| 898 | - | } | |
| 545 | + | r.dropShm() | |
| 546 | + | r.destroySlots() | |
| 899 | 547 | if r.pool != 0 { | |
| 900 | 548 | d.DestroyCommandPool(r.device, r.pool, nil) | |
| 901 | 549 | r.pool = 0 | |
| 902 | 550 | } | |
| 903 | - | if r.stagingMapped != nil { | |
| 904 | - | d.UnmapMemory(r.device, r.stagingMemory) | |
| 905 | - | r.stagingMapped = nil | |
| 906 | - | } | |
| 907 | - | if r.staging != 0 { | |
| 908 | - | d.DestroyBuffer(r.device, r.staging, nil) | |
| 909 | - | r.staging = 0 | |
| 910 | - | } | |
| 911 | - | if r.stagingMemory != 0 { | |
| 912 | - | d.FreeMemory(r.device, r.stagingMemory, nil) | |
| 913 | - | r.stagingMemory = 0 | |
| 914 | - | } | |
| 915 | 551 | if r.mapped != nil { | |
| 916 | 552 | d.UnmapMemory(r.device, r.bufferMemory) | |
| 917 | 553 | r.mapped = nil | |
| @@ -924,7 +560,7 @@ func (r *Renderer) Close() { | |||
| 924 | 560 | d.FreeMemory(r.device, r.bufferMemory, nil) | |
| 925 | 561 | r.bufferMemory = 0 | |
| 926 | 562 | } | |
| 927 | - | r.destroyBlend() | |
| 563 | + | r.destroyComposer() | |
| 928 | 564 | r.freeTarget(&r.own) | |
| 929 | 565 | d.DestroyDevice(r.device, nil) | |
| 930 | 566 | r.device = 0 | |
| @@ -6,7 +6,6 @@ import ( | |||||
| 6 | "math" | 6 | "math" | ||
| 7 | "os" | 7 | "os" | ||
| 8 | "runtime" | 8 | "runtime" | ||
| 9 | - | "slices" | |||
| 10 | "strconv" | 9 | "strconv" | ||
| 11 | "unsafe" | 10 | "unsafe" | ||
| 12 | 11 | ||||
| @@ -31,43 +30,41 @@ type Renderer struct { | |||||
| 31 | renderMods []uint64 | 30 | renderMods []uint64 | ||
| 32 | // last is the target of the last frame; readback copies it into | 31 | // last is the target of the last frame; readback copies it into | ||
| 33 | // buffer only when Pixels asks (stale until then). | 32 | // buffer only when Pixels asks (stale until then). | ||
| 34 | - | last *target | 33 | + | last *target |
| 35 | - | readBack bool | 34 | + | readBack bool |
| 36 | - | buffer vk.Buffer | 35 | + | buffer vk.Buffer |
| 37 | - | bufferMemory vk.DeviceMemory | 36 | + | bufferMemory vk.DeviceMemory |
| 38 | - | staging vk.Buffer | 37 | + | mapped unsafe.Pointer |
| 39 | - | stagingMemory vk.DeviceMemory | 38 | + | pool vk.CommandPool |
| 40 | - | stagingMapped unsafe.Pointer | 39 | + | memory vk.PhysicalDeviceMemoryProperties |
| 41 | - | stagingSize int | 40 | + | family uint32 |
| 42 | - | mapped unsafe.Pointer | 41 | + | // Frames in flight (frame.go): frame is the last frame recorded, |
| 43 | - | pool vk.CommandPool | 42 | + | // submitted the last submitted, completed the last known finished. |
| 44 | - | command vk.CommandBuffer | 43 | + | slots [frameSlots]frameSlot |
| 45 | - | fence vk.Fence | 44 | + | frame, submitted, completed uint64 |
| 46 | - | memory vk.PhysicalDeviceMemoryProperties | 45 | + | deferred []deferredFree |
| 47 | - | family uint32 | 46 | + | // syncFD: frames export a sync file (else Render waits for them). |
| 47 | + | syncFD bool | |||
| 48 | // dmabuf is what clients may send; imports are their buffers by ID. | 48 | // dmabuf is what clients may send; imports are their buffers by ID. | ||
| 49 | dmabuf ports.DMABufSupport | 49 | dmabuf ports.DMABufSupport | ||
| 50 | imports map[uint64]*imported | 50 | imports map[uint64]*imported | ||
| 51 | // pools are client wl_shm pools mapped by pool ID. | 51 | // pools are client wl_shm pools mapped by pool ID. | ||
| 52 | pools map[uint64]*mapping | 52 | pools map[uint64]*mapping | ||
| 53 | - | // retired are pool mappings replaced this frame, unmapped after staging. | 53 | + | // retired are pool mappings replaced this frame, unmapped once the |
| 54 | + | // frame's copies are done. | |||
| 54 | retired [][]byte | 55 | retired [][]byte | ||
| 55 | - | frame uint64 | 56 | + | // shm are the GPU copies of wl_shm surfaces (shm.go); copied counts |
| 56 | - | // fillRegions is reused by Render for the bands of solid fills. | 57 | + | // the bytes copied into them, for tests. |
| 57 | - | fillRegions []vk.BufferImageCopy | 58 | + | shm map[shmKey]*shmSurface |
| 59 | + | copied int | |||
| 58 | // cursors are the exported cursor images (CursorBuffers). | 60 | // cursors are the exported cursor images (CursorBuffers). | ||
| 59 | cursors [2]*cursorImage | 61 | cursors [2]*cursorImage | ||
| 60 | - | // blend draws translucent client pixels (blend.go); align is the | 62 | + | // compose draws every frame (compose.go); maxRange bounds one |
| 61 | - | // device's storage buffer offset alignment. | 63 | + | // storage buffer binding. |
| 62 | - | blend blendPipeline | 64 | + | compose composer |
| 63 | - | align int | 65 | + | maxRange int |
| 64 | - | maxRange int // bytes of blended pixels one frame may bind | |||
| 65 | } | 66 | } | ||
| 66 | 67 | ||||
| 67 | - | // fillRows is the height of the strip staged for a solid fill: the CPU | |||
| 68 | - | // writes it once, the GPU copies it down the rect. | |||
| 69 | - | const fillRows = 64 | |||
| 70 | - | ||||
| 71 | func checked(name string, result vk.Result) error { | 68 | func checked(name string, result vk.Result) error { | ||
| 72 | if err := vk.Check(result); err != nil { | 69 | if err := vk.Check(result); err != nil { | ||
| 73 | return fmt.Errorf("%s: %w", name, err) | 70 | return fmt.Errorf("%s: %w", name, err) | ||
| @@ -79,7 +76,7 @@ func New(width, height int) (r *Renderer, err error) { | |||||
| 79 | if width <= 0 || height <= 0 || width > math.MaxUint32 || height > math.MaxUint32 || uint64(width)*uint64(height) > uint64(math.MaxInt/4) { | 76 | if width <= 0 || height <= 0 || width > math.MaxUint32 || height > math.MaxUint32 || uint64(width)*uint64(height) > uint64(math.MaxInt/4) { | ||
| 80 | return nil, fmt.Errorf("invalid renderer dimensions %d x %d", width, height) | 77 | return nil, fmt.Errorf("invalid renderer dimensions %d x %d", width, height) | ||
| 81 | } | 78 | } | ||
| 82 | - | r = &Renderer{width: width, height: height, imports: map[uint64]*imported{}, pools: map[uint64]*mapping{}} | 79 | + | r = &Renderer{width: width, height: height, imports: map[uint64]*imported{}, pools: map[uint64]*mapping{}, shm: map[shmKey]*shmSurface{}} |
| 83 | defer func() { | 80 | defer func() { | ||
| 84 | if err != nil { | 81 | if err != nil { | ||
| 85 | r.Close() | 82 | r.Close() | ||
| @@ -155,7 +152,6 @@ func New(width, height int) (r *Renderer, err error) { | |||||
| 155 | r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory) | 152 | r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory) | ||
| 156 | var properties vk.PhysicalDeviceProperties | 153 | var properties vk.PhysicalDeviceProperties | ||
| 157 | r.id.GetPhysicalDeviceProperties(physical, &properties) | 154 | r.id.GetPhysicalDeviceProperties(physical, &properties) | ||
| 158 | - | r.align = int(max(properties.Limits.MinStorageBufferOffsetAlignment, 4)) | |||
| 159 | r.maxRange = int(properties.Limits.MaxStorageBufferRange) | 155 | r.maxRange = int(properties.Limits.MaxStorageBufferRange) | ||
| 160 | r.family = family | 156 | r.family = family | ||
| 161 | priority := float32(1) | 157 | priority := float32(1) | ||
| @@ -189,6 +185,9 @@ func New(width, height int) (r *Renderer, err error) { | |||||
| 189 | r.dmabuf = sup | 185 | r.dmabuf = sup | ||
| 190 | r.probeRenderModifiers(physical) | 186 | r.probeRenderModifiers(physical) | ||
| 191 | } | 187 | } | ||
| 188 | + | // VK_KHR_external_semaphore_fd (enabled above): frames export | |||
| 189 | + | // their fence as a sync file. | |||
| 190 | + | r.syncFD = r.dd.GetSemaphoreFdKHR != nil | |||
| 192 | } | 191 | } | ||
| 193 | r.dd.GetDeviceQueue(r.device, family, 0, &r.queue) | 192 | r.dd.GetDeviceQueue(r.device, family, 0, &r.queue) | ||
| 194 | extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1} | 193 | extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1} | ||
| @@ -216,7 +215,7 @@ func New(width, height int) (r *Renderer, err error) { | |||||
| 216 | return | 215 | return | ||
| 217 | } | 216 | } | ||
| 218 | r.dd.GetBufferMemoryRequirements(r.device, r.buffer, &req) | 217 | r.dd.GetBufferMemoryRequirements(r.device, r.buffer, &req) | ||
| 219 | - | // Readback (screenshots, fallback) reads this buffer: uncached memory is ~100x slower. | 218 | + | // Readback (screenshots) reads this buffer: uncached memory is ~100x slower. |
| 220 | kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit|vk.MemoryPropertyHostCachedBit) | 219 | kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit|vk.MemoryPropertyHostCachedBit) | ||
| 221 | if err != nil { | 220 | if err != nil { | ||
| 222 | kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit) | 221 | kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit) | ||
| @@ -234,44 +233,17 @@ func New(width, height int) (r *Renderer, err error) { | |||||
| 234 | if err = checked("vkMapMemory", r.dd.MapMemory(r.device, r.bufferMemory, 0, size, 0, &r.mapped)); err != nil { | 233 | if err = checked("vkMapMemory", r.dd.MapMemory(r.device, r.bufferMemory, 0, size, 0, &r.mapped)); err != nil { | ||
| 235 | return | 234 | return | ||
| 236 | } | 235 | } | ||
| 237 | - | // Start with two frame-sized regions; later frames grow staging on demand. | |||
| 238 | - | stagingSize := size * 2 | |||
| 239 | - | r.stagingSize = int(stagingSize) | |||
| 240 | - | stagingInfo := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: stagingSize, Usage: stagingUsage, SharingMode: vk.SharingModeExclusive} | |||
| 241 | - | if err = checked("vkCreateBuffer(staging)", r.dd.CreateBuffer(r.device, &stagingInfo, nil, &r.staging)); err != nil { | |||
| 242 | - | return | |||
| 243 | - | } | |||
| 244 | - | r.dd.GetBufferMemoryRequirements(r.device, r.staging, &req) | |||
| 245 | - | kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit) | |||
| 246 | - | if err != nil { | |||
| 247 | - | return | |||
| 248 | - | } | |||
| 249 | - | alloc = vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind} | |||
| 250 | - | if err = checked("vkAllocateMemory(staging)", r.dd.AllocateMemory(r.device, &alloc, nil, &r.stagingMemory)); err != nil { | |||
| 251 | - | return | |||
| 252 | - | } | |||
| 253 | - | if err = checked("vkBindBufferMemory(staging)", r.dd.BindBufferMemory(r.device, r.staging, r.stagingMemory, 0)); err != nil { | |||
| 254 | - | return | |||
| 255 | - | } | |||
| 256 | - | if err = checked("vkMapMemory(staging)", r.dd.MapMemory(r.device, r.stagingMemory, 0, stagingSize, 0, &r.stagingMapped)); err != nil { | |||
| 257 | - | return | |||
| 258 | - | } | |||
| 259 | pi := vk.CommandPoolCreateInfo{SType: vk.StructureTypeCommandPoolCreateInfo, Flags: vk.CommandPoolCreateResetCommandBufferBit, QueueFamilyIndex: family} | 236 | pi := vk.CommandPoolCreateInfo{SType: vk.StructureTypeCommandPoolCreateInfo, Flags: vk.CommandPoolCreateResetCommandBufferBit, QueueFamilyIndex: family} | ||
| 260 | if err = checked("vkCreateCommandPool", r.dd.CreateCommandPool(r.device, &pi, nil, &r.pool)); err != nil { | 237 | if err = checked("vkCreateCommandPool", r.dd.CreateCommandPool(r.device, &pi, nil, &r.pool)); err != nil { | ||
| 261 | return | 238 | return | ||
| 262 | } | 239 | } | ||
| 263 | - | ai := vk.CommandBufferAllocateInfo{SType: vk.StructureTypeCommandBufferAllocateInfo, CommandPool: r.pool, Level: vk.CommandBufferLevelPrimary, CommandBufferCount: 1} | 240 | + | if err = r.createSlots(); err != nil { |
| 264 | - | if err = checked("vkAllocateCommandBuffers", r.dd.AllocateCommandBuffers(r.device, &ai, &r.command)); err != nil { | |||
| 265 | return | 241 | return | ||
| 266 | } | 242 | } | ||
| 267 | if err = r.createView(&r.own); err != nil { | 243 | if err = r.createView(&r.own); err != nil { | ||
| 268 | return | 244 | return | ||
| 269 | } | 245 | } | ||
| 270 | - | if err = r.createBlend(); err != nil { | 246 | + | if err = r.createComposer(); err != nil { |
| 271 | - | return | |||
| 272 | - | } | |||
| 273 | - | fi := vk.FenceCreateInfo{SType: vk.StructureTypeFenceCreateInfo} | |||
| 274 | - | if err = checked("vkCreateFence", r.dd.CreateFence(r.device, &fi, nil, &r.fence)); err != nil { | |||
| 275 | return | 247 | return | ||
| 276 | } | 248 | } | ||
| 277 | runtime.KeepAlive(priority) | 249 | runtime.KeepAlive(priority) | ||
| @@ -325,116 +297,9 @@ func windowColor(id ports.WindowID) [3]uint8 { | |||||
| 325 | return [3]uint8{uint8(a * 255), uint8(b * 255), uint8(c * 255)} | 297 | return [3]uint8{uint8(a * 255), uint8(b * 255), uint8(c * 255)} | ||
| 326 | } | 298 | } | ||
| 327 | 299 | ||||
| 328 | - | // ensureStaging grows the mapped transfer buffer before recording any commands. | 300 | + | // draws walks the scene into quads in paint order, copying new wl_shm |
| 329 | - | func (r *Renderer) ensureStaging(size int) error { | 301 | + | // content into GPU buffers on the way. |
| 330 | - | if size <= r.stagingSize { | 302 | + | func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) []draw { |
| 331 | - | return nil | |||
| 332 | - | } | |||
| 333 | - | d := r.dd | |||
| 334 | - | capacity := size | |||
| 335 | - | info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(capacity), Usage: stagingUsage, SharingMode: vk.SharingModeExclusive} | |||
| 336 | - | var buffer vk.Buffer | |||
| 337 | - | if err := checked("vkCreateBuffer(staging)", d.CreateBuffer(r.device, &info, nil, &buffer)); err != nil { | |||
| 338 | - | return err | |||
| 339 | - | } | |||
| 340 | - | var memory vk.DeviceMemory | |||
| 341 | - | defer func() { | |||
| 342 | - | if buffer != 0 { | |||
| 343 | - | d.DestroyBuffer(r.device, buffer, nil) | |||
| 344 | - | } | |||
| 345 | - | if memory != 0 { | |||
| 346 | - | d.FreeMemory(r.device, memory, nil) | |||
| 347 | - | } | |||
| 348 | - | }() | |||
| 349 | - | var req vk.MemoryRequirements | |||
| 350 | - | d.GetBufferMemoryRequirements(r.device, buffer, &req) | |||
| 351 | - | kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit) | |||
| 352 | - | if err != nil { | |||
| 353 | - | return err | |||
| 354 | - | } | |||
| 355 | - | alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind} | |||
| 356 | - | if err := checked("vkAllocateMemory(staging)", d.AllocateMemory(r.device, &alloc, nil, &memory)); err != nil { | |||
| 357 | - | return err | |||
| 358 | - | } | |||
| 359 | - | if err := checked("vkBindBufferMemory(staging)", d.BindBufferMemory(r.device, buffer, memory, 0)); err != nil { | |||
| 360 | - | return err | |||
| 361 | - | } | |||
| 362 | - | var mapped unsafe.Pointer | |||
| 363 | - | if err := checked("vkMapMemory(staging)", d.MapMemory(r.device, memory, 0, vk.DeviceSize(capacity), 0, &mapped)); err != nil { | |||
| 364 | - | return err | |||
| 365 | - | } | |||
| 366 | - | d.UnmapMemory(r.device, r.stagingMemory) | |||
| 367 | - | d.DestroyBuffer(r.device, r.staging, nil) | |||
| 368 | - | d.FreeMemory(r.device, r.stagingMemory, nil) | |||
| 369 | - | r.staging, r.stagingMemory, r.stagingMapped, r.stagingSize = buffer, memory, mapped, capacity | |||
| 370 | - | buffer, memory = 0, 0 | |||
| 371 | - | return nil | |||
| 372 | - | } | |||
| 373 | - | ||||
| 374 | - | func (r *Renderer) Clear(rgb [3]uint8) error { | |||
| 375 | - | return r.render(ports.Scene{Background: fmt.Sprintf("#%02x%02x%02x", rgb[0], rgb[1], rgb[2])}, nil) | |||
| 376 | - | } | |||
| 377 | - | ||||
| 378 | - | // upload is one piece of a frame: a solid fill, client pixels staged from | |||
| 379 | - | // a wl_shm pool, or a dmabuf blitted in place. | |||
| 380 | - | type upload struct { | |||
| 381 | - | rect image.Rectangle | |||
| 382 | - | offset int | |||
| 383 | - | pixels []byte | |||
| 384 | - | stride int | |||
| 385 | - | // dst covers src; they differ in size when the buffer is not drawn at | |||
| 386 | - | // the physical size (integer-scale clients, rounding). | |||
| 387 | - | dst, src image.Rectangle | |||
| 388 | - | color [3]uint8 | |||
| 389 | - | opaque bool | |||
| 390 | - | // blend draws the staged pixels over the target instead of copying. | |||
| 391 | - | blend bool | |||
| 392 | - | // dma is a client GPU buffer, blitted instead of copied from staging. | |||
| 393 | - | dma *imported | |||
| 394 | - | // fill is a solid color: staging holds one strip of rows. | |||
| 395 | - | fill bool | |||
| 396 | - | } | |||
| 397 | - | ||||
| 398 | - | // size is the staging bytes of an upload: a strip for a fill. | |||
| 399 | - | func (u *upload) size() int { | |||
| 400 | - | if u.fill { | |||
| 401 | - | return u.rect.Dx() * min(u.rect.Dy(), fillRows) * 4 | |||
| 402 | - | } | |||
| 403 | - | return u.rect.Dx() * u.rect.Dy() * 4 | |||
| 404 | - | } | |||
| 405 | - | ||||
| 406 | - | // stage copies the visible pixels of a client buffer into dst, resampling | |||
| 407 | - | // when the buffer is not drawn at its size. | |||
| 408 | - | func (u *upload) stage(dst []byte) { | |||
| 409 | - | for row := 0; row < u.rect.Dy(); row++ { | |||
| 410 | - | line := dst[row*u.rect.Dx()*4 : (row+1)*u.rect.Dx()*4] | |||
| 411 | - | y := u.rect.Min.Y + row | |||
| 412 | - | if u.dst.Size() == u.src.Size() { | |||
| 413 | - | start := (u.src.Min.Y+y-u.dst.Min.Y)*u.stride + (u.src.Min.X+u.rect.Min.X-u.dst.Min.X)*4 | |||
| 414 | - | copy(line, u.pixels[start:start+len(line)]) | |||
| 415 | - | } else { | |||
| 416 | - | // Nearest-neighbour resample, sampling pixel centres. | |||
| 417 | - | sy := u.src.Min.Y + ((y-u.dst.Min.Y)*2+1)*u.src.Dy()/(u.dst.Dy()*2) | |||
| 418 | - | src := u.pixels[sy*u.stride:] | |||
| 419 | - | for i := 0; i < u.rect.Dx(); i++ { | |||
| 420 | - | sx := u.src.Min.X + ((u.rect.Min.X+i-u.dst.Min.X)*2+1)*u.src.Dx()/(u.dst.Dx()*2) | |||
| 421 | - | copy(line[i*4:i*4+4], src[sx*4:sx*4+4]) | |||
| 422 | - | } | |||
| 423 | - | } | |||
| 424 | - | if u.opaque { | |||
| 425 | - | for i := 3; i < len(line); i += 4 { | |||
| 426 | - | line[i] = 255 | |||
| 427 | - | } | |||
| 428 | - | } | |||
| 429 | - | } | |||
| 430 | - | } | |||
| 431 | - | ||||
| 432 | - | // Render draws a frame. It still waits for the GPU; done is nil. | |||
| 433 | - | func (r *Renderer) Render(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) (done *os.File, err error) { | |||
| 434 | - | return nil, r.render(s, contents) | |||
| 435 | - | } | |||
| 436 | - | ||||
| 437 | - | func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent) error { | |||
| 438 | // Scene rects are logical; everything below works in physical pixels. | 303 | // Scene rects are logical; everything below works in physical pixels. | ||
| 439 | scale := s.Scale | 304 | scale := s.Scale | ||
| 440 | if scale <= 0 { | 305 | if scale <= 0 { | ||
| @@ -442,21 +307,20 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 442 | } | 307 | } | ||
| 443 | phys := func(v int) int { return int(math.Round(float64(v) * scale)) } | 308 | phys := func(v int) int { return int(math.Round(float64(v) * scale)) } | ||
| 444 | physRect := func(x, y, w, h int) image.Rectangle { return image.Rect(phys(x), phys(y), phys(x+w), phys(y+h)) } | 309 | physRect := func(x, y, w, h int) image.Rectangle { return image.Rect(phys(x), phys(y), phys(x+w), phys(y+h)) } | ||
| 445 | - | var uploads []upload | 310 | + | var draws []draw |
| 446 | bounds := image.Rect(0, 0, r.width, r.height) | 311 | bounds := image.Rect(0, 0, r.width, r.height) | ||
| 447 | add := func(rect image.Rectangle, c [3]uint8) { | 312 | add := func(rect image.Rectangle, c [3]uint8) { | ||
| 448 | rect = rect.Intersect(bounds) | 313 | rect = rect.Intersect(bounds) | ||
| 449 | if rect.Empty() { | 314 | if rect.Empty() { | ||
| 450 | return | 315 | return | ||
| 451 | } | 316 | } | ||
| 452 | - | // A fill stages a strip of up to fillRows rows; the GPU copies the | 317 | + | draws = append(draws, r.fillDraw(rect, c)) |
| 453 | - | // strip down the rect. | |||
| 454 | - | uploads = append(uploads, upload{rect: rect, color: c, fill: true}) | |||
| 455 | } | 318 | } | ||
| 456 | - | // addContent maps src (buffer pixels) onto dst (physical pixels). | 319 | + | // addContent draws the part of a buffer mapped onto full (physical |
| 457 | - | addContent := func(dst, src image.Rectangle, content *ports.SurfaceContent) { | 320 | + | // pixels) that dst shows. key names the surface, seq its content. |
| 321 | + | addContent := func(dst, full image.Rectangle, content *ports.SurfaceContent, key shmKey, seq uint64) { | |||
| 458 | rect := dst.Intersect(bounds) | 322 | rect := dst.Intersect(bounds) | ||
| 459 | - | if rect.Empty() || src.Empty() { | 323 | + | if rect.Empty() { |
| 460 | return | 324 | return | ||
| 461 | } | 325 | } | ||
| 462 | if content.DMABuf != nil { | 326 | if content.DMABuf != nil { | ||
| @@ -465,7 +329,9 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 465 | // The window shows its background until a buffer imports. | 329 | // The window shows its background until a buffer imports. | ||
| 466 | return | 330 | return | ||
| 467 | } | 331 | } | ||
| 468 | - | uploads = append(uploads, upload{rect: rect, dst: dst, src: src, dma: im}) | 332 | + | dr := r.contentDraw(rect, full, content.Width, content.Height, modeImage, content.Opaque) |
| 333 | + | dr.set, dr.im = im.set, im | |||
| 334 | + | draws = append(draws, dr) | |||
| 469 | return | 335 | return | ||
| 470 | } | 336 | } | ||
| 471 | b := content.SHM | 337 | b := content.SHM | ||
| @@ -473,11 +339,19 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 473 | if err != nil { | 339 | if err != nil { | ||
| 474 | return | 340 | return | ||
| 475 | } | 341 | } | ||
| 476 | - | uploads = append(uploads, upload{rect: rect, pixels: pixels[b.Offset:], stride: b.Stride, opaque: content.Opaque, blend: !content.Opaque, dst: dst, src: src}) | 342 | + | st := shmState{pool: b.Pool, offset: b.Offset, stride: b.Stride, w: content.Width, h: content.Height, seq: seq} |
| 343 | + | c, err := r.shmCopyFor(key, st, pixels) | |||
| 344 | + | if err != nil { | |||
| 345 | + | return | |||
| 346 | + | } | |||
| 347 | + | dr := r.contentDraw(rect, full, content.Width, content.Height, modeBuffer, content.Opaque) | |||
| 348 | + | dr.set = c.set | |||
| 349 | + | dr.pc.buf = [4]uint32{0, uint32(content.Width), uint32(content.Height), 0} | |||
| 350 | + | draws = append(draws, dr) | |||
| 477 | } | 351 | } | ||
| 478 | // drawSurface draws one surface buffer with its origin at (x, y) | 352 | // drawSurface draws one surface buffer with its origin at (x, y) | ||
| 479 | // logical, clipped to clip (logical). | 353 | // logical, clipped to clip (logical). | ||
| 480 | - | drawSurface := func(content *ports.SurfaceContent, x, y int, clip image.Rectangle) { | 354 | + | drawSurface := func(content *ports.SurfaceContent, x, y int, clip image.Rectangle, key shmKey, seq uint64) { |
| 481 | lw, lh := content.LogicalW, content.LogicalH | 355 | lw, lh := content.LogicalW, content.LogicalH | ||
| 482 | if lw <= 0 || lh <= 0 { | 356 | if lw <= 0 || lh <= 0 { | ||
| 483 | lw, lh = content.Width, content.Height | 357 | lw, lh = content.Width, content.Height | ||
| @@ -499,26 +373,24 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 499 | if dst.Empty() { | 373 | if dst.Empty() { | ||
| 500 | return | 374 | return | ||
| 501 | } | 375 | } | ||
| 502 | - | // The visible part of dst in buffer pixels. | 376 | + | addContent(dst, full, content, key, seq) |
| 503 | - | bx := func(v int) int { return (v - full.Min.X) * content.Width / full.Dx() } | |||
| 504 | - | by := func(v int) int { return (v - full.Min.Y) * content.Height / full.Dy() } | |||
| 505 | - | src := image.Rect(bx(dst.Min.X), by(dst.Min.Y), bx(dst.Max.X), by(dst.Max.Y)) | |||
| 506 | - | addContent(dst, src, content) | |||
| 507 | } | 377 | } | ||
| 508 | // place draws a surface tree with its window geometry at (x, y) | 378 | // place draws a surface tree with its window geometry at (x, y) | ||
| 509 | // logical, clipped to w×h logical: client shadows fall outside. | 379 | // logical, clipped to w×h logical: client shadows fall outside. | ||
| 510 | - | place := func(content *ports.SurfaceContent, x, y, w, h int) { | 380 | + | // Every surface of the tree is keyed by its window and place in it; |
| 381 | + | // the tree's Seq changes with any of them. | |||
| 382 | + | place := func(id ports.WindowID, content *ports.SurfaceContent, x, y, w, h int) { | |||
| 511 | clip := image.Rect(x, y, x+w, y+h) | 383 | clip := image.Rect(x, y, x+w, y+h) | ||
| 512 | ox, oy := x-content.Geometry.X, y-content.Geometry.Y | 384 | ox, oy := x-content.Geometry.X, y-content.Geometry.Y | ||
| 513 | for i := range content.Children { | 385 | for i := range content.Children { | ||
| 514 | if ch := &content.Children[i]; ch.Below { | 386 | if ch := &content.Children[i]; ch.Below { | ||
| 515 | - | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | 387 | + | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq) |
| 516 | } | 388 | } | ||
| 517 | } | 389 | } | ||
| 518 | - | drawSurface(content, ox, oy, clip) | 390 | + | drawSurface(content, ox, oy, clip, shmKey{id, 0}, content.Seq) |
| 519 | for i := range content.Children { | 391 | for i := range content.Children { | ||
| 520 | if ch := &content.Children[i]; !ch.Below { | 392 | if ch := &content.Children[i]; !ch.Below { | ||
| 521 | - | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip) | 393 | + | drawSurface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq) |
| 522 | } | 394 | } | ||
| 523 | } | 395 | } | ||
| 524 | } | 396 | } | ||
| @@ -534,7 +406,7 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 534 | continue | 406 | continue | ||
| 535 | } | 407 | } | ||
| 536 | content := contents[layer.ID] | 408 | content := contents[layer.ID] | ||
| 537 | - | place(&content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H) | 409 | + | place(layer.ID, &content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H) |
| 538 | } | 410 | } | ||
| 539 | } | 411 | } | ||
| 540 | addLayers(false) | 412 | addLayers(false) | ||
| @@ -558,7 +430,7 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 558 | add(body, windowColor(w.ID)) | 430 | add(body, windowColor(w.ID)) | ||
| 559 | } else { | 431 | } else { | ||
| 560 | add(body, parseColor(s.Background)) | 432 | add(body, parseColor(s.Background)) | ||
| 561 | - | place(&content, cx, cy, cw, ch) | 433 | + | place(w.ID, &content, cx, cy, cw, ch) |
| 562 | } | 434 | } | ||
| 563 | borderColor := s.Border.Inactive | 435 | borderColor := s.Border.Inactive | ||
| 564 | if w.Focused { | 436 | if w.Focused { | ||
| @@ -583,225 +455,14 @@ func (r *Renderer) render(s ports.Scene, contents map[ports.WindowID]ports.Surfa | |||||
| 583 | for _, w := range s.Windows { | 455 | for _, w := range s.Windows { | ||
| 584 | if w.Popup && w.OverLayers == overLayers && !w.Hidden && w.Rect.W > 0 && w.Rect.H > 0 { | 456 | if w.Popup && w.OverLayers == overLayers && !w.Hidden && w.Rect.W > 0 && w.Rect.H > 0 { | ||
| 585 | content := contents[w.ID] | 457 | content := contents[w.ID] | ||
| 586 | - | place(&content, w.Rect.X, w.Rect.Y, w.Rect.W, w.Rect.H) | 458 | + | place(w.ID, &content, w.Rect.X, w.Rect.Y, w.Rect.W, w.Rect.H) |
| 587 | } | 459 | } | ||
| 588 | } | 460 | } | ||
| 589 | } | 461 | } | ||
| 590 | addPopups(false) | 462 | addPopups(false) | ||
| 591 | addLayers(true) | 463 | addLayers(true) | ||
| 592 | addPopups(true) | 464 | addPopups(true) | ||
| 593 | - | // Blended pixels are staged first, so the shaders bind only them, | 465 | + | return draws |
| 594 | - | // within the device's storage buffer range; past it they are copied. | |||
| 595 | - | // The draw table follows them, then the other uploads. | |||
| 596 | - | blends, used := 0, 0 | |||
| 597 | - | for k := range uploads { | |||
| 598 | - | u := &uploads[k] | |||
| 599 | - | if u.blend && used+u.size() > r.maxRange { | |||
| 600 | - | u.blend = false | |||
| 601 | - | } | |||
| 602 | - | if u.blend { | |||
| 603 | - | u.offset = used | |||
| 604 | - | used += u.size() | |||
| 605 | - | blends++ | |||
| 606 | - | } | |||
| 607 | - | } | |||
| 608 | - | pixelBytes := used | |||
| 609 | - | tableOffset := (used + r.align - 1) / r.align * r.align | |||
| 610 | - | used = tableOffset + blends*drawSize | |||
| 611 | - | for k := range uploads { | |||
| 612 | - | if u := &uploads[k]; !u.blend && u.dma == nil { | |||
| 613 | - | u.offset = used | |||
| 614 | - | used += u.size() | |||
| 615 | - | } | |||
| 616 | - | } | |||
| 617 | - | if err := r.ensureStaging(used); err != nil { | |||
| 618 | - | return err | |||
| 619 | - | } | |||
| 620 | - | data := unsafe.Slice((*byte)(r.stagingMapped), r.stagingSize) | |||
| 621 | - | if blends > 0 { | |||
| 622 | - | r.bindStaging(pixelBytes, tableOffset, blends) | |||
| 623 | - | i := 0 | |||
| 624 | - | for k := range uploads { | |||
| 625 | - | if uploads[k].blend { | |||
| 626 | - | r.putDraw(data[tableOffset:], i, &uploads[k]) | |||
| 627 | - | i++ | |||
| 628 | - | } | |||
| 629 | - | } | |||
| 630 | - | } | |||
| 631 | - | for _, u := range uploads { | |||
| 632 | - | if u.dma != nil { | |||
| 633 | - | continue | |||
| 634 | - | } | |||
| 635 | - | if u.fill { | |||
| 636 | - | line := data[u.offset : u.offset+u.rect.Dx()*min(u.rect.Dy(), fillRows)*4] | |||
| 637 | - | for i := 0; i < len(line); i += 4 { | |||
| 638 | - | line[i], line[i+1], line[i+2], line[i+3] = u.color[2], u.color[1], u.color[0], 255 | |||
| 639 | - | } | |||
| 640 | - | continue | |||
| 641 | - | } | |||
| 642 | - | dst := data[u.offset : u.offset+u.rect.Dx()*u.rect.Dy()*4] | |||
| 643 | - | // The client may shrink its pool under the mapping: its window | |||
| 644 | - | // then shows garbage for this frame, never a crash. | |||
| 645 | - | copyGuarded(func() { u.stage(dst) }) | |||
| 646 | - | } | |||
| 647 | - | rgb := parseColor(s.Background) | |||
| 648 | - | d := r.dd | |||
| 649 | - | if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil { | |||
| 650 | - | return err | |||
| 651 | - | } | |||
| 652 | - | begin := vk.CommandBufferBeginInfo{SType: vk.StructureTypeCommandBufferBeginInfo} | |||
| 653 | - | if err := checked("vkBeginCommandBuffer", d.BeginCommandBuffer(r.command, &begin)); err != nil { | |||
| 654 | - | return err | |||
| 655 | - | } | |||
| 656 | - | rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | |||
| 657 | - | tg := r.target() | |||
| 658 | - | srcStage := vk.PipelineStageFlags(vk.PipelineStageTopOfPipeBit) | |||
| 659 | - | srcAccess := vk.AccessFlags(0) | |||
| 660 | - | if tg.layout == vk.ImageLayoutTransferSrcOptimal { | |||
| 661 | - | srcStage = vk.PipelineStageTransferBit | |||
| 662 | - | srcAccess = vk.AccessTransferReadBit | |||
| 663 | - | } | |||
| 664 | - | // The whole target is cleared: its old contents never matter. | |||
| 665 | - | barrier := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, SrcAccessMask: srcAccess, DstAccessMask: vk.AccessTransferWriteBit, OldLayout: tg.layout, NewLayout: vk.ImageLayoutTransferDstOptimal, Image: tg.image, SubresourceRange: rangeInfo} | |||
| 666 | - | if tg.exported { | |||
| 667 | - | // Acquire from the display (foreign queue family). | |||
| 668 | - | barrier.OldLayout = vk.ImageLayoutUndefined | |||
| 669 | - | barrier.SrcQueueFamilyIndex, barrier.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |||
| 670 | - | srcStage, barrier.SrcAccessMask = vk.PipelineStageTopOfPipeBit, 0 | |||
| 671 | - | } | |||
| 672 | - | d.CmdPipelineBarrier(r.command, srcStage, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | |||
| 673 | - | barrier.SrcQueueFamilyIndex, barrier.DstQueueFamilyIndex = 0, 0 | |||
| 674 | - | // VkClearColorValue is a union; UNORM formats read it as float32. | |||
| 675 | - | unorm := func(v uint8) uint32 { return math.Float32bits(float32(v) / 255) } | |||
| 676 | - | color := vk.ClearColorValue{unorm(rgb[0]), unorm(rgb[1]), unorm(rgb[2]), math.Float32bits(1)} | |||
| 677 | - | d.CmdClearColorImage(r.command, tg.image, vk.ImageLayoutTransferDstOptimal, &color, 1, &rangeInfo) | |||
| 678 | - | barrier.SrcAccessMask = vk.AccessTransferWriteBit | |||
| 679 | - | barrier.DstAccessMask = vk.AccessTransferWriteBit | |||
| 680 | - | barrier.OldLayout = vk.ImageLayoutTransferDstOptimal | |||
| 681 | - | barrier.NewLayout = vk.ImageLayoutTransferDstOptimal | |||
| 682 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | |||
| 683 | - | // Client buffers come from the foreign queue family (their driver) and | |||
| 684 | - | // go back to it after the frame, so the next frame acquires them again. | |||
| 685 | - | var dmas []*imported | |||
| 686 | - | for _, u := range uploads { | |||
| 687 | - | if u.dma != nil && !slices.Contains(dmas, u.dma) { | |||
| 688 | - | dmas = append(dmas, u.dma) | |||
| 689 | - | } | |||
| 690 | - | } | |||
| 691 | - | ownership := func(acquire bool) { | |||
| 692 | - | for _, im := range dmas { | |||
| 693 | - | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, Image: im.image, SubresourceRange: rangeInfo} | |||
| 694 | - | if acquire { | |||
| 695 | - | // GENERAL, never UNDEFINED: the client's contents must survive. | |||
| 696 | - | b.OldLayout, b.NewLayout = vk.ImageLayoutGeneral, vk.ImageLayoutTransferSrcOptimal | |||
| 697 | - | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | |||
| 698 | - | b.DstAccessMask = vk.AccessTransferReadBit | |||
| 699 | - | } else { | |||
| 700 | - | b.OldLayout, b.NewLayout = vk.ImageLayoutTransferSrcOptimal, vk.ImageLayoutGeneral | |||
| 701 | - | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |||
| 702 | - | b.SrcAccessMask = vk.AccessTransferReadBit | |||
| 703 | - | } | |||
| 704 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | |||
| 705 | - | } | |||
| 706 | - | } | |||
| 707 | - | ownership(true) | |||
| 708 | - | drawn := 0 // blended uploads drawn so far: their index in the draw table | |||
| 709 | - | for i := 0; i < len(uploads); i++ { | |||
| 710 | - | f := uploads[i] | |||
| 711 | - | if i > 0 && !f.blend { | |||
| 712 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | |||
| 713 | - | } | |||
| 714 | - | if f.blend { | |||
| 715 | - | // Consecutive blended uploads share one draw: blending follows | |||
| 716 | - | // primitive order within it. | |||
| 717 | - | n := 1 | |||
| 718 | - | for i+n < len(uploads) && uploads[i+n].blend { | |||
| 719 | - | n++ | |||
| 720 | - | } | |||
| 721 | - | r.drawBlended(tg.image, tg.view, drawn, drawn+n) | |||
| 722 | - | drawn += n | |||
| 723 | - | i += n - 1 | |||
| 724 | - | continue | |||
| 725 | - | } | |||
| 726 | - | rect := f.rect | |||
| 727 | - | if f.dma != nil { | |||
| 728 | - | // Map the visible part of dst back onto the buffer. | |||
| 729 | - | sx := func(x int) int32 { return int32(f.src.Min.X + (x-f.dst.Min.X)*f.src.Dx()/f.dst.Dx()) } | |||
| 730 | - | sy := func(y int) int32 { return int32(f.src.Min.Y + (y-f.dst.Min.Y)*f.src.Dy()/f.dst.Dy()) } | |||
| 731 | - | layers := vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1} | |||
| 732 | - | blit := vk.ImageBlit{ | |||
| 733 | - | SrcSubresource: layers, | |||
| 734 | - | SrcOffsets: [2]vk.Offset3D{{X: sx(rect.Min.X), Y: sy(rect.Min.Y)}, {X: sx(rect.Max.X), Y: sy(rect.Max.Y), Z: 1}}, | |||
| 735 | - | DstSubresource: layers, | |||
| 736 | - | DstOffsets: [2]vk.Offset3D{{X: int32(rect.Min.X), Y: int32(rect.Min.Y)}, {X: int32(rect.Max.X), Y: int32(rect.Max.Y), Z: 1}}, | |||
| 737 | - | } | |||
| 738 | - | d.CmdBlitImage(r.command, f.dma.image, vk.ImageLayoutTransferSrcOptimal, tg.image, vk.ImageLayoutTransferDstOptimal, 1, &blit, vk.FilterNearest) | |||
| 739 | - | continue | |||
| 740 | - | } | |||
| 741 | - | layers := vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1} | |||
| 742 | - | if f.fill { | |||
| 743 | - | // Every band reads the same staged strip. | |||
| 744 | - | r.fillRegions = r.fillRegions[:0] | |||
| 745 | - | for y := rect.Min.Y; y < rect.Max.Y; y += fillRows { | |||
| 746 | - | h := min(fillRows, rect.Max.Y-y) | |||
| 747 | - | r.fillRegions = append(r.fillRegions, vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: layers, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(h), Depth: 1}}) | |||
| 748 | - | } | |||
| 749 | - | d.CmdCopyBufferToImage(r.command, r.staging, tg.image, vk.ImageLayoutTransferDstOptimal, uint32(len(r.fillRegions)), &r.fillRegions[0]) | |||
| 750 | - | continue | |||
| 751 | - | } | |||
| 752 | - | region := vk.BufferImageCopy{BufferOffset: vk.DeviceSize(f.offset), ImageSubresource: layers, ImageOffset: vk.Offset3D{X: int32(rect.Min.X), Y: int32(rect.Min.Y)}, ImageExtent: vk.Extent3D{Width: uint32(rect.Dx()), Height: uint32(rect.Dy()), Depth: 1}} | |||
| 753 | - | d.CmdCopyBufferToImage(r.command, r.staging, tg.image, vk.ImageLayoutTransferDstOptimal, 1, ®ion) | |||
| 754 | - | } | |||
| 755 | - | ownership(false) | |||
| 756 | - | barrier.SrcAccessMask = vk.AccessTransferWriteBit | |||
| 757 | - | barrier.DstAccessMask = vk.AccessTransferReadBit | |||
| 758 | - | barrier.OldLayout = vk.ImageLayoutTransferDstOptimal | |||
| 759 | - | barrier.NewLayout = vk.ImageLayoutTransferSrcOptimal | |||
| 760 | - | if tg.exported { | |||
| 761 | - | // Release to the display: KMS scans it out after the fence. | |||
| 762 | - | barrier.NewLayout = vk.ImageLayoutGeneral | |||
| 763 | - | barrier.DstAccessMask = 0 | |||
| 764 | - | barrier.SrcQueueFamilyIndex, barrier.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | |||
| 765 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &barrier) | |||
| 766 | - | } else { | |||
| 767 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &barrier) | |||
| 768 | - | } | |||
| 769 | - | if err := checked("vkEndCommandBuffer", d.EndCommandBuffer(r.command)); err != nil { | |||
| 770 | - | return err | |||
| 771 | - | } | |||
| 772 | - | // Implicit sync: wait for the client's GPU writes to each buffer. | |||
| 773 | - | var waits []vk.Semaphore | |||
| 774 | - | for _, im := range dmas { | |||
| 775 | - | if sem := r.readFence(im); sem != 0 { | |||
| 776 | - | waits = append(waits, sem) | |||
| 777 | - | } | |||
| 778 | - | } | |||
| 779 | - | defer func() { | |||
| 780 | - | for _, sem := range waits { | |||
| 781 | - | d.DestroySemaphore(r.device, sem, nil) | |||
| 782 | - | } | |||
| 783 | - | }() | |||
| 784 | - | submit := vk.SubmitInfo{SType: vk.StructureTypeSubmitInfo, CommandBufferCount: 1, CommandBuffers: &r.command} | |||
| 785 | - | stages := make([]vk.PipelineStageFlags, len(waits)) | |||
| 786 | - | if len(waits) > 0 { | |||
| 787 | - | for i := range stages { | |||
| 788 | - | stages[i] = vk.PipelineStageTransferBit | |||
| 789 | - | } | |||
| 790 | - | submit.WaitSemaphoreCount, submit.WaitSemaphores, submit.WaitDstStageMask = uint32(len(waits)), &waits[0], &stages[0] | |||
| 791 | - | } | |||
| 792 | - | if err := checked("vkQueueSubmit", d.QueueSubmit(r.queue, 1, &submit, r.fence)); err != nil { | |||
| 793 | - | return err | |||
| 794 | - | } | |||
| 795 | - | runtime.KeepAlive(stages) | |||
| 796 | - | tg.layout = barrier.NewLayout | |||
| 797 | - | r.last, r.readBack = tg, false | |||
| 798 | - | // The fence wait is the GPU completion ADR 014 requires before a flip. | |||
| 799 | - | if err := checked("vkWaitForFences", d.WaitForFences(r.device, 1, &r.fence, 1, math.MaxUint64)); err != nil { | |||
| 800 | - | return err | |||
| 801 | - | } | |||
| 802 | - | r.dropUnused() | |||
| 803 | - | r.dropPools() | |||
| 804 | - | return checked("vkResetFences", d.ResetFences(r.device, 1, &r.fence)) | |||
| 805 | } | 466 | } | ||
| 806 | 467 | ||||
| 807 | func (r *Renderer) Pixels() *image.RGBA { | 468 | func (r *Renderer) Pixels() *image.RGBA { | ||
| @@ -823,52 +484,44 @@ const ( | |||||
| 823 | wholeSize = ^vk.DeviceSize(0) | 484 | wholeSize = ^vk.DeviceSize(0) | ||
| 824 | ) | 485 | ) | ||
| 825 | 486 | ||||
| 826 | - | // readback copies the last frame into the host buffer, once per frame. | 487 | + | // readback copies the last frame into the host buffer, once per frame |
| 488 | + | // (screenshots only). | |||
| 827 | func (r *Renderer) readback() error { | 489 | func (r *Renderer) readback() error { | ||
| 828 | if r.readBack || r.last == nil || r.last.image == 0 { | 490 | if r.readBack || r.last == nil || r.last.image == 0 { | ||
| 829 | return nil | 491 | return nil | ||
| 830 | } | 492 | } | ||
| 831 | - | d := r.dd | 493 | + | // The copy reads the frame: wait for it, and keep the ring ordered. |
| 832 | - | if err := checked("vkResetCommandBuffer", d.ResetCommandBuffer(r.command, 0)); err != nil { | 494 | + | if err := r.waitFrame(r.submitted); err != nil { |
| 833 | - | return err | |||
| 834 | - | } | |||
| 835 | - | begin := vk.CommandBufferBeginInfo{SType: vk.StructureTypeCommandBufferBeginInfo} | |||
| 836 | - | if err := checked("vkBeginCommandBuffer", d.BeginCommandBuffer(r.command, &begin)); err != nil { | |||
| 837 | return err | 495 | return err | ||
| 838 | } | 496 | } | ||
| 839 | t := r.last | 497 | t := r.last | ||
| 840 | - | rangeInfo := vk.ImageSubresourceRange{AspectMask: vk.ImageAspectColorBit, LevelCount: 1, LayerCount: 1} | 498 | + | err := r.oneShot(func(cmd vk.CommandBuffer) { |
| 841 | - | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessTransferReadBit, OldLayout: t.layout, NewLayout: vk.ImageLayoutTransferSrcOptimal, Image: t.image, SubresourceRange: rangeInfo} | 499 | + | d := r.dd |
| 842 | - | if t.exported { | 500 | + | b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, DstAccessMask: vk.AccessTransferReadBit, OldLayout: t.layout, NewLayout: vk.ImageLayoutTransferSrcOptimal, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: t.image, SubresourceRange: colorRange} |
| 843 | - | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family | 501 | + | if t.exported { |
| 844 | - | b.SrcAccessMask = 0 | 502 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family |
| 845 | - | } | 503 | + | } |
| 846 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) | 504 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b) |
| 847 | - | region := vk.BufferImageCopy{ImageSubresource: vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1}, ImageExtent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}} | 505 | + | region := vk.BufferImageCopy{ImageSubresource: vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1}, ImageExtent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}} |
| 848 | - | d.CmdCopyImageToBuffer(r.command, t.image, vk.ImageLayoutTransferSrcOptimal, r.buffer, 1, ®ion) | 506 | + | d.CmdCopyImageToBuffer(cmd, t.image, vk.ImageLayoutTransferSrcOptimal, r.buffer, 1, ®ion) |
| 849 | - | // Make the copy visible to host reads of the mapped buffer. | 507 | + | // Make the copy visible to host reads of the mapped buffer. |
| 850 | - | hb := vk.BufferMemoryBarrier{SType: vk.StructureTypeBufferMemoryBarrier, SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessHostReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Buffer: r.buffer, Size: wholeSize} | 508 | + | hb := vk.BufferMemoryBarrier{SType: vk.StructureTypeBufferMemoryBarrier, SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessHostReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Buffer: r.buffer, Size: wholeSize} |
| 851 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageHostBit, 0, 0, nil, 1, &hb, 0, nil) | 509 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit, vk.PipelineStageHostBit, 0, 0, nil, 1, &hb, 0, nil) |
| 852 | - | if t.exported { | 510 | + | if t.exported { |
| 853 | - | b.OldLayout, b.NewLayout = vk.ImageLayoutTransferSrcOptimal, vk.ImageLayoutGeneral | 511 | + | b.OldLayout, b.NewLayout = vk.ImageLayoutTransferSrcOptimal, vk.ImageLayoutGeneral |
| 854 | - | b.SrcAccessMask, b.DstAccessMask = vk.AccessTransferReadBit, 0 | 512 | + | b.SrcAccessMask, b.DstAccessMask = vk.AccessTransferReadBit, 0 |
| 855 | - | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT | 513 | + | b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT |
| 856 | - | d.CmdPipelineBarrier(r.command, vk.PipelineStageTransferBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b) | 514 | + | d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b) |
| 857 | - | } else { | 515 | + | } |
| 858 | - | t.layout = vk.ImageLayoutTransferSrcOptimal | 516 | + | }) |
| 859 | - | } | 517 | + | if err != nil { |
| 860 | - | if err := checked("vkEndCommandBuffer", d.EndCommandBuffer(r.command)); err != nil { | |||
| 861 | - | return err | |||
| 862 | - | } | |||
| 863 | - | submit := vk.SubmitInfo{SType: vk.StructureTypeSubmitInfo, CommandBufferCount: 1, CommandBuffers: &r.command} | |||
| 864 | - | if err := checked("vkQueueSubmit", d.QueueSubmit(r.queue, 1, &submit, r.fence)); err != nil { | |||
| 865 | return err | 518 | return err | ||
| 866 | } | 519 | } | ||
| 867 | - | if err := checked("vkWaitForFences", d.WaitForFences(r.device, 1, &r.fence, 1, math.MaxUint64)); err != nil { | 520 | + | if !t.exported { |
| 868 | - | return err | 521 | + | t.layout = vk.ImageLayoutTransferSrcOptimal |
| 869 | } | 522 | } | ||
| 870 | r.readBack = true | 523 | r.readBack = true | ||
| 871 | - | return checked("vkResetFences", d.ResetFences(r.device, 1, &r.fence)) | 524 | + | return nil |
| 872 | } | 525 | } | ||
| 873 | 526 | ||||
| 874 | func (r *Renderer) Close() { | 527 | func (r *Renderer) Close() { | ||
| @@ -879,6 +532,7 @@ func (r *Renderer) Close() { | |||||
| 879 | d := r.dd | 532 | d := r.dd | ||
| 880 | if r.device != 0 { | 533 | if r.device != 0 { | ||
| 881 | _ = checked("vkDeviceWaitIdle", d.DeviceWaitIdle(r.device)) | 534 | _ = checked("vkDeviceWaitIdle", d.DeviceWaitIdle(r.device)) | ||
| 535 | + | r.idle() | |||
| 882 | } | 536 | } | ||
| 883 | r.dropTargets() | 537 | r.dropTargets() | ||
| 884 | r.dropCursors() | 538 | r.dropCursors() | ||
| @@ -888,30 +542,12 @@ func (r *Renderer) Close() { | |||||
| 888 | } | 542 | } | ||
| 889 | r.frame += importTTL + 1 | 543 | r.frame += importTTL + 1 | ||
| 890 | r.dropPools() | 544 | r.dropPools() | ||
| 891 | - | if r.fence != 0 { | 545 | + | r.dropShm() |
| 892 | - | d.DestroyFence(r.device, r.fence, nil) | 546 | + | r.destroySlots() |
| 893 | - | r.fence = 0 | |||
| 894 | - | } | |||
| 895 | - | if r.command != 0 { | |||
| 896 | - | d.FreeCommandBuffers(r.device, r.pool, 1, &r.command) | |||
| 897 | - | r.command = 0 | |||
| 898 | - | } | |||
| 899 | if r.pool != 0 { | 547 | if r.pool != 0 { | ||
| 900 | d.DestroyCommandPool(r.device, r.pool, nil) | 548 | d.DestroyCommandPool(r.device, r.pool, nil) | ||
| 901 | r.pool = 0 | 549 | r.pool = 0 | ||
| 902 | } | 550 | } | ||
| 903 | - | if r.stagingMapped != nil { | |||
| 904 | - | d.UnmapMemory(r.device, r.stagingMemory) | |||
| 905 | - | r.stagingMapped = nil | |||
| 906 | - | } | |||
| 907 | - | if r.staging != 0 { | |||
| 908 | - | d.DestroyBuffer(r.device, r.staging, nil) | |||
| 909 | - | r.staging = 0 | |||
| 910 | - | } | |||
| 911 | - | if r.stagingMemory != 0 { | |||
| 912 | - | d.FreeMemory(r.device, r.stagingMemory, nil) | |||
| 913 | - | r.stagingMemory = 0 | |||
| 914 | - | } | |||
| 915 | if r.mapped != nil { | 551 | if r.mapped != nil { | ||
| 916 | d.UnmapMemory(r.device, r.bufferMemory) | 552 | d.UnmapMemory(r.device, r.bufferMemory) | ||
| 917 | r.mapped = nil | 553 | r.mapped = nil | ||
| @@ -924,7 +560,7 @@ func (r *Renderer) Close() { | |||||
| 924 | d.FreeMemory(r.device, r.bufferMemory, nil) | 560 | d.FreeMemory(r.device, r.bufferMemory, nil) | ||
| 925 | r.bufferMemory = 0 | 561 | r.bufferMemory = 0 | ||
| 926 | } | 562 | } | ||
| 927 | - | r.destroyBlend() | 563 | + | r.destroyComposer() |
| 928 | r.freeTarget(&r.own) | 564 | r.freeTarget(&r.own) | ||
| 929 | d.DestroyDevice(r.device, nil) | 565 | d.DestroyDevice(r.device, nil) | ||
| 930 | r.device = 0 | 566 | r.device = 0 | ||
M internal/adapters/vulkan/renderer_test.go +2 -3
| @@ -335,10 +335,9 @@ func TestRendererGeometryAndSubsurfaces(t *testing.T) { | |||
| 335 | 335 | } | |
| 336 | 336 | } | |
| 337 | 337 | ||
| 338 | - | // A fill taller than one staged strip is copied down in bands: every row | |
| 339 | - | // through the last one gets the color. | |
| 338 | + | // A tall fill covers every row through the last one. | |
| 340 | 339 | func TestRendererTallFill(t *testing.T) { | |
| 341 | - | h := fillRows*2 + 7 | |
| 340 | + | h := 135 | |
| 342 | 341 | r, err := New(8, h) | |
| 343 | 342 | if err != nil { | |
| 344 | 343 | t.Skipf("Vulkan unavailable: %v", err) | |
| @@ -335,10 +335,9 @@ func TestRendererGeometryAndSubsurfaces(t *testing.T) { | |||||
| 335 | } | 335 | } | ||
| 336 | } | 336 | } | ||
| 337 | 337 | ||||
| 338 | - | // A fill taller than one staged strip is copied down in bands: every row | 338 | + | // A tall fill covers every row through the last one. |
| 339 | - | // through the last one gets the color. | |||
| 340 | func TestRendererTallFill(t *testing.T) { | 339 | func TestRendererTallFill(t *testing.T) { | ||
| 341 | - | h := fillRows*2 + 7 | 340 | + | h := 135 |
| 342 | r, err := New(8, h) | 341 | r, err := New(8, h) | ||
| 343 | if err != nil { | 342 | if err != nil { | ||
| 344 | t.Skipf("Vulkan unavailable: %v", err) | 343 | t.Skipf("Vulkan unavailable: %v", err) | ||
D internal/adapters/vulkan/shaders/blend.frag +0 -22
| @@ -1,22 +0,0 @@ | |||
| 1 | - | #version 450 | |
| 2 | - | // Reads the staged B8G8R8A8 pixel under the fragment: premultiplied, as | |
| 3 | - | // Wayland ARGB buffers are, so the pipeline blends ONE, ONE_MINUS_SRC_ALPHA. | |
| 4 | - | ||
| 5 | - | struct Draw { | |
| 6 | - | ivec4 rect; | |
| 7 | - | uvec4 pixels; | |
| 8 | - | }; | |
| 9 | - | ||
| 10 | - | layout(std430, set = 0, binding = 0) readonly buffer Pixels { uint pixels[]; }; | |
| 11 | - | layout(std430, set = 0, binding = 1) readonly buffer Draws { Draw draws[]; }; | |
| 12 | - | ||
| 13 | - | layout(location = 0) flat in uint draw; | |
| 14 | - | layout(location = 0) out vec4 color; | |
| 15 | - | ||
| 16 | - | void main() { | |
| 17 | - | Draw d = draws[draw]; | |
| 18 | - | ivec2 p = ivec2(gl_FragCoord.xy) - d.rect.xy; | |
| 19 | - | // Little-endian B,G,R,A bytes: unpack yields (B, G, R, A). | |
| 20 | - | vec4 bgra = unpackUnorm4x8(pixels[d.pixels.x + uint(p.y) * d.pixels.y + uint(p.x)]); | |
| 21 | - | color = bgra.zyxw; | |
| 22 | - | } | |
| @@ -1,22 +0,0 @@ | |||||
| 1 | - | #version 450 | |||
| 2 | - | // Reads the staged B8G8R8A8 pixel under the fragment: premultiplied, as | |||
| 3 | - | // Wayland ARGB buffers are, so the pipeline blends ONE, ONE_MINUS_SRC_ALPHA. | |||
| 4 | - | ||||
| 5 | - | struct Draw { | |||
| 6 | - | ivec4 rect; | |||
| 7 | - | uvec4 pixels; | |||
| 8 | - | }; | |||
| 9 | - | ||||
| 10 | - | layout(std430, set = 0, binding = 0) readonly buffer Pixels { uint pixels[]; }; | |||
| 11 | - | layout(std430, set = 0, binding = 1) readonly buffer Draws { Draw draws[]; }; | |||
| 12 | - | ||||
| 13 | - | layout(location = 0) flat in uint draw; | |||
| 14 | - | layout(location = 0) out vec4 color; | |||
| 15 | - | ||||
| 16 | - | void main() { | |||
| 17 | - | Draw d = draws[draw]; | |||
| 18 | - | ivec2 p = ivec2(gl_FragCoord.xy) - d.rect.xy; | |||
| 19 | - | // Little-endian B,G,R,A bytes: unpack yields (B, G, R, A). | |||
| 20 | - | vec4 bgra = unpackUnorm4x8(pixels[d.pixels.x + uint(p.y) * d.pixels.y + uint(p.x)]); | |||
| 21 | - | color = bgra.zyxw; | |||
| 22 | - | } | |||
D internal/adapters/vulkan/shaders/blend.frag.spv +0 -0 binary file: no text diff
binary file: no text diff
D internal/adapters/vulkan/shaders/blend.vert +0 -21
| @@ -1,21 +0,0 @@ | |||
| 1 | - | #version 450 | |
| 2 | - | // One quad per instance: the instance indexes the draw table. | |
| 3 | - | ||
| 4 | - | struct Draw { | |
| 5 | - | ivec4 rect; // x0, y0, x1, y1 in target pixels | |
| 6 | - | uvec4 pixels; // first pixel word in staging, row width, target w, target h | |
| 7 | - | }; | |
| 8 | - | ||
| 9 | - | layout(std430, set = 0, binding = 1) readonly buffer Draws { Draw draws[]; }; | |
| 10 | - | ||
| 11 | - | layout(location = 0) flat out uint draw; | |
| 12 | - | ||
| 13 | - | void main() { | |
| 14 | - | Draw d = draws[gl_InstanceIndex]; | |
| 15 | - | // Two triangles: 0,1,2 and 2,1,3 of the corners. | |
| 16 | - | const ivec2 corners[6] = ivec2[](ivec2(0, 0), ivec2(1, 0), ivec2(0, 1), ivec2(0, 1), ivec2(1, 0), ivec2(1, 1)); | |
| 17 | - | ivec2 c = corners[gl_VertexIndex]; | |
| 18 | - | vec2 p = vec2(c.x == 0 ? d.rect.x : d.rect.z, c.y == 0 ? d.rect.y : d.rect.w); | |
| 19 | - | gl_Position = vec4(p / vec2(d.pixels.zw) * 2.0 - 1.0, 0.0, 1.0); | |
| 20 | - | draw = uint(gl_InstanceIndex); | |
| 21 | - | } | |
| @@ -1,21 +0,0 @@ | |||||
| 1 | - | #version 450 | |||
| 2 | - | // One quad per instance: the instance indexes the draw table. | |||
| 3 | - | ||||
| 4 | - | struct Draw { | |||
| 5 | - | ivec4 rect; // x0, y0, x1, y1 in target pixels | |||
| 6 | - | uvec4 pixels; // first pixel word in staging, row width, target w, target h | |||
| 7 | - | }; | |||
| 8 | - | ||||
| 9 | - | layout(std430, set = 0, binding = 1) readonly buffer Draws { Draw draws[]; }; | |||
| 10 | - | ||||
| 11 | - | layout(location = 0) flat out uint draw; | |||
| 12 | - | ||||
| 13 | - | void main() { | |||
| 14 | - | Draw d = draws[gl_InstanceIndex]; | |||
| 15 | - | // Two triangles: 0,1,2 and 2,1,3 of the corners. | |||
| 16 | - | const ivec2 corners[6] = ivec2[](ivec2(0, 0), ivec2(1, 0), ivec2(0, 1), ivec2(0, 1), ivec2(1, 0), ivec2(1, 1)); | |||
| 17 | - | ivec2 c = corners[gl_VertexIndex]; | |||
| 18 | - | vec2 p = vec2(c.x == 0 ? d.rect.x : d.rect.z, c.y == 0 ? d.rect.y : d.rect.w); | |||
| 19 | - | gl_Position = vec4(p / vec2(d.pixels.zw) * 2.0 - 1.0, 0.0, 1.0); | |||
| 20 | - | draw = uint(gl_InstanceIndex); | |||
| 21 | - | } | |||
D internal/adapters/vulkan/shaders/blend.vert.spv +0 -0 binary file: no text diff
binary file: no text diff
A internal/adapters/vulkan/shaders/compose.frag +55 -0
| @@ -0,0 +1,55 @@ | |||
| 1 | + | #version 450 | |
| 2 | + | // Draws a solid fill, a client dmabuf (sampled image) or a client wl_shm | |
| 3 | + | // buffer (B8G8R8A8 words in a storage buffer). Client pixels are | |
| 4 | + | // premultiplied, as Wayland buffers are: the pipeline blends ONE, | |
| 5 | + | // ONE_MINUS_SRC_ALPHA. Scaled draws filter linearly; 1:1 draws read the | |
| 6 | + | // texel under the fragment, pixel exact. | |
| 7 | + | ||
| 8 | + | layout(push_constant) uniform Draw { | |
| 9 | + | ivec4 rect; | |
| 10 | + | vec4 map; | |
| 11 | + | vec4 color; | |
| 12 | + | uvec4 buf; | |
| 13 | + | uvec4 misc; | |
| 14 | + | } d; | |
| 15 | + | ||
| 16 | + | layout(set = 0, binding = 0) uniform sampler2D tex; | |
| 17 | + | layout(std430, set = 0, binding = 1) readonly buffer Pixels { uint pixels[]; }; | |
| 18 | + | ||
| 19 | + | layout(location = 0) out vec4 color; | |
| 20 | + | ||
| 21 | + | const uint modeSolid = 0u; | |
| 22 | + | const uint modeImage = 1u; | |
| 23 | + | const uint flagOpaque = 1u; | |
| 24 | + | const uint flagExact = 2u; | |
| 25 | + | ||
| 26 | + | // texel reads buffer pixel p, clamped to the buffer (clamp to edge). | |
| 27 | + | vec4 texel(ivec2 p) { | |
| 28 | + | p = clamp(p, ivec2(0), ivec2(d.buf.yz) - 1); | |
| 29 | + | // Little-endian B,G,R,A bytes: unpack yields (B, G, R, A). | |
| 30 | + | return unpackUnorm4x8(pixels[d.buf.x + uint(p.y) * d.buf.y + uint(p.x)]).zyxw; | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | void main() { | |
| 34 | + | vec2 src = d.map.xy + gl_FragCoord.xy * d.map.zw; | |
| 35 | + | bool exact = (d.misc.y & flagExact) != 0u; | |
| 36 | + | vec4 c; | |
| 37 | + | if (d.misc.x == modeSolid) { | |
| 38 | + | c = d.color; | |
| 39 | + | } else if (d.misc.x == modeImage) { | |
| 40 | + | ivec2 size = textureSize(tex, 0); | |
| 41 | + | c = exact ? texelFetch(tex, clamp(ivec2(floor(src)), ivec2(0), size - 1), 0) : texture(tex, src / vec2(size)); | |
| 42 | + | } else if (exact) { | |
| 43 | + | c = texel(ivec2(floor(src))); | |
| 44 | + | } else { | |
| 45 | + | // Bilinear: four texel reads around the sample point. | |
| 46 | + | vec2 p = src - 0.5; | |
| 47 | + | ivec2 i = ivec2(floor(p)); | |
| 48 | + | vec2 f = p - vec2(i); | |
| 49 | + | c = mix(mix(texel(i), texel(i + ivec2(1, 0)), f.x), mix(texel(i + ivec2(0, 1)), texel(i + ivec2(1, 1)), f.x), f.y); | |
| 50 | + | } | |
| 51 | + | if ((d.misc.y & flagOpaque) != 0u) { | |
| 52 | + | c.a = 1.0; | |
| 53 | + | } | |
| 54 | + | color = c; | |
| 55 | + | } | |
| @@ -0,0 +1,55 @@ | |||||
| 1 | + | #version 450 | |||
| 2 | + | // Draws a solid fill, a client dmabuf (sampled image) or a client wl_shm | |||
| 3 | + | // buffer (B8G8R8A8 words in a storage buffer). Client pixels are | |||
| 4 | + | // premultiplied, as Wayland buffers are: the pipeline blends ONE, | |||
| 5 | + | // ONE_MINUS_SRC_ALPHA. Scaled draws filter linearly; 1:1 draws read the | |||
| 6 | + | // texel under the fragment, pixel exact. | |||
| 7 | + | ||||
| 8 | + | layout(push_constant) uniform Draw { | |||
| 9 | + | ivec4 rect; | |||
| 10 | + | vec4 map; | |||
| 11 | + | vec4 color; | |||
| 12 | + | uvec4 buf; | |||
| 13 | + | uvec4 misc; | |||
| 14 | + | } d; | |||
| 15 | + | ||||
| 16 | + | layout(set = 0, binding = 0) uniform sampler2D tex; | |||
| 17 | + | layout(std430, set = 0, binding = 1) readonly buffer Pixels { uint pixels[]; }; | |||
| 18 | + | ||||
| 19 | + | layout(location = 0) out vec4 color; | |||
| 20 | + | ||||
| 21 | + | const uint modeSolid = 0u; | |||
| 22 | + | const uint modeImage = 1u; | |||
| 23 | + | const uint flagOpaque = 1u; | |||
| 24 | + | const uint flagExact = 2u; | |||
| 25 | + | ||||
| 26 | + | // texel reads buffer pixel p, clamped to the buffer (clamp to edge). | |||
| 27 | + | vec4 texel(ivec2 p) { | |||
| 28 | + | p = clamp(p, ivec2(0), ivec2(d.buf.yz) - 1); | |||
| 29 | + | // Little-endian B,G,R,A bytes: unpack yields (B, G, R, A). | |||
| 30 | + | return unpackUnorm4x8(pixels[d.buf.x + uint(p.y) * d.buf.y + uint(p.x)]).zyxw; | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | void main() { | |||
| 34 | + | vec2 src = d.map.xy + gl_FragCoord.xy * d.map.zw; | |||
| 35 | + | bool exact = (d.misc.y & flagExact) != 0u; | |||
| 36 | + | vec4 c; | |||
| 37 | + | if (d.misc.x == modeSolid) { | |||
| 38 | + | c = d.color; | |||
| 39 | + | } else if (d.misc.x == modeImage) { | |||
| 40 | + | ivec2 size = textureSize(tex, 0); | |||
| 41 | + | c = exact ? texelFetch(tex, clamp(ivec2(floor(src)), ivec2(0), size - 1), 0) : texture(tex, src / vec2(size)); | |||
| 42 | + | } else if (exact) { | |||
| 43 | + | c = texel(ivec2(floor(src))); | |||
| 44 | + | } else { | |||
| 45 | + | // Bilinear: four texel reads around the sample point. | |||
| 46 | + | vec2 p = src - 0.5; | |||
| 47 | + | ivec2 i = ivec2(floor(p)); | |||
| 48 | + | vec2 f = p - vec2(i); | |||
| 49 | + | c = mix(mix(texel(i), texel(i + ivec2(1, 0)), f.x), mix(texel(i + ivec2(0, 1)), texel(i + ivec2(1, 1)), f.x), f.y); | |||
| 50 | + | } | |||
| 51 | + | if ((d.misc.y & flagOpaque) != 0u) { | |||
| 52 | + | c.a = 1.0; | |||
| 53 | + | } | |||
| 54 | + | color = c; | |||
| 55 | + | } | |||
A internal/adapters/vulkan/shaders/compose.frag.spv +0 -0 binary file: no text diff
binary file: no text diff
A internal/adapters/vulkan/shaders/compose.vert +17 -0
| @@ -0,0 +1,17 @@ | |||
| 1 | + | #version 450 | |
| 2 | + | // One quad per draw: two triangles over the visible target rect. | |
| 3 | + | ||
| 4 | + | layout(push_constant) uniform Draw { | |
| 5 | + | ivec4 rect; // visible x0, y0, x1, y1 in target pixels | |
| 6 | + | vec4 map; // source texel = map.xy + fragment centre * map.zw | |
| 7 | + | vec4 color; // solid fills: premultiplied RGBA | |
| 8 | + | uvec4 buf; // buffer draws: first word, row width, height, unused | |
| 9 | + | uvec4 misc; // mode, flags, target width, target height | |
| 10 | + | } d; | |
| 11 | + | ||
| 12 | + | void main() { | |
| 13 | + | const ivec2 corners[6] = ivec2[](ivec2(0, 0), ivec2(1, 0), ivec2(0, 1), ivec2(0, 1), ivec2(1, 0), ivec2(1, 1)); | |
| 14 | + | ivec2 c = corners[gl_VertexIndex]; | |
| 15 | + | vec2 p = vec2(c.x == 0 ? d.rect.x : d.rect.z, c.y == 0 ? d.rect.y : d.rect.w); | |
| 16 | + | gl_Position = vec4(p / vec2(d.misc.zw) * 2.0 - 1.0, 0.0, 1.0); | |
| 17 | + | } | |
| @@ -0,0 +1,17 @@ | |||||
| 1 | + | #version 450 | |||
| 2 | + | // One quad per draw: two triangles over the visible target rect. | |||
| 3 | + | ||||
| 4 | + | layout(push_constant) uniform Draw { | |||
| 5 | + | ivec4 rect; // visible x0, y0, x1, y1 in target pixels | |||
| 6 | + | vec4 map; // source texel = map.xy + fragment centre * map.zw | |||
| 7 | + | vec4 color; // solid fills: premultiplied RGBA | |||
| 8 | + | uvec4 buf; // buffer draws: first word, row width, height, unused | |||
| 9 | + | uvec4 misc; // mode, flags, target width, target height | |||
| 10 | + | } d; | |||
| 11 | + | ||||
| 12 | + | void main() { | |||
| 13 | + | const ivec2 corners[6] = ivec2[](ivec2(0, 0), ivec2(1, 0), ivec2(0, 1), ivec2(0, 1), ivec2(1, 0), ivec2(1, 1)); | |||
| 14 | + | ivec2 c = corners[gl_VertexIndex]; | |||
| 15 | + | vec2 p = vec2(c.x == 0 ? d.rect.x : d.rect.z, c.y == 0 ? d.rect.y : d.rect.w); | |||
| 16 | + | gl_Position = vec4(p / vec2(d.misc.zw) * 2.0 - 1.0, 0.0, 1.0); | |||
| 17 | + | } | |||
A internal/adapters/vulkan/shaders/compose.vert.spv +0 -0 binary file: no text diff
binary file: no text diff
M internal/adapters/vulkan/shm.go +183 -7
| @@ -3,15 +3,20 @@ package vulkan | |||
| 3 | 3 | import ( | |
| 4 | 4 | "fmt" | |
| 5 | 5 | "runtime/debug" | |
| 6 | + | "unsafe" | |
| 6 | 7 | ||
| 7 | 8 | "github.com/bnema/nefertty/internal/ports" | |
| 9 | + | vk "github.com/bnema/purego-vulkan/vulkan" | |
| 8 | 10 | "golang.org/x/sys/unix" | |
| 9 | 11 | ) | |
| 10 | 12 | ||
| 11 | - | // wl_shm buffers are read in place: each client pool is mapped once, read | |
| 12 | - | // only, and the visible pixels are copied from it into staging when a frame | |
| 13 | - | // is drawn. Mappings follow the dmabuf import rules: kept while drawn, | |
| 14 | - | // dropped after importTTL frames without use. | |
| 13 | + | // wl_shm buffers: each client pool is mapped once, read only. A surface's | |
| 14 | + | // pixels are copied once per new content into a persistently mapped, | |
| 15 | + | // host-visible GPU buffer (device-local when the GPU exposes it, ReBAR) | |
| 16 | + | // that the fragment shader reads directly: one memcpy per row, no staging | |
| 17 | + | // copy, no resample, no alpha fix-up on the CPU. Each surface has two GPU | |
| 18 | + | // buffers used in turn, so the CPU never writes one a frame in flight | |
| 19 | + | // reads. Mappings and buffers are dropped after importTTL frames unused. | |
| 15 | 20 | ||
| 16 | 21 | // mapping is a client pool mapped read-only. | |
| 17 | 22 | type mapping struct { | |
| @@ -27,8 +32,8 @@ func (r *Renderer) shmPixels(b *ports.SHMBuffer, end int) ([]byte, error) { | |||
| 27 | 32 | m.last = r.frame | |
| 28 | 33 | return m.data, nil | |
| 29 | 34 | } | |
| 30 | - | // Uploads collected earlier this frame may still slice the old | |
| 31 | - | // mapping: it is unmapped once staging is done. | |
| 35 | + | // Draws collected earlier this frame may still read the old | |
| 36 | + | // mapping: it is unmapped once the frame is recorded. | |
| 32 | 37 | r.retired = append(r.retired, m.data) | |
| 33 | 38 | delete(r.pools, b.Pool) | |
| 34 | 39 | } | |
| @@ -53,7 +58,8 @@ func (r *Renderer) shmPixels(b *ports.SHMBuffer, end int) ([]byte, error) { | |||
| 53 | 58 | } | |
| 54 | 59 | ||
| 55 | 60 | // dropPools unmaps replaced mappings and pools not drawn for importTTL | |
| 56 | - | // frames. | |
| 61 | + | // frames. The GPU never reads a mapping: this runs once the CPU copies of | |
| 62 | + | // a frame are done. | |
| 57 | 63 | func (r *Renderer) dropPools() { | |
| 58 | 64 | for _, data := range r.retired { | |
| 59 | 65 | _ = unix.Munmap(data) | |
| @@ -80,3 +86,173 @@ func copyGuarded(copy func()) (ok bool) { | |||
| 80 | 86 | copy() | |
| 81 | 87 | return true | |
| 82 | 88 | } | |
| 89 | + | ||
| 90 | + | // gpuBuffer is a host-visible storage buffer, mapped for its whole life. | |
| 91 | + | type gpuBuffer struct { | |
| 92 | + | buffer vk.Buffer | |
| 93 | + | memory vk.DeviceMemory | |
| 94 | + | mapped unsafe.Pointer | |
| 95 | + | size int | |
| 96 | + | // last is the frame that last read it: it is written or freed only | |
| 97 | + | // once that frame completed. | |
| 98 | + | last uint64 | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | func (r *Renderer) newGPUBuffer(b *gpuBuffer, size int) error { | |
| 102 | + | d := r.dd | |
| 103 | + | ok := false | |
| 104 | + | defer func() { | |
| 105 | + | if !ok { | |
| 106 | + | r.freeGPUBuffer(b) | |
| 107 | + | } | |
| 108 | + | }() | |
| 109 | + | info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(size), Usage: vk.BufferUsageStorageBufferBit, SharingMode: vk.SharingModeExclusive} | |
| 110 | + | if err := checked("vkCreateBuffer(shm)", d.CreateBuffer(r.device, &info, nil, &b.buffer)); err != nil { | |
| 111 | + | return err | |
| 112 | + | } | |
| 113 | + | var req vk.MemoryRequirements | |
| 114 | + | d.GetBufferMemoryRequirements(r.device, b.buffer, &req) | |
| 115 | + | host := vk.MemoryPropertyFlags(vk.MemoryPropertyHostVisibleBit | vk.MemoryPropertyHostCoherentBit) | |
| 116 | + | kind, err := r.findMemoryType(req.MemoryTypeBits, host|vk.MemoryPropertyDeviceLocalBit) | |
| 117 | + | if err != nil { | |
| 118 | + | kind, err = r.findMemoryType(req.MemoryTypeBits, host) | |
| 119 | + | } | |
| 120 | + | if err != nil { | |
| 121 | + | return err | |
| 122 | + | } | |
| 123 | + | alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind} | |
| 124 | + | if err := checked("vkAllocateMemory(shm)", d.AllocateMemory(r.device, &alloc, nil, &b.memory)); err != nil { | |
| 125 | + | return err | |
| 126 | + | } | |
| 127 | + | if err := checked("vkBindBufferMemory(shm)", d.BindBufferMemory(r.device, b.buffer, b.memory, 0)); err != nil { | |
| 128 | + | return err | |
| 129 | + | } | |
| 130 | + | if err := checked("vkMapMemory(shm)", d.MapMemory(r.device, b.memory, 0, vk.DeviceSize(size), 0, &b.mapped)); err != nil { | |
| 131 | + | return err | |
| 132 | + | } | |
| 133 | + | b.size, ok = size, true | |
| 134 | + | return nil | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | func (r *Renderer) freeGPUBuffer(b *gpuBuffer) { | |
| 138 | + | d := r.dd | |
| 139 | + | if b.mapped != nil { | |
| 140 | + | d.UnmapMemory(r.device, b.memory) | |
| 141 | + | } | |
| 142 | + | if b.buffer != 0 { | |
| 143 | + | d.DestroyBuffer(r.device, b.buffer, nil) | |
| 144 | + | } | |
| 145 | + | if b.memory != 0 { | |
| 146 | + | d.FreeMemory(r.device, b.memory, nil) | |
| 147 | + | } | |
| 148 | + | *b = gpuBuffer{} | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | // shmKey is a surface of a window: index 0 is the root, i+1 its child i. | |
| 152 | + | type shmKey struct { | |
| 153 | + | win ports.WindowID | |
| 154 | + | index int | |
| 155 | + | } | |
| 156 | + | ||
| 157 | + | // shmState is what a GPU buffer holds: a client buffer at a content Seq. | |
| 158 | + | type shmState struct { | |
| 159 | + | pool uint64 | |
| 160 | + | offset, stride int | |
| 161 | + | w, h int | |
| 162 | + | seq uint64 | |
| 163 | + | } | |
| 164 | + | ||
| 165 | + | // shmCopy is one of a surface's two GPU buffers and the set binding it. | |
| 166 | + | type shmCopy struct { | |
| 167 | + | gpu gpuBuffer | |
| 168 | + | pool vk.DescriptorPool | |
| 169 | + | set vk.DescriptorSet | |
| 170 | + | holds shmState | |
| 171 | + | valid bool | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | // shmSurface is the GPU copy of one surface's wl_shm content. | |
| 175 | + | type shmSurface struct { | |
| 176 | + | bufs [2]*shmCopy | |
| 177 | + | last uint64 // frame that drew it | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | // shmCopyFor returns the GPU buffer holding a surface's content st, copying | |
| 181 | + | // the client pixels into one when no buffer holds them yet. | |
| 182 | + | func (r *Renderer) shmCopyFor(key shmKey, st shmState, pixels []byte) (*shmCopy, error) { | |
| 183 | + | s := r.shm[key] | |
| 184 | + | if s == nil { | |
| 185 | + | s = &shmSurface{} | |
| 186 | + | r.shm[key] = s | |
| 187 | + | } | |
| 188 | + | s.last = r.frame | |
| 189 | + | for _, c := range s.bufs { | |
| 190 | + | if c != nil && c.valid && c.holds == st { | |
| 191 | + | c.gpu.last = r.frame | |
| 192 | + | return c, nil | |
| 193 | + | } | |
| 194 | + | } | |
| 195 | + | // The older buffer takes the new content. | |
| 196 | + | i := 0 | |
| 197 | + | if s.bufs[0] != nil && (s.bufs[1] == nil || s.bufs[1].gpu.last < s.bufs[0].gpu.last) { | |
| 198 | + | i = 1 | |
| 199 | + | } | |
| 200 | + | size := st.w * st.h * 4 | |
| 201 | + | if size > r.maxRange { | |
| 202 | + | return nil, fmt.Errorf("shm buffer of %d bytes past the storage buffer range", size) | |
| 203 | + | } | |
| 204 | + | c := s.bufs[i] | |
| 205 | + | if c != nil && c.gpu.size != size { | |
| 206 | + | r.retire(c.gpu.last, func() { r.freeShmCopy(c) }) | |
| 207 | + | s.bufs[i], c = nil, nil | |
| 208 | + | } | |
| 209 | + | if c == nil { | |
| 210 | + | c = &shmCopy{} | |
| 211 | + | if err := r.newGPUBuffer(&c.gpu, size); err != nil { | |
| 212 | + | return nil, err | |
| 213 | + | } | |
| 214 | + | var err error | |
| 215 | + | if c.pool, c.set, err = r.newSet(r.compose.dummyView, c.gpu.buffer); err != nil { | |
| 216 | + | r.freeGPUBuffer(&c.gpu) | |
| 217 | + | return nil, err | |
| 218 | + | } | |
| 219 | + | s.bufs[i] = c | |
| 220 | + | } | |
| 221 | + | if err := r.waitFrame(c.gpu.last); err != nil { | |
| 222 | + | return nil, err | |
| 223 | + | } | |
| 224 | + | dst := unsafe.Slice((*byte)(c.gpu.mapped), size) | |
| 225 | + | row := st.w * 4 | |
| 226 | + | // The client may shrink its pool under the mapping: its window then | |
| 227 | + | // shows garbage for this frame, never a crash, and is copied again. | |
| 228 | + | c.valid = copyGuarded(func() { | |
| 229 | + | for y := range st.h { | |
| 230 | + | copy(dst[y*row:(y+1)*row], pixels[st.offset+y*st.stride:]) | |
| 231 | + | } | |
| 232 | + | }) | |
| 233 | + | r.copied += size | |
| 234 | + | c.holds, c.gpu.last = st, r.frame | |
| 235 | + | return c, nil | |
| 236 | + | } | |
| 237 | + | ||
| 238 | + | func (r *Renderer) freeShmCopy(c *shmCopy) { | |
| 239 | + | if c.pool != 0 { | |
| 240 | + | r.dd.DestroyDescriptorPool(r.device, c.pool, nil) | |
| 241 | + | c.pool = 0 | |
| 242 | + | } | |
| 243 | + | r.freeGPUBuffer(&c.gpu) | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | // dropShm frees the GPU buffers of surfaces not drawn for importTTL frames. | |
| 247 | + | func (r *Renderer) dropShm() { | |
| 248 | + | for key, s := range r.shm { | |
| 249 | + | if r.frame-s.last > importTTL { | |
| 250 | + | for _, c := range s.bufs { | |
| 251 | + | if c != nil { | |
| 252 | + | r.retire(c.gpu.last, func() { r.freeShmCopy(c) }) | |
| 253 | + | } | |
| 254 | + | } | |
| 255 | + | delete(r.shm, key) | |
| 256 | + | } | |
| 257 | + | } | |
| 258 | + | } | |
| @@ -3,15 +3,20 @@ package vulkan | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "fmt" | 4 | "fmt" | ||
| 5 | "runtime/debug" | 5 | "runtime/debug" | ||
| 6 | + | "unsafe" | |||
| 6 | 7 | ||||
| 7 | "github.com/bnema/nefertty/internal/ports" | 8 | "github.com/bnema/nefertty/internal/ports" | ||
| 9 | + | vk "github.com/bnema/purego-vulkan/vulkan" | |||
| 8 | "golang.org/x/sys/unix" | 10 | "golang.org/x/sys/unix" | ||
| 9 | ) | 11 | ) | ||
| 10 | 12 | ||||
| 11 | - | // wl_shm buffers are read in place: each client pool is mapped once, read | 13 | + | // wl_shm buffers: each client pool is mapped once, read only. A surface's |
| 12 | - | // only, and the visible pixels are copied from it into staging when a frame | 14 | + | // pixels are copied once per new content into a persistently mapped, |
| 13 | - | // is drawn. Mappings follow the dmabuf import rules: kept while drawn, | 15 | + | // host-visible GPU buffer (device-local when the GPU exposes it, ReBAR) |
| 14 | - | // dropped after importTTL frames without use. | 16 | + | // that the fragment shader reads directly: one memcpy per row, no staging |
| 17 | + | // copy, no resample, no alpha fix-up on the CPU. Each surface has two GPU | |||
| 18 | + | // buffers used in turn, so the CPU never writes one a frame in flight | |||
| 19 | + | // reads. Mappings and buffers are dropped after importTTL frames unused. | |||
| 15 | 20 | ||||
| 16 | // mapping is a client pool mapped read-only. | 21 | // mapping is a client pool mapped read-only. | ||
| 17 | type mapping struct { | 22 | type mapping struct { | ||
| @@ -27,8 +32,8 @@ func (r *Renderer) shmPixels(b *ports.SHMBuffer, end int) ([]byte, error) { | |||||
| 27 | m.last = r.frame | 32 | m.last = r.frame | ||
| 28 | return m.data, nil | 33 | return m.data, nil | ||
| 29 | } | 34 | } | ||
| 30 | - | // Uploads collected earlier this frame may still slice the old | 35 | + | // Draws collected earlier this frame may still read the old |
| 31 | - | // mapping: it is unmapped once staging is done. | 36 | + | // mapping: it is unmapped once the frame is recorded. |
| 32 | r.retired = append(r.retired, m.data) | 37 | r.retired = append(r.retired, m.data) | ||
| 33 | delete(r.pools, b.Pool) | 38 | delete(r.pools, b.Pool) | ||
| 34 | } | 39 | } | ||
| @@ -53,7 +58,8 @@ func (r *Renderer) shmPixels(b *ports.SHMBuffer, end int) ([]byte, error) { | |||||
| 53 | } | 58 | } | ||
| 54 | 59 | ||||
| 55 | // dropPools unmaps replaced mappings and pools not drawn for importTTL | 60 | // dropPools unmaps replaced mappings and pools not drawn for importTTL | ||
| 56 | - | // frames. | 61 | + | // frames. The GPU never reads a mapping: this runs once the CPU copies of |
| 62 | + | // a frame are done. | |||
| 57 | func (r *Renderer) dropPools() { | 63 | func (r *Renderer) dropPools() { | ||
| 58 | for _, data := range r.retired { | 64 | for _, data := range r.retired { | ||
| 59 | _ = unix.Munmap(data) | 65 | _ = unix.Munmap(data) | ||
| @@ -80,3 +86,173 @@ func copyGuarded(copy func()) (ok bool) { | |||||
| 80 | copy() | 86 | copy() | ||
| 81 | return true | 87 | return true | ||
| 82 | } | 88 | } | ||
| 89 | + | ||||
| 90 | + | // gpuBuffer is a host-visible storage buffer, mapped for its whole life. | |||
| 91 | + | type gpuBuffer struct { | |||
| 92 | + | buffer vk.Buffer | |||
| 93 | + | memory vk.DeviceMemory | |||
| 94 | + | mapped unsafe.Pointer | |||
| 95 | + | size int | |||
| 96 | + | // last is the frame that last read it: it is written or freed only | |||
| 97 | + | // once that frame completed. | |||
| 98 | + | last uint64 | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | func (r *Renderer) newGPUBuffer(b *gpuBuffer, size int) error { | |||
| 102 | + | d := r.dd | |||
| 103 | + | ok := false | |||
| 104 | + | defer func() { | |||
| 105 | + | if !ok { | |||
| 106 | + | r.freeGPUBuffer(b) | |||
| 107 | + | } | |||
| 108 | + | }() | |||
| 109 | + | info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(size), Usage: vk.BufferUsageStorageBufferBit, SharingMode: vk.SharingModeExclusive} | |||
| 110 | + | if err := checked("vkCreateBuffer(shm)", d.CreateBuffer(r.device, &info, nil, &b.buffer)); err != nil { | |||
| 111 | + | return err | |||
| 112 | + | } | |||
| 113 | + | var req vk.MemoryRequirements | |||
| 114 | + | d.GetBufferMemoryRequirements(r.device, b.buffer, &req) | |||
| 115 | + | host := vk.MemoryPropertyFlags(vk.MemoryPropertyHostVisibleBit | vk.MemoryPropertyHostCoherentBit) | |||
| 116 | + | kind, err := r.findMemoryType(req.MemoryTypeBits, host|vk.MemoryPropertyDeviceLocalBit) | |||
| 117 | + | if err != nil { | |||
| 118 | + | kind, err = r.findMemoryType(req.MemoryTypeBits, host) | |||
| 119 | + | } | |||
| 120 | + | if err != nil { | |||
| 121 | + | return err | |||
| 122 | + | } | |||
| 123 | + | alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind} | |||
| 124 | + | if err := checked("vkAllocateMemory(shm)", d.AllocateMemory(r.device, &alloc, nil, &b.memory)); err != nil { | |||
| 125 | + | return err | |||
| 126 | + | } | |||
| 127 | + | if err := checked("vkBindBufferMemory(shm)", d.BindBufferMemory(r.device, b.buffer, b.memory, 0)); err != nil { | |||
| 128 | + | return err | |||
| 129 | + | } | |||
| 130 | + | if err := checked("vkMapMemory(shm)", d.MapMemory(r.device, b.memory, 0, vk.DeviceSize(size), 0, &b.mapped)); err != nil { | |||
| 131 | + | return err | |||
| 132 | + | } | |||
| 133 | + | b.size, ok = size, true | |||
| 134 | + | return nil | |||
| 135 | + | } | |||
| 136 | + | ||||
| 137 | + | func (r *Renderer) freeGPUBuffer(b *gpuBuffer) { | |||
| 138 | + | d := r.dd | |||
| 139 | + | if b.mapped != nil { | |||
| 140 | + | d.UnmapMemory(r.device, b.memory) | |||
| 141 | + | } | |||
| 142 | + | if b.buffer != 0 { | |||
| 143 | + | d.DestroyBuffer(r.device, b.buffer, nil) | |||
| 144 | + | } | |||
| 145 | + | if b.memory != 0 { | |||
| 146 | + | d.FreeMemory(r.device, b.memory, nil) | |||
| 147 | + | } | |||
| 148 | + | *b = gpuBuffer{} | |||
| 149 | + | } | |||
| 150 | + | ||||
| 151 | + | // shmKey is a surface of a window: index 0 is the root, i+1 its child i. | |||
| 152 | + | type shmKey struct { | |||
| 153 | + | win ports.WindowID | |||
| 154 | + | index int | |||
| 155 | + | } | |||
| 156 | + | ||||
| 157 | + | // shmState is what a GPU buffer holds: a client buffer at a content Seq. | |||
| 158 | + | type shmState struct { | |||
| 159 | + | pool uint64 | |||
| 160 | + | offset, stride int | |||
| 161 | + | w, h int | |||
| 162 | + | seq uint64 | |||
| 163 | + | } | |||
| 164 | + | ||||
| 165 | + | // shmCopy is one of a surface's two GPU buffers and the set binding it. | |||
| 166 | + | type shmCopy struct { | |||
| 167 | + | gpu gpuBuffer | |||
| 168 | + | pool vk.DescriptorPool | |||
| 169 | + | set vk.DescriptorSet | |||
| 170 | + | holds shmState | |||
| 171 | + | valid bool | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | // shmSurface is the GPU copy of one surface's wl_shm content. | |||
| 175 | + | type shmSurface struct { | |||
| 176 | + | bufs [2]*shmCopy | |||
| 177 | + | last uint64 // frame that drew it | |||
| 178 | + | } | |||
| 179 | + | ||||
| 180 | + | // shmCopyFor returns the GPU buffer holding a surface's content st, copying | |||
| 181 | + | // the client pixels into one when no buffer holds them yet. | |||
| 182 | + | func (r *Renderer) shmCopyFor(key shmKey, st shmState, pixels []byte) (*shmCopy, error) { | |||
| 183 | + | s := r.shm[key] | |||
| 184 | + | if s == nil { | |||
| 185 | + | s = &shmSurface{} | |||
| 186 | + | r.shm[key] = s | |||
| 187 | + | } | |||
| 188 | + | s.last = r.frame | |||
| 189 | + | for _, c := range s.bufs { | |||
| 190 | + | if c != nil && c.valid && c.holds == st { | |||
| 191 | + | c.gpu.last = r.frame | |||
| 192 | + | return c, nil | |||
| 193 | + | } | |||
| 194 | + | } | |||
| 195 | + | // The older buffer takes the new content. | |||
| 196 | + | i := 0 | |||
| 197 | + | if s.bufs[0] != nil && (s.bufs[1] == nil || s.bufs[1].gpu.last < s.bufs[0].gpu.last) { | |||
| 198 | + | i = 1 | |||
| 199 | + | } | |||
| 200 | + | size := st.w * st.h * 4 | |||
| 201 | + | if size > r.maxRange { | |||
| 202 | + | return nil, fmt.Errorf("shm buffer of %d bytes past the storage buffer range", size) | |||
| 203 | + | } | |||
| 204 | + | c := s.bufs[i] | |||
| 205 | + | if c != nil && c.gpu.size != size { | |||
| 206 | + | r.retire(c.gpu.last, func() { r.freeShmCopy(c) }) | |||
| 207 | + | s.bufs[i], c = nil, nil | |||
| 208 | + | } | |||
| 209 | + | if c == nil { | |||
| 210 | + | c = &shmCopy{} | |||
| 211 | + | if err := r.newGPUBuffer(&c.gpu, size); err != nil { | |||
| 212 | + | return nil, err | |||
| 213 | + | } | |||
| 214 | + | var err error | |||
| 215 | + | if c.pool, c.set, err = r.newSet(r.compose.dummyView, c.gpu.buffer); err != nil { | |||
| 216 | + | r.freeGPUBuffer(&c.gpu) | |||
| 217 | + | return nil, err | |||
| 218 | + | } | |||
| 219 | + | s.bufs[i] = c | |||
| 220 | + | } | |||
| 221 | + | if err := r.waitFrame(c.gpu.last); err != nil { | |||
| 222 | + | return nil, err | |||
| 223 | + | } | |||
| 224 | + | dst := unsafe.Slice((*byte)(c.gpu.mapped), size) | |||
| 225 | + | row := st.w * 4 | |||
| 226 | + | // The client may shrink its pool under the mapping: its window then | |||
| 227 | + | // shows garbage for this frame, never a crash, and is copied again. | |||
| 228 | + | c.valid = copyGuarded(func() { | |||
| 229 | + | for y := range st.h { | |||
| 230 | + | copy(dst[y*row:(y+1)*row], pixels[st.offset+y*st.stride:]) | |||
| 231 | + | } | |||
| 232 | + | }) | |||
| 233 | + | r.copied += size | |||
| 234 | + | c.holds, c.gpu.last = st, r.frame | |||
| 235 | + | return c, nil | |||
| 236 | + | } | |||
| 237 | + | ||||
| 238 | + | func (r *Renderer) freeShmCopy(c *shmCopy) { | |||
| 239 | + | if c.pool != 0 { | |||
| 240 | + | r.dd.DestroyDescriptorPool(r.device, c.pool, nil) | |||
| 241 | + | c.pool = 0 | |||
| 242 | + | } | |||
| 243 | + | r.freeGPUBuffer(&c.gpu) | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | // dropShm frees the GPU buffers of surfaces not drawn for importTTL frames. | |||
| 247 | + | func (r *Renderer) dropShm() { | |||
| 248 | + | for key, s := range r.shm { | |||
| 249 | + | if r.frame-s.last > importTTL { | |||
| 250 | + | for _, c := range s.bufs { | |||
| 251 | + | if c != nil { | |||
| 252 | + | r.retire(c.gpu.last, func() { r.freeShmCopy(c) }) | |||
| 253 | + | } | |||
| 254 | + | } | |||
| 255 | + | delete(r.shm, key) | |||
| 256 | + | } | |||
| 257 | + | } | |||
| 258 | + | } | |||
M internal/adapters/vulkan/targets.go +8 -14
| @@ -105,9 +105,8 @@ func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) { | |||
| 105 | 105 | mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount) | |
| 106 | 106 | list.DrmFormatModifierProperties = &mods[0] | |
| 107 | 107 | r.id.GetPhysicalDeviceFormatProperties2(physical, vk.FormatB8g8r8a8Unorm, &fp) | |
| 108 | - | need := vk.FormatFeatureFlags(formatFeatureTransferDst | formatFeatureTransferSrc | vk.FormatFeatureBlitDstBit | vk.FormatFeatureColorAttachmentBit | vk.FormatFeatureColorAttachmentBlendBit) | |
| 109 | - | // Targets are drawn on too (blend.go): a transfer-only modifier is | |
| 110 | - | // not a target, and without any the output cannot start. | |
| 108 | + | need := vk.FormatFeatureFlags(formatFeatureTransferSrc | vk.FormatFeatureColorAttachmentBit | vk.FormatFeatureColorAttachmentBlendBit) | |
| 109 | + | // Targets are drawn on (compose.go) and read back for screenshots. | |
| 111 | 110 | for _, m := range mods[:list.DrmFormatModifierCount] { | |
| 112 | 111 | if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) { | |
| 113 | 112 | r.renderMods = append(r.renderMods, m.DrmFormatModifier) | |
| @@ -127,18 +126,12 @@ func (r *Renderer) exportable(physical vk.PhysicalDevice, modifier uint64) bool | |||
| 127 | 126 | return extOut.ExternalMemoryProperties.ExternalMemoryFeatures&vk.ExternalMemoryFeatureExportableBit != 0 | |
| 128 | 127 | } | |
| 129 | 128 | ||
| 130 | - | // VK_FORMAT_FEATURE_TRANSFER_SRC_BIT / _DST_BIT (Vulkan 1.1), missing from | |
| 131 | - | // the generated bindings. | |
| 132 | - | const ( | |
| 133 | - | formatFeatureTransferSrc = 1 << 14 | |
| 134 | - | formatFeatureTransferDst = 1 << 15 | |
| 135 | - | ) | |
| 136 | - | ||
| 137 | - | // Targets are copied into and drawn on (blend.go). | |
| 138 | - | const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit | vk.ImageUsageColorAttachmentBit | |
| 129 | + | // VK_FORMAT_FEATURE_TRANSFER_SRC_BIT (Vulkan 1.1), missing from the | |
| 130 | + | // generated bindings. | |
| 131 | + | const formatFeatureTransferSrc = 1 << 14 | |
| 139 | 132 | ||
| 140 | - | // Staging feeds copies and, as a storage buffer, the blend shaders. | |
| 141 | - | const stagingUsage = vk.BufferUsageTransferSrcBit | vk.BufferUsageStorageBufferBit | |
| 133 | + | // Targets are drawn on (compose.go) and copied from for screenshots. | |
| 134 | + | const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageColorAttachmentBit | |
| 142 | 135 | ||
| 143 | 136 | // exportTarget creates one exported image with a modifier from mods. | |
| 144 | 137 | func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | |
| @@ -213,6 +206,7 @@ func (r *Renderer) freeTarget(t *target) { | |||
| 213 | 206 | func (r *Renderer) dropTargets() { | |
| 214 | 207 | if len(r.targets) > 0 && r.device != 0 { | |
| 215 | 208 | _ = checked("vkDeviceWaitIdle", r.dd.DeviceWaitIdle(r.device)) | |
| 209 | + | r.idle() | |
| 216 | 210 | } | |
| 217 | 211 | for _, t := range r.targets { | |
| 218 | 212 | if r.last == t { | |
| @@ -105,9 +105,8 @@ func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) { | |||||
| 105 | mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount) | 105 | mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount) | ||
| 106 | list.DrmFormatModifierProperties = &mods[0] | 106 | list.DrmFormatModifierProperties = &mods[0] | ||
| 107 | r.id.GetPhysicalDeviceFormatProperties2(physical, vk.FormatB8g8r8a8Unorm, &fp) | 107 | r.id.GetPhysicalDeviceFormatProperties2(physical, vk.FormatB8g8r8a8Unorm, &fp) | ||
| 108 | - | need := vk.FormatFeatureFlags(formatFeatureTransferDst | formatFeatureTransferSrc | vk.FormatFeatureBlitDstBit | vk.FormatFeatureColorAttachmentBit | vk.FormatFeatureColorAttachmentBlendBit) | 108 | + | need := vk.FormatFeatureFlags(formatFeatureTransferSrc | vk.FormatFeatureColorAttachmentBit | vk.FormatFeatureColorAttachmentBlendBit) |
| 109 | - | // Targets are drawn on too (blend.go): a transfer-only modifier is | 109 | + | // Targets are drawn on (compose.go) and read back for screenshots. |
| 110 | - | // not a target, and without any the output cannot start. | |||
| 111 | for _, m := range mods[:list.DrmFormatModifierCount] { | 110 | for _, m := range mods[:list.DrmFormatModifierCount] { | ||
| 112 | if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) { | 111 | if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) { | ||
| 113 | r.renderMods = append(r.renderMods, m.DrmFormatModifier) | 112 | r.renderMods = append(r.renderMods, m.DrmFormatModifier) | ||
| @@ -127,18 +126,12 @@ func (r *Renderer) exportable(physical vk.PhysicalDevice, modifier uint64) bool | |||||
| 127 | return extOut.ExternalMemoryProperties.ExternalMemoryFeatures&vk.ExternalMemoryFeatureExportableBit != 0 | 126 | return extOut.ExternalMemoryProperties.ExternalMemoryFeatures&vk.ExternalMemoryFeatureExportableBit != 0 | ||
| 128 | } | 127 | } | ||
| 129 | 128 | ||||
| 130 | - | // VK_FORMAT_FEATURE_TRANSFER_SRC_BIT / _DST_BIT (Vulkan 1.1), missing from | 129 | + | // VK_FORMAT_FEATURE_TRANSFER_SRC_BIT (Vulkan 1.1), missing from the |
| 131 | - | // the generated bindings. | 130 | + | // generated bindings. |
| 132 | - | const ( | 131 | + | const formatFeatureTransferSrc = 1 << 14 |
| 133 | - | formatFeatureTransferSrc = 1 << 14 | |||
| 134 | - | formatFeatureTransferDst = 1 << 15 | |||
| 135 | - | ) | |||
| 136 | - | ||||
| 137 | - | // Targets are copied into and drawn on (blend.go). | |||
| 138 | - | const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageTransferDstBit | vk.ImageUsageColorAttachmentBit | |||
| 139 | 132 | ||||
| 140 | - | // Staging feeds copies and, as a storage buffer, the blend shaders. | 133 | + | // Targets are drawn on (compose.go) and copied from for screenshots. |
| 141 | - | const stagingUsage = vk.BufferUsageTransferSrcBit | vk.BufferUsageStorageBufferBit | 134 | + | const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageColorAttachmentBit |
| 142 | 135 | ||||
| 143 | // exportTarget creates one exported image with a modifier from mods. | 136 | // exportTarget creates one exported image with a modifier from mods. | ||
| 144 | func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | 137 | func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) { | ||
| @@ -213,6 +206,7 @@ func (r *Renderer) freeTarget(t *target) { | |||||
| 213 | func (r *Renderer) dropTargets() { | 206 | func (r *Renderer) dropTargets() { | ||
| 214 | if len(r.targets) > 0 && r.device != 0 { | 207 | if len(r.targets) > 0 && r.device != 0 { | ||
| 215 | _ = checked("vkDeviceWaitIdle", r.dd.DeviceWaitIdle(r.device)) | 208 | _ = checked("vkDeviceWaitIdle", r.dd.DeviceWaitIdle(r.device)) | ||
| 209 | + | r.idle() | |||
| 216 | } | 210 | } | ||
| 217 | for _, t := range r.targets { | 211 | for _, t := range r.targets { | ||
| 218 | if r.last == t { | 212 | if r.last == t { | ||