mirror of https://github.com/wayvr-org/wayvr.git
glyph upload fixes (#531)
* remove unused pass cache, cache imageviews instead * glyph upload batching * image upload batching (wip)
This commit is contained in:
parent
7f97dc884f
commit
4b78e39af7
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::tab::settings::{
|
use crate::tab::settings::{
|
||||||
SettingType, SettingsMountParams, SettingsTab,
|
SettingType, SettingsMountParams, SettingsTab,
|
||||||
macros::{options_category, options_checkbox, options_range_f32},
|
macros::{options_category, options_checkbox, options_range_f32, options_slider_f32},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct State {}
|
pub struct State {}
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,7 @@ pub struct Rectangle {
|
||||||
pub struct ImagePrimitive {
|
pub struct ImagePrimitive {
|
||||||
pub content: CustomGlyphData,
|
pub content: CustomGlyphData,
|
||||||
pub content_key: usize,
|
pub content_key: usize,
|
||||||
|
pub skip_cache: bool,
|
||||||
|
|
||||||
pub border: f32, // width in pixels
|
pub border: f32, // width in pixels
|
||||||
pub border_color: Color,
|
pub border_color: Color,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
drawing::{self},
|
drawing::{self},
|
||||||
font_config,
|
font_config,
|
||||||
gfx::{WGfx, cmd::GfxCommandBuffer},
|
gfx::{WGfx, cmd::GfxCommandBuffer},
|
||||||
renderer_vk::image::{ImagePipeline, ImageRenderer},
|
renderer_vk::image::{ImagePipeline, ImageRenderer, ImageViewCache},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
|
@ -62,6 +62,7 @@ impl RendererPass<'_> {
|
||||||
viewport: &mut Viewport,
|
viewport: &mut Viewport,
|
||||||
cmd_buf: &mut GfxCommandBuffer,
|
cmd_buf: &mut GfxCommandBuffer,
|
||||||
text_atlas: &mut TextAtlas,
|
text_atlas: &mut TextAtlas,
|
||||||
|
image_view_cache: &mut ImageViewCache,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if self.submitted {
|
if self.submitted {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -95,7 +96,9 @@ impl RendererPass<'_> {
|
||||||
|
|
||||||
self.submitted = true;
|
self.submitted = true;
|
||||||
self.rect_renderer.render(gfx, viewport, &vk_scissor, cmd_buf)?;
|
self.rect_renderer.render(gfx, viewport, &vk_scissor, cmd_buf)?;
|
||||||
self.image_renderer.render(gfx, viewport, &vk_scissor, cmd_buf)?;
|
self
|
||||||
|
.image_renderer
|
||||||
|
.render(gfx, viewport, &vk_scissor, cmd_buf, image_view_cache)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut font_system = font_system.system.lock();
|
let mut font_system = font_system.system.lock();
|
||||||
|
|
@ -169,6 +172,7 @@ pub struct Context {
|
||||||
pub dirty: bool,
|
pub dirty: bool,
|
||||||
pixel_scale: f32,
|
pixel_scale: f32,
|
||||||
empty_text: Rc<RefCell<Buffer>>,
|
empty_text: Rc<RefCell<Buffer>>,
|
||||||
|
image_cache: ImageViewCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ContextDrawResult {
|
pub struct ContextDrawResult {
|
||||||
|
|
@ -187,6 +191,7 @@ impl Context {
|
||||||
pixel_scale,
|
pixel_scale,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
empty_text: Rc::new(RefCell::new(Buffer::new_empty(DEFAULT_METRICS))),
|
empty_text: Rc::new(RefCell::new(Buffer::new_empty(DEFAULT_METRICS))),
|
||||||
|
image_cache: ImageViewCache::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,6 +247,9 @@ impl Context {
|
||||||
let mut needs_new_pass = true;
|
let mut needs_new_pass = true;
|
||||||
let mut cur_scissor: Option<drawing::Boundary> = None;
|
let mut cur_scissor: Option<drawing::Boundary> = None;
|
||||||
|
|
||||||
|
// drop unreferenced image views to avoid vram leaks
|
||||||
|
self.image_cache.retain(|_, v| v.content.strong_count() > 0);
|
||||||
|
|
||||||
for primitive in primitives {
|
for primitive in primitives {
|
||||||
if needs_new_pass {
|
if needs_new_pass {
|
||||||
passes.push(RendererPass::new(
|
passes.push(RendererPass::new(
|
||||||
|
|
@ -339,6 +347,7 @@ impl Context {
|
||||||
&mut self.viewport,
|
&mut self.viewport,
|
||||||
cmd_buf,
|
cmd_buf,
|
||||||
&mut atlas.text_atlas,
|
&mut atlas.text_atlas,
|
||||||
|
&mut self.image_cache,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Weak},
|
||||||
|
};
|
||||||
|
|
||||||
use cosmic_text::SubpixelBin;
|
use cosmic_text::SubpixelBin;
|
||||||
use glam::Mat4;
|
use glam::Mat4;
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
buffer::{BufferContents, BufferUsage, Subbuffer},
|
buffer::{BufferContents, BufferUsage},
|
||||||
command_buffer::CommandBufferUsage,
|
command_buffer::CommandBufferUsage,
|
||||||
format::Format,
|
format::Format,
|
||||||
image::view::ImageView,
|
image::view::ImageView,
|
||||||
|
|
@ -16,12 +19,11 @@ use crate::{
|
||||||
gfx::{
|
gfx::{
|
||||||
BLEND_ALPHA, WGfx,
|
BLEND_ALPHA, WGfx,
|
||||||
cmd::GfxCommandBuffer,
|
cmd::GfxCommandBuffer,
|
||||||
pass::WGfxPass,
|
|
||||||
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
||||||
},
|
},
|
||||||
renderer_vk::{
|
renderer_vk::{
|
||||||
model_buffer::ModelBuffer,
|
model_buffer::ModelBuffer,
|
||||||
text::custom_glyph::{CustomGlyphData, RasterizeCustomGlyphRequest, RasterizedCustomGlyph},
|
text::custom_glyph::{CustomGlyphContent, CustomGlyphData, RasterizeCustomGlyphRequest, RasterizedCustomGlyph},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -65,24 +67,36 @@ impl ImagePipeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type ImageViewCache = HashMap<usize, CachedImageView>;
|
||||||
|
|
||||||
|
pub struct CachedImageView {
|
||||||
|
pub(super) content: Weak<CustomGlyphContent>,
|
||||||
|
view: Arc<ImageView>,
|
||||||
|
res: [u32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
struct ImageVertexWithContent {
|
struct ImageVertexWithContent {
|
||||||
vert: ImageVertex,
|
vert: ImageVertex,
|
||||||
content: CustomGlyphData,
|
content: CustomGlyphData,
|
||||||
content_key: usize, // identifies an image tag.
|
skip_cache: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CachedPass {
|
struct PendingImageUpload {
|
||||||
content_id: usize,
|
content_id: usize,
|
||||||
vert_buffer: Subbuffer<[ImageVertex]>,
|
content: Weak<CustomGlyphContent>,
|
||||||
inner: WGfxPass<ImageVertex>,
|
raster: RasterizedCustomGlyph,
|
||||||
res: [u32; 2],
|
}
|
||||||
|
|
||||||
|
enum ImageViewSource {
|
||||||
|
Ready(Arc<ImageView>),
|
||||||
|
PendingUpload(usize),
|
||||||
|
Missing,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ImageRenderer {
|
pub struct ImageRenderer {
|
||||||
pipeline: ImagePipeline,
|
pipeline: ImagePipeline,
|
||||||
image_verts: Vec<ImageVertexWithContent>,
|
image_verts: Vec<ImageVertexWithContent>,
|
||||||
model_buffer: ModelBuffer,
|
model_buffer: ModelBuffer,
|
||||||
cached_passes: HashMap<usize, CachedPass>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageRenderer {
|
impl ImageRenderer {
|
||||||
|
|
@ -91,15 +105,9 @@ impl ImageRenderer {
|
||||||
model_buffer: ModelBuffer::new(&pipeline.gfx)?,
|
model_buffer: ModelBuffer::new(&pipeline.gfx)?,
|
||||||
pipeline,
|
pipeline,
|
||||||
image_verts: vec![],
|
image_verts: vec![],
|
||||||
cached_passes: HashMap::new(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin(&mut self) {
|
|
||||||
self.image_verts.clear();
|
|
||||||
self.model_buffer.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_image(&mut self, boundary: Boundary, image: ImagePrimitive, transform: &Mat4) {
|
pub fn add_image(&mut self, boundary: Boundary, image: ImagePrimitive, transform: &Mat4) {
|
||||||
let in_model_idx = self
|
let in_model_idx = self
|
||||||
.model_buffer
|
.model_buffer
|
||||||
|
|
@ -118,15 +126,11 @@ impl ImageRenderer {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
content: image.content,
|
content: image.content,
|
||||||
content_key: image.content_key,
|
skip_cache: image.skip_cache,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upload_image(
|
fn rasterize_image(res: [u32; 2], img: &ImageVertexWithContent) -> Option<RasterizedCustomGlyph> {
|
||||||
gfx: &Arc<WGfx>,
|
|
||||||
res: [u32; 2],
|
|
||||||
img: &ImageVertexWithContent,
|
|
||||||
) -> anyhow::Result<Option<Arc<ImageView>>> {
|
|
||||||
let Some(raster) = RasterizedCustomGlyph::try_from(&RasterizeCustomGlyphRequest {
|
let Some(raster) = RasterizedCustomGlyph::try_from(&RasterizeCustomGlyphRequest {
|
||||||
data: img.content.clone(),
|
data: img.content.clone(),
|
||||||
width: res[0] as _,
|
width: res[0] as _,
|
||||||
|
|
@ -136,21 +140,10 @@ impl ImageRenderer {
|
||||||
scale: 1.0, // unused
|
scale: 1.0, // unused
|
||||||
}) else {
|
}) else {
|
||||||
log::error!("Unable to rasterize custom image");
|
log::error!("Unable to rasterize custom image");
|
||||||
return Ok(None);
|
return None;
|
||||||
};
|
};
|
||||||
let mut cmd_buf = gfx.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
|
||||||
|
|
||||||
let image = cmd_buf.upload_image(
|
Some(raster)
|
||||||
raster.width.into(),
|
|
||||||
raster.height.into(),
|
|
||||||
Format::R8G8B8A8_UNORM,
|
|
||||||
&raster.data,
|
|
||||||
)?;
|
|
||||||
let image_view = ImageView::new_default(image)?;
|
|
||||||
|
|
||||||
cmd_buf.build_and_execute_now()?;
|
|
||||||
|
|
||||||
Ok(Some(image_view))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(
|
pub fn render(
|
||||||
|
|
@ -159,65 +152,122 @@ impl ImageRenderer {
|
||||||
viewport: &mut Viewport,
|
viewport: &mut Viewport,
|
||||||
vk_scissor: &graphics::viewport::Scissor,
|
vk_scissor: &graphics::viewport::Scissor,
|
||||||
cmd_buf: &mut GfxCommandBuffer,
|
cmd_buf: &mut GfxCommandBuffer,
|
||||||
|
image_view_cache: &mut ImageViewCache,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let res = viewport.resolution();
|
let res = viewport.resolution();
|
||||||
self.model_buffer.upload(gfx)?;
|
self.model_buffer.upload(gfx)?;
|
||||||
|
|
||||||
|
let mut pending_upload_by_key = HashMap::<usize, usize>::new();
|
||||||
|
let mut pending_uploads = Vec::<PendingImageUpload>::new();
|
||||||
|
let mut image_sources = Vec::<ImageViewSource>::with_capacity(self.image_verts.len());
|
||||||
|
|
||||||
|
// decide which images need to be rasterized and uploaded
|
||||||
for img in &self.image_verts {
|
for img in &self.image_verts {
|
||||||
let pass = if let Some(x) = self.cached_passes.get_mut(&img.content_key) {
|
if let Some(upload_idx) = pending_upload_by_key.get(&img.content.id) {
|
||||||
if x.content_id != img.content.id || x.res != res {
|
image_sources.push(ImageViewSource::PendingUpload(*upload_idx));
|
||||||
// image changed
|
continue;
|
||||||
let Some(image_view) = Self::upload_image(&self.pipeline.gfx, res, img)? else {
|
}
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
x.inner
|
if let Some(cached) = image_view_cache.get(&img.content.id)
|
||||||
.update_sampler(2, image_view, self.pipeline.gfx.texture_filter)?;
|
&& !img.skip_cache
|
||||||
}
|
&& cached.res == res
|
||||||
|
{
|
||||||
|
image_sources.push(ImageViewSource::Ready(cached.view.clone()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
x
|
let Some(raster) = Self::rasterize_image(res, img) else {
|
||||||
} else {
|
image_sources.push(ImageViewSource::Missing);
|
||||||
let vert_buffer = self.pipeline.gfx.empty_buffer(
|
continue;
|
||||||
BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST,
|
};
|
||||||
(std::mem::size_of::<ImageVertex>()) as _,
|
|
||||||
|
let upload_idx = pending_uploads.len();
|
||||||
|
pending_uploads.push(PendingImageUpload {
|
||||||
|
content: Arc::downgrade(&img.content.content),
|
||||||
|
content_id: img.content.id,
|
||||||
|
raster,
|
||||||
|
});
|
||||||
|
pending_upload_by_key.insert(img.content.id, upload_idx);
|
||||||
|
image_sources.push(ImageViewSource::PendingUpload(upload_idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
// upload every missing/stale image using one transfer command buffer
|
||||||
|
let mut uploaded_image_views = vec![None; pending_uploads.len()];
|
||||||
|
|
||||||
|
if !pending_uploads.is_empty() {
|
||||||
|
let mut xfer_cmd_buf = gfx.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
|
|
||||||
|
for (upload_idx, upload) in pending_uploads.iter().enumerate() {
|
||||||
|
log::trace!("Uploading image {}", upload.content_id);
|
||||||
|
let image = xfer_cmd_buf.upload_image(
|
||||||
|
upload.raster.width.into(),
|
||||||
|
upload.raster.height.into(),
|
||||||
|
Format::R8G8B8A8_UNORM,
|
||||||
|
&upload.raster.data,
|
||||||
)?;
|
)?;
|
||||||
|
uploaded_image_views[upload_idx] = Some(ImageView::new_default(image)?);
|
||||||
|
}
|
||||||
|
|
||||||
let Some(image_view) = Self::upload_image(&self.pipeline.gfx, res, img)? else {
|
xfer_cmd_buf.build_and_execute_now()?;
|
||||||
|
|
||||||
|
for (upload_idx, upload) in pending_uploads.iter().enumerate() {
|
||||||
|
let Some(image_view) = uploaded_image_views[upload_idx].as_ref() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let set0 = viewport.get_image_descriptor(&self.pipeline);
|
image_view_cache.insert(
|
||||||
let set1 = self.model_buffer.get_image_descriptor(&self.pipeline);
|
upload.content_id,
|
||||||
let set2 = self
|
CachedImageView {
|
||||||
.pipeline
|
content: upload.content.clone(),
|
||||||
.inner
|
view: image_view.clone(),
|
||||||
.uniform_sampler(2, image_view, self.pipeline.gfx.texture_filter)?;
|
|
||||||
|
|
||||||
let pass = self.pipeline.inner.create_pass(
|
|
||||||
[res[0] as _, res[1] as _],
|
|
||||||
[0.0, 0.0],
|
|
||||||
vert_buffer.clone(),
|
|
||||||
0..4,
|
|
||||||
0..1,
|
|
||||||
vec![set0, set1, set2],
|
|
||||||
vk_scissor,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.cached_passes.insert(
|
|
||||||
img.content_key,
|
|
||||||
CachedPass {
|
|
||||||
content_id: img.content.id,
|
|
||||||
vert_buffer,
|
|
||||||
inner: pass,
|
|
||||||
res,
|
res,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.cached_passes.get_mut(&img.content_key).unwrap()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the rendering work
|
||||||
|
for (img, image_source) in self.image_verts.iter().zip(image_sources.iter()) {
|
||||||
|
let image_view = match image_source {
|
||||||
|
ImageViewSource::Ready(image_view) => image_view.clone(),
|
||||||
|
ImageViewSource::PendingUpload(upload_idx, ..) => {
|
||||||
|
let Some(image_view) = uploaded_image_views
|
||||||
|
.get(*upload_idx)
|
||||||
|
.and_then(|image_view| image_view.as_ref())
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
image_view.clone()
|
||||||
|
}
|
||||||
|
ImageViewSource::Missing => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
pass.vert_buffer.write()?[0..1].clone_from_slice(&[img.vert]);
|
let vert_buffer = self.pipeline.gfx.empty_buffer(
|
||||||
|
BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST,
|
||||||
|
(std::mem::size_of::<ImageVertex>()) as _,
|
||||||
|
)?;
|
||||||
|
|
||||||
cmd_buf.run_ref(&pass.inner)?;
|
let set0 = viewport.get_image_descriptor(&self.pipeline);
|
||||||
|
let set1 = self.model_buffer.get_image_descriptor(&self.pipeline);
|
||||||
|
let set2 = self
|
||||||
|
.pipeline
|
||||||
|
.inner
|
||||||
|
.uniform_sampler(2, image_view, self.pipeline.gfx.texture_filter)?;
|
||||||
|
|
||||||
|
let pass = self.pipeline.inner.create_pass(
|
||||||
|
[res[0] as _, res[1] as _],
|
||||||
|
[0.0, 0.0],
|
||||||
|
vert_buffer.clone(),
|
||||||
|
0..4,
|
||||||
|
0..1,
|
||||||
|
vec![set0, set1, set2],
|
||||||
|
vk_scissor,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
vert_buffer.write()?[0..1].clone_from_slice(&[img.vert]);
|
||||||
|
|
||||||
|
cmd_buf.run_ref(&pass)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ use crate::{
|
||||||
gfx::{
|
gfx::{
|
||||||
BLEND_ALPHA, WGfx,
|
BLEND_ALPHA, WGfx,
|
||||||
cmd::GfxCommandBuffer,
|
cmd::GfxCommandBuffer,
|
||||||
pass::WGfxPass,
|
|
||||||
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
||||||
},
|
},
|
||||||
renderer_vk::model_buffer::ModelBuffer,
|
renderer_vk::model_buffer::ModelBuffer,
|
||||||
|
|
@ -59,18 +58,12 @@ impl RectPipeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CachedPass {
|
|
||||||
pass: WGfxPass<RectVertex>,
|
|
||||||
res: [u32; 2],
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RectRenderer {
|
pub struct RectRenderer {
|
||||||
pipeline: RectPipeline,
|
pipeline: RectPipeline,
|
||||||
rect_vertices: Vec<RectVertex>,
|
rect_vertices: Vec<RectVertex>,
|
||||||
vert_buffer: Subbuffer<[RectVertex]>,
|
vert_buffer: Subbuffer<[RectVertex]>,
|
||||||
vert_buffer_len: usize,
|
vert_buffer_len: usize,
|
||||||
model_buffer: ModelBuffer,
|
model_buffer: ModelBuffer,
|
||||||
pass: Option<CachedPass>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RectRenderer {
|
impl RectRenderer {
|
||||||
|
|
@ -88,15 +81,9 @@ impl RectRenderer {
|
||||||
rect_vertices: vec![],
|
rect_vertices: vec![],
|
||||||
vert_buffer,
|
vert_buffer,
|
||||||
vert_buffer_len: BUFFER_SIZE,
|
vert_buffer_len: BUFFER_SIZE,
|
||||||
pass: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin(&mut self) {
|
|
||||||
self.rect_vertices.clear();
|
|
||||||
self.model_buffer.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_rect(&mut self, boundary: Boundary, rectangle: Rectangle, transform: &Mat4) {
|
pub fn add_rect(&mut self, boundary: Boundary, rectangle: Rectangle, transform: &Mat4) {
|
||||||
let in_model_idx = self
|
let in_model_idx = self
|
||||||
.model_buffer
|
.model_buffer
|
||||||
|
|
@ -144,27 +131,20 @@ impl RectRenderer {
|
||||||
self.model_buffer.upload(gfx)?;
|
self.model_buffer.upload(gfx)?;
|
||||||
self.upload_verts()?;
|
self.upload_verts()?;
|
||||||
|
|
||||||
let cache = match self.pass.take() {
|
let set0 = viewport.get_rect_descriptor(&self.pipeline);
|
||||||
Some(p) if p.res == res => p,
|
let set1 = self.model_buffer.get_rect_descriptor(&self.pipeline);
|
||||||
_ => {
|
let pass = self.pipeline.color_rect.create_pass(
|
||||||
let set0 = viewport.get_rect_descriptor(&self.pipeline);
|
[res[0] as _, res[1] as _],
|
||||||
let set1 = self.model_buffer.get_rect_descriptor(&self.pipeline);
|
[0.0, 0.0],
|
||||||
let pass = self.pipeline.color_rect.create_pass(
|
self.vert_buffer.clone(),
|
||||||
[res[0] as _, res[1] as _],
|
0..4,
|
||||||
[0.0, 0.0],
|
0..self.rect_vertices.len() as _,
|
||||||
self.vert_buffer.clone(),
|
vec![set0, set1],
|
||||||
0..4,
|
vk_scissor,
|
||||||
0..self.rect_vertices.len() as _,
|
)?;
|
||||||
vec![set0, set1],
|
|
||||||
vk_scissor,
|
|
||||||
)?;
|
|
||||||
CachedPass { pass, res }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.rect_vertices.clear();
|
self.rect_vertices.clear();
|
||||||
cmd_buf.run_ref(&cache.pass)?;
|
cmd_buf.run_ref(&pass)?;
|
||||||
self.pass = Some(cache);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,7 @@ impl CustomGlyphData {
|
||||||
Ok(data) => Ok(data),
|
Ok(data) => Ok(data),
|
||||||
Err(hashed_asset) => {
|
Err(hashed_asset) => {
|
||||||
let data = Self::new(CustomGlyphContent::from_bin_raster(data)?);
|
let data = Self::new(CustomGlyphContent::from_bin_raster(data)?);
|
||||||
|
log::trace!("Caching {path} with content_id {}", data.id);
|
||||||
globals_borrow.custom_glyph_cache.insert(hashed_asset, &data);
|
globals_borrow.custom_glyph_cache.insert(hashed_asset, &data);
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
gfx::{cmd::GfxCommandBuffer, pass::WGfxPass},
|
gfx::cmd::GfxCommandBuffer,
|
||||||
renderer_vk::{model_buffer::ModelBuffer, text::text_atlas::TEXT_ATLAS_ISLAND_PADDING_PX, viewport::Viewport},
|
renderer_vk::{model_buffer::ModelBuffer, text::text_atlas::TEXT_ATLAS_ISLAND_PADDING_PX, viewport::Viewport},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -9,19 +9,16 @@ use super::{
|
||||||
text_atlas::{GlyphVertex, TextAtlas, TextPipeline},
|
text_atlas::{GlyphVertex, TextAtlas, TextPipeline},
|
||||||
};
|
};
|
||||||
use cosmic_text::{Color, SubpixelBin, SwashContent};
|
use cosmic_text::{Color, SubpixelBin, SwashContent};
|
||||||
use etagere::size2;
|
use etagere::{AllocId, size2};
|
||||||
use glam::{Mat4, Vec2, Vec3};
|
use glam::{Mat4, Vec2, Vec3};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
buffer::{BufferUsage, Subbuffer},
|
buffer::{BufferUsage, Subbuffer},
|
||||||
command_buffer::CommandBufferUsage,
|
command_buffer::CommandBufferUsage,
|
||||||
pipeline::graphics,
|
pipeline::graphics,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CachedPass {
|
|
||||||
pass: WGfxPass<GlyphVertex>,
|
|
||||||
res: [u32; 2],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A text renderer that uses cached glyphs to render text into an existing render pass.
|
/// A text renderer that uses cached glyphs to render text into an existing render pass.
|
||||||
pub struct TextRenderer {
|
pub struct TextRenderer {
|
||||||
pipeline: TextPipeline,
|
pipeline: TextPipeline,
|
||||||
|
|
@ -29,7 +26,6 @@ pub struct TextRenderer {
|
||||||
vertex_buffer_capacity: usize,
|
vertex_buffer_capacity: usize,
|
||||||
glyph_vertices: Vec<GlyphVertex>,
|
glyph_vertices: Vec<GlyphVertex>,
|
||||||
model_buffer: ModelBuffer,
|
model_buffer: ModelBuffer,
|
||||||
pass: Option<CachedPass>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextRenderer {
|
impl TextRenderer {
|
||||||
|
|
@ -49,7 +45,6 @@ impl TextRenderer {
|
||||||
vertex_buffer,
|
vertex_buffer,
|
||||||
vertex_buffer_capacity: INITIAL_CAPACITY,
|
vertex_buffer_capacity: INITIAL_CAPACITY,
|
||||||
glyph_vertices: Vec::new(),
|
glyph_vertices: Vec::new(),
|
||||||
pass: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,6 +60,10 @@ impl TextRenderer {
|
||||||
self.glyph_vertices.clear();
|
self.glyph_vertices.clear();
|
||||||
|
|
||||||
let resolution = viewport.resolution();
|
let resolution = viewport.resolution();
|
||||||
|
let mut glyphs_to_render = Vec::new();
|
||||||
|
let mut pending_glyph_uploads = Vec::new();
|
||||||
|
let mut missing_glyphs = HashSet::new();
|
||||||
|
let mut unavailable_glyphs = HashSet::new();
|
||||||
|
|
||||||
for text_area in text_areas {
|
for text_area in text_areas {
|
||||||
let bounds_min_x = text_area.bounds.left.max(0);
|
let bounds_min_x = text_area.bounds.left.max(0);
|
||||||
|
|
@ -101,26 +100,14 @@ impl TextRenderer {
|
||||||
.or(glyph.color)
|
.or(glyph.color)
|
||||||
.unwrap_or(text_area.default_color);
|
.unwrap_or(text_area.default_color);
|
||||||
|
|
||||||
if let Some(glyph_to_render) = prepare_glyph(
|
if queue_missing_glyph_upload(
|
||||||
&mut PrepareGlyphParams {
|
atlas,
|
||||||
label_pos: Vec2::new(text_area.left, text_area.top),
|
font_system,
|
||||||
x,
|
cache,
|
||||||
y,
|
cache_key,
|
||||||
line_y: 0.0,
|
&mut missing_glyphs,
|
||||||
color,
|
&mut unavailable_glyphs,
|
||||||
cache_key,
|
&mut pending_glyph_uploads,
|
||||||
atlas,
|
|
||||||
cache,
|
|
||||||
font_system,
|
|
||||||
model_buffer: &mut self.model_buffer,
|
|
||||||
scale_factor: text_area.scale,
|
|
||||||
glyph_scale: f32::from(width) / f32::from(cached_width),
|
|
||||||
bounds_min_x,
|
|
||||||
bounds_min_y,
|
|
||||||
bounds_max_x,
|
|
||||||
bounds_max_y,
|
|
||||||
transform: &text_area.transform,
|
|
||||||
},
|
|
||||||
|_cache, _font_system| -> Option<GetGlyphImageResult> {
|
|_cache, _font_system| -> Option<GetGlyphImageResult> {
|
||||||
if cached_width == 0 || cached_height == 0 {
|
if cached_width == 0 || cached_height == 0 {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -148,8 +135,22 @@ impl TextRenderer {
|
||||||
data: output.data,
|
data: output.data,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)? {
|
) {
|
||||||
self.glyph_vertices.push(glyph_to_render);
|
glyphs_to_render.push(QueuedGlyph {
|
||||||
|
label_pos: Vec2::new(text_area.left, text_area.top),
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
line_y: 0.0,
|
||||||
|
color,
|
||||||
|
cache_key,
|
||||||
|
transform: text_area.transform,
|
||||||
|
scale_factor: text_area.scale,
|
||||||
|
glyph_scale: f32::from(width) / f32::from(cached_width),
|
||||||
|
bounds_min_x,
|
||||||
|
bounds_min_y,
|
||||||
|
bounds_max_x,
|
||||||
|
bounds_max_y,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,26 +177,16 @@ impl TextRenderer {
|
||||||
.or(glyph.color_opt)
|
.or(glyph.color_opt)
|
||||||
.unwrap_or(text_area.default_color);
|
.unwrap_or(text_area.default_color);
|
||||||
|
|
||||||
if let Some(glyph_to_render) = prepare_glyph(
|
let cache_key = GlyphonCacheKey::Text(physical_glyph.cache_key);
|
||||||
&mut PrepareGlyphParams {
|
|
||||||
label_pos: Vec2::new(text_area.left, text_area.top),
|
if queue_missing_glyph_upload(
|
||||||
x: physical_glyph.x,
|
atlas,
|
||||||
y: physical_glyph.y,
|
font_system,
|
||||||
line_y: run.line_y,
|
cache,
|
||||||
color,
|
cache_key,
|
||||||
cache_key: GlyphonCacheKey::Text(physical_glyph.cache_key),
|
&mut missing_glyphs,
|
||||||
atlas,
|
&mut unavailable_glyphs,
|
||||||
cache,
|
&mut pending_glyph_uploads,
|
||||||
font_system,
|
|
||||||
model_buffer: &mut self.model_buffer,
|
|
||||||
glyph_scale: 1.0,
|
|
||||||
scale_factor: text_area.scale,
|
|
||||||
bounds_min_x,
|
|
||||||
bounds_min_y,
|
|
||||||
bounds_max_x,
|
|
||||||
bounds_max_y,
|
|
||||||
transform: &text_area.transform,
|
|
||||||
},
|
|
||||||
|cache, font_system| -> Option<GetGlyphImageResult> {
|
|cache, font_system| -> Option<GetGlyphImageResult> {
|
||||||
let image = cache.get_image_uncached(font_system, physical_glyph.cache_key)?;
|
let image = cache.get_image_uncached(font_system, physical_glyph.cache_key)?;
|
||||||
|
|
||||||
|
|
@ -217,13 +208,39 @@ impl TextRenderer {
|
||||||
data: image.data,
|
data: image.data,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)? {
|
) {
|
||||||
self.glyph_vertices.push(glyph_to_render);
|
glyphs_to_render.push(QueuedGlyph {
|
||||||
|
label_pos: Vec2::new(text_area.left, text_area.top),
|
||||||
|
x: physical_glyph.x,
|
||||||
|
y: physical_glyph.y,
|
||||||
|
line_y: run.line_y,
|
||||||
|
color,
|
||||||
|
cache_key,
|
||||||
|
transform: text_area.transform,
|
||||||
|
glyph_scale: 1.0,
|
||||||
|
scale_factor: text_area.scale,
|
||||||
|
bounds_min_x,
|
||||||
|
bounds_min_y,
|
||||||
|
bounds_max_x,
|
||||||
|
bounds_max_y,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upload_missing_glyphs(atlas, font_system, cache, pending_glyph_uploads)?;
|
||||||
|
|
||||||
|
for glyph in &glyphs_to_render {
|
||||||
|
if let Some(glyph_to_render) = prepare_glyph(&mut PrepareGlyphParams {
|
||||||
|
glyph,
|
||||||
|
atlas,
|
||||||
|
model_buffer: &mut self.model_buffer,
|
||||||
|
}) {
|
||||||
|
self.glyph_vertices.push(glyph_to_render);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let will_render = !self.glyph_vertices.is_empty();
|
let will_render = !self.glyph_vertices.is_empty();
|
||||||
if !will_render {
|
if !will_render {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -259,31 +276,24 @@ impl TextRenderer {
|
||||||
let res = viewport.resolution();
|
let res = viewport.resolution();
|
||||||
self.model_buffer.upload(&atlas.common.gfx)?;
|
self.model_buffer.upload(&atlas.common.gfx)?;
|
||||||
|
|
||||||
let cache = match self.pass.take() {
|
let descriptor_sets = vec![
|
||||||
Some(p) if p.res == res => p,
|
atlas.color_atlas.image_descriptor.clone(),
|
||||||
_ => {
|
atlas.mask_atlas.image_descriptor.clone(),
|
||||||
let descriptor_sets = vec![
|
viewport.get_text_descriptor(&self.pipeline),
|
||||||
atlas.color_atlas.image_descriptor.clone(),
|
self.model_buffer.get_text_descriptor(&self.pipeline),
|
||||||
atlas.mask_atlas.image_descriptor.clone(),
|
];
|
||||||
viewport.get_text_descriptor(&self.pipeline),
|
|
||||||
self.model_buffer.get_text_descriptor(&self.pipeline),
|
|
||||||
];
|
|
||||||
|
|
||||||
let pass = self.pipeline.inner.create_pass(
|
let pass = self.pipeline.inner.create_pass(
|
||||||
[res[0] as _, res[1] as _],
|
[res[0] as _, res[1] as _],
|
||||||
[0.0, 0.0],
|
[0.0, 0.0],
|
||||||
self.vertex_buffer.clone(),
|
self.vertex_buffer.clone(),
|
||||||
0..4,
|
0..4,
|
||||||
0..self.glyph_vertices.len() as u32,
|
0..self.glyph_vertices.len() as u32,
|
||||||
descriptor_sets,
|
descriptor_sets,
|
||||||
vk_scissor,
|
vk_scissor,
|
||||||
)?;
|
)?;
|
||||||
CachedPass { pass, res }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cmd_buf.run_ref(&cache.pass)?;
|
cmd_buf.run_ref(&pass)?;
|
||||||
self.pass = Some(cache);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -303,18 +313,28 @@ struct GetGlyphImageResult {
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PrepareGlyphParams<'a> {
|
struct PendingGlyphUpload {
|
||||||
|
cache_key: GlyphonCacheKey,
|
||||||
|
image: GetGlyphImageResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AtlasGlyphUpload {
|
||||||
|
upload_index: usize,
|
||||||
|
atlas_id: AllocId,
|
||||||
|
atlas_with_island_min: [u32; 2],
|
||||||
|
size_with_island: [u32; 2],
|
||||||
|
size_with_island_area: usize,
|
||||||
|
atlas_glyph_min: [u32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QueuedGlyph {
|
||||||
label_pos: Vec2,
|
label_pos: Vec2,
|
||||||
x: i32,
|
x: i32,
|
||||||
y: i32,
|
y: i32,
|
||||||
line_y: f32,
|
line_y: f32,
|
||||||
color: Color,
|
color: Color,
|
||||||
cache_key: GlyphonCacheKey,
|
cache_key: GlyphonCacheKey,
|
||||||
atlas: &'a mut TextAtlas,
|
transform: Mat4,
|
||||||
cache: &'a mut SwashCache,
|
|
||||||
font_system: &'a mut FontSystem,
|
|
||||||
model_buffer: &'a mut ModelBuffer,
|
|
||||||
transform: &'a Mat4,
|
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
glyph_scale: f32,
|
glyph_scale: f32,
|
||||||
bounds_min_x: i32,
|
bounds_min_x: i32,
|
||||||
|
|
@ -323,102 +343,207 @@ struct PrepareGlyphParams<'a> {
|
||||||
bounds_max_y: i32,
|
bounds_max_y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_glyph(
|
struct PrepareGlyphParams<'a> {
|
||||||
par: &mut PrepareGlyphParams,
|
glyph: &'a QueuedGlyph,
|
||||||
|
atlas: &'a mut TextAtlas,
|
||||||
|
model_buffer: &'a mut ModelBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_missing_glyph_upload(
|
||||||
|
atlas: &mut TextAtlas,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
cache: &mut SwashCache,
|
||||||
|
cache_key: GlyphonCacheKey,
|
||||||
|
missing_glyphs: &mut HashSet<GlyphonCacheKey>,
|
||||||
|
unavailable_glyphs: &mut HashSet<GlyphonCacheKey>,
|
||||||
|
pending_glyph_uploads: &mut Vec<PendingGlyphUpload>,
|
||||||
get_glyph_image: impl FnOnce(&mut SwashCache, &mut FontSystem) -> Option<GetGlyphImageResult>,
|
get_glyph_image: impl FnOnce(&mut SwashCache, &mut FontSystem) -> Option<GetGlyphImageResult>,
|
||||||
) -> anyhow::Result<Option<GlyphVertex>> {
|
) -> bool {
|
||||||
let gfx = par.atlas.common.gfx.clone();
|
if mark_glyph_in_use_if_cached(atlas, cache_key) {
|
||||||
let details = if let Some(details) = par.atlas.mask_atlas.glyph_cache.get(&par.cache_key) {
|
return true;
|
||||||
par.atlas.mask_atlas.glyphs_in_use.insert(par.cache_key);
|
}
|
||||||
details
|
|
||||||
} else if let Some(details) = par.atlas.color_atlas.glyph_cache.get(&par.cache_key) {
|
if unavailable_glyphs.contains(&cache_key) {
|
||||||
par.atlas.color_atlas.glyphs_in_use.insert(par.cache_key);
|
return false;
|
||||||
details
|
}
|
||||||
} else {
|
|
||||||
let Some(image) = (get_glyph_image)(par.cache, par.font_system) else {
|
if missing_glyphs.insert(cache_key) {
|
||||||
return Ok(None);
|
let Some(image) = get_glyph_image(cache, font_system) else {
|
||||||
|
unavailable_glyphs.insert(cache_key);
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let should_rasterize = image.width > 0 && image.height > 0;
|
pending_glyph_uploads.push(PendingGlyphUpload { cache_key, image });
|
||||||
|
}
|
||||||
|
|
||||||
let (gpu_cache, atlas_id, inner) = if should_rasterize {
|
true
|
||||||
let mut inner = par.atlas.inner_for_content_mut(image.content_type);
|
}
|
||||||
|
|
||||||
// Find a position in the packer
|
fn mark_glyph_in_use_if_cached(atlas: &mut TextAtlas, cache_key: GlyphonCacheKey) -> bool {
|
||||||
let allocation = loop {
|
if atlas.mask_atlas.glyph_cache.get(&cache_key).is_some() {
|
||||||
if let Some(a) = inner.try_allocate(image.width as usize, image.height as usize) {
|
atlas.mask_atlas.glyphs_in_use.insert(cache_key);
|
||||||
break a;
|
true
|
||||||
}
|
} else if atlas.color_atlas.glyph_cache.get(&cache_key).is_some() {
|
||||||
if !par.atlas.grow(par.font_system, par.cache, image.content_type)? {
|
atlas.color_atlas.glyphs_in_use.insert(cache_key);
|
||||||
anyhow::bail!(
|
true
|
||||||
"Atlas full. atlas: {:?} cache_key: {:?}",
|
} else {
|
||||||
image.content_type,
|
false
|
||||||
par.cache_key
|
}
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
inner = par.atlas.inner_for_content_mut(image.content_type);
|
fn upload_missing_glyphs(
|
||||||
};
|
atlas: &mut TextAtlas,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
cache: &mut SwashCache,
|
||||||
|
pending_glyph_uploads: Vec<PendingGlyphUpload>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if pending_glyph_uploads.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let atlas_with_island_min = allocation.rectangle.min;
|
let gfx = atlas.common.gfx.clone();
|
||||||
let size_with_island = allocation.rectangle.size();
|
let mut rasterized_uploads = Vec::new();
|
||||||
let atlas_glyph_min =
|
let mut skipped_uploads = Vec::new();
|
||||||
allocation.rectangle.min + size2(TEXT_ATLAS_ISLAND_PADDING_PX as i32, TEXT_ATLAS_ISLAND_PADDING_PX as i32);
|
|
||||||
|
|
||||||
let mut cmd_buf = gfx.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
for upload in pending_glyph_uploads {
|
||||||
|
if upload.image.width > 0 && upload.image.height > 0 {
|
||||||
|
rasterized_uploads.push(upload);
|
||||||
|
} else {
|
||||||
|
skipped_uploads.push(upload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set data to zeros for the whole glyph island
|
let mut atlas_uploads = Vec::new();
|
||||||
// TODO: use `vkCmdClearColorImage` with an image subresource (or xywh region?) to omit unnecessary allocation
|
|
||||||
let zero_bytes_data: Vec<u8> = vec![0x00; size_with_island.area() as usize * 4 /* RGBX */];
|
if !rasterized_uploads.is_empty() {
|
||||||
|
'allocate_all: loop {
|
||||||
|
atlas_uploads.clear();
|
||||||
|
|
||||||
|
for (upload_index, upload) in rasterized_uploads.iter().enumerate() {
|
||||||
|
let content_type = upload.image.content_type;
|
||||||
|
|
||||||
|
let allocation = loop {
|
||||||
|
if let Some(allocation) = {
|
||||||
|
let inner = atlas.inner_for_content_mut(content_type);
|
||||||
|
inner.try_allocate(upload.image.width as usize, upload.image.height as usize)
|
||||||
|
} {
|
||||||
|
break allocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !atlas.grow(font_system, cache, content_type)? {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Atlas full. atlas: {:?} cache_key: {:?}",
|
||||||
|
content_type,
|
||||||
|
upload.cache_key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `grow` can rebuild the atlas allocator and move existing glyphs. Any
|
||||||
|
// allocations made for this batch before the grow are provisional, so
|
||||||
|
// discard them and recompute the batch offsets against the new atlas.
|
||||||
|
continue 'allocate_all;
|
||||||
|
};
|
||||||
|
|
||||||
|
let atlas_with_island_min = allocation.rectangle.min;
|
||||||
|
let size_with_island = allocation.rectangle.size();
|
||||||
|
let atlas_glyph_min =
|
||||||
|
allocation.rectangle.min + size2(TEXT_ATLAS_ISLAND_PADDING_PX as i32, TEXT_ATLAS_ISLAND_PADDING_PX as i32);
|
||||||
|
|
||||||
|
atlas_uploads.push(AtlasGlyphUpload {
|
||||||
|
upload_index,
|
||||||
|
atlas_id: allocation.id,
|
||||||
|
atlas_with_island_min: [atlas_with_island_min.x as u32, atlas_with_island_min.y as u32],
|
||||||
|
size_with_island: [size_with_island.width as u32, size_with_island.height as u32],
|
||||||
|
size_with_island_area: size_with_island.area() as usize,
|
||||||
|
atlas_glyph_min: [atlas_glyph_min.x as u32, atlas_glyph_min.y as u32],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cmd_buf = gfx.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||||
|
|
||||||
|
for upload in &atlas_uploads {
|
||||||
|
let rasterized = &rasterized_uploads[upload.upload_index];
|
||||||
|
let inner = atlas.inner_for_content_mut(rasterized.image.content_type);
|
||||||
|
|
||||||
|
// Set data to zeros for the whole glyph island.
|
||||||
|
// TODO: use `vkCmdClearColorImage` with an image subresource (or xywh region?) to omit unnecessary allocation.
|
||||||
|
let zero_bytes_data: Vec<u8> = vec![0x00; upload.size_with_island_area * 4 /* RGBX */];
|
||||||
cmd_buf.update_image(
|
cmd_buf.update_image(
|
||||||
inner.image_view.image(),
|
inner.image_view.image(),
|
||||||
&zero_bytes_data,
|
&zero_bytes_data,
|
||||||
[atlas_with_island_min.x as u32, atlas_with_island_min.y as u32, 0],
|
[upload.atlas_with_island_min[0], upload.atlas_with_island_min[1], 0],
|
||||||
Some([size_with_island.width as u32, size_with_island.height as u32, 1]),
|
Some([upload.size_with_island[0], upload.size_with_island[1], 1]),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Upload glyph itself
|
// Upload glyph itself.
|
||||||
cmd_buf.update_image(
|
cmd_buf.update_image(
|
||||||
inner.image_view.image(),
|
inner.image_view.image(),
|
||||||
&image.data,
|
&rasterized.image.data,
|
||||||
[atlas_glyph_min.x as u32, atlas_glyph_min.y as u32, 0],
|
[upload.atlas_glyph_min[0], upload.atlas_glyph_min[1], 0],
|
||||||
Some([image.width.into(), image.height.into(), 1]),
|
Some([rasterized.image.width.into(), rasterized.image.height.into(), 1]),
|
||||||
)?;
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
cmd_buf.build_and_execute_now()?; //TODO: do not wait for fence here
|
cmd_buf.build_and_execute_now()?;
|
||||||
|
}
|
||||||
|
|
||||||
(
|
for upload in atlas_uploads {
|
||||||
GpuCacheStatus::InAtlas {
|
let rasterized = &rasterized_uploads[upload.upload_index];
|
||||||
x: atlas_glyph_min.x as u16,
|
let inner = atlas.inner_for_content_mut(rasterized.image.content_type);
|
||||||
y: atlas_glyph_min.y as u16,
|
|
||||||
content_type: image.content_type,
|
|
||||||
},
|
|
||||||
Some(allocation.id),
|
|
||||||
inner,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let inner = &mut par.atlas.color_atlas;
|
|
||||||
(GpuCacheStatus::SkipRasterization, None, inner)
|
|
||||||
};
|
|
||||||
|
|
||||||
inner.glyphs_in_use.insert(par.cache_key);
|
inner.glyphs_in_use.insert(rasterized.cache_key);
|
||||||
// Insert the glyph into the cache and return the details reference
|
let _ = inner.glyph_cache.get_or_insert(rasterized.cache_key, || GlyphDetails {
|
||||||
inner.glyph_cache.get_or_insert(par.cache_key, || GlyphDetails {
|
width: rasterized.image.width,
|
||||||
width: image.width,
|
height: rasterized.image.height,
|
||||||
height: image.height,
|
gpu_cache: GpuCacheStatus::InAtlas {
|
||||||
gpu_cache,
|
x: upload.atlas_glyph_min[0] as u16,
|
||||||
atlas_id,
|
y: upload.atlas_glyph_min[1] as u16,
|
||||||
top: image.top,
|
content_type: rasterized.image.content_type,
|
||||||
left: image.left,
|
},
|
||||||
})
|
atlas_id: Some(upload.atlas_id),
|
||||||
|
top: rasterized.image.top,
|
||||||
|
left: rasterized.image.left,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for upload in skipped_uploads {
|
||||||
|
let inner = &mut atlas.color_atlas;
|
||||||
|
|
||||||
|
inner.glyphs_in_use.insert(upload.cache_key);
|
||||||
|
let _ = inner.glyph_cache.get_or_insert(upload.cache_key, || GlyphDetails {
|
||||||
|
width: upload.image.width,
|
||||||
|
height: upload.image.height,
|
||||||
|
gpu_cache: GpuCacheStatus::SkipRasterization,
|
||||||
|
atlas_id: None,
|
||||||
|
top: upload.image.top,
|
||||||
|
left: upload.image.left,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_glyph(par: &mut PrepareGlyphParams) -> Option<GlyphVertex> {
|
||||||
|
let glyph = par.glyph;
|
||||||
|
let details = if let Some(details) = par.atlas.mask_atlas.glyph_cache.get(&glyph.cache_key) {
|
||||||
|
par.atlas.mask_atlas.glyphs_in_use.insert(glyph.cache_key);
|
||||||
|
details
|
||||||
|
} else if let Some(details) = par.atlas.color_atlas.glyph_cache.get(&glyph.cache_key) {
|
||||||
|
par.atlas.color_atlas.glyphs_in_use.insert(glyph.cache_key);
|
||||||
|
details
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut x = par.x + i32::from(details.left);
|
let mut x = glyph.x + i32::from(details.left);
|
||||||
let mut y = (par.line_y * par.scale_factor).round() as i32 + par.y - i32::from(details.top);
|
let mut y = (glyph.line_y * glyph.scale_factor).round() as i32 + glyph.y - i32::from(details.top);
|
||||||
|
|
||||||
let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache {
|
let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache {
|
||||||
GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),
|
GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),
|
||||||
GpuCacheStatus::SkipRasterization => return Ok(None),
|
GpuCacheStatus::SkipRasterization => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut glyph_width = i32::from(details.width);
|
let mut glyph_width = i32::from(details.width);
|
||||||
|
|
@ -426,79 +551,79 @@ fn prepare_glyph(
|
||||||
|
|
||||||
// Starts beyond right edge or ends beyond left edge
|
// Starts beyond right edge or ends beyond left edge
|
||||||
let max_x = x + glyph_width;
|
let max_x = x + glyph_width;
|
||||||
if x > par.bounds_max_x || max_x < par.bounds_min_x {
|
if x > glyph.bounds_max_x || max_x < glyph.bounds_min_x {
|
||||||
return Ok(None);
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts beyond bottom edge or ends beyond top edge
|
// Starts beyond bottom edge or ends beyond top edge
|
||||||
let max_y = y + glyph_height;
|
let max_y = y + glyph_height;
|
||||||
if y > par.bounds_max_y || max_y < par.bounds_min_y {
|
if y > glyph.bounds_max_y || max_y < glyph.bounds_min_y {
|
||||||
return Ok(None);
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clip left edge
|
// Clip left edge
|
||||||
if x < par.bounds_min_x {
|
if x < glyph.bounds_min_x {
|
||||||
let right_shift = par.bounds_min_x - x;
|
let right_shift = glyph.bounds_min_x - x;
|
||||||
|
|
||||||
x = par.bounds_min_x;
|
x = glyph.bounds_min_x;
|
||||||
glyph_width = max_x - par.bounds_min_x;
|
glyph_width = max_x - glyph.bounds_min_x;
|
||||||
atlas_x += right_shift as u16;
|
atlas_x += right_shift as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clip right edge
|
// Clip right edge
|
||||||
if x + glyph_width > par.bounds_max_x {
|
if x + glyph_width > glyph.bounds_max_x {
|
||||||
glyph_width = par.bounds_max_x - x;
|
glyph_width = glyph.bounds_max_x - x;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clip top edge
|
// Clip top edge
|
||||||
if y < par.bounds_min_y {
|
if y < glyph.bounds_min_y {
|
||||||
let bottom_shift = par.bounds_min_y - y;
|
let bottom_shift = glyph.bounds_min_y - y;
|
||||||
|
|
||||||
y = par.bounds_min_y;
|
y = glyph.bounds_min_y;
|
||||||
glyph_height = max_y - par.bounds_min_y;
|
glyph_height = max_y - glyph.bounds_min_y;
|
||||||
atlas_y += bottom_shift as u16;
|
atlas_y += bottom_shift as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clip bottom edge
|
// Clip bottom edge
|
||||||
if y + glyph_height > par.bounds_max_y {
|
if y + glyph_height > glyph.bounds_max_y {
|
||||||
glyph_height = par.bounds_max_y - y;
|
glyph_height = glyph.bounds_max_y - y;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut model = Mat4::IDENTITY;
|
let mut model = Mat4::IDENTITY;
|
||||||
|
|
||||||
// top-left text transform
|
// top-left text transform
|
||||||
model *= Mat4::from_translation(Vec3::new(
|
model *= Mat4::from_translation(Vec3::new(
|
||||||
par.label_pos.x / par.scale_factor,
|
glyph.label_pos.x / glyph.scale_factor,
|
||||||
par.label_pos.y / par.scale_factor,
|
glyph.label_pos.y / glyph.scale_factor,
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
|
|
||||||
model *= *par.transform;
|
model *= glyph.transform;
|
||||||
|
|
||||||
// per-character transform
|
// per-character transform
|
||||||
model *= Mat4::from_translation(Vec3::new(
|
model *= Mat4::from_translation(Vec3::new(
|
||||||
((x as f32) - par.label_pos.x) / par.scale_factor,
|
((x as f32) - glyph.label_pos.x) / glyph.scale_factor,
|
||||||
((y as f32) - par.label_pos.y) / par.scale_factor,
|
((y as f32) - glyph.label_pos.y) / glyph.scale_factor,
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
|
|
||||||
model *= glam::Mat4::from_scale(Vec3::new(
|
model *= glam::Mat4::from_scale(Vec3::new(
|
||||||
glyph_width as f32 / par.scale_factor,
|
glyph_width as f32 / glyph.scale_factor,
|
||||||
glyph_height as f32 / par.scale_factor,
|
glyph_height as f32 / glyph.scale_factor,
|
||||||
0.0,
|
0.0,
|
||||||
));
|
));
|
||||||
|
|
||||||
let in_model_idx = par.model_buffer.register(&model);
|
let in_model_idx = par.model_buffer.register(&model);
|
||||||
|
|
||||||
Ok(Some(GlyphVertex {
|
Some(GlyphVertex {
|
||||||
in_model_idx,
|
in_model_idx,
|
||||||
in_rect_dim: [glyph_width as u16, glyph_height as u16],
|
in_rect_dim: [glyph_width as u16, glyph_height as u16],
|
||||||
in_uv: [atlas_x, atlas_y],
|
in_uv: [atlas_x, atlas_y],
|
||||||
in_color: par.color.0,
|
in_color: glyph.color.0,
|
||||||
in_content_type: [
|
in_content_type: [
|
||||||
content_type as u16,
|
content_type as u16,
|
||||||
0, // unused (TODO!)
|
0, // unused (TODO!)
|
||||||
],
|
],
|
||||||
scale: par.glyph_scale,
|
scale: glyph.glyph_scale,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ pub struct WidgetImage {
|
||||||
params: WidgetImageParams,
|
params: WidgetImageParams,
|
||||||
id: WidgetID,
|
id: WidgetID,
|
||||||
content_key: usize,
|
content_key: usize,
|
||||||
|
dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetImage {
|
impl WidgetImage {
|
||||||
|
|
@ -40,6 +41,7 @@ impl WidgetImage {
|
||||||
params,
|
params,
|
||||||
id: WidgetID::null(),
|
id: WidgetID::null(),
|
||||||
content_key: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed),
|
content_key: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed),
|
||||||
|
dirty: true,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +52,7 @@ impl WidgetImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.params.glyph_data = content;
|
self.params.glyph_data = content;
|
||||||
|
self.dirty = true;
|
||||||
alterables.mark_dirty_and_redraw(self.id);
|
alterables.mark_dirty_and_redraw(self.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,11 +82,13 @@ impl WidgetObj for WidgetImage {
|
||||||
ImagePrimitive {
|
ImagePrimitive {
|
||||||
content,
|
content,
|
||||||
content_key: self.content_key,
|
content_key: self.content_key,
|
||||||
|
skip_cache: self.dirty,
|
||||||
border: self.params.border,
|
border: self.params.border,
|
||||||
border_color: self.params.border_color,
|
border_color: self.params.border_color,
|
||||||
round_units,
|
round_units,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
self.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn measure(
|
fn measure(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue