Skip to main content

ry_gfx/
render_queue.rs

1//! # RyDit Render Queue - Command Queue + Double Buffering + Platform Sync
2//!
3//! **TRES CAPAS CRÍTICAS PARA RENDIMIENTO Y COMPATIBILIDAD**
4//!
5//! 1. **Command Queue (8192+ draw calls)** - Acumular comandos de dibujado
6//! 2. **Double Buffering** - Separar lógica de renderizado
7//! 3. **Platform Sync (XFlush/XSync)** - Sincronización con X11
8//!
9//! ## Arquitectura
10//!
11//! ```text
12//! ┌─────────────────────────────────────────────────────────┐
13//! │  RYDIT RENDER QUEUE                                     │
14//! ├─────────────────────────────────────────────────────────┤
15//! │                                                         │
16//! │  ┌─────────────────────────────────────────────────┐   │
17//! │  │  1. COMMAND QUEUE (8192+ draw calls)            │   │
18//! │  │  - Acumular: circle, rect, line, text, etc.     │   │
19//! │  │  - Ejecutar: batch processing                   │   │
20//! │  │  - Buffer circular: head, tail, capacity        │   │
21//! │  └─────────────────────────────────────────────────┘   │
22//! │                         │                               │
23//! │  ┌──────────────────────▼──────────────────────────┐   │
24//! │  │  2. DOUBLE BUFFERING                            │   │
25//! │  │  - Front buffer: lógica (executor)              │   │
26//! │  │  - Back buffer: render (draw calls)             │   │
27//! │  │  - Swap: intercambiar buffers por frame         │   │
28//! │  └─────────────────────────────────────────────────┘   │
29//! │                         │                               │
30//! │  ┌──────────────────────▼──────────────────────────┐   │
31//! │  │  3. PLATFORM SYNC (X11)                         │   │
32//! │  │  - XFlush: forzar flush de comandos             │   │
33//! │  │  - XSync: sincronizar con servidor X11          │   │
34//! │  │  - glFlush: OpenGL buffer swap                  │   │
35//! │  └─────────────────────────────────────────────────┘   │
36//! │                                                         │
37//! └─────────────────────────────────────────────────────────┘
38//! ```
39//!
40//! ## Uso
41//!
42//! ```rust,no_run
43//! use ry_gfx::render_queue::{RenderQueue, DrawCommand};
44//!
45//! let mut queue = RenderQueue::with_capacity(8192);
46//!
47//! // Acumular comandos (front buffer - lógica)
48//! queue.push(DrawCommand::Circle { x: 400, y: 300, radius: 50, color: "rojo" });
49//! queue.push(DrawCommand::Rect { x: 100, y: 100, w: 100, h: 100, color: "verde" });
50//!
51//! // Ejecutar todos los comandos (back buffer - render)
52//! queue.execute(&mut gfx);
53//!
54//! // Platform sync (X11)
55//! queue.platform_sync();
56//! ```
57
58use crate::Assets;
59use crate::{ColorRydit, RyditGfx};
60use std::collections::VecDeque;
61
62// ✅ v0.10.8: PlatformSync ahora viene de v-shield
63pub use v_shield::platform_sync::{PlatformSync, PlatformSyncMode};
64
65// ============================================================================
66// DRAW COMMANDS - Tipos de comandos de dibujado
67// ============================================================================
68
69/// Comandos de dibujado para la queue
70#[derive(Debug, Clone)]
71pub enum DrawCommand {
72    /// draw.circle(x, y, radius, color)
73    Circle {
74        x: i32,
75        y: i32,
76        radius: i32,
77        color: ColorRydit,
78    },
79    /// draw.rect(x, y, w, h, color)
80    Rect {
81        x: i32,
82        y: i32,
83        w: i32,
84        h: i32,
85        color: ColorRydit,
86    },
87    /// draw.line(x1, y1, x2, y2, color)
88    Line {
89        x1: i32,
90        y1: i32,
91        x2: i32,
92        y2: i32,
93        color: ColorRydit,
94    },
95    /// draw.text(text, x, y, size, color)
96    Text {
97        text: String,
98        x: i32,
99        y: i32,
100        size: i32,
101        color: ColorRydit,
102    },
103    /// draw.triangle(v1, v2, v3, color)
104    Triangle {
105        v1: (i32, i32),
106        v2: (i32, i32),
107        v3: (i32, i32),
108        color: ColorRydit,
109    },
110    /// draw.texture(id, x, y, scale, rotation, color)
111    Texture {
112        id: String,
113        x: f32,
114        y: f32,
115        scale: f32,
116        rotation: f32,
117        color: ColorRydit,
118    },
119    /// Limpiar pantalla
120    Clear { color: ColorRydit },
121}
122
123// ============================================================================
124// RENDER QUEUE - Command Queue (8192+ draw calls)
125// ============================================================================
126
127/// Command Queue para draw calls
128///
129/// **Capacidad**: 8192+ comandos por frame
130/// **Estrategia**: Buffer circular con head/tail
131pub struct RenderQueue {
132    /// Cola de comandos (buffer circular)
133    commands: VecDeque<DrawCommand>,
134    /// Capacidad máxima
135    capacity: usize,
136    /// Comandos acumulados en este frame
137    frame_count: usize,
138    /// Total de comandos ejecutados
139    total_executed: usize,
140    /// Estadísticas: máximo de comandos en un frame
141    max_frame_count: usize,
142}
143
144impl RenderQueue {
145    /// Crear queue con capacidad por defecto (8192)
146    pub fn new() -> Self {
147        Self::with_capacity(8192)
148    }
149
150    /// Crear queue con capacidad custom
151    pub fn with_capacity(capacity: usize) -> Self {
152        println!(
153            "[RENDER QUEUE] Command Queue creada: capacidad={} (8192+ draw calls)",
154            capacity
155        );
156        Self {
157            commands: VecDeque::with_capacity(capacity),
158            capacity,
159            frame_count: 0,
160            total_executed: 0,
161            max_frame_count: 0,
162        }
163    }
164
165    /// Push de comando a la queue
166    pub fn push(&mut self, command: DrawCommand) {
167        // Verificar overflow
168        if self.commands.len() >= self.capacity {
169            // Buffer lleno: remover el comando más antiguo (política FIFO)
170            self.commands.pop_front();
171            #[cfg(debug_assertions)]
172            eprintln!(
173                "[RENDER QUEUE] WARNING: Buffer lleno ({}), removiendo comando antiguo",
174                self.capacity
175            );
176        }
177
178        self.commands.push_back(command);
179        self.frame_count += 1;
180    }
181
182    /// Ejecutar todos los comandos acumulados
183    pub fn execute(&mut self, gfx: &mut RyditGfx, assets: &Assets) {
184        if self.commands.is_empty() {
185            return;
186        }
187
188        // Actualizar estadísticas
189        if self.frame_count > self.max_frame_count {
190            self.max_frame_count = self.frame_count;
191        }
192        self.total_executed += self.frame_count;
193
194        #[cfg(debug_assertions)]
195        eprintln!(
196            "[RENDER QUEUE] Ejecutando {} comandos (total: {}, max frame: {})",
197            self.frame_count, self.total_executed, self.max_frame_count
198        );
199
200        // Ejecutar todos los comandos EN UN SOLO begin_draw
201        {
202            let mut d = gfx.begin_draw();
203
204            for command in self.commands.drain(..) {
205                match command {
206                    DrawCommand::Circle {
207                        x,
208                        y,
209                        radius,
210                        color,
211                    } => {
212                        d.draw_circle(x, y, radius, color);
213                    }
214                    DrawCommand::Rect { x, y, w, h, color } => {
215                        d.draw_rectangle(x, y, w, h, color);
216                    }
217                    DrawCommand::Line {
218                        x1,
219                        y1,
220                        x2,
221                        y2,
222                        color,
223                    } => {
224                        d.draw_line(x1, y1, x2, y2, color);
225                    }
226                    DrawCommand::Text {
227                        text,
228                        x,
229                        y,
230                        size,
231                        color,
232                    } => {
233                        d.draw_text(&text, x, y, size, color);
234                    }
235                    DrawCommand::Triangle { v1, v2, v3, color } => {
236                        d.draw_triangle(v1, v2, v3, color);
237                    }
238                    DrawCommand::Texture {
239                        id,
240                        x,
241                        y,
242                        scale,
243                        rotation,
244                        color,
245                    } => {
246                        // Dibujar textura con escala y rotación
247                        assets.draw_texture_ex_by_id(
248                            &mut d.draw,
249                            &id,
250                            x,
251                            y,
252                            scale,
253                            rotation,
254                            color,
255                        );
256                    }
257                    DrawCommand::Clear { color } => {
258                        d.clear(color);
259                    }
260                }
261            }
262
263            // Drop EXPLÍCITO para forzar buffer swap (fix Zink/Vulkan)
264            drop(d);
265        }
266
267        // Reset contador de frame
268        self.frame_count = 0;
269    }
270
271    /// ✅ v0.10.4: Ejecutar comandos con DrawHandle existente (para integrar con partículas)
272    pub fn execute_with_handle(&mut self, d: &mut crate::DrawHandle, assets: &Assets) {
273        if self.commands.is_empty() {
274            return;
275        }
276
277        // Actualizar estadísticas
278        if self.frame_count > self.max_frame_count {
279            self.max_frame_count = self.frame_count;
280        }
281        self.total_executed += self.frame_count;
282
283        for command in self.commands.drain(..) {
284            match command {
285                DrawCommand::Circle {
286                    x,
287                    y,
288                    radius,
289                    color,
290                } => {
291                    d.draw_circle(x, y, radius, color);
292                }
293                DrawCommand::Rect { x, y, w, h, color } => {
294                    d.draw_rectangle(x, y, w, h, color);
295                }
296                DrawCommand::Line {
297                    x1,
298                    y1,
299                    x2,
300                    y2,
301                    color,
302                } => {
303                    d.draw_line(x1, y1, x2, y2, color);
304                }
305                DrawCommand::Text {
306                    text,
307                    x,
308                    y,
309                    size,
310                    color,
311                } => {
312                    d.draw_text(&text, x, y, size, color);
313                }
314                DrawCommand::Triangle { v1, v2, v3, color } => {
315                    d.draw_triangle(v1, v2, v3, color);
316                }
317                DrawCommand::Texture {
318                    id,
319                    x,
320                    y,
321                    scale,
322                    rotation,
323                    color,
324                } => {
325                    // d es DrawHandle, usar d.draw para obtener RaylibDrawHandle
326                    assets.draw_texture_ex_by_id(&mut d.draw, &id, x, y, scale, rotation, color);
327                }
328                DrawCommand::Clear { color } => {
329                    d.clear(color);
330                }
331            }
332        }
333
334        // Reset contador de frame
335        self.frame_count = 0;
336    }
337
338    /// Limpiar queue sin ejecutar
339    pub fn clear(&mut self) {
340        self.commands.clear();
341        self.frame_count = 0;
342    }
343
344    /// Obtener cantidad de comandos pendientes
345    pub fn len(&self) -> usize {
346        self.commands.len()
347    }
348
349    /// Verificar si está vacía
350    pub fn is_empty(&self) -> bool {
351        self.commands.is_empty()
352    }
353
354    /// Obtener capacidad máxima
355    pub fn capacity(&self) -> usize {
356        self.capacity
357    }
358
359    /// Obtener estadísticas
360    pub fn stats(&self) -> RenderQueueStats {
361        RenderQueueStats {
362            pending: self.commands.len(),
363            frame_count: self.frame_count,
364            total_executed: self.total_executed,
365            max_frame_count: self.max_frame_count,
366            capacity: self.capacity,
367        }
368    }
369}
370
371impl Default for RenderQueue {
372    fn default() -> Self {
373        Self::new()
374    }
375}
376
377/// Estadísticas de la render queue
378#[derive(Debug, Clone, Default)]
379pub struct RenderQueueStats {
380    pub pending: usize,
381    pub frame_count: usize,
382    pub total_executed: usize,
383    pub max_frame_count: usize,
384    pub capacity: usize,
385}
386
387impl std::fmt::Display for RenderQueueStats {
388    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389        write!(
390            f,
391            "RenderQueue {{ pending: {}, frame: {}, total: {}, max: {}, capacity: {} }}",
392            self.pending,
393            self.frame_count,
394            self.total_executed,
395            self.max_frame_count,
396            self.capacity
397        )
398    }
399}
400
401// ============================================================================
402// DOUBLE BUFFERING - Separar lógica de renderizado
403// ============================================================================
404
405/// Double Buffer para draw commands
406///
407/// **Front Buffer**: Lógica acumula comandos (executor)
408/// **Back Buffer**: Render ejecuta comandos (gfx)
409/// **Swap**: Intercambiar buffers al final del frame
410pub struct DoubleBuffer {
411    /// Front buffer (acumulación - lógica)
412    front: RenderQueue,
413    /// Back buffer (ejecución - render)
414    back: RenderQueue,
415    /// Frame actual
416    frame: u64,
417}
418
419impl DoubleBuffer {
420    /// Crear double buffer con capacidad
421    pub fn new(capacity: usize) -> Self {
422        println!("[DOUBLE BUFFER] Creado con capacidad={}", capacity);
423        Self {
424            front: RenderQueue::with_capacity(capacity),
425            back: RenderQueue::with_capacity(capacity),
426            frame: 0,
427        }
428    }
429
430    /// Push al front buffer (lógica)
431    pub fn push(&mut self, command: DrawCommand) {
432        self.front.push(command);
433    }
434
435    /// Swap de buffers: front → back
436    /// Retorna el back buffer anterior para ejecutar
437    pub fn swap(&mut self) {
438        self.frame += 1;
439
440        // Intercambiar buffers
441        std::mem::swap(&mut self.front, &mut self.back);
442
443        #[cfg(debug_assertions)]
444        eprintln!(
445            "[DOUBLE BUFFER] Frame {} - Swap completado (back: {} comandos)",
446            self.frame,
447            self.back.len()
448        );
449    }
450
451    /// Ejecutar back buffer
452    pub fn execute(&mut self, gfx: &mut RyditGfx, assets: &Assets) {
453        self.back.execute(gfx, assets);
454        self.back.clear();
455    }
456
457    /// Swap + execute (operación combinada)
458    pub fn swap_and_execute(&mut self, gfx: &mut RyditGfx, assets: &Assets) {
459        self.swap();
460        self.execute(gfx, assets);
461    }
462
463    /// Obtener frame actual
464    pub fn frame(&self) -> u64 {
465        self.frame
466    }
467
468    /// Obtener estadísticas de ambos buffers
469    pub fn stats(&self) -> (RenderQueueStats, RenderQueueStats) {
470        (self.front.stats(), self.back.stats())
471    }
472}
473
474// ✅ v0.10.8: PlatformSync migrado a v-shield (re-export arriba)
475
476// ============================================================================
477// INTEGRACIÓN CON RyditGfx
478// ============================================================================
479
480/// Extensión para RyditGfx con render queue
481pub trait RyditGfxExt {
482    /// Ejecutar comandos desde queue
483    fn execute_queue(&mut self, queue: &mut RenderQueue, assets: &Assets);
484
485    /// Platform sync (X11)
486    fn platform_sync(&mut self);
487}
488
489impl RyditGfxExt for RyditGfx {
490    fn execute_queue(&mut self, queue: &mut RenderQueue, assets: &Assets) {
491        queue.execute(self, assets);
492    }
493
494    fn platform_sync(&mut self) {
495        // Platform sync se maneja externamente
496        // El buffer swap ya ocurre en Drop de DrawHandle
497    }
498}