use async_trait::async_trait;
use std::sync::Arc;
use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer};
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferToImageInfo, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract};
use vulkano::format::Format;
use vulkano::image::sampler::{Filter, Sampler, SamplerCreateInfo};
use vulkano::image::view::ImageView;
use vulkano::image::{Image, ImageCreateInfo, ImageType, ImageUsage};
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter};
use vulkano::pipeline::graphics::color_blend::{AttachmentBlend, ColorBlendAttachmentState, ColorBlendState};
use vulkano::pipeline::graphics::input_assembly::InputAssemblyState;
use vulkano::pipeline::graphics::multisample::MultisampleState;
use vulkano::pipeline::graphics::rasterization::{CullMode, RasterizationState};
use vulkano::pipeline::graphics::vertex_input::{Vertex, VertexDefinition};
use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo;
use vulkano::pipeline::layout::PipelineDescriptorSetLayoutCreateInfo;
use vulkano::pipeline::{Pipeline, PipelineBindPoint, PipelineLayout, PipelineShaderStageCreateInfo};
use vulkano::render_pass::Subpass;
use vulkano::sync::GpuFuture;
use vulkano::{DeviceSize, Validated};
use crate::event::Event;
use crate::render::font::compositor::FontRenderer;
use crate::render::font::layout::PositionedChar;
use crate::render::font::size::FontSizer;
use crate::render::font::Font;
use crate::render::image::EngineImageViewSampler;
use crate::render::pipeline::{EngineGraphicsPipeline, VKGraphicsPipelineSource, ViewportType};
use crate::render::{EngineDevice, FlatVertex, RenderTarget, Renderer, RendererPostEvent, VulkanoError};
use crate::render::descriptor_set::EngineDescriptorSet;
use crate::resource::{Resource, ResourceLoadError, ResourceLoadResult, ResourceMut, SubResourceLoader};
pub const UNICODE_LATIN_START: u32 = 0x20;
pub const UNICODE_LATIN_END: u32 = 0x7F;
pub trait FontSheetMap: Send + Sync + 'static {
fn map_char_direct(&self, c: char) -> Option<u32>;
fn replacement(&self) -> Option<u32>;
fn count(&self) -> u32;
fn map_char(&self, c: char) -> Option<u32> {
self.map_char_direct(c).or(self.replacement())
}
fn supported_chars(&self) -> Box<dyn Iterator<Item=char>>;
}
pub struct UnicodeFontSheetMap {
char_start: u32,
length: u32,
}
impl UnicodeFontSheetMap {
#[inline]
pub fn new(block_begin: u32, block_end: u32) -> Self {
Self {
char_start: block_begin,
length: block_end - block_begin,
}
}
#[inline]
pub fn basic_latin() -> Self {
Self::new(UNICODE_LATIN_START, UNICODE_LATIN_END)
}
}
impl FontSheetMap for UnicodeFontSheetMap {
fn map_char_direct(&self, c: char) -> Option<u32> {
let c_idx = c as u32;
if c_idx < self.char_start || c_idx >= (self.char_start + self.length) {
None
} else {
Some(c_idx - self.char_start)
}
}
fn replacement(&self) -> Option<u32> {
Some(self.length)
}
fn count(&self) -> u32 {
self.length + 1
}
fn supported_chars(&self) -> Box<dyn Iterator<Item=char>> {
Box::new(
(self.char_start..(self.char_start + self.length))
.map(|char_idx| char::from_u32(char_idx).unwrap())
.chain([char::REPLACEMENT_CHARACTER].into_iter())
)
}
}
pub struct FontSheet {
sheet: Arc<ImageView>,
mapper: Arc<dyn FontSheetMap>,
sizer: Arc<dyn FontSizer>,
}
impl FontSheet {
#[inline]
pub fn new(
sheet: Arc<ImageView>,
mapper: Arc<dyn FontSheetMap>,
sizer: Arc<dyn FontSizer>,
) -> Self {
Self {
sheet,
mapper,
sizer,
}
}
#[inline]
pub fn from_font(
font: &Arc<Resource<dyn Font>>,
renderer: Arc<Renderer>,
px_scale: f32,
mapper: Arc<dyn FontSheetMap>,
) -> ResourceLoadResult<FontSheet> {
font.attach_sub_resource_blocking(FontSheetLoader {
renderer,
px_scale,
mapper
})
}
#[inline]
pub fn image_view(&self) -> &Arc<ImageView> { &self.sheet }
#[inline]
pub fn mapper(&self) -> &Arc<dyn FontSheetMap> { &self.mapper }
#[inline]
pub fn sizer(&self) -> &Arc<dyn FontSizer> { &self.sizer }
}
struct FontSheetLoader {
renderer: Arc<Renderer>,
px_scale: f32,
mapper: Arc<dyn FontSheetMap>,
}
impl FontSheetLoader {
fn build_upload_buffer(
device: &Arc<EngineDevice>,
upload_buffer: Subbuffer<[u8]>,
image: Arc<Image>,
) -> Result<Arc<PrimaryAutoCommandBuffer>, ResourceLoadError> {
let mut uploads = AutoCommandBufferBuilder::primary(
device.command_buffer_allocator().as_ref(),
device.queue().queue_family_index(),
CommandBufferUsage::OneTimeSubmit,
).map_err(Validated::unwrap).map_err(ResourceLoadError::from_error)?;
uploads.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(
upload_buffer,
image.clone(),
)).map_err(ResourceLoadError::from_error)?;
uploads
.build()
.map_err(Validated::unwrap).map_err(ResourceLoadError::from_error)
}
}
#[async_trait]
impl SubResourceLoader<FontSheet, dyn Font> for FontSheetLoader {
async fn load(&self, parent: &dyn Font) -> Result<Box<FontSheet>, ResourceLoadError> {
let char_img_data = parent.char_img_data(self.px_scale, self.mapper.as_ref());
let img_size = char_img_data.img_size();
let img_byte_size = char_img_data.img_byte_size();
let sizer = char_img_data.sizer();
let image = Image::new(
self.renderer.device().memory_allocator().clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_SRGB,
extent: [img_size.x, img_size.y, 1],
array_layers: char_img_data.len() as u32,
usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED,
..Default::default()
},
AllocationCreateInfo::default(),
).map_err(Validated::unwrap).map_err(ResourceLoadError::from_error)?;
let upload_buffer = Buffer::new_slice(
self.renderer.device().memory_allocator().clone(),
BufferCreateInfo {
usage: BufferUsage::TRANSFER_SRC,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_HOST
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
(img_byte_size * char_img_data.len()) as DeviceSize,
).map_err(Validated::unwrap).map_err(ResourceLoadError::from_error)?;
for (idx, glyph) in char_img_data.enumerate() {
let start_idx = idx * img_byte_size;
let end_idx = (idx + 1) * img_byte_size;
upload_buffer.write()
.map_err(ResourceLoadError::from_error)?
[start_idx..end_idx]
.copy_from_slice(&glyph);
}
let cmd_buffer = Self::build_upload_buffer(
self.renderer.device(),
upload_buffer,
image.clone(),
)?;
cmd_buffer
.execute(self.renderer.device().queue().clone())
.map_err(ResourceLoadError::from_error)?
.then_signal_fence_and_flush()
.map_err(Validated::unwrap).map_err(ResourceLoadError::from_error)?
.await
.map_err(ResourceLoadError::from_error)?;
let image_view = ImageView::new_default(image)
.map_err(Validated::unwrap).map_err(ResourceLoadError::from_error)?;
Ok(Box::new(FontSheet::new(
image_view,
self.mapper.clone(),
sizer,
)))
}
async fn update(&self, resource: ResourceMut<FontSheet>, parent: &dyn Font) -> Result<(), ResourceLoadError> {
let new_value = self.load(parent).await?;
self.renderer.event_bus().register_once(move |_event: &mut Event<RendererPostEvent>| {
resource.replace(new_value);
}).unwrap().await
.map_err(ResourceLoadError::from_error)
}
}
#[derive(BufferContents, Vertex)]
#[repr(C)]
struct GlyphInstance {
#[format(R32_SFLOAT)]
index: f32,
#[format(R32G32_SFLOAT)]
offset: [f32; 2],
#[format(R32G32_SFLOAT)]
scale: [f32; 2],
#[format(R32G32B32_SFLOAT)]
color: [f32; 3],
}
mod text_vert {
vulkano_shaders::shader! {
ty: "vertex",
path: "src/render/font/shaders/sheet.vert",
}
}
mod text_frag {
vulkano_shaders::shader! {
ty: "fragment",
path: "src/render/font/shaders/sheet.frag",
}
}
pub struct FontSheetRenderer {
target: Arc<dyn RenderTarget>,
device: Arc<EngineDevice>,
font_sheet: Arc<Resource<FontSheet>>,
graphics: Arc<EngineGraphicsPipeline>,
glyph_buffer: Subbuffer<[FlatVertex]>,
descriptor_set: EngineDescriptorSet,
}
impl FontSheetRenderer {
pub fn new(renderer: &Arc<Renderer>, subpass: &Subpass, font_sheet: Arc<Resource<FontSheet>>) -> Box<dyn FontRenderer> {
let text_vert = text_vert::load(renderer.device().vk_device().clone())
.expect("Failed to create vertex shader module")
.entry_point("main").unwrap();
let text_frag = text_frag::load(renderer.device().vk_device().clone())
.expect("Failed to create fragment shader module")
.entry_point("main").unwrap();
let vertex_input_state = Some([FlatVertex::per_vertex(), GlyphInstance::per_instance()]
.definition(&text_vert.info().input_interface)
.unwrap()
);
let stages = [
PipelineShaderStageCreateInfo::new(text_vert),
PipelineShaderStageCreateInfo::new(text_frag),
];
let layout = PipelineLayout::new(
renderer.device().vk_device().clone(),
PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages)
.into_pipeline_layout_create_info(renderer.device().vk_device().clone())
.unwrap(),
).unwrap();
let descriptor_layout = layout.set_layouts().get(0).unwrap().clone();
let create_info = GraphicsPipelineCreateInfo {
stages: stages.into_iter().collect(),
vertex_input_state,
subpass: Some(subpass.clone().into()),
color_blend_state: Some(ColorBlendState::with_attachment_states(
subpass.num_color_attachments(),
ColorBlendAttachmentState {
blend: Some(AttachmentBlend::alpha()),
..Default::default()
},
)),
input_assembly_state: Some(InputAssemblyState::default()),
rasterization_state: Some(RasterizationState {
cull_mode: CullMode::Back,
..Default::default()
}),
multisample_state: Some(MultisampleState::default()),
..GraphicsPipelineCreateInfo::layout(layout)
};
let graphics = EngineGraphicsPipeline::new(
renderer,
create_info,
ViewportType::TopLeft,
);
let font_sampler = Arc::new(EngineImageViewSampler::new(
font_sheet.read().image_view().clone(),
Sampler::new(
renderer.device().vk_device().clone(),
SamplerCreateInfo {
mag_filter: Filter::Linear,
min_filter: Filter::Linear,
..Default::default()
}
).unwrap(),
));
let update_font_sampler = font_sampler.clone();
font_sheet.attach_update_callback_blocking(move |font_sheet| {
let update_font_sampler = update_font_sampler.clone();
async move {
update_font_sampler.set_image_view(font_sheet.read().image_view().clone());
}
});
let glyph_buffer = FlatVertex::buffer(
renderer.device().memory_allocator().clone(),
glm::vec2(0.0, 0.0),
glm::vec2(1.0, 1.0),
);
let descriptor_set = EngineDescriptorSet::builder(renderer.clone())
.layout(descriptor_layout)
.descriptor_source(0, font_sampler)
.build();
Box::new(Self {
target: renderer.target().clone(),
device: renderer.device().clone(),
font_sheet,
graphics,
glyph_buffer,
descriptor_set,
})
}
}
impl FontRenderer for FontSheetRenderer {
fn build_commands(
&self,
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
buffer: Vec<PositionedChar>,
) -> Result<(), VulkanoError> {
let graphics = self.graphics.vk_graphics();
let font_sheet = self.font_sheet.read();
let mapper = font_sheet.mapper();
let sizer = font_sheet.sizer();
let screen_size = self.target.extent();
let screen_scale = glm::vec2(
2.0 / screen_size.x as f32,
2.0 / screen_size.y as f32,
);
let glyphs = buffer.into_iter()
.filter_map(|layout_char| {
if let Some(index) = mapper.map_char(layout_char.value) {
Some(GlyphInstance {
index: index as f32,
offset: [
layout_char.position.x * screen_scale.x - 1.0,
layout_char.position.y * screen_scale.y - 1.0,
],
scale: [
sizer.width(layout_char.size) * screen_scale.x,
sizer.height(layout_char.size) * screen_scale.y,
],
color: layout_char.color.into(),
})
} else {
None
}
})
.collect::<Vec<_>>();
if glyphs.is_empty() {
return Ok(());
}
let instance_buffer = Buffer::from_iter(
self.device.memory_allocator().clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
glyphs,
).map_err(Validated::unwrap)?;
let instance_buffer_len = instance_buffer.len() as u32;
builder
.bind_pipeline_graphics(graphics.clone()).unwrap()
.bind_descriptor_sets(
PipelineBindPoint::Graphics,
graphics.layout().clone(),
0,
self.descriptor_set.descriptor_set().map_err(Validated::unwrap)?,
).unwrap()
.bind_vertex_buffers(0, (
self.glyph_buffer.clone(),
instance_buffer,
)).unwrap()
.draw(
self.glyph_buffer.len() as u32,
instance_buffer_len,
0,
0,
).unwrap();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unicode_map_basic_latin() {
let basic_latin_chars = format!(
"{}{}{}",
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
char::REPLACEMENT_CHARACTER,
);
let basic_latin_char_count = basic_latin_chars.chars().count();
let mapper = UnicodeFontSheetMap::basic_latin();
assert_eq!(mapper.supported_chars().collect::<String>(), basic_latin_chars);
assert_eq!(mapper.count() as usize, basic_latin_char_count);
assert_eq!(mapper.replacement(), Some((basic_latin_char_count - 1) as u32));
}
}