use ash::vk;
use crate::vulkan::owned::{OwnedSampler, VkDevice};
use super::allocator::{DeviceAllocator, PooledBuffer, PooledImage};
pub(super) struct GpuImage {
pub image: vk::Image,
pub view: vk::ImageView,
pub aux_views: Vec<vk::ImageView>,
pooled: PooledImage,
}
impl GpuImage {
pub(super) fn null() -> Self {
Self {
image: vk::Image::null(),
view: vk::ImageView::null(),
aux_views: Vec::new(),
pooled: PooledImage::null(),
}
}
pub(super) fn from_pooled(pooled: PooledImage, view: vk::ImageView) -> Self {
pooled.attach_view(view);
Self {
image: pooled.image(),
view,
aux_views: Vec::new(),
pooled,
}
}
pub(super) fn push_aux_view(&mut self, view: vk::ImageView) {
self.pooled.attach_view(view);
self.aux_views.push(view);
}
pub(super) fn borrowed(image: vk::Image, view: vk::ImageView) -> Self {
Self {
image,
view,
aux_views: Vec::new(),
pooled: PooledImage::null(),
}
}
}
pub(super) fn find_memory_type(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
type_filter: u32,
properties: vk::MemoryPropertyFlags,
) -> Result<u32, String> {
let mem_props = unsafe { instance.get_physical_device_memory_properties(physical_device) };
for i in 0..mem_props.memory_type_count {
if (type_filter & (1 << i)) != 0
&& mem_props.memory_types[i as usize]
.property_flags
.contains(properties)
{
return Ok(i);
}
}
Err("no suitable memory type found".to_string())
}
#[derive(Clone, Copy)]
pub(super) struct ImageSpec {
pub width: u32,
pub height: u32,
pub format: vk::Format,
pub tiling: vk::ImageTiling,
pub usage: vk::ImageUsageFlags,
pub mem_props: vk::MemoryPropertyFlags,
pub samples: vk::SampleCountFlags,
}
pub(super) fn create_image(
alloc: &DeviceAllocator,
spec: &ImageSpec,
) -> crate::gfx::error::RenderResult<PooledImage> {
let &ImageSpec {
width,
height,
format,
tiling,
usage,
mem_props,
samples,
} = spec;
let img_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.extent(vk::Extent3D {
width,
height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.format(format)
.tiling(tiling)
.initial_layout(vk::ImageLayout::UNDEFINED)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.samples(samples);
alloc.create_image(&img_info, mem_props)
}
pub(super) fn create_image_view(
device: &VkDevice,
image: vk::Image,
format: vk::Format,
aspect: vk::ImageAspectFlags,
) -> Result<vk::ImageView, String> {
let view_info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(format)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(aspect)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1),
);
unsafe { device.create_image_view(&view_info, None) }
.map_err(|e| format!("create_image_view: {e}"))
}
pub(super) fn one_shot_submit_nowait<F>(
device: &VkDevice,
command_pool: vk::CommandPool,
queue: vk::Queue,
f: F,
) -> Result<vk::CommandBuffer, String>
where
F: FnOnce(vk::CommandBuffer),
{
let alloc_info = vk::CommandBufferAllocateInfo::default()
.command_pool(command_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(1);
let cmd = unsafe { device.allocate_command_buffers(&alloc_info) }
.map_err(|e| format!("one_shot allocate: {e}"))?[0];
let begin_info =
vk::CommandBufferBeginInfo::default().flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
unsafe { device.begin_command_buffer(cmd, &begin_info) }
.map_err(|e| format!("one_shot begin: {e}"))?;
f(cmd);
unsafe { device.end_command_buffer(cmd) }.map_err(|e| format!("one_shot end: {e}"))?;
let submit_info = vk::SubmitInfo::default().command_buffers(std::slice::from_ref(&cmd));
unsafe { device.queue_submit(queue, std::slice::from_ref(&submit_info), vk::Fence::null()) }
.map_err(|e| format!("one_shot submit: {e}"))?;
Ok(cmd)
}
pub(super) fn one_shot_submit<F>(
device: &VkDevice,
command_pool: vk::CommandPool,
queue: vk::Queue,
f: F,
) -> Result<(), String>
where
F: FnOnce(vk::CommandBuffer),
{
let cmd = one_shot_submit_nowait(device, command_pool, queue, f)?;
unsafe { device.queue_wait_idle(queue) }.map_err(|e| format!("one_shot wait: {e}"))?;
unsafe { device.free_command_buffers(command_pool, std::slice::from_ref(&cmd)) };
Ok(())
}
#[derive(Clone, Copy)]
pub(super) struct LayoutTransition {
pub old_layout: vk::ImageLayout,
pub new_layout: vk::ImageLayout,
pub aspect: vk::ImageAspectFlags,
}
#[derive(Clone, Copy)]
pub(super) struct SubresourceRange {
pub base_layer: u32,
pub layer_count: u32,
pub base_mip: u32,
pub mip_count: u32,
}
pub(super) fn transition_image_layout_range(
device: &VkDevice,
cmd: vk::CommandBuffer,
image: vk::Image,
transition: LayoutTransition,
range: SubresourceRange,
) {
let LayoutTransition {
old_layout,
new_layout,
aspect,
} = transition;
let SubresourceRange {
base_layer,
layer_count,
base_mip,
mip_count,
} = range;
let (src_access, dst_access, src_stage, dst_stage) =
layout_transition_access(old_layout, new_layout);
let barrier = vk::ImageMemoryBarrier::default()
.old_layout(old_layout)
.new_layout(new_layout)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(image)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(aspect)
.base_mip_level(base_mip)
.level_count(mip_count)
.base_array_layer(base_layer)
.layer_count(layer_count),
)
.src_access_mask(src_access)
.dst_access_mask(dst_access);
unsafe {
device.cmd_pipeline_barrier(
cmd,
src_stage,
dst_stage,
vk::DependencyFlags::empty(),
&[],
&[],
std::slice::from_ref(&barrier),
);
}
}
fn layout_transition_access(
old_layout: vk::ImageLayout,
new_layout: vk::ImageLayout,
) -> (
vk::AccessFlags,
vk::AccessFlags,
vk::PipelineStageFlags,
vk::PipelineStageFlags,
) {
match (old_layout, new_layout) {
(vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) => (
vk::AccessFlags::empty(),
vk::AccessFlags::TRANSFER_WRITE,
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::PipelineStageFlags::TRANSFER,
),
(vk::ImageLayout::TRANSFER_DST_OPTIMAL, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) => (
vk::AccessFlags::TRANSFER_WRITE,
vk::AccessFlags::SHADER_READ,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::FRAGMENT_SHADER,
),
(vk::ImageLayout::UNDEFINED, vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL) => (
vk::AccessFlags::empty(),
vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
),
(vk::ImageLayout::UNDEFINED, vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) => (
vk::AccessFlags::empty(),
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
),
(vk::ImageLayout::UNDEFINED, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) => (
vk::AccessFlags::empty(),
vk::AccessFlags::SHADER_READ,
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::PipelineStageFlags::FRAGMENT_SHADER,
),
(
vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
) => (
vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
vk::AccessFlags::SHADER_READ,
vk::PipelineStageFlags::LATE_FRAGMENT_TESTS,
vk::PipelineStageFlags::FRAGMENT_SHADER,
),
(
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
) => (
vk::AccessFlags::SHADER_READ,
vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
),
_ => (
vk::AccessFlags::empty(),
vk::AccessFlags::empty(),
vk::PipelineStageFlags::ALL_COMMANDS,
vk::PipelineStageFlags::ALL_COMMANDS,
),
}
}
pub(super) fn transition_image_layout(
device: &VkDevice,
cmd: vk::CommandBuffer,
image: vk::Image,
old_layout: vk::ImageLayout,
new_layout: vk::ImageLayout,
aspect: vk::ImageAspectFlags,
) {
transition_image_layout_range(
device,
cmd,
image,
LayoutTransition {
old_layout,
new_layout,
aspect,
},
SubresourceRange {
base_layer: 0,
layer_count: 1,
base_mip: 0,
mip_count: 1,
},
);
}
pub(super) fn transition_image_layout_array(
device: &VkDevice,
cmd: vk::CommandBuffer,
image: vk::Image,
old_layout: vk::ImageLayout,
new_layout: vk::ImageLayout,
aspect: vk::ImageAspectFlags,
layer_count: u32,
) {
transition_image_layout_range(
device,
cmd,
image,
LayoutTransition {
old_layout,
new_layout,
aspect,
},
SubresourceRange {
base_layer: 0,
layer_count,
base_mip: 0,
mip_count: 1,
},
);
}
#[derive(Clone, Copy)]
pub(super) struct GpuUploadContext<'a> {
pub alloc: &'a DeviceAllocator,
pub device: &'a VkDevice,
pub command_pool: vk::CommandPool,
pub queue: vk::Queue,
}
pub(super) struct UploadInFlight {
pub staging: PooledBuffer,
pub cmd: vk::CommandBuffer,
}
pub(super) struct StreamedUploadRetire {
pub _image: GpuImage,
pub _staging: PooledBuffer,
pub cmd: vk::CommandBuffer,
pub retire_at: u64,
}
impl StreamedUploadRetire {
pub(super) fn destroy(&self, device: &VkDevice, command_pool: vk::CommandPool) {
unsafe {
device.free_command_buffers(command_pool, std::slice::from_ref(&self.cmd));
}
}
}
pub(super) fn upload_texture_deferred(
ctx: &GpuUploadContext,
width: u32,
height: u32,
pixels: &[u8],
) -> crate::gfx::error::RenderResult<(GpuImage, UploadInFlight)> {
let base = (width as usize) * (height as usize) * 4;
if pixels.len() < base {
return Err(format!(
"pixel data too short for {}x{} RGBA texture ({} bytes, need {})",
width,
height,
pixels.len(),
base
)
.into());
}
let chain = crate::gfx::mipmap::generate_mip_chain(width, height, pixels);
let levels: Vec<TextureLevel<'_>> = chain
.iter()
.map(|m| TextureLevel {
width: m.width,
height: m.height,
data: &m.pixels,
})
.collect();
upload_texture_levels_deferred(ctx, vk::Format::R8G8B8A8_UNORM, &levels)
}
pub(super) struct TextureLevel<'a> {
pub width: u32,
pub height: u32,
pub data: &'a [u8],
}
fn vk_texture_format(format: concinnity_core::build::texture::TextureFormat) -> vk::Format {
use concinnity_core::build::texture::TextureFormat;
match format {
TextureFormat::Rgba8 => vk::Format::R8G8B8A8_UNORM,
TextureFormat::Bc1 => vk::Format::BC1_RGBA_UNORM_BLOCK,
TextureFormat::Bc3 => vk::Format::BC3_UNORM_BLOCK,
TextureFormat::Bc5 => vk::Format::BC5_UNORM_BLOCK,
TextureFormat::Bc7 => vk::Format::BC7_UNORM_BLOCK,
}
}
pub(super) fn upload_texture_image_deferred(
ctx: &GpuUploadContext,
image: &concinnity_core::build::texture::TextureImage,
) -> crate::gfx::error::RenderResult<(GpuImage, UploadInFlight)> {
use concinnity_core::build::texture::TextureFormat;
if image.format == TextureFormat::Rgba8 {
let mip = image
.mips
.first()
.ok_or("RGBA8 texture image has no mip level")?;
return upload_texture_deferred(ctx, mip.width, mip.height, &mip.data);
}
let levels: Vec<TextureLevel<'_>> = image
.mips
.iter()
.map(|m| TextureLevel {
width: m.width,
height: m.height,
data: &m.data,
})
.collect();
upload_texture_levels_deferred(ctx, vk_texture_format(image.format), &levels)
}
pub(super) fn upload_texture_image(
ctx: &GpuUploadContext,
image: &concinnity_core::build::texture::TextureImage,
) -> crate::gfx::error::RenderResult<GpuImage> {
let (img, in_flight) = upload_texture_image_deferred(ctx, image)?;
finish_upload(ctx, in_flight)?;
Ok(img)
}
pub(super) fn finish_upload(
ctx: &GpuUploadContext,
in_flight: UploadInFlight,
) -> Result<(), String> {
unsafe { ctx.device.queue_wait_idle(ctx.queue) }.map_err(|e| format!("upload wait: {e}"))?;
unsafe {
ctx.device
.free_command_buffers(ctx.command_pool, std::slice::from_ref(&in_flight.cmd));
}
drop(in_flight);
ctx.alloc.reclaim_idle();
Ok(())
}
fn upload_texture_levels_deferred(
ctx: &GpuUploadContext,
format: vk::Format,
levels: &[TextureLevel<'_>],
) -> crate::gfx::error::RenderResult<(GpuImage, UploadInFlight)> {
let &GpuUploadContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let base = levels.first().ok_or("texture upload has no mip level")?;
let (width, height) = (base.width, base.height);
let mip_count = levels.len() as u32;
let total: usize = levels.iter().map(|m| m.data.len()).sum();
let staging = alloc.create_buffer(
total as vk::DeviceSize,
vk::BufferUsageFlags::TRANSFER_SRC,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
let mut off = 0usize;
for m in levels {
staging.write_bytes(off, m.data);
off += m.data.len();
}
let img_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.extent(vk::Extent3D {
width,
height,
depth: 1,
})
.mip_levels(mip_count)
.array_layers(1)
.format(format)
.tiling(vk::ImageTiling::OPTIMAL)
.initial_layout(vk::ImageLayout::UNDEFINED)
.usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.samples(vk::SampleCountFlags::TYPE_1);
let pooled = alloc.create_image(&img_info, vk::MemoryPropertyFlags::DEVICE_LOCAL)?;
let image = pooled.image();
let cmd = one_shot_submit_nowait(device, command_pool, queue, |cmd| {
transition_image_layout_range(
device,
cmd,
image,
LayoutTransition {
old_layout: vk::ImageLayout::UNDEFINED,
new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
aspect: vk::ImageAspectFlags::COLOR,
},
SubresourceRange {
base_layer: 0,
layer_count: 1,
base_mip: 0,
mip_count,
},
);
let mut regions: Vec<vk::BufferImageCopy> = Vec::with_capacity(mip_count as usize);
let mut off = 0u64;
for (m, level) in levels.iter().enumerate() {
regions.push(
vk::BufferImageCopy::default()
.buffer_offset(off)
.buffer_row_length(0)
.buffer_image_height(0)
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.mip_level(m as u32)
.base_array_layer(0)
.layer_count(1),
)
.image_offset(vk::Offset3D::default())
.image_extent(vk::Extent3D {
width: level.width,
height: level.height,
depth: 1,
}),
);
off += level.data.len() as u64;
}
unsafe {
device.cmd_copy_buffer_to_image(
cmd,
staging.buffer(),
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
®ions,
);
}
transition_image_layout_range(
device,
cmd,
image,
LayoutTransition {
old_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
aspect: vk::ImageAspectFlags::COLOR,
},
SubresourceRange {
base_layer: 0,
layer_count: 1,
base_mip: 0,
mip_count,
},
);
})?;
let view = {
let info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(format)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(mip_count)
.base_array_layer(0)
.layer_count(1),
);
unsafe { device.create_image_view(&info, None) }
.map_err(|e| format!("create_image_view: {e}"))?
};
Ok((
GpuImage::from_pooled(pooled, view),
UploadInFlight { staging, cmd },
))
}
pub(super) fn upload_texture(
ctx: &GpuUploadContext,
width: u32,
height: u32,
pixels: &[u8],
) -> Result<GpuImage, String> {
let (img, in_flight) = upload_texture_deferred(ctx, width, height, pixels)?;
finish_upload(ctx, in_flight)?;
Ok(img)
}
pub(super) fn create_fallback_white(ctx: &GpuUploadContext) -> Result<GpuImage, String> {
upload_texture(ctx, 1, 1, &[255u8, 255, 255, 255])
}
pub(super) fn create_fallback_flat_normal(ctx: &GpuUploadContext) -> Result<GpuImage, String> {
upload_texture(ctx, 1, 1, &[128u8, 128, 255, 255])
}
pub(super) fn upload_color_lut(
ctx: &GpuUploadContext,
size: u32,
data: &[u8],
) -> Result<GpuImage, String> {
let &GpuUploadContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let needed = (size as usize).pow(3) * 4;
if data.len() < needed {
return Err(format!(
"color LUT data too short for size {}: {} bytes, need {}",
size,
data.len(),
needed
));
}
let byte_size = needed as vk::DeviceSize;
let staging = alloc.create_buffer(
byte_size,
vk::BufferUsageFlags::TRANSFER_SRC,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
staging.write_bytes(0, &data[..needed]);
let img_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_3D)
.extent(vk::Extent3D {
width: size,
height: size,
depth: size,
})
.mip_levels(1)
.array_layers(1)
.format(vk::Format::R8G8B8A8_UNORM)
.tiling(vk::ImageTiling::OPTIMAL)
.initial_layout(vk::ImageLayout::UNDEFINED)
.usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.samples(vk::SampleCountFlags::TYPE_1);
let pooled = alloc
.create_image(&img_info, vk::MemoryPropertyFlags::DEVICE_LOCAL)
.map_err(|e| format!("create_image (LUT): {e}"))?;
let image = pooled.image();
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout(
device,
cmd,
image,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageAspectFlags::COLOR,
);
let copy_region = vk::BufferImageCopy::default()
.buffer_offset(0)
.buffer_row_length(0)
.buffer_image_height(0)
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.mip_level(0)
.base_array_layer(0)
.layer_count(1),
)
.image_offset(vk::Offset3D::default())
.image_extent(vk::Extent3D {
width: size,
height: size,
depth: size,
});
unsafe {
device.cmd_copy_buffer_to_image(
cmd,
staging.buffer(),
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
std::slice::from_ref(©_region),
);
}
transition_image_layout(
device,
cmd,
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageAspectFlags::COLOR,
);
})?;
let view_info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_3D)
.format(vk::Format::R8G8B8A8_UNORM)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1),
);
let view = unsafe { device.create_image_view(&view_info, None) }
.map_err(|e| format!("create_image_view (LUT): {e}"))?;
Ok(GpuImage::from_pooled(pooled, view))
}
pub(super) fn upload_float_lut(
ctx: &GpuUploadContext<'_>,
size: u32,
components: u32,
texels: &[f32],
) -> Result<GpuImage, String> {
let GpuUploadContext {
alloc,
device,
command_pool,
queue,
} = *ctx;
let needed = (size as usize) * (size as usize) * components as usize;
if texels.len() < needed {
return Err(format!(
"float LUT data too short for {size}x{size}x{components}: {} floats, need {needed}",
texels.len()
));
}
let format = match components {
4 => vk::Format::R32G32B32A32_SFLOAT,
2 => vk::Format::R32G32_SFLOAT,
other => return Err(format!("unsupported float LUT component count {other}")),
};
let byte_size = (needed * std::mem::size_of::<f32>()) as vk::DeviceSize;
let staging = alloc.create_buffer(
byte_size,
vk::BufferUsageFlags::TRANSFER_SRC,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
staging.write_slice(0, &texels[..needed]);
let img_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.extent(vk::Extent3D {
width: size,
height: size,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.format(format)
.tiling(vk::ImageTiling::OPTIMAL)
.initial_layout(vk::ImageLayout::UNDEFINED)
.usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.samples(vk::SampleCountFlags::TYPE_1);
let pooled = alloc
.create_image(&img_info, vk::MemoryPropertyFlags::DEVICE_LOCAL)
.map_err(|e| format!("create_image (float LUT): {e}"))?;
let image = pooled.image();
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout(
device,
cmd,
image,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageAspectFlags::COLOR,
);
let copy_region = vk::BufferImageCopy::default()
.buffer_offset(0)
.buffer_row_length(0)
.buffer_image_height(0)
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.mip_level(0)
.base_array_layer(0)
.layer_count(1),
)
.image_offset(vk::Offset3D::default())
.image_extent(vk::Extent3D {
width: size,
height: size,
depth: 1,
});
unsafe {
device.cmd_copy_buffer_to_image(
cmd,
staging.buffer(),
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
std::slice::from_ref(©_region),
);
}
transition_image_layout(
device,
cmd,
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageAspectFlags::COLOR,
);
})?;
let view_info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(format)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1),
);
let view = unsafe { device.create_image_view(&view_info, None) }
.map_err(|e| format!("create_image_view (float LUT): {e}"))?;
Ok(GpuImage::from_pooled(pooled, view))
}
pub(super) fn create_fallback_color_lut(ctx: &GpuUploadContext) -> Result<GpuImage, String> {
let mut data = Vec::with_capacity(2 * 2 * 2 * 4);
for b in 0..2u8 {
for g in 0..2u8 {
for r in 0..2u8 {
data.extend_from_slice(&[r * 255, g * 255, b * 255, 255]);
}
}
}
upload_color_lut(ctx, 2, &data)
}
pub(super) fn create_shadow_map_array(
ctx: &GpuUploadContext,
size: u32,
layers: u32,
) -> Result<GpuImage, String> {
let &GpuUploadContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let (w, h, layer_count) = if size > 0 {
(size, size, layers.max(1))
} else {
(1, 1, 1)
};
let img_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.extent(vk::Extent3D {
width: w,
height: h,
depth: 1,
})
.mip_levels(1)
.array_layers(layer_count)
.format(vk::Format::D32_SFLOAT)
.tiling(vk::ImageTiling::OPTIMAL)
.initial_layout(vk::ImageLayout::UNDEFINED)
.usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT | vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.samples(vk::SampleCountFlags::TYPE_1);
let pooled = alloc
.create_image(&img_info, vk::MemoryPropertyFlags::DEVICE_LOCAL)
.map_err(|e| format!("create_image (shadow array): {e}"))?;
let image = pooled.image();
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout_array(
device,
cmd,
image,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageAspectFlags::DEPTH,
layer_count,
);
})?;
let view = {
let info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D_ARRAY)
.format(vk::Format::D32_SFLOAT)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::DEPTH)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(layer_count),
);
unsafe { device.create_image_view(&info, None) }
.map_err(|e| format!("shadow array view: {e}"))?
};
let mut aux_views = Vec::with_capacity(layer_count as usize);
for i in 0..layer_count {
let info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::D32_SFLOAT)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::DEPTH)
.base_mip_level(0)
.level_count(1)
.base_array_layer(i)
.layer_count(1),
);
let v = unsafe { device.create_image_view(&info, None) }
.map_err(|e| format!("shadow slice view {i}: {e}"))?;
aux_views.push(v);
}
let mut img = GpuImage::from_pooled(pooled, view);
for v in aux_views {
img.push_aux_view(v);
}
Ok(img)
}
pub(super) fn create_depth_image(
ctx: &GpuUploadContext,
width: u32,
height: u32,
samples: vk::SampleCountFlags,
) -> Result<GpuImage, String> {
let &GpuUploadContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let pooled = create_image(
alloc,
&ImageSpec {
width,
height,
format: vk::Format::D32_SFLOAT,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT | vk::ImageUsageFlags::SAMPLED,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples,
},
)?;
let image = pooled.image();
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout(
device,
cmd,
image,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
vk::ImageAspectFlags::DEPTH,
);
})?;
let view = create_image_view(
device,
image,
vk::Format::D32_SFLOAT,
vk::ImageAspectFlags::DEPTH,
)?;
Ok(GpuImage::from_pooled(pooled, view))
}
pub(super) fn create_msaa_color_image(
ctx: &GpuUploadContext,
width: u32,
height: u32,
format: vk::Format,
samples: vk::SampleCountFlags,
) -> Result<GpuImage, String> {
let &GpuUploadContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let pooled = create_image(
alloc,
&ImageSpec {
width,
height,
format,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::TRANSIENT_ATTACHMENT
| vk::ImageUsageFlags::COLOR_ATTACHMENT,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples,
},
)?;
let image = pooled.image();
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout(
device,
cmd,
image,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
vk::ImageAspectFlags::COLOR,
);
})?;
let view = create_image_view(device, image, format, vk::ImageAspectFlags::COLOR)?;
Ok(GpuImage::from_pooled(pooled, view))
}
pub(super) fn create_hdr_resolve_image(
alloc: &DeviceAllocator,
device: &VkDevice,
width: u32,
height: u32,
format: vk::Format,
) -> Result<GpuImage, String> {
let pooled = create_image(
alloc,
&ImageSpec {
width,
height,
format,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::COLOR_ATTACHMENT
| vk::ImageUsageFlags::SAMPLED
| vk::ImageUsageFlags::TRANSFER_SRC,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples: vk::SampleCountFlags::TYPE_1,
},
)?;
let view = create_image_view(device, pooled.image(), format, vk::ImageAspectFlags::COLOR)?;
Ok(GpuImage::from_pooled(pooled, view))
}
pub(super) fn create_sampler_linear_repeat(
device: &VkDevice,
max_anisotropy: f32,
) -> Result<OwnedSampler, String> {
let aniso = max_anisotropy > 1.0;
let info = vk::SamplerCreateInfo::default()
.mag_filter(vk::Filter::LINEAR)
.min_filter(vk::Filter::LINEAR)
.address_mode_u(vk::SamplerAddressMode::REPEAT)
.address_mode_v(vk::SamplerAddressMode::REPEAT)
.address_mode_w(vk::SamplerAddressMode::REPEAT)
.anisotropy_enable(aniso)
.max_anisotropy(if aniso { max_anisotropy } else { 1.0 })
.border_color(vk::BorderColor::INT_OPAQUE_BLACK)
.unnormalized_coordinates(false)
.compare_enable(false)
.mipmap_mode(vk::SamplerMipmapMode::LINEAR)
.min_lod(0.0)
.max_lod(vk::LOD_CLAMP_NONE);
device
.create_sampler(&info)
.map_err(|e| format!("linear repeat sampler: {e}"))
}
pub(super) fn create_sampler_shadow(device: &VkDevice) -> Result<OwnedSampler, String> {
let info = vk::SamplerCreateInfo::default()
.mag_filter(vk::Filter::LINEAR)
.min_filter(vk::Filter::LINEAR)
.address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.anisotropy_enable(false)
.border_color(vk::BorderColor::FLOAT_OPAQUE_WHITE)
.unnormalized_coordinates(false)
.compare_enable(true)
.compare_op(vk::CompareOp::LESS_OR_EQUAL)
.mipmap_mode(vk::SamplerMipmapMode::LINEAR);
device
.create_sampler(&info)
.map_err(|e| format!("shadow sampler: {e}"))
}
pub(super) fn create_sampler_linear_clamp(device: &VkDevice) -> Result<OwnedSampler, String> {
let info = vk::SamplerCreateInfo::default()
.mag_filter(vk::Filter::LINEAR)
.min_filter(vk::Filter::LINEAR)
.address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.anisotropy_enable(false)
.border_color(vk::BorderColor::INT_OPAQUE_BLACK)
.unnormalized_coordinates(false)
.compare_enable(false)
.mipmap_mode(vk::SamplerMipmapMode::LINEAR);
device
.create_sampler(&info)
.map_err(|e| format!("linear clamp sampler: {e}"))
}
pub(super) struct EnvironmentMapTextures {
pub irradiance: GpuImage,
pub prefilter: GpuImage,
pub prefilter_mip_count: u32,
}
fn create_cube_image(
ctx: &GpuUploadContext,
face_size: u32,
mip_bytes: &[&[u8]],
) -> Result<GpuImage, String> {
let &GpuUploadContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let mip_count = mip_bytes.len() as u32;
if mip_count == 0 {
return Err("cubemap upload: mip_bytes must not be empty".into());
}
let mut mip_sizes: Vec<usize> = Vec::with_capacity(mip_count as usize);
let mut total: usize = 0;
for (m, bytes) in mip_bytes.iter().enumerate() {
let s = (face_size >> m) as usize;
if s == 0 {
return Err(format!(
"cubemap mip {} would have zero face size (face_size {} too small)",
m, face_size
));
}
let face_bytes = s * s * 16;
let needed = 6 * face_bytes;
if bytes.len() < needed {
return Err(format!(
"cubemap mip {} too short: {} bytes, need {}",
m,
bytes.len(),
needed
));
}
mip_sizes.push(needed);
total += needed;
}
let img_info = vk::ImageCreateInfo::default()
.flags(vk::ImageCreateFlags::CUBE_COMPATIBLE)
.image_type(vk::ImageType::TYPE_2D)
.extent(vk::Extent3D {
width: face_size,
height: face_size,
depth: 1,
})
.mip_levels(mip_count)
.array_layers(6)
.format(vk::Format::R32G32B32A32_SFLOAT)
.tiling(vk::ImageTiling::OPTIMAL)
.initial_layout(vk::ImageLayout::UNDEFINED)
.usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.samples(vk::SampleCountFlags::TYPE_1);
let pooled = alloc
.create_image(&img_info, vk::MemoryPropertyFlags::DEVICE_LOCAL)
.map_err(|e| format!("create_image (cube): {e}"))?;
let image = pooled.image();
let staging = alloc.create_buffer(
total as vk::DeviceSize,
vk::BufferUsageFlags::TRANSFER_SRC,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
let mut off = 0usize;
for (m, bytes) in mip_bytes.iter().enumerate() {
staging.write_bytes(off, &bytes[..mip_sizes[m]]);
off += mip_sizes[m];
}
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout_range(
device,
cmd,
image,
LayoutTransition {
old_layout: vk::ImageLayout::UNDEFINED,
new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
aspect: vk::ImageAspectFlags::COLOR,
},
SubresourceRange {
base_layer: 0,
layer_count: 6,
base_mip: 0,
mip_count,
},
);
let mut regions: Vec<vk::BufferImageCopy> = Vec::with_capacity((mip_count * 6) as usize);
let mut off = 0u64;
for m in 0..mip_count as usize {
let s = face_size >> m;
let face_bytes = (s as u64) * (s as u64) * 16;
for face in 0..6u32 {
regions.push(
vk::BufferImageCopy::default()
.buffer_offset(off)
.buffer_row_length(0)
.buffer_image_height(0)
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.mip_level(m as u32)
.base_array_layer(face)
.layer_count(1),
)
.image_offset(vk::Offset3D::default())
.image_extent(vk::Extent3D {
width: s,
height: s,
depth: 1,
}),
);
off += face_bytes;
}
}
unsafe {
device.cmd_copy_buffer_to_image(
cmd,
staging.buffer(),
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
®ions,
);
}
transition_image_layout_range(
device,
cmd,
image,
LayoutTransition {
old_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
aspect: vk::ImageAspectFlags::COLOR,
},
SubresourceRange {
base_layer: 0,
layer_count: 6,
base_mip: 0,
mip_count,
},
);
})?;
let view = {
let info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::CUBE)
.format(vk::Format::R32G32B32A32_SFLOAT)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(mip_count)
.base_array_layer(0)
.layer_count(6),
);
unsafe { device.create_image_view(&info, None) }.map_err(|e| format!("cube view: {e}"))?
};
Ok(GpuImage::from_pooled(pooled, view))
}
pub(super) fn create_fallback_cubemap(
ctx: &GpuUploadContext,
value: [f32; 4],
) -> Result<GpuImage, String> {
let mut face_bytes = Vec::with_capacity(6 * 16);
for _ in 0..6 {
for v in &value {
face_bytes.extend_from_slice(&v.to_le_bytes());
}
}
create_cube_image(ctx, 1, &[&face_bytes])
}
pub(super) fn upload_environment_map(
ctx: &GpuUploadContext,
irradiance_face: u32,
irradiance_bytes: &[u8],
prefilter_face: u32,
mip_bytes: &[&[u8]],
) -> Result<EnvironmentMapTextures, String> {
if mip_bytes.is_empty() {
return Err("envmap upload: prefilter mip_bytes must not be empty".into());
}
let irradiance = create_cube_image(ctx, irradiance_face, &[irradiance_bytes])
.map_err(|e| format!("envmap irradiance: {e}"))?;
let prefilter = create_cube_image(ctx, prefilter_face, mip_bytes)
.map_err(|e| format!("envmap prefilter: {e}"))?;
Ok(EnvironmentMapTextures {
irradiance,
prefilter,
prefilter_mip_count: mip_bytes.len() as u32,
})
}
pub(super) fn upload_probe_prefilter_cube(
ctx: &GpuUploadContext,
prefilter_face: u32,
mip_bytes: &[&[u8]],
) -> Result<GpuImage, String> {
if mip_bytes.is_empty() {
return Err("probe prefilter upload: mip_bytes must not be empty".into());
}
create_cube_image(ctx, prefilter_face, mip_bytes).map_err(|e| format!("probe prefilter: {e}"))
}
pub(super) fn create_sampler_cube_linear(device: &VkDevice) -> Result<OwnedSampler, String> {
let info = vk::SamplerCreateInfo::default()
.mag_filter(vk::Filter::LINEAR)
.min_filter(vk::Filter::LINEAR)
.address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.anisotropy_enable(false)
.border_color(vk::BorderColor::INT_OPAQUE_BLACK)
.unnormalized_coordinates(false)
.compare_enable(false)
.mipmap_mode(vk::SamplerMipmapMode::LINEAR)
.min_lod(0.0)
.max_lod(vk::LOD_CLAMP_NONE);
device
.create_sampler(&info)
.map_err(|e| format!("cube sampler: {e}"))
}