Skip to main content

rotex_vulkan/bridge/
resources.rs

1use std::collections::HashSet;
2
3use ash::vk;
4
5use super::{VulkanBridge, types::VertexLayoutId};
6use crate::backend::vulkan::{Device, ImageDescriptor, RotexBuffer, RotexImage, RotexSampler, SamplerDescriptor};
7use crate::error::{Error, ErrorKind, vk_error};
8use rotex_types::resource::{
9    CreatedResources, IndexFormat, MaterialId, MeshDescriptor, MeshId, ResourceBatchCreate,
10    ResourceBatchUpdate, ResourceCreateDescriptor, ResourceHandle, ResourceUpdateDescriptor,
11    TextureDescriptor, TextureFormat, TextureId, VertexBufferLayout, VertexFormat,
12};
13
14impl VulkanBridge {
15    pub(super) fn ensure_default_texture(&mut self) -> Result<&super::types::TextureResource, Error> {
16        if self.default_texture.is_none() {
17            let fallback_descriptor = TextureDescriptor {
18                width: 1,
19                height: 1,
20                format: TextureFormat::Rgba8Unorm,
21                data: vec![255, 255, 255, 255],
22            };
23            self.default_texture = Some(self.create_texture_resource(&fallback_descriptor)?);
24        }
25        Ok(self.default_texture.as_ref().expect("default texture initialized"))
26    }
27
28    pub(super) fn create_texture_resource(
29        &mut self,
30        desc: &TextureDescriptor,
31    ) -> Result<super::types::TextureResource, Error> {
32        validate_texture_descriptor(desc)?;
33        let staging_buffer = RotexBuffer::new(
34            self.instance.raw(),
35            self.device.raw(),
36            desc.data.len() as vk::DeviceSize,
37            vk::BufferUsageFlags::TRANSFER_SRC,
38            vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
39        )?;
40        write_bytes(self.device.raw(), &staging_buffer, &desc.data)?;
41
42        let mut image = RotexImage::new(
43            self.instance.raw(),
44            self.device.raw(),
45            ImageDescriptor::default(
46                map_texture_format(desc.format),
47                vk::Extent3D {
48                    width: desc.width,
49                    height: desc.height,
50                    depth: 1,
51                },
52                vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED,
53                vk::MemoryPropertyFlags::DEVICE_LOCAL,
54            ),
55        )?;
56        let sampler = RotexSampler::new(
57            self.device.raw(),
58            SamplerDescriptor::default().with_filters(vk::Filter::LINEAR, vk::Filter::LINEAR),
59        )?;
60
61        if let Err(err) = self.upload_staging_texture(&staging_buffer, &mut image, desc.width, desc.height) {
62            sampler.destroy(self.device.raw());
63            image.destroy(self.device.raw());
64            staging_buffer.destroy(self.device.raw());
65            return Err(err);
66        }
67        staging_buffer.destroy(self.device.raw());
68
69        let layouts = [self.texture_set_layout.handle()];
70        let mut descriptor_sets = match self
71            .texture_descriptor_pool
72            .allocate_sets(self.device.raw(), &layouts)
73        {
74            Ok(sets) => sets,
75            Err(err) => {
76                sampler.destroy(self.device.raw());
77                image.destroy(self.device.raw());
78                return Err(err);
79            }
80        };
81        let descriptor_set = descriptor_sets.pop().expect("one descriptor set");
82        descriptor_set.write_image_sampler(
83            self.device.raw(),
84            0,
85            image.view(),
86            sampler.handle(),
87            vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
88        );
89
90        Ok(super::types::TextureResource {
91            descriptor: desc.clone(),
92            image,
93            sampler,
94            descriptor_set,
95        })
96    }
97
98    fn upload_staging_texture(
99        &mut self,
100        staging_buffer: &RotexBuffer,
101        image: &mut RotexImage,
102        width: u32,
103        height: u32,
104    ) -> Result<(), Error> {
105        self.in_flight_fence.wait(self.device.raw(), u64::MAX)?;
106        self.in_flight_fence.reset(self.device.raw())?;
107        unsafe {
108            self.device.raw().logical_device().reset_command_buffer(
109                self.command_buffer.handle(),
110                vk::CommandBufferResetFlags::empty(),
111            )
112        }
113        .map_err(vk_error)?;
114        self.command_buffer.begin(
115            self.device.raw(),
116            vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT,
117        )?;
118        image.transition_layout(
119            self.device.raw(),
120            &self.command_buffer,
121            vk::ImageLayout::TRANSFER_DST_OPTIMAL,
122        );
123        self.command_buffer.copy_buffer_to_image(
124            self.device.raw(),
125            staging_buffer.handle(),
126            image.handle(),
127            width,
128            height,
129        );
130        image.transition_layout(
131            self.device.raw(),
132            &self.command_buffer,
133            vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
134        );
135        self.command_buffer.end(self.device.raw())?;
136
137        let command_buffers = [self.command_buffer.handle()];
138        let submit = vk::SubmitInfo::default().command_buffers(&command_buffers);
139        let queue = self.device.raw().get_queue(self.graphics_queue_index, 0);
140        unsafe {
141            self.device.raw().logical_device().queue_submit(
142                queue,
143                &[submit],
144                self.in_flight_fence.handle(),
145            )
146        }
147        .map_err(vk_error)?;
148        self.in_flight_fence.wait(self.device.raw(), u64::MAX)?;
149        Ok(())
150    }
151
152    pub(super) fn create_mesh_resource(
153        &self,
154        desc: &MeshDescriptor,
155        vertex_layout_id: VertexLayoutId,
156    ) -> Result<super::types::MeshResource, Error> {
157        if desc.index_count == 0 {
158            return Err(Error::fatal(ErrorKind::NoCompatibleDevice));
159        }
160        let vertex_size = desc.vertex_data.len() as vk::DeviceSize;
161        let index_size = desc.index_data.len() as vk::DeviceSize;
162        let index_type = map_index_type(desc.index_format);
163        let min_index_bytes = desc.index_count as usize * index_format_size(desc.index_format);
164        if desc.index_data.len() < min_index_bytes {
165            return Err(Error::fatal(ErrorKind::Unsupported(
166                "Index data is shorter than index_count requires",
167            )));
168        }
169        let vertex_buffer = RotexBuffer::new(
170            self.instance.raw(),
171            self.device.raw(),
172            vertex_size,
173            vk::BufferUsageFlags::VERTEX_BUFFER,
174            vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
175        )?;
176        let index_buffer = RotexBuffer::new(
177            self.instance.raw(),
178            self.device.raw(),
179            index_size,
180            vk::BufferUsageFlags::INDEX_BUFFER,
181            vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
182        )?;
183        write_bytes(self.device.raw(), &vertex_buffer, &desc.vertex_data)?;
184        write_bytes(self.device.raw(), &index_buffer, &desc.index_data)?;
185        Ok(super::types::MeshResource {
186            vertex_buffer,
187            index_buffer,
188            index_type,
189            index_count: desc.index_count,
190            vertex_layout_id,
191        })
192    }
193
194    pub fn create_resources(
195        &mut self,
196        descriptor: ResourceBatchCreate,
197    ) -> Result<CreatedResources, Error> {
198        let mut handles = Vec::with_capacity(descriptor.resources.len());
199        for item in descriptor.resources {
200            match item {
201                ResourceCreateDescriptor::Mesh(mesh) => {
202                    validate_vertex_layout(&mesh.vertex_layout, mesh.vertex_data.len())?;
203                    let vertex_layout_id = self.intern_vertex_layout(&mesh.vertex_layout)?;
204                    let id = MeshId(self.next_mesh_id);
205                    self.next_mesh_id += 1;
206                    let resource = self.create_mesh_resource(&mesh, vertex_layout_id)?;
207                    self.meshes.insert(id, resource);
208                    handles.push(ResourceHandle::Mesh(id));
209                }
210                ResourceCreateDescriptor::Texture(texture) => {
211                    let id = TextureId(self.next_texture_id);
212                    self.next_texture_id += 1;
213                    let resource = self.create_texture_resource(&texture)?;
214                    self.textures.insert(id, resource);
215                    handles.push(ResourceHandle::Texture(id));
216                }
217                ResourceCreateDescriptor::Material(material) => {
218                    let id = MaterialId(self.next_material_id);
219                    self.next_material_id += 1;
220                    self.materials
221                        .insert(id, super::types::MaterialResource { descriptor: material });
222                    handles.push(ResourceHandle::Material(id));
223                }
224            }
225        }
226        Ok(CreatedResources { handles })
227    }
228
229    pub fn update_resources(
230        &mut self,
231        descriptor: ResourceBatchUpdate,
232    ) -> Result<(), Error> {
233        if descriptor.updates.iter().any(|update| {
234            matches!(
235                update,
236                ResourceUpdateDescriptor::Mesh { .. }
237                    | ResourceUpdateDescriptor::Texture { .. }
238                    | ResourceUpdateDescriptor::Material { .. }
239            )
240        }) {
241            self.in_flight_fence.wait(self.device.raw(), u64::MAX)?;
242        }
243        for item in descriptor.updates {
244            match item {
245                ResourceUpdateDescriptor::Mesh {
246                    id,
247                    vertex_data,
248                    vertex_layout,
249                    index_data,
250                    index_format,
251                    index_count,
252                } => {
253                    validate_vertex_layout(&vertex_layout, vertex_data.len())?;
254                    let vertex_layout_id = self.intern_vertex_layout(&vertex_layout)?;
255                    let resource = self.create_mesh_resource(
256                        &MeshDescriptor {
257                            vertex_data,
258                            vertex_layout,
259                            index_data,
260                            index_format,
261                            index_count,
262                        },
263                        vertex_layout_id,
264                    )?;
265                    let old = self
266                        .meshes
267                        .remove(&id)
268                        .ok_or(Error::fatal(ErrorKind::NoCompatibleDevice))?;
269                    old.vertex_buffer.destroy(self.device.raw());
270                    old.index_buffer.destroy(self.device.raw());
271                    self.meshes.insert(id, resource);
272                }
273                ResourceUpdateDescriptor::Texture { id, data } => {
274                    let old_texture_descriptor = self
275                        .textures
276                        .get(&id)
277                        .ok_or(Error::fatal(ErrorKind::NoCompatibleDevice))?;
278                    let mut updated_texture_descriptor = old_texture_descriptor.descriptor.clone();
279                    updated_texture_descriptor.data = data;
280                    let updated_texture = self.create_texture_resource(&updated_texture_descriptor)?;
281                    let previous = self
282                        .textures
283                        .insert(id, updated_texture)
284                        .expect("texture must exist after get check");
285                    previous.destroy(self.device.raw(), &self.texture_descriptor_pool);
286                }
287                ResourceUpdateDescriptor::Material {
288                    id,
289                    enable_depth,
290                    texture,
291                } => {
292                    let material = self
293                        .materials
294                        .get_mut(&id)
295                        .ok_or(Error::fatal(ErrorKind::NoCompatibleDevice))?;
296                    if let Some(enable_depth) = enable_depth {
297                        material.descriptor.enable_depth = enable_depth;
298                    }
299                    if let Some(texture) = texture {
300                        material.descriptor.texture = texture;
301                    }
302                    self.invalidate_material_pipelines(id);
303                }
304            }
305        }
306        Ok(())
307    }
308
309    fn intern_vertex_layout(&mut self, layout: &VertexBufferLayout) -> Result<VertexLayoutId, Error> {
310        let layout_id = compute_vertex_layout_id(layout);
311        if let Some(existing) = self.vertex_layouts.get(&layout_id) {
312            if existing != layout {
313                return Err(Error::fatal(ErrorKind::Unsupported(
314                    "Vertex layout ID collision detected",
315                )));
316            }
317        } else {
318            self.vertex_layouts.insert(layout_id, layout.clone());
319        }
320        Ok(layout_id)
321    }
322}
323
324fn write_bytes(device: &Device, buffer: &RotexBuffer, data: &[u8]) -> Result<(), Error> {
325    let ptr = buffer.map(device)? as *mut u8;
326    unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), ptr, data.len()) };
327    buffer.unmap(device);
328    Ok(())
329}
330
331fn map_index_type(index_format: IndexFormat) -> vk::IndexType {
332    match index_format {
333        IndexFormat::Uint16 => vk::IndexType::UINT16,
334        IndexFormat::Uint32 => vk::IndexType::UINT32,
335    }
336}
337
338fn index_format_size(index_format: IndexFormat) -> usize {
339    match index_format {
340        IndexFormat::Uint16 => 2,
341        IndexFormat::Uint32 => 4,
342    }
343}
344
345fn validate_vertex_layout(layout: &VertexBufferLayout, vertex_data_len: usize) -> Result<(), Error> {
346    if layout.array_stride == 0 {
347        return Err(Error::fatal(ErrorKind::Unsupported(
348            "Vertex layout stride must be greater than zero",
349        )));
350    }
351    if layout.array_stride > u32::MAX as u64 {
352        return Err(Error::fatal(ErrorKind::Unsupported(
353            "Vertex layout stride exceeds Vulkan limits",
354        )));
355    }
356    if !vertex_data_len.is_multiple_of(layout.array_stride as usize) {
357        return Err(Error::fatal(ErrorKind::Unsupported(
358            "Vertex data size is not aligned with vertex stride",
359        )));
360    }
361
362    let mut seen_locations = HashSet::new();
363    for attribute in &layout.attributes {
364        if !seen_locations.insert(attribute.location) {
365            return Err(Error::fatal(ErrorKind::Unsupported(
366                "Vertex layout has duplicate attribute location",
367            )));
368        }
369        let format_size = vertex_format_size(attribute.format);
370        if attribute.offset + format_size > layout.array_stride {
371            return Err(Error::fatal(ErrorKind::Unsupported(
372                "Vertex attribute exceeds stride bounds",
373            )));
374        }
375        if attribute.offset > u32::MAX as u64 {
376            return Err(Error::fatal(ErrorKind::Unsupported(
377                "Vertex attribute offset exceeds Vulkan limits",
378            )));
379        }
380    }
381    Ok(())
382}
383
384fn vertex_format_size(format: VertexFormat) -> u64 {
385    match format {
386        VertexFormat::Float32 => 4,
387        VertexFormat::Float32x2 => 8,
388        VertexFormat::Float32x3 => 12,
389        VertexFormat::Float32x4 => 16,
390        VertexFormat::Uint32 => 4,
391    }
392}
393
394fn vertex_format_tag(format: VertexFormat) -> u8 {
395    match format {
396        VertexFormat::Float32 => 1,
397        VertexFormat::Float32x2 => 2,
398        VertexFormat::Float32x3 => 3,
399        VertexFormat::Float32x4 => 4,
400        VertexFormat::Uint32 => 5,
401    }
402}
403
404fn map_texture_format(format: TextureFormat) -> vk::Format {
405    match format {
406        TextureFormat::Rgba8Unorm => vk::Format::R8G8B8A8_UNORM,
407    }
408}
409
410fn expected_texture_bytes(desc: &TextureDescriptor) -> Option<usize> {
411    (desc.width as usize)
412        .checked_mul(desc.height as usize)?
413        .checked_mul(4)
414}
415
416fn validate_texture_descriptor(desc: &TextureDescriptor) -> Result<(), Error> {
417    if desc.width == 0 || desc.height == 0 {
418        return Err(Error::fatal(ErrorKind::Unsupported(
419            "Texture dimensions must be greater than zero",
420        )));
421    }
422    let Some(expected_bytes) = expected_texture_bytes(desc) else {
423        return Err(Error::fatal(ErrorKind::Unsupported(
424            "Texture dimensions overflow expected byte size",
425        )));
426    };
427    if desc.data.len() != expected_bytes {
428        return Err(Error::fatal(ErrorKind::Unsupported(
429            "Texture data size does not match width*height*4",
430        )));
431    }
432    Ok(())
433}
434
435fn compute_vertex_layout_id(layout: &VertexBufferLayout) -> VertexLayoutId {
436    const FNV_OFFSET: u64 = 0xcbf29ce484222325;
437    const FNV_PRIME: u64 = 0x100000001b3;
438
439    fn hash_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
440        for byte in bytes {
441            hash ^= *byte as u64;
442            hash = hash.wrapping_mul(FNV_PRIME);
443        }
444        hash
445    }
446
447    let mut hash = FNV_OFFSET;
448    hash = hash_bytes(hash, &layout.array_stride.to_le_bytes());
449
450    let mut attributes = layout.attributes.clone();
451    attributes.sort_by_key(|attr| (attr.location, attr.offset, vertex_format_tag(attr.format)));
452    hash = hash_bytes(hash, &(attributes.len() as u32).to_le_bytes());
453    for attribute in attributes {
454        hash = hash_bytes(hash, &attribute.location.to_le_bytes());
455        hash = hash_bytes(hash, &[vertex_format_tag(attribute.format)]);
456        hash = hash_bytes(hash, &attribute.offset.to_le_bytes());
457    }
458
459    VertexLayoutId(hash)
460}