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}