//go:build linux package vkdevice import ( "fmt" "math" "unsafe" "github.com/bnema/nefergui/internal/text" "github.com/bnema/purego-vulkan/vulkan" ) // AtlasSize and AtlasPages are shared with the CPU preparer at the session boundary. const AtlasSize = 1024 const AtlasPages = 4 type glyphAtlas struct { device *Device Image vulkan.Image Memory vulkan.DeviceMemory View vulkan.ImageView Sampler vulkan.Sampler Layout vulkan.DescriptorSetLayout Pool vulkan.DescriptorPool Set vulkan.DescriptorSet initialized bool } func (a *glyphAtlas) Close() { if a == nil || a.device == nil { return } d := a.device.Dispatch if a.Pool != 0 { d.DestroyDescriptorPool(a.device.Logical, a.Pool, nil) a.Pool = 0 } if a.Layout != 0 { d.DestroyDescriptorSetLayout(a.device.Logical, a.Layout, nil) a.Layout = 0 } if a.Sampler != 0 { d.DestroySampler(a.device.Logical, a.Sampler, nil) a.Sampler = 0 } if a.View != 0 { d.DestroyImageView(a.device.Logical, a.View, nil) a.View = 0 } if a.Image != 0 { d.DestroyImage(a.device.Logical, a.Image, nil) a.Image = 0 } if a.Memory != 0 { d.FreeMemory(a.device.Logical, a.Memory, nil) a.Memory = 0 } } func (d *Device) newGlyphAtlas() (a *glyphAtlas, err error) { a = &glyphAtlas{device: d} defer func() { if err != nil { a.Close() } }() 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} if err = vulkan.Check(d.Dispatch.CreateImage(d.Logical, &ci, nil, &a.Image)); err != nil { return nil, fmt.Errorf("create glyph atlas: %w", err) } var req vulkan.MemoryRequirements d.Dispatch.GetImageMemoryRequirements(d.Logical, a.Image, &req) var mem vulkan.PhysicalDeviceMemoryProperties d.InstanceDispatch.GetPhysicalDeviceMemoryProperties(d.Physical, &mem) index := uint32(math.MaxUint32) for i := uint32(0); i < mem.MemoryTypeCount; i++ { if req.MemoryTypeBits&(1< TRANSFER // write dependency waits for prior submissions sampling even evicted pages; the // reverse dependency makes new pixels visible to this frame's fragment shader. func (a *glyphAtlas) recordUploads(cmd vulkan.CommandBuffer, staging vulkan.Buffer, uploads []text.Upload) { d := a.device.Dispatch sub := vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: AtlasPages} 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} dep := vulkan.DependencyInfo{SType: vulkan.StructureTypeDependencyInfo, ImageMemoryBarrierCount: 1, ImageMemoryBarriers: &barrier} if !a.initialized { barrier.OldLayout = vulkan.ImageLayoutUndefined barrier.SrcStageMask = vulkan.PipelineStage2None barrier.SrcAccessMask = 0 } if !a.initialized || len(uploads) > 0 { d.CmdPipelineBarrier2(cmd, &dep) } if !a.initialized { clear := vulkan.ClearColorValue{} d.CmdClearColorImage(cmd, a.Image, vulkan.ImageLayoutTransferDstOptimal, &clear, 1, &sub) if len(uploads) > 0 { // Clear and copy are both transfer writes; order overlapping regions. barrier.OldLayout = vulkan.ImageLayoutTransferDstOptimal barrier.NewLayout = vulkan.ImageLayoutTransferDstOptimal barrier.SrcStageMask = vulkan.PipelineStage2TransferBit barrier.SrcAccessMask = vulkan.Access2TransferWriteBit barrier.DstStageMask = vulkan.PipelineStage2TransferBit barrier.DstAccessMask = vulkan.Access2TransferWriteBit d.CmdPipelineBarrier2(cmd, &dep) } } offset := vulkan.DeviceSize(0) for _, u := range uploads { 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}} d.CmdCopyBufferToImage(cmd, staging, a.Image, vulkan.ImageLayoutTransferDstOptimal, 1, ®ion) offset += vulkan.DeviceSize(len(u.Bytes)) } if !a.initialized || len(uploads) > 0 { barrier.OldLayout = vulkan.ImageLayoutTransferDstOptimal barrier.NewLayout = vulkan.ImageLayoutShaderReadOnlyOptimal barrier.SrcStageMask = vulkan.PipelineStage2TransferBit barrier.SrcAccessMask = vulkan.Access2TransferWriteBit barrier.DstStageMask = vulkan.PipelineStage2FragmentShaderBit barrier.DstAccessMask = vulkan.Access2ShaderSampledReadBit d.CmdPipelineBarrier2(cmd, &dep) } } func (f *Frame) stageGlyphs(uploads []text.Upload) error { if len(uploads) == 0 { return nil } total := 0 for _, u := range uploads { 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 { return fmt.Errorf("invalid atlas upload") } total += len(u.Bytes) } if f.glyphStaging == nil || total > f.glyphStaging.capacity { if f.glyphStaging != nil { f.glyphStaging.Close() } f.glyphStaging = &instanceBuffer{device: f.device} b := f.glyphStaging ci := vulkan.BufferCreateInfo{SType: vulkan.StructureTypeBufferCreateInfo, Size: vulkan.DeviceSize(total), Usage: vulkan.BufferUsageTransferSrcBit, SharingMode: vulkan.SharingModeExclusive} if err := vulkan.Check(f.device.Dispatch.CreateBuffer(f.device.Logical, &ci, nil, &b.Buffer)); err != nil { return err } var req vulkan.MemoryRequirements f.device.Dispatch.GetBufferMemoryRequirements(f.device.Logical, b.Buffer, &req) var mem vulkan.PhysicalDeviceMemoryProperties f.device.InstanceDispatch.GetPhysicalDeviceMemoryProperties(f.device.Physical, &mem) index := uint32(math.MaxUint32) want := vulkan.MemoryPropertyFlags(vulkan.MemoryPropertyHostVisibleBit | vulkan.MemoryPropertyHostCoherentBit) for i := uint32(0); i < mem.MemoryTypeCount; i++ { if req.MemoryTypeBits&(1<