1use crate::device::VulkanContext;
9use ash::vk;
10use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme};
11use gpu_allocator::MemoryLocation;
12use impeller_hal::{Error, Extent2D, PixelFormat, Result, TextureDescriptor, TextureUsage};
13
14pub struct VulkanTexture {
21 pub(crate) image: vk::Image,
22 pub(crate) memory: TextureMemory,
23 pub(crate) extent: Extent2D,
24 pub(crate) format: PixelFormat,
25 pub(crate) layout: std::cell::Cell<vk::ImageLayout>,
35 pub(crate) usage: TextureUsage,
36 pub(crate) mip_levels: u32,
38}
39
40pub(crate) enum TextureMemory {
46 Pooled(Allocation),
48 Dedicated(vk::DeviceMemory),
50 Borrowed,
58}
59
60impl VulkanTexture {
61 pub fn wrap_image(
67 image: vk::Image,
68 extent: Extent2D,
69 format: PixelFormat,
70 layout: vk::ImageLayout,
71 ) -> VulkanTexture {
72 VulkanTexture {
73 image,
74 memory: TextureMemory::Borrowed,
75 extent,
76 format,
77 layout: std::cell::Cell::new(layout),
78 mip_levels: 1,
81 usage: TextureUsage {
82 render_target: true,
83 sampled: false,
84 ..TextureUsage::default()
85 },
86 }
87 }
88
89 pub fn extent(&self) -> Extent2D {
90 self.extent
91 }
92
93 pub fn format(&self) -> PixelFormat {
94 self.format
95 }
96
97 pub fn raw_image(&self) -> vk::Image {
98 self.image
99 }
100
101 pub(crate) fn layout(&self) -> vk::ImageLayout {
102 self.layout.get()
103 }
104
105 pub(crate) fn set_layout(&self, layout: vk::ImageLayout) {
107 self.layout.set(layout);
108 }
109
110 pub fn byte_size(&self) -> u64 {
112 self.extent.area() * self.format.bytes_per_pixel() as u64
113 }
114}
115
116pub(crate) fn vk_format(format: PixelFormat) -> vk::Format {
118 match format {
119 PixelFormat::Rgba8Unorm => vk::Format::R8G8B8A8_UNORM,
120 PixelFormat::Rgba8UnormSrgb => vk::Format::R8G8B8A8_SRGB,
121 PixelFormat::Bgra8Unorm => vk::Format::B8G8R8A8_UNORM,
122 PixelFormat::Bgra8UnormSrgb => vk::Format::B8G8R8A8_SRGB,
123 PixelFormat::Rgb10A2Unorm => vk::Format::A2B10G10R10_UNORM_PACK32,
124 PixelFormat::Rgba16Float => vk::Format::R16G16B16A16_SFLOAT,
125 PixelFormat::R8Unorm => vk::Format::R8_UNORM,
126 }
127}
128
129impl VulkanContext {
130 pub fn create_texture(&mut self, desc: &TextureDescriptor) -> Result<VulkanTexture> {
137 if desc.is_external() {
138 return Err(Error::Unsupported("external image import"));
139 }
140 self.capabilities().check_texture(desc)?;
141 if !self.capabilities().can_allocate(desc.extent) {
142 return Err(Error::LimitExceeded {
143 what: "texture dimension",
144 requested: desc.extent.width.max(desc.extent.height) as u64,
145 limit: self.capabilities().max_texture_size as u64,
146 });
147 }
148 if desc.extent.is_empty() {
149 return Err(Error::Unsupported("zero-sized texture"));
150 }
151
152 let mut usage = vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::TRANSFER_DST;
153 if desc.usage.sampled {
154 usage |= vk::ImageUsageFlags::SAMPLED;
155 }
156 if desc.usage.render_target {
157 usage |= vk::ImageUsageFlags::COLOR_ATTACHMENT;
158 }
159
160 let info = vk::ImageCreateInfo::default()
161 .image_type(vk::ImageType::TYPE_2D)
162 .format(vk_format(desc.format))
163 .extent(vk::Extent3D {
164 width: desc.extent.width,
165 height: desc.extent.height,
166 depth: 1,
167 })
168 .mip_levels(desc.mip_levels.max(1))
169 .array_layers(1)
170 .samples(sample_count_flags(desc.sample_count))
171 .tiling(vk::ImageTiling::OPTIMAL)
172 .usage(usage)
173 .sharing_mode(vk::SharingMode::EXCLUSIVE)
174 .initial_layout(vk::ImageLayout::UNDEFINED);
175
176 let device = self.raw_device().clone();
177 let image = unsafe { device.create_image(&info, None) }
178 .map_err(|e| backend_err("create_image", e))?;
179
180 let requirements = unsafe { device.get_image_memory_requirements(image) };
181 let allocation = self
182 .allocator_mut()
183 .allocate(&AllocationCreateDesc {
184 name: "texture",
185 requirements,
186 location: MemoryLocation::GpuOnly,
187 linear: false,
188 allocation_scheme: AllocationScheme::GpuAllocatorManaged,
189 })
190 .map_err(|e| {
191 unsafe { device.destroy_image(image, None) };
192 Error::OutOfMemory {
193 what: "texture memory",
194 }
195 .with_detail(e)
196 })?;
197
198 unsafe { device.bind_image_memory(image, allocation.memory(), allocation.offset()) }
199 .map_err(|e| backend_err("bind_image_memory", e))?;
200
201 Ok(VulkanTexture {
202 image,
203 memory: TextureMemory::Pooled(allocation),
204 extent: desc.extent,
205 format: desc.format,
206 layout: std::cell::Cell::new(vk::ImageLayout::UNDEFINED),
207 usage: desc.usage,
208 mip_levels: desc.mip_levels.max(1),
209 })
210 }
211
212 pub fn destroy_texture(&mut self, texture: VulkanTexture) {
214 match texture.memory {
217 TextureMemory::Pooled(allocation) => {
218 let _ = self.allocator_mut().free(allocation);
219 }
220 TextureMemory::Dedicated(memory) => {
221 unsafe { self.raw_device().free_memory(memory, None) };
224 }
225 TextureMemory::Borrowed => return,
228 }
229 unsafe { self.raw_device().destroy_image(texture.image, None) };
233 }
234
235 pub fn transition_for_present(&mut self, texture: &VulkanTexture) -> Result<()> {
243 if texture.layout() == vk::ImageLayout::PRESENT_SRC_KHR {
244 return Ok(());
245 }
246 let device = self.raw_device().clone();
247 let cmd = self.begin_one_shot()?;
248 transition(
249 &device,
250 cmd,
251 texture.raw_image(),
252 texture.layout(),
253 vk::ImageLayout::PRESENT_SRC_KHR,
254 );
255 self.submit_one_shot(cmd)?;
256 texture.set_layout(vk::ImageLayout::PRESENT_SRC_KHR);
257 Ok(())
258 }
259
260 pub fn clear_texture(&mut self, texture: &mut VulkanTexture, color: [f32; 4]) -> Result<()> {
266 let device = self.raw_device().clone();
267 let cmd = self.begin_one_shot()?;
268
269 transition(
270 &device,
271 cmd,
272 texture.image,
273 texture.layout.get(),
274 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
275 );
276
277 let clear = vk::ClearColorValue { float32: color };
278 let range = vk::ImageSubresourceRange::default()
279 .aspect_mask(vk::ImageAspectFlags::COLOR)
280 .level_count(vk::REMAINING_MIP_LEVELS)
281 .layer_count(1);
282 unsafe {
283 device.cmd_clear_color_image(
284 cmd,
285 texture.image,
286 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
287 &clear,
288 &[range],
289 );
290 }
291 texture.layout.set(vk::ImageLayout::TRANSFER_DST_OPTIMAL);
292
293 self.submit_one_shot(cmd)
294 }
295
296 pub fn write_texture(&mut self, texture: &mut VulkanTexture, pixels: &[u8]) -> Result<()> {
307 let size = texture.byte_size();
308 if pixels.len() as u64 != size {
309 return Err(Error::Backend {
310 backend: "vulkan",
311 detail: format!("texture wants {size} bytes, {} supplied", pixels.len()),
312 });
313 }
314 let device = self.raw_device().clone();
315
316 let buffer_info = vk::BufferCreateInfo::default()
317 .size(size)
318 .usage(vk::BufferUsageFlags::TRANSFER_SRC)
319 .sharing_mode(vk::SharingMode::EXCLUSIVE);
320 let buffer = unsafe { device.create_buffer(&buffer_info, None) }
321 .map_err(|e| backend_err("create_buffer", e))?;
322 let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
323 let mut allocation = self
324 .allocator_mut()
325 .allocate(&AllocationCreateDesc {
326 name: "upload",
327 requirements,
328 location: MemoryLocation::CpuToGpu,
329 linear: true,
330 allocation_scheme: AllocationScheme::GpuAllocatorManaged,
331 })
332 .map_err(|e| {
333 unsafe { device.destroy_buffer(buffer, None) };
334 Error::OutOfMemory {
335 what: "upload buffer",
336 }
337 .with_detail(e)
338 })?;
339 if let Err(e) =
340 unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()) }
341 {
342 let _ = self.allocator_mut().free(allocation);
343 unsafe { device.destroy_buffer(buffer, None) };
344 return Err(backend_err("bind_buffer_memory", e));
345 }
346
347 let staged = match allocation.mapped_slice_mut() {
348 Some(slice) => {
349 slice[..pixels.len()].copy_from_slice(pixels);
350 Ok(())
351 }
352 None => Err(Error::Backend {
353 backend: "vulkan",
354 detail: "upload allocation was not host-visible".into(),
355 }),
356 };
357
358 let outcome = staged.and_then(|()| {
359 let cmd = self.begin_one_shot()?;
360 transition(
361 &device,
362 cmd,
363 texture.image,
364 texture.layout.get(),
365 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
366 );
367 let region = vk::BufferImageCopy::default()
368 .image_subresource(
369 vk::ImageSubresourceLayers::default()
370 .aspect_mask(vk::ImageAspectFlags::COLOR)
371 .layer_count(1),
372 )
373 .image_extent(vk::Extent3D {
374 width: texture.extent.width,
375 height: texture.extent.height,
376 depth: 1,
377 });
378 unsafe {
381 device.cmd_copy_buffer_to_image(
382 cmd,
383 buffer,
384 texture.image,
385 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
386 &[region],
387 );
388 }
389 texture.layout.set(vk::ImageLayout::TRANSFER_DST_OPTIMAL);
390 generate_mipmaps(&device, cmd, texture);
393 self.submit_one_shot(cmd)
394 });
395
396 let _ = self.allocator_mut().free(allocation);
397 unsafe { device.destroy_buffer(buffer, None) };
400 outcome
401 }
402
403 pub fn read_texture(&mut self, texture: &mut VulkanTexture) -> Result<Vec<u8>> {
404 let size = texture.byte_size();
405 let device = self.raw_device().clone();
406
407 let buffer_info = vk::BufferCreateInfo::default()
408 .size(size)
409 .usage(vk::BufferUsageFlags::TRANSFER_DST)
410 .sharing_mode(vk::SharingMode::EXCLUSIVE);
411 let buffer = unsafe { device.create_buffer(&buffer_info, None) }
412 .map_err(|e| backend_err("create_buffer", e))?;
413 let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
414 let allocation = self
415 .allocator_mut()
416 .allocate(&AllocationCreateDesc {
417 name: "readback",
418 requirements,
419 location: MemoryLocation::GpuToCpu,
420 linear: true,
421 allocation_scheme: AllocationScheme::GpuAllocatorManaged,
422 })
423 .map_err(|e| {
424 unsafe { device.destroy_buffer(buffer, None) };
425 Error::OutOfMemory {
426 what: "readback buffer",
427 }
428 .with_detail(e)
429 })?;
430 unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()) }
431 .map_err(|e| backend_err("bind_buffer_memory", e))?;
432
433 let cmd = self.begin_one_shot()?;
434 transition(
435 &device,
436 cmd,
437 texture.image,
438 texture.layout.get(),
439 vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
440 );
441 let region = vk::BufferImageCopy::default()
442 .image_subresource(
443 vk::ImageSubresourceLayers::default()
444 .aspect_mask(vk::ImageAspectFlags::COLOR)
445 .layer_count(1),
446 )
447 .image_extent(vk::Extent3D {
448 width: texture.extent.width,
449 height: texture.extent.height,
450 depth: 1,
451 });
452 unsafe {
453 device.cmd_copy_image_to_buffer(
454 cmd,
455 texture.image,
456 vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
457 buffer,
458 &[region],
459 );
460 }
461 texture.layout.set(vk::ImageLayout::TRANSFER_SRC_OPTIMAL);
462
463 let pixels = self.submit_one_shot(cmd).and_then(|()| {
466 allocation
469 .mapped_slice()
470 .map(|s| s[..size as usize].to_vec())
471 .ok_or(Error::Backend {
472 backend: "vulkan",
473 detail: "readback allocation was not host-visible".into(),
474 })
475 });
476
477 let _ = self.allocator_mut().free(allocation);
478 unsafe { device.destroy_buffer(buffer, None) };
479 pixels
480 }
481}
482
483fn generate_mipmaps(device: &ash::Device, cmd: vk::CommandBuffer, texture: &VulkanTexture) {
501 if texture.mip_levels <= 1 {
502 return;
503 }
504 let level_range = |level: u32| {
505 vk::ImageSubresourceRange::default()
506 .aspect_mask(vk::ImageAspectFlags::COLOR)
507 .base_mip_level(level)
508 .level_count(1)
509 .layer_count(1)
510 };
511 let barrier = |level: u32, from: vk::ImageLayout, to: vk::ImageLayout| {
512 let barrier = vk::ImageMemoryBarrier::default()
513 .old_layout(from)
514 .new_layout(to)
515 .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
516 .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
517 .image(texture.image)
518 .subresource_range(level_range(level))
519 .src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
520 .dst_access_mask(vk::AccessFlags::TRANSFER_READ);
521 unsafe {
524 device.cmd_pipeline_barrier(
525 cmd,
526 vk::PipelineStageFlags::TRANSFER,
527 vk::PipelineStageFlags::TRANSFER,
528 vk::DependencyFlags::empty(),
529 &[],
530 &[],
531 &[barrier],
532 );
533 }
534 };
535
536 let mut width = texture.extent.width.max(1) as i32;
537 let mut height = texture.extent.height.max(1) as i32;
538 for level in 1..texture.mip_levels {
539 barrier(
542 level - 1,
543 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
544 vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
545 );
546 let next_width = (width / 2).max(1);
550 let next_height = (height / 2).max(1);
551 let blit = vk::ImageBlit::default()
552 .src_subresource(
553 vk::ImageSubresourceLayers::default()
554 .aspect_mask(vk::ImageAspectFlags::COLOR)
555 .mip_level(level - 1)
556 .layer_count(1),
557 )
558 .src_offsets([
559 vk::Offset3D { x: 0, y: 0, z: 0 },
560 vk::Offset3D {
561 x: width,
562 y: height,
563 z: 1,
564 },
565 ])
566 .dst_subresource(
567 vk::ImageSubresourceLayers::default()
568 .aspect_mask(vk::ImageAspectFlags::COLOR)
569 .mip_level(level)
570 .layer_count(1),
571 )
572 .dst_offsets([
573 vk::Offset3D { x: 0, y: 0, z: 0 },
574 vk::Offset3D {
575 x: next_width,
576 y: next_height,
577 z: 1,
578 },
579 ]);
580 unsafe {
582 device.cmd_blit_image(
583 cmd,
584 texture.image,
585 vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
586 texture.image,
587 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
588 &[blit],
589 vk::Filter::LINEAR,
590 );
591 }
592 width = next_width;
593 height = next_height;
594 }
595
596 for level in 0..texture.mip_levels - 1 {
601 barrier(
602 level,
603 vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
604 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
605 );
606 }
607}
608
609pub(crate) fn transition(
616 device: &ash::Device,
617 cmd: vk::CommandBuffer,
618 image: vk::Image,
619 from: vk::ImageLayout,
620 to: vk::ImageLayout,
621) {
622 let (src_stage, src_access) = source_scope(from);
623 let (dst_stage, dst_access) = destination_scope(to);
624
625 let barrier = vk::ImageMemoryBarrier::default()
626 .old_layout(from)
627 .new_layout(to)
628 .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
629 .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
630 .image(image)
631 .subresource_range(
632 vk::ImageSubresourceRange::default()
633 .aspect_mask(vk::ImageAspectFlags::COLOR)
634 .level_count(vk::REMAINING_MIP_LEVELS)
640 .layer_count(1),
641 )
642 .src_access_mask(src_access)
643 .dst_access_mask(dst_access);
644
645 unsafe {
646 device.cmd_pipeline_barrier(
647 cmd,
648 src_stage,
649 dst_stage,
650 vk::DependencyFlags::empty(),
651 &[],
652 &[],
653 &[barrier],
654 );
655 }
656}
657
658fn source_scope(layout: vk::ImageLayout) -> (vk::PipelineStageFlags, vk::AccessFlags) {
671 match layout {
672 vk::ImageLayout::UNDEFINED | vk::ImageLayout::PREINITIALIZED => (
674 vk::PipelineStageFlags::TOP_OF_PIPE,
675 vk::AccessFlags::empty(),
676 ),
677 vk::ImageLayout::TRANSFER_DST_OPTIMAL => (
678 vk::PipelineStageFlags::TRANSFER,
679 vk::AccessFlags::TRANSFER_WRITE,
680 ),
681 vk::ImageLayout::TRANSFER_SRC_OPTIMAL => (
682 vk::PipelineStageFlags::TRANSFER,
683 vk::AccessFlags::TRANSFER_READ,
684 ),
685
686 vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL => (
687 vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
688 vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
689 ),
690 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL => (
691 vk::PipelineStageFlags::FRAGMENT_SHADER,
692 vk::AccessFlags::SHADER_READ,
693 ),
694 vk::ImageLayout::PRESENT_SRC_KHR => (
697 vk::PipelineStageFlags::BOTTOM_OF_PIPE,
698 vk::AccessFlags::empty(),
699 ),
700 _ => (
701 vk::PipelineStageFlags::ALL_COMMANDS,
702 vk::AccessFlags::MEMORY_READ | vk::AccessFlags::MEMORY_WRITE,
703 ),
704 }
705}
706
707fn destination_scope(layout: vk::ImageLayout) -> (vk::PipelineStageFlags, vk::AccessFlags) {
709 match layout {
710 vk::ImageLayout::TRANSFER_DST_OPTIMAL => (
711 vk::PipelineStageFlags::TRANSFER,
712 vk::AccessFlags::TRANSFER_WRITE,
713 ),
714 vk::ImageLayout::TRANSFER_SRC_OPTIMAL => (
715 vk::PipelineStageFlags::TRANSFER,
716 vk::AccessFlags::TRANSFER_READ,
717 ),
718 vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL => (
719 vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
720 vk::AccessFlags::COLOR_ATTACHMENT_WRITE | vk::AccessFlags::COLOR_ATTACHMENT_READ,
721 ),
722 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL => (
723 vk::PipelineStageFlags::FRAGMENT_SHADER,
724 vk::AccessFlags::SHADER_READ,
725 ),
726 vk::ImageLayout::PRESENT_SRC_KHR => (
729 vk::PipelineStageFlags::BOTTOM_OF_PIPE,
730 vk::AccessFlags::empty(),
731 ),
732 _ => (
733 vk::PipelineStageFlags::ALL_COMMANDS,
734 vk::AccessFlags::MEMORY_READ | vk::AccessFlags::MEMORY_WRITE,
735 ),
736 }
737}
738
739fn sample_count_flags(count: u32) -> vk::SampleCountFlags {
740 match count {
741 2 => vk::SampleCountFlags::TYPE_2,
742 4 => vk::SampleCountFlags::TYPE_4,
743 8 => vk::SampleCountFlags::TYPE_8,
744 16 => vk::SampleCountFlags::TYPE_16,
745 _ => vk::SampleCountFlags::TYPE_1,
746 }
747}
748
749pub(crate) fn backend_err(what: &str, e: vk::Result) -> Error {
750 Error::Backend {
751 backend: "vulkan",
752 detail: format!("{what}: {e:?}"),
753 }
754}
755
756pub(crate) trait WithDetail {
758 fn with_detail<E: std::fmt::Display>(self, e: E) -> Error;
759}
760
761impl WithDetail for Error {
762 fn with_detail<E: std::fmt::Display>(self, e: E) -> Error {
763 Error::Backend {
764 backend: "vulkan",
765 detail: format!("{self}: {e}"),
766 }
767 }
768}
769
770#[cfg(test)]
771mod barrier_tests {
772 use super::*;
773
774 const USED: &[vk::ImageLayout] = &[
776 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
777 vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
778 vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
779 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
780 vk::ImageLayout::PRESENT_SRC_KHR,
781 ];
782
783 #[test]
784 fn a_known_layout_names_less_than_everything() {
785 for layout in USED {
791 let (src_stage, _) = source_scope(*layout);
792 let (dst_stage, _) = destination_scope(*layout);
793 assert_ne!(
794 src_stage,
795 vk::PipelineStageFlags::ALL_COMMANDS,
796 "{layout:?} falls back to a full drain as a source"
797 );
798 assert_ne!(
799 dst_stage,
800 vk::PipelineStageFlags::ALL_COMMANDS,
801 "{layout:?} falls back to a full drain as a destination"
802 );
803 }
804 }
805
806 #[test]
807 fn an_unknown_layout_names_everything() {
808 let (stage, access) = source_scope(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
812 assert_eq!(stage, vk::PipelineStageFlags::ALL_COMMANDS);
813 assert!(access.contains(vk::AccessFlags::MEMORY_WRITE));
814 }
815
816 #[test]
817 fn leaving_undefined_waits_for_nothing() {
818 let (stage, access) = source_scope(vk::ImageLayout::UNDEFINED);
822 assert_eq!(stage, vk::PipelineStageFlags::TOP_OF_PIPE);
823 assert!(access.is_empty());
824 }
825}