1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//! Renderer for the Grafo library.
use std::num::NonZeroUsize;
use std::sync::Arc;
use ahash::{HashMap, HashMapExt};
use lyon::tessellation::FillTessellator;
use tracing::warn;
use wgpu::{BindGroup, BufferUsages, CompositeAlphaMode, InstanceDescriptor, SurfaceTarget};
use crate::effect::{
self, compile_composite_pipeline, compile_effect_pipeline, create_params_bind_group,
EffectError, EffectInstance, LoadedEffect, OffscreenTexturePool,
};
use crate::pipeline::{
compute_padded_bytes_per_row, create_and_depth_texture, create_argb_swizzle_bind_group,
create_argb_swizzle_pipeline, create_msaa_color_texture, create_offscreen_color_texture,
create_pipeline, create_readback_buffer, create_storage_input_buffer,
create_storage_output_buffer, encode_copy_texture_to_buffer, render_buffer_range_to_texture,
ArgbParams, PipelineType, Uniforms,
};
use crate::shape::{CachedShapeDrawData, DrawShapeCommand, Shape, ShapeDrawData};
use crate::texture_manager::TextureManager;
use crate::util::{to_logical, PoolManager};
use crate::vertex::{InstanceColor, InstanceMetadata, InstanceTransform};
use crate::CachedShape;
use crate::Color;
#[cfg(feature = "render_metrics")]
use self::metrics::RenderLoopMetricsTracker;
use self::types::{DrawCommand, RendererScratch};
mod construction;
mod draw_queue;
mod effects;
#[cfg(feature = "render_metrics")]
pub mod metrics;
mod passes;
mod preparation;
mod readback;
mod rect_utils;
pub use construction::RendererCreationError;
mod rendering;
mod surface;
mod traversal;
mod types;
pub type MathRect = lyon::math::Box2D;
// TODO: move to the config/constructor
const MAX_CACHED_SHAPES: usize = 1024;
/// Semantic texture layers for a shape. Background is layer 0, Foreground is layer 1.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum TextureLayer {
Background,
Foreground,
}
impl From<TextureLayer> for usize {
fn from(value: TextureLayer) -> Self {
match value {
TextureLayer::Background => 0,
TextureLayer::Foreground => 1,
}
}
}
/// The renderer for the Grafo library. This is the main struct used to render shapes and images.
pub struct Renderer<'a> {
// Window information
/// Size of the window in pixels.
pub(crate) physical_size: (u32, u32),
/// Scale factor of the window (e.g., for high-DPI displays).
scale_factor: f64,
/// AA fringe offset in physical pixels. Controls how far the anti-aliasing
/// fringe extends outward from shape edges. Default is 0.75.
fringe_width: f32,
// WGPU components
instance: wgpu::Instance,
surface: Option<wgpu::Surface<'a>>,
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
config: wgpu::SurfaceConfiguration,
tessellator: FillTessellator,
buffers_pool_manager: PoolManager,
texture_manager: TextureManager,
/// Tree structure holding shapes to be rendered.
draw_tree: easy_tree::Tree<DrawCommand>,
/// Maps node metadata indices to their clip-parent node ids.
metadata_to_clips: HashMap<usize, usize>,
/// Uniforms for the stencil-increment ("and") rendering pipeline.
and_uniforms: Uniforms,
/// GPU buffer backing the "and" pipeline uniforms.
and_uniform_buffer: wgpu::Buffer,
/// Bind group for the stencil-increment rendering pipeline.
and_bind_group: BindGroup,
/// Render pipeline for stencil-increment operations.
and_pipeline: Arc<wgpu::RenderPipeline>,
/// Bind group layouts for shape texture layers (groups 1 and 2).
shape_texture_bind_group_layout_background: Arc<wgpu::BindGroupLayout>,
shape_texture_bind_group_layout_foreground: Arc<wgpu::BindGroupLayout>,
/// Monotonic counter to invalidate cached shape texture bind groups when the layout changes.
shape_texture_layout_epoch: u64,
/// Default transparent texture bind groups for both layers.
default_shape_texture_bind_groups: [Arc<wgpu::BindGroup>; 2], // [background, foreground]
/// Render pipeline for decrementing stencil values.
decrementing_pipeline: Arc<wgpu::RenderPipeline>,
/// Uniforms for the decrementing pipeline.
decrementing_uniforms: Uniforms,
/// GPU buffer backing the decrementing pipeline uniforms.
decrementing_uniform_buffer: wgpu::Buffer,
/// Bind group for the decrementing pipeline.
decrementing_bind_group: BindGroup,
temp_vertices: Vec<crate::vertex::CustomVertex>,
temp_indices: Vec<u16>,
/// Per-frame map from cache key to (index_start, index_count) in the
/// aggregated buffers, used to avoid duplicating vertex/index data for
/// cached shapes that share the same geometry.
geometry_dedup_map: HashMap<u64, (usize, usize)>,
/// Per-frame instance transforms for shapes.
temp_instance_transforms: Vec<InstanceTransform>,
/// Per-frame instance colors for shapes.
temp_instance_colors: Vec<InstanceColor>,
/// Per-frame instance metadata (draw order) for shapes.
temp_instance_metadata: Vec<InstanceMetadata>,
aggregated_vertex_buffer: Option<wgpu::Buffer>,
aggregated_index_buffer: Option<wgpu::Buffer>,
aggregated_instance_transform_buffer: Option<wgpu::Buffer>,
aggregated_instance_color_buffer: Option<wgpu::Buffer>,
aggregated_instance_metadata_buffer: Option<wgpu::Buffer>,
identity_instance_transform_buffer: Option<wgpu::Buffer>,
identity_instance_color_buffer: Option<wgpu::Buffer>,
identity_instance_metadata_buffer: Option<wgpu::Buffer>,
/// Loaded shapes to reuse later during rendering without loading/tessellating again.
/// Not an LRU cache: evicting a shape also results in it not being rendered.
shape_cache: HashMap<u64, CachedShape>,
// Cached resources for render_to_argb32 compute swizzle path
argb_cs_bgl: Option<wgpu::BindGroupLayout>,
argb_cs_pipeline: Option<wgpu::ComputePipeline>,
argb_swizzle_bind_group: Option<wgpu::BindGroup>,
argb_params_buffer: Option<wgpu::Buffer>,
argb_input_buffer: Option<wgpu::Buffer>,
argb_output_storage_buffer: Option<wgpu::Buffer>,
argb_readback_buffer: Option<wgpu::Buffer>,
argb_input_buffer_size: u64,
argb_output_buffer_size: u64,
argb_cached_width: u32,
argb_cached_height: u32,
argb_offscreen_texture: Option<wgpu::Texture>,
// Cached resources for render_to_buffer (BGRA bytes) path
rtb_offscreen_texture: Option<wgpu::Texture>,
rtb_readback_buffer: Option<wgpu::Buffer>,
rtb_cached_width: u32,
rtb_cached_height: u32,
/// Current MSAA sample count (1 = off, 4 = 4x, etc.)
msaa_sample_count: u32,
/// The multisampled color texture (None when sample_count == 1).
msaa_color_texture: Option<wgpu::Texture>,
/// View of the MSAA color texture.
msaa_color_texture_view: Option<wgpu::TextureView>,
/// Cached depth/stencil texture, reused across frames.
/// Recreated on resize or MSAA sample count change.
depth_stencil_texture: Option<wgpu::Texture>,
/// View of the cached depth/stencil texture.
depth_stencil_view: Option<wgpu::TextureView>,
// ── Effect system ──────────────────────────────────────────────────
/// Loaded (compiled) effects, keyed by user-provided effect_id.
loaded_effects: HashMap<u64, LoadedEffect>,
/// Per-node group effect instances, keyed by node_id.
group_effects: HashMap<usize, EffectInstance>,
/// Per-node backdrop effect instances, keyed by node_id.
/// A backdrop effect processes the pixels already rendered behind a shape.
backdrop_effects: HashMap<usize, EffectInstance>,
/// Pool of offscreen textures for effect compositing.
offscreen_texture_pool: OffscreenTexturePool,
/// Shared composite pipeline for drawing effect results into the parent target.
/// Created lazily on first use.
composite_pipeline: Option<wgpu::RenderPipeline>,
/// Bind group layout for the composite pipeline's input texture.
composite_bgl: Option<wgpu::BindGroupLayout>,
/// Reusable sampler for effect texture sampling.
effect_sampler: Option<wgpu::Sampler>,
// ── Backdrop effect infrastructure ─────────────────────────────────
/// Snapshot texture for backdrop effects (copy of current render target).
backdrop_snapshot_texture: Option<wgpu::Texture>,
backdrop_snapshot_view: Option<wgpu::TextureView>,
/// Stencil-only pipeline: writes stencil but no color output.
/// Used for Step 1 of the three-step backdrop draw.
stencil_only_pipeline: Option<wgpu::RenderPipeline>,
/// Shape color pipeline with stencil Keep: draws color but doesn't modify stencil.
/// Used for Step 3 of the three-step backdrop draw.
backdrop_color_pipeline: Option<wgpu::RenderPipeline>,
/// Gradient color pipeline with stencil Keep for backdrop shapes.
backdrop_color_gradient_pipeline: Option<wgpu::RenderPipeline>,
/// Pipeline for rendering leaf nodes (no children) with stencil Equal + Keep.
/// Avoids the redundant increment + decrement pair for childless shapes.
leaf_draw_pipeline: Arc<wgpu::RenderPipeline>,
/// Gradient pipeline for leaf nodes.
leaf_draw_gradient_pipeline: Arc<wgpu::RenderPipeline>,
/// Gradient pipeline for visible non-leaf parents that increment stencil.
and_gradient_pipeline: Arc<wgpu::RenderPipeline>,
// ── Gradient fill infrastructure ───────────────────────────────────
/// Bind group layout for gradient resources (group 3 in shader).
gradient_bind_group_layout: wgpu::BindGroupLayout,
/// Monotonic counter to invalidate cached gradient bind groups when the layout changes.
gradient_bind_group_layout_epoch: u64,
/// Sampler for gradient ramp textures (nearest, clamp-to-edge).
gradient_ramp_sampler: wgpu::Sampler,
#[cfg(feature = "render_metrics")]
/// Tracking for cumulative render-loop timing metrics.
render_loop_metrics_tracker: RenderLoopMetricsTracker,
#[cfg(feature = "render_metrics")]
/// Per-phase timing breakdown for the most recently rendered frame.
last_phase_timings: self::metrics::PhaseTimings,
#[cfg(feature = "render_metrics")]
/// Per-frame pipeline switch counts for the most recently rendered frame.
last_pipeline_switch_counts: self::metrics::PipelineSwitchCounts,
// ── Reusable scratch state ───────────────────────────────────────────
scratch: RendererScratch,
}
/// Default AA fringe width in physical pixels.
const DEFAULT_FRINGE_WIDTH: f32 = 0.75;
impl<'a> Renderer<'a> {
const DEFAULT_FRINGE_WIDTH: f32 = DEFAULT_FRINGE_WIDTH;
pub(super) fn begin_frame_scratch(&mut self) {
self.scratch.begin_frame();
}
pub(super) fn trim_scratch_on_resize_or_policy(&mut self) {
// This is safe to call frequently: `shrink_to` is effectively a no-op
// when capacities are below thresholds, so this acts as amortized
// memory hygiene for long-running sessions.
self.buffers_pool_manager.trim();
self.scratch.trim_to_policy();
}
}