mirror of https://github.com/wayvr-org/wayvr.git
image upload batching (wip)
This commit is contained in:
parent
73d42a0981
commit
ed10cd8ee6
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::tab::settings::{
|
use crate::tab::settings::{
|
||||||
macros::{options_category, options_checkbox, options_range_f32, options_slider_f32},
|
|
||||||
SettingType, SettingsMountParams, SettingsTab,
|
SettingType, SettingsMountParams, SettingsTab,
|
||||||
|
macros::{options_category, options_checkbox, options_range_f32, options_slider_f32},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct State {}
|
pub struct State {}
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,22 @@ use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use cosmic_text::Buffer;
|
use cosmic_text::Buffer;
|
||||||
use glam::{Mat4, Vec2, Vec3};
|
use glam::{Mat4, Vec2, Vec3};
|
||||||
use slotmap::{new_key_type, SlotMap};
|
use slotmap::{SlotMap, new_key_type};
|
||||||
use vulkano::pipeline::graphics::viewport;
|
use vulkano::pipeline::graphics::viewport;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
drawing::{self},
|
drawing::{self},
|
||||||
font_config,
|
font_config,
|
||||||
gfx::{cmd::GfxCommandBuffer, WGfx},
|
gfx::{WGfx, cmd::GfxCommandBuffer},
|
||||||
renderer_vk::image::{ImagePipeline, ImageRenderer, ImageViewCache},
|
renderer_vk::image::{ImagePipeline, ImageRenderer, ImageViewCache},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
rect::{RectPipeline, RectRenderer},
|
rect::{RectPipeline, RectRenderer},
|
||||||
text::{
|
text::{
|
||||||
|
DEFAULT_METRICS, SWASH_CACHE, TextArea, TextBounds,
|
||||||
text_atlas::{TextAtlas, TextPipeline},
|
text_atlas::{TextAtlas, TextPipeline},
|
||||||
text_renderer::TextRenderer,
|
text_renderer::TextRenderer,
|
||||||
TextArea, TextBounds, DEFAULT_METRICS, SWASH_CACHE,
|
|
||||||
},
|
},
|
||||||
viewport::Viewport,
|
viewport::Viewport,
|
||||||
};
|
};
|
||||||
|
|
@ -247,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(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
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;
|
||||||
|
|
@ -14,13 +17,13 @@ use vulkano::{
|
||||||
use crate::{
|
use crate::{
|
||||||
drawing::{Boundary, ImagePrimitive},
|
drawing::{Boundary, ImagePrimitive},
|
||||||
gfx::{
|
gfx::{
|
||||||
|
BLEND_ALPHA, WGfx,
|
||||||
cmd::GfxCommandBuffer,
|
cmd::GfxCommandBuffer,
|
||||||
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
||||||
WGfx, BLEND_ALPHA,
|
|
||||||
},
|
},
|
||||||
renderer_vk::{
|
renderer_vk::{
|
||||||
model_buffer::ModelBuffer,
|
model_buffer::ModelBuffer,
|
||||||
text::custom_glyph::{CustomGlyphData, RasterizeCustomGlyphRequest, RasterizedCustomGlyph},
|
text::custom_glyph::{CustomGlyphContent, CustomGlyphData, RasterizeCustomGlyphRequest, RasterizedCustomGlyph},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -67,6 +70,7 @@ impl ImagePipeline {
|
||||||
pub type ImageViewCache = HashMap<usize, CachedImageView>;
|
pub type ImageViewCache = HashMap<usize, CachedImageView>;
|
||||||
|
|
||||||
pub struct CachedImageView {
|
pub struct CachedImageView {
|
||||||
|
pub(super) content: Weak<CustomGlyphContent>,
|
||||||
view: Arc<ImageView>,
|
view: Arc<ImageView>,
|
||||||
res: [u32; 2],
|
res: [u32; 2],
|
||||||
}
|
}
|
||||||
|
|
@ -74,10 +78,21 @@ pub struct CachedImageView {
|
||||||
struct ImageVertexWithContent {
|
struct ImageVertexWithContent {
|
||||||
vert: ImageVertex,
|
vert: ImageVertex,
|
||||||
content: CustomGlyphData,
|
content: CustomGlyphData,
|
||||||
content_key: usize, // identifies an image tag.
|
|
||||||
skip_cache: bool,
|
skip_cache: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PendingImageUpload {
|
||||||
|
content_id: usize,
|
||||||
|
content: Weak<CustomGlyphContent>,
|
||||||
|
raster: RasterizedCustomGlyph,
|
||||||
|
}
|
||||||
|
|
||||||
|
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>,
|
||||||
|
|
@ -111,16 +126,11 @@ impl ImageRenderer {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
content: image.content,
|
content: image.content,
|
||||||
content_key: image.content_key,
|
|
||||||
skip_cache: image.skip_cache,
|
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 _,
|
||||||
|
|
@ -130,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(
|
||||||
|
|
@ -158,26 +157,90 @@ impl ImageRenderer {
|
||||||
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 image_view = if let Some(x) = image_view_cache.get_mut(&img.content_key) {
|
if let Some(upload_idx) = pending_upload_by_key.get(&img.content.id) {
|
||||||
if img.skip_cache || 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.view = image_view;
|
if let Some(cached) = image_view_cache.get(&img.content.id)
|
||||||
x.res = res;
|
&& !img.skip_cache
|
||||||
}
|
&& cached.res == res
|
||||||
|
{
|
||||||
|
image_sources.push(ImageViewSource::Ready(cached.view.clone()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
x.view.clone()
|
let Some(raster) = Self::rasterize_image(res, img) else {
|
||||||
} else {
|
image_sources.push(ImageViewSource::Missing);
|
||||||
let Some(image_view) = Self::upload_image(&self.pipeline.gfx, res, img)? else {
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
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)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
image_view_cache.insert(img.content_key, CachedImageView { view: image_view, res });
|
image_view_cache.insert(
|
||||||
image_view_cache.get_mut(&img.content_key).unwrap().view.clone()
|
upload.content_id,
|
||||||
|
CachedImageView {
|
||||||
|
content: upload.content.clone(),
|
||||||
|
view: image_view.clone(),
|
||||||
|
res,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
let vert_buffer = self.pipeline.gfx.empty_buffer(
|
let vert_buffer = self.pipeline.gfx.empty_buffer(
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ use vulkano::{
|
||||||
use crate::{
|
use crate::{
|
||||||
drawing::{Boundary, Rectangle},
|
drawing::{Boundary, Rectangle},
|
||||||
gfx::{
|
gfx::{
|
||||||
|
BLEND_ALPHA, WGfx,
|
||||||
cmd::GfxCommandBuffer,
|
cmd::GfxCommandBuffer,
|
||||||
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
||||||
WGfx, BLEND_ALPHA,
|
|
||||||
},
|
},
|
||||||
renderer_vk::model_buffer::ModelBuffer,
|
renderer_vk::model_buffer::ModelBuffer,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
ContentType, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, TextArea,
|
||||||
custom_glyph::{CustomGlyphCacheKey, RasterizeCustomGlyphRequest, RasterizedCustomGlyph},
|
custom_glyph::{CustomGlyphCacheKey, RasterizeCustomGlyphRequest, RasterizedCustomGlyph},
|
||||||
text_atlas::{GlyphVertex, TextAtlas, TextPipeline},
|
text_atlas::{GlyphVertex, TextAtlas, TextPipeline},
|
||||||
ContentType, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, TextArea,
|
|
||||||
};
|
};
|
||||||
use cosmic_text::{Color, SubpixelBin, SwashContent};
|
use cosmic_text::{Color, SubpixelBin, SwashContent};
|
||||||
use etagere::{size2, AllocId};
|
use etagere::{AllocId, size2};
|
||||||
use glam::{Mat4, Vec2, Vec3};
|
use glam::{Mat4, Vec2, Vec3};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
globals::Globals,
|
globals::Globals,
|
||||||
layout::WidgetID,
|
layout::WidgetID,
|
||||||
renderer_vk::text::custom_glyph::CustomGlyphData,
|
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||||
widget::{util::WLength, WidgetStateFlags},
|
widget::{WidgetStateFlags, util::WLength},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{WidgetObj, WidgetState};
|
use super::{WidgetObj, WidgetState};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue