use ash::vk;
use rustc_hash::FxHashMap;
use std::sync::Arc;
use super::atlas::{AtlasSlot, GlyphKey, RasterizedGlyph};
use super::cell::{CellBg, CellText, GridUniforms};
use crate::context::vulkan::{
allocate_host_visible_buffer_raw, VkShared, VulkanBuffer, VulkanContext, VulkanImage,
FRAMES_IN_FLIGHT,
};
use crate::renderer::image_cache::atlas::AtlasAllocator;
const BG_VERT_SPV: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/grid_bg.vert.spv"));
const BG_FRAG_SPV: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/grid_bg.frag.spv"));
const TEXT_VERT_SPV: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/grid_text.vert.spv"));
const TEXT_FRAG_SPV: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/grid_text.frag.spv"));
const CURSOR_ROW_SLOTS: usize = 2;
const ATLAS_SIZE: u16 = 2048;
struct PendingUpload {
x: u16,
y: u16,
w: u16,
h: u16,
bytes: Vec<u8>,
}
pub struct VulkanGlyphAtlas {
image: VulkanImage,
allocator: AtlasAllocator,
slots: FxHashMap<GlyphKey, AtlasSlot>,
bytes_per_pixel: u32,
pending: Vec<PendingUpload>,
initialized: bool,
staging: [Option<crate::context::vulkan::VulkanBuffer>; FRAMES_IN_FLIGHT],
staging_capacity: [usize; FRAMES_IN_FLIGHT],
}
impl VulkanGlyphAtlas {
pub fn new_grayscale(ctx: &VulkanContext) -> Self {
Self::new(ctx, vk::Format::R8_UNORM, 1)
}
pub fn new_color(ctx: &VulkanContext) -> Self {
Self::new(ctx, vk::Format::R8G8B8A8_UNORM, 4)
}
fn new(ctx: &VulkanContext, format: vk::Format, bytes_per_pixel: u32) -> Self {
let image = ctx.allocate_sampled_image(
ATLAS_SIZE as u32,
ATLAS_SIZE as u32,
format,
vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED,
);
let img_handle = image.handle();
ctx.submit_oneshot(|cmd| unsafe {
let to_read = vk::ImageMemoryBarrier2::default()
.src_stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE)
.src_access_mask(vk::AccessFlags2::empty())
.dst_stage_mask(vk::PipelineStageFlags2::FRAGMENT_SHADER)
.dst_access_mask(vk::AccessFlags2::SHADER_READ)
.old_layout(vk::ImageLayout::UNDEFINED)
.new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(img_handle)
.subresource_range(color_subresource_range());
let barriers = [to_read];
let dep = vk::DependencyInfo::default().image_memory_barriers(&barriers);
ctx.shared().raw.cmd_pipeline_barrier2(cmd, &dep);
});
Self {
image,
allocator: AtlasAllocator::new(ATLAS_SIZE, ATLAS_SIZE),
slots: FxHashMap::default(),
bytes_per_pixel,
pending: Vec::new(),
initialized: true,
staging: std::array::from_fn(|_| None),
staging_capacity: [0; FRAMES_IN_FLIGHT],
}
}
pub fn flush_uploads(
&mut self,
shared: &Arc<VkShared>,
cmd: vk::CommandBuffer,
slot: usize,
) {
if self.pending.is_empty() {
return;
}
let total_bytes: usize = self
.pending
.iter()
.map(|p| (p.w as usize) * (p.h as usize) * self.bytes_per_pixel as usize)
.sum();
if total_bytes > self.staging_capacity[slot] {
let new_cap = total_bytes.next_power_of_two().max(256 * 1024);
self.staging[slot] = Some(allocate_host_visible_buffer_raw(
shared,
new_cap as u64,
vk::BufferUsageFlags::TRANSFER_SRC,
));
self.staging_capacity[slot] = new_cap;
}
let staging = self.staging[slot].as_ref().unwrap();
let staging_ptr = staging.as_mut_ptr();
let staging_handle = staging.handle();
let bpp = self.bytes_per_pixel as usize;
let mut offset: u64 = 0;
let mut copies: Vec<vk::BufferImageCopy> = Vec::with_capacity(self.pending.len());
unsafe {
for upload in self.pending.drain(..) {
let bytes = (upload.w as usize) * (upload.h as usize) * bpp;
std::ptr::copy_nonoverlapping(
upload.bytes.as_ptr(),
staging_ptr.add(offset as usize),
bytes,
);
copies.push(image_copy_region(
offset, upload.x, upload.y, upload.w, upload.h,
));
offset += bytes as u64;
}
}
upload_to_atlas(&shared.raw, cmd, staging_handle, self, &copies);
}
#[inline]
pub fn lookup(&self, key: GlyphKey) -> Option<AtlasSlot> {
self.slots.get(&key).copied()
}
#[inline]
pub fn image_view(&self) -> vk::ImageView {
self.image.view()
}
pub fn insert(
&mut self,
key: GlyphKey,
glyph: RasterizedGlyph<'_>,
) -> Option<AtlasSlot> {
if glyph.width == 0 || glyph.height == 0 {
let slot = AtlasSlot {
x: 0,
y: 0,
w: 0,
h: 0,
bearing_x: glyph.bearing_x,
bearing_y: glyph.bearing_y,
};
self.slots.insert(key, slot);
return Some(slot);
}
let (x, y) = self.allocator.allocate(glyph.width, glyph.height)?;
let slot = AtlasSlot {
x,
y,
w: glyph.width,
h: glyph.height,
bearing_x: glyph.bearing_x,
bearing_y: glyph.bearing_y,
};
self.slots.insert(key, slot);
self.pending.push(PendingUpload {
x,
y,
w: glyph.width,
h: glyph.height,
bytes: glyph.bytes.to_vec(),
});
Some(slot)
}
}
pub struct VulkanGridRenderer {
shared: Arc<VkShared>,
cols: u32,
rows: u32,
bg_buffers: [VulkanBuffer; FRAMES_IN_FLIGHT],
bg_dirty: [bool; FRAMES_IN_FLIGHT],
bg_cpu: Vec<CellBg>,
uniform_buffers: [VulkanBuffer; FRAMES_IN_FLIGHT],
bg_descriptor_pool: vk::DescriptorPool,
bg_descriptor_set_layout: vk::DescriptorSetLayout,
bg_descriptor_sets: [vk::DescriptorSet; FRAMES_IN_FLIGHT],
bg_pipeline_layout: vk::PipelineLayout,
bg_pipeline: vk::Pipeline,
fg_rows: Vec<Vec<CellText>>,
fg_staging: Vec<CellText>,
fg_buffers: [Option<VulkanBuffer>; FRAMES_IN_FLIGHT],
fg_capacity: [usize; FRAMES_IN_FLIGHT],
fg_live_count: [u32; FRAMES_IN_FLIGHT],
fg_dirty: [bool; FRAMES_IN_FLIGHT],
text_uniform_descriptor_set_layout: vk::DescriptorSetLayout,
text_atlas_descriptor_set_layout: vk::DescriptorSetLayout,
text_descriptor_pool: vk::DescriptorPool,
text_uniform_descriptor_sets: [vk::DescriptorSet; FRAMES_IN_FLIGHT],
text_atlas_descriptor_set: vk::DescriptorSet,
text_pipeline_layout: vk::PipelineLayout,
text_pipeline: vk::Pipeline,
sampler: vk::Sampler,
pub atlas_grayscale: VulkanGlyphAtlas,
pub atlas_color: VulkanGlyphAtlas,
needs_full_rebuild: bool,
}
impl VulkanGridRenderer {
pub fn new(ctx: &VulkanContext, cols: u32, rows: u32) -> Self {
let shared = ctx.shared().clone();
let device = &shared.raw;
let bg_buffers = std::array::from_fn(|_| alloc_bg_buffer(ctx, cols, rows));
let uniform_buffers = std::array::from_fn(|_| {
ctx.allocate_host_visible_buffer(
std::mem::size_of::<GridUniforms>() as u64,
vk::BufferUsageFlags::UNIFORM_BUFFER,
)
});
let bg_descriptor_set_layout = create_bg_descriptor_set_layout(device);
let bg_descriptor_pool = create_bg_descriptor_pool(device);
let bg_descriptor_sets = allocate_descriptor_sets(
device,
bg_descriptor_pool,
bg_descriptor_set_layout,
);
for slot in 0..FRAMES_IN_FLIGHT {
update_bg_descriptor_set(
device,
bg_descriptor_sets[slot],
&uniform_buffers[slot],
&bg_buffers[slot],
);
}
let bg_pipeline_layout =
create_pipeline_layout(device, &[bg_descriptor_set_layout]);
let pipeline_cache = ctx.pipeline_cache();
let bg_pipeline = create_bg_pipeline(
device,
pipeline_cache,
bg_pipeline_layout,
ctx.swapchain_format(),
);
let atlas_grayscale = VulkanGlyphAtlas::new_grayscale(ctx);
let atlas_color = VulkanGlyphAtlas::new_color(ctx);
let sampler = create_sampler(device);
let text_uniform_descriptor_set_layout =
create_text_uniform_descriptor_set_layout(device);
let text_atlas_descriptor_set_layout =
create_text_atlas_descriptor_set_layout(device);
let text_descriptor_pool = create_text_descriptor_pool(device);
let text_uniform_descriptor_sets = allocate_descriptor_sets(
device,
text_descriptor_pool,
text_uniform_descriptor_set_layout,
);
for slot in 0..FRAMES_IN_FLIGHT {
update_text_uniform_descriptor_set(
device,
text_uniform_descriptor_sets[slot],
&uniform_buffers[slot],
);
}
let text_atlas_descriptor_set = allocate_one_descriptor_set(
device,
text_descriptor_pool,
text_atlas_descriptor_set_layout,
);
update_text_atlas_descriptor_set(
device,
text_atlas_descriptor_set,
&atlas_grayscale.image,
&atlas_color.image,
sampler,
);
let text_pipeline_layout = create_pipeline_layout(
device,
&[
text_uniform_descriptor_set_layout,
text_atlas_descriptor_set_layout,
],
);
let text_pipeline = create_text_pipeline(
device,
pipeline_cache,
text_pipeline_layout,
ctx.swapchain_format(),
);
let bg_len = (cols as usize) * (rows as usize);
Self {
shared,
cols,
rows,
bg_buffers,
bg_dirty: [true; FRAMES_IN_FLIGHT],
bg_cpu: vec![CellBg::TRANSPARENT; bg_len],
uniform_buffers,
bg_descriptor_pool,
bg_descriptor_set_layout,
bg_descriptor_sets,
bg_pipeline_layout,
bg_pipeline,
fg_rows: init_fg_rows(rows),
fg_staging: Vec::new(),
fg_buffers: std::array::from_fn(|_| None),
fg_capacity: [0; FRAMES_IN_FLIGHT],
fg_live_count: [0; FRAMES_IN_FLIGHT],
fg_dirty: [true; FRAMES_IN_FLIGHT],
text_uniform_descriptor_set_layout,
text_atlas_descriptor_set_layout,
text_descriptor_pool,
text_uniform_descriptor_sets,
text_atlas_descriptor_set,
text_pipeline_layout,
text_pipeline,
sampler,
atlas_grayscale,
atlas_color,
needs_full_rebuild: true,
}
}
#[inline]
pub fn needs_full_rebuild(&self) -> bool {
self.needs_full_rebuild
}
#[inline]
pub fn mark_full_rebuild_done(&mut self) {
self.needs_full_rebuild = false;
}
pub fn resize(&mut self, cols: u32, rows: u32) {
if cols == self.cols && rows == self.rows {
return;
}
unsafe {
let _ = self.shared.device_wait_idle();
}
self.cols = cols;
self.rows = rows;
let bg_len = (cols as usize) * (rows as usize);
self.bg_cpu = vec![CellBg::TRANSPARENT; bg_len];
let bg_byte_size = (bg_len * std::mem::size_of::<CellBg>())
.max(std::mem::size_of::<CellBg>()) as u64;
self.bg_buffers = std::array::from_fn(|_| {
allocate_host_visible_buffer_raw(
&self.shared,
bg_byte_size,
vk::BufferUsageFlags::STORAGE_BUFFER,
)
});
for slot in 0..FRAMES_IN_FLIGHT {
update_bg_descriptor_set(
&self.shared.raw,
self.bg_descriptor_sets[slot],
&self.uniform_buffers[slot],
&self.bg_buffers[slot],
);
}
self.bg_dirty = [true; FRAMES_IN_FLIGHT];
self.fg_rows = init_fg_rows(rows);
self.fg_dirty = [true; FRAMES_IN_FLIGHT];
self.fg_live_count = [0; FRAMES_IN_FLIGHT];
self.needs_full_rebuild = true;
}
pub fn write_row(&mut self, row: u32, bg: &[CellBg], fg: &[CellText]) {
let idx = (row as usize) + 1;
if let Some(slot) = self.fg_rows.get_mut(idx) {
slot.clear();
slot.extend_from_slice(fg);
self.fg_dirty = [true; FRAMES_IN_FLIGHT];
}
if row >= self.rows {
return;
}
let row_start = (row as usize) * (self.cols as usize);
let row_len = (self.cols as usize).min(bg.len());
self.bg_cpu[row_start..row_start + row_len].copy_from_slice(&bg[..row_len]);
for slot in &mut self.bg_cpu[row_start + row_len..row_start + self.cols as usize]
{
*slot = CellBg::TRANSPARENT;
}
self.bg_dirty = [true; FRAMES_IN_FLIGHT];
}
pub fn clear_row(&mut self, row: u32) {
let idx = (row as usize) + 1;
if let Some(slot) = self.fg_rows.get_mut(idx) {
if !slot.is_empty() {
self.fg_dirty = [true; FRAMES_IN_FLIGHT];
}
slot.clear();
}
if row >= self.rows {
return;
}
let row_start = (row as usize) * (self.cols as usize);
for slot in &mut self.bg_cpu[row_start..row_start + self.cols as usize] {
*slot = CellBg::TRANSPARENT;
}
self.bg_dirty = [true; FRAMES_IN_FLIGHT];
}
pub fn set_block_cursor(&mut self, cells: &[CellText]) {
if let Some(slot) = self.fg_rows.first_mut() {
if slot.is_empty() && cells.is_empty() {
return;
}
slot.clear();
slot.extend_from_slice(cells);
self.fg_dirty = [true; FRAMES_IN_FLIGHT];
}
}
pub fn set_non_block_cursor(&mut self, cells: &[CellText]) {
let idx = self.fg_rows.len().saturating_sub(1);
if let Some(slot) = self.fg_rows.get_mut(idx) {
if slot.is_empty() && cells.is_empty() {
return;
}
slot.clear();
slot.extend_from_slice(cells);
self.fg_dirty = [true; FRAMES_IN_FLIGHT];
}
}
pub fn clear_cursor(&mut self) {
let mut changed = false;
if let Some(slot) = self.fg_rows.first_mut() {
if !slot.is_empty() {
slot.clear();
changed = true;
}
}
let last = self.fg_rows.len().saturating_sub(1);
if last > 0 {
if let Some(slot) = self.fg_rows.get_mut(last) {
if !slot.is_empty() {
slot.clear();
changed = true;
}
}
}
if changed {
self.fg_dirty = [true; FRAMES_IN_FLIGHT];
}
}
#[inline]
pub fn lookup_glyph(&self, key: GlyphKey) -> Option<AtlasSlot> {
self.atlas_grayscale.lookup(key)
}
#[inline]
pub fn lookup_glyph_color(&self, key: GlyphKey) -> Option<AtlasSlot> {
self.atlas_color.lookup(key)
}
#[inline]
pub fn insert_glyph(
&mut self,
key: GlyphKey,
glyph: RasterizedGlyph<'_>,
) -> Option<AtlasSlot> {
self.atlas_grayscale.insert(key, glyph)
}
#[inline]
pub fn insert_glyph_color(
&mut self,
key: GlyphKey,
glyph: RasterizedGlyph<'_>,
) -> Option<AtlasSlot> {
self.atlas_color.insert(key, glyph)
}
pub fn prepare(
&mut self,
ctx: &VulkanContext,
cmd: vk::CommandBuffer,
frame_slot: usize,
) {
debug_assert!(frame_slot < FRAMES_IN_FLIGHT);
if self.atlas_grayscale.pending.is_empty() && self.atlas_color.pending.is_empty()
{
return;
}
self.flush_pending_uploads(ctx, cmd, frame_slot);
}
pub fn render_bg(
&mut self,
_ctx: &VulkanContext,
cmd: vk::CommandBuffer,
frame_slot: usize,
uniforms: &GridUniforms,
) {
debug_assert!(frame_slot < FRAMES_IN_FLIGHT);
let slot = frame_slot;
if self.bg_dirty[slot] {
unsafe {
let dst = self.bg_buffers[slot].as_mut_ptr() as *mut CellBg;
std::ptr::copy_nonoverlapping(
self.bg_cpu.as_ptr(),
dst,
self.bg_cpu.len(),
);
}
self.bg_dirty[slot] = false;
}
unsafe {
let dst = self.uniform_buffers[slot].as_mut_ptr() as *mut GridUniforms;
std::ptr::write(dst, *uniforms);
}
unsafe {
self.shared.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.bg_pipeline,
);
self.shared.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.bg_pipeline_layout,
0,
&[self.bg_descriptor_sets[slot]],
&[],
);
self.shared.cmd_draw(cmd, 3, 1, 0, 0);
}
}
pub fn render_text(
&mut self,
ctx: &VulkanContext,
cmd: vk::CommandBuffer,
frame_slot: usize,
_uniforms: &GridUniforms,
) {
debug_assert!(frame_slot < FRAMES_IN_FLIGHT);
let slot = frame_slot;
if self.fg_dirty[slot] {
self.fg_staging.clear();
for row in &self.fg_rows {
self.fg_staging.extend_from_slice(row);
}
let needed = self.fg_staging.len();
if needed > self.fg_capacity[slot] {
let new_cap = needed.next_power_of_two().max(64);
self.fg_buffers[slot] = Some(ctx.allocate_host_visible_buffer(
(new_cap * std::mem::size_of::<CellText>()) as u64,
vk::BufferUsageFlags::VERTEX_BUFFER,
));
self.fg_capacity[slot] = new_cap;
}
if needed > 0 {
let buf = self.fg_buffers[slot].as_ref().unwrap();
unsafe {
let dst = buf.as_mut_ptr() as *mut CellText;
std::ptr::copy_nonoverlapping(self.fg_staging.as_ptr(), dst, needed);
}
}
self.fg_live_count[slot] = needed as u32;
self.fg_dirty[slot] = false;
}
let instance_count = self.fg_live_count[slot];
if instance_count == 0 {
return;
}
unsafe {
self.shared.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.text_pipeline,
);
self.shared.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.text_pipeline_layout,
0,
&[
self.text_uniform_descriptor_sets[slot],
self.text_atlas_descriptor_set,
],
&[],
);
let buf = self.fg_buffers[slot].as_ref().unwrap();
self.shared
.cmd_bind_vertex_buffers(cmd, 0, &[buf.handle()], &[0]);
self.shared.cmd_draw(cmd, 4, instance_count, 0, 0);
}
}
fn flush_pending_uploads(
&mut self,
_ctx: &VulkanContext,
cmd: vk::CommandBuffer,
slot: usize,
) {
self.atlas_grayscale.flush_uploads(&self.shared, cmd, slot);
self.atlas_color.flush_uploads(&self.shared, cmd, slot);
}
}
fn upload_to_atlas(
device: &ash::Device,
cmd: vk::CommandBuffer,
staging: vk::Buffer,
atlas: &mut VulkanGlyphAtlas,
copies: &[vk::BufferImageCopy],
) {
let old_layout = if atlas.initialized {
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL
} else {
vk::ImageLayout::UNDEFINED
};
unsafe {
let to_transfer = vk::ImageMemoryBarrier2::default()
.src_stage_mask(if atlas.initialized {
vk::PipelineStageFlags2::FRAGMENT_SHADER
} else {
vk::PipelineStageFlags2::TOP_OF_PIPE
})
.src_access_mask(if atlas.initialized {
vk::AccessFlags2::SHADER_READ
} else {
vk::AccessFlags2::empty()
})
.dst_stage_mask(vk::PipelineStageFlags2::COPY)
.dst_access_mask(vk::AccessFlags2::TRANSFER_WRITE)
.old_layout(old_layout)
.new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(atlas.image.handle())
.subresource_range(color_subresource_range());
let barriers = [to_transfer];
let dep = vk::DependencyInfo::default().image_memory_barriers(&barriers);
device.cmd_pipeline_barrier2(cmd, &dep);
device.cmd_copy_buffer_to_image(
cmd,
staging,
atlas.image.handle(),
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
copies,
);
let to_shader_read = vk::ImageMemoryBarrier2::default()
.src_stage_mask(vk::PipelineStageFlags2::COPY)
.src_access_mask(vk::AccessFlags2::TRANSFER_WRITE)
.dst_stage_mask(vk::PipelineStageFlags2::FRAGMENT_SHADER)
.dst_access_mask(vk::AccessFlags2::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)
.image(atlas.image.handle())
.subresource_range(color_subresource_range());
let barriers = [to_shader_read];
let dep = vk::DependencyInfo::default().image_memory_barriers(&barriers);
device.cmd_pipeline_barrier2(cmd, &dep);
}
atlas.initialized = true;
}
impl Drop for VulkanGridRenderer {
fn drop(&mut self) {
unsafe {
let _ = self.shared.device_wait_idle();
self.shared.destroy_pipeline(self.text_pipeline, None);
self.shared
.destroy_pipeline_layout(self.text_pipeline_layout, None);
self.shared
.destroy_descriptor_pool(self.text_descriptor_pool, None);
self.shared.destroy_descriptor_set_layout(
self.text_atlas_descriptor_set_layout,
None,
);
self.shared.destroy_descriptor_set_layout(
self.text_uniform_descriptor_set_layout,
None,
);
self.shared.destroy_sampler(self.sampler, None);
self.shared.destroy_pipeline(self.bg_pipeline, None);
self.shared
.destroy_pipeline_layout(self.bg_pipeline_layout, None);
self.shared
.destroy_descriptor_pool(self.bg_descriptor_pool, None);
self.shared
.destroy_descriptor_set_layout(self.bg_descriptor_set_layout, None);
}
}
}
fn alloc_bg_buffer(ctx: &VulkanContext, cols: u32, rows: u32) -> VulkanBuffer {
let size = (cols as u64)
.saturating_mul(rows as u64)
.saturating_mul(std::mem::size_of::<CellBg>() as u64)
.max(std::mem::size_of::<CellBg>() as u64);
ctx.allocate_host_visible_buffer(size, vk::BufferUsageFlags::STORAGE_BUFFER)
}
fn init_fg_rows(rows: u32) -> Vec<Vec<CellText>> {
(0..(rows as usize + CURSOR_ROW_SLOTS))
.map(|_| Vec::new())
.collect()
}
fn color_subresource_range() -> vk::ImageSubresourceRange {
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1)
}
fn image_copy_region(
buffer_offset: u64,
x: u16,
y: u16,
w: u16,
h: u16,
) -> vk::BufferImageCopy {
vk::BufferImageCopy::default()
.buffer_offset(buffer_offset)
.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 {
x: x as i32,
y: y as i32,
z: 0,
})
.image_extent(vk::Extent3D {
width: w as u32,
height: h as u32,
depth: 1,
})
}
fn create_bg_descriptor_set_layout(device: &ash::Device) -> vk::DescriptorSetLayout {
let bindings = [
vk::DescriptorSetLayoutBinding::default()
.binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT),
vk::DescriptorSetLayoutBinding::default()
.binding(1)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::FRAGMENT),
];
let info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
unsafe {
device
.create_descriptor_set_layout(&info, None)
.expect("create_descriptor_set_layout(grid.bg)")
}
}
fn create_bg_descriptor_pool(device: &ash::Device) -> vk::DescriptorPool {
let sizes = [
vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: FRAMES_IN_FLIGHT as u32,
},
vk::DescriptorPoolSize {
ty: vk::DescriptorType::STORAGE_BUFFER,
descriptor_count: FRAMES_IN_FLIGHT as u32,
},
];
let info = vk::DescriptorPoolCreateInfo::default()
.max_sets(FRAMES_IN_FLIGHT as u32)
.pool_sizes(&sizes);
unsafe {
device
.create_descriptor_pool(&info, None)
.expect("create_descriptor_pool(grid.bg)")
}
}
fn create_text_uniform_descriptor_set_layout(
device: &ash::Device,
) -> vk::DescriptorSetLayout {
let bindings = [vk::DescriptorSetLayoutBinding::default()
.binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT)];
let info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
unsafe {
device
.create_descriptor_set_layout(&info, None)
.expect("create_descriptor_set_layout(grid.text uniform)")
}
}
fn create_text_atlas_descriptor_set_layout(
device: &ash::Device,
) -> vk::DescriptorSetLayout {
let bindings = [
vk::DescriptorSetLayoutBinding::default()
.binding(0)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::FRAGMENT),
vk::DescriptorSetLayoutBinding::default()
.binding(1)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::FRAGMENT),
];
let info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
unsafe {
device
.create_descriptor_set_layout(&info, None)
.expect("create_descriptor_set_layout(grid.text atlas)")
}
}
fn create_text_descriptor_pool(device: &ash::Device) -> vk::DescriptorPool {
let sizes = [
vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: FRAMES_IN_FLIGHT as u32,
},
vk::DescriptorPoolSize {
ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
descriptor_count: 2,
},
];
let info = vk::DescriptorPoolCreateInfo::default()
.max_sets((FRAMES_IN_FLIGHT + 1) as u32)
.pool_sizes(&sizes);
unsafe {
device
.create_descriptor_pool(&info, None)
.expect("create_descriptor_pool(grid.text)")
}
}
fn allocate_descriptor_sets(
device: &ash::Device,
pool: vk::DescriptorPool,
layout: vk::DescriptorSetLayout,
) -> [vk::DescriptorSet; FRAMES_IN_FLIGHT] {
let layouts = [layout; FRAMES_IN_FLIGHT];
let info = vk::DescriptorSetAllocateInfo::default()
.descriptor_pool(pool)
.set_layouts(&layouts);
let sets = unsafe {
device
.allocate_descriptor_sets(&info)
.expect("allocate_descriptor_sets")
};
let mut out = [vk::DescriptorSet::null(); FRAMES_IN_FLIGHT];
out.copy_from_slice(&sets);
out
}
fn allocate_one_descriptor_set(
device: &ash::Device,
pool: vk::DescriptorPool,
layout: vk::DescriptorSetLayout,
) -> vk::DescriptorSet {
let layouts = [layout];
let info = vk::DescriptorSetAllocateInfo::default()
.descriptor_pool(pool)
.set_layouts(&layouts);
unsafe {
device
.allocate_descriptor_sets(&info)
.expect("allocate_descriptor_sets(one)")[0]
}
}
fn update_bg_descriptor_set(
device: &ash::Device,
set: vk::DescriptorSet,
uniform: &VulkanBuffer,
cells: &VulkanBuffer,
) {
let uniform_info = vk::DescriptorBufferInfo::default()
.buffer(uniform.handle())
.offset(0)
.range(uniform.size());
let uniform_infos = [uniform_info];
let cells_info = vk::DescriptorBufferInfo::default()
.buffer(cells.handle())
.offset(0)
.range(cells.size());
let cells_infos = [cells_info];
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(&uniform_infos),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(&cells_infos),
];
unsafe {
device.update_descriptor_sets(&writes, &[]);
}
}
fn update_text_uniform_descriptor_set(
device: &ash::Device,
set: vk::DescriptorSet,
uniform: &VulkanBuffer,
) {
let uniform_info = vk::DescriptorBufferInfo::default()
.buffer(uniform.handle())
.offset(0)
.range(uniform.size());
let uniform_infos = [uniform_info];
let writes = [vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(&uniform_infos)];
unsafe {
device.update_descriptor_sets(&writes, &[]);
}
}
fn update_text_atlas_descriptor_set(
device: &ash::Device,
set: vk::DescriptorSet,
grayscale: &VulkanImage,
color: &VulkanImage,
sampler: vk::Sampler,
) {
let gray_info = vk::DescriptorImageInfo::default()
.sampler(sampler)
.image_view(grayscale.view())
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
let gray_infos = [gray_info];
let color_info = vk::DescriptorImageInfo::default()
.sampler(sampler)
.image_view(color.view())
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
let color_infos = [color_info];
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(&gray_infos),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(&color_infos),
];
unsafe {
device.update_descriptor_sets(&writes, &[]);
}
}
fn create_pipeline_layout(
device: &ash::Device,
set_layouts: &[vk::DescriptorSetLayout],
) -> vk::PipelineLayout {
let info = vk::PipelineLayoutCreateInfo::default().set_layouts(set_layouts);
unsafe {
device
.create_pipeline_layout(&info, None)
.expect("create_pipeline_layout(grid)")
}
}
fn create_sampler(device: &ash::Device) -> vk::Sampler {
let info = vk::SamplerCreateInfo::default()
.mag_filter(vk::Filter::NEAREST)
.min_filter(vk::Filter::NEAREST)
.mipmap_mode(vk::SamplerMipmapMode::NEAREST)
.address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE);
unsafe {
device
.create_sampler(&info, None)
.expect("create_sampler(grid.text)")
}
}
fn create_bg_pipeline(
device: &ash::Device,
pipeline_cache: vk::PipelineCache,
layout: vk::PipelineLayout,
color_format: vk::Format,
) -> vk::Pipeline {
build_pipeline(
device,
pipeline_cache,
layout,
color_format,
BG_VERT_SPV,
BG_FRAG_SPV,
&[], &[],
vk::PrimitiveTopology::TRIANGLE_LIST,
BlendMode::Premultiplied, )
}
fn create_text_pipeline(
device: &ash::Device,
pipeline_cache: vk::PipelineCache,
layout: vk::PipelineLayout,
color_format: vk::Format,
) -> vk::Pipeline {
let bindings = [vk::VertexInputBindingDescription::default()
.binding(0)
.stride(std::mem::size_of::<CellText>() as u32)
.input_rate(vk::VertexInputRate::INSTANCE)];
let attrs = [
vk::VertexInputAttributeDescription::default()
.location(0)
.binding(0)
.format(vk::Format::R32G32_UINT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.location(1)
.binding(0)
.format(vk::Format::R32G32_UINT)
.offset(8),
vk::VertexInputAttributeDescription::default()
.location(2)
.binding(0)
.format(vk::Format::R16G16_SINT)
.offset(16),
vk::VertexInputAttributeDescription::default()
.location(3)
.binding(0)
.format(vk::Format::R16G16_UINT)
.offset(20),
vk::VertexInputAttributeDescription::default()
.location(4)
.binding(0)
.format(vk::Format::R8G8B8A8_UNORM)
.offset(24),
vk::VertexInputAttributeDescription::default()
.location(5)
.binding(0)
.format(vk::Format::R8_UINT)
.offset(28),
vk::VertexInputAttributeDescription::default()
.location(6)
.binding(0)
.format(vk::Format::R8_UINT)
.offset(29),
];
build_pipeline(
device,
pipeline_cache,
layout,
color_format,
TEXT_VERT_SPV,
TEXT_FRAG_SPV,
&bindings,
&attrs,
vk::PrimitiveTopology::TRIANGLE_STRIP,
BlendMode::PremultipliedOverFromOne, )
}
#[derive(Copy, Clone)]
enum BlendMode {
Premultiplied,
PremultipliedOverFromOne,
}
#[allow(clippy::too_many_arguments)]
fn build_pipeline(
device: &ash::Device,
pipeline_cache: vk::PipelineCache,
layout: vk::PipelineLayout,
color_format: vk::Format,
vert_spv: &[u8],
frag_spv: &[u8],
vertex_bindings: &[vk::VertexInputBindingDescription],
vertex_attrs: &[vk::VertexInputAttributeDescription],
topology: vk::PrimitiveTopology,
blend: BlendMode,
) -> vk::Pipeline {
let vert = load_shader_module(device, vert_spv);
let frag = load_shader_module(device, frag_spv);
let entry = c"main";
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert)
.name(entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag)
.name(entry),
];
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(vertex_bindings)
.vertex_attribute_descriptions(vertex_attrs);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(topology)
.primitive_restart_enable(false);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
.polygon_mode(vk::PolygonMode::FILL)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.line_width(1.0);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let (src_rgb, dst_rgb) = match blend {
BlendMode::Premultiplied => (
vk::BlendFactor::SRC_ALPHA,
vk::BlendFactor::ONE_MINUS_SRC_ALPHA,
),
BlendMode::PremultipliedOverFromOne => {
(vk::BlendFactor::ONE, vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
}
};
let blend_attachment = vk::PipelineColorBlendAttachmentState::default()
.blend_enable(true)
.src_color_blend_factor(src_rgb)
.dst_color_blend_factor(dst_rgb)
.color_blend_op(vk::BlendOp::ADD)
.src_alpha_blend_factor(vk::BlendFactor::ONE)
.dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.alpha_blend_op(vk::BlendOp::ADD)
.color_write_mask(vk::ColorComponentFlags::RGBA);
let blend_attachments = [blend_attachment];
let color_blend =
vk::PipelineColorBlendStateCreateInfo::default().attachments(&blend_attachments);
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic_state =
vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let color_attachment_formats = [color_format];
let mut rendering = vk::PipelineRenderingCreateInfo::default()
.color_attachment_formats(&color_attachment_formats);
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vertex_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&rasterization)
.multisample_state(&multisample)
.color_blend_state(&color_blend)
.dynamic_state(&dynamic_state)
.layout(layout)
.push_next(&mut rendering);
let pipeline = unsafe {
device
.create_graphics_pipelines(pipeline_cache, &[pipeline_info], None)
.map_err(|(_, e)| e)
.expect("create_graphics_pipelines(grid)")[0]
};
unsafe {
device.destroy_shader_module(vert, None);
device.destroy_shader_module(frag, None);
}
pipeline
}
fn load_shader_module(device: &ash::Device, bytes: &[u8]) -> vk::ShaderModule {
let code = ash::util::read_spv(&mut std::io::Cursor::new(bytes))
.expect("read_spv (embedded grid shader is valid)");
let info = vk::ShaderModuleCreateInfo::default().code(&code);
unsafe {
device
.create_shader_module(&info, None)
.expect("create_shader_module(grid)")
}
}