use memoffset::offset_of;
use std::cell::RefCell;
use std::ffi::CStr;
use std::fmt::Debug;
use std::mem;
use std::os::raw::c_uchar;
use std::slice;
use crate::ash::vk;
use crate::imgui::{Context as ImGui, DrawIdx as ImDrawIdx, DrawVert as ImDrawVert, Ui};
use crate::vk_helper::Error as VkHelperError;
use crate::vk_helper::*;
use crate::vk_mem::{AllocationCreateFlags, AllocationCreateInfo};
use glsl_to_spirv_macros::{glsl_fs, glsl_vs};
use glsl_to_spirv_macros_impl::GLSLEmbedImpl;
use snafu::{ResultExt, Snafu};
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("vk_helper::Allocator creation error: {}", source))]
VkHelperAllocatorNew { source: VkHelperError },
#[snafu(display("vk_helper::ShaderModule vertex creation error: {}", source))]
VkHelperVertexShaderModuleNew { source: VkHelperError },
#[snafu(display("vk_helper::ShaderModule fragment creation error: {}", source))]
VkHelperFragmentShaderModuleNew { source: VkHelperError },
#[snafu(display("vk_helper::Sampler creation error: {}", source))]
VkHelperSamplerNew { source: VkHelperError },
#[snafu(display("vk_helper::DescriptorSetLayout creation error: {}", source))]
VkHelperDescriptorSetLayoutNew { source: VkHelperError },
#[snafu(display("vk_helper::PipelineLayout creation error: {}", source))]
VkHelperPipelineLayoutNew { source: VkHelperError },
#[snafu(display("vk_helper::Buffer creation error: {}", source))]
VkHelperBufferNew { source: VkHelperError },
#[snafu(display("vk_helper::Image creation error: {}", source))]
VkHelperImageNew { source: VkHelperError },
#[snafu(display("vk_helper::ImageView creation error: {}", source))]
VkHelperImageViewNew { source: VkHelperError },
#[snafu(display("vk_helper::DescriptorPool creation error: {}", source))]
VkHelperDescriptorPoolNew { source: VkHelperError },
#[snafu(display("vk_helper::DescriptorSet creation error: {}", source))]
VkHelperDescriptorSetNew { source: VkHelperError },
#[snafu(display("vk_helper::Pipeline creation error: {}", source))]
VkHelperPipelineNew { source: VkHelperError },
#[snafu(display("Pipeline not defined in ui renderer"))]
PipelineNone,
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Renderer<T: Clone + Default + Debug> {
pipeline_layout: PipelineLayout<T>,
vertex_shader: ShaderModule<T>,
fragment_shader: ShaderModule<T>,
pipeline: RefCell<Option<Pipeline<T>>>,
old_pipeline: RefCell<Option<Pipeline<T>>>,
vertex_buffers: [Buffer<T>; 2],
index_buffers: [Buffer<T>; 2],
descriptor_set: DescriptorSet<T>,
frame_index: RefCell<usize>,
}
impl<T: Clone + Default + Debug> Renderer<T> {
const QUAD_COUNT_PER_FRAME: usize = 64 * 1024;
const VERTEX_COUNT_PER_FRAME: usize = 4 * Self::QUAD_COUNT_PER_FRAME;
const INDEX_COUNT_PER_FRAME: usize = 6 * Self::QUAD_COUNT_PER_FRAME;
const PUSH_CONSTANT_SIZE: usize = 8;
const FRAME_COUNT: usize = 2;
pub fn new_simple(
instance: Instance<T>,
physical_device: vk::PhysicalDevice,
device: Device<T>,
command_buffer: &CommandBuffer<T>,
imgui: &mut ImGui,
) -> Result<Self> {
let allocator = Allocator::new(
instance,
physical_device,
device.clone(),
Default::default(),
Default::default(),
)
.context(VkHelperAllocatorNew {})?;
Self::new(device, allocator, command_buffer, imgui)
}
pub fn new(
device: Device<T>,
allocator: Allocator<T>,
command_buffer: &CommandBuffer<T>,
imgui: &mut ImGui,
) -> Result<Self> {
use arrayvec::ArrayVec;
let vertex_shader = ShaderModule::new_simple(
device.clone(),
glsl_vs! { r#"#version 430 core
layout(location = 0) in vec2 a_pos;
layout(location = 1) in vec2 a_uv;
layout(location = 2) in vec4 a_col;
layout(push_constant) uniform push_t {
vec2 g_dims_rcp;
};
out gl_PerVertex {
vec4 gl_Position;
};
layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec4 v_col;
void main() {
gl_Position = vec4(a_pos * g_dims_rcp * 2.0 - 1.0, 0, 1);
v_uv = a_uv;
v_col = a_col;
}"# },
Default::default(),
)
.context(VkHelperVertexShaderModuleNew {})?;
let fragment_shader = ShaderModule::new_simple(
device.clone(),
glsl_fs! { r#"#version 430 core
layout(location = 0) in vec2 v_uv;
layout(location = 1) in vec4 v_col;
layout(set = 0, binding = 0) uniform sampler2D g_tex;
layout(location = 0) out vec4 o_col;
void main() {
o_col = v_col*texture(g_tex, v_uv);
}"# },
Default::default(),
)
.context(VkHelperFragmentShaderModuleNew {})?;
let sampler = Sampler::new(
device.clone(),
&vk::SamplerCreateInfo::builder()
.mag_filter(vk::Filter::LINEAR)
.min_filter(vk::Filter::LINEAR),
Default::default(),
)
.context(VkHelperSamplerNew {})?;
let binding = *vk::DescriptorSetLayoutBinding::builder()
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::FRAGMENT);
let descriptor_set_layout = DescriptorSetLayout::new(
device.clone(),
slice::from_ref(&binding),
vk::DescriptorSetLayoutCreateInfo::builder(),
Default::default(),
)
.context(VkHelperDescriptorSetLayoutNew {})?;
let push_constant_range = vk::PushConstantRange {
stage_flags: vk::ShaderStageFlags::VERTEX,
offset: 0,
size: Self::PUSH_CONSTANT_SIZE as u32,
};
let create_info = vk::PipelineLayoutCreateInfo::builder()
.push_constant_ranges(slice::from_ref(&push_constant_range));
let pipeline_layout = PipelineLayout::new(
device.clone(),
vec![descriptor_set_layout.clone()],
create_info,
Default::default(),
)
.context(VkHelperPipelineLayoutNew {})?;
let vertex_buffers = {
let create_info = vk::BufferCreateInfo::builder()
.size(
(Self::VERTEX_COUNT_PER_FRAME * mem::size_of::<ImDrawVert>()) as vk::DeviceSize,
)
.usage(vk::BufferUsageFlags::VERTEX_BUFFER);
let mut buffers = ArrayVec::<[Buffer<T>; 2]>::new();
for buffer in std::iter::repeat_with(|| {
Buffer::new(
allocator.clone(),
&create_info,
&AllocationCreateInfo {
usage: crate::vk_mem::MemoryUsage::CpuOnly,
flags: AllocationCreateFlags::MAPPED,
..Default::default()
},
Default::default(),
)
.unwrap()
})
.take(Self::FRAME_COUNT)
{
buffers.push(buffer);
}
buffers.into_inner().unwrap()
};
let index_buffers = {
let create_info = vk::BufferCreateInfo::builder()
.size((Self::INDEX_COUNT_PER_FRAME * mem::size_of::<ImDrawIdx>()) as vk::DeviceSize)
.usage(vk::BufferUsageFlags::INDEX_BUFFER);
let mut buffers = ArrayVec::<[Buffer<T>; 2]>::new();
for buffer in std::iter::repeat_with(|| {
Buffer::new(
allocator.clone(),
&create_info,
&AllocationCreateInfo {
usage: crate::vk_mem::MemoryUsage::CpuOnly,
flags: AllocationCreateFlags::MAPPED,
..Default::default()
},
Default::default(),
)
.unwrap()
})
.take(Self::FRAME_COUNT)
{
buffers.push(buffer);
}
buffers.into_inner().unwrap()
};
let mut fonts = imgui.fonts();
let imgui::FontAtlasTexture {
width: image_width,
height: image_height,
data: image_pixels,
} = fonts.build_rgba32_texture();
let create_info = vk::BufferCreateInfo::builder()
.size(vk::DeviceSize::from(image_width * image_height * 4))
.usage(vk::BufferUsageFlags::TRANSFER_SRC);
let image_buffer = Buffer::new(
allocator.clone(),
&create_info,
&AllocationCreateInfo {
usage: crate::vk_mem::MemoryUsage::CpuOnly,
flags: AllocationCreateFlags::MAPPED,
..Default::default()
},
Default::default(),
)
.context(VkHelperBufferNew {})?;
let create_info = vk::ImageCreateInfo::builder()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::R8G8B8A8_UNORM)
.extent(vk::Extent3D {
width: image_width,
height: image_height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST);
let image = Image::new(
allocator,
&create_info,
&AllocationCreateInfo {
usage: crate::vk_mem::MemoryUsage::GpuOnly,
..Default::default()
},
Default::default(),
)
.context(VkHelperImageNew {})?;
let create_info = vk::ImageViewCreateInfo::builder()
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::R8G8B8A8_UNORM)
.subresource_range(
*vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.level_count(1)
.layer_count(1),
);
let image_view = ImageView::new(image.clone(), create_info, Default::default())
.context(VkHelperImageViewNew {})?;
let transfer_from_undef = vk::ImageMemoryBarrier::builder()
.dst_access_mask(vk::AccessFlags::TRANSFER_WRITE)
.new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.subresource_range(
*vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.level_count(1)
.layer_count(1),
);
command_buffer.pipeline_barrier(
vk::PipelineStageFlags::HOST,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
vec![],
vec![(image.clone(), transfer_from_undef)],
);
let descriptor_pool_sizes = [*vk::DescriptorPoolSize::builder()
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(1)];
let create_info = vk::DescriptorPoolCreateInfo::builder()
.flags(vk::DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET)
.max_sets(1)
.pool_sizes(&descriptor_pool_sizes);
let descriptor_pool = DescriptorPool::new(device, &create_info, Default::default())
.context(VkHelperDescriptorPoolNew {})?;
let descriptor_set = DescriptorSet::new(
&descriptor_pool,
slice::from_ref(&descriptor_set_layout),
vk::DescriptorSetAllocateInfo::builder(),
Default::default(),
vec![],
)
.context(VkHelperDescriptorSetNew {})?[0]
.clone();
descriptor_set.write_image(
sampler,
image_view,
vk::DescriptorImageInfo::builder()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL),
vk::WriteDescriptorSet::builder(),
);
let image_base = image_buffer
.allocator
.borrow_mut()
.get_allocation_info(&image_buffer.allocation)
.unwrap()
.get_mapped_data() as *mut c_uchar;
assert_eq!(
(image_width * image_height * 4) as usize,
image_pixels.len()
);
unsafe {
image_base.copy_from_nonoverlapping(image_pixels.as_ptr(), image_pixels.len());
};
image_buffer.flush(0, 0).unwrap();
let buffer_image_copy = vk::BufferImageCopy::builder()
.image_subresource(
*vk::ImageSubresourceLayers::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.layer_count(1),
)
.image_extent(vk::Extent3D {
width: image_width,
height: image_height,
depth: 1,
});
command_buffer.copy_buffer_to_image(
image_buffer,
image.clone(),
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
slice::from_ref(&*buffer_image_copy),
);
let shader_from_transfer = vk::ImageMemoryBarrier::builder()
.src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
.dst_access_mask(vk::AccessFlags::SHADER_READ)
.old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.subresource_range(
*vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.level_count(1)
.layer_count(1),
);
command_buffer.pipeline_barrier(
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::DependencyFlags::empty(),
&[],
vec![],
vec![(image, shader_from_transfer)],
);
Ok(Self {
pipeline_layout,
vertex_shader,
fragment_shader,
pipeline: Default::default(),
old_pipeline: Default::default(),
vertex_buffers,
index_buffers,
descriptor_set,
frame_index: Default::default(),
})
}
pub fn begin_frame(&self) {
if self.old_pipeline.borrow().is_some() {
self.old_pipeline.replace(None);
}
}
pub fn set_render_pass(&self, render_pass: RenderPass<T>) -> Result<()> {
let mut shaders = std::collections::HashMap::new();
shaders.insert(vk::ShaderStageFlags::VERTEX, self.vertex_shader.clone());
shaders.insert(vk::ShaderStageFlags::FRAGMENT, self.fragment_shader.clone());
let shader_entry_name = CStr::from_bytes_with_nul(b"main\0").unwrap();
let shader_stage_create_infos = [
*vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::VERTEX)
.name(&shader_entry_name),
*vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::FRAGMENT)
.name(&shader_entry_name),
];
let vertex_input_binding = vk::VertexInputBindingDescription::builder()
.binding(0)
.stride(mem::size_of::<ImDrawVert>() as u32)
.input_rate(vk::VertexInputRate::VERTEX);
#[allow(clippy::unneeded_field_pattern)]
let vertex_input_attributes = [
*vk::VertexInputAttributeDescription::builder()
.location(0)
.binding(0)
.format(vk::Format::R32G32_SFLOAT)
.offset(offset_of!(ImDrawVert, pos) as u32),
*vk::VertexInputAttributeDescription::builder()
.location(1)
.binding(0)
.format(vk::Format::R32G32_SFLOAT)
.offset(offset_of!(ImDrawVert, uv) as u32),
*vk::VertexInputAttributeDescription::builder()
.location(2)
.binding(0)
.format(vk::Format::R8G8B8A8_UNORM)
.offset(offset_of!(ImDrawVert, col) as u32),
];
let vertex_input_state_create_info = vk::PipelineVertexInputStateCreateInfo::builder()
.vertex_binding_descriptions(slice::from_ref(&vertex_input_binding))
.vertex_attribute_descriptions(&vertex_input_attributes);
let input_assembly_state_create_info = vk::PipelineInputAssemblyStateCreateInfo::builder()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let viewport = vk::Viewport::builder().build();
let scissor = vk::Rect2D::builder().build();
let viewport_state_create_info = vk::PipelineViewportStateCreateInfo::builder()
.viewports(slice::from_ref(&viewport))
.scissors(slice::from_ref(&scissor));
let rasterization_state_create_info = vk::PipelineRasterizationStateCreateInfo::builder()
.polygon_mode(vk::PolygonMode::FILL)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::CLOCKWISE)
.line_width(1.0);
let depth_stencil_state_create_info = vk::PipelineDepthStencilStateCreateInfo::builder()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::ALWAYS);
let multisample_state_create_info = vk::PipelineMultisampleStateCreateInfo::builder()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let color_blend_attachment_state = vk::PipelineColorBlendAttachmentState::builder()
.blend_enable(true)
.src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.color_blend_op(vk::BlendOp::ADD)
.src_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.dst_alpha_blend_factor(vk::BlendFactor::ZERO)
.alpha_blend_op(vk::BlendOp::ADD)
.color_write_mask(vk::ColorComponentFlags::all());
let color_blend_state_create_info = vk::PipelineColorBlendStateCreateInfo::builder()
.attachments(slice::from_ref(&color_blend_attachment_state));
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let pipeline_dynamic_state_create_info =
vk::PipelineDynamicStateCreateInfo::builder().dynamic_states(&dynamic_states);
let create_info = vk::GraphicsPipelineCreateInfo::builder()
.vertex_input_state(&vertex_input_state_create_info)
.input_assembly_state(&input_assembly_state_create_info)
.viewport_state(&viewport_state_create_info)
.rasterization_state(&rasterization_state_create_info)
.depth_stencil_state(&depth_stencil_state_create_info)
.multisample_state(&multisample_state_create_info)
.color_blend_state(&color_blend_state_create_info)
.dynamic_state(&pipeline_dynamic_state_create_info);
self.old_pipeline.replace(Some(
Pipeline::new(
self.pipeline_layout.clone(),
render_pass,
shaders,
&shader_stage_create_infos,
create_info,
Default::default(),
)
.context(VkHelperPipelineNew {})?,
));
self.old_pipeline.swap(&self.pipeline);
Ok(())
}
pub fn render(&self, ui: Ui, command_buffer: &CommandBuffer<T>) -> Result<()> {
if self.pipeline.borrow().is_none() {
return Err(Error::PipelineNone);
};
let draw_data = ui.render();
use crate::ash::version::DeviceV1_0;
let x = draw_data.display_pos[0];
let y = draw_data.display_pos[1];
let width = draw_data.display_size[0];
let height = draw_data.display_size[1];
if !(width > 0.0 && height > 0.0) {
return Ok(());
}
let vertex_buffer = &self.vertex_buffers[*self.frame_index.borrow()];
let index_buffer = self.index_buffers[*self.frame_index.borrow()].clone();
command_buffer.bind_pipeline(self.pipeline.borrow().clone().unwrap());
command_buffer.bind_descriptor_sets(0, vec![self.descriptor_set.clone()], &[]);
let dims_rcp = [width.recip(), height.recip()];
unsafe {
command_buffer.command_pool.device.cmd_push_constants(
***command_buffer,
**self.pipeline_layout,
vk::ShaderStageFlags::VERTEX,
0,
std::slice::from_raw_parts(
dims_rcp.as_ptr() as *const u8,
dims_rcp.len() * std::mem::size_of::<f32>() / std::mem::size_of::<u8>(),
),
)
};
let viewport = vk::Viewport {
x,
y,
width,
height,
..Default::default()
};
unsafe {
command_buffer.command_pool.device.cmd_set_viewport(
***command_buffer,
0,
slice::from_ref(&viewport),
)
};
command_buffer.bind_vertex_buffers(0, slice::from_ref(&vertex_buffer), &[0]);
command_buffer.bind_index_buffer(index_buffer.clone(), 0, vk::IndexType::UINT16);
#[allow(clippy::cast_ptr_alignment)]
let vertex_base = vertex_buffer
.allocator
.borrow_mut()
.get_allocation_info(&vertex_buffer.allocation)
.unwrap()
.get_mapped_data() as *mut ImDrawVert;
#[allow(clippy::cast_ptr_alignment)]
let index_base = index_buffer
.allocator
.borrow_mut()
.get_allocation_info(&index_buffer.allocation)
.unwrap()
.get_mapped_data() as *mut ImDrawIdx;
let mut vertex_offset = 0;
let mut index_offset = 0;
for draw_list in draw_data.draw_lists() {
let vtx_buffer = draw_list.vtx_buffer();
let idx_buffer = draw_list.idx_buffer();
let next_vertex_offset = vertex_offset + vtx_buffer.len();
let next_index_offset = index_offset + idx_buffer.len();
if next_vertex_offset > Self::VERTEX_COUNT_PER_FRAME
|| next_index_offset > Self::INDEX_COUNT_PER_FRAME
{
break;
}
unsafe {
vertex_base
.add(vertex_offset)
.copy_from_nonoverlapping(vtx_buffer.as_ptr(), vtx_buffer.len());
index_base
.add(index_offset)
.copy_from_nonoverlapping(idx_buffer.as_ptr(), idx_buffer.len());
}
let clip_off = draw_data.display_pos;
for cmd in draw_list.commands() {
match cmd {
imgui::DrawCmd::Elements {
count,
cmd_params:
imgui::DrawCmdParams {
clip_rect,
..
},
} => {
let offset = vk::Offset2D {
x: (clip_rect[0] - clip_off[0]) as i32,
y: (clip_rect[1] - clip_off[1]) as i32,
};
let (z, w) = (
(clip_rect[2] - clip_off[0]) as i32,
(clip_rect[3] - clip_off[1]) as i32,
);
let scissor = vk::Rect2D {
offset,
extent: vk::Extent2D {
width: (z - offset.x) as u32,
height: (w - offset.y) as u32,
},
};
if scissor.offset.x < width as i32
&& scissor.offset.y < height as i32
&& z >= 0
&& w >= 0
{
unsafe {
command_buffer.command_pool.device.cmd_set_scissor(
***command_buffer,
0,
slice::from_ref(&scissor),
);
command_buffer.command_pool.device.cmd_draw_indexed(
***command_buffer,
count as u32,
1,
index_offset as u32,
vertex_offset as i32,
0,
);
}
}
index_offset += count;
}
imgui::DrawCmd::ResetRenderState => (), imgui::DrawCmd::RawCallback { .. } => (), };
}
vertex_offset = next_vertex_offset;
assert_eq!(index_offset, next_index_offset);
}
vertex_buffer.flush(0, 0).unwrap();
index_buffer.flush(0, 0).unwrap();
self.frame_index
.replace_with(|frame_index| (1 + *frame_index) % Self::FRAME_COUNT);
Ok(())
}
}