use crate::ecs::world::World;
use super::{
GlobalUniforms, INITIAL_RECT_CAPACITY, INITIAL_TEXT_INSTANCE_CAPACITY, LayerDrawGroup,
TextBuildEntry, TextDrawCall, TextInstanceData, UiPass, UiRectInstance, UiTextVertex,
};
impl UiPass {
fn ensure_rect_capacity(&mut self, device: &wgpu::Device, required: usize) {
if required > self.rect_instance_capacity {
let new_capacity = (required * 2).max(INITIAL_RECT_CAPACITY);
self.rect_instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Rect Instance Buffer (Resized)"),
size: (std::mem::size_of::<UiRectInstance>() * new_capacity) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.rect_instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("UiPass Rect Instance Bind Group (Resized)"),
layout: &self.rect_instance_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: self.rect_instance_buffer.as_entire_binding(),
}],
});
self.rect_instance_capacity = new_capacity;
}
}
fn ensure_text_vertex_capacity(&mut self, device: &wgpu::Device, required: usize) {
if required > self.text_vertex_capacity {
let new_capacity = (required * 2).max(1000);
self.text_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Text Vertex Buffer (Resized)"),
size: (std::mem::size_of::<UiTextVertex>() * new_capacity) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.text_vertex_capacity = new_capacity;
}
}
fn ensure_text_index_capacity(&mut self, device: &wgpu::Device, required: usize) {
if required > self.text_index_capacity {
let new_capacity = (required * 2).max(3000);
self.text_index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Text Index Buffer (Resized)"),
size: (std::mem::size_of::<u32>() * new_capacity) as u64,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.text_index_capacity = new_capacity;
}
}
fn ensure_text_instance_capacity(&mut self, device: &wgpu::Device, required: usize) {
if required > self.text_instance_capacity {
let new_capacity = (required * 2).max(INITIAL_TEXT_INSTANCE_CAPACITY);
self.text_instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Text Instance Buffer (Resized)"),
size: (std::mem::size_of::<TextInstanceData>() * new_capacity) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.text_instance_capacity = new_capacity;
self.rebuild_text_data_bind_group(device);
}
}
fn ensure_character_color_capacity(&mut self, device: &wgpu::Device, required: usize) {
if required > self.character_color_capacity {
let new_capacity = (required * 2).max(1000);
self.character_color_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Character Color Buffer (Resized)"),
size: (std::mem::size_of::<[f32; 4]>() * new_capacity) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.character_color_capacity = new_capacity;
self.rebuild_text_data_bind_group(device);
}
}
fn ensure_character_bg_color_capacity(&mut self, device: &wgpu::Device, required: usize) {
if required > self.character_bg_color_capacity {
let new_capacity = (required * 2).max(1000);
self.character_bg_color_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Character BG Color Buffer (Resized)"),
size: (std::mem::size_of::<[f32; 4]>() * new_capacity) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.character_bg_color_capacity = new_capacity;
self.rebuild_text_data_bind_group(device);
}
}
fn rebuild_text_data_bind_group(&mut self, device: &wgpu::Device) {
self.text_data_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("UiPass Text Data Bind Group (Rebuilt)"),
layout: &self.text_data_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.text_instance_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: self.character_color_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.character_bg_color_buffer.as_entire_binding(),
},
],
});
}
pub fn update_texture_bind_groups(
&mut self,
device: &wgpu::Device,
font_textures: &[(wgpu::Texture, wgpu::TextureView)],
) {
self.cached_font_texture_views.clear();
for (_texture, texture_view) in font_textures {
self.cached_font_texture_views.push(texture_view.clone());
}
self.rebuild_font_bind_groups(device);
}
fn rebuild_font_bind_groups(&mut self, device: &wgpu::Device) {
self.text_font_bind_groups.clear();
for texture_view in &self.cached_font_texture_views {
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
..Default::default()
});
self.text_font_bind_groups
.push(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("UiPass Text Font Bind Group"),
layout: &self.text_font_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
}));
}
}
pub(super) fn prepare_pass(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
world: &World,
) {
self.use_subpixel = world.resources.retained_ui.subpixel_text_rendering;
if let Some((width, height)) = world.resources.window.cached_viewport_size {
self.screen_width = width as f32;
self.screen_height = height as f32;
}
let projection =
nalgebra_glm::ortho(0.0, self.screen_width, self.screen_height, 0.0, -1.0, 1.0);
let globals = GlobalUniforms { projection };
queue.write_buffer(
&self.global_uniform_buffer,
0,
bytemuck::cast_slice(&[globals]),
);
let mut rects: Vec<_> = world.resources.retained_ui.frame_rects.iter().collect();
rects.sort_by(|a, b| match a.layer.cmp(&b.layer) {
std::cmp::Ordering::Equal => a.z_index.cmp(&b.z_index),
other => other,
});
let rect_instances: Vec<UiRectInstance> = rects
.iter()
.map(|rect| {
let combined_z = (rect.layer as i32) * 100000 + rect.z_index;
let depth = 0.1 + (combined_z as f32 / 10000000.0) * 0.8;
let clip = rect
.clip_rect
.map(|c| [c.min.x, c.min.y, c.max.x, c.max.y])
.unwrap_or([0.0, 0.0, 0.0, 0.0]);
UiRectInstance {
position_size: [rect.position.x, rect.position.y, rect.size.x, rect.size.y],
color: [rect.color.x, rect.color.y, rect.color.z, rect.color.w],
border_color: [
rect.border_color.x,
rect.border_color.y,
rect.border_color.z,
rect.border_color.w,
],
clip_rect: clip,
params: [rect.corner_radius, rect.border_width, depth, rect.rotation],
}
})
.collect();
self.rect_count = rect_instances.len() as u32;
if !rect_instances.is_empty() {
self.ensure_rect_capacity(device, rect_instances.len());
queue.write_buffer(
&self.rect_instance_buffer,
0,
bytemuck::cast_slice(&rect_instances),
);
}
let mut all_vertices: Vec<UiTextVertex> = Vec::new();
let mut all_indices: Vec<u32> = Vec::new();
let mut all_character_colors: Vec<[f32; 4]> = Vec::new();
let mut all_character_bg_colors: Vec<[f32; 4]> = Vec::new();
let mut text_instance_data: Vec<TextInstanceData> = Vec::new();
let mut text_build_entries: Vec<TextBuildEntry> = Vec::new();
let mut character_color_offset = 0u32;
let bitmap_font_sizes: Vec<f32> = world
.resources
.text_cache
.font_manager
.bitmap_fonts
.iter()
.map(|f| f.font_size)
.collect();
let mut sorted_text: Vec<_> = world
.resources
.retained_ui
.frame_text_meshes
.iter()
.collect();
sorted_text.sort_by(|a, b| match a.layer.cmp(&b.layer) {
std::cmp::Ordering::Equal => a.z_index.cmp(&b.z_index),
other => other,
});
for text_instance in sorted_text {
const SCALE: f32 = 100.0;
let instance_index = text_instance_data.len() as u32;
let vertex_offset = all_vertices.len() as u32;
let index_offset = all_indices.len() as u32;
let char_count = text_instance.mesh.vertices.len() / 4;
if self.use_subpixel {
for chunk in text_instance.mesh.vertices.chunks(4) {
let mut min_x = f32::INFINITY;
for vertex in chunk {
min_x = min_x.min(vertex.position.x * SCALE + text_instance.position.x);
}
let snap_x = min_x.round() - min_x;
for vertex in chunk {
all_vertices.push(UiTextVertex {
position: [
vertex.position.x * SCALE + text_instance.position.x + snap_x,
-vertex.position.y * SCALE + text_instance.position.y,
0.0,
],
tex_coords: [vertex.tex_coords.x, vertex.tex_coords.y],
character_index: vertex.character_index + character_color_offset,
text_instance_index: instance_index,
_padding: 0,
});
}
}
} else {
for vertex in &text_instance.mesh.vertices {
all_vertices.push(UiTextVertex {
position: [
vertex.position.x * SCALE + text_instance.position.x,
-vertex.position.y * SCALE + text_instance.position.y,
0.0,
],
tex_coords: [vertex.tex_coords.x, vertex.tex_coords.y],
character_index: vertex.character_index + character_color_offset,
text_instance_index: instance_index,
_padding: 0,
});
}
}
if let Some(colors) = &text_instance.character_colors {
for character_index in 0..char_count {
let color = colors
.get(character_index)
.and_then(|c| *c)
.map(|c| [c.x, c.y, c.z, c.w])
.unwrap_or([0.0, 0.0, 0.0, 0.0]);
all_character_colors.push(color);
}
} else {
for _ in 0..char_count {
all_character_colors.push([0.0, 0.0, 0.0, 0.0]);
}
}
if let Some(bg_colors) = &text_instance.character_background_colors {
for character_index in 0..char_count {
let color = bg_colors
.get(character_index)
.and_then(|c| *c)
.map(|c| [c.x, c.y, c.z, c.w])
.unwrap_or([0.0, 0.0, 0.0, 0.0]);
all_character_bg_colors.push(color);
}
} else {
for _ in 0..char_count {
all_character_bg_colors.push([0.0, 0.0, 0.0, 0.0]);
}
}
character_color_offset += char_count as u32;
for &index in &text_instance.mesh.indices {
all_indices.push(index + vertex_offset);
}
let combined_z = (text_instance.layer as i32) * 100000 + text_instance.z_index;
let depth = 0.1 + (combined_z as f32 / 10000000.0) * 0.8;
let clip = text_instance
.clip_rect
.map(|c| [c.min.x, c.min.y, c.max.x, c.max.y])
.unwrap_or([0.0, 0.0, 0.0, 0.0]);
let ui_atlas_font_size = bitmap_font_sizes
.get(text_instance.font_index)
.copied()
.unwrap_or(18.0);
let size_ratio = text_instance.font_size / ui_atlas_font_size;
text_instance_data.push(TextInstanceData {
color: [
text_instance.color.x,
text_instance.color.y,
text_instance.color.z,
text_instance.color.w,
],
outline_color: [
text_instance.outline_color.x,
text_instance.outline_color.y,
text_instance.outline_color.z,
text_instance.outline_color.w,
],
clip_rect: clip,
params: [text_instance.outline_width, depth, size_ratio, 0.0],
});
text_build_entries.push(TextBuildEntry {
font_index: text_instance.font_index,
index_offset,
index_count: text_instance.mesh.indices.len() as u32,
layer: text_instance.layer,
});
}
self.text_draw_calls.clear();
if !text_build_entries.is_empty() {
let mut current_font = text_build_entries[0].font_index;
let mut current_layer = text_build_entries[0].layer;
let mut batch_index_start = text_build_entries[0].index_offset;
let mut batch_index_end =
text_build_entries[0].index_offset + text_build_entries[0].index_count;
for entry in text_build_entries.iter().skip(1) {
if entry.font_index == current_font
&& entry.index_offset == batch_index_end
&& entry.layer == current_layer
{
batch_index_end = entry.index_offset + entry.index_count;
} else {
self.text_draw_calls.push(TextDrawCall {
font_index: current_font,
index_start: batch_index_start,
index_count: batch_index_end - batch_index_start,
layer: current_layer,
});
current_font = entry.font_index;
current_layer = entry.layer;
batch_index_start = entry.index_offset;
batch_index_end = entry.index_offset + entry.index_count;
}
}
self.text_draw_calls.push(TextDrawCall {
font_index: current_font,
index_start: batch_index_start,
index_count: batch_index_end - batch_index_start,
layer: current_layer,
});
}
self.layer_draw_groups.clear();
{
let mut rect_ranges: Vec<(super::super::UiLayer, u32, u32)> = Vec::new();
if !rects.is_empty() {
let mut range_start = 0u32;
let mut range_layer = rects[0].layer;
for (index, rect) in rects.iter().enumerate() {
if rect.layer != range_layer {
rect_ranges.push((range_layer, range_start, index as u32 - range_start));
range_layer = rect.layer;
range_start = index as u32;
}
}
rect_ranges.push((range_layer, range_start, rects.len() as u32 - range_start));
}
let mut all_layers: Vec<super::super::UiLayer> = Vec::new();
for (layer, _, _) in &rect_ranges {
if !all_layers.contains(layer) {
all_layers.push(*layer);
}
}
for draw_call in &self.text_draw_calls {
if !all_layers.contains(&draw_call.layer) {
all_layers.push(draw_call.layer);
}
}
all_layers.sort();
for layer in &all_layers {
let (rect_start, rect_count) = rect_ranges
.iter()
.find(|(l, _, _)| l == layer)
.map(|(_, start, count)| (*start, *count))
.unwrap_or((0, 0));
let text_draw_start = self
.text_draw_calls
.iter()
.position(|dc| dc.layer == *layer)
.unwrap_or(0);
let text_draw_count = self
.text_draw_calls
.iter()
.filter(|dc| dc.layer == *layer)
.count();
self.layer_draw_groups.push(LayerDrawGroup {
rect_start,
rect_count,
text_draw_start,
text_draw_count,
});
}
}
if all_character_colors.is_empty() {
all_character_colors.push([0.0, 0.0, 0.0, 0.0]);
}
if all_character_bg_colors.is_empty() {
all_character_bg_colors.push([0.0, 0.0, 0.0, 0.0]);
}
self.ensure_character_color_capacity(device, all_character_colors.len());
queue.write_buffer(
&self.character_color_buffer,
0,
bytemuck::cast_slice(&all_character_colors),
);
self.ensure_character_bg_color_capacity(device, all_character_bg_colors.len());
queue.write_buffer(
&self.character_bg_color_buffer,
0,
bytemuck::cast_slice(&all_character_bg_colors),
);
if !text_instance_data.is_empty() {
self.ensure_text_instance_capacity(device, text_instance_data.len());
queue.write_buffer(
&self.text_instance_buffer,
0,
bytemuck::cast_slice(&text_instance_data),
);
}
if !all_vertices.is_empty() {
self.ensure_text_vertex_capacity(device, all_vertices.len());
queue.write_buffer(
&self.text_vertex_buffer,
0,
bytemuck::cast_slice(&all_vertices),
);
}
if !all_indices.is_empty() {
self.ensure_text_index_capacity(device, all_indices.len());
queue.write_buffer(
&self.text_index_buffer,
0,
bytemuck::cast_slice(&all_indices),
);
}
}
}