1//go:build linux
2
3package vkdevice
4
5import (
6 "fmt"
7 "math"
8 "unsafe"
9
10 "github.com/bnema/nefergui/internal/text"
11 "github.com/bnema/purego-vulkan/vulkan"
12)
13
14// AtlasSize and AtlasPages are shared with the CPU preparer at the session boundary.
15const AtlasSize = 1024
16const AtlasPages = 4
17
18type glyphAtlas struct {
19 device *Device
20 Image vulkan.Image
21 Memory vulkan.DeviceMemory
22 View vulkan.ImageView
23 Sampler vulkan.Sampler
24 Layout vulkan.DescriptorSetLayout
25 Pool vulkan.DescriptorPool
26 Set vulkan.DescriptorSet
27 initialized bool
28}
29
30func (a *glyphAtlas) Close() {
31 if a == nil || a.device == nil {
32 return
33 }
34 d := a.device.Dispatch
35 if a.Pool != 0 {
36 d.DestroyDescriptorPool(a.device.Logical, a.Pool, nil)
37 a.Pool = 0
38 }
39 if a.Layout != 0 {
40 d.DestroyDescriptorSetLayout(a.device.Logical, a.Layout, nil)
41 a.Layout = 0
42 }
43 if a.Sampler != 0 {
44 d.DestroySampler(a.device.Logical, a.Sampler, nil)
45 a.Sampler = 0
46 }
47 if a.View != 0 {
48 d.DestroyImageView(a.device.Logical, a.View, nil)
49 a.View = 0
50 }
51 if a.Image != 0 {
52 d.DestroyImage(a.device.Logical, a.Image, nil)
53 a.Image = 0
54 }
55 if a.Memory != 0 {
56 d.FreeMemory(a.device.Logical, a.Memory, nil)
57 a.Memory = 0
58 }
59}
60
61func (d *Device) newGlyphAtlas() (a *glyphAtlas, err error) {
62 a = &glyphAtlas{device: d}
63 defer func() {
64 if err != nil {
65 a.Close()
66 }
67 }()
68 ci := vulkan.ImageCreateInfo{SType: vulkan.StructureTypeImageCreateInfo, ImageType: vulkan.ImageType2d, Format: vulkan.FormatR8Unorm, Extent: vulkan.Extent3D{Width: AtlasSize, Height: AtlasSize, Depth: 1}, MipLevels: 1, ArrayLayers: AtlasPages, Samples: vulkan.SampleCount1Bit, Tiling: vulkan.ImageTilingOptimal, Usage: vulkan.ImageUsageSampledBit | vulkan.ImageUsageTransferDstBit, SharingMode: vulkan.SharingModeExclusive, InitialLayout: vulkan.ImageLayoutUndefined}
69 if err = vulkan.Check(d.Dispatch.CreateImage(d.Logical, &ci, nil, &a.Image)); err != nil {
70 return nil, fmt.Errorf("create glyph atlas: %w", err)
71 }
72 var req vulkan.MemoryRequirements
73 d.Dispatch.GetImageMemoryRequirements(d.Logical, a.Image, &req)
74 var mem vulkan.PhysicalDeviceMemoryProperties
75 d.InstanceDispatch.GetPhysicalDeviceMemoryProperties(d.Physical, &mem)
76 index := uint32(math.MaxUint32)
77 for i := uint32(0); i < mem.MemoryTypeCount; i++ {
78 if req.MemoryTypeBits&(1<<i) != 0 && mem.MemoryTypes[i].PropertyFlags&vulkan.MemoryPropertyDeviceLocalBit != 0 {
79 index = i
80 break
81 }
82 }
83 if index == math.MaxUint32 {
84 return nil, fmt.Errorf("no device-local atlas memory")
85 }
86 alloc := vulkan.MemoryAllocateInfo{SType: vulkan.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: index}
87 if err = vulkan.Check(d.Dispatch.AllocateMemory(d.Logical, &alloc, nil, &a.Memory)); err != nil {
88 return nil, err
89 }
90 if err = vulkan.Check(d.Dispatch.BindImageMemory(d.Logical, a.Image, a.Memory, 0)); err != nil {
91 return nil, err
92 }
93 view := vulkan.ImageViewCreateInfo{SType: vulkan.StructureTypeImageViewCreateInfo, Image: a.Image, ViewType: vulkan.ImageViewType2dArray, Format: vulkan.FormatR8Unorm, SubresourceRange: vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: AtlasPages}}
94 if err = vulkan.Check(d.Dispatch.CreateImageView(d.Logical, &view, nil, &a.View)); err != nil {
95 return nil, err
96 }
97 sampler := vulkan.SamplerCreateInfo{SType: vulkan.StructureTypeSamplerCreateInfo, MagFilter: vulkan.FilterNearest, MinFilter: vulkan.FilterNearest, MipmapMode: vulkan.SamplerMipmapModeNearest, AddressModeU: vulkan.SamplerAddressModeClampToEdge, AddressModeV: vulkan.SamplerAddressModeClampToEdge, AddressModeW: vulkan.SamplerAddressModeClampToEdge}
98 if err = vulkan.Check(d.Dispatch.CreateSampler(d.Logical, &sampler, nil, &a.Sampler)); err != nil {
99 return nil, err
100 }
101 binding := vulkan.DescriptorSetLayoutBinding{DescriptorType: vulkan.DescriptorTypeCombinedImageSampler, DescriptorCount: 1, StageFlags: vulkan.ShaderStageFragmentBit}
102 li := vulkan.DescriptorSetLayoutCreateInfo{SType: vulkan.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 1, Bindings: &binding}
103 if err = vulkan.Check(d.Dispatch.CreateDescriptorSetLayout(d.Logical, &li, nil, &a.Layout)); err != nil {
104 return nil, err
105 }
106 poolSize := vulkan.DescriptorPoolSize{Type: vulkan.DescriptorTypeCombinedImageSampler, DescriptorCount: 1}
107 pi := vulkan.DescriptorPoolCreateInfo{SType: vulkan.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &poolSize}
108 if err = vulkan.Check(d.Dispatch.CreateDescriptorPool(d.Logical, &pi, nil, &a.Pool)); err != nil {
109 return nil, err
110 }
111 ai := vulkan.DescriptorSetAllocateInfo{SType: vulkan.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: a.Pool, DescriptorSetCount: 1, SetLayouts: &a.Layout}
112 if err = vulkan.Check(d.Dispatch.AllocateDescriptorSets(d.Logical, &ai, &a.Set)); err != nil {
113 return nil, err
114 }
115 info := vulkan.DescriptorImageInfo{Sampler: a.Sampler, ImageView: a.View, ImageLayout: vulkan.ImageLayoutShaderReadOnlyOptimal}
116 write := vulkan.WriteDescriptorSet{SType: vulkan.StructureTypeWriteDescriptorSet, DstSet: a.Set, DescriptorCount: 1, DescriptorType: vulkan.DescriptorTypeCombinedImageSampler, ImageInfo: &info}
117 d.Dispatch.UpdateDescriptorSets(d.Logical, 1, &write, 0, nil)
118 return a, nil
119}
120
121// recordUploads uses one queue for all frame slots. The FRAGMENT read -> TRANSFER
122// write dependency waits for prior submissions sampling even evicted pages; the
123// reverse dependency makes new pixels visible to this frame's fragment shader.
124func (a *glyphAtlas) recordUploads(cmd vulkan.CommandBuffer, staging vulkan.Buffer, uploads []text.Upload) {
125 d := a.device.Dispatch
126 sub := vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: AtlasPages}
127 barrier := vulkan.ImageMemoryBarrier2{SType: vulkan.StructureTypeImageMemoryBarrier2, SrcQueueFamilyIndex: ^uint32(0), DstQueueFamilyIndex: ^uint32(0), Image: a.Image, SubresourceRange: sub, OldLayout: vulkan.ImageLayoutShaderReadOnlyOptimal, NewLayout: vulkan.ImageLayoutTransferDstOptimal, SrcStageMask: vulkan.PipelineStage2FragmentShaderBit, SrcAccessMask: vulkan.Access2ShaderSampledReadBit, DstStageMask: vulkan.PipelineStage2TransferBit, DstAccessMask: vulkan.Access2TransferWriteBit}
128 dep := vulkan.DependencyInfo{SType: vulkan.StructureTypeDependencyInfo, ImageMemoryBarrierCount: 1, ImageMemoryBarriers: &barrier}
129 if !a.initialized {
130 barrier.OldLayout = vulkan.ImageLayoutUndefined
131 barrier.SrcStageMask = vulkan.PipelineStage2None
132 barrier.SrcAccessMask = 0
133 }
134 if !a.initialized || len(uploads) > 0 {
135 d.CmdPipelineBarrier2(cmd, &dep)
136 }
137 if !a.initialized {
138 clear := vulkan.ClearColorValue{}
139 d.CmdClearColorImage(cmd, a.Image, vulkan.ImageLayoutTransferDstOptimal, &clear, 1, &sub)
140 if len(uploads) > 0 {
141 // Clear and copy are both transfer writes; order overlapping regions.
142 barrier.OldLayout = vulkan.ImageLayoutTransferDstOptimal
143 barrier.NewLayout = vulkan.ImageLayoutTransferDstOptimal
144 barrier.SrcStageMask = vulkan.PipelineStage2TransferBit
145 barrier.SrcAccessMask = vulkan.Access2TransferWriteBit
146 barrier.DstStageMask = vulkan.PipelineStage2TransferBit
147 barrier.DstAccessMask = vulkan.Access2TransferWriteBit
148 d.CmdPipelineBarrier2(cmd, &dep)
149 }
150 }
151 offset := vulkan.DeviceSize(0)
152 for _, u := range uploads {
153 region := vulkan.BufferImageCopy{BufferOffset: offset, ImageSubresource: vulkan.ImageSubresourceLayers{AspectMask: vulkan.ImageAspectColorBit, BaseArrayLayer: uint32(u.Page), LayerCount: 1}, ImageOffset: vulkan.Offset3D{X: int32(u.Rect.Min.X), Y: int32(u.Rect.Min.Y)}, ImageExtent: vulkan.Extent3D{Width: uint32(u.Rect.Dx()), Height: uint32(u.Rect.Dy()), Depth: 1}}
154 d.CmdCopyBufferToImage(cmd, staging, a.Image, vulkan.ImageLayoutTransferDstOptimal, 1, ®ion)
155 offset += vulkan.DeviceSize(len(u.Bytes))
156 }
157 if !a.initialized || len(uploads) > 0 {
158 barrier.OldLayout = vulkan.ImageLayoutTransferDstOptimal
159 barrier.NewLayout = vulkan.ImageLayoutShaderReadOnlyOptimal
160 barrier.SrcStageMask = vulkan.PipelineStage2TransferBit
161 barrier.SrcAccessMask = vulkan.Access2TransferWriteBit
162 barrier.DstStageMask = vulkan.PipelineStage2FragmentShaderBit
163 barrier.DstAccessMask = vulkan.Access2ShaderSampledReadBit
164 d.CmdPipelineBarrier2(cmd, &dep)
165 }
166}
167
168func (f *Frame) stageGlyphs(uploads []text.Upload) error {
169 if len(uploads) == 0 {
170 return nil
171 }
172 total := 0
173 for _, u := range uploads {
174 if u.Page < 0 || u.Page >= AtlasPages || u.Rect.Min.X < 0 || u.Rect.Min.Y < 0 || u.Rect.Max.X > AtlasSize || u.Rect.Max.Y > AtlasSize || u.Rect.Empty() || len(u.Bytes) != u.Rect.Dx()*u.Rect.Dy() || len(u.Bytes) > math.MaxInt-total {
175 return fmt.Errorf("invalid atlas upload")
176 }
177 total += len(u.Bytes)
178 }
179 if f.glyphStaging == nil || total > f.glyphStaging.capacity {
180 if f.glyphStaging != nil {
181 f.glyphStaging.Close()
182 }
183 f.glyphStaging = &instanceBuffer{device: f.device}
184 b := f.glyphStaging
185 ci := vulkan.BufferCreateInfo{SType: vulkan.StructureTypeBufferCreateInfo, Size: vulkan.DeviceSize(total), Usage: vulkan.BufferUsageTransferSrcBit, SharingMode: vulkan.SharingModeExclusive}
186 if err := vulkan.Check(f.device.Dispatch.CreateBuffer(f.device.Logical, &ci, nil, &b.Buffer)); err != nil {
187 return err
188 }
189 var req vulkan.MemoryRequirements
190 f.device.Dispatch.GetBufferMemoryRequirements(f.device.Logical, b.Buffer, &req)
191 var mem vulkan.PhysicalDeviceMemoryProperties
192 f.device.InstanceDispatch.GetPhysicalDeviceMemoryProperties(f.device.Physical, &mem)
193 index := uint32(math.MaxUint32)
194 want := vulkan.MemoryPropertyFlags(vulkan.MemoryPropertyHostVisibleBit | vulkan.MemoryPropertyHostCoherentBit)
195 for i := uint32(0); i < mem.MemoryTypeCount; i++ {
196 if req.MemoryTypeBits&(1<<i) != 0 && mem.MemoryTypes[i].PropertyFlags&want == want {
197 index = i
198 break
199 }
200 }
201 if index == math.MaxUint32 {
202 return fmt.Errorf("no host-coherent atlas staging memory")
203 }
204 ai := vulkan.MemoryAllocateInfo{SType: vulkan.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: index}
205 if err := vulkan.Check(f.device.Dispatch.AllocateMemory(f.device.Logical, &ai, nil, &b.Memory)); err != nil {
206 return err
207 }
208 if err := vulkan.Check(f.device.Dispatch.BindBufferMemory(f.device.Logical, b.Buffer, b.Memory, 0)); err != nil {
209 return err
210 }
211 b.capacity = total
212 }
213 var ptr unsafe.Pointer
214 if err := vulkan.Check(f.device.Dispatch.MapMemory(f.device.Logical, f.glyphStaging.Memory, 0, vulkan.DeviceSize(total), 0, &ptr)); err != nil {
215 return err
216 }
217 buf := unsafe.Slice((*byte)(ptr), total)
218 offset := 0
219 for _, u := range uploads {
220 copy(buf[offset:], u.Bytes)
221 offset += len(u.Bytes)
222 }
223 f.device.Dispatch.UnmapMemory(f.device.Logical, f.glyphStaging.Memory)
224 return nil
225}