use crate::ecs::text::components::{HudAnchor, TextAlignment};
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use nalgebra_glm::{Mat4, Vec2, Vec3};
use wgpu::util::DeviceExt;
use super::hud::{BackgroundVertex, HudBackgroundUniforms};
const INITIAL_RECT_CAPACITY: usize = 4096;
const INITIAL_TEXT_VERTEX_CAPACITY: usize = 64000;
const INITIAL_TEXT_INDEX_CAPACITY: usize = 96000;
const INITIAL_TEXT_INSTANCE_CAPACITY: usize = 1024;
const INITIAL_CHARACTER_COLOR_CAPACITY: usize = 10000;
const INITIAL_BACKGROUND_VERTEX_CAPACITY: usize = 20000;
const INITIAL_BACKGROUND_INDEX_CAPACITY: usize = 30000;
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct UiRectInstance {
position_size: [f32; 4],
color: [f32; 4],
border_color: [f32; 4],
clip_rect: [f32; 4],
params: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct TextInstanceData {
color: [f32; 4],
outline_color: [f32; 4],
clip_rect: [f32; 4],
params: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct UiPassVertex {
position: [f32; 2],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct UiTextVertex {
position: [f32; 3],
tex_coords: [f32; 2],
character_index: u32,
text_instance_index: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GlobalUniforms {
projection: Mat4,
}
struct TextDrawCall {
font_index: usize,
index_start: u32,
index_count: u32,
layer: super::UiLayer,
}
struct LayerDrawGroup {
rect_start: u32,
rect_count: u32,
text_draw_start: usize,
text_draw_count: usize,
}
pub struct UiPass {
rect_pipeline: wgpu::RenderPipeline,
text_pipeline: wgpu::RenderPipeline,
text_pipeline_subpixel: wgpu::RenderPipeline,
background_pipeline: wgpu::RenderPipeline,
use_subpixel: bool,
global_uniform_buffer: wgpu::Buffer,
global_uniform_bind_group: wgpu::BindGroup,
rect_quad_vertex_buffer: wgpu::Buffer,
rect_quad_index_buffer: wgpu::Buffer,
rect_instance_buffer: wgpu::Buffer,
rect_instance_bind_group_layout: wgpu::BindGroupLayout,
rect_instance_bind_group: wgpu::BindGroup,
rect_instance_capacity: usize,
rect_count: u32,
text_vertex_buffer: wgpu::Buffer,
text_index_buffer: wgpu::Buffer,
text_instance_buffer: wgpu::Buffer,
character_color_buffer: wgpu::Buffer,
text_data_bind_group_layout: wgpu::BindGroupLayout,
text_data_bind_group: wgpu::BindGroup,
text_font_bind_group_layout: wgpu::BindGroupLayout,
text_font_bind_groups: Vec<wgpu::BindGroup>,
_text_global_bind_group_layout: wgpu::BindGroupLayout,
text_global_bind_group: wgpu::BindGroup,
text_vertex_capacity: usize,
text_index_capacity: usize,
text_instance_capacity: usize,
character_color_capacity: usize,
text_draw_calls: Vec<TextDrawCall>,
layer_draw_groups: Vec<LayerDrawGroup>,
background_vertex_buffer: wgpu::Buffer,
background_index_buffer: wgpu::Buffer,
background_uniform_buffer: wgpu::Buffer,
_background_uniform_bind_group_layout: wgpu::BindGroupLayout,
background_uniform_bind_group: wgpu::BindGroup,
background_vertex_capacity: usize,
background_index_capacity: usize,
background_index_count: u32,
cached_font_texture_views: Vec<wgpu::TextureView>,
screen_width: f32,
screen_height: f32,
}
impl UiPass {
pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
let global_uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("UiPass Global Uniform Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let global_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Global Uniform Buffer"),
size: std::mem::size_of::<GlobalUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let global_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("UiPass Global Uniform Bind Group"),
layout: &global_uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: global_uniform_buffer.as_entire_binding(),
}],
});
let rect_instance_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("UiPass Rect Instance Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let rect_instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Rect Instance Buffer"),
size: (std::mem::size_of::<UiRectInstance>() * INITIAL_RECT_CAPACITY) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let rect_instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("UiPass Rect Instance Bind Group"),
layout: &rect_instance_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: rect_instance_buffer.as_entire_binding(),
}],
});
let rect_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("UiPass Rect Shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../../shaders/ui_pass_rect.wgsl").into(),
),
});
let rect_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("UiPass Rect Pipeline Layout"),
bind_group_layouts: &[
&global_uniform_bind_group_layout,
&rect_instance_bind_group_layout,
],
push_constant_ranges: &[],
});
let vertices = [
UiPassVertex {
position: [0.0, 0.0],
},
UiPassVertex {
position: [1.0, 0.0],
},
UiPassVertex {
position: [1.0, 1.0],
},
UiPassVertex {
position: [0.0, 1.0],
},
];
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
let rect_quad_vertex_buffer =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("UiPass Rect Quad Vertex Buffer"),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let rect_quad_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("UiPass Rect Quad Index Buffer"),
contents: bytemuck::cast_slice(&indices),
usage: wgpu::BufferUsages::INDEX,
});
let rect_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("UiPass Rect Pipeline"),
layout: Some(&rect_pipeline_layout),
vertex: wgpu::VertexState {
module: &rect_shader,
entry_point: Some("vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<UiPassVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
}],
}],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::GreaterEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: &rect_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
multiview: None,
cache: None,
});
let text_data_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("UiPass Text Data Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let text_instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Text Instance Buffer"),
size: (std::mem::size_of::<TextInstanceData>() * INITIAL_TEXT_INSTANCE_CAPACITY) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let character_color_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Character Color Buffer"),
size: (std::mem::size_of::<[f32; 4]>() * INITIAL_CHARACTER_COLOR_CAPACITY) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let text_data_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("UiPass Text Data Bind Group"),
layout: &text_data_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: text_instance_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: character_color_buffer.as_entire_binding(),
},
],
});
let text_font_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("UiPass Text Font Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let text_global_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("UiPass Text Global Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let text_global_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("UiPass Text Global Bind Group"),
layout: &text_global_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: global_uniform_buffer.as_entire_binding(),
}],
});
let text_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("UiPass Text Bitmap Shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../../shaders/ui_pass_text_bitmap.wgsl").into(),
),
});
let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("UiPass Text Pipeline Layout"),
bind_group_layouts: &[
&text_data_bind_group_layout,
&text_font_bind_group_layout,
&text_global_bind_group_layout,
],
push_constant_ranges: &[],
});
let text_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Text Vertex Buffer"),
size: (std::mem::size_of::<UiTextVertex>() * INITIAL_TEXT_VERTEX_CAPACITY) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let text_index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Text Index Buffer"),
size: (std::mem::size_of::<u32>() * INITIAL_TEXT_INDEX_CAPACITY) as u64,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let text_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("UiPass Text Pipeline"),
layout: Some(&text_pipeline_layout),
vertex: wgpu::VertexState {
module: &text_shader,
entry_point: Some("vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<UiTextVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: (3 * 4) as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: (5 * 4) as wgpu::BufferAddress,
shader_location: 2,
format: wgpu::VertexFormat::Uint32,
},
wgpu::VertexAttribute {
offset: (6 * 4) as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Uint32,
},
],
}],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::GreaterEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: &text_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
multiview: None,
cache: None,
});
let text_pipeline_subpixel =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("UiPass Text Pipeline Subpixel"),
layout: Some(&text_pipeline_layout),
vertex: wgpu::VertexState {
module: &text_shader,
entry_point: Some("vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<UiTextVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: (3 * 4) as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: (5 * 4) as wgpu::BufferAddress,
shader_location: 2,
format: wgpu::VertexFormat::Uint32,
},
wgpu::VertexAttribute {
offset: (6 * 4) as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Uint32,
},
],
}],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::GreaterEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: &text_shader,
entry_point: Some("fs_main_subpixel"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrc,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
}),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
multiview: None,
cache: None,
});
let background_uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("UiPass Background Uniform Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let background_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("UiPass Background Shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../../shaders/hud_background.wgsl").into(),
),
});
let background_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("UiPass Background Pipeline Layout"),
bind_group_layouts: &[&background_uniform_bind_group_layout],
push_constant_ranges: &[],
});
let background_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("UiPass Background Pipeline"),
layout: Some(&background_pipeline_layout),
vertex: wgpu::VertexState {
module: &background_shader,
entry_point: Some("vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<BackgroundVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: (3 * 4) as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32,
},
wgpu::VertexAttribute {
offset: (4 * 4) as wgpu::BufferAddress,
shader_location: 2,
format: wgpu::VertexFormat::Float32x4,
},
],
}],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::GreaterEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: &background_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
multiview: None,
cache: None,
});
let background_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Background Vertex Buffer"),
size: (std::mem::size_of::<BackgroundVertex>() * INITIAL_BACKGROUND_VERTEX_CAPACITY)
as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let background_index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Background Index Buffer"),
size: (std::mem::size_of::<u32>() * INITIAL_BACKGROUND_INDEX_CAPACITY) as u64,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let background_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Background Uniform Buffer"),
size: std::mem::size_of::<HudBackgroundUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let background_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("UiPass Background Uniform Bind Group"),
layout: &background_uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: background_uniform_buffer.as_entire_binding(),
}],
});
Self {
rect_pipeline,
text_pipeline,
text_pipeline_subpixel,
background_pipeline,
use_subpixel: false,
global_uniform_buffer,
global_uniform_bind_group,
rect_quad_vertex_buffer,
rect_quad_index_buffer,
rect_instance_buffer,
rect_instance_bind_group_layout,
rect_instance_bind_group,
rect_instance_capacity: INITIAL_RECT_CAPACITY,
rect_count: 0,
text_vertex_buffer,
text_index_buffer,
text_instance_buffer,
character_color_buffer,
text_data_bind_group_layout,
text_data_bind_group,
text_font_bind_group_layout,
text_font_bind_groups: Vec::new(),
_text_global_bind_group_layout: text_global_bind_group_layout,
text_global_bind_group,
text_vertex_capacity: INITIAL_TEXT_VERTEX_CAPACITY,
text_index_capacity: INITIAL_TEXT_INDEX_CAPACITY,
text_instance_capacity: INITIAL_TEXT_INSTANCE_CAPACITY,
character_color_capacity: INITIAL_CHARACTER_COLOR_CAPACITY,
text_draw_calls: Vec::new(),
layer_draw_groups: Vec::new(),
background_vertex_buffer,
background_index_buffer,
background_uniform_buffer,
_background_uniform_bind_group_layout: background_uniform_bind_group_layout,
background_uniform_bind_group,
background_vertex_capacity: INITIAL_BACKGROUND_VERTEX_CAPACITY,
background_index_capacity: INITIAL_BACKGROUND_INDEX_CAPACITY,
background_index_count: 0,
cached_font_texture_views: Vec::new(),
screen_width: 800.0,
screen_height: 600.0,
}
}
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_background_vertex_capacity(&mut self, device: &wgpu::Device, required: usize) {
if required > self.background_vertex_capacity {
let new_capacity = (required * 2).max(1000);
self.background_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Background Vertex Buffer (Resized)"),
size: (std::mem::size_of::<BackgroundVertex>() * new_capacity) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.background_vertex_capacity = new_capacity;
}
}
fn ensure_background_index_capacity(&mut self, device: &wgpu::Device, required: usize) {
if required > self.background_index_capacity {
let new_capacity = (required * 2).max(3000);
self.background_index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("UiPass Background 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.background_index_capacity = new_capacity;
}
}
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(),
},
],
});
}
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::FilterMode::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),
},
],
}));
}
}
}
fn calculate_screen_position(
position: Vec2,
bounds: Vec2,
anchor: HudAnchor,
text_alignment: TextAlignment,
has_anchor_character: bool,
screen_width: f32,
screen_height: f32,
) -> Vec2 {
const SCALE: f32 = 100.0;
let text_width = bounds.x * SCALE;
let text_height = bounds.y * SCALE;
let base_position = if has_anchor_character {
match anchor {
HudAnchor::TopLeft => Vec2::new(0.0, 0.0),
HudAnchor::TopCenter => Vec2::new(screen_width * 0.5, 0.0),
HudAnchor::TopRight => Vec2::new(screen_width, 0.0),
HudAnchor::CenterLeft => Vec2::new(0.0, screen_height * 0.5 - text_height * 0.5),
HudAnchor::Center => {
Vec2::new(screen_width * 0.5, screen_height * 0.5 - text_height * 0.5)
}
HudAnchor::CenterRight => {
Vec2::new(screen_width, screen_height * 0.5 - text_height * 0.5)
}
HudAnchor::BottomLeft => Vec2::new(0.0, screen_height - text_height),
HudAnchor::BottomCenter => Vec2::new(screen_width * 0.5, screen_height - text_height),
HudAnchor::BottomRight => Vec2::new(screen_width, screen_height - text_height),
}
} else {
match (anchor, text_alignment) {
(HudAnchor::TopLeft, _) => Vec2::new(0.0, 0.0),
(HudAnchor::TopCenter, TextAlignment::Center) => Vec2::new(screen_width * 0.5, 0.0),
(HudAnchor::TopCenter, TextAlignment::Left) => {
Vec2::new(screen_width * 0.5 - text_width * 0.5, 0.0)
}
(HudAnchor::TopCenter, TextAlignment::Right) => {
Vec2::new(screen_width * 0.5 + text_width * 0.5, 0.0)
}
(HudAnchor::TopRight, _) => Vec2::new(screen_width - text_width, 0.0),
(HudAnchor::CenterLeft, _) => Vec2::new(0.0, screen_height * 0.5 - text_height * 0.5),
(HudAnchor::Center, TextAlignment::Center) => {
Vec2::new(screen_width * 0.5, screen_height * 0.5 - text_height * 0.5)
}
(HudAnchor::Center, TextAlignment::Left) => Vec2::new(
screen_width * 0.5 - text_width * 0.5,
screen_height * 0.5 - text_height * 0.5,
),
(HudAnchor::Center, TextAlignment::Right) => Vec2::new(
screen_width * 0.5 + text_width * 0.5,
screen_height * 0.5 - text_height * 0.5,
),
(HudAnchor::CenterRight, _) => Vec2::new(
screen_width - text_width,
screen_height * 0.5 - text_height * 0.5,
),
(HudAnchor::BottomLeft, _) => Vec2::new(0.0, screen_height - text_height),
(HudAnchor::BottomCenter, TextAlignment::Center) => {
Vec2::new(screen_width * 0.5, screen_height - text_height)
}
(HudAnchor::BottomCenter, TextAlignment::Left) => Vec2::new(
screen_width * 0.5 - text_width * 0.5,
screen_height - text_height,
),
(HudAnchor::BottomCenter, TextAlignment::Right) => Vec2::new(
screen_width * 0.5 + text_width * 0.5,
screen_height - text_height,
),
(HudAnchor::BottomRight, _) => {
Vec2::new(screen_width - text_width, screen_height - text_height)
}
}
};
base_position + position
}
struct TextBuildEntry {
font_index: usize,
index_offset: u32,
index_count: u32,
layer: super::UiLayer,
}
impl PassNode<World> for UiPass {
fn name(&self) -> &'static str {
"ui_pass"
}
fn reads(&self) -> Vec<&str> {
vec![]
}
fn writes(&self) -> Vec<&str> {
vec![]
}
fn reads_writes(&self) -> Vec<&str> {
vec!["color", "depth"]
}
fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, world: &World) {
self.use_subpixel = world.resources.retained_ui.subpixel_text_rendering;
let scale_factor = world.resources.window.cached_scale_factor;
let bitmap_font_sizes: Vec<f32> = world
.resources
.text_cache
.font_manager
.bitmap_fonts
.iter()
.map(|f| f.font_size)
.collect();
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 text_instance_data: Vec<TextInstanceData> = Vec::new();
let mut text_build_entries: Vec<TextBuildEntry> = Vec::new();
let mut character_color_offset = 0u32;
let text_entities = world.query_entities(crate::ecs::world::HUD_TEXT);
for entity in text_entities {
let hud_text = world.get_hud_text(entity);
let character_colors = world.get_text_character_colors(entity);
if let Some(hud_text) = hud_text
&& let Some(mesh) = &hud_text.cached_mesh
{
const SCALE: f32 = 100.0;
let screen_position = calculate_screen_position(
hud_text.position,
mesh.bounds,
hud_text.anchor,
hud_text.properties.alignment,
hud_text.properties.anchor_character.is_some(),
self.screen_width,
self.screen_height,
);
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 text_content = world
.resources
.text_cache
.get_text(hud_text.text_index)
.map(|s| s.to_string())
.unwrap_or_default();
let char_count = text_content.chars().filter(|c| *c != '\n').count();
if self.use_subpixel {
for chunk in mesh.vertices.chunks(4) {
let mut min_x = f32::INFINITY;
let mut min_y = f32::INFINITY;
for vertex in chunk {
min_x = min_x.min(vertex.position.x * SCALE + screen_position.x);
min_y = min_y.min(-vertex.position.y * SCALE + screen_position.y);
}
let snap_x = min_x.round() - min_x;
let snap_y = min_y.round() - min_y;
for vertex in chunk {
all_vertices.push(UiTextVertex {
position: [
vertex.position.x * SCALE + screen_position.x + snap_x,
-vertex.position.y * SCALE + screen_position.y + snap_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 &mesh.vertices {
all_vertices.push(UiTextVertex {
position: [
vertex.position.x * SCALE + screen_position.x,
-vertex.position.y * SCALE + screen_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,
});
}
}
for character_index in 0..char_count {
let color = if let Some(colors) = character_colors {
colors
.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])
} else {
[0.0, 0.0, 0.0, 0.0]
};
all_character_colors.push(color);
}
character_color_offset += char_count as u32;
for &index in &mesh.indices {
all_indices.push(index + vertex_offset);
}
let depth = 0.05f32;
let hud_atlas_font_size = bitmap_font_sizes
.get(hud_text.font_index)
.copied()
.unwrap_or(18.0);
let size_ratio = hud_text.properties.font_size * scale_factor / hud_atlas_font_size;
text_instance_data.push(TextInstanceData {
color: [
hud_text.properties.color.x,
hud_text.properties.color.y,
hud_text.properties.color.z,
hud_text.properties.color.w,
],
outline_color: [
hud_text.properties.outline_color.x,
hud_text.properties.outline_color.y,
hud_text.properties.outline_color.z,
hud_text.properties.outline_color.w,
],
clip_rect: [0.0, 0.0, 0.0, 0.0],
params: [hud_text.properties.outline_width, depth, size_ratio, 0.0],
});
text_build_entries.push(TextBuildEntry {
font_index: hud_text.font_index,
index_offset,
index_count: mesh.indices.len() as u32,
layer: super::UiLayer::Background,
});
}
}
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;
let mut min_y = f32::INFINITY;
for vertex in chunk {
min_x = min_x.min(vertex.position.x * SCALE + text_instance.position.x);
min_y = min_y.min(-vertex.position.y * SCALE + text_instance.position.y);
}
let snap_x = min_x.round() - min_x;
let snap_y = min_y.round() - min_y;
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 + snap_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]);
}
}
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::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::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]);
}
self.ensure_character_color_capacity(device, all_character_colors.len());
queue.write_buffer(
&self.character_color_buffer,
0,
bytemuck::cast_slice(&all_character_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),
);
}
let mut bg_vertices: Vec<BackgroundVertex> = Vec::new();
let mut bg_indices: Vec<u32> = Vec::new();
let bg_text_entities = world.query_entities(crate::ecs::world::HUD_TEXT);
for entity in bg_text_entities {
let bg_colors = world.get_text_character_background_colors(entity);
let hud_text = world.get_hud_text(entity);
if let Some(bg_colors) = bg_colors
&& let Some(hud_text) = hud_text
&& let Some(monospace_width) = hud_text.properties.monospace_width
&& let Some(mesh) = &hud_text.cached_mesh
{
let screen_position = calculate_screen_position(
hud_text.position,
mesh.bounds,
hud_text.anchor,
hud_text.properties.alignment,
hud_text.properties.anchor_character.is_some(),
self.screen_width,
self.screen_height,
);
let cell_width = monospace_width;
let cell_height = hud_text.properties.font_size * hud_text.properties.line_height;
let text_content = world
.resources
.text_cache
.get_text(hud_text.text_index)
.map(|s| s.to_string())
.unwrap_or_default();
let mut char_index = 0usize;
let mut line_y = screen_position.y;
for line in text_content.split('\n') {
let mut char_x = screen_position.x;
for _character in line.chars() {
if let Some(Some(bg_color)) = bg_colors.colors.get(char_index)
&& bg_color.w > 0.0
{
let vertex_base = bg_vertices.len() as u32;
let left = char_x;
let right = char_x + cell_width;
let top = line_y;
let bottom = line_y + cell_height;
let color = *bg_color;
bg_vertices.push(BackgroundVertex {
position: Vec3::new(left, top, 0.0),
_pad: 0.0,
color,
});
bg_vertices.push(BackgroundVertex {
position: Vec3::new(right, top, 0.0),
_pad: 0.0,
color,
});
bg_vertices.push(BackgroundVertex {
position: Vec3::new(right, bottom, 0.0),
_pad: 0.0,
color,
});
bg_vertices.push(BackgroundVertex {
position: Vec3::new(left, bottom, 0.0),
_pad: 0.0,
color,
});
bg_indices.push(vertex_base);
bg_indices.push(vertex_base + 1);
bg_indices.push(vertex_base + 2);
bg_indices.push(vertex_base);
bg_indices.push(vertex_base + 2);
bg_indices.push(vertex_base + 3);
}
char_x += cell_width;
char_index += 1;
}
line_y += cell_height;
}
}
}
self.background_index_count = bg_indices.len() as u32;
if !bg_vertices.is_empty() {
self.ensure_background_vertex_capacity(device, bg_vertices.len());
queue.write_buffer(
&self.background_vertex_buffer,
0,
bytemuck::cast_slice(&bg_vertices),
);
self.ensure_background_index_capacity(device, bg_indices.len());
queue.write_buffer(
&self.background_index_buffer,
0,
bytemuck::cast_slice(&bg_indices),
);
let bg_uniforms = HudBackgroundUniforms {
projection,
depth: 0.05,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
};
queue.write_buffer(
&self.background_uniform_buffer,
0,
bytemuck::cast_slice(&[bg_uniforms]),
);
}
}
fn execute<'r, 'e>(
&mut self,
context: PassExecutionContext<'r, 'e, World>,
) -> Result<
Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
crate::render::wgpu::rendergraph::RenderGraphError,
> {
if !context.is_pass_enabled() {
return Ok(context.into_sub_graph_commands());
}
let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
let (depth_view, depth_load, depth_store) = context.get_depth_attachment("depth")?;
let (render_target_width, render_target_height) = context.get_texture_size("color")?;
let has_rects = self.rect_count > 0;
let has_text = !self.text_draw_calls.is_empty();
let has_backgrounds = self.background_index_count > 0;
if has_rects || has_text || has_backgrounds {
let mut render_pass = context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("UI Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_view,
resolve_target: None,
ops: wgpu::Operations {
load: color_load,
store: color_store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth_view,
depth_ops: Some(wgpu::Operations {
load: depth_load,
store: depth_store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_scissor_rect(0, 0, render_target_width, render_target_height);
if has_backgrounds {
render_pass.set_pipeline(&self.background_pipeline);
render_pass.set_vertex_buffer(0, self.background_vertex_buffer.slice(..));
render_pass.set_index_buffer(
self.background_index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.set_bind_group(0, &self.background_uniform_bind_group, &[]);
render_pass.draw_indexed(0..self.background_index_count, 0, 0..1);
}
for group in &self.layer_draw_groups {
if group.rect_count > 0 {
render_pass.set_pipeline(&self.rect_pipeline);
render_pass.set_bind_group(0, &self.global_uniform_bind_group, &[]);
render_pass.set_bind_group(1, &self.rect_instance_bind_group, &[]);
render_pass.set_vertex_buffer(0, self.rect_quad_vertex_buffer.slice(..));
render_pass.set_index_buffer(
self.rect_quad_index_buffer.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.draw_indexed(
0..6,
0,
group.rect_start..(group.rect_start + group.rect_count),
);
}
if group.text_draw_count > 0 {
let text_pipeline = if self.use_subpixel {
&self.text_pipeline_subpixel
} else {
&self.text_pipeline
};
render_pass.set_pipeline(text_pipeline);
render_pass.set_bind_group(0, &self.text_data_bind_group, &[]);
render_pass.set_bind_group(2, &self.text_global_bind_group, &[]);
render_pass.set_vertex_buffer(0, self.text_vertex_buffer.slice(..));
render_pass.set_index_buffer(
self.text_index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
let text_end = group.text_draw_start + group.text_draw_count;
for draw_call in &self.text_draw_calls[group.text_draw_start..text_end] {
if draw_call.font_index < self.text_font_bind_groups.len() {
render_pass.set_bind_group(
1,
&self.text_font_bind_groups[draw_call.font_index],
&[],
);
render_pass.draw_indexed(
draw_call.index_start
..(draw_call.index_start + draw_call.index_count),
0,
0..1,
);
}
}
}
}
}
Ok(context.into_sub_graph_commands())
}
}