Skip to main content

shape_viz_core/renderer/
context.rs

1//! Rendering context for drawing operations
2
3use crate::error::Result;
4use crate::style::ChartStyle;
5use crate::theme::{ChartTheme, Color};
6use crate::viewport::{Rect, Viewport};
7use std::sync::Arc;
8
9use super::vertex::{Vertex, VertexBuffer};
10
11#[cfg(feature = "text-rendering")]
12use crate::text::{TextAnchor, TextBaseline, TextItem};
13
14/// Rendering context passed to layers for drawing operations
15pub struct RenderContext {
16    /// GPU device
17    pub device: Arc<wgpu::Device>,
18    /// GPU queue
19    pub queue: Arc<wgpu::Queue>,
20    /// Vertex buffers for batching
21    vertex_buffers: Vec<VertexBuffer>,
22    /// Current vertices being batched
23    current_vertices: Vec<Vertex>,
24    /// Maximum vertices per buffer
25    max_vertices_per_buffer: u32,
26    /// Buffers that contain freshly uploaded geometry awaiting a draw call
27    pending_draws: Vec<usize>,
28    /// Index of next buffer to use in current frame (prevents reuse before GPU finishes)
29    next_buffer_index: usize,
30    /// Current viewport
31    viewport: Viewport,
32    /// Current theme
33    theme: ChartTheme,
34    /// Current style parameters
35    style: ChartStyle,
36    /// Text items grouped by stage for proper z-ordering
37    #[cfg(feature = "text-rendering")]
38    staged_text: std::collections::BTreeMap<i32, Vec<TextItem>>,
39    /// Current stage priority for text grouping (derived from LayerStage)
40    #[cfg(feature = "text-rendering")]
41    current_stage_priority: i32,
42}
43
44impl RenderContext {
45    /// Create a new render context
46    pub fn new(
47        device: Arc<wgpu::Device>,
48        queue: Arc<wgpu::Queue>,
49        viewport: Viewport,
50        theme: ChartTheme,
51        style: ChartStyle,
52    ) -> Self {
53        Self {
54            device,
55            queue,
56            vertex_buffers: Vec::new(),
57            current_vertices: Vec::new(),
58            max_vertices_per_buffer: 100_000, // 100K vertices per buffer
59            pending_draws: Vec::new(),
60            next_buffer_index: 0,
61            viewport,
62            theme,
63            style,
64            #[cfg(feature = "text-rendering")]
65            staged_text: std::collections::BTreeMap::new(),
66            #[cfg(feature = "text-rendering")]
67            current_stage_priority: 0,
68        }
69    }
70
71    /// Set the current stage priority for text grouping
72    #[cfg(feature = "text-rendering")]
73    pub fn set_stage_priority(&mut self, priority: i32) {
74        self.current_stage_priority = priority;
75    }
76
77    /// Get the current viewport
78    pub fn viewport(&self) -> &Viewport {
79        &self.viewport
80    }
81
82    /// Get the current theme
83    pub fn theme(&self) -> &ChartTheme {
84        &self.theme
85    }
86
87    /// Get the current style
88    pub fn style(&self) -> &ChartStyle {
89        &self.style
90    }
91
92    /// Update viewport and theme
93    pub fn update(&mut self, viewport: Viewport, theme: ChartTheme, style: ChartStyle) {
94        self.viewport = viewport;
95        self.theme = theme;
96        self.style = style;
97    }
98
99    /// Add vertices to the current batch
100    pub fn add_vertices(&mut self, vertices: &[Vertex]) {
101        self.current_vertices.extend_from_slice(vertices);
102    }
103
104    /// Add a single vertex to the current batch
105    pub fn add_vertex(&mut self, vertex: Vertex) {
106        self.current_vertices.push(vertex);
107    }
108
109    /// Draw a line between two points
110    pub fn draw_line(&mut self, start: [f32; 2], end: [f32; 2], color: Color, thickness: f32) {
111        let half_thickness = thickness * 0.5;
112
113        // Calculate perpendicular vector for line thickness
114        let dx = end[0] - start[0];
115        let dy = end[1] - start[1];
116        let len = (dx * dx + dy * dy).sqrt();
117
118        if len > 0.0 {
119            let nx = -dy / len * half_thickness;
120            let ny = dx / len * half_thickness;
121
122            // Create quad for the line
123            let vertices = [
124                Vertex::new([start[0] + nx, start[1] + ny], color),
125                Vertex::new([start[0] - nx, start[1] - ny], color),
126                Vertex::new([end[0] + nx, end[1] + ny], color),
127                Vertex::new([start[0] - nx, start[1] - ny], color),
128                Vertex::new([end[0] - nx, end[1] - ny], color),
129                Vertex::new([end[0] + nx, end[1] + ny], color),
130            ];
131
132            self.add_vertices(&vertices);
133        }
134    }
135
136    /// Draw a dashed line between two points
137    pub fn draw_dashed_line(
138        &mut self,
139        start: [f32; 2],
140        end: [f32; 2],
141        color: Color,
142        thickness: f32,
143        dash: f32,
144        gap: f32,
145    ) {
146        // Early out for degenerate line
147        let dx = end[0] - start[0];
148        let dy = end[1] - start[1];
149        let len = (dx * dx + dy * dy).sqrt();
150        if len == 0.0 {
151            return;
152        }
153        // Normalized direction
154        let dir_x = dx / len;
155        let dir_y = dy / len;
156        let mut distance = 0.0;
157        let mut current_start = start;
158        while distance < len {
159            let seg_len = (dash).min(len - distance);
160            let current_end = [
161                current_start[0] + dir_x * seg_len,
162                current_start[1] + dir_y * seg_len,
163            ];
164            self.draw_line(current_start, current_end, color, thickness);
165            // Advance by dash + gap
166            distance += dash + gap;
167            current_start = [start[0] + dir_x * distance, start[1] + dir_y * distance];
168        }
169    }
170
171    /// Draw a rectangle
172    pub fn draw_rect(&mut self, rect: Rect, color: Color) {
173        let x1 = rect.x;
174        let y1 = rect.y;
175        let x2 = rect.x + rect.width;
176        let y2 = rect.y + rect.height;
177
178        let vertices = [
179            Vertex::new([x1, y1], color),
180            Vertex::new([x2, y1], color),
181            Vertex::new([x1, y2], color),
182            Vertex::new([x2, y1], color),
183            Vertex::new([x2, y2], color),
184            Vertex::new([x1, y2], color),
185        ];
186
187        self.add_vertices(&vertices);
188    }
189
190    /// Draw a filled triangle
191    pub fn draw_triangle(&mut self, p1: [f32; 2], p2: [f32; 2], p3: [f32; 2], color: Color) {
192        let vertices = [
193            Vertex::new(p1, color),
194            Vertex::new(p2, color),
195            Vertex::new(p3, color),
196        ];
197
198        self.add_vertices(&vertices);
199    }
200
201    /// Draw a filled circle (approximated with triangles)
202    pub fn draw_circle(&mut self, center: [f32; 2], radius: f32, color: Color) {
203        const SEGMENTS: u32 = 16;
204        let angle_step = std::f32::consts::TAU / SEGMENTS as f32;
205
206        for i in 0..SEGMENTS {
207            let angle1 = i as f32 * angle_step;
208            let angle2 = (i + 1) as f32 * angle_step;
209
210            let p1 = [
211                center[0] + radius * angle1.cos(),
212                center[1] + radius * angle1.sin(),
213            ];
214            let p2 = [
215                center[0] + radius * angle2.cos(),
216                center[1] + radius * angle2.sin(),
217            ];
218
219            self.draw_triangle(center, p1, p2, color);
220        }
221    }
222
223    /// Draw a filled rounded rectangle
224    pub fn draw_rounded_rect(&mut self, rect: Rect, radius: f32, color: Color) {
225        let r = radius.min(rect.width / 2.0).min(rect.height / 2.0);
226
227        // Center rectangle (full width, minus top/bottom radius)
228        self.draw_rect(
229            Rect::new(rect.x, rect.y + r, rect.width, rect.height - 2.0 * r),
230            color,
231        );
232
233        // Top rectangle (between corners)
234        self.draw_rect(
235            Rect::new(rect.x + r, rect.y, rect.width - 2.0 * r, r),
236            color,
237        );
238
239        // Bottom rectangle (between corners)
240        self.draw_rect(
241            Rect::new(
242                rect.x + r,
243                rect.y + rect.height - r,
244                rect.width - 2.0 * r,
245                r,
246            ),
247            color,
248        );
249
250        // Four corner quarter-circles
251        const SEGMENTS: u32 = 8;
252        let angle_step = std::f32::consts::FRAC_PI_2 / SEGMENTS as f32;
253
254        // Top-left corner
255        let tl_center = [rect.x + r, rect.y + r];
256        for i in 0..SEGMENTS {
257            let a1 = std::f32::consts::PI + i as f32 * angle_step;
258            let a2 = std::f32::consts::PI + (i + 1) as f32 * angle_step;
259            self.draw_triangle(
260                tl_center,
261                [tl_center[0] + r * a1.cos(), tl_center[1] + r * a1.sin()],
262                [tl_center[0] + r * a2.cos(), tl_center[1] + r * a2.sin()],
263                color,
264            );
265        }
266
267        // Top-right corner
268        let tr_center = [rect.x + rect.width - r, rect.y + r];
269        for i in 0..SEGMENTS {
270            let a1 = -std::f32::consts::FRAC_PI_2 + i as f32 * angle_step;
271            let a2 = -std::f32::consts::FRAC_PI_2 + (i + 1) as f32 * angle_step;
272            self.draw_triangle(
273                tr_center,
274                [tr_center[0] + r * a1.cos(), tr_center[1] + r * a1.sin()],
275                [tr_center[0] + r * a2.cos(), tr_center[1] + r * a2.sin()],
276                color,
277            );
278        }
279
280        // Bottom-right corner
281        let br_center = [rect.x + rect.width - r, rect.y + rect.height - r];
282        for i in 0..SEGMENTS {
283            let a1 = i as f32 * angle_step;
284            let a2 = (i + 1) as f32 * angle_step;
285            self.draw_triangle(
286                br_center,
287                [br_center[0] + r * a1.cos(), br_center[1] + r * a1.sin()],
288                [br_center[0] + r * a2.cos(), br_center[1] + r * a2.sin()],
289                color,
290            );
291        }
292
293        // Bottom-left corner
294        let bl_center = [rect.x + r, rect.y + rect.height - r];
295        for i in 0..SEGMENTS {
296            let a1 = std::f32::consts::FRAC_PI_2 + i as f32 * angle_step;
297            let a2 = std::f32::consts::FRAC_PI_2 + (i + 1) as f32 * angle_step;
298            self.draw_triangle(
299                bl_center,
300                [bl_center[0] + r * a1.cos(), bl_center[1] + r * a1.sin()],
301                [bl_center[0] + r * a2.cos(), bl_center[1] + r * a2.sin()],
302                color,
303            );
304        }
305    }
306
307    /// Draw a filled rectangle with border
308    pub fn draw_rect_with_border(
309        &mut self,
310        rect: Rect,
311        fill_color: Color,
312        border_color: Color,
313        border_width: f32,
314    ) {
315        // Draw fill
316        self.draw_rect(rect, fill_color);
317
318        // Draw border
319        let half_border = border_width * 0.5;
320
321        // Top border
322        self.draw_rect(
323            Rect::new(
324                rect.x - half_border,
325                rect.y - half_border,
326                rect.width + border_width,
327                border_width,
328            ),
329            border_color,
330        );
331
332        // Bottom border
333        self.draw_rect(
334            Rect::new(
335                rect.x - half_border,
336                rect.y + rect.height - half_border,
337                rect.width + border_width,
338                border_width,
339            ),
340            border_color,
341        );
342
343        // Left border
344        self.draw_rect(
345            Rect::new(
346                rect.x - half_border,
347                rect.y - half_border,
348                border_width,
349                rect.height + border_width,
350            ),
351            border_color,
352        );
353
354        // Right border
355        self.draw_rect(
356            Rect::new(
357                rect.x + rect.width - half_border,
358                rect.y - half_border,
359                border_width,
360                rect.height + border_width,
361            ),
362            border_color,
363        );
364    }
365
366    /// Flush the current vertex batch to GPU buffers
367    pub fn flush(&mut self) -> Result<()> {
368        if self.current_vertices.is_empty() {
369            return Ok(());
370        }
371
372        // Use the next available buffer in sequence to avoid reusing buffers
373        // before the GPU has finished reading from them
374        let required_capacity = self.current_vertices.len() as u32;
375
376        // Check if we can use the next buffer in sequence
377        let buffer_index = if self.next_buffer_index < self.vertex_buffers.len() {
378            // Check if existing buffer has enough capacity
379            if self.vertex_buffers[self.next_buffer_index].capacity >= required_capacity {
380                self.next_buffer_index
381            } else {
382                // Need a larger buffer, create new one
383                let capacity = required_capacity.max(self.max_vertices_per_buffer);
384                let buffer = VertexBuffer::new(&self.device, capacity);
385                self.vertex_buffers.push(buffer);
386                self.vertex_buffers.len() - 1
387            }
388        } else {
389            // Need to create a new buffer
390            let capacity = required_capacity.max(self.max_vertices_per_buffer);
391            let buffer = VertexBuffer::new(&self.device, capacity);
392            self.vertex_buffers.push(buffer);
393            self.vertex_buffers.len() - 1
394        };
395
396        // Update the buffer and advance to next
397        self.vertex_buffers[buffer_index].update(&self.queue, &self.current_vertices)?;
398        self.pending_draws.push(buffer_index);
399        self.next_buffer_index = buffer_index + 1;
400
401        self.current_vertices.clear();
402        Ok(())
403    }
404
405    /// Submit any uploaded vertex buffers to the active render pass.
406    pub fn commit(&mut self, render_pass: &mut wgpu::RenderPass) -> Result<()> {
407        if !self.current_vertices.is_empty() {
408            self.flush()?;
409        }
410
411        for &buffer_index in &self.pending_draws {
412            let buffer = &mut self.vertex_buffers[buffer_index];
413            if buffer.vertex_count > 0 {
414                render_pass.set_vertex_buffer(0, buffer.slice());
415                render_pass.draw(0..buffer.vertex_count, 0..1);
416                buffer.vertex_count = 0;
417            }
418        }
419
420        self.pending_draws.clear();
421        Ok(())
422    }
423
424    /// Get all vertex buffers for rendering
425    pub fn vertex_buffers(&self) -> &[VertexBuffer] {
426        &self.vertex_buffers
427    }
428
429    /// Clear all batched vertices and reset buffer allocation for new frame
430    pub fn clear(&mut self) {
431        self.current_vertices.clear();
432        self.pending_draws.clear();
433        // Reset buffer index for new frame - buffers can be reused once
434        // the previous frame's render pass has completed
435        self.next_buffer_index = 0;
436        for buffer in &mut self.vertex_buffers {
437            buffer.vertex_count = 0;
438        }
439        #[cfg(feature = "text-rendering")]
440        {
441            self.staged_text.clear();
442            self.current_stage_priority = 0;
443        }
444    }
445
446    /// Get all text items for rendering (flattened from all stages, in order)
447    #[cfg(feature = "text-rendering")]
448    pub fn text_items(&self) -> Vec<TextItem> {
449        self.staged_text
450            .values()
451            .flat_map(|items| items.iter().cloned())
452            .collect()
453    }
454
455    /// Get text items for a specific stage priority
456    #[cfg(feature = "text-rendering")]
457    pub fn text_items_for_stage(&self, stage_priority: i32) -> &[TextItem] {
458        self.staged_text
459            .get(&stage_priority)
460            .map(|v| v.as_slice())
461            .unwrap_or(&[])
462    }
463
464    /// Get all stage priorities that have text
465    #[cfg(feature = "text-rendering")]
466    pub fn text_stage_priorities(&self) -> Vec<i32> {
467        self.staged_text.keys().copied().collect()
468    }
469
470    /// Take text items for a specific stage (removes them from context)
471    #[cfg(feature = "text-rendering")]
472    pub fn take_text_for_stage(&mut self, stage_priority: i32) -> Vec<TextItem> {
473        self.staged_text.remove(&stage_priority).unwrap_or_default()
474    }
475
476    /// Draw text at the specified position
477    #[cfg(feature = "text-rendering")]
478    pub fn draw_text(&mut self, text: &str, x: f32, y: f32, color: Color, font_size: Option<f32>) {
479        self.staged_text
480            .entry(self.current_stage_priority)
481            .or_default()
482            .push(TextItem {
483                text: text.to_string(),
484                x,
485                y,
486                font_size: font_size.unwrap_or(12.0),
487                color,
488                anchor: TextAnchor::Start,
489                baseline: TextBaseline::Top,
490            });
491    }
492
493    /// Draw text with anchor and baseline options
494    #[cfg(feature = "text-rendering")]
495    pub fn draw_text_anchored(
496        &mut self,
497        text: &str,
498        x: f32,
499        y: f32,
500        color: Color,
501        font_size: Option<f32>,
502        anchor: TextAnchor,
503        baseline: TextBaseline,
504    ) {
505        self.staged_text
506            .entry(self.current_stage_priority)
507            .or_default()
508            .push(TextItem {
509                text: text.to_string(),
510                x,
511                y,
512                font_size: font_size.unwrap_or(12.0),
513                color,
514                anchor,
515                baseline,
516            });
517    }
518}