Images!
This commit is contained in:
@@ -6,7 +6,9 @@ use cosmic_text::{
|
||||
Attrs, Buffer, CacheKey, FontSystem, Metrics, Shaping, SwashCache, SwashContent, SwashImage,
|
||||
};
|
||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
use ruin_ui::{Color, DisplayItem, Point, PreparedText, Rect, SceneSnapshot, UiSize};
|
||||
use ruin_ui::{
|
||||
Color, DisplayItem, Point, PreparedImage, PreparedText, Rect, SceneSnapshot, UiSize,
|
||||
};
|
||||
use tracing::trace;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
@@ -70,6 +72,11 @@ struct CachedTextTexture {
|
||||
size: UiSize,
|
||||
}
|
||||
|
||||
struct CachedImageTexture {
|
||||
_texture: wgpu::Texture,
|
||||
bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
struct GlyphAtlas {
|
||||
texture: wgpu::Texture,
|
||||
bind_group: wgpu::BindGroup,
|
||||
@@ -106,6 +113,12 @@ struct UploadedText {
|
||||
vertex_count: u32,
|
||||
}
|
||||
|
||||
struct UploadedImage {
|
||||
bind_group: wgpu::BindGroup,
|
||||
vertex_buffer: wgpu::Buffer,
|
||||
vertex_count: u32,
|
||||
}
|
||||
|
||||
struct UploadedAtlasText {
|
||||
vertex_buffer: wgpu::Buffer,
|
||||
vertex_count: u32,
|
||||
@@ -181,10 +194,13 @@ pub struct WgpuSceneRenderer {
|
||||
swash_cache: SwashCache,
|
||||
text_cache: HashMap<TextTextureKey, CachedTextTexture>,
|
||||
text_cache_order: VecDeque<TextTextureKey>,
|
||||
image_cache: HashMap<u64, CachedImageTexture>,
|
||||
image_cache_order: VecDeque<u64>,
|
||||
glyph_atlas: GlyphAtlas,
|
||||
}
|
||||
|
||||
const MAX_TEXT_CACHE_ENTRIES: usize = 64;
|
||||
const MAX_IMAGE_CACHE_ENTRIES: usize = 64;
|
||||
const GLYPH_ATLAS_SIZE: u32 = 2048;
|
||||
const GLYPH_ATLAS_PADDING: u32 = 1;
|
||||
|
||||
@@ -397,6 +413,8 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
|
||||
swash_cache: SwashCache::new(),
|
||||
text_cache: HashMap::new(),
|
||||
text_cache_order: VecDeque::new(),
|
||||
image_cache: HashMap::new(),
|
||||
image_cache_order: VecDeque::new(),
|
||||
glyph_atlas,
|
||||
})
|
||||
}
|
||||
@@ -434,6 +452,14 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let text_prepare_start = std::time::Instant::now();
|
||||
let uploaded_images: Vec<_> = scene
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
DisplayItem::Image(image) => self.prepare_uploaded_image(image, scene.logical_size),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let uploaded_atlas_text = self.prepare_uploaded_atlas_text(scene);
|
||||
let uploaded_texts: Vec<_> = scene
|
||||
.items
|
||||
@@ -480,6 +506,14 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
|
||||
pass.set_vertex_buffer(0, vertex_buffer.slice(..));
|
||||
pass.draw(0..vertices.len() as u32, 0..1);
|
||||
}
|
||||
if !uploaded_images.is_empty() {
|
||||
pass.set_pipeline(&self.text_pipeline);
|
||||
for image in &uploaded_images {
|
||||
pass.set_bind_group(0, &image.bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, image.vertex_buffer.slice(..));
|
||||
pass.draw(0..image.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
if let Some(atlas_text) = uploaded_atlas_text.as_ref() {
|
||||
pass.set_pipeline(&self.text_pipeline);
|
||||
pass.set_bind_group(0, &self.glyph_atlas.bind_group, &[]);
|
||||
@@ -501,6 +535,7 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
|
||||
target: "ruin_ui_renderer_wgpu::perf",
|
||||
scene_version = scene.version,
|
||||
quad_vertices = vertices.len(),
|
||||
image_batches = uploaded_images.len(),
|
||||
atlas_text_vertices = uploaded_atlas_text
|
||||
.as_ref()
|
||||
.map_or(0_u32, |text| text.vertex_count),
|
||||
@@ -825,6 +860,36 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
|
||||
Some(rect)
|
||||
}
|
||||
|
||||
fn prepare_uploaded_image(
|
||||
&mut self,
|
||||
image: &PreparedImage,
|
||||
logical_size: UiSize,
|
||||
) -> Option<UploadedImage> {
|
||||
let key = image.resource.id();
|
||||
if !self.image_cache.contains_key(&key) {
|
||||
let cached = self.create_cached_image_texture(image);
|
||||
self.image_cache.insert(key, cached);
|
||||
}
|
||||
self.touch_image_cache_entry(key);
|
||||
let cached = self
|
||||
.image_cache
|
||||
.get(&key)
|
||||
.expect("image cache entry should exist after insertion");
|
||||
let vertices = build_image_vertices(image.rect, image.uv_rect, logical_size);
|
||||
let vertex_buffer = self
|
||||
.device
|
||||
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("ruin-ui-renderer-wgpu-image-vertices"),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
Some(UploadedImage {
|
||||
bind_group: cached.bind_group.clone(),
|
||||
vertex_buffer,
|
||||
vertex_count: vertices.len() as u32,
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_uploaded_text(
|
||||
&mut self,
|
||||
text: &PreparedText,
|
||||
@@ -879,6 +944,23 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
|
||||
}
|
||||
}
|
||||
|
||||
fn touch_image_cache_entry(&mut self, key: u64) {
|
||||
if let Some(position) = self
|
||||
.image_cache_order
|
||||
.iter()
|
||||
.position(|existing| *existing == key)
|
||||
{
|
||||
self.image_cache_order.remove(position);
|
||||
}
|
||||
self.image_cache_order.push_back(key);
|
||||
while self.image_cache_order.len() > MAX_IMAGE_CACHE_ENTRIES {
|
||||
let Some(evicted) = self.image_cache_order.pop_front() else {
|
||||
break;
|
||||
};
|
||||
self.image_cache.remove(&evicted);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_cached_text_texture(&self, text: &RasterizedText) -> CachedTextTexture {
|
||||
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("ruin-ui-renderer-wgpu-texture"),
|
||||
@@ -935,6 +1017,61 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
|
||||
size: text.size,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_cached_image_texture(&self, image: &PreparedImage) -> CachedImageTexture {
|
||||
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("ruin-ui-renderer-wgpu-image-texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: image.resource.width(),
|
||||
height: image.resource.height(),
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
self.queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
image.resource.pixels(),
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(image.resource.width() * 4),
|
||||
rows_per_image: Some(image.resource.height()),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: image.resource.width(),
|
||||
height: image.resource.height(),
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("ruin-ui-renderer-wgpu-image-bind-group"),
|
||||
layout: &self.text_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&self.text_sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
CachedImageTexture {
|
||||
_texture: texture,
|
||||
bind_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_glyph_atlas(
|
||||
@@ -1234,6 +1371,55 @@ fn build_text_vertices(origin: Point, size: UiSize, logical_size: UiSize) -> [Te
|
||||
]
|
||||
}
|
||||
|
||||
fn build_image_vertices(
|
||||
rect: Rect,
|
||||
uv_rect: (f32, f32, f32, f32),
|
||||
logical_size: UiSize,
|
||||
) -> [TextVertex; 6] {
|
||||
let left = to_ndc_x(rect.origin.x, logical_size.width.max(1.0));
|
||||
let right = to_ndc_x(rect.origin.x + rect.size.width, logical_size.width.max(1.0));
|
||||
let top = to_ndc_y(rect.origin.y, logical_size.height.max(1.0));
|
||||
let bottom = to_ndc_y(
|
||||
rect.origin.y + rect.size.height,
|
||||
logical_size.height.max(1.0),
|
||||
);
|
||||
let (u0, v0, u1, v1) = uv_rect;
|
||||
let color = [1.0, 1.0, 1.0, 1.0];
|
||||
|
||||
[
|
||||
TextVertex {
|
||||
position: [left, top],
|
||||
uv: [u0, v0],
|
||||
color,
|
||||
},
|
||||
TextVertex {
|
||||
position: [left, bottom],
|
||||
uv: [u0, v1],
|
||||
color,
|
||||
},
|
||||
TextVertex {
|
||||
position: [right, top],
|
||||
uv: [u1, v0],
|
||||
color,
|
||||
},
|
||||
TextVertex {
|
||||
position: [right, top],
|
||||
uv: [u1, v0],
|
||||
color,
|
||||
},
|
||||
TextVertex {
|
||||
position: [left, bottom],
|
||||
uv: [u0, v1],
|
||||
color,
|
||||
},
|
||||
TextVertex {
|
||||
position: [right, bottom],
|
||||
uv: [u1, v1],
|
||||
color,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn push_glyph_vertices(
|
||||
vertices: &mut Vec<TextVertex>,
|
||||
glyph_rect: PixelRect,
|
||||
|
||||
Reference in New Issue
Block a user