lgpu 0.1.2

Lightweight, bindless-first Vulkan 1.3 wrapper for Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
//! Logical GPU device and queue submission.

use crate::{
    BindlessComputePipeline, Buffer, BufferDesc, BufferId, CommandBuffer, CommandEncoder,
    ComputePipeline, Error, GraphicsPipeline, Sampler, SamplerDesc, SamplerId, Shader,
    SurfaceTexture, Texture, TextureDesc, TextureFormat, TextureId, TextureUsage,
    TimelineSemaphore,
};
use std::sync::Arc;

pub struct Device {
    pub(crate) vulkan_device: Arc<lgpu_hal::vulkan::VulkanDevice>,
}

pub struct Queue {
    pub(crate) vulkan_device: Arc<lgpu_hal::vulkan::VulkanDevice>,
}

impl Device {
    /// Creates an uninitialized GPU [`Buffer`] according to [`BufferDesc`].
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lgpu::{BufferDesc, BufferUsage, Device, MemoryLocation};
    ///
    /// # fn example(device: &Device) {
    /// let desc = BufferDesc {
    ///     label: Some("StorageBuffer"),
    ///     size: 1024,
    ///     usage: BufferUsage::STORAGE,
    ///     memory_location: MemoryLocation::GpuOnly,
    /// };
    /// let buffer = device.create_buffer(&desc).expect("Failed to create buffer");
    /// # }
    /// ```
    pub fn create_buffer(&self, desc: &BufferDesc) -> Result<Buffer, Error> {
        let hal_buffer = self.vulkan_device.create_buffer(desc)?;
        let id = if self.vulkan_device.bindless_heap.is_some() {
            Some(BufferId(
                self.vulkan_device.register_bindless_buffer(&hal_buffer)?,
            ))
        } else {
            None
        };

        Ok(Buffer {
            hal_buffer: Some(hal_buffer),
            vulkan_device: Arc::clone(&self.vulkan_device),
            size: desc.size,
            id,
        })
    }

    /// Creates a host-mapped GPU [`Buffer`] initialized with the provided byte slice.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lgpu::{BufferDesc, BufferUsage, Device, MemoryLocation};
    ///
    /// # fn example(device: &Device) {
    /// let desc = BufferDesc {
    ///     label: Some("InitBuffer"),
    ///     size: 64,
    ///     usage: BufferUsage::STORAGE,
    ///     memory_location: MemoryLocation::CpuToGpu,
    /// };
    /// let initial_bytes = [1u8, 2, 3, 4];
    /// let buffer = device.create_buffer_init(&desc, &initial_bytes).unwrap();
    /// # }
    /// ```
    pub fn create_buffer_init(&self, desc: &BufferDesc, data: &[u8]) -> Result<Buffer, Error> {
        let buffer = self.create_buffer(desc)?;
        buffer.write(data)?;
        Ok(buffer)
    }

    /// Creates a host-mapped GPU [`Buffer`] initialized with a typed slice implementing [`bytemuck::NoUninit`].
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lgpu::{BufferDesc, BufferUsage, Device, MemoryLocation};
    ///
    /// # fn example(device: &Device) {
    /// let desc = BufferDesc {
    ///     label: Some("FloatBuffer"),
    ///     size: 16,
    ///     usage: BufferUsage::STORAGE,
    ///     memory_location: MemoryLocation::CpuToGpu,
    /// };
    /// let floats = [1.0f32, 2.0, 3.0, 4.0];
    /// let buffer = device.create_buffer_init_slice(&desc, &floats).unwrap();
    /// # }
    /// ```
    pub fn create_buffer_init_slice<T: bytemuck::NoUninit>(
        &self,
        desc: &BufferDesc,
        data: &[T],
    ) -> Result<Buffer, Error> {
        self.create_buffer_init(desc, bytemuck::cast_slice(data))
    }

    pub fn create_shader_spirv(&self, spirv_words: &[u32]) -> Result<Shader, Error> {
        let hal_shader = self.vulkan_device.create_shader_from_spirv(spirv_words)?;
        Ok(Shader {
            hal_shader: Some(hal_shader),
            vulkan_device: Arc::clone(&self.vulkan_device),
        })
    }

    pub fn create_compute_pipeline(
        &self,
        shader: &Shader,
        buffer_count: u32,
    ) -> Result<ComputePipeline, Error> {
        let raw_shader = shader.hal_shader.as_ref().ok_or_else(|| {
            Error::Hal(lgpu_hal::HalError::InvalidParameter(
                "Shader hal resource is missing or destroyed".to_string(),
            ))
        })?;
        let hal_pipeline = self
            .vulkan_device
            .create_compute_pipeline(raw_shader, buffer_count)?;
        Ok(ComputePipeline {
            hal_pipeline: Some(hal_pipeline),
            vulkan_device: Arc::clone(&self.vulkan_device),
        })
    }

    pub fn create_bindless_compute_pipeline(
        &self,
        shader: &Shader,
    ) -> Result<BindlessComputePipeline, Error> {
        let raw_shader = shader.hal_shader.as_ref().ok_or_else(|| {
            Error::Hal(lgpu_hal::HalError::InvalidParameter(
                "Shader hal resource is missing or destroyed".to_string(),
            ))
        })?;
        let pipeline = self
            .vulkan_device
            .create_bindless_compute_pipeline(raw_shader)?;
        Ok(BindlessComputePipeline {
            pipeline: Some(pipeline),
            vulkan_device: Arc::clone(&self.vulkan_device),
        })
    }

    /// Creates a GPU [`Texture`] according to [`TextureDesc`].
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lgpu::{Device, TextureDesc, TextureFormat, TextureUsage};
    ///
    /// # fn example(device: &Device) {
    /// let desc = TextureDesc::new_2d(
    ///     512,
    ///     512,
    ///     TextureFormat::Rgba8Unorm,
    ///     TextureUsage::TEXTURE_BINDING | TextureUsage::COPY_DST,
    /// );
    /// let texture = device.create_texture(&desc).expect("Failed to create texture");
    /// # }
    /// ```
    pub fn create_texture(&self, desc: &TextureDesc) -> Result<Texture, Error> {
        let hal_texture = self.vulkan_device.create_texture(desc)?;
        let id = if desc.usage.contains(TextureUsage::TEXTURE_BINDING)
            && self.vulkan_device.bindless_heap.is_some()
        {
            Some(TextureId(
                self.vulkan_device.register_bindless_texture(&hal_texture)?,
            ))
        } else {
            None
        };
        let storage_id = if desc.usage.contains(TextureUsage::STORAGE_BINDING)
            && self.vulkan_device.bindless_heap.is_some()
        {
            Some(TextureId(
                self.vulkan_device
                    .register_bindless_storage_image(&hal_texture)?,
            ))
        } else {
            None
        };
        Ok(Texture {
            hal_texture: Some(hal_texture),
            vulkan_device: Arc::clone(&self.vulkan_device),
            width: desc.width,
            height: desc.height,
            format: desc.format,
            id,
            storage_id,
            owned: true,
        })
    }

    pub fn create_sampler(&self, desc: &SamplerDesc) -> Result<Sampler, Error> {
        let hal_sampler = self.vulkan_device.create_sampler(desc)?;
        let id = if self.vulkan_device.bindless_heap.is_some() {
            Some(SamplerId(
                self.vulkan_device.register_bindless_sampler(&hal_sampler)?,
            ))
        } else {
            None
        };
        Ok(Sampler {
            hal_sampler: Some(hal_sampler),
            vulkan_device: Arc::clone(&self.vulkan_device),
            id,
        })
    }

    /// Performs a batched update of bindless buffer descriptors in a single driver call.
    pub fn write_buffer_descriptors_batch(&self, writes: &[(u32, &Buffer)]) -> Result<(), Error> {
        let mut raw = Vec::with_capacity(writes.len());
        for (slot, buf) in writes {
            let hal_buf = buf.hal_buffer.as_ref().ok_or_else(|| {
                Error::Hal(lgpu_hal::HalError::InvalidParameter(
                    "Buffer hal resource is missing or destroyed".to_string(),
                ))
            })?;
            raw.push((*slot, hal_buf));
        }
        self.vulkan_device.write_buffer_descriptors_batch(&raw)?;
        Ok(())
    }

    pub fn create_graphics_pipeline(
        &self,
        shader: &Shader,
        vs_entry: &str,
        fs_entry: &str,
        color_format: TextureFormat,
    ) -> Result<GraphicsPipeline, Error> {
        self.create_graphics_pipeline_with_depth(
            shader,
            vs_entry,
            fs_entry,
            color_format,
            None,
            false,
            false,
        )
    }

    pub fn create_graphics_pipeline_with_depth(
        &self,
        shader: &Shader,
        vs_entry: &str,
        fs_entry: &str,
        color_format: TextureFormat,
        depth_format: Option<TextureFormat>,
        depth_test: bool,
        depth_write: bool,
    ) -> Result<GraphicsPipeline, Error> {
        let raw_shader = shader.hal_shader.as_ref().ok_or_else(|| {
            Error::Hal(lgpu_hal::HalError::InvalidParameter(
                "Shader hal resource is missing or destroyed".to_string(),
            ))
        })?;
        let pipeline = self.vulkan_device.create_graphics_pipeline_with_depth(
            raw_shader,
            vs_entry,
            fs_entry,
            color_format,
            depth_format,
            depth_test,
            depth_write,
        )?;
        Ok(GraphicsPipeline {
            pipeline: Some(pipeline),
            vulkan_device: Arc::clone(&self.vulkan_device),
        })
    }

    pub fn create_graphics_pipeline_with_desc(
        &self,
        shader: &Shader,
        desc: &lgpu_hal::GraphicsPipelineDesc,
    ) -> Result<GraphicsPipeline, Error> {
        let raw_shader = shader.hal_shader.as_ref().ok_or_else(|| {
            Error::Hal(lgpu_hal::HalError::InvalidParameter(
                "Shader hal resource is missing or destroyed".to_string(),
            ))
        })?;
        let pipeline = self
            .vulkan_device
            .create_graphics_pipeline_with_desc(raw_shader, desc)?;
        Ok(GraphicsPipeline {
            pipeline: Some(pipeline),
            vulkan_device: Arc::clone(&self.vulkan_device),
        })
    }

    /// Creates a new [`CommandEncoder`] for recording graphics and compute commands.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lgpu::Device;
    ///
    /// # fn example(device: &Device) {
    /// let mut encoder = device.create_command_encoder().expect("Failed to create encoder");
    /// // Record barriers, clear passes, or dispatches...
    /// let command_buffer = encoder.finish();
    /// # }
    /// ```
    pub fn create_command_encoder(&self) -> Result<CommandEncoder, Error> {
        let recorder = self.vulkan_device.create_command_recorder()?;
        Ok(CommandEncoder {
            recorder: Some(recorder),
            vulkan_device: Arc::clone(&self.vulkan_device),
        })
    }

    pub fn create_timeline_semaphore(
        &self,
        initial_value: u64,
    ) -> Result<TimelineSemaphore, Error> {
        let hal_semaphore = self
            .vulkan_device
            .create_timeline_semaphore(initial_value)?;
        Ok(TimelineSemaphore {
            hal_semaphore: Some(hal_semaphore),
            vulkan_device: Arc::clone(&self.vulkan_device),
        })
    }

    pub fn wait_idle(&self) -> Result<(), Error> {
        self.vulkan_device.wait_idle()?;
        Ok(())
    }

    pub fn write_texture(&self, texture: &Texture, data: &[u8]) -> Result<(), Error> {
        let raw_tex = texture.hal_texture.as_ref().ok_or_else(|| {
            Error::Hal(lgpu_hal::HalError::InvalidParameter(
                "Texture hal resource is missing or destroyed".to_string(),
            ))
        })?;
        self.vulkan_device.write_texture(raw_tex, data)?;
        Ok(())
    }
}

