use crate::{
BatchType, BorderAlignment, BorderStyle, BoxSizing, Color, Context, CornerRadius,
DEFAULT_BASIC, DEFAULT_FLEX, DrawBatch, EdgeInsets, EntityId, LayoutPoint, LayoutRect,
LayoutSize, LayoutStore, Length, MichiuError, MichiuSoA, MichiuTrace, OutputStore, Pipeline,
QuadInstance, RenderData, RendererView, TextAlign, TextCacheKey, TextCacheValue, TextSpan,
TextureAtlas, Vertex, VisualProperty, trace_lifecycle,
};
use rustc_hash::FxHashMap;
use slotmap::SecondaryMap;
use std::collections::HashMap;
use std::num::NonZeroIsize;
use std::sync::Arc;
use wgpu::util::DeviceExt;
use wgpu::wgt::CommandEncoderDescriptor;
use wgpu::{CurrentSurfaceTexture, PipelineCompilationOptions};
pub struct WgpuRenderer {
pub(crate) surface: wgpu::Surface<'static>,
pub(crate) device: wgpu::Device,
pub(crate) queue: wgpu::Queue,
pub(crate) config: wgpu::SurfaceConfiguration,
pub(crate) pipeline: wgpu::RenderPipeline,
pub(crate) punchout_pipeline: wgpu::RenderPipeline,
pub(crate) vertex_buffer: wgpu::Buffer,
pub(crate) index_buffer: wgpu::Buffer,
pub(crate) instance_buffer: wgpu::Buffer,
pub(crate) instance_buffer_capacity: usize,
pub(crate) instance_staging: Vec<QuadInstance>,
pub(crate) config_buffer: wgpu::Buffer,
pub(crate) config_bind_group: wgpu::BindGroup,
pub(crate) config_bind_group_layout: wgpu::BindGroupLayout,
pub(crate) atlas: TextureAtlas,
pub(crate) temp_uv_map: SecondaryMap<EntityId, [f32; 4]>,
pub(crate) text_cache: FxHashMap<TextCacheKey, TextCacheValue>,
pub(crate) render_data: RenderData,
pub(crate) external_bind_groups: FxHashMap<EntityId, (wgpu::TextureView, wgpu::BindGroup)>,
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct GlobalConfig {
pub(crate) screen_size: [f32; 2],
pub(crate) scale: f32,
pub(crate) _padding: f32, }
impl WgpuRenderer {
pub(crate) async fn new(
visual: *mut std::ffi::c_void, size: LayoutSize,
scale_factor: f32,
) -> crate::Result<Self> {
let instance = WgpuRenderer::create_instance();
let target = unsafe { wgpu::SurfaceTargetUnsafe::CompositionVisual(visual) };
let surface = unsafe {
instance
.create_surface_unsafe(target)
.map_err(|e| MichiuError::CreateSurfaceError { source: e })?
};
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
apply_limit_buckets: false,
})
.await
.map_err(|e| MichiuError::RequestAdapterError { source: e })?;
let mut custom_limits = wgpu::Limits::downlevel_defaults();
custom_limits.max_non_sampler_bindings = 2048;
custom_limits.max_bind_groups = 3;
custom_limits.max_vertex_buffers = 3;
custom_limits.max_texture_dimension_2d = 8192;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("Michiu Renderer Device"),
required_features: wgpu::Features::empty(),
required_limits: custom_limits,
memory_hints: wgpu::MemoryHints::MemoryUsage,
experimental_features: wgpu::ExperimentalFeatures::default(),
trace: wgpu::Trace::Off,
})
.await
.map_err(|e| MichiuError::RequestDeviceError { source: e })?;
let config = WgpuRenderer::surface_config(size, &surface, &adapter);
surface.configure(&device, &config);
let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
let config_buffer = WgpuRenderer::create_buffer(size, scale_factor, &device);
let atlas = TextureAtlas::new(&device, 2048);
let config_bind_group_layout = WgpuRenderer::create_bind_group_layout(&device);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[Some(&config_bind_group_layout)],
immediate_size: 0,
});
let pipeline =
WgpuRenderer::create_render_pipeline(&pipeline_layout, &device, &shader, &config);
let punchout_pipeline =
WgpuRenderer::create_punchout_pipeline(&pipeline_layout, &device, &shader, &config);
let (vertex_buffer, index_buffer) = WgpuRenderer::create_buffer_init(&device);
let buffer_capacity = 64;
let instance_buffer = WgpuRenderer::create_instance_buffer(&device, buffer_capacity);
let config_bind_group = WgpuRenderer::create_bind_group(
&device,
&config_bind_group_layout,
&config_buffer,
&instance_buffer,
&atlas,
);
Ok(Self {
surface,
device,
queue,
config,
pipeline,
punchout_pipeline,
vertex_buffer,
index_buffer,
instance_buffer,
instance_buffer_capacity: buffer_capacity,
instance_staging: Vec::with_capacity(64),
config_buffer,
config_bind_group,
config_bind_group_layout,
atlas,
temp_uv_map: SecondaryMap::new(),
text_cache: FxHashMap::default(),
render_data: RenderData::new(),
external_bind_groups: FxHashMap::default(),
})
}
#[inline]
fn create_instance() -> wgpu::Instance {
wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::DX12,
flags: wgpu::InstanceFlags::empty(),
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
backend_options: wgpu::BackendOptions {
dx12: wgpu::Dx12BackendOptions {
shader_compiler: wgpu::Dx12Compiler::default(),
presentation_system: wgpu::Dx12SwapchainKind::DxgiFromVisual, ..Default::default()
},
..Default::default()
},
display: None,
})
}
#[inline]
fn surface_config(
size: LayoutSize,
surface: &wgpu::Surface,
adapter: &wgpu::Adapter,
) -> wgpu::SurfaceConfiguration {
let caps = surface.get_capabilities(adapter);
let surface_format = caps
.formats
.iter()
.copied()
.find(wgpu::TextureFormat::is_srgb) .unwrap_or(caps.formats[0]);
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width.max(1.0) as u32,
height: size.height.max(1.0) as u32,
present_mode: wgpu::PresentMode::Fifo, alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
view_formats: vec![],
desired_maximum_frame_latency: 2,
color_space: wgpu::SurfaceColorSpace::Auto,
}
}
#[inline]
fn create_buffer(size: LayoutSize, scale_factor: f32, device: &wgpu::Device) -> wgpu::Buffer {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Global Config Buffer"),
contents: bytemuck::cast_slice(&[GlobalConfig {
screen_size: [size.width, size.height],
scale: scale_factor,
_padding: 0.0,
}]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})
}
#[inline]
fn create_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
label: None,
})
}
#[inline]
fn create_render_pipeline(
pipeline_layout: &wgpu::PipelineLayout,
device: &wgpu::Device,
shader: &wgpu::ShaderModule,
config: &wgpu::SurfaceConfiguration,
) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(pipeline_layout),
vertex: wgpu::VertexState {
module: shader,
entry_point: Some("vs_main"),
buffers: &[Some(wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 0,
shader_location: 0,
}],
})],
compilation_options: PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
})
}
#[inline]
fn create_punchout_pipeline(
pipeline_layout: &wgpu::PipelineLayout,
device: &wgpu::Device,
shader: &wgpu::ShaderModule,
config: &wgpu::SurfaceConfiguration,
) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Punchout Render Pipeline"),
layout: Some(pipeline_layout),
vertex: wgpu::VertexState {
module: shader,
entry_point: Some("vs_main"),
buffers: &[Some(wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 0,
shader_location: 0,
}],
})],
compilation_options: PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add,
},
}),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
})
}
#[inline]
fn create_buffer_init(device: &wgpu::Device) -> (wgpu::Buffer, wgpu::Buffer) {
let vertices = [
Vertex {
position: [0.0, 0.0],
},
Vertex {
position: [1.0, 0.0],
},
Vertex {
position: [1.0, 1.0],
},
Vertex {
position: [0.0, 1.0],
},
];
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let indices: [u16; 6] = [0, 1, 2, 2, 3, 0];
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(&indices),
usage: wgpu::BufferUsages::INDEX,
});
(vertex_buffer, index_buffer)
}
#[inline]
fn create_instance_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Instance Storage Buffer"),
size: (capacity * std::mem::size_of::<QuadInstance>()) as wgpu::BufferAddress,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
}
#[inline]
fn create_bind_group(
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
config: &wgpu::Buffer,
instance: &wgpu::Buffer,
atlas: &TextureAtlas,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: config.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&atlas.view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&atlas.sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: instance.as_entire_binding(),
},
],
label: None,
})
}
#[expect(clippy::cast_precision_loss)]
pub(crate) fn resize(&mut self, new_physical_size: (u32, u32), scale_factor: f32) {
if new_physical_size.0 > 0 && new_physical_size.1 > 0 {
self.config.width = new_physical_size.0;
self.config.height = new_physical_size.1;
self.surface.configure(&self.device, &self.config);
let logical_width = new_physical_size.0 as f32 / scale_factor;
let logical_height = new_physical_size.1 as f32 / scale_factor;
let config_data = GlobalConfig {
screen_size: [logical_width, logical_height],
scale: scale_factor,
_padding: 0.0,
};
self.queue
.write_buffer(&self.config_buffer, 0, bytemuck::cast_slice(&[config_data]));
}
}
pub(crate) fn render(&mut self, cx: &mut Context, scale_factor: f32) {
let _context_guard = crate::bind_context(cx);
for id in cx.topology.topo_despawned_queue.drain(..) {
self.external_bind_groups.remove(&id);
}
Pipeline::collect_render_data(
cx,
&mut RendererView {
render_data: &mut self.render_data,
atlas: &mut self.atlas,
text_cache: &mut self.text_cache,
queue: &self.queue,
},
);
if self.render_data.batches.is_empty() {
return;
}
let mut render_data = std::mem::take(&mut self.render_data);
let wgpu::CurrentSurfaceTexture::Success(surface_texture) =
self.surface.get_current_texture()
else {
return;
};
let view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
self.instance_staging.clear();
for (i, instance) in render_data.instances.iter().enumerate() {
let entity_id = render_data.entity_ids[i];
let (inst, _) = WgpuRenderer::build_quad_instance_for_entity(cx, entity_id, instance);
self.instance_staging.push(inst);
}
self.ensure_instance_buffer_capacity(self.instance_staging.len());
self.update_external_texture_bind_groups(cx);
self.queue.write_buffer(
&self.instance_buffer,
0,
bytemuck::cast_slice(&self.instance_staging),
);
#[cfg(feature = "trace-lifecycle")]
trace_lifecycle!(None, &mut cx.debug, || MichiuTrace::WriteBuffer {
staging: Arc::from(self.instance_staging.clone()),
add: None,
});
let mut encoder = self
.device
.create_command_encoder(&CommandEncoderDescriptor::default());
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Background Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
..Default::default()
});
rpass.set_pipeline(&self.pipeline);
rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
rpass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
let mut instance_offset = 0;
let mut current_pipeline = None;
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
for batch in &render_data.batches {
let offset = batch.instance_offset as u32;
let count = batch.instance_count as u32;
if count == 0 {
continue;
}
let needed_pipeline = match batch.batch_type {
BatchType::Normal => &self.pipeline,
BatchType::Punchout => &self.punchout_pipeline,
};
if current_pipeline != Some(std::ptr::from_ref(needed_pipeline)) {
rpass.set_pipeline(needed_pipeline);
current_pipeline = Some(std::ptr::from_ref(needed_pipeline));
}
self.bind_texture_for_batch(&mut rpass, batch, &render_data.entity_ids);
let clip = batch.scissor_rect;
let phys_x = (clip.x * scale_factor).round().max(0.0) as u32;
let phys_y = (clip.y * scale_factor).round().max(0.0) as u32;
let phys_w = (clip.width * scale_factor).round().max(0.0) as u32;
let phys_h = (clip.height * scale_factor).round().max(0.0) as u32;
let target_w = self.config.width;
let target_h = self.config.height;
if phys_x >= target_w || phys_y >= target_h {
continue;
}
let clamped_w = phys_w.min(target_w - phys_x);
let clamped_h = phys_h.min(target_h - phys_y);
if clamped_w == 0 || clamped_h == 0 {
continue;
}
rpass.set_scissor_rect(phys_x, phys_y, clamped_w, clamped_h);
rpass.draw_indexed(0..6, 0, offset..(offset + count));
}
}
self.queue.submit(Some(encoder.finish()));
self.queue.present(surface_texture);
#[cfg(feature = "trace-lifecycle")]
trace_lifecycle!(None, &mut cx.debug, || MichiuTrace::Present { add: None });
self.device.poll(wgpu::PollType::Poll);
self.render_data = render_data;
}
fn update_external_texture_bind_groups(&mut self, cx: &Context) {
for (id, provider) in &cx.contents.cont_external_textures {
let view = provider.resolve_view(&self.device, &self.queue);
let need_update = self
.external_bind_groups
.get(&id)
.is_none_or(|(cached_id, _)| *cached_id != view);
if need_update {
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.config_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.config_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.atlas.sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: self.instance_buffer.as_entire_binding(),
},
],
label: Some("External Texture Bind Group"),
});
self.external_bind_groups.insert(id, (view, bind_group));
}
}
}
fn bind_texture_for_batch<'a>(
&'a self,
rpass: &mut wgpu::RenderPass<'a>,
batch: &DrawBatch,
entity_ids: &[EntityId],
) {
if let Some(&first_id) = entity_ids.get(batch.instance_offset) {
if let Some((_, bind_group)) = self.external_bind_groups.get(&first_id) {
rpass.set_bind_group(0, bind_group, &[]);
return;
}
}
rpass.set_bind_group(0, &self.config_bind_group, &[]);
}
#[track_caller]
fn build_quad_instance_for_entity(
cx: &mut Context,
entity_id: EntityId,
instance: &QuadInstance,
) -> (QuadInstance, bool) {
let basic =
&cx.layouts
.lay_resolved_basic
.find_or(entity_id, &DEFAULT_BASIC, &mut cx.debug);
let flex = &cx
.layouts
.lay_resolved_flex
.find_or(entity_id, &DEFAULT_FLEX, &mut cx.debug);
let _grid = &cx
.layouts
.lay_resolved_grid
.find_or_default(entity_id, &mut cx.debug);
let default_visual = VisualProperty::default();
let visual = cx
.renders
.rnd_visual
.find(entity_id)
.unwrap_or(&default_visual);
let origin = visual.transform_origin.map_or([0.5, 0.5], |p| [p.x, p.y]);
let opacity = visual.opacity.unwrap_or(1.0);
let box_sizing_val = match basic.box_sizing {
BoxSizing::BorderBox => 0.0f32,
BoxSizing::ContentBox => 1.0f32,
};
let border_lengths = visual.border_lengths.unwrap_or(EdgeInsets::px_all(1.0));
let styles = visual.border_styles.unwrap_or([BorderStyle::Solid; 4]);
let aligns = visual
.border_alignments
.unwrap_or([BorderAlignment::Start; 4]);
let mut border_flags = 0u32;
for i in 0..4 {
let s_val = styles[i] as u32;
let a_val = aligns[i] as u32;
border_flags |= s_val << (i * 4);
border_flags |= a_val << (i * 4 + 2);
}
let o_width = visual.outline_width.unwrap_or_default();
let o_color = visual.outline_color.unwrap_or_default();
let o_lengths = visual.outline_lengths.unwrap_or(EdgeInsets::px_all(1.0));
let o_offset = visual.outline_offset.unwrap_or(0.0);
let o_styles = visual.outline_styles.unwrap_or([BorderStyle::Solid; 4]);
let o_aligns = visual
.outline_alignments
.unwrap_or([BorderAlignment::Start; 4]);
let mut outline_flags = 0u32;
for i in 0..4 {
let s_val = o_styles[i] as u32;
let a_val = o_aligns[i] as u32;
outline_flags |= s_val << (i * 4);
outline_flags |= a_val << (i * 4 + 2);
}
let shadow_color =
if instance.shadow_color != Color::TRANSPARENT && visual.shadow_params.is_some() {
let mut color = visual.shadow_color.unwrap_or_default();
let mut has_active_visual_parent = false;
let mut curr_id = entity_id;
while let Some(parent_id) = *cx.topology.topo_parents.at(curr_id) {
if cx
.topology
.topo_active_masks
.at(parent_id)
.has_external_visual_content()
&& cx.renders.rnd_active_external_visual.contains(&parent_id)
{
has_active_visual_parent = true;
break;
}
curr_id = parent_id;
}
if has_active_visual_parent {
color.a *= 0.45;
}
color
} else {
Color::TRANSPARENT
};
let shadow_params = if shadow_color == Color::TRANSPARENT {
[0.0; 4]
} else {
match visual.shadow_params {
Some(shadow) => [shadow.offset.x, shadow.offset.y, shadow.blur, shadow.spread],
None => [0.0; 4],
}
};
let packed_transform = instance.transform;
(
#[expect(clippy::cast_precision_loss)]
QuadInstance {
rect: instance.rect,
transform: packed_transform,
transform_origin: origin,
color: instance.color,
corner_radius: visual.corner_radius.unwrap_or_default(),
border_width: instance.border_width,
border_color: visual.border_color.unwrap_or_default(),
border_lengths,
opacity_mode_sizing: [
opacity,
instance.opacity_mode_sizing[1], box_sizing_val,
border_flags as f32,
],
uv_min: instance.uv_min,
uv_max: instance.uv_max,
gradient_end_color: instance.gradient_end_color,
gradient_angle: instance.gradient_angle,
_padding: 0.0,
shadow_color,
shadow_params,
outline_width: o_width,
outline_color: o_color,
outline_lengths: o_lengths,
outline_offset_and_flags: [o_offset, outline_flags as f32, 0.0, 0.0],
alpha_mode_y_flip_srgb_gamma: instance.alpha_mode_y_flip_srgb_gamma,
},
false, )
}
fn ensure_instance_buffer_capacity(&mut self, total_instances: usize) {
if total_instances > self.instance_buffer_capacity {
let new_capacity = (self.instance_buffer_capacity * 3 / 2)
.max(total_instances)
.max(64);
let size = (new_capacity * std::mem::size_of::<QuadInstance>()) as wgpu::BufferAddress;
self.instance_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Instance Storage Buffer"),
size,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.instance_buffer_capacity = new_capacity;
self.external_bind_groups.clear();
self.config_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.config_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.config_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&self.atlas.view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.atlas.sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: self.instance_buffer.as_entire_binding(),
},
],
label: None,
});
}
}
#[inline]
#[must_use]
pub fn surface(&self) -> &wgpu::Surface<'_> {
&self.surface
}
#[inline]
#[must_use]
pub fn device(&self) -> &wgpu::Device {
&self.device
}
#[inline]
#[must_use]
pub fn queue(&self) -> &wgpu::Queue {
&self.queue
}
#[inline]
#[must_use]
pub fn config(&self) -> &wgpu::SurfaceConfiguration {
&self.config
}
#[inline]
#[must_use]
pub fn pipeline(&self) -> &wgpu::RenderPipeline {
&self.pipeline
}
#[inline]
#[must_use]
pub fn punchout_pipeline(&self) -> &wgpu::RenderPipeline {
&self.punchout_pipeline
}
#[inline]
#[must_use]
pub fn vertex_buffer(&self) -> &wgpu::Buffer {
&self.vertex_buffer
}
#[inline]
#[must_use]
pub fn index_buffer(&self) -> &wgpu::Buffer {
&self.index_buffer
}
#[inline]
#[must_use]
pub fn instance_buffer(&self) -> &wgpu::Buffer {
&self.instance_buffer
}
#[inline]
#[must_use]
pub fn instance_buffer_capacity(&self) -> &usize {
&self.instance_buffer_capacity
}
#[inline]
#[must_use]
pub fn config_buffer(&self) -> &wgpu::Buffer {
&self.config_buffer
}
#[inline]
#[must_use]
pub fn config_bind_group(&self) -> &wgpu::BindGroup {
&self.config_bind_group
}
#[inline]
#[must_use]
pub fn config_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
&self.config_bind_group_layout
}
#[inline]
#[must_use]
pub fn atlas(&self) -> &TextureAtlas {
&self.atlas
}
#[inline]
#[must_use]
pub fn temp_uv_map(&self) -> &SecondaryMap<EntityId, [f32; 4]> {
&self.temp_uv_map
}
#[inline]
#[must_use]
pub fn text_cache(&self) -> &FxHashMap<TextCacheKey, TextCacheValue> {
&self.text_cache
}
#[inline]
#[must_use]
pub fn render_data(&self) -> &RenderData {
&self.render_data
}
#[inline]
#[must_use]
pub fn external_bind_groups(
&self,
) -> &FxHashMap<EntityId, (wgpu::TextureView, wgpu::BindGroup)> {
&self.external_bind_groups
}
#[inline]
pub(crate) fn raw_wgpu_renderer_mut(&mut self) -> RawWgpuRenderer<'_> {
RawWgpuRenderer {
surface: &mut self.surface,
device: &mut self.device,
queue: &mut self.queue,
config: &mut self.config,
pipeline: &mut self.pipeline,
punchout_pipeline: &mut self.punchout_pipeline,
vertex_buffer: &mut self.vertex_buffer,
index_buffer: &mut self.index_buffer,
instance_buffer: &mut self.instance_buffer,
instance_buffer_capacity: &mut self.instance_buffer_capacity,
instance_staging: &mut self.instance_staging,
config_buffer: &mut self.config_buffer,
config_bind_group: &mut self.config_bind_group,
config_bind_group_layout: &mut self.config_bind_group_layout,
atlas: &mut self.atlas,
temp_uv_map: &mut self.temp_uv_map,
text_cache: &mut self.text_cache,
render_data: &mut self.render_data,
external_bind_groups: &mut self.external_bind_groups,
}
}
}
pub struct RawWgpuRenderer<'a> {
pub surface: &'a mut wgpu::Surface<'static>,
pub device: &'a mut wgpu::Device,
pub queue: &'a mut wgpu::Queue,
pub config: &'a mut wgpu::SurfaceConfiguration,
pub pipeline: &'a mut wgpu::RenderPipeline,
pub punchout_pipeline: &'a mut wgpu::RenderPipeline,
pub vertex_buffer: &'a mut wgpu::Buffer,
pub index_buffer: &'a mut wgpu::Buffer,
pub instance_buffer: &'a mut wgpu::Buffer,
pub instance_buffer_capacity: &'a mut usize,
pub instance_staging: &'a mut Vec<QuadInstance>,
pub config_buffer: &'a mut wgpu::Buffer,
pub config_bind_group: &'a mut wgpu::BindGroup,
pub config_bind_group_layout: &'a mut wgpu::BindGroupLayout,
pub atlas: &'a mut TextureAtlas,
pub temp_uv_map: &'a mut SecondaryMap<EntityId, [f32; 4]>,
pub text_cache: &'a mut FxHashMap<TextCacheKey, TextCacheValue>,
pub render_data: &'a mut RenderData,
pub external_bind_groups: &'a mut FxHashMap<EntityId, (wgpu::TextureView, wgpu::BindGroup)>,
}