v0.5.1 657f658

atlas.go RawHistoryFolder

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, &region)
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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 12

↑↓ moveenter openesc close