atagen

monolith

public
Star0
Watch0
a031f2a94df4fef8f039a91deef03d88cf4ac99b
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/render-vk/src/image.rsRust405 lines12.3 KB
1
use crate::Error;
2
use crate::device::Shared;
3
use crate::format::{components, vk_format};
4
use ash::vk;
5
use gpu_allocator::MemoryLocation;
6
use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme};
7
use smithay::backend::allocator::Fourcc;
8
use smithay::backend::renderer::{Texture, TextureMapping};
9
use std::fmt;
10
use std::marker::PhantomData;
11
use std::sync::{Arc, Mutex};
12
use std::time::{Duration, Instant};
13
14
pub(crate) enum Memory {
15
    Allocated(Allocation),
16
    /// Memory imported from a dmabuf, owned directly.
17
    Imported(vk::DeviceMemory),
18
}
19
20
pub(crate) struct ImageInner {
21
    pub shared: Arc<Shared>,
22
    pub image: vk::Image,
23
    pub view: vk::ImageView,
24
    pub memory: Mutex<Option<Memory>>,
25
    pub fourcc: Fourcc,
26
    pub format: vk::Format,
27
    pub width: u32,
28
    pub height: u32,
29
    pub layout: Mutex<vk::ImageLayout>,
30
    /// Shared with other devices or processes through a dmabuf. Every use
31
    /// acquires the image from the foreign queue family and releases it after.
32
    pub external: bool,
33
}
34
35
impl fmt::Debug for ImageInner {
36
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37
        f.debug_struct("ImageInner")
38
            .field("image", &self.image)
39
            .field("fourcc", &self.fourcc)
40
            .field("size", &(self.width, self.height))
41
            .field("external", &self.external)
42
            .finish()
43
    }