impl Queue {
    /// Submits a [`CommandBuffer`] to the GPU queue and waits synchronously for execution to finish.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lgpu::{CommandBuffer, Queue};
    ///
    /// # fn example(queue: &Queue, cmd: CommandBuffer) {
    /// queue.submit(cmd).expect("GPU submission failed");
    /// # }
    /// ```
    pub fn submit(&self, command_buffer: CommandBuffer) -> Result<(), Error> {
        self.vulkan_device
            .submit_and_wait(command_buffer.recorder)?;
        Ok(())
    }

    /// Submits command buffer work targeted to a swapchain surface without presenting.
    /// The acquired surface can then be presented explicitly via [`SurfaceTexture::present`].
    pub fn submit_surface_work(
        &self,
        command_buffer: CommandBuffer,
        surface_texture: &SurfaceTexture,
    ) -> Result<(), Error> {
        self.vulkan_device.submit_for_surface(
            command_buffer.recorder,
            surface_texture.acquire_semaphore,
            surface_texture.render_semaphore,
            surface_texture.frame_fence,
        )?;
        Ok(())
    }

    /// Submits command buffer work targeted to a swapchain surface and immediately queues presentation.
    pub fn submit_surface(
        &self,
        command_buffer: CommandBuffer,
        surface_texture: SurfaceTexture,
    ) -> Result<(), Error> {
        self.submit_surface_work(command_buffer, &surface_texture)?;
        surface_texture.present(self)
    }

    pub fn submit_batches(&self, command_buffers: Vec<CommandBuffer>) -> Result<(), Error> {
        let recorders = command_buffers.into_iter().map(|cb| cb.recorder).collect();
        self.vulkan_device.submit_batches_and_wait(recorders)?;
        Ok(())
    }

    pub fn submit_async(
        &self,
        command_buffer: CommandBuffer,
        wait_semaphores: &[(&TimelineSemaphore, u64)],
        signal_semaphores: &[(&TimelineSemaphore, u64)],
    ) -> Result<(), Error> {
        self.submit_batches_async(vec![command_buffer], wait_semaphores, signal_semaphores)
    }

    pub fn submit_batches_async(
        &self,
        command_buffers: Vec<CommandBuffer>,
        wait_semaphores: &[(&TimelineSemaphore, u64)],
        signal_semaphores: &[(&TimelineSemaphore, u64)],
    ) -> Result<(), Error> {
        let recorders = command_buffers.into_iter().map(|cb| cb.recorder).collect();
        let mut raw_wait = Vec::with_capacity(wait_semaphores.len());
        for (s, v) in wait_semaphores {
            let sem = s.hal_semaphore.as_ref().ok_or_else(|| {
                Error::Hal(lgpu_hal::HalError::InvalidParameter(
                    "Wait semaphore is missing or destroyed".to_string(),
                ))
            })?;
            raw_wait.push((sem, *v));
        }
        let mut raw_signal = Vec::with_capacity(signal_semaphores.len());
        for (s, v) in signal_semaphores {
            let sem = s.hal_semaphore.as_ref().ok_or_else(|| {
                Error::Hal(lgpu_hal::HalError::InvalidParameter(
                    "Signal semaphore is missing or destroyed".to_string(),
                ))
            })?;
            raw_signal.push((sem, *v));
        }
        self.vulkan_device
            .submit_batches_async(recorders, &raw_wait, &raw_signal)?;
        Ok(())
    }

    pub fn write_texture(&self, texture: &Texture, data: &[u8]) -> Result<(), Error> {
        let raw_tex = texture.hal_texture.as_ref().ok_or_else(|| {
            Error::Hal(lgpu_hal::HalError::InvalidParameter(
                "Texture hal resource is missing or destroyed".to_string(),
            ))
        })?;
        self.vulkan_device.write_texture(raw_tex, data)?;
        Ok(())
    }

    /// Writes byte data into a host-mapped [`Buffer`] at the specified offset.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lgpu::{Buffer, Queue};
    ///
    /// # fn example(queue: &Queue, buffer: &Buffer) {
    /// queue.write_buffer(buffer, 0, &[1u8, 2, 3, 4]).expect("Failed to write buffer");
    /// # }
    /// ```
    pub fn write_buffer(&self, buffer: &Buffer, offset: u64, data: &[u8]) -> Result<(), Error> {
        buffer.write_at(offset, data)
    }
}