1package gpu
2
3import (
4 "fmt"
5 "math"
6 "unsafe"
7
8 "github.com/bnema/purego-vulkan/vulkan"
9 "golang.org/x/sys/unix"
10)
11
12// Image is a render target backed by exportable DMA-BUF memory. FD stays owned
13// by the Image; the Wayland layer duplicates it on import.
14type Image struct {
15 dev *Device
16 image vulkan.Image
17 memory vulkan.DeviceMemory
18 view vulkan.ImageView
19
20 Width, Height int32
21 FD int
22 Modifier uint64
23 Offset, Stride uint32
24
25 // used is false until the first render: the first transition starts from
26 // an undefined layout.
27 used bool
28}
29
30// NewImage allocates a Width x Height B8G8R8A8 image with the given modifier.
31func (d *Device) NewImage(width, height int32, mod Modifier) (img *Image, err error) {
32 if width <= 0 || height <= 0 {
33 return nil, fmt.Errorf("gpu: invalid image size %dx%d", width, height)
34 }
35 img = &Image{dev: d, Width: width, Height: height, FD: -1}
36 defer func() {
37 if err != nil {
38 img.Close()
39 img = nil
40 }
41 }()
42 modifier := uint64(mod)
43 list := vulkan.ImageDrmFormatModifierListCreateInfoEXT{SType: vulkan.StructureTypeImageDRMFormatModifierListCreateInfoEXT, DrmFormatModifierCount: 1, DrmFormatModifiers: &modifier}
44 ext := vulkan.ExternalMemoryImageCreateInfo{SType: vulkan.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&list), HandleTypes: vulkan.ExternalMemoryHandleTypeDMABUFBitEXT}
45 ci := vulkan.ImageCreateInfo{
46 SType: vulkan.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&ext),
47 ImageType: vulkan.ImageType2d, Format: vulkan.FormatB8g8r8a8Unorm,
48 Extent: vulkan.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1},
49 MipLevels: 1, ArrayLayers: 1, Samples: vulkan.SampleCount1Bit,
50 Tiling: vulkan.ImageTilingDRMFormatModifierEXT, Usage: vulkan.ImageUsageColorAttachmentBit,
51 SharingMode: vulkan.SharingModeExclusive, InitialLayout: vulkan.ImageLayoutUndefined,
52 }
53 if err = vulkan.Check(d.disp.CreateImage(d.logical, &ci, nil, &img.image)); err != nil {
54 return nil, fmt.Errorf("gpu: create image: %w", err)
55 }
56 props := vulkan.ImageDrmFormatModifierPropertiesEXT{SType: vulkan.StructureTypeImageDRMFormatModifierPropertiesEXT}
57 if err = vulkan.Check(d.disp.GetImageDrmFormatModifierPropertiesEXT(d.logical, img.image, &props)); err != nil {
58 return nil, err
59 }
60 if props.DrmFormatModifier != modifier {
61 return nil, fmt.Errorf("gpu: asked for modifier %#x, driver chose %#x", modifier, props.DrmFormatModifier)
62 }
63 img.Modifier = modifier
64 sub := vulkan.ImageSubresource{AspectMask: vulkan.ImageAspectMemoryPlane0BitEXT}
65 var layout vulkan.SubresourceLayout
66 d.disp.GetImageSubresourceLayout(d.logical, img.image, &sub, &layout)
67 if layout.RowPitch == 0 || layout.RowPitch > math.MaxUint32 || layout.Offset > math.MaxUint32 {
68 return nil, fmt.Errorf("gpu: unusable plane layout offset %d stride %d", layout.Offset, layout.RowPitch)
69 }
70 img.Offset, img.Stride = uint32(layout.Offset), uint32(layout.RowPitch)
71
72 reqInfo := vulkan.ImageMemoryRequirementsInfo2{SType: vulkan.StructureTypeImageMemoryRequirementsInfo2, Image: img.image}
73 reqs := vulkan.MemoryRequirements2{SType: vulkan.StructureTypeMemoryRequirements2}
74 d.disp.GetImageMemoryRequirements2(d.logical, &reqInfo, &reqs)
75 idx, err := d.memoryType(reqs.MemoryRequirements.MemoryTypeBits, vulkan.MemoryPropertyDeviceLocalBit)
76 if err != nil {
77 return nil, err
78 }
79 ded := vulkan.MemoryDedicatedAllocateInfo{SType: vulkan.StructureTypeMemoryDedicatedAllocateInfo, Image: img.image}
80 exp := vulkan.ExportMemoryAllocateInfo{SType: vulkan.StructureTypeExportMemoryAllocateInfo, Next: unsafe.Pointer(&ded), HandleTypes: vulkan.ExternalMemoryHandleTypeDMABUFBitEXT}
81 ai := vulkan.MemoryAllocateInfo{SType: vulkan.StructureTypeMemoryAllocateInfo, Next: unsafe.Pointer(&exp), AllocationSize: reqs.MemoryRequirements.Size, MemoryTypeIndex: idx}
82 if err = vulkan.Check(d.disp.AllocateMemory(d.logical, &ai, nil, &img.memory)); err != nil {
83 return nil, fmt.Errorf("gpu: allocate image memory: %w", err)
84 }
85 bind := vulkan.BindImageMemoryInfo{SType: vulkan.StructureTypeBindImageMemoryInfo, Image: img.image, Memory: img.memory}
86 if err = vulkan.Check(d.disp.BindImageMemory2(d.logical, 1, &bind)); err != nil {
87 return nil, fmt.Errorf("gpu: bind image memory: %w", err)
88 }
89 fdi := vulkan.MemoryGetFdInfoKHR{SType: vulkan.StructureTypeMemoryGetFDInfoKHR, Memory: img.memory, HandleType: vulkan.ExternalMemoryHandleTypeDMABUFBitEXT}
90 fd := int32(-1)
91 if err = vulkan.Check(d.disp.GetMemoryFdKHR(d.logical, &fdi, &fd)); err != nil {
92 return nil, fmt.Errorf("gpu: export DMA-BUF: %w", err)
93 }
94 img.FD = int(fd)
95 vi := vulkan.ImageViewCreateInfo{
96 SType: vulkan.StructureTypeImageViewCreateInfo, Image: img.image, ViewType: vulkan.ImageViewType2d,
97 Format: vulkan.FormatB8g8r8a8Unorm,
98 SubresourceRange: vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: 1},
99 }
100 if err = vulkan.Check(d.disp.CreateImageView(d.logical, &vi, nil, &img.view)); err != nil {
101 return nil, err
102 }
103 return img, nil
104}
105
106// Close frees the image. The GPU must be done with it; the compositor's import
107// holds its own kernel reference.
108func (i *Image) Close() {
109 if i == nil || i.dev == nil {
110 return
111 }
112 d := i.dev
113 if i.FD >= 0 {
114 _ = unix.Close(i.FD)
115 i.FD = -1
116 }
117 if i.view != 0 {
118 d.disp.DestroyImageView(d.logical, i.view, nil)
119 i.view = 0
120 }
121 if i.image != 0 {
122 d.disp.DestroyImage(d.logical, i.image, nil)
123 i.image = 0
124 }
125 if i.memory != 0 {
126 d.disp.FreeMemory(d.logical, i.memory, nil)
127 i.memory = 0
128 }
129}