44
}
45
46
impl Drop for ImageInner {
47
    fn drop(&mut self) {
48
        let d = &self.shared.device.device;
49
        unsafe {
50
            d.destroy_image_view(self.view, None);
51
            d.destroy_image(self.image, None);
52
        }
53
        match self.memory.lock().unwrap_or_else(|e| e.into_inner()).take() {
54
            Some(Memory::Allocated(a)) => {
55
                let _ = self
56
                    .shared
57
                    .allocator
58
                    .lock()
59
                    .unwrap_or_else(|e| e.into_inner())
60
                    .free(a);
61
            }
62
            Some(Memory::Imported(m)) => unsafe { d.free_memory(m, None) },
63
            None => {}
64
        }
65
    }
66
}
67
68
impl ImageInner {
69
    pub fn create(
70
        shared: &Arc<Shared>,
71
        fourcc: Fourcc,
72
        width: u32,
73
        height: u32,
74
        usage: vk::ImageUsageFlags,
75
    ) -> Result<Arc<Self>, Error> {
76
        let fmt = vk_format(fourcc).ok_or(Error::UnsupportedFormat(fourcc))?;
77
        if width == 0 || height == 0 {
78
            return Err(Error::InvalidSize(width as i32, height as i32));
79
        }
80
        let d = &shared.device.device;
81
        let info = vk::ImageCreateInfo::default()
82
            .image_type(vk::ImageType::TYPE_2D)
83
            .format(fmt.format)
84
            .extent(vk::Extent3D {
85
                width,
86
                height,
87
                depth: 1,
88
            })
89
            .mip_levels(1)
90
            .array_layers(1)
91
            .samples(vk::SampleCountFlags::TYPE_1)
92
            .tiling(vk::ImageTiling::OPTIMAL)
93
            .usage(usage)
94
            .initial_layout(vk::ImageLayout::UNDEFINED);
95
        let image = unsafe { d.create_image(&info, None)? };
96
        let requirements = unsafe { d.get_image_memory_requirements(image) };
97
        let allocation = shared
98
            .allocator
99
            .lock()
100
            .unwrap_or_else(|e| e.into_inner())
101
            .allocate(&AllocationCreateDesc {
102
                name: "image",
103
                requirements,
104
                location: MemoryLocation::GpuOnly,
105
                linear: false,
106
                allocation_scheme: AllocationScheme::GpuAllocatorManaged,
107
            });
108
        let allocation = match allocation {
109
            Ok(a) => a,
110
            Err(e) => {
111
                unsafe { d.destroy_image(image, None) };
112
                return Err(e.into());
113
            }
114
        };
115
        let memory = Memory::Allocated(allocation);
116
        let (mem, offset) = match &memory {
117
            Memory::Allocated(a) => (unsafe { a.memory() }, a.offset()),
118
            Memory::Imported(m) => (*m, 0),
119
        };
120
        Self::finish(
121
            shared, image, memory, mem, offset, fourcc, width, height, false,
122
        )
123
    }
124
125
    /// Binds memory, creates the view and wraps everything up. Takes
126
    /// ownership of `image` and `memory`, releasing both on failure.
127
    #[expect(clippy::too_many_arguments)]
128
    pub fn finish(
129
        shared: &Arc<Shared>,
130
        image: vk::Image,
131
        memory: Memory,
132
        mem: vk::DeviceMemory,
133
        offset: u64,
134
        fourcc: Fourcc,
135
        width: u32,
136
        height: u32,
137
        external: bool,
138
    ) -> Result<Arc<Self>, Error> {
139
        let fmt = vk_format(fourcc).ok_or(Error::UnsupportedFormat(fourcc))?;
140
        let d = &shared.device.device;
141
        let mut inner = Self {
142
            shared: shared.clone(),
143
            image,
144
            view: vk::ImageView::null(),
145
            memory: Mutex::new(Some(memory)),
146
            fourcc,
147
            format: fmt.format,
148
            width,
149
            height,
150
            layout: Mutex::new(if external {
151
                vk::ImageLayout::GENERAL
152
            } else {
153
                vk::ImageLayout::UNDEFINED
154
            }),
155
            external,
156
        };
157
        unsafe { d.bind_image_memory(image, mem, offset)? };
158
        let view_info = vk::ImageViewCreateInfo::default()
159
            .image(image)
160
            .view_type(vk::ImageViewType::TYPE_2D)
161
            .format(fmt.format)
162
            .components(components(fmt.opaque))
163
            .subresource_range(color_range());
164
        inner.view = unsafe { d.create_image_view(&view_info, None)? };
165
        Ok(Arc::new(inner))
166
    }
167
168
    pub fn layout(&self) -> vk::ImageLayout {
169
        *self.layout.lock().unwrap_or_else(|e| e.into_inner())
170
    }
171
172
    pub fn set_layout(&self, layout: vk::ImageLayout) {
173
        *self.layout.lock().unwrap_or_else(|e| e.into_inner()) = layout;
174
    }
175
}
176
177
pub(crate) fn color_range() -> vk::ImageSubresourceRange {
178
    vk::ImageSubresourceRange::default()
179
        .aspect_mask(vk::ImageAspectFlags::COLOR)
180
        .level_count(1)
181
        .layer_count(1)
182
}
183
184
/// A host-visible buffer for uploads and readbacks. Dropped ones go back to
185
/// a pool on `Shared` instead of being freed. Allocating and freeing large
186
/// host-visible memory takes a driver lock on NVIDIA that `vkQueueSubmit`
187
/// also waits on, so a staging buffer made per upload on the import worker
188
/// stalled the main thread's submits for 1 to 3 ms.
189
pub(crate) struct Staging {
190
    pub shared: Arc<Shared>,
191
    pub buffer: vk::Buffer,
192
    pub allocation: Option<Allocation>,
193
    size: usize,
194
    usage: vk::BufferUsageFlags,
195
    location: MemoryLocation,
196
}
197
198
pub(crate) struct Pooled {
199
    pub buffer: vk::Buffer,
200
    pub allocation: Allocation,
201
    size: usize,
202
    usage: vk::BufferUsageFlags,
203
    location: MemoryLocation,
204
    returned: Instant,
205
}
206
207
/// Pooled staging memory kept at most, past which dropped buffers are freed.
208
const POOL_BYTES: usize = 32 * 1024 * 1024;
209
210
/// How long a pooled buffer may go unused before it is freed.
211
const POOL_IDLE: Duration = Duration::from_secs(2);
212
213
/// Frees pooled staging buffers that have sat unused for [`POOL_IDLE`].
214
pub(crate) fn trim_staging(shared: &Shared) {
215
    let now = Instant::now();
216
    let expired: Vec<Pooled> = shared
217
        .staging_pool
218
        .lock()
219
        .unwrap_or_else(|e| e.into_inner())
220
        .extract_if(.., |p| now - p.returned >= POOL_IDLE)
221
        .collect();
222
    if expired.is_empty() {
223
        return;
224
    }
225
    let mut allocator = shared.allocator.lock().unwrap_or_else(|e| e.into_inner());
226
    for p in expired {
227
        unsafe { shared.device.device.destroy_buffer(p.buffer, None) };
228
        let _ = allocator.free(p.allocation);
229
    }
230
}
231
232
impl Staging {
233
    pub fn new(
234
        shared: &Arc<Shared>,
235
        size: usize,
236
        usage: vk::BufferUsageFlags,
237
        location: MemoryLocation,
238
    ) -> Result<Self, Error> {
239
        let reused = {
240
            let mut pool = shared
241
                .staging_pool
242
                .lock()
243
                .unwrap_or_else(|e| e.into_inner());
244
            let fits = |p: &Pooled| {
245
                p.usage == usage && p.location == location && p.size >= size && p.size <= size * 2
246
            };
247
            pool.iter().position(fits).map(|i| pool.swap_remove(i))
248
        };
249
        if let Some(p) = reused {
250
            return Ok(Self {
251
                shared: shared.clone(),
252
                buffer: p.buffer,
253
                allocation: Some(p.allocation),
254
                size: p.size,
255
                usage,
256
                location,
257
            });
258
        }
259
        let d = &shared.device.device;
260
        let buffer = unsafe {
261
            d.create_buffer(
262
                &vk::BufferCreateInfo::default()
263
                    .size(size as u64)
264
                    .usage(usage),
265
                None,
266
            )?
267
        };
268
        let requirements = unsafe { d.get_buffer_memory_requirements(buffer) };
269
        let allocation = shared
270
            .allocator
271
            .lock()
272
            .unwrap_or_else(|e| e.into_inner())
273
            .allocate(&AllocationCreateDesc {
274
                name: "staging",
275
                requirements,
276
                location,
277
                linear: true,
278
                allocation_scheme: AllocationScheme::GpuAllocatorManaged,
279
            });
280
        let allocation = match allocation {
281
            Ok(a) => a,
282
            Err(e) => {
283
                unsafe { d.destroy_buffer(buffer, None) };
284
                return Err(e.into());
285
            }
286
        };
287
        unsafe { d.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())? };
288
        Ok(Self {
289
            shared: shared.clone(),
290
            buffer,
291
            allocation: Some(allocation),
292
            size,
293
            usage,
294
            location,
295
        })
296
    }
297
298
    pub fn bytes(&self) -> &[u8] {
299
        self.allocation
300
            .as_ref()
301
            .and_then(|a| a.mapped_slice())
302
            .unwrap_or(&[])
303
    }
304
305
    pub fn bytes_mut(&mut self) -> &mut [u8] {
306
        self.allocation
307
            .as_mut()
308
            .and_then(|a| a.mapped_slice_mut())
309
            .unwrap_or(&mut [])
310
    }
311
}
312
313
impl Drop for Staging {
314
    fn drop(&mut self) {
315
        let Some(allocation) = self.allocation.take() else {
316
            return;
317
        };
318
        let mut pool = self
319
            .shared
320
            .staging_pool
321
            .lock()
322
            .unwrap_or_else(|e| e.into_inner());
323
        let pooled: usize = pool.iter().map(|p| p.size).sum();
324
        if pooled + self.size <= POOL_BYTES {
325
            pool.push(Pooled {
326
                buffer: self.buffer,
327
                allocation,
328
                size: self.size,
329
                usage: self.usage,
330
                location: self.location,
331
                returned: Instant::now(),
332
            });
333
            return;
334
        }
335
        drop(pool);
336
        unsafe { self.shared.device.device.destroy_buffer(self.buffer, None) };
337
        let _ = self
338
            .shared
339
            .allocator
340
            .lock()
341
            .unwrap_or_else(|e| e.into_inner())
342
            .free(allocation);
343
    }
344
}
345
346
/// A sampled texture.
347
#[derive(Clone, Debug)]
348
pub struct VulkanTexture(pub(crate) Arc<ImageInner>);
349
350
/// An image the renderer can draw into, from [`smithay::backend::renderer::Offscreen`].
351
#[derive(Debug)]
352
pub struct VulkanImage(pub(crate) Arc<ImageInner>);
353
354
impl VulkanImage {
355
    /// The image's contents as a texture, for drawing it elsewhere.
356
    pub fn texture(&self) -> VulkanTexture {
357
        VulkanTexture(self.0.clone())
358
    }
359
}
360
361
/// A bound render target, borrowing whatever it was bound from.
362
#[derive(Debug)]
363
pub struct VulkanTarget<'a>(
364
    pub(crate) Arc<ImageInner>,
365
    pub(crate) PhantomData<&'a mut ()>,
366
);
367
368
/// Pixels read back from the GPU.
369
#[derive(Debug)]
370
pub struct VulkanMapping {
371
    pub(crate) data: Vec<u8>,
372
    pub(crate) width: u32,
373
    pub(crate) height: u32,
374
    pub(crate) fourcc: Fourcc,
375
}
376
377
macro_rules! texture_impl {
378
    ($t:ty, |$s:ident| $inner:expr) => {
379
        impl Texture for $t {
380
            fn width(&self) -> u32 {
381
                let $s = self;
382
                $inner.width
383
            }
384
            fn height(&self) -> u32 {
385
                let $s = self;
386
                $inner.height
387
            }
388
            fn format(&self) -> Option<Fourcc> {
389
                let $s = self;
390
                Some($inner.fourcc)
391
            }
392
        }
393
    };
394
}
395
396
texture_impl!(VulkanTexture, |s| s.0);
397
texture_impl!(VulkanImage, |s| s.0);
398
texture_impl!(VulkanTarget<'_>, |s| s.0);
399
texture_impl!(VulkanMapping, |s| s);
400
401
impl TextureMapping for VulkanMapping {
402
    fn flipped(&self) -> bool {
403
        false
404
    }
405
}