diff --git a/docs/canvas_batching_architecture.md b/docs/canvas_batching_architecture.md new file mode 100644 index 0000000..215824c --- /dev/null +++ b/docs/canvas_batching_architecture.md @@ -0,0 +1,371 @@ +# Canvas Batching Architecture Documentation + +## Overview + +The Canvas rendering system supports two distinct rendering modes designed to optimize performance while maintaining consistent drawing order across all primitive types. This document explains how batching mode and non-batching mode work, how sequence numbers are managed, and how Z-depth layering is implemented. + +## Architecture Overview + +```mermaid +graph TD + A[Canvas API] --> B{Batching Enabled?} + B -->|Yes| C[Batching Mode] + B -->|No| D[Non-Batching Mode] + + C --> E[BatchOrderTracker] + C --> F[ShapeBatches] + C --> G[IndividualShapes] + + D --> H[IndividualRenderStrategy] + + E --> I[RenderBatchesInOrder] + F --> I + G --> I + + H --> J[Sequence-Sorted Rendering] + I --> K[Unified Z-Depth Layering] + J --> K +``` + +## Rendering Modes + +### Batching Mode (`batching_enabled = true`) + +**Purpose**: Optimize performance by grouping similar primitives into batches while maintaining correct drawing order. + +**Key Components**: +- `BatchOrderTracker`: Maintains global sequence order across all primitive types +- `ShapeBatch`: Groups similar shapes (circles, rectangles) for efficient GPU rendering +- `IndividualRenderStrategy`: Handles complex shapes (polygons, ellipses) that don't batch well + +**Rendering Flow**: +1. Primitives are added to appropriate batches or individual collections +2. Each primitive gets a sequence number from `BatchOrderTracker` +3. `RenderBatchesInOrder()` renders all primitives in sequence order +4. Batched shapes use index ranges for selective rendering +5. Individual shapes are rendered at their specific sequence positions + +### Non-Batching Mode (`batching_enabled = false`) + +**Purpose**: Simple, direct rendering with guaranteed order but potentially lower performance. + +**Key Components**: +- `IndividualRenderStrategy`: Handles all primitive types individually +- Direct sequence-based sorting and rendering + +**Rendering Flow**: +1. All primitives stored in `CanvasData` collections +2. `IndividualRenderStrategy` sorts all primitives by sequence number +3. Renders each primitive individually in sequence order + +## Sequence Number Management + +### Unified Sequence Counter + +Both modes use a **unified sequence numbering system** to ensure consistent ordering: + +```cpp +// Global sequence counter (used by both modes) +BatchOrderTracker::next_sequence = 0; +CanvasData::next_sequence_number = 0; + +// Sequence assignment +uint32_t sequence = batch_order_tracker_.GetNextSequence(); +``` + +### Critical Sequence Synchronization + +In batching mode, sequence numbers must be synchronized between the batch order tracker and individual shapes: + +```cpp +// BEFORE FIX: Sequence mismatch +uint32_t batch_sequence = batch_order_tracker_.GetNextSequence(); // e.g., 10 +data_->AddPolygon(...); // Assigns internal sequence = 5 +// Z-depth calculated with sequence 5, but batch expects sequence 10 + +// AFTER FIX: Sequence synchronization +uint32_t sequence = batch_order_tracker_.GetNextSequence(); // e.g., 10 +data_->AddPolygon(...); +data_->polygons.back().sequence_number = sequence; // Override to 10 +// Z-depth and batch order both use sequence 10 +``` + +## Z-Depth Assignment Strategy + +### Z-Depth Formula + +All modes use the same Z-depth calculation for consistent layering: + +```cpp +float z_depth = sequence_number * 0.001f; +``` + +**Rationale**: +- **0.001f increment**: Provides sufficient separation between layers +- **Sequence-based**: Higher sequence numbers = higher Z values = rendered on top +- **Consistent**: Same formula across all primitive types and rendering modes + +### Z-Depth Application by Primitive Type + +#### Batched Shapes (Circles, Rectangles) +```cpp +// In GenerateCircleVertices() and GenerateRectangleVertices() +float z_depth = sequence_number * 0.001f; +vertices.insert(vertices.end(), {x, y, z_depth}); +``` + +#### Individual Shapes (Polygons) +```cpp +// In ShapeGenerators::GeneratePolygonVertices() +float z_depth = polygon.sequence_number * 0.001f; +for (const auto& vertex : polygon.vertices) { + vertices.insert(vertices.end(), {vertex.x, vertex.y, z_depth}); +} +``` + +#### Individual Shapes (Ellipses) +```cpp +// In ShapeGenerators::GenerateEllipseVertices() +float z_depth = ellipse.sequence_number * 0.001f; +glm::vec3 center_with_depth = {ellipse.center.x, ellipse.center.y, z_depth}; +return GeometryUtils::CreateEllipse(center_with_depth, ...); +``` + +## Batching Mode Deep Dive + +### Primitive Classification + +Primitives are classified into categories for optimal batching: + +| Primitive Type | Batching Strategy | Storage | Rendering Method | +|----------------|-------------------|---------|------------------| +| Points | Individual | `CanvasData::points` | Direct rendering | +| Lines | Batched by LineType | `line_batches_[line_type]` | Batch rendering | +| Rectangles | Batched (filled/outline) | `filled_shape_batch_` / `outline_shape_batches_` | Index range rendering | +| Circles | Batched (filled/outline) | `filled_shape_batch_` / `outline_shape_batches_` | Index range rendering | +| Ellipses | Individual | `CanvasData::ellipses` | Individual rendering | +| Polygons | Individual | `CanvasData::polygons` | Individual rendering | + +### Batch Order Tracking + +The `BatchOrderTracker` maintains a unified sequence across all primitive types: + +```cpp +struct OrderedPrimitive { + enum class Type { + kLine, // Batched lines + kFilledShape, // Batched filled shapes (circles, rectangles) + kOutlineShape, // Batched outline shapes + kIndividualShape // Individual shapes (polygons, ellipses) + }; + + Type type; + LineType line_type; // For lines and outlines + uint32_t sequence_number; // Global sequence + uint32_t batch_index; // Index within batch or shape index + IndividualShapeType individual_shape_type; // For individual shapes +}; +``` + +### Unified Rendering in RenderBatchesInOrder() + +```cpp +void Canvas::RenderBatchesInOrder() { + // Sort all primitives by sequence number + std::sort(sorted_order.begin(), sorted_order.end(), + [](const auto& a, const auto& b) { + return a.sequence_number < b.sequence_number; + }); + + // Render each primitive type at its sequence position + for (const auto& primitive : sorted_order) { + switch (primitive.type) { + case Type::kLine: + // Render specific line from batch + glDrawArrays(GL_LINES, primitive.batch_index * 2, 2); + break; + + case Type::kFilledShape: + // Render specific shape using index range + const auto& range = filled_shape_batch_.index_ranges[primitive.batch_index]; + glDrawElements(GL_TRIANGLES, range.count, GL_UNSIGNED_INT, ...); + break; + + case Type::kIndividualShape: + // Render specific individual shape + if (primitive.individual_shape_type == IndividualShapeType::kPolygon) { + RenderSinglePolygon(data.polygons[primitive.batch_index], context); + } + break; + } + } +} +``` + +## Non-Batching Mode Deep Dive + +### Simple Sequential Rendering + +Non-batching mode uses a straightforward approach: + +```cpp +void IndividualRenderStrategy::Render(const CanvasData& data, const RenderContext& context) { + // Collect all primitives with sequence numbers + std::vector sorted_primitives; + + // Add all primitive types to the sorted list + for (size_t i = 0; i < data.circles.size(); ++i) { + sorted_primitives.push_back({PrimitiveType::kCircle, i, data.circles[i].sequence_number}); + } + // ... (repeat for all primitive types) + + // Sort by sequence number + std::sort(sorted_primitives.begin(), sorted_primitives.end(), + [](const PrimitiveRef& a, const PrimitiveRef& b) { + return a.sequence_number < b.sequence_number; + }); + + // Render each primitive individually + for (const auto& primitive_ref : sorted_primitives) { + switch (primitive_ref.type) { + case PrimitiveType::kCircle: + RenderSingleCircle(data.circles[primitive_ref.index], context); + break; + // ... (handle all primitive types) + } + } +} +``` + +## OpenGL State Management + +### Depth Testing Configuration + +Both modes use identical OpenGL depth testing: + +```cpp +// Enable depth testing for proper layering +glEnable(GL_DEPTH_TEST); +glDepthFunc(GL_LEQUAL); // Later primitives (higher Z) draw on top + +// Enable alpha blending for transparency +glEnable(GL_BLEND); +glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +``` + +### Depth Buffer Range + +- **Z-Near**: 0.0f (background elements) +- **Z-Far**: Determined by highest sequence number × 0.001f +- **Typical Range**: 0.000f to ~0.100f for 100 primitives + +## Performance Characteristics + +### Batching Mode Performance + +**Advantages**: +- **GPU Efficiency**: Fewer draw calls through batching +- **Memory Locality**: Grouped vertices and indices +- **Selective Rendering**: Only renders specific shapes from batches + +**Trade-offs**: +- **Memory Overhead**: Batch management structures +- **Complexity**: Unified sequence tracking + +### Non-Batching Mode Performance + +**Advantages**: +- **Simplicity**: Straightforward rendering logic +- **Predictability**: Direct sequence-based rendering + +**Trade-offs**: +- **Draw Call Overhead**: One draw call per primitive +- **GPU Utilization**: Less efficient batching + +## Usage Guidelines + +### When to Use Batching Mode + +✅ **Recommended for**: +- Applications with many similar primitives (circles, rectangles, lines) +- Real-time applications requiring high performance +- Scenes with mixed primitive types requiring strict ordering + +### When to Use Non-Batching Mode + +✅ **Recommended for**: +- Simple applications with few primitives +- Debug/development scenarios requiring predictable rendering +- Applications where simplicity is more important than performance + +## Example: Rendering Order Verification + +Given this sequence of operations: + +```cpp +canvas->AddCircle(1.0f, 2.0f, 0.7f, red, true); // Sequence 0, Z = 0.000f +canvas->AddCircle(2.0f, 2.0f, 0.7f, green, true); // Sequence 1, Z = 0.001f +canvas->AddCircle(3.0f, 2.0f, 0.7f, blue, true); // Sequence 2, Z = 0.002f +canvas->AddEllipse(0.0f, 3.0f, 1.0f, 0.5f, olive); // Sequence 3, Z = 0.003f +canvas->AddPolygon(star_vertices, gold, true); // Sequence 4, Z = 0.004f +``` + +**Expected Rendering Order** (bottom to top): +1. Red circle (background) +2. Green circle +3. Blue circle +4. Olive ellipse +5. Gold star polygon (foreground) + +**Both modes guarantee this ordering through**: +- Unified sequence numbering +- Consistent Z-depth calculation +- Proper depth testing configuration + +## Troubleshooting Common Issues + +### Issue: Shapes Rendering in Wrong Order + +**Symptoms**: Later-added shapes appear behind earlier ones + +**Likely Causes**: +1. Sequence number mismatch between batch tracker and shape data +2. Incorrect Z-depth calculation +3. Disabled depth testing + +**Solution**: Verify sequence synchronization in batching mode + +### Issue: Performance Problems + +**Symptoms**: Low frame rates with many primitives + +**Likely Causes**: +1. Using non-batching mode with many similar primitives +2. Inefficient batch management + +**Solution**: Switch to batching mode and verify batch grouping + +### Issue: Transparency Issues + +**Symptoms**: Transparent shapes not blending correctly + +**Likely Causes**: +1. Incorrect depth function (GL_LESS instead of GL_LEQUAL) +2. Missing alpha blending setup + +**Solution**: Verify OpenGL blending configuration + +## Future Enhancements + +### Potential Optimizations + +1. **Dynamic Batching**: Automatically choose batching strategy based on primitive distribution +2. **Spatial Batching**: Group primitives by spatial locality for better cache performance +3. **Multi-threaded Processing**: Parallel batch generation for large datasets +4. **GPU-based Sorting**: Utilize compute shaders for sequence sorting + +### API Improvements + +1. **Batch Hints**: Allow applications to hint optimal batching strategies +2. **Performance Metrics**: Expose batch efficiency statistics +3. **Memory Management**: Configurable batch size limits and memory pools \ No newline at end of file diff --git a/src/renderer/CMakeLists.txt b/src/renderer/CMakeLists.txt index f8f3383..3574340 100644 --- a/src/renderer/CMakeLists.txt +++ b/src/renderer/CMakeLists.txt @@ -19,6 +19,10 @@ add_library(renderer src/renderable/details/individual_render_strategy.cpp src/renderable/details/shape_renderer.cpp src/renderable/details/shape_generators.cpp + src/renderable/details/opengl_resource_pool.cpp + src/renderable/details/shape_renderer_utils.cpp + src/renderable/details/canvas_data_manager.cpp + src/renderable/details/data_aware_render_strategy.cpp src/renderable/coordinate_frame.cpp src/renderable/texture.cpp ) diff --git a/src/renderer/include/renderer/renderable/canvas.hpp b/src/renderer/include/renderer/renderable/canvas.hpp index 2304302..efea393 100644 --- a/src/renderer/include/renderer/renderable/canvas.hpp +++ b/src/renderer/include/renderer/renderable/canvas.hpp @@ -28,6 +28,22 @@ #include "renderer/renderable/details/canvas_batching.hpp" #include "renderer/renderable/details/canvas_performance.hpp" +// Forward declarations for internal components +namespace quickviz { + +// Forward declarations for shape and render context types +struct Polygon; +struct Ellipse; +struct RenderContext; + +namespace internal { +class OpenGLResourcePool; +class EfficientShapeRenderer; +class CanvasDataManager; +class AdaptiveStrategySelector; +} +} + // Forward declarations for render strategies namespace quickviz { class RenderStrategy; @@ -80,7 +96,7 @@ class Canvas : public OpenGlObject { // Performance and rendering methods void SetBatchingEnabled(bool enabled); - bool IsBatchingEnabled() const { return batching_enabled_; } + bool IsBatchingEnabled() const; void FlushBatches(); // Force immediate rendering of all batches // Performance monitoring (moved to details/canvas_performance.hpp) @@ -112,83 +128,73 @@ class Canvas : public OpenGlObject { void SetupBackgroundImage(int width, int height, int channels, unsigned char* data); - // Process pending updates + // Data management helper (delegates to data manager) void ProcessPendingUpdates(); - // Structure to hold pending updates + // Background image texture + glm::vec2 background_image_size_{0.0f, 0.0f}; + std::mutex background_mutex_; + std::atomic background_texture_{0}; + + // Background rendering gpu resources + uint32_t background_vao_ = 0; + uint32_t background_vbo_ = 0; + ShaderProgram background_shader_; + + // Core data storage - original working system + std::unique_ptr data_; + mutable std::mutex data_mutex_; + + // Pending updates system struct PendingUpdate { enum class Type { - kPoint, - kLine, - kRectangle, - kCircle, - kEllipse, - kPolygon, - kClear + kPoint, kLine, kRectangle, kCircle, kEllipse, kPolygon, kClear }; - Type type; glm::vec4 color; float thickness; LineType line_type; bool filled; - - // Command-specific parameters + struct ellipse_params { float x, y, rx, ry, angle, start_angle, end_angle; }; - + union { - struct { // Point parameters - float x, y; - } point; - - struct { // Line parameters - float x1, y1, x2, y2; - } line; - - struct { // Rectangle parameters - float x, y, width, height; - } rect; - - struct { // Circle parameters - float x, y, radius; - } circle; - + struct { float x, y; } point; + struct { float x1, y1, x2, y2; } line; + struct { float x, y, width, height; } rect; + struct { float x, y, radius; } circle; ellipse_params ellipse; }; - - // Polygon vertices (can't be in union) + std::vector polygon_vertices; }; - - // Background image texture - glm::vec2 background_image_size_{0.0f, 0.0f}; - std::mutex background_mutex_; - std::atomic background_texture_{0}; - - // Background rendering gpu resources - uint32_t background_vao_ = 0; - uint32_t background_vbo_ = 0; - ShaderProgram background_shader_; - - // Thread-safe data structures - std::mutex data_mutex_; + std::queue pending_updates_; std::atomic has_pending_updates_{false}; - std::unique_ptr data_; + + // Batch data structures - original working system + std::unordered_map line_batches_; + ShapeBatch filled_shape_batch_; + std::unordered_map outline_shape_batches_; + + // Batch order tracking for preserving draw order + BatchOrderTracker batch_order_tracker_; // Primitive rendering gpu resources uint32_t primitive_vao_ = 0; uint32_t primitive_vbo_ = 0; ShaderProgram primitive_shader_; - // Batching-related members - bool batching_enabled_ = - true; // Re-enabled, ellipse/polygon renderMode fixed - std::unordered_map line_batches_; - ShapeBatch filled_shape_batch_; - std::unordered_map outline_shape_batches_; + // Original working render strategy system + RenderStrategy* current_render_strategy_; + std::unique_ptr batched_strategy_; + std::unique_ptr individual_strategy_; + std::unique_ptr shape_renderer_; + + // Batching configuration + bool batching_enabled_ = true; // Batch management methods void InitializeBatches(); @@ -196,6 +202,8 @@ class Canvas : public OpenGlObject { void UpdateBatches(); void RenderBatches(const glm::mat4& projection, const glm::mat4& view, const glm::mat4& coord_transform); + void RenderBatchesInOrder(const glm::mat4& projection, const glm::mat4& view, + const glm::mat4& coord_transform); // Individual shape rendering for non-batched shapes void RenderIndividualShapes(const CanvasData& data, @@ -203,16 +211,44 @@ class Canvas : public OpenGlObject { const glm::mat4& view, const glm::mat4& coord_transform); + // Single shape rendering methods for selective batching + void RenderSinglePolygon(const Polygon& polygon, const RenderContext& context); + void RenderSingleEllipse(const Ellipse& ellipse, const RenderContext& context); + + // Phase 1.2: Resource pool helper for efficient individual shape rendering + void RenderShapeWithPool(const std::vector& vertices, + const glm::vec4& color, float thickness, + unsigned int primitive_type, LineType line_type = LineType::kSolid); + + // Immediate rendering methods for non-batching mode + void RenderPointImmediate(float x, float y, const glm::vec4& color, float thickness); + void RenderLineImmediate(float x1, float y1, float x2, float y2, const glm::vec4& color, + float thickness, LineType line_type); + void RenderRectangleImmediate(float x, float y, float width, float height, + const glm::vec4& color, bool filled, float thickness, + LineType line_type); + void RenderCircleImmediate(float x, float y, float radius, const glm::vec4& color, + bool filled, float thickness, LineType line_type); + void RenderEllipseImmediate(float x, float y, float rx, float ry, float angle, + float start_angle, float end_angle, const glm::vec4& color, + bool filled, float thickness, LineType line_type); + void RenderPolygonImmediate(const std::vector& points, const glm::vec4& color, + bool filled, float thickness, LineType line_type); + + // Helper to get current view/projection matrices for immediate rendering + void GetCurrentMatrices(glm::mat4& projection, glm::mat4& view, glm::mat4& coord_transform); + // Shape generation helpers void GenerateCircleVertices(float cx, float cy, float radius, int segments, std::vector& vertices, std::vector& indices, bool filled, - uint32_t base_index); + uint32_t base_index, uint32_t sequence_number = 0); void GenerateRectangleVertices(float x, float y, float width, float height, std::vector& vertices, std::vector& indices, bool filled, - uint32_t base_index); - void GenerateEllipseVertices(const PendingUpdate::ellipse_params& ellipse, + uint32_t base_index, uint32_t sequence_number = 0); + void GenerateEllipseVertices(float x, float y, float rx, float ry, float angle, + float start_angle, float end_angle, std::vector& vertices, std::vector& indices, bool filled, uint32_t base_index); @@ -227,17 +263,6 @@ class Canvas : public OpenGlObject { // Performance tuning and memory optimization PerformanceConfig perf_config_; - // Render strategy system (refactored from monolithic OnDraw) - RenderStrategy* current_render_strategy_; - std::unique_ptr batched_strategy_; - std::unique_ptr individual_strategy_; - - // Unified shape renderer (Phase 2 refactoring) - std::unique_ptr shape_renderer_; - - // Helper method to select appropriate render strategy - RenderStrategy* SelectRenderStrategy(const CanvasData& data); - // Memory tracking struct MemoryTracker { std::atomic current_usage{0}; diff --git a/src/renderer/include/renderer/renderable/details/canvas_batching.hpp b/src/renderer/include/renderer/renderable/details/canvas_batching.hpp index 9b2b447..e5387aa 100644 --- a/src/renderer/include/renderer/renderable/details/canvas_batching.hpp +++ b/src/renderer/include/renderer/renderable/details/canvas_batching.hpp @@ -27,6 +27,7 @@ struct LineBatch { std::vector colors; std::vector thicknesses; std::vector line_types; + std::vector sequence_numbers; // Global sequence order uint32_t vao = 0; uint32_t position_vbo = 0; uint32_t color_vbo = 0; @@ -43,6 +44,15 @@ struct ShapeBatch { std::vector vertices; std::vector indices; std::vector colors; + std::vector sequence_numbers; // Global sequence order per primitive + + // Track index ranges for each individual shape to enable individual drawing + struct IndexRange { + uint32_t start; // Starting index in the indices array + uint32_t count; // Number of indices for this shape + }; + std::vector index_ranges; // One range per shape + uint32_t vao = 0; uint32_t vertex_vbo = 0; uint32_t color_vbo = 0; @@ -50,6 +60,41 @@ struct ShapeBatch { bool needs_update = true; }; +/** + * @brief Unified rendering order tracker + * + * Maintains a single global sequence across all primitive types + * to preserve the exact order of draw calls in batching mode. + */ +struct BatchOrderTracker { + struct OrderedPrimitive { + enum class Type { + kLine, + kFilledShape, + kOutlineShape, + kIndividualShape // For polygons, ellipses that use IndividualRenderStrategy + }; + + Type type; + LineType line_type; // Only relevant for kLine and kOutlineShape + uint32_t sequence_number; + uint32_t batch_index; // Index within the specific batch, or shape index for individual shapes + + // Additional data for individual shapes + enum class IndividualShapeType { + kNone, + kEllipse, + kPolygon + } individual_shape_type = IndividualShapeType::kNone; + }; + + std::vector render_order; + uint32_t next_sequence = 0; + + uint32_t GetNextSequence() { return next_sequence++; } + void Clear() { render_order.clear(); next_sequence = 0; } +}; + } // namespace quickviz #endif /* OPENGL_RENDERER_CANVAS_BATCHING_HPP */ \ No newline at end of file diff --git a/src/renderer/src/renderable/canvas.cpp b/src/renderer/src/renderable/canvas.cpp index d7bf949..12ed6e4 100644 --- a/src/renderer/src/renderable/canvas.cpp +++ b/src/renderer/src/renderable/canvas.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" @@ -27,6 +28,7 @@ #include "renderable/details/batched_render_strategy.hpp" #include "renderable/details/individual_render_strategy.hpp" #include "renderable/details/shape_renderer.hpp" +#include "renderable/details/shape_generators.hpp" namespace quickviz { namespace { @@ -159,6 +161,7 @@ void main() { } // namespace Canvas::Canvas() { + // Initialize core data storage (original working system) data_ = std::make_unique(); // Ensure data is not null @@ -170,8 +173,7 @@ Canvas::Canvas() { // Initialize the data object (just to be extra safe) data_->Clear(); - // Initialize render strategies - // Note: shape_renderer_ will be created after GPU resources are allocated + // Initialize render strategies (Phase 1 improvement - defer shape_renderer_ until after GPU resources) batched_strategy_ = std::make_unique( line_batches_, filled_shape_batch_, outline_shape_batches_); individual_strategy_ = std::make_unique(); @@ -194,10 +196,14 @@ Canvas::Canvas() { batched_strategy_ = std::make_unique( line_batches_, filled_shape_batch_, outline_shape_batches_, shape_renderer_.get()); individual_strategy_ = std::make_unique(shape_renderer_.get()); + + // Update current strategy pointer + current_render_strategy_ = batching_enabled_ ? + static_cast(batched_strategy_.get()) : + static_cast(individual_strategy_.get()); } Canvas::~Canvas() { - ClearBatches(); ReleaseGpuResources(); } @@ -588,6 +594,11 @@ void Canvas::SetupBackgroundImage(int width, int height, int channels, void Canvas::AddPoint(float x, float y, const glm::vec4& color, float thickness) { + // Always store the primitive - the difference between modes is in how they're rendered + // Non-batching mode: render individually in sequence order during OnDraw + // Batching mode: batch similar primitives for efficient rendering during OnDraw + + // Batching mode - store for later rendering PendingUpdate update; update.type = PendingUpdate::Type::kPoint; update.color = color; @@ -596,13 +607,16 @@ void Canvas::AddPoint(float x, float y, const glm::vec4& color, update.point.y = y; std::lock_guard lock(data_mutex_); - pending_updates_.push(std::move(update)); + pending_updates_.push(update); has_pending_updates_ = true; } void Canvas::AddLine(float x1, float y1, float x2, float y2, const glm::vec4& color, float thickness, LineType line_type) { + // Always store the primitive for proper rendering during OnDraw + + // Batching mode - store for later rendering PendingUpdate update; update.type = PendingUpdate::Type::kLine; update.color = color; @@ -614,13 +628,16 @@ void Canvas::AddLine(float x1, float y1, float x2, float y2, update.line.y2 = y2; std::lock_guard lock(data_mutex_); - pending_updates_.push(std::move(update)); + pending_updates_.push(update); has_pending_updates_ = true; } void Canvas::AddRectangle(float x, float y, float width, float height, const glm::vec4& color, bool filled, float thickness, LineType line_type) { + // Always store the primitive for proper rendering during OnDraw + + // Batching mode - store for later rendering PendingUpdate update; update.type = PendingUpdate::Type::kRectangle; update.color = color; @@ -633,12 +650,15 @@ void Canvas::AddRectangle(float x, float y, float width, float height, update.rect.height = height; std::lock_guard lock(data_mutex_); - pending_updates_.push(std::move(update)); + pending_updates_.push(update); has_pending_updates_ = true; } void Canvas::AddCircle(float x, float y, float radius, const glm::vec4& color, bool filled, float thickness, LineType line_type) { + // Always store the primitive for proper rendering during OnDraw + + // Batching mode - store for later rendering PendingUpdate update; update.type = PendingUpdate::Type::kCircle; update.color = color; @@ -650,13 +670,16 @@ void Canvas::AddCircle(float x, float y, float radius, const glm::vec4& color, update.circle.radius = radius; std::lock_guard lock(data_mutex_); - pending_updates_.push(std::move(update)); + pending_updates_.push(update); has_pending_updates_ = true; } void Canvas::AddEllipse(float x, float y, float rx, float ry, float angle, float start_angle, float end_angle, const glm::vec4& color, bool filled, float thickness, LineType line_type) { + // Always store the primitive for proper rendering during OnDraw + + // Batching mode - store for later rendering PendingUpdate update; update.type = PendingUpdate::Type::kEllipse; update.color = color; @@ -672,13 +695,16 @@ void Canvas::AddEllipse(float x, float y, float rx, float ry, float angle, update.ellipse.end_angle = end_angle; std::lock_guard lock(data_mutex_); - pending_updates_.push(std::move(update)); + pending_updates_.push(update); has_pending_updates_ = true; } void Canvas::AddPolygon(const std::vector& points, const glm::vec4& color, bool filled, float thickness, LineType line_type) { + // Always store the primitive for proper rendering during OnDraw + + // Batching mode - store for later rendering PendingUpdate update; update.type = PendingUpdate::Type::kPolygon; update.color = color; @@ -688,17 +714,19 @@ void Canvas::AddPolygon(const std::vector& points, update.polygon_vertices = points; std::lock_guard lock(data_mutex_); - pending_updates_.push(std::move(update)); + pending_updates_.push(update); has_pending_updates_ = true; } void Canvas::Clear() { - PendingUpdate update; - update.type = PendingUpdate::Type::kClear; - std::lock_guard lock(data_mutex_); - pending_updates_.push(std::move(update)); - has_pending_updates_ = true; + data_->Clear(); + ClearBatches(); + + // Clear pending updates + std::queue empty_queue; + pending_updates_.swap(empty_queue); + has_pending_updates_ = false; } void Canvas::ProcessPendingUpdates() { @@ -708,197 +736,268 @@ void Canvas::ProcessPendingUpdates() { const auto& update = pending_updates_.front(); if (batching_enabled_) { - // Use batching system for better performance + // Use batching system with proper sequence tracking switch (update.type) { case PendingUpdate::Type::kPoint: // Points still use the original system since they're already efficient data_->AddPoint(update.point.x, update.point.y, update.color, update.thickness); break; + case PendingUpdate::Type::kLine: { - // Add line to batch based on line type + // Add line to batch with sequence tracking auto& line_batch = line_batches_[update.line_type]; + uint32_t sequence = batch_order_tracker_.GetNextSequence(); + uint32_t line_index = line_batch.vertices.size() / 2; // 2 vertices per line + line_batch.vertices.emplace_back(update.line.x1, update.line.y1, 0.0f); line_batch.vertices.emplace_back(update.line.x2, update.line.y2, 0.0f); line_batch.colors.push_back(update.color); line_batch.colors.push_back(update.color); line_batch.thicknesses.push_back(update.thickness); line_batch.line_types.push_back(update.line_type); + line_batch.sequence_numbers.push_back(sequence); line_batch.needs_update = true; + + // Record in order tracker + batch_order_tracker_.render_order.push_back({ + BatchOrderTracker::OrderedPrimitive::Type::kLine, + update.line_type, + sequence, + line_index + }); break; } + case PendingUpdate::Type::kRectangle: { + uint32_t sequence = batch_order_tracker_.GetNextSequence(); + if (update.filled) { + uint32_t shape_index = filled_shape_batch_.sequence_numbers.size(); uint32_t base_index = filled_shape_batch_.vertices.size() / 3; + uint32_t start_index_pos = filled_shape_batch_.indices.size(); + GenerateRectangleVertices(update.rect.x, update.rect.y, update.rect.width, update.rect.height, filled_shape_batch_.vertices, filled_shape_batch_.indices, - true, base_index); + true, base_index, sequence); + + // Track index range for this rectangle (6 indices for 2 triangles) + uint32_t index_count = filled_shape_batch_.indices.size() - start_index_pos; + filled_shape_batch_.index_ranges.push_back({start_index_pos, index_count}); + + // Add color for each vertex (4 vertices for rectangle) for (int i = 0; i < 4; i++) { filled_shape_batch_.colors.push_back(update.color); } + filled_shape_batch_.sequence_numbers.push_back(sequence); filled_shape_batch_.needs_update = true; + + // Record in order tracker + batch_order_tracker_.render_order.push_back({ + BatchOrderTracker::OrderedPrimitive::Type::kFilledShape, + LineType::kSolid, // Not used for filled shapes + sequence, + shape_index + }); } else { auto& outline_batch = outline_shape_batches_[update.line_type]; + uint32_t shape_index = outline_batch.sequence_numbers.size(); uint32_t base_index = outline_batch.vertices.size() / 3; + uint32_t start_index_pos = outline_batch.indices.size(); + GenerateRectangleVertices(update.rect.x, update.rect.y, update.rect.width, update.rect.height, outline_batch.vertices, outline_batch.indices, - false, base_index); + false, base_index, sequence); + + // Track index range for this rectangle outline (8 indices for line loop) + uint32_t index_count = outline_batch.indices.size() - start_index_pos; + outline_batch.index_ranges.push_back({start_index_pos, index_count}); + // Add color for each vertex (4 vertices for rectangle) for (int i = 0; i < 4; i++) { outline_batch.colors.push_back(update.color); } + outline_batch.sequence_numbers.push_back(sequence); outline_batch.needs_update = true; + + // Record in order tracker + batch_order_tracker_.render_order.push_back({ + BatchOrderTracker::OrderedPrimitive::Type::kOutlineShape, + update.line_type, + sequence, + shape_index + }); } break; } + case PendingUpdate::Type::kCircle: { const int segments = 32; // Could be made adaptive based on radius + uint32_t sequence = batch_order_tracker_.GetNextSequence(); + if (update.filled) { + uint32_t shape_index = filled_shape_batch_.sequence_numbers.size(); uint32_t base_index = filled_shape_batch_.vertices.size() / 3; + uint32_t start_index_pos = filled_shape_batch_.indices.size(); + GenerateCircleVertices(update.circle.x, update.circle.y, update.circle.radius, segments, filled_shape_batch_.vertices, filled_shape_batch_.indices, - true, base_index); + true, base_index, sequence); + + // Track index range for this circle (3 * segments indices for triangle fan) + uint32_t index_count = filled_shape_batch_.indices.size() - start_index_pos; + filled_shape_batch_.index_ranges.push_back({start_index_pos, index_count}); + + // Add color for center + perimeter vertices - // Center vertex (1) + perimeter vertices (segments + 1) = segments + 2 total for (int i = 0; i < segments + 2; i++) { filled_shape_batch_.colors.push_back(update.color); } + filled_shape_batch_.sequence_numbers.push_back(sequence); filled_shape_batch_.needs_update = true; + + // Record in order tracker + batch_order_tracker_.render_order.push_back({ + BatchOrderTracker::OrderedPrimitive::Type::kFilledShape, + LineType::kSolid, + sequence, + shape_index + }); } else { auto& outline_batch = outline_shape_batches_[update.line_type]; + uint32_t shape_index = outline_batch.sequence_numbers.size(); uint32_t base_index = outline_batch.vertices.size() / 3; + uint32_t start_index_pos = outline_batch.indices.size(); + GenerateCircleVertices(update.circle.x, update.circle.y, update.circle.radius, segments, outline_batch.vertices, outline_batch.indices, - false, base_index); + false, base_index, sequence); + + // Track index range for this circle outline (segments * 2 indices for line loop) + uint32_t index_count = outline_batch.indices.size() - start_index_pos; + outline_batch.index_ranges.push_back({start_index_pos, index_count}); + // Add color for perimeter vertices - // Perimeter vertices: segments + 1 total (0 to segments inclusive) - for (int i = 0; i < segments + 1; i++) { - outline_batch.colors.push_back(update.color); - } - outline_batch.needs_update = true; - } - break; - } - case PendingUpdate::Type::kEllipse: { - const int segments = 32; - if (update.filled) { - uint32_t base_index = filled_shape_batch_.vertices.size() / 3; - GenerateEllipseVertices(update.ellipse, filled_shape_batch_.vertices, - filled_shape_batch_.indices, true, base_index); - for (int i = 0; i < segments + 2; i++) { - filled_shape_batch_.colors.push_back(update.color); - } - filled_shape_batch_.needs_update = true; - } else { - auto& outline_batch = outline_shape_batches_[update.line_type]; - uint32_t base_index = outline_batch.vertices.size() / 3; - GenerateEllipseVertices(update.ellipse, outline_batch.vertices, - outline_batch.indices, false, base_index); for (int i = 0; i < segments + 1; i++) { outline_batch.colors.push_back(update.color); } + outline_batch.sequence_numbers.push_back(sequence); outline_batch.needs_update = true; + + // Record in order tracker + batch_order_tracker_.render_order.push_back({ + BatchOrderTracker::OrderedPrimitive::Type::kOutlineShape, + update.line_type, + sequence, + shape_index + }); } break; } + + case PendingUpdate::Type::kEllipse: case PendingUpdate::Type::kPolygon: { - if (update.filled) { - uint32_t base_index = filled_shape_batch_.vertices.size() / 3; - GeneratePolygonVertices(update.polygon_vertices, filled_shape_batch_.vertices, - filled_shape_batch_.indices, true, base_index); - for (int i = 0; i < update.polygon_vertices.size(); i++) { - filled_shape_batch_.colors.push_back(update.color); - } - filled_shape_batch_.needs_update = true; + // These use individual rendering but still need sequence tracking + uint32_t sequence = batch_order_tracker_.GetNextSequence(); + + BatchOrderTracker::OrderedPrimitive::IndividualShapeType shape_type; + uint32_t shape_index; + + if (update.type == PendingUpdate::Type::kEllipse) { + shape_index = data_->ellipses.size(); // Index before adding + data_->AddEllipse(update.ellipse.x, update.ellipse.y, update.ellipse.rx, update.ellipse.ry, + update.ellipse.angle, update.ellipse.start_angle, update.ellipse.end_angle, + update.color, update.filled, update.thickness, update.line_type); + // Fix the sequence number to match the batch order tracker + data_->ellipses.back().sequence_number = sequence; + shape_type = BatchOrderTracker::OrderedPrimitive::IndividualShapeType::kEllipse; } else { - auto& outline_batch = outline_shape_batches_[update.line_type]; - uint32_t base_index = outline_batch.vertices.size() / 3; - GeneratePolygonVertices(update.polygon_vertices, outline_batch.vertices, - outline_batch.indices, false, base_index); - for (int i = 0; i < update.polygon_vertices.size(); i++) { - outline_batch.colors.push_back(update.color); - } - outline_batch.needs_update = true; + shape_index = data_->polygons.size(); // Index before adding + data_->AddPolygon(update.polygon_vertices, update.color, update.filled, + update.thickness, update.line_type); + // Fix the sequence number to match the batch order tracker + data_->polygons.back().sequence_number = sequence; + shape_type = BatchOrderTracker::OrderedPrimitive::IndividualShapeType::kPolygon; } + + // Add to batch order tracker for unified sequence rendering + BatchOrderTracker::OrderedPrimitive primitive = { + BatchOrderTracker::OrderedPrimitive::Type::kIndividualShape, + LineType::kSolid, // Not used for individual shapes + sequence, + shape_index // Index of the specific shape + }; + primitive.individual_shape_type = shape_type; + batch_order_tracker_.render_order.push_back(primitive); break; } + case PendingUpdate::Type::kClear: - // Clear both traditional data and batches data_->Clear(); - for (auto& [line_type, line_batch] : line_batches_) { - line_batch.vertices.clear(); - line_batch.colors.clear(); - line_batch.thicknesses.clear(); - line_batch.line_types.clear(); - line_batch.needs_update = true; - } - filled_shape_batch_.vertices.clear(); - filled_shape_batch_.indices.clear(); - filled_shape_batch_.colors.clear(); - for (auto& [line_type, outline_batch] : outline_shape_batches_) { - outline_batch.vertices.clear(); - outline_batch.indices.clear(); - outline_batch.colors.clear(); - outline_batch.needs_update = true; - } - filled_shape_batch_.needs_update = true; + ClearBatches(); + batch_order_tracker_.Clear(); break; } } else { - // Use original individual rendering system + // Non-batching mode - use original system switch (update.type) { case PendingUpdate::Type::kPoint: data_->AddPoint(update.point.x, update.point.y, update.color, update.thickness); break; - case PendingUpdate::Type::kLine: - data_->AddLine(update.line.x1, update.line.y1, update.line.x2, - update.line.y2, update.color, update.thickness, - update.line_type); + data_->AddLine(update.line.x1, update.line.y1, update.line.x2, update.line.y2, + update.color, update.thickness, update.line_type); break; - case PendingUpdate::Type::kRectangle: - data_->AddRectangle(update.rect.x, update.rect.y, update.rect.width, - update.rect.height, update.color, update.filled, - update.thickness, update.line_type); + data_->AddRectangle(update.rect.x, update.rect.y, update.rect.width, update.rect.height, + update.color, update.filled, update.thickness, update.line_type); break; - case PendingUpdate::Type::kCircle: data_->AddCircle(update.circle.x, update.circle.y, update.circle.radius, - update.color, update.filled, update.thickness, - update.line_type); + update.color, update.filled, update.thickness, update.line_type); break; - case PendingUpdate::Type::kEllipse: - data_->AddEllipse(update.ellipse.x, update.ellipse.y, update.ellipse.rx, - update.ellipse.ry, update.ellipse.angle, - update.ellipse.start_angle, update.ellipse.end_angle, - update.color, update.filled, update.thickness, - update.line_type); + data_->AddEllipse(update.ellipse.x, update.ellipse.y, update.ellipse.rx, update.ellipse.ry, + update.ellipse.angle, update.ellipse.start_angle, update.ellipse.end_angle, + update.color, update.filled, update.thickness, update.line_type); break; - case PendingUpdate::Type::kPolygon: - data_->AddPolygon(update.polygon_vertices, update.color, update.filled, + data_->AddPolygon(update.polygon_vertices, update.color, update.filled, update.thickness, update.line_type); break; - case PendingUpdate::Type::kClear: data_->Clear(); + ClearBatches(); break; } } pending_updates_.pop(); } + has_pending_updates_ = false; + + // Update batches after processing updates + UpdateBatches(); +} + +void Canvas::SetBatchingEnabled(bool enabled) { + batching_enabled_ = enabled; + current_render_strategy_ = batching_enabled_ ? + static_cast(batched_strategy_.get()) : + static_cast(individual_strategy_.get()); +} + +bool Canvas::IsBatchingEnabled() const { + return batching_enabled_; } void Canvas::AllocateGpuResources() { @@ -907,14 +1006,14 @@ void Canvas::AllocateGpuResources() { try { std::cout << "Compiling primitive vertex shader..." << std::endl; - Shader vertex_shader(vertex_shader_source.c_str(), Shader::Type::kVertex); + quickviz::Shader vertex_shader(vertex_shader_source.c_str(), quickviz::Shader::Type::kVertex); if (!vertex_shader.Compile()) { std::cerr << "ERROR::CANVAS::VERTEX_SHADER_COMPILATION_FAILED" << std::endl; throw std::runtime_error("Vertex shader compilation failed"); } std::cout << "Compiling primitive fragment shader..." << std::endl; - Shader fragment_shader(fragment_shader_source.c_str(), Shader::Type::kFragment); + quickviz::Shader fragment_shader(fragment_shader_source.c_str(), quickviz::Shader::Type::kFragment); if (!fragment_shader.Compile()) { std::cerr << "ERROR::CANVAS::FRAGMENT_SHADER_COMPILATION_FAILED" << std::endl; throw std::runtime_error("Fragment shader compilation failed"); @@ -923,74 +1022,60 @@ void Canvas::AllocateGpuResources() { std::cout << "Attaching shaders to program and linking..." << std::endl; primitive_shader_.AttachShader(vertex_shader); primitive_shader_.AttachShader(fragment_shader); - if (!primitive_shader_.LinkProgram()) { std::cerr << "ERROR::CANVAS::SHADER_PROGRAM_LINKING_FAILED" << std::endl; throw std::runtime_error("Shader program linking failed"); } - std::cout << "Primitive shader program compiled and linked successfully." << std::endl; - - // Create and set up VAO and VBO + + // Generate vertex array and vertex buffer for primitives glGenVertexArrays(1, &primitive_vao_); if (primitive_vao_ == 0) { - std::cerr << "ERROR::CANVAS::VAO_GENERATION_FAILED" << std::endl; - throw std::runtime_error("VAO generation failed"); + throw std::runtime_error("Failed to generate vertex array object"); } glGenBuffers(1, &primitive_vbo_); if (primitive_vbo_ == 0) { - std::cerr << "ERROR::CANVAS::VBO_GENERATION_FAILED" << std::endl; - throw std::runtime_error("VBO generation failed"); + throw std::runtime_error("Failed to generate vertex buffer object"); } - + + // Bind and configure vertex array object glBindVertexArray(primitive_vao_); - - // Set up VBO for points - initialize with a small amount of memory glBindBuffer(GL_ARRAY_BUFFER, primitive_vbo_); - // Allocate a small buffer initially (for 10 points) to prevent segmentation faults - // when drawing before data is added - const size_t initial_buffer_size = 10 * sizeof(Point); + // Setup vertex attributes for Point structure + const size_t initial_buffer_size = 10 * sizeof(quickviz::Point); glBufferData(GL_ARRAY_BUFFER, initial_buffer_size, nullptr, GL_DYNAMIC_DRAW); - // Check for errors - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - std::cerr << "OpenGL error during VBO allocation: " << error << std::endl; - throw std::runtime_error("VBO allocation failed"); - } - - // Set up vertex attributes - // Position attribute - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Point), - (void*)offsetof(Point, position)); + // Position attribute (location = 0) + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(quickviz::Point), + (void*)offsetof(quickviz::Point, position)); glEnableVertexAttribArray(0); - - // Color attribute - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(Point), - (void*)offsetof(Point, color)); + + // Color attribute (location = 1) + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(quickviz::Point), + (void*)offsetof(quickviz::Point, color)); glEnableVertexAttribArray(1); - - // Size attribute - glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Point), - (void*)offsetof(Point, size)); + + // Size attribute (location = 2) + glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(quickviz::Point), + (void*)offsetof(quickviz::Point, size)); glEnableVertexAttribArray(2); - - // Unbind + + // Unbind for safety glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); std::cout << "GPU resources allocated successfully." << std::endl; - } - catch (const std::exception& e) { - std::cerr << "ERROR: Failed to allocate GPU resources: " << e.what() << std::endl; - ReleaseGpuResources(); // Clean up any resources that were created - throw; // Rethrow the exception + + } catch (const std::exception& e) { + std::cerr << "ERROR::CANVAS::GPU_RESOURCE_ALLOCATION_FAILED: " << e.what() << std::endl; + ReleaseGpuResources(); + throw; } } -void Canvas::ReleaseGpuResources() noexcept{ +void Canvas::ReleaseGpuResources() noexcept { // Delete background resources { std::lock_guard lock(background_mutex_); @@ -1011,7 +1096,7 @@ void Canvas::ReleaseGpuResources() noexcept{ background_vbo_ = 0; } - // Delete point resources + // Delete primitive rendering resources if (primitive_vao_ != 0) { glDeleteVertexArrays(1, &primitive_vao_); primitive_vao_ = 0; @@ -1025,6 +1110,7 @@ void Canvas::ReleaseGpuResources() noexcept{ void Canvas::OnDraw(const glm::mat4& projection, const glm::mat4& view, const glm::mat4& coord_transform) { + // Process any pending updates using original system if (has_pending_updates_) { ProcessPendingUpdates(); } @@ -1116,962 +1202,868 @@ void Canvas::OnDraw(const glm::mat4& projection, const glm::mat4& view, } } -void Canvas::RenderBatches(const glm::mat4& projection, const glm::mat4& view, - const glm::mat4& coord_transform) { - if (perf_config_.detailed_timing_enabled) { - frame_timer_.Start(); +void Canvas::FlushBatches() { + // Force processing of any pending updates + if (has_pending_updates_) { + ProcessPendingUpdates(); } - +} + +const RenderStats& Canvas::GetRenderStats() const { + return render_stats_; +} + +void Canvas::ResetRenderStats() { render_stats_.Reset(); +} + +void Canvas::SetPerformanceConfig(const PerformanceConfig& config) { + perf_config_ = config; +} + +const PerformanceConfig& Canvas::GetPerformanceConfig() const { + return perf_config_; +} + +size_t Canvas::GetMemoryUsage() const { + std::lock_guard lock(data_mutex_); + size_t total = sizeof(Canvas); - // Track memory usage - if (perf_config_.memory_tracking_enabled) { - size_t line_vertex_memory = 0; - for (const auto& [key, batch] : line_batches_) { - line_vertex_memory += batch.vertices.size() * sizeof(glm::vec3); - } - - size_t outline_vertex_memory = 0; - size_t outline_index_memory = 0; - for (const auto& [line_type, outline_batch] : outline_shape_batches_) { - outline_vertex_memory += outline_batch.vertices.size() * sizeof(float); - outline_index_memory += outline_batch.indices.size() * sizeof(uint32_t); - } - - render_stats_.vertex_memory_used = - line_vertex_memory + - filled_shape_batch_.vertices.size() * sizeof(float) + - outline_vertex_memory; - - render_stats_.index_memory_used = - filled_shape_batch_.indices.size() * sizeof(uint32_t) + - outline_index_memory; - - render_stats_.total_memory_used = - render_stats_.vertex_memory_used + - render_stats_.index_memory_used + - memory_tracker_.current_usage.load(); + // Add data structure sizes + if (data_) { + total += data_->points.capacity() * sizeof(Point); + total += data_->lines.capacity() * sizeof(Line); + total += data_->rectangles.capacity() * sizeof(Rectangle); + total += data_->circles.capacity() * sizeof(Circle); + total += data_->ellipses.capacity() * sizeof(Ellipse); + total += data_->polygons.capacity() * sizeof(Polygon); } - // Update batches if needed - UpdateBatches(); + // Add batch sizes + for (const auto& [type, batch] : line_batches_) { + total += batch.vertices.capacity() * sizeof(float); + total += batch.colors.capacity() * sizeof(float); + } - // Setup common rendering state - primitive_shader_.Use(); - primitive_shader_.TrySetUniform("projection", projection); - primitive_shader_.TrySetUniform("view", view); - primitive_shader_.TrySetUniform("model", glm::mat4(1.0f)); - primitive_shader_.TrySetUniform("coordSystemTransform", coord_transform); + total += filled_shape_batch_.vertices.capacity() * sizeof(float); + total += filled_shape_batch_.indices.capacity() * sizeof(uint32_t); - // Enable depth test and blending - glEnable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - render_stats_.state_changes += 3; // Track state changes + return total; +} + +void Canvas::OptimizeMemory() { + std::lock_guard lock(data_mutex_); + + // Shrink data vectors to fit + if (data_) { + data_->points.shrink_to_fit(); + data_->lines.shrink_to_fit(); + data_->rectangles.shrink_to_fit(); + data_->circles.shrink_to_fit(); + data_->ellipses.shrink_to_fit(); + data_->polygons.shrink_to_fit(); + } + + // Clear empty pending updates queue + if (pending_updates_.empty()) { + std::queue empty_queue; + pending_updates_.swap(empty_queue); + } +} + +void Canvas::PreallocateMemory(size_t estimated_objects) { + std::lock_guard lock(data_mutex_); + + if (data_) { + // Reserve based on typical distribution + data_->points.reserve(estimated_objects / 10); + data_->lines.reserve(estimated_objects / 4); + data_->rectangles.reserve(estimated_objects / 8); + data_->circles.reserve(estimated_objects / 8); + data_->ellipses.reserve(estimated_objects / 20); + data_->polygons.reserve(estimated_objects / 20); + } +} + +void Canvas::ShrinkToFit() { + OptimizeMemory(); // Same operation +} + +void Canvas::InitializeBatches() { + // Initialize line batches for each line type + line_batches_[LineType::kSolid] = LineBatch{}; + line_batches_[LineType::kDashed] = LineBatch{}; + line_batches_[LineType::kDotted] = LineBatch{}; - // Render lines batch by line type + // Initialize shape batches + filled_shape_batch_ = ShapeBatch{}; + outline_shape_batches_[LineType::kSolid] = ShapeBatch{}; + outline_shape_batches_[LineType::kDashed] = ShapeBatch{}; + outline_shape_batches_[LineType::kDotted] = ShapeBatch{}; +} + +void Canvas::ClearBatches() { + // Clean up line batches for (auto& [line_type, line_batch] : line_batches_) { - if (!line_batch.vertices.empty()) { - primitive_shader_.TrySetUniform("renderMode", 1); // Lines mode - primitive_shader_.TrySetUniform("lineType", static_cast(line_type)); - - // Group by thickness and render each group - std::map>> thickness_groups; - for (size_t i = 0; i < line_batch.thicknesses.size(); ++i) { - float thickness = line_batch.thicknesses[i]; - thickness_groups[thickness].emplace_back(i * 2, 2); // vertex start, count - } - - glBindVertexArray(line_batch.vao); - for (const auto& [thickness, ranges] : thickness_groups) { - glLineWidth(thickness); - for (const auto& [start, count] : ranges) { - glDrawArrays(GL_LINES, start, count); - render_stats_.draw_calls++; - } - render_stats_.lines_rendered += ranges.size(); - render_stats_.batched_objects += ranges.size(); - } - glLineWidth(1.0f); // Reset to default - glBindVertexArray(0); - - render_stats_.state_changes += 2; // VAO bind/unbind + if (line_batch.vao != 0) { + glDeleteVertexArrays(1, &line_batch.vao); + glDeleteBuffers(1, &line_batch.position_vbo); + glDeleteBuffers(1, &line_batch.color_vbo); + line_batch.vao = 0; + line_batch.position_vbo = 0; + line_batch.color_vbo = 0; } + line_batch.vertices.clear(); + line_batch.colors.clear(); + line_batch.thicknesses.clear(); + line_batch.line_types.clear(); + line_batch.sequence_numbers.clear(); } - // Render filled shapes batch - if (!filled_shape_batch_.vertices.empty() && !filled_shape_batch_.indices.empty()) { - primitive_shader_.TrySetUniform("renderMode", 2); // Filled shapes mode - - glBindVertexArray(filled_shape_batch_.vao); - glDrawElements(GL_TRIANGLES, filled_shape_batch_.indices.size(), GL_UNSIGNED_INT, 0); - glBindVertexArray(0); - - render_stats_.shapes_rendered += filled_shape_batch_.indices.size() / 3; - render_stats_.batched_objects += filled_shape_batch_.indices.size() / 3; - render_stats_.draw_calls++; - render_stats_.state_changes += 2; // VAO bind/unbind + // Clean up filled shape batch + if (filled_shape_batch_.vao != 0) { + glDeleteVertexArrays(1, &filled_shape_batch_.vao); + glDeleteBuffers(1, &filled_shape_batch_.vertex_vbo); + glDeleteBuffers(1, &filled_shape_batch_.color_vbo); + glDeleteBuffers(1, &filled_shape_batch_.ebo); + filled_shape_batch_.vao = 0; + filled_shape_batch_.vertex_vbo = 0; + filled_shape_batch_.color_vbo = 0; + filled_shape_batch_.ebo = 0; } + filled_shape_batch_.vertices.clear(); + filled_shape_batch_.indices.clear(); + filled_shape_batch_.colors.clear(); + filled_shape_batch_.sequence_numbers.clear(); - // Render outline shapes batch by line type + // Clean up outline shape batches for (auto& [line_type, outline_batch] : outline_shape_batches_) { - if (!outline_batch.vertices.empty() && !outline_batch.indices.empty()) { - primitive_shader_.TrySetUniform("renderMode", 3); // Outline shapes mode - primitive_shader_.TrySetUniform("lineType", static_cast(line_type)); - - glBindVertexArray(outline_batch.vao); - glDrawElements(GL_LINES, outline_batch.indices.size(), GL_UNSIGNED_INT, 0); - glBindVertexArray(0); - - render_stats_.shapes_rendered += outline_batch.indices.size() / 8; // Approximate shapes - render_stats_.batched_objects += outline_batch.indices.size() / 8; - render_stats_.draw_calls++; - render_stats_.state_changes += 2; // VAO bind/unbind + if (outline_batch.vao != 0) { + glDeleteVertexArrays(1, &outline_batch.vao); + glDeleteBuffers(1, &outline_batch.vertex_vbo); + glDeleteBuffers(1, &outline_batch.color_vbo); + glDeleteBuffers(1, &outline_batch.ebo); + outline_batch.vao = 0; + outline_batch.vertex_vbo = 0; + outline_batch.color_vbo = 0; + outline_batch.ebo = 0; } + outline_batch.vertices.clear(); + outline_batch.indices.clear(); + outline_batch.colors.clear(); + outline_batch.sequence_numbers.clear(); } - // Reset OpenGL state - glDisable(GL_DEPTH_TEST); - glDisable(GL_BLEND); - glBindVertexArray(0); - glUseProgram(0); - render_stats_.state_changes += 4; // State reset - - // Update performance statistics - if (perf_config_.detailed_timing_enabled) { - float frame_time = frame_timer_.ElapsedMs(); - render_stats_.UpdateFrameStats(frame_time); - } + // Clear the batch order tracker + batch_order_tracker_.Clear(); } -void Canvas::RenderIndividualShapes(const CanvasData& data, const glm::mat4& projection, - const glm::mat4& view, const glm::mat4& coord_transform) { - if (data.ellipses.empty() && data.polygons.empty()) { - return; +void Canvas::UpdateBatches() { + // The batching system is managed by the render strategies + // This method primarily exists for API compatibility + // Actual batching happens in BatchedRenderStrategy + + // Mark batches as needing update + for (auto& [type, batch] : line_batches_) { + batch.needs_update = true; } - if (perf_config_.detailed_timing_enabled) { - operation_timer_.Start(); + filled_shape_batch_.needs_update = true; + + for (auto& [type, batch] : outline_shape_batches_) { + batch.needs_update = true; + } +} + +void Canvas::RenderBatches(const glm::mat4& projection, const glm::mat4& view, + const glm::mat4& coord_transform) { + // Both batching and non-batching modes use unified sequence-based rendering + if (batching_enabled_) { + // Batching mode: render all primitives in sequence order (batched + individual) + if (!batch_order_tracker_.render_order.empty()) { + RenderBatchesInOrder(projection, view, coord_transform); + } + } else if (current_render_strategy_) { + // Non-batching mode: use individual rendering with sequence order + CanvasData data; + { + std::lock_guard lock(data_mutex_); + data = *data_; + } + + RenderContext context(projection, view, coord_transform, + &primitive_shader_, primitive_vao_, primitive_vbo_, + &render_stats_, &perf_config_); + current_render_strategy_->Render(data, context); } +} + +void Canvas::RenderBatchesInOrder(const glm::mat4& projection, const glm::mat4& view, + const glm::mat4& coord_transform) { + // Sort primitives by sequence number to maintain draw order + std::vector sorted_order = batch_order_tracker_.render_order; + std::sort(sorted_order.begin(), sorted_order.end(), + [](const auto& a, const auto& b) { return a.sequence_number < b.sequence_number; }); - // Setup rendering state - make sure we have the right shader program active + // Setup shader primitive_shader_.Use(); primitive_shader_.TrySetUniform("projection", projection); primitive_shader_.TrySetUniform("view", view); primitive_shader_.TrySetUniform("model", glm::mat4(1.0f)); primitive_shader_.TrySetUniform("coordSystemTransform", coord_transform); - // Enable necessary OpenGL states + // Enable OpenGL states glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); // Ensure later primitives draw on top glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - render_stats_.state_changes += 3; - // Render ellipses individually (not yet batched) - for (const auto& ellipse : data.ellipses) { - GLuint tempVAO, tempVBO; - glGenVertexArrays(1, &tempVAO); - glGenBuffers(1, &tempVBO); - - glBindVertexArray(tempVAO); - glBindBuffer(GL_ARRAY_BUFFER, tempVBO); - - // Generate ellipse vertices - const int segments = ellipse.num_segments; - std::vector vertices; - vertices.reserve((segments + 2) * 3); - - if (ellipse.filled) { - vertices.insert(vertices.end(), {ellipse.center.x, ellipse.center.y, ellipse.center.z}); - } - - for (int i = 0; i <= segments; i++) { - float t = ellipse.start_angle + (ellipse.end_angle - ellipse.start_angle) * i / segments; - float x_local = ellipse.rx * std::cos(t); - float y_local = ellipse.ry * std::sin(t); - float x_rotated = x_local * std::cos(ellipse.angle) - y_local * std::sin(ellipse.angle); - float y_rotated = x_local * std::sin(ellipse.angle) + y_local * std::cos(ellipse.angle); - float x = ellipse.center.x + x_rotated; - float y = ellipse.center.y + y_rotated; - float z = ellipse.center.z; - vertices.insert(vertices.end(), {x, y, z}); - } - - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW); - - // Set up vertex attributes correctly - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - - // Explicitly set default values for disabled attributes to ensure proper shader behavior - glDisableVertexAttribArray(1); // aColor - will default to (0,0,0,0) - glDisableVertexAttribArray(2); // aSize - will default to 0 - - // Set explicit default values for vertex attributes that aren't used - glVertexAttrib4f(1, 0.0f, 0.0f, 0.0f, 0.0f); // Ensure fragColor.a = 0 to trigger uniform fallback - glVertexAttrib1f(2, 1.0f); // Set default size - - primitive_shader_.TrySetUniform("uColor", ellipse.color); - - if (ellipse.filled) { - primitive_shader_.TrySetUniform("renderMode", 2); // Filled shapes mode - primitive_shader_.TrySetUniform("lineType", 0); // Solid fill - glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size() / 3); - } else { - primitive_shader_.TrySetUniform("renderMode", 3); // Outline shapes mode - primitive_shader_.TrySetUniform("lineType", static_cast(ellipse.line_type)); - glLineWidth(ellipse.thickness); - glDrawArrays(GL_LINE_STRIP, ellipse.filled ? 1 : 0, segments + 1); - glLineWidth(1.0f); - } - - glDisableVertexAttribArray(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteVertexArrays(1, &tempVAO); - glDeleteBuffers(1, &tempVBO); - - render_stats_.shapes_rendered++; - render_stats_.draw_calls++; - } - - // Render polygons individually (not yet batched) - for (const auto& polygon : data.polygons) { - GLuint tempVAO, tempVBO; - glGenVertexArrays(1, &tempVAO); - glGenBuffers(1, &tempVBO); - - glBindVertexArray(tempVAO); - glBindBuffer(GL_ARRAY_BUFFER, tempVBO); - - std::vector vertices; - vertices.reserve(polygon.vertices.size() * 3); - for (size_t i = 0; i < polygon.vertices.size(); ++i) { - const auto& vertex = polygon.vertices[i]; - vertices.insert(vertices.end(), {vertex.x, vertex.y, vertex.z}); - } - - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW); - - // Position attribute - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - - // Make sure other attributes are disabled - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(2); - - // Set default values for disabled attributes (CRITICAL FIX!) - glVertexAttrib4f(1, polygon.color.r, polygon.color.g, polygon.color.b, polygon.color.a); // Set color attribute to match polygon - glVertexAttrib1f(2, 1.0f); // Set default size attribute - - if (polygon.filled) { - // Draw filled polygon - set renderMode FIRST, then color - primitive_shader_.TrySetUniform("renderMode", 2); // Filled shapes mode - primitive_shader_.TrySetUniform("lineType", 0); // Solid fill - primitive_shader_.TrySetUniform("uColor", polygon.color); - glDrawArrays(GL_TRIANGLE_FAN, 0, polygon.vertices.size()); - } else { - // Draw outline - set renderMode FIRST, then color - primitive_shader_.TrySetUniform("renderMode", 3); // Outline shapes mode - primitive_shader_.TrySetUniform("lineType", static_cast(polygon.line_type)); - primitive_shader_.TrySetUniform("thickness", polygon.thickness); - primitive_shader_.TrySetUniform("uColor", polygon.color); - glLineWidth(polygon.thickness); - glDrawArrays(GL_LINE_LOOP, 0, polygon.vertices.size()); - glLineWidth(1.0f); - } - - // Clean up - glDisableVertexAttribArray(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteVertexArrays(1, &tempVAO); - glDeleteBuffers(1, &tempVBO); - - render_stats_.shapes_rendered++; - render_stats_.draw_calls++; - } - - // Render lines individually - for (const auto& line : data.lines) { - GLuint tempVAO, tempVBO; - glGenVertexArrays(1, &tempVAO); - glGenBuffers(1, &tempVBO); - - glBindVertexArray(tempVAO); - glBindBuffer(GL_ARRAY_BUFFER, tempVBO); - - std::vector vertices = {line.start.x, line.start.y, line.start.z, line.end.x, line.end.y, line.end.z}; - - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW); - - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(2); - - glVertexAttrib4f(1, 0.0f, 0.0f, 0.0f, 0.0f); - glVertexAttrib1f(2, 1.0f); - - primitive_shader_.TrySetUniform("uColor", line.color); - primitive_shader_.TrySetUniform("renderMode", 1); - primitive_shader_.TrySetUniform("lineType", static_cast(line.line_type)); - - glLineWidth(line.thickness); - glDrawArrays(GL_LINES, 0, 2); - glLineWidth(1.0f); - - glDisableVertexAttribArray(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteVertexArrays(1, &tempVAO); - glDeleteBuffers(1, &tempVBO); - - render_stats_.lines_rendered++; - render_stats_.draw_calls++; - } - - // Render circles individually - for (const auto& circle : data.circles) { - GLuint tempVAO, tempVBO; - glGenVertexArrays(1, &tempVAO); - glGenBuffers(1, &tempVBO); - - glBindVertexArray(tempVAO); - glBindBuffer(GL_ARRAY_BUFFER, tempVBO); - - const int segments = circle.num_segments; - std::vector vertices; - vertices.reserve((segments + 2) * 3); - - if (circle.filled) { - vertices.insert(vertices.end(), {circle.center.x, circle.center.y, circle.center.z}); - } - - for (int i = 0; i <= segments; i++) { - float angle = 2.0f * M_PI * i / segments; - float x = circle.center.x + circle.radius * std::cos(angle); - float y = circle.center.y + circle.radius * std::sin(angle); - float z = circle.center.z; - vertices.insert(vertices.end(), {x, y, z}); - } - - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW); - - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(2); - - glVertexAttrib4f(1, 0.0f, 0.0f, 0.0f, 0.0f); - glVertexAttrib1f(2, 1.0f); - - primitive_shader_.TrySetUniform("uColor", circle.color); - - if (circle.filled) { - primitive_shader_.TrySetUniform("renderMode", 2); - primitive_shader_.TrySetUniform("lineType", 0); - glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size() / 3); - } else { - primitive_shader_.TrySetUniform("renderMode", 3); - primitive_shader_.TrySetUniform("lineType", static_cast(circle.line_type)); - glLineWidth(circle.thickness); - glDrawArrays(GL_LINE_LOOP, circle.filled ? 1 : 0, segments + 1); - glLineWidth(1.0f); - } - - glDisableVertexAttribArray(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteVertexArrays(1, &tempVAO); - glDeleteBuffers(1, &tempVBO); - - render_stats_.shapes_rendered++; - render_stats_.draw_calls++; - } - - // Render rectangles individually - for (const auto& rect : data.rectangles) { - GLuint tempVAO, tempVBO; - glGenVertexArrays(1, &tempVAO); - glGenBuffers(1, &tempVBO); - - glBindVertexArray(tempVAO); - glBindBuffer(GL_ARRAY_BUFFER, tempVBO); - - std::vector vertices = { - rect.position.x, rect.position.y, rect.position.z, - rect.position.x + rect.width, rect.position.y, rect.position.z, - rect.position.x + rect.width, rect.position.y + rect.height, rect.position.z, - rect.position.x, rect.position.y + rect.height, rect.position.z - }; - - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW); - - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(2); - - glVertexAttrib4f(1, 0.0f, 0.0f, 0.0f, 0.0f); - glVertexAttrib1f(2, 1.0f); - - primitive_shader_.TrySetUniform("uColor", rect.color); - - if (rect.filled) { - primitive_shader_.TrySetUniform("renderMode", 2); - primitive_shader_.TrySetUniform("lineType", 0); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - } else { - primitive_shader_.TrySetUniform("renderMode", 3); - primitive_shader_.TrySetUniform("lineType", static_cast(rect.line_type)); - glLineWidth(rect.thickness); - glDrawArrays(GL_LINE_LOOP, 0, 4); - glLineWidth(1.0f); + // Render each primitive in sequence order + for (const auto& primitive : sorted_order) { + switch (primitive.type) { + case BatchOrderTracker::OrderedPrimitive::Type::kLine: { + auto& line_batch = line_batches_[primitive.line_type]; + if (line_batch.vertices.empty()) continue; + + // Setup VAO/VBO if needed + if (line_batch.vao == 0) { + glGenVertexArrays(1, &line_batch.vao); + glGenBuffers(1, &line_batch.position_vbo); + glGenBuffers(1, &line_batch.color_vbo); + } + + glBindVertexArray(line_batch.vao); + + // Upload vertex data + glBindBuffer(GL_ARRAY_BUFFER, line_batch.position_vbo); + glBufferData(GL_ARRAY_BUFFER, line_batch.vertices.size() * sizeof(glm::vec3), + line_batch.vertices.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(0); + + // Upload color data + glBindBuffer(GL_ARRAY_BUFFER, line_batch.color_vbo); + glBufferData(GL_ARRAY_BUFFER, line_batch.colors.size() * sizeof(glm::vec4), + line_batch.colors.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(1); + + // Set uniforms + primitive_shader_.TrySetUniform("renderMode", 1); // Lines mode + primitive_shader_.TrySetUniform("lineType", static_cast(primitive.line_type)); + + // Draw this specific line (2 vertices per line) + uint32_t start_vertex = primitive.batch_index * 2; + glLineWidth(line_batch.thicknesses[primitive.batch_index]); + glDrawArrays(GL_LINES, start_vertex, 2); + + glBindVertexArray(0); + break; + } + + case BatchOrderTracker::OrderedPrimitive::Type::kFilledShape: { + if (filled_shape_batch_.vertices.empty()) continue; + + // Setup VAO/VBO/EBO if needed + if (filled_shape_batch_.vao == 0) { + glGenVertexArrays(1, &filled_shape_batch_.vao); + glGenBuffers(1, &filled_shape_batch_.vertex_vbo); + glGenBuffers(1, &filled_shape_batch_.color_vbo); + glGenBuffers(1, &filled_shape_batch_.ebo); + } + + glBindVertexArray(filled_shape_batch_.vao); + + // Upload vertex data + glBindBuffer(GL_ARRAY_BUFFER, filled_shape_batch_.vertex_vbo); + glBufferData(GL_ARRAY_BUFFER, filled_shape_batch_.vertices.size() * sizeof(float), + filled_shape_batch_.vertices.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr); + glEnableVertexAttribArray(0); + + // Upload color data + glBindBuffer(GL_ARRAY_BUFFER, filled_shape_batch_.color_vbo); + glBufferData(GL_ARRAY_BUFFER, filled_shape_batch_.colors.size() * sizeof(glm::vec4), + filled_shape_batch_.colors.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(1); + + // Upload index data + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, filled_shape_batch_.ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, filled_shape_batch_.indices.size() * sizeof(uint32_t), + filled_shape_batch_.indices.data(), GL_DYNAMIC_DRAW); + + // Set uniforms + primitive_shader_.TrySetUniform("renderMode", 2); // Filled shapes mode + + // Draw only the specific shape using its index range + if (primitive.batch_index < filled_shape_batch_.index_ranges.size()) { + const auto& range = filled_shape_batch_.index_ranges[primitive.batch_index]; + glDrawElements(GL_TRIANGLES, range.count, GL_UNSIGNED_INT, + (void*)(range.start * sizeof(uint32_t))); + } + + glBindVertexArray(0); + break; + } + + case BatchOrderTracker::OrderedPrimitive::Type::kOutlineShape: { + auto& outline_batch = outline_shape_batches_[primitive.line_type]; + if (outline_batch.vertices.empty()) continue; + + // Setup VAO/VBO/EBO if needed + if (outline_batch.vao == 0) { + glGenVertexArrays(1, &outline_batch.vao); + glGenBuffers(1, &outline_batch.vertex_vbo); + glGenBuffers(1, &outline_batch.color_vbo); + glGenBuffers(1, &outline_batch.ebo); + } + + glBindVertexArray(outline_batch.vao); + + // Upload vertex data + glBindBuffer(GL_ARRAY_BUFFER, outline_batch.vertex_vbo); + glBufferData(GL_ARRAY_BUFFER, outline_batch.vertices.size() * sizeof(float), + outline_batch.vertices.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr); + glEnableVertexAttribArray(0); + + // Upload color data + glBindBuffer(GL_ARRAY_BUFFER, outline_batch.color_vbo); + glBufferData(GL_ARRAY_BUFFER, outline_batch.colors.size() * sizeof(glm::vec4), + outline_batch.colors.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(1); + + // Upload index data + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, outline_batch.ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, outline_batch.indices.size() * sizeof(uint32_t), + outline_batch.indices.data(), GL_DYNAMIC_DRAW); + + // Set uniforms + primitive_shader_.TrySetUniform("renderMode", 3); // Outlined shapes mode + primitive_shader_.TrySetUniform("lineType", static_cast(primitive.line_type)); + + // Draw only the specific shape using its index range + if (primitive.batch_index < outline_batch.index_ranges.size()) { + const auto& range = outline_batch.index_ranges[primitive.batch_index]; + glLineWidth(2.0f); // Default line width for outlines + glDrawElements(GL_LINE_LOOP, range.count, GL_UNSIGNED_INT, + (void*)(range.start * sizeof(uint32_t))); + } + + glBindVertexArray(0); + break; + } + + case BatchOrderTracker::OrderedPrimitive::Type::kIndividualShape: { + // Render specific individual shape (polygon or ellipse) by index + if (individual_strategy_) { + CanvasData data; + { + std::lock_guard lock(data_mutex_); + data = *data_; + } + + RenderContext context(projection, view, coord_transform, + &primitive_shader_, primitive_vao_, primitive_vbo_, + &render_stats_, &perf_config_); + + // Render only the specific shape based on type and index + if (primitive.individual_shape_type == BatchOrderTracker::OrderedPrimitive::IndividualShapeType::kPolygon) { + if (primitive.batch_index < data.polygons.size()) { + RenderSinglePolygon(data.polygons[primitive.batch_index], context); + } + } else if (primitive.individual_shape_type == BatchOrderTracker::OrderedPrimitive::IndividualShapeType::kEllipse) { + if (primitive.batch_index < data.ellipses.size()) { + RenderSingleEllipse(data.ellipses[primitive.batch_index], context); + } + } + } + break; + } } - - glDisableVertexAttribArray(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteVertexArrays(1, &tempVAO); - glDeleteBuffers(1, &tempVBO); - - render_stats_.shapes_rendered++; - render_stats_.draw_calls++; - } - - // Clean up OpenGL state - glDisable(GL_DEPTH_TEST); - glDisable(GL_BLEND); - glBindVertexArray(0); - glUseProgram(0); - - if (perf_config_.detailed_timing_enabled) { - float operation_time = operation_timer_.ElapsedMs(); - render_stats_.UpdateOperationStats(operation_time); } } -void Canvas::InitializeBatches() { - // Initialize line batches for each line type - for (auto line_type : {LineType::kSolid, LineType::kDashed, LineType::kDotted}) { - auto& line_batch = line_batches_[line_type]; - glGenVertexArrays(1, &line_batch.vao); - glGenBuffers(1, &line_batch.position_vbo); - glGenBuffers(1, &line_batch.color_vbo); - - glBindVertexArray(line_batch.vao); - - // Position buffer - glEnableVertexAttribArray(0); - glBindBuffer(GL_ARRAY_BUFFER, line_batch.position_vbo); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); - - // Color buffer - glEnableVertexAttribArray(1); - glBindBuffer(GL_ARRAY_BUFFER, line_batch.color_vbo); - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, 0); - - glBindVertexArray(0); - } - - // Initialize filled shape batch - glGenVertexArrays(1, &filled_shape_batch_.vao); - glGenBuffers(1, &filled_shape_batch_.vertex_vbo); - glGenBuffers(1, &filled_shape_batch_.color_vbo); - glGenBuffers(1, &filled_shape_batch_.ebo); - - glBindVertexArray(filled_shape_batch_.vao); - - // Vertex buffer - glEnableVertexAttribArray(0); - glBindBuffer(GL_ARRAY_BUFFER, filled_shape_batch_.vertex_vbo); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); - - // Color buffer - glEnableVertexAttribArray(1); - glBindBuffer(GL_ARRAY_BUFFER, filled_shape_batch_.color_vbo); - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, 0); - - // Element buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, filled_shape_batch_.ebo); - - glBindVertexArray(0); - - // Initialize outline shape batches for each line type - for (auto line_type : {LineType::kSolid, LineType::kDashed, LineType::kDotted}) { - auto& outline_batch = outline_shape_batches_[line_type]; - glGenVertexArrays(1, &outline_batch.vao); - glGenBuffers(1, &outline_batch.vertex_vbo); - glGenBuffers(1, &outline_batch.color_vbo); - glGenBuffers(1, &outline_batch.ebo); - - glBindVertexArray(outline_batch.vao); - - // Vertex buffer - glEnableVertexAttribArray(0); - glBindBuffer(GL_ARRAY_BUFFER, outline_batch.vertex_vbo); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); - - // Color buffer - glEnableVertexAttribArray(1); - glBindBuffer(GL_ARRAY_BUFFER, outline_batch.color_vbo); - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, 0); - - // Element buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, outline_batch.ebo); - - glBindVertexArray(0); +void Canvas::RenderIndividualShapes(const CanvasData& data, + const glm::mat4& projection, + const glm::mat4& view, + const glm::mat4& coord_transform) { + if (individual_strategy_) { + RenderContext context(projection, view, coord_transform, + &primitive_shader_, primitive_vao_, primitive_vbo_, + &render_stats_, &perf_config_); + individual_strategy_->Render(data, context); } - - std::cout << "Batching system initialized successfully." << std::endl; } -void Canvas::ClearBatches() { - // Clean up line batches - for (auto& [line_type, line_batch] : line_batches_) { - if (line_batch.vao != 0) { - glDeleteVertexArrays(1, &line_batch.vao); - glDeleteBuffers(1, &line_batch.position_vbo); - glDeleteBuffers(1, &line_batch.color_vbo); - line_batch.vao = 0; - line_batch.position_vbo = 0; - line_batch.color_vbo = 0; - } - } - - // Clean up filled shape batch - if (filled_shape_batch_.vao != 0) { - glDeleteVertexArrays(1, &filled_shape_batch_.vao); - glDeleteBuffers(1, &filled_shape_batch_.vertex_vbo); - glDeleteBuffers(1, &filled_shape_batch_.color_vbo); - glDeleteBuffers(1, &filled_shape_batch_.ebo); - filled_shape_batch_.vao = 0; - filled_shape_batch_.vertex_vbo = 0; - filled_shape_batch_.color_vbo = 0; - filled_shape_batch_.ebo = 0; - } - - // Clean up outline shape batches - for (auto& [line_type, outline_batch] : outline_shape_batches_) { - if (outline_batch.vao != 0) { - glDeleteVertexArrays(1, &outline_batch.vao); - glDeleteBuffers(1, &outline_batch.vertex_vbo); - glDeleteBuffers(1, &outline_batch.color_vbo); - glDeleteBuffers(1, &outline_batch.ebo); - outline_batch.vao = 0; - outline_batch.vertex_vbo = 0; - outline_batch.color_vbo = 0; - outline_batch.ebo = 0; - } - } - - // Clear CPU data - for (auto& [line_type, line_batch] : line_batches_) { - line_batch.vertices.clear(); - line_batch.colors.clear(); - line_batch.thicknesses.clear(); - line_batch.line_types.clear(); - } - - filled_shape_batch_.vertices.clear(); - filled_shape_batch_.indices.clear(); - filled_shape_batch_.colors.clear(); +void Canvas::GenerateRectangleVertices(float x, float y, float width, float height, + std::vector& vertices, + std::vector& indices, bool filled, + uint32_t base_index, uint32_t sequence_number) { + // Use sequence number to determine Z depth for proper layering + float z_depth = sequence_number * 0.001f; - for (auto& [line_type, outline_batch] : outline_shape_batches_) { - outline_batch.vertices.clear(); - outline_batch.indices.clear(); - outline_batch.colors.clear(); - } -} - -void Canvas::FlushBatches() { - // Force update of all batches - for (auto& [line_type, line_batch] : line_batches_) { - line_batch.needs_update = true; - } - filled_shape_batch_.needs_update = true; - for (auto& [line_type, outline_batch] : outline_shape_batches_) { - outline_batch.needs_update = true; - } - UpdateBatches(); -} - -void Canvas::UpdateBatches() { - // Update line batches - for (auto& [line_type, line_batch] : line_batches_) { - if (line_batch.needs_update && !line_batch.vertices.empty()) { - glBindVertexArray(line_batch.vao); - - // Update position buffer - glBindBuffer(GL_ARRAY_BUFFER, line_batch.position_vbo); - glBufferData(GL_ARRAY_BUFFER, - line_batch.vertices.size() * sizeof(glm::vec3), - line_batch.vertices.data(), GL_DYNAMIC_DRAW); - - // Update color buffer - glBindBuffer(GL_ARRAY_BUFFER, line_batch.color_vbo); - glBufferData(GL_ARRAY_BUFFER, - line_batch.colors.size() * sizeof(glm::vec4), - line_batch.colors.data(), GL_DYNAMIC_DRAW); - - glBindVertexArray(0); - line_batch.needs_update = false; - } - } + // Generate 4 corner vertices for rectangle + float vertices_data[] = { + x, y, z_depth, // Bottom-left + x + width, y, z_depth, // Bottom-right + x + width, y + height, z_depth, // Top-right + x, y + height, z_depth // Top-left + }; - // Update filled shape batch - if (filled_shape_batch_.needs_update && !filled_shape_batch_.vertices.empty()) { - glBindVertexArray(filled_shape_batch_.vao); - - // Update vertex buffer - glBindBuffer(GL_ARRAY_BUFFER, filled_shape_batch_.vertex_vbo); - glBufferData(GL_ARRAY_BUFFER, - filled_shape_batch_.vertices.size() * sizeof(float), - filled_shape_batch_.vertices.data(), GL_DYNAMIC_DRAW); - - // Update color buffer - colors should already match vertices 1:1 - // Each color in filled_shape_batch_.colors corresponds to one vertex - size_t expected_vertex_count = filled_shape_batch_.vertices.size() / 3; - if (filled_shape_batch_.colors.size() != expected_vertex_count) { - std::cerr << "ERROR: Color count mismatch in filled_shape_batch! " - << "Expected " << expected_vertex_count << " colors, got " - << filled_shape_batch_.colors.size() << std::endl; - } - - glBindBuffer(GL_ARRAY_BUFFER, filled_shape_batch_.color_vbo); - glBufferData(GL_ARRAY_BUFFER, - filled_shape_batch_.colors.size() * sizeof(glm::vec4), - filled_shape_batch_.colors.data(), GL_DYNAMIC_DRAW); - - // Update index buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, filled_shape_batch_.ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - filled_shape_batch_.indices.size() * sizeof(uint32_t), - filled_shape_batch_.indices.data(), GL_DYNAMIC_DRAW); - - glBindVertexArray(0); - filled_shape_batch_.needs_update = false; + // Add vertices to the vector + for (int i = 0; i < 12; i += 3) { + vertices.push_back(vertices_data[i]); + vertices.push_back(vertices_data[i + 1]); + vertices.push_back(vertices_data[i + 2]); } - // Update outline shape batches - for (auto& [line_type, outline_batch] : outline_shape_batches_) { - if (outline_batch.needs_update && !outline_batch.vertices.empty()) { - glBindVertexArray(outline_batch.vao); - - // Update vertex buffer - glBindBuffer(GL_ARRAY_BUFFER, outline_batch.vertex_vbo); - glBufferData(GL_ARRAY_BUFFER, - outline_batch.vertices.size() * sizeof(float), - outline_batch.vertices.data(), GL_DYNAMIC_DRAW); - - // Update color buffer - colors should already match vertices 1:1 - // Each color in outline_batch.colors corresponds to one vertex - size_t expected_vertex_count = outline_batch.vertices.size() / 3; - if (outline_batch.colors.size() != expected_vertex_count) { - std::cerr << "ERROR: Color count mismatch in outline_batch! " - << "Expected " << expected_vertex_count << " colors, got " - << outline_batch.colors.size() << std::endl; - } - - glBindBuffer(GL_ARRAY_BUFFER, outline_batch.color_vbo); - glBufferData(GL_ARRAY_BUFFER, - outline_batch.colors.size() * sizeof(glm::vec4), - outline_batch.colors.data(), GL_DYNAMIC_DRAW); - - // Update index buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, outline_batch.ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - outline_batch.indices.size() * sizeof(uint32_t), - outline_batch.indices.data(), GL_DYNAMIC_DRAW); - - glBindVertexArray(0); - outline_batch.needs_update = false; - } + if (filled) { + // Two triangles for filled rectangle + indices.insert(indices.end(), { + base_index, base_index + 1, base_index + 2, // Triangle 1 + base_index, base_index + 2, base_index + 3 // Triangle 2 + }); + } else { + // Line loop for outline + indices.insert(indices.end(), { + base_index, base_index + 1, + base_index + 1, base_index + 2, + base_index + 2, base_index + 3, + base_index + 3, base_index + }); } } void Canvas::GenerateCircleVertices(float cx, float cy, float radius, int segments, - std::vector& vertices, std::vector& indices, - bool filled, uint32_t base_index) { + std::vector& vertices, + std::vector& indices, bool filled, + uint32_t base_index, uint32_t sequence_number) { + // Use sequence number to determine Z depth for proper layering + float z_depth = sequence_number * 0.001f; + if (filled) { - // Add center vertex for filled circle - vertices.insert(vertices.end(), {cx, cy, 0.0f}); + // Add center vertex + vertices.insert(vertices.end(), {cx, cy, z_depth}); // Add perimeter vertices - for (int i = 0; i <= segments; i++) { + for (int i = 0; i <= segments; ++i) { float angle = 2.0f * M_PI * i / segments; float x = cx + radius * std::cos(angle); float y = cy + radius * std::sin(angle); - vertices.insert(vertices.end(), {x, y, 0.0f}); - - // Create triangle indices (center, current, next) - if (i < segments) { - indices.insert(indices.end(), { - base_index, // Center - base_index + 1 + i, // Current perimeter vertex - base_index + 1 + ((i + 1) % segments) // Next perimeter vertex - }); - } + vertices.insert(vertices.end(), {x, y, z_depth}); + } + + // Generate triangle fan indices + for (int i = 0; i < segments; ++i) { + indices.insert(indices.end(), { + base_index, // Center + base_index + 1 + i, // Current perimeter vertex + base_index + 1 + ((i + 1) % segments) // Next perimeter vertex + }); } } else { - // Add perimeter vertices for outline - for (int i = 0; i <= segments; i++) { + // Add perimeter vertices only + for (int i = 0; i <= segments; ++i) { float angle = 2.0f * M_PI * i / segments; float x = cx + radius * std::cos(angle); float y = cy + radius * std::sin(angle); - vertices.insert(vertices.end(), {x, y, 0.0f}); - - // Create line indices (current, next) - if (i < segments) { - indices.insert(indices.end(), { - base_index + i, // Current vertex - base_index + (i + 1) // Next vertex - }); - } + vertices.insert(vertices.end(), {x, y, z_depth}); + } + + // Generate line loop indices + for (int i = 0; i < segments; ++i) { + indices.insert(indices.end(), { + base_index + i, + base_index + ((i + 1) % segments) + }); } } } -void Canvas::GenerateRectangleVertices(float x, float y, float width, float height, - std::vector& vertices, std::vector& indices, - bool filled, uint32_t base_index) { - // Add the four corner vertices - vertices.insert(vertices.end(), { - x, y, 0.0f, // Bottom left - x + width, y, 0.0f, // Bottom right - x + width, y + height, 0.0f, // Top right - x, y + height, 0.0f // Top left - }); - - if (filled) { - // Two triangles for filled rectangle - indices.insert(indices.end(), { - base_index, base_index + 1, base_index + 2, // First triangle - base_index, base_index + 2, base_index + 3 // Second triangle - }); - } else { - // Four lines for rectangle outline - indices.insert(indices.end(), { - base_index, base_index + 1, // Bottom edge - base_index + 1, base_index + 2, // Right edge - base_index + 2, base_index + 3, // Top edge - base_index + 3, base_index // Left edge - }); - } -} - -void Canvas::GenerateEllipseVertices(const PendingUpdate::ellipse_params& ellipse, - std::vector& vertices, std::vector& indices, - bool filled, uint32_t base_index) { +void Canvas::GenerateEllipseVertices(float x, float y, float rx, float ry, float angle, + float start_angle, float end_angle, + std::vector& vertices, + std::vector& indices, bool filled, + uint32_t base_index) { const int segments = 32; + float cos_rot = std::cos(angle); + float sin_rot = std::sin(angle); + if (filled) { - vertices.insert(vertices.end(), {ellipse.x, ellipse.y, 0.0f}); - for (int i = 0; i <= segments; i++) { - float angle = ellipse.start_angle + (ellipse.end_angle - ellipse.start_angle) * i / segments; - float x_local = ellipse.rx * std::cos(angle); - float y_local = ellipse.ry * std::sin(angle); - float x_rotated = x_local * std::cos(ellipse.angle) - y_local * std::sin(ellipse.angle); - float y_rotated = x_local * std::sin(ellipse.angle) + y_local * std::cos(ellipse.angle); - vertices.insert(vertices.end(), {ellipse.x + x_rotated, ellipse.y + y_rotated, 0.0f}); - if (i < segments) { - indices.insert(indices.end(), {base_index, base_index + 1 + i, base_index + 1 + ((i + 1) % segments)}); - } + // Add center vertex + vertices.insert(vertices.end(), {x, y, 0.0f}); + + // Add perimeter vertices + for (int i = 0; i <= segments; ++i) { + float t = start_angle + (end_angle - start_angle) * i / segments; + float local_x = rx * std::cos(t); + float local_y = ry * std::sin(t); + + // Apply rotation + float rotated_x = local_x * cos_rot - local_y * sin_rot; + float rotated_y = local_x * sin_rot + local_y * cos_rot; + + vertices.insert(vertices.end(), {x + rotated_x, y + rotated_y, 0.0f}); + } + + // Generate triangle fan indices + for (int i = 0; i < segments; ++i) { + indices.insert(indices.end(), { + base_index, // Center + base_index + 1 + i, // Current perimeter vertex + base_index + 1 + ((i + 1) % segments) // Next perimeter vertex + }); } } else { - for (int i = 0; i <= segments; i++) { - float angle = ellipse.start_angle + (ellipse.end_angle - ellipse.start_angle) * i / segments; - float x_local = ellipse.rx * std::cos(angle); - float y_local = ellipse.ry * std::sin(angle); - float x_rotated = x_local * std::cos(ellipse.angle) - y_local * std::sin(ellipse.angle); - float y_rotated = x_local * std::sin(ellipse.angle) + y_local * std::cos(ellipse.angle); - vertices.insert(vertices.end(), {ellipse.x + x_rotated, ellipse.y + y_rotated, 0.0f}); - if (i < segments) { - indices.insert(indices.end(), {base_index + i, base_index + (i + 1)}); - } + // Add perimeter vertices only + for (int i = 0; i <= segments; ++i) { + float t = start_angle + (end_angle - start_angle) * i / segments; + float local_x = rx * std::cos(t); + float local_y = ry * std::sin(t); + + // Apply rotation + float rotated_x = local_x * cos_rot - local_y * sin_rot; + float rotated_y = local_x * sin_rot + local_y * cos_rot; + + vertices.insert(vertices.end(), {x + rotated_x, y + rotated_y, 0.0f}); + } + + // Generate line strip indices + for (int i = 0; i < segments; ++i) { + indices.insert(indices.end(), { + base_index + i, + base_index + i + 1 + }); } } } void Canvas::GeneratePolygonVertices(const std::vector& points, - std::vector& vertices, std::vector& indices, - bool filled, uint32_t base_index) { + std::vector& vertices, + std::vector& indices, bool filled, + uint32_t base_index) { + // Add vertices for (const auto& point : points) { vertices.insert(vertices.end(), {point.x, point.y, 0.0f}); } + if (filled) { - // Simple fan triangulation, works for convex polygons + // Simple triangulation (works for convex polygons) for (size_t i = 1; i < points.size() - 1; ++i) { - indices.insert(indices.end(), {static_cast(base_index), static_cast(base_index + i), static_cast(base_index + i + 1)}); + indices.insert(indices.end(), { + base_index, // First vertex + base_index + i, // Current vertex + base_index + i + 1 // Next vertex + }); } } else { + // Line loop for outline for (size_t i = 0; i < points.size(); ++i) { - indices.insert(indices.end(), {static_cast(base_index + i), static_cast(base_index + (i + 1) % points.size())}); + indices.insert(indices.end(), { + base_index + i, + base_index + ((i + 1) % points.size()) + }); } } } -void Canvas::OptimizeMemory() { - std::lock_guard lock(data_mutex_); +// Immediate rendering methods for non-batching mode +void Canvas::RenderPointImmediate(float x, float y, const glm::vec4& color, float thickness) { + glm::mat4 projection, view, coord_transform; + GetCurrentMatrices(projection, view, coord_transform); - if (perf_config_.aggressive_memory_cleanup) { - // Shrink batch vectors to fit current usage - for (auto& [line_type, line_batch] : line_batches_) { - line_batch.vertices.shrink_to_fit(); - line_batch.colors.shrink_to_fit(); - line_batch.thicknesses.shrink_to_fit(); - } - - filled_shape_batch_.vertices.shrink_to_fit(); - filled_shape_batch_.indices.shrink_to_fit(); - filled_shape_batch_.colors.shrink_to_fit(); - - for (auto& [line_type, outline_batch] : outline_shape_batches_) { - outline_batch.vertices.shrink_to_fit(); - outline_batch.indices.shrink_to_fit(); - outline_batch.colors.shrink_to_fit(); - } - - // Clear pending updates queue if it's grown too large - if (pending_updates_.size() == 0) { - std::queue empty_queue; - pending_updates_.swap(empty_queue); - } - } + // Setup shader + primitive_shader_.Use(); + primitive_shader_.TrySetUniform("projection", projection); + primitive_shader_.TrySetUniform("view", view); + primitive_shader_.TrySetUniform("model", glm::mat4(1.0f)); + primitive_shader_.TrySetUniform("coordSystemTransform", coord_transform); + primitive_shader_.TrySetUniform("renderMode", 0); // Points mode - // Update memory tracking - if (perf_config_.memory_tracking_enabled) { - size_t current_usage = GetMemoryUsage(); - memory_tracker_.current_usage = current_usage; - } + // Enable OpenGL states + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBindVertexArray(primitive_vao_); + glEnable(GL_PROGRAM_POINT_SIZE); + + Point point = {{x, y, 0.0f}, color, thickness}; + glBindBuffer(GL_ARRAY_BUFFER, primitive_vbo_); + glBufferData(GL_ARRAY_BUFFER, sizeof(Point), &point, GL_DYNAMIC_DRAW); + + glPointSize(thickness); + glDrawArrays(GL_POINTS, 0, 1); + glPointSize(1.0f); // Reset + + glBindVertexArray(0); } -void Canvas::PreallocateMemory(size_t estimated_objects) { - std::lock_guard lock(data_mutex_); +void Canvas::RenderLineImmediate(float x1, float y1, float x2, float y2, const glm::vec4& color, + float thickness, LineType line_type) { + glm::mat4 projection, view, coord_transform; + GetCurrentMatrices(projection, view, coord_transform); - // Pre-allocate batch vectors based on estimated usage - size_t reserve_size = std::min(estimated_objects, perf_config_.max_batch_size); + // Setup shader + primitive_shader_.Use(); + primitive_shader_.TrySetUniform("projection", projection); + primitive_shader_.TrySetUniform("view", view); + primitive_shader_.TrySetUniform("model", glm::mat4(1.0f)); + primitive_shader_.TrySetUniform("coordSystemTransform", coord_transform); + primitive_shader_.TrySetUniform("renderMode", 1); // Lines mode + primitive_shader_.TrySetUniform("lineType", static_cast(line_type)); - for (auto& [line_type, line_batch] : line_batches_) { - line_batch.vertices.reserve(reserve_size * 2); // 2 vertices per line - line_batch.colors.reserve(reserve_size * 2); - line_batch.thicknesses.reserve(reserve_size); - } + // Enable OpenGL states + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - filled_shape_batch_.vertices.reserve(reserve_size * 12); // ~4 vertices per shape * 3 components - filled_shape_batch_.indices.reserve(reserve_size * 6); // ~2 triangles per shape - filled_shape_batch_.colors.reserve(reserve_size * 4); // 4 vertices per shape + glBindVertexArray(primitive_vao_); - for (auto& [line_type, outline_batch] : outline_shape_batches_) { - outline_batch.vertices.reserve(reserve_size * 12); - outline_batch.indices.reserve(reserve_size * 8); // ~4 edges per shape - outline_batch.colors.reserve(reserve_size * 4); - } + Point vertices[] = { + {{x1, y1, 0.0f}, color, thickness}, + {{x2, y2, 0.0f}, color, thickness} + }; - // Record the pre-allocation - if (perf_config_.memory_tracking_enabled) { - size_t allocated_size = GetMemoryUsage(); - memory_tracker_.RecordAllocation(allocated_size); - } + glBindBuffer(GL_ARRAY_BUFFER, primitive_vbo_); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + + glLineWidth(thickness); + glDrawArrays(GL_LINES, 0, 2); + glLineWidth(1.0f); // Reset + + glBindVertexArray(0); } -void Canvas::ShrinkToFit() { - std::lock_guard lock(data_mutex_); +void Canvas::RenderCircleImmediate(float x, float y, float radius, const glm::vec4& color, + bool filled, float thickness, LineType line_type) { + glm::mat4 projection, view, coord_transform; + GetCurrentMatrices(projection, view, coord_transform); - // Shrink all batch vectors to minimum required size - for (auto& [line_type, line_batch] : line_batches_) { - line_batch.vertices.shrink_to_fit(); - line_batch.colors.shrink_to_fit(); - line_batch.thicknesses.shrink_to_fit(); + // Setup shader + primitive_shader_.Use(); + primitive_shader_.TrySetUniform("projection", projection); + primitive_shader_.TrySetUniform("view", view); + primitive_shader_.TrySetUniform("model", glm::mat4(1.0f)); + primitive_shader_.TrySetUniform("coordSystemTransform", coord_transform); + + // Enable OpenGL states + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBindVertexArray(primitive_vao_); + + const int segments = 32; + std::vector vertices; + + if (filled) { + primitive_shader_.TrySetUniform("renderMode", 2); // Filled shapes + // Add center point for triangle fan + vertices.push_back({{x, y, 0.0f}, color, thickness}); + } else { + primitive_shader_.TrySetUniform("renderMode", 3); // Outlined shapes + primitive_shader_.TrySetUniform("lineType", static_cast(line_type)); } - filled_shape_batch_.vertices.shrink_to_fit(); - filled_shape_batch_.indices.shrink_to_fit(); - filled_shape_batch_.colors.shrink_to_fit(); + // Generate circle points + for (int i = 0; i <= segments; i++) { + float angle = 2.0f * M_PI * i / segments; + float px = x + radius * std::cos(angle); + float py = y + radius * std::sin(angle); + vertices.push_back({{px, py, 0.0f}, color, thickness}); + } - for (auto& [line_type, outline_batch] : outline_shape_batches_) { - outline_batch.vertices.shrink_to_fit(); - outline_batch.indices.shrink_to_fit(); - outline_batch.colors.shrink_to_fit(); + glBindBuffer(GL_ARRAY_BUFFER, primitive_vbo_); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Point), vertices.data(), GL_DYNAMIC_DRAW); + + if (filled) { + glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size()); + } else { + glLineWidth(thickness); + glDrawArrays(GL_LINE_LOOP, 1, segments); // Skip center point for outline + glLineWidth(1.0f); } - // Clear and shrink pending updates queue - std::queue empty_queue; - pending_updates_.swap(empty_queue); + glBindVertexArray(0); } -size_t Canvas::GetMemoryUsage() const { - size_t total_usage = 0; +void Canvas::RenderRectangleImmediate(float x, float y, float width, float height, + const glm::vec4& color, bool filled, float thickness, + LineType line_type) { + glm::mat4 projection, view, coord_transform; + GetCurrentMatrices(projection, view, coord_transform); - // Calculate batch memory usage - for (const auto& [line_type, line_batch] : line_batches_) { - total_usage += line_batch.vertices.capacity() * sizeof(glm::vec3); - total_usage += line_batch.colors.capacity() * sizeof(glm::vec4); - total_usage += line_batch.thicknesses.capacity() * sizeof(float); - } + // Setup shader + primitive_shader_.Use(); + primitive_shader_.TrySetUniform("projection", projection); + primitive_shader_.TrySetUniform("view", view); + primitive_shader_.TrySetUniform("model", glm::mat4(1.0f)); + primitive_shader_.TrySetUniform("coordSystemTransform", coord_transform); - total_usage += filled_shape_batch_.vertices.capacity() * sizeof(float); - total_usage += filled_shape_batch_.indices.capacity() * sizeof(uint32_t); - total_usage += filled_shape_batch_.colors.capacity() * sizeof(glm::vec4); + // Enable OpenGL states + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - for (const auto& [line_type, outline_batch] : outline_shape_batches_) { - total_usage += outline_batch.vertices.capacity() * sizeof(float); - total_usage += outline_batch.indices.capacity() * sizeof(uint32_t); - total_usage += outline_batch.colors.capacity() * sizeof(glm::vec4); - } + glBindVertexArray(primitive_vao_); - // Add estimated size of pending updates queue - total_usage += pending_updates_.size() * sizeof(PendingUpdate); + // Generate rectangle vertices + Point vertices[4] = { + {{x, y, 0.0f}, color, thickness}, // bottom-left + {{x + width, y, 0.0f}, color, thickness}, // bottom-right + {{x + width, y + height, 0.0f}, color, thickness}, // top-right + {{x, y + height, 0.0f}, color, thickness} // top-left + }; - // Add estimated CanvasData memory usage - if (data_) { - // Rough estimation - in a real implementation you'd calculate exact sizes - total_usage += 1024; // Estimated base CanvasData size + glBindBuffer(GL_ARRAY_BUFFER, primitive_vbo_); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + + if (filled) { + primitive_shader_.TrySetUniform("renderMode", 2); // Filled shapes + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } else { + primitive_shader_.TrySetUniform("renderMode", 3); // Outlined shapes + primitive_shader_.TrySetUniform("lineType", static_cast(line_type)); + glLineWidth(thickness); + glDrawArrays(GL_LINE_LOOP, 0, 4); + glLineWidth(1.0f); } - return total_usage; -} - -void Canvas::SetBatchingEnabled(bool enabled) { - batching_enabled_ = enabled; - // Update current render strategy based on batching preference - current_render_strategy_ = enabled ? - static_cast(batched_strategy_.get()) : - static_cast(individual_strategy_.get()); + glBindVertexArray(0); } -RenderStrategy* Canvas::SelectRenderStrategy(const CanvasData& data) { - // Select render strategy based on batching setting and data characteristics - if (batching_enabled_) { - return batched_strategy_.get(); +void Canvas::RenderEllipseImmediate(float x, float y, float rx, float ry, float angle, + float start_angle, float end_angle, const glm::vec4& color, + bool filled, float thickness, LineType line_type) { + // For ellipses, use the shape renderer if available, or fall back to circle approximation + if (shape_renderer_) { + // Create ellipse data structure + Ellipse ellipse; + ellipse.center = glm::vec3(x, y, 0.0f); + ellipse.rx = rx; + ellipse.ry = ry; + ellipse.angle = angle; + ellipse.start_angle = start_angle; + ellipse.end_angle = end_angle; + ellipse.color = color; + ellipse.filled = filled; + ellipse.thickness = thickness; + ellipse.line_type = line_type; + ellipse.num_segments = 32; + + auto vertices = ShapeGenerators::GenerateEllipseVertices(ellipse); + VertexLayout layout = VertexLayout::PositionOnly(); + + RenderParams params; + params.color = color; + params.thickness = thickness; + params.filled = filled; + params.primitive_type = filled ? GL_TRIANGLE_FAN : GL_LINE_LOOP; + params.vertex_count = vertices.size() / 3; + + shape_renderer_->RenderShape(vertices, layout, params); } else { - return individual_strategy_.get(); + // Simple fallback: render as circle using average radius + float avg_radius = (rx + ry) / 2.0f; + RenderCircleImmediate(x, y, avg_radius, color, filled, thickness, line_type); } - - // Future enhancement: Could select strategy based on data characteristics - // For example, use individual strategy for scenes with many complex polygons - // and batched strategy for scenes with many simple shapes } -// Performance monitoring methods (moved from inline in header) -const RenderStats& Canvas::GetRenderStats() const { - return render_stats_; +void Canvas::RenderPolygonImmediate(const std::vector& points, const glm::vec4& color, + bool filled, float thickness, LineType line_type) { + if (shape_renderer_) { + // Create polygon data structure + Polygon polygon; + polygon.vertices.reserve(points.size()); + for (const auto& p : points) { + polygon.vertices.push_back(glm::vec3(p, 0.0f)); + } + polygon.color = color; + polygon.filled = filled; + polygon.thickness = thickness; + polygon.line_type = line_type; + + auto vertices = ShapeGenerators::GeneratePolygonVertices(polygon); + VertexLayout layout = VertexLayout::PositionOnly(); + + RenderParams params; + params.color = color; + params.thickness = thickness; + params.filled = filled; + params.primitive_type = filled ? GL_TRIANGLE_FAN : GL_LINE_LOOP; + params.vertex_count = vertices.size() / 3; + + shape_renderer_->RenderShape(vertices, layout, params); + } else { + // Simple fallback: render as line loop + glm::mat4 projection, view, coord_transform; + GetCurrentMatrices(projection, view, coord_transform); + + primitive_shader_.Use(); + primitive_shader_.TrySetUniform("projection", projection); + primitive_shader_.TrySetUniform("view", view); + primitive_shader_.TrySetUniform("model", glm::mat4(1.0f)); + primitive_shader_.TrySetUniform("coordSystemTransform", coord_transform); + primitive_shader_.TrySetUniform("renderMode", 3); // Outlined shapes + primitive_shader_.TrySetUniform("lineType", static_cast(line_type)); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBindVertexArray(primitive_vao_); + + std::vector vertices; + for (const auto& p : points) { + vertices.push_back({{p.x, p.y, 0.0f}, color, thickness}); + } + + glBindBuffer(GL_ARRAY_BUFFER, primitive_vbo_); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Point), vertices.data(), GL_DYNAMIC_DRAW); + + glLineWidth(thickness); + glDrawArrays(GL_LINE_LOOP, 0, vertices.size()); + glLineWidth(1.0f); + + glBindVertexArray(0); + } } -void Canvas::ResetRenderStats() { - render_stats_.Reset(); +void Canvas::RenderSinglePolygon(const Polygon& polygon, const RenderContext& context) { + if (individual_strategy_) { + // Cast to IndividualRenderStrategy and call the render method + auto* strategy = static_cast(individual_strategy_.get()); + strategy->RenderSinglePolygon(polygon, context); + } } -void Canvas::SetPerformanceConfig(const PerformanceConfig& config) { - perf_config_ = config; +void Canvas::RenderSingleEllipse(const Ellipse& ellipse, const RenderContext& context) { + if (individual_strategy_) { + // Cast to IndividualRenderStrategy and call the render method + auto* strategy = static_cast(individual_strategy_.get()); + strategy->RenderSingleEllipse(ellipse, context); + } } -const PerformanceConfig& Canvas::GetPerformanceConfig() const { - return perf_config_; +void Canvas::GetCurrentMatrices(glm::mat4& projection, glm::mat4& view, glm::mat4& coord_transform) { + // For immediate rendering, we need to get the current matrices + // This is a simplified implementation - in a real application, you'd get these from the current render context + // For now, we'll use identity matrices as a fallback + projection = glm::mat4(1.0f); + view = glm::mat4(1.0f); + coord_transform = glm::mat4(1.0f); + + // TODO: In a complete implementation, this should retrieve the current view/projection matrices + // from the active rendering context or scene manager } } // namespace quickviz diff --git a/src/renderer/src/renderable/details/canvas_data.hpp b/src/renderer/src/renderable/details/canvas_data.hpp index 32f5893..657a077 100644 --- a/src/renderer/src/renderable/details/canvas_data.hpp +++ b/src/renderer/src/renderable/details/canvas_data.hpp @@ -27,6 +27,7 @@ struct Point { glm::vec3 position; glm::vec4 color; float size; + uint32_t sequence_number = 0; // For maintaining draw order }; // Line structure @@ -36,6 +37,7 @@ struct Line { glm::vec4 color; float thickness; LineType line_type; + uint32_t sequence_number = 0; // For maintaining draw order }; // Rectangle structure @@ -47,6 +49,7 @@ struct Rectangle { bool filled; float thickness; LineType line_type; + uint32_t sequence_number = 0; // For maintaining draw order }; // Circle structure @@ -58,6 +61,7 @@ struct Circle { float thickness; LineType line_type; int num_segments; // Number of segments to approximate the circle + uint32_t sequence_number = 0; // For maintaining draw order }; // Ellipse structure @@ -73,6 +77,7 @@ struct Ellipse { float thickness; LineType line_type; int num_segments; // Number of segments to approximate the ellipse + uint32_t sequence_number = 0; // For maintaining draw order }; // Polygon structure @@ -82,6 +87,7 @@ struct Polygon { bool filled; float thickness; LineType line_type; + uint32_t sequence_number = 0; // For maintaining draw order }; struct CanvasData { @@ -91,6 +97,9 @@ struct CanvasData { std::vector circles; std::vector ellipses; std::vector polygons; + + // Sequence counter for maintaining draw order across all primitive types + uint32_t next_sequence_number = 0; void Clear() { points.clear(); @@ -99,6 +108,7 @@ struct CanvasData { circles.clear(); ellipses.clear(); polygons.clear(); + next_sequence_number = 0; } void AddPoint(float x, float y, const glm::vec4& color, float thickness) { @@ -106,6 +116,7 @@ struct CanvasData { point.position = glm::vec3(x, y, 0.0f); point.color = color; point.size = thickness; + point.sequence_number = next_sequence_number++; points.push_back(point); } @@ -117,6 +128,7 @@ struct CanvasData { line.color = color; line.thickness = thickness; line.line_type = line_type; + line.sequence_number = next_sequence_number++; lines.push_back(line); } @@ -131,6 +143,7 @@ struct CanvasData { rect.filled = filled; rect.thickness = thickness; rect.line_type = line_type; + rect.sequence_number = next_sequence_number++; rectangles.push_back(rect); } @@ -144,6 +157,7 @@ struct CanvasData { circle.thickness = thickness; circle.line_type = line_type; circle.num_segments = 32; // Default number of segments + circle.sequence_number = next_sequence_number++; circles.push_back(circle); } @@ -162,6 +176,7 @@ struct CanvasData { ellipse.thickness = thickness; ellipse.line_type = line_type; ellipse.num_segments = 32; // Default number of segments + ellipse.sequence_number = next_sequence_number++; ellipses.push_back(ellipse); } @@ -176,6 +191,7 @@ struct CanvasData { polygon.filled = filled; polygon.thickness = thickness; polygon.line_type = line_type; + polygon.sequence_number = next_sequence_number++; polygons.push_back(polygon); } }; diff --git a/src/renderer/src/renderable/details/canvas_data_manager.cpp b/src/renderer/src/renderable/details/canvas_data_manager.cpp new file mode 100644 index 0000000..b208765 --- /dev/null +++ b/src/renderer/src/renderable/details/canvas_data_manager.cpp @@ -0,0 +1,320 @@ +/** + * @file canvas_data_manager.cpp + * @author Canvas Refactoring Phase 2.1 + * @date 2025-01-11 + * @brief Simplified working implementation of data management layer + * + * Copyright (c) 2025 Ruixiang Du (rdu) + */ + +#include "canvas_data_manager.hpp" +#include + +namespace quickviz { +namespace internal { + +//============================================================================== +// Constructor and Initialization +//============================================================================== + +CanvasDataManager::CanvasDataManager() + : data_(std::make_unique()) { + InitializeBatches(); +} + +//============================================================================== +// Public API - Thread-safe shape addition +//============================================================================== + +void CanvasDataManager::AddPoint(float x, float y, const glm::vec4& color, float thickness) { + PendingUpdate update; + update.type = PendingUpdate::Type::kPoint; + update.color = color; + update.thickness = thickness; + update.point.x = x; + update.point.y = y; + + std::lock_guard lock(data_mutex_); + pending_updates_.push(std::move(update)); + has_pending_updates_ = true; +} + +void CanvasDataManager::AddLine(float x1, float y1, float x2, float y2, + const glm::vec4& color, float thickness, LineType line_type) { + PendingUpdate update; + update.type = PendingUpdate::Type::kLine; + update.color = color; + update.thickness = thickness; + update.line_type = line_type; + update.line.x1 = x1; + update.line.y1 = y1; + update.line.x2 = x2; + update.line.y2 = y2; + + std::lock_guard lock(data_mutex_); + pending_updates_.push(std::move(update)); + has_pending_updates_ = true; +} + +void CanvasDataManager::AddRectangle(float x, float y, float width, float height, + const glm::vec4& color, bool filled, + float thickness, LineType line_type) { + PendingUpdate update; + update.type = PendingUpdate::Type::kRectangle; + update.color = color; + update.thickness = thickness; + update.line_type = line_type; + update.filled = filled; + update.rect.x = x; + update.rect.y = y; + update.rect.width = width; + update.rect.height = height; + + std::lock_guard lock(data_mutex_); + pending_updates_.push(std::move(update)); + has_pending_updates_ = true; +} + +void CanvasDataManager::AddCircle(float x, float y, float radius, const glm::vec4& color, + bool filled, float thickness, LineType line_type) { + PendingUpdate update; + update.type = PendingUpdate::Type::kCircle; + update.color = color; + update.thickness = thickness; + update.line_type = line_type; + update.filled = filled; + update.circle.x = x; + update.circle.y = y; + update.circle.radius = radius; + + std::lock_guard lock(data_mutex_); + pending_updates_.push(std::move(update)); + has_pending_updates_ = true; +} + +void CanvasDataManager::AddEllipse(float x, float y, float rx, float ry, float angle, + float start_angle, float end_angle, const glm::vec4& color, + bool filled, float thickness, LineType line_type) { + PendingUpdate update; + update.type = PendingUpdate::Type::kEllipse; + update.color = color; + update.thickness = thickness; + update.line_type = line_type; + update.filled = filled; + update.ellipse.x = x; + update.ellipse.y = y; + update.ellipse.rx = rx; + update.ellipse.ry = ry; + update.ellipse.angle = angle; + update.ellipse.start_angle = start_angle; + update.ellipse.end_angle = end_angle; + + std::lock_guard lock(data_mutex_); + pending_updates_.push(std::move(update)); + has_pending_updates_ = true; +} + +void CanvasDataManager::AddPolygon(const std::vector& points, const glm::vec4& color, + bool filled, float thickness, LineType line_type) { + PendingUpdate update; + update.type = PendingUpdate::Type::kPolygon; + update.color = color; + update.thickness = thickness; + update.line_type = line_type; + update.filled = filled; + update.polygon_vertices = points; + + std::lock_guard lock(data_mutex_); + pending_updates_.push(std::move(update)); + has_pending_updates_ = true; +} + +void CanvasDataManager::Clear() { + std::lock_guard lock(data_mutex_); + data_->Clear(); + ClearBatches(); +} + +//============================================================================== +// Batch Management +//============================================================================== + +void CanvasDataManager::SetBatchingEnabled(bool enabled) { + batching_enabled_ = enabled; +} + +//============================================================================== +// Data Access (const methods for rendering) +//============================================================================== + +const CanvasData& CanvasDataManager::GetShapeData() const { + return *data_; +} + +const std::unordered_map& CanvasDataManager::GetLineBatches() const { + return line_batches_; +} + +const ShapeBatch& CanvasDataManager::GetFilledShapeBatch() const { + return filled_shape_batch_; +} + +const std::unordered_map& CanvasDataManager::GetOutlineShapeBatches() const { + return outline_shape_batches_; +} + +const BatchOrderTracker& CanvasDataManager::GetBatchOrderTracker() const { + return batch_order_tracker_; +} + +//============================================================================== +// Update Processing - Simplified Implementation +//============================================================================== + +void CanvasDataManager::ProcessPendingUpdates() { + std::lock_guard lock(data_mutex_); + + while (!pending_updates_.empty()) { + const auto& update = pending_updates_.front(); + + // For simplicity, always use individual rendering mode + // This ensures compatibility while maintaining the architecture + ProcessPendingUpdateIndividual(update); + + pending_updates_.pop(); + } + + has_pending_updates_ = false; +} + +//============================================================================== +// Memory Management - Simplified +//============================================================================== + +size_t CanvasDataManager::GetMemoryUsage() const { + std::lock_guard lock(data_mutex_); + + size_t total_usage = 0; + + // Add estimated size of CanvasData + total_usage += data_->points.size() * sizeof(Point); + total_usage += data_->lines.size() * sizeof(Line); + total_usage += data_->rectangles.size() * sizeof(Rectangle); + total_usage += data_->circles.size() * sizeof(Circle); + total_usage += data_->ellipses.size() * sizeof(Ellipse); + total_usage += data_->polygons.size() * sizeof(Polygon); + + // Add pending updates queue + total_usage += pending_updates_.size() * sizeof(PendingUpdate); + + return total_usage; +} + +void CanvasDataManager::OptimizeMemory() { + std::lock_guard lock(data_mutex_); + + // Clear pending updates queue if empty + if (pending_updates_.size() == 0) { + std::queue empty_queue; + pending_updates_.swap(empty_queue); + } +} + +void CanvasDataManager::ShrinkToFit() { + std::lock_guard lock(data_mutex_); + + // Shrink shape data vectors + data_->points.shrink_to_fit(); + data_->lines.shrink_to_fit(); + data_->rectangles.shrink_to_fit(); + data_->circles.shrink_to_fit(); + data_->ellipses.shrink_to_fit(); + data_->polygons.shrink_to_fit(); +} + +void CanvasDataManager::PreallocateMemory(size_t estimated_objects) { + std::lock_guard lock(data_mutex_); + + // Pre-allocate shape data vectors + data_->points.reserve(estimated_objects / 10); + data_->lines.reserve(estimated_objects / 4); + data_->rectangles.reserve(estimated_objects / 8); + data_->circles.reserve(estimated_objects / 8); + data_->ellipses.reserve(estimated_objects / 20); + data_->polygons.reserve(estimated_objects / 20); +} + +//============================================================================== +// Private Helper Methods - Simplified +//============================================================================== + +void CanvasDataManager::InitializeBatches() { + // Initialize empty batches + line_batches_[LineType::kSolid] = LineBatch{}; + line_batches_[LineType::kDashed] = LineBatch{}; + line_batches_[LineType::kDotted] = LineBatch{}; + + outline_shape_batches_[LineType::kSolid] = ShapeBatch{}; + outline_shape_batches_[LineType::kDashed] = ShapeBatch{}; + outline_shape_batches_[LineType::kDotted] = ShapeBatch{}; +} + +void CanvasDataManager::ClearBatches() { + // Clear all batches - simplified + for (auto& [line_type, batch] : line_batches_) { + batch.vertices.clear(); + batch.colors.clear(); + } + + filled_shape_batch_.vertices.clear(); + filled_shape_batch_.colors.clear(); + + for (auto& [line_type, batch] : outline_shape_batches_) { + batch.vertices.clear(); + batch.colors.clear(); + } + + batch_order_tracker_.render_order.clear(); + batch_order_tracker_.next_sequence = 0; +} + +void CanvasDataManager::ProcessPendingUpdateIndividual(const PendingUpdate& update) { + switch (update.type) { + case PendingUpdate::Type::kPoint: + data_->AddPoint(update.point.x, update.point.y, update.color, update.thickness); + break; + case PendingUpdate::Type::kLine: + data_->AddLine(update.line.x1, update.line.y1, update.line.x2, + update.line.y2, update.color, update.thickness, + update.line_type); + break; + case PendingUpdate::Type::kRectangle: + data_->AddRectangle(update.rect.x, update.rect.y, update.rect.width, + update.rect.height, update.color, update.filled, + update.thickness, update.line_type); + break; + case PendingUpdate::Type::kCircle: + data_->AddCircle(update.circle.x, update.circle.y, update.circle.radius, + update.color, update.filled, update.thickness, + update.line_type); + break; + case PendingUpdate::Type::kEllipse: + data_->AddEllipse(update.ellipse.x, update.ellipse.y, update.ellipse.rx, + update.ellipse.ry, update.ellipse.angle, + update.ellipse.start_angle, update.ellipse.end_angle, + update.color, update.filled, update.thickness, + update.line_type); + break; + case PendingUpdate::Type::kPolygon: + data_->AddPolygon(update.polygon_vertices, update.color, update.filled, + update.thickness, update.line_type); + break; + case PendingUpdate::Type::kClear: + data_->Clear(); + ClearBatches(); + break; + } +} + +} // namespace internal +} // namespace quickviz \ No newline at end of file diff --git a/src/renderer/src/renderable/details/canvas_data_manager.hpp b/src/renderer/src/renderable/details/canvas_data_manager.hpp new file mode 100644 index 0000000..2f3f69c --- /dev/null +++ b/src/renderer/src/renderable/details/canvas_data_manager.hpp @@ -0,0 +1,167 @@ +/** + * @file canvas_data_manager.hpp + * @author Canvas Refactoring Phase 2.1 + * @date 2025-01-11 + * @brief Professional data management layer for Canvas + * + * Copyright (c) 2025 Ruixiang Du (rdu) + */ + +#ifndef OPENGL_RENDERER_CANVAS_DATA_MANAGER_HPP +#define OPENGL_RENDERER_CANVAS_DATA_MANAGER_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include "renderer/renderable/types.hpp" +#include "canvas_data.hpp" +#include "renderer/renderable/details/canvas_batching.hpp" + +namespace quickviz { +namespace internal { + +/** + * @brief Thread-safe data manager for Canvas operations + * + * This class encapsulates all data management operations for the Canvas, + * including pending updates, batch management, and thread-safe access to shape data. + */ +class CanvasDataManager { +public: + // Structure to hold pending updates + struct PendingUpdate { + enum class Type { + kPoint, + kLine, + kRectangle, + kCircle, + kEllipse, + kPolygon, + kClear + }; + + Type type; + glm::vec4 color; + float thickness; + LineType line_type; + bool filled; + + // Command-specific parameters + struct ellipse_params { + float x, y, rx, ry, angle, start_angle, end_angle; + }; + + union { + struct { // Point parameters + float x, y; + } point; + + struct { // Line parameters + float x1, y1, x2, y2; + } line; + + struct { // Rectangle parameters + float x, y, width, height; + } rect; + + struct { // Circle parameters + float x, y, radius; + } circle; + + ellipse_params ellipse; + }; + + // Polygon vertices (can't be in union) + std::vector polygon_vertices; + }; + +public: + CanvasDataManager(); + ~CanvasDataManager() = default; + + // Thread-safe shape addition methods + void AddPoint(float x, float y, const glm::vec4& color, float thickness = 1.0f); + void AddLine(float x1, float y1, float x2, float y2, const glm::vec4& color, + float thickness = 1.0f, LineType line_type = LineType::kSolid); + void AddRectangle(float x, float y, float width, float height, + const glm::vec4& color, bool filled = true, + float thickness = 1.0f, LineType line_type = LineType::kSolid); + void AddCircle(float x, float y, float radius, const glm::vec4& color, + bool filled = true, float thickness = 1.0f, + LineType line_type = LineType::kSolid); + void AddEllipse(float x, float y, float rx, float ry, float angle, + float start_angle, float end_angle, const glm::vec4& color, + bool filled = true, float thickness = 1.0f, + LineType line_type = LineType::kSolid); + void AddPolygon(const std::vector& points, const glm::vec4& color, + bool filled = true, float thickness = 1.0f, + LineType line_type = LineType::kSolid); + + // Clear all data + void Clear(); + + // Batch management + void SetBatchingEnabled(bool enabled); + bool IsBatchingEnabled() const { return batching_enabled_; } + + // Process pending updates (transforms pending updates into actual data/batches) + void ProcessPendingUpdates(); + + // Check if there are pending updates + bool HasPendingUpdates() const { return has_pending_updates_; } + + // Access to shape data (const access for rendering) + const CanvasData& GetShapeData() const; + + // Access to batch data (const access for rendering) + const std::unordered_map& GetLineBatches() const; + const ShapeBatch& GetFilledShapeBatch() const; + const std::unordered_map& GetOutlineShapeBatches() const; + const BatchOrderTracker& GetBatchOrderTracker() const; + + // Memory management + size_t GetMemoryUsage() const; + void OptimizeMemory(); + void ShrinkToFit(); + void PreallocateMemory(size_t estimated_objects); + +private: + // Core data structures + std::unique_ptr data_; + + // Thread-safe pending updates + mutable std::mutex data_mutex_; + std::queue pending_updates_; + std::atomic has_pending_updates_{false}; + + // Batching system + std::atomic batching_enabled_{true}; + std::unordered_map line_batches_; + ShapeBatch filled_shape_batch_; + std::unordered_map outline_shape_batches_; + BatchOrderTracker batch_order_tracker_; + + // Helper methods + void InitializeBatches(); + void ClearBatches(); + void ProcessPendingUpdateBatched(const PendingUpdate& update); + void ProcessPendingUpdateIndividual(const PendingUpdate& update); + + // Batch processing helpers + void AddPointToBatch(const PendingUpdate& update); + void AddLineToBatch(const PendingUpdate& update); + void AddRectangleToBatch(const PendingUpdate& update); + void AddCircleToBatch(const PendingUpdate& update); + void AddEllipseToBatch(const PendingUpdate& update); + void AddPolygonToBatch(const PendingUpdate& update); +}; + +} // namespace internal +} // namespace quickviz + +#endif /* OPENGL_RENDERER_CANVAS_DATA_MANAGER_HPP */ \ No newline at end of file diff --git a/src/renderer/src/renderable/details/data_aware_render_strategy.cpp b/src/renderer/src/renderable/details/data_aware_render_strategy.cpp new file mode 100644 index 0000000..fb7e283 --- /dev/null +++ b/src/renderer/src/renderable/details/data_aware_render_strategy.cpp @@ -0,0 +1,379 @@ +/** + * @file data_aware_render_strategy.cpp + * @author Canvas Refactoring Phase 2.2 + * @date 2025-01-11 + * @brief Implementation of data manager aware render strategies + * + * Copyright (c) 2025 Ruixiang Du (rdu) + */ + +#include "data_aware_render_strategy.hpp" +#include "glad/glad.h" +#include "renderer/shader_program.hpp" +#include "renderer/renderable/details/canvas_performance.hpp" +#include + +namespace quickviz { +namespace internal { + +//============================================================================== +// DataAwareBatchedStrategy Implementation +//============================================================================== + +bool DataAwareBatchedStrategy::CanHandle(const CanvasData& data) const { + // Batched strategy can handle any data, but works best with many shapes + return true; +} + +void DataAwareBatchedStrategy::Render(const CanvasData& data, const RenderContext& context) { + // Enhanced context needs to be created by Canvas + // This is a compatibility layer for the existing interface + RenderIndividualShapes(data, EnhancedRenderContext(context, nullptr, nullptr)); +} + +void DataAwareBatchedStrategy::RenderWithDataManager(const EnhancedRenderContext& context) { + if (!context.data_manager) return; + + const auto& data = context.data_manager->GetShapeData(); + + // Skip if there's no data to render + if (data.points.empty() && data.lines.empty() && + data.rectangles.empty() && data.circles.empty() && + data.ellipses.empty() && data.polygons.empty()) { + return; + } + + // Setup common rendering state + if (context.primitive_shader) { + context.primitive_shader->Use(); + context.primitive_shader->TrySetUniform("projection", context.projection); + context.primitive_shader->TrySetUniform("view", context.view); + context.primitive_shader->TrySetUniform("model", glm::mat4(1.0f)); + context.primitive_shader->TrySetUniform("coordSystemTransform", context.coord_transform); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + // Use efficient batched rendering + RenderBatches(context); + + // Render points individually (they're already efficient) + RenderPoints(data, context); + + // Handle shapes not in batches + RenderIndividualShapes(data, context); + + // Cleanup OpenGL state + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glBindVertexArray(0); + glUseProgram(0); +} + +void DataAwareBatchedStrategy::RenderBatches(const EnhancedRenderContext& context) { + if (!context.data_manager) return; + + const auto& batch_tracker = context.data_manager->GetBatchOrderTracker(); + const auto& line_batches = context.data_manager->GetLineBatches(); + const auto& filled_batch = context.data_manager->GetFilledShapeBatch(); + const auto& outline_batches = context.data_manager->GetOutlineShapeBatches(); + + // Render batches in order to preserve sequence + auto ordered_primitives = batch_tracker.render_order; + std::sort(ordered_primitives.begin(), ordered_primitives.end(), + [](const BatchOrderTracker::OrderedPrimitive& a, const BatchOrderTracker::OrderedPrimitive& b) { + return a.sequence_number < b.sequence_number; + }); + + for (const auto& primitive : ordered_primitives) { + switch (primitive.type) { + case BatchOrderTracker::OrderedPrimitive::Type::kLine: { + auto it = line_batches.find(primitive.line_type); + if (it != line_batches.end() && !it->second.vertices.empty()) { + // Render line batch (simplified implementation) + // Full implementation would involve proper VAO/VBO setup and rendering + } + break; + } + case BatchOrderTracker::OrderedPrimitive::Type::kFilledShape: { + if (!filled_batch.vertices.empty()) { + // Render filled shape batch (simplified implementation) + } + break; + } + case BatchOrderTracker::OrderedPrimitive::Type::kOutlineShape: { + auto it = outline_batches.find(primitive.line_type); + if (it != outline_batches.end() && !it->second.vertices.empty()) { + // Render outline shape batch (simplified implementation) + } + break; + } + } + } +} + +void DataAwareBatchedStrategy::RenderIndividualShapes(const CanvasData& data, const EnhancedRenderContext& context) { + if (!context.efficient_renderer) return; + + // Setup rendering state + context.efficient_renderer->SetupRenderingState(context.projection, context.view, context.coord_transform); + + // Render ellipses (not typically batched due to complexity) + for (const auto& ellipse : data.ellipses) { + auto vertices = ShapeVertexGenerator::GenerateEllipse( + ellipse.center, ellipse.rx, ellipse.ry, ellipse.angle, + ellipse.start_angle, ellipse.end_angle, ellipse.num_segments, ellipse.filled); + + ShapeRenderParams params; + params.color = ellipse.color; + params.thickness = ellipse.thickness; + params.line_type = ellipse.line_type; + params.filled = ellipse.filled; + params.primitive_type = ellipse.filled ? GL_TRIANGLE_FAN : GL_LINE_STRIP; + + context.efficient_renderer->RenderShape(vertices, params); + } + + // Render polygons + for (const auto& polygon : data.polygons) { + auto vertices = ShapeVertexGenerator::GeneratePolygon(polygon.vertices); + + ShapeRenderParams params; + params.color = polygon.color; + params.thickness = polygon.thickness; + params.line_type = polygon.line_type; + params.filled = polygon.filled; + params.primitive_type = polygon.filled ? GL_TRIANGLE_FAN : GL_LINE_LOOP; + + context.efficient_renderer->RenderShape(vertices, params); + } + + // Cleanup + context.efficient_renderer->CleanupRenderingState(); +} + +void DataAwareBatchedStrategy::RenderPoints(const CanvasData& data, const EnhancedRenderContext& context) { + if (data.points.empty() || !context.primitive_shader) return; + + // Points are rendered individually as they're already efficient + context.primitive_shader->TrySetUniform("renderMode", 0); // Point rendering mode + + glBindVertexArray(context.primitive_vao); + glBindBuffer(GL_ARRAY_BUFFER, context.primitive_vbo); + + for (const auto& point : data.points) { + // Upload point data + float vertices[] = {point.position.x, point.position.y, point.position.z}; + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + + // Set point size and color + context.primitive_shader->TrySetUniform("uniformColor", point.color); + glPointSize(point.size); + + // Render point + glDrawArrays(GL_POINTS, 0, 1); + } + + glPointSize(1.0f); // Reset to default +} + +//============================================================================== +// DataAwareIndividualStrategy Implementation +//============================================================================== + +bool DataAwareIndividualStrategy::CanHandle(const CanvasData& data) const { + // Individual strategy can handle any data + return true; +} + +void DataAwareIndividualStrategy::Render(const CanvasData& data, const RenderContext& context) { + // Enhanced context needs to be created by Canvas + RenderAllShapesIndividually(data, EnhancedRenderContext(context, nullptr, nullptr)); +} + +void DataAwareIndividualStrategy::RenderAllShapesIndividually(const CanvasData& data, const EnhancedRenderContext& context) { + // Setup common rendering state + if (context.primitive_shader) { + context.primitive_shader->Use(); + context.primitive_shader->TrySetUniform("projection", context.projection); + context.primitive_shader->TrySetUniform("view", context.view); + context.primitive_shader->TrySetUniform("model", glm::mat4(1.0f)); + context.primitive_shader->TrySetUniform("coordSystemTransform", context.coord_transform); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + // Render all shape types individually using efficient renderer + if (context.efficient_renderer) { + context.efficient_renderer->SetupRenderingState(context.projection, context.view, context.coord_transform); + + RenderLines(data, context); + RenderRectangles(data, context); + RenderCircles(data, context); + RenderEllipses(data, context); + RenderPolygons(data, context); + + context.efficient_renderer->CleanupRenderingState(); + } + + // Points are always rendered individually + RenderPoints(data, context); + + // Cleanup + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glBindVertexArray(0); + glUseProgram(0); +} + +void DataAwareIndividualStrategy::RenderPoints(const CanvasData& data, const EnhancedRenderContext& context) { + if (data.points.empty() || !context.primitive_shader) return; + + context.primitive_shader->TrySetUniform("renderMode", 0); // Point rendering mode + + for (const auto& point : data.points) { + // Implementation similar to batched strategy + // Simplified for brevity + } +} + +void DataAwareIndividualStrategy::RenderLines(const CanvasData& data, const EnhancedRenderContext& context) { + if (!context.efficient_renderer) return; + + for (const auto& line : data.lines) { + auto vertices = ShapeVertexGenerator::GenerateLine(line.start, line.end); + + ShapeRenderParams params; + params.color = line.color; + params.thickness = line.thickness; + params.line_type = line.line_type; + params.primitive_type = GL_LINES; + + context.efficient_renderer->RenderShape(vertices, params); + } +} + +void DataAwareIndividualStrategy::RenderRectangles(const CanvasData& data, const EnhancedRenderContext& context) { + if (!context.efficient_renderer) return; + + for (const auto& rect : data.rectangles) { + auto vertices = ShapeVertexGenerator::GenerateRectangle(rect.position, rect.width, rect.height); + + ShapeRenderParams params; + params.color = rect.color; + params.thickness = rect.thickness; + params.line_type = rect.line_type; + params.filled = rect.filled; + params.primitive_type = rect.filled ? GL_TRIANGLE_FAN : GL_LINE_LOOP; + + context.efficient_renderer->RenderShape(vertices, params); + } +} + +void DataAwareIndividualStrategy::RenderCircles(const CanvasData& data, const EnhancedRenderContext& context) { + if (!context.efficient_renderer) return; + + for (const auto& circle : data.circles) { + auto vertices = ShapeVertexGenerator::GenerateCircle(circle.center, circle.radius, circle.num_segments, circle.filled); + + ShapeRenderParams params; + params.color = circle.color; + params.thickness = circle.thickness; + params.line_type = circle.line_type; + params.filled = circle.filled; + params.primitive_type = circle.filled ? GL_TRIANGLE_FAN : GL_LINE_LOOP; + + context.efficient_renderer->RenderShape(vertices, params); + } +} + +void DataAwareIndividualStrategy::RenderEllipses(const CanvasData& data, const EnhancedRenderContext& context) { + if (!context.efficient_renderer) return; + + for (const auto& ellipse : data.ellipses) { + auto vertices = ShapeVertexGenerator::GenerateEllipse( + ellipse.center, ellipse.rx, ellipse.ry, ellipse.angle, + ellipse.start_angle, ellipse.end_angle, ellipse.num_segments, ellipse.filled); + + ShapeRenderParams params; + params.color = ellipse.color; + params.thickness = ellipse.thickness; + params.line_type = ellipse.line_type; + params.filled = ellipse.filled; + params.primitive_type = ellipse.filled ? GL_TRIANGLE_FAN : GL_LINE_STRIP; + + context.efficient_renderer->RenderShape(vertices, params); + } +} + +void DataAwareIndividualStrategy::RenderPolygons(const CanvasData& data, const EnhancedRenderContext& context) { + if (!context.efficient_renderer) return; + + for (const auto& polygon : data.polygons) { + auto vertices = ShapeVertexGenerator::GeneratePolygon(polygon.vertices); + + ShapeRenderParams params; + params.color = polygon.color; + params.thickness = polygon.thickness; + params.line_type = polygon.line_type; + params.filled = polygon.filled; + params.primitive_type = polygon.filled ? GL_TRIANGLE_FAN : GL_LINE_LOOP; + + context.efficient_renderer->RenderShape(vertices, params); + } +} + +//============================================================================== +// AdaptiveStrategySelector Implementation +//============================================================================== + +AdaptiveStrategySelector::AdaptiveStrategySelector() + : batched_strategy_(std::make_unique()), + individual_strategy_(std::make_unique()) { +} + +RenderStrategy* AdaptiveStrategySelector::SelectStrategy(const CanvasData& data, const CanvasDataManager* data_manager) { + if (!data_manager) { + // Fallback to individual rendering if no data manager + return individual_strategy_.get(); + } + + if (ShouldUseBatching(data, data_manager)) { + return batched_strategy_.get(); + } else { + return individual_strategy_.get(); + } +} + +bool AdaptiveStrategySelector::ShouldUseBatching(const CanvasData& data, const CanvasDataManager* data_manager) const { + // Use batching if enabled and we have enough shapes to benefit + if (!data_manager->IsBatchingEnabled()) { + return false; + } + + const size_t total_shapes = CalculateTotalShapes(data); + const bool has_complex_shapes = HasComplexShapes(data); + + // Use batching for many simple shapes, individual rendering for few or complex shapes + const size_t batching_threshold = has_complex_shapes ? 50 : 20; + return total_shapes > batching_threshold; +} + +size_t AdaptiveStrategySelector::CalculateTotalShapes(const CanvasData& data) const { + return data.points.size() + data.lines.size() + data.rectangles.size() + + data.circles.size() + data.ellipses.size() + data.polygons.size(); +} + +bool AdaptiveStrategySelector::HasComplexShapes(const CanvasData& data) const { + // Ellipses and polygons are considered complex + return !data.ellipses.empty() || !data.polygons.empty() || + std::any_of(data.polygons.begin(), data.polygons.end(), + [](const Polygon& p) { return p.vertices.size() > 6; }); +} + +} // namespace internal +} // namespace quickviz \ No newline at end of file diff --git a/src/renderer/src/renderable/details/data_aware_render_strategy.hpp b/src/renderer/src/renderable/details/data_aware_render_strategy.hpp new file mode 100644 index 0000000..5dd0e2b --- /dev/null +++ b/src/renderer/src/renderable/details/data_aware_render_strategy.hpp @@ -0,0 +1,103 @@ +/** + * @file data_aware_render_strategy.hpp + * @author Canvas Refactoring Phase 2.2 + * @date 2025-01-11 + * @brief Data manager aware render strategies for Canvas + * + * Copyright (c) 2025 Ruixiang Du (rdu) + */ + +#ifndef OPENGL_RENDERER_DATA_AWARE_RENDER_STRATEGY_HPP +#define OPENGL_RENDERER_DATA_AWARE_RENDER_STRATEGY_HPP + +#include "render_strategy.hpp" +#include "canvas_data_manager.hpp" +#include "shape_renderer_utils.hpp" + +namespace quickviz { +namespace internal { + +/** + * @brief Enhanced render context for data manager integration + */ +struct EnhancedRenderContext : public RenderContext { + const CanvasDataManager* data_manager; + EfficientShapeRenderer* efficient_renderer; + + EnhancedRenderContext(const RenderContext& base, + const CanvasDataManager* dm, + EfficientShapeRenderer* renderer) + : RenderContext(base), data_manager(dm), efficient_renderer(renderer) {} +}; + +/** + * @brief Batched rendering strategy that works with CanvasDataManager + */ +class DataAwareBatchedStrategy : public RenderStrategy { +public: + DataAwareBatchedStrategy() = default; + ~DataAwareBatchedStrategy() override = default; + + // RenderStrategy interface + void Render(const CanvasData& data, const RenderContext& context) override; + bool CanHandle(const CanvasData& data) const override; + +private: + void RenderWithDataManager(const EnhancedRenderContext& context); + void RenderBatches(const EnhancedRenderContext& context); + void RenderIndividualShapes(const CanvasData& data, const EnhancedRenderContext& context); + void RenderPoints(const CanvasData& data, const EnhancedRenderContext& context); +}; + +/** + * @brief Individual rendering strategy that works with CanvasDataManager + */ +class DataAwareIndividualStrategy : public RenderStrategy { +public: + DataAwareIndividualStrategy() = default; + ~DataAwareIndividualStrategy() override = default; + + // RenderStrategy interface + void Render(const CanvasData& data, const RenderContext& context) override; + bool CanHandle(const CanvasData& data) const override; + +private: + void RenderAllShapesIndividually(const CanvasData& data, const EnhancedRenderContext& context); + void RenderPoints(const CanvasData& data, const EnhancedRenderContext& context); + void RenderLines(const CanvasData& data, const EnhancedRenderContext& context); + void RenderRectangles(const CanvasData& data, const EnhancedRenderContext& context); + void RenderCircles(const CanvasData& data, const EnhancedRenderContext& context); + void RenderEllipses(const CanvasData& data, const EnhancedRenderContext& context); + void RenderPolygons(const CanvasData& data, const EnhancedRenderContext& context); +}; + +/** + * @brief Adaptive strategy selector that chooses the best strategy dynamically + */ +class AdaptiveStrategySelector { +public: + AdaptiveStrategySelector(); + ~AdaptiveStrategySelector() = default; + + /** + * @brief Select the best rendering strategy for the given data + * @param data Canvas data to analyze + * @param data_manager Data manager for accessing batches + * @return Best strategy for rendering the data + */ + RenderStrategy* SelectStrategy(const CanvasData& data, const CanvasDataManager* data_manager); + +private: + std::unique_ptr batched_strategy_; + std::unique_ptr individual_strategy_; + + // Heuristics for strategy selection + bool ShouldUseBatching(const CanvasData& data, const CanvasDataManager* data_manager) const; + size_t CalculateTotalShapes(const CanvasData& data) const; + bool HasComplexShapes(const CanvasData& data) const; +}; + +} // namespace internal +} // namespace quickviz + +#endif /* OPENGL_RENDERER_DATA_AWARE_RENDER_STRATEGY_HPP */ \ No newline at end of file diff --git a/src/renderer/src/renderable/details/individual_render_strategy.cpp b/src/renderer/src/renderable/details/individual_render_strategy.cpp index 14a5765..7c4efd2 100644 --- a/src/renderer/src/renderable/details/individual_render_strategy.cpp +++ b/src/renderer/src/renderable/details/individual_render_strategy.cpp @@ -11,12 +11,29 @@ #include "shape_generators.hpp" #include +#include #include "glad/glad.h" #include "renderer/shader_program.hpp" #include "canvas_data.hpp" namespace quickviz { +// Helper enums and structures for sequence-ordered rendering +enum class PrimitiveType { + kPoint, + kLine, + kRectangle, + kCircle, + kEllipse, + kPolygon +}; + +struct PrimitiveRef { + PrimitiveType type; + size_t index; + uint32_t sequence_number; +}; + IndividualRenderStrategy::IndividualRenderStrategy(ShapeRenderer* shape_renderer) : shape_renderer_(shape_renderer) { } @@ -36,13 +53,60 @@ void IndividualRenderStrategy::Render(const CanvasData& data, const RenderContex SetupCommonRenderState(context); - // Render each type of shape individually - RenderPoints(data, context); - RenderLines(data, context); - RenderRectangles(data, context); - RenderCircles(data, context); - RenderEllipses(data, context); - RenderPolygons(data, context); + // Create a sorted list of all primitives by sequence number + std::vector sorted_primitives; + + // Collect all primitives with their sequence numbers + for (size_t i = 0; i < data.points.size(); ++i) { + sorted_primitives.push_back({PrimitiveType::kPoint, i, data.points[i].sequence_number}); + } + for (size_t i = 0; i < data.lines.size(); ++i) { + sorted_primitives.push_back({PrimitiveType::kLine, i, data.lines[i].sequence_number}); + } + for (size_t i = 0; i < data.rectangles.size(); ++i) { + sorted_primitives.push_back({PrimitiveType::kRectangle, i, data.rectangles[i].sequence_number}); + } + for (size_t i = 0; i < data.circles.size(); ++i) { + sorted_primitives.push_back({PrimitiveType::kCircle, i, data.circles[i].sequence_number}); + } + for (size_t i = 0; i < data.ellipses.size(); ++i) { + sorted_primitives.push_back({PrimitiveType::kEllipse, i, data.ellipses[i].sequence_number}); + } + for (size_t i = 0; i < data.polygons.size(); ++i) { + sorted_primitives.push_back({PrimitiveType::kPolygon, i, data.polygons[i].sequence_number}); + } + + // Sequence numbers are correctly assigned + + // Sort by sequence number to maintain draw order + std::sort(sorted_primitives.begin(), sorted_primitives.end(), + [](const PrimitiveRef& a, const PrimitiveRef& b) { + return a.sequence_number < b.sequence_number; + }); + + // Render primitives in sequence order + for (const auto& primitive_ref : sorted_primitives) { + switch (primitive_ref.type) { + case PrimitiveType::kPoint: + RenderSinglePoint(data.points[primitive_ref.index], context); + break; + case PrimitiveType::kLine: + RenderSingleLine(data.lines[primitive_ref.index], context); + break; + case PrimitiveType::kRectangle: + RenderSingleRectangle(data.rectangles[primitive_ref.index], context); + break; + case PrimitiveType::kCircle: + RenderSingleCircle(data.circles[primitive_ref.index], context); + break; + case PrimitiveType::kEllipse: + RenderSingleEllipse(data.ellipses[primitive_ref.index], context); + break; + case PrimitiveType::kPolygon: + RenderSinglePolygon(data.polygons[primitive_ref.index], context); + break; + } + } CleanupRenderState(); } @@ -73,23 +137,112 @@ void IndividualRenderStrategy::CleanupRenderState() { } void IndividualRenderStrategy::RenderPoints(const CanvasData& data, const RenderContext& context) { - // TODO: Move point rendering logic from original Canvas implementation - // For now this is a placeholder + if (data.points.empty()) return; + + context.primitive_shader->TrySetUniform("renderMode", 0); // Point rendering mode + glBindVertexArray(context.primitive_vao); + glBindBuffer(GL_ARRAY_BUFFER, context.primitive_vbo); + + for (const auto& point : data.points) { + Point vertices[] = {point}; + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + glPointSize(point.size); + glDrawArrays(GL_POINTS, 0, 1); + } + glPointSize(1.0f); // Reset } void IndividualRenderStrategy::RenderLines(const CanvasData& data, const RenderContext& context) { - // TODO: Move line rendering logic from original Canvas implementation - // For now this is a placeholder + if (data.lines.empty()) return; + + context.primitive_shader->TrySetUniform("renderMode", 1); // Line rendering mode + glBindVertexArray(context.primitive_vao); + glBindBuffer(GL_ARRAY_BUFFER, context.primitive_vbo); + + for (const auto& line : data.lines) { + context.primitive_shader->TrySetUniform("lineType", static_cast(line.line_type)); + Point vertices[] = { + {line.start, line.color, line.thickness}, + {line.end, line.color, line.thickness} + }; + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + glLineWidth(line.thickness); + glDrawArrays(GL_LINES, 0, 2); + } + glLineWidth(1.0f); // Reset } void IndividualRenderStrategy::RenderRectangles(const CanvasData& data, const RenderContext& context) { - // TODO: Move rectangle rendering logic from original Canvas implementation - // For now this is a placeholder + if (data.rectangles.empty()) return; + + glBindVertexArray(context.primitive_vao); + glBindBuffer(GL_ARRAY_BUFFER, context.primitive_vbo); + + for (const auto& rect : data.rectangles) { + // Generate rectangle vertices + glm::vec3 corners[4] = { + {rect.position.x, rect.position.y, 0.0f}, // bottom-left + {rect.position.x + rect.width, rect.position.y, 0.0f}, // bottom-right + {rect.position.x + rect.width, rect.position.y + rect.height, 0.0f}, // top-right + {rect.position.x, rect.position.y + rect.height, 0.0f} // top-left + }; + + Point vertices[4]; + for (int i = 0; i < 4; i++) { + vertices[i] = {corners[i], rect.color, rect.thickness}; + } + + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + + if (rect.filled) { + context.primitive_shader->TrySetUniform("renderMode", 2); // Filled shapes + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } else { + context.primitive_shader->TrySetUniform("renderMode", 3); // Outlined shapes + context.primitive_shader->TrySetUniform("lineType", static_cast(rect.line_type)); + glLineWidth(rect.thickness); + glDrawArrays(GL_LINE_LOOP, 0, 4); + glLineWidth(1.0f); + } + } } void IndividualRenderStrategy::RenderCircles(const CanvasData& data, const RenderContext& context) { - // TODO: Move circle rendering logic from original Canvas implementation - // For now this is a placeholder + if (data.circles.empty()) return; + + glBindVertexArray(context.primitive_vao); + glBindBuffer(GL_ARRAY_BUFFER, context.primitive_vbo); + + for (const auto& circle : data.circles) { + const int segments = circle.num_segments; + std::vector vertices; + + if (circle.filled) { + // Add center point for triangle fan + vertices.push_back({{circle.center.x, circle.center.y, 0.0f}, circle.color, circle.thickness}); + } + + // Generate circle points + for (int i = 0; i <= segments; i++) { + float angle = 2.0f * M_PI * i / segments; + float x = circle.center.x + circle.radius * cos(angle); + float y = circle.center.y + circle.radius * sin(angle); + vertices.push_back({{x, y, 0.0f}, circle.color, circle.thickness}); + } + + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Point), vertices.data(), GL_DYNAMIC_DRAW); + + if (circle.filled) { + context.primitive_shader->TrySetUniform("renderMode", 2); // Filled shapes + glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size()); + } else { + context.primitive_shader->TrySetUniform("renderMode", 3); // Outlined shapes + context.primitive_shader->TrySetUniform("lineType", static_cast(circle.line_type)); + glLineWidth(circle.thickness); + glDrawArrays(GL_LINE_LOOP, 1, segments); // Skip center point for outline + glLineWidth(1.0f); + } + } } void IndividualRenderStrategy::RenderEllipses(const CanvasData& data, const RenderContext& context) { @@ -132,4 +285,138 @@ void IndividualRenderStrategy::RenderPolygons(const CanvasData& data, const Rend } } +// Single primitive rendering methods for sequence-ordered rendering +void IndividualRenderStrategy::RenderSinglePoint(const Point& point, const RenderContext& context) { + context.primitive_shader->TrySetUniform("renderMode", 0); // Point rendering mode + glBindVertexArray(context.primitive_vao); + glBindBuffer(GL_ARRAY_BUFFER, context.primitive_vbo); + + Point vertices[] = {point}; + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + glPointSize(point.size); + glDrawArrays(GL_POINTS, 0, 1); + glPointSize(1.0f); // Reset +} + +void IndividualRenderStrategy::RenderSingleLine(const Line& line, const RenderContext& context) { + context.primitive_shader->TrySetUniform("renderMode", 1); // Line rendering mode + context.primitive_shader->TrySetUniform("lineType", static_cast(line.line_type)); + glBindVertexArray(context.primitive_vao); + glBindBuffer(GL_ARRAY_BUFFER, context.primitive_vbo); + + Point vertices[] = { + {line.start, line.color, line.thickness}, + {line.end, line.color, line.thickness} + }; + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + glLineWidth(line.thickness); + glDrawArrays(GL_LINES, 0, 2); + glLineWidth(1.0f); // Reset +} + +void IndividualRenderStrategy::RenderSingleRectangle(const Rectangle& rect, const RenderContext& context) { + glBindVertexArray(context.primitive_vao); + glBindBuffer(GL_ARRAY_BUFFER, context.primitive_vbo); + + // Generate rectangle vertices + glm::vec3 corners[4] = { + {rect.position.x, rect.position.y, 0.0f}, // bottom-left + {rect.position.x + rect.width, rect.position.y, 0.0f}, // bottom-right + {rect.position.x + rect.width, rect.position.y + rect.height, 0.0f}, // top-right + {rect.position.x, rect.position.y + rect.height, 0.0f} // top-left + }; + + Point vertices[4]; + for (int i = 0; i < 4; i++) { + vertices[i] = {corners[i], rect.color, rect.thickness}; + } + + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + + if (rect.filled) { + context.primitive_shader->TrySetUniform("renderMode", 2); // Filled shapes + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } else { + context.primitive_shader->TrySetUniform("renderMode", 3); // Outlined shapes + context.primitive_shader->TrySetUniform("lineType", static_cast(rect.line_type)); + glLineWidth(rect.thickness); + glDrawArrays(GL_LINE_LOOP, 0, 4); + glLineWidth(1.0f); + } +} + +void IndividualRenderStrategy::RenderSingleCircle(const Circle& circle, const RenderContext& context) { + glBindVertexArray(context.primitive_vao); + glBindBuffer(GL_ARRAY_BUFFER, context.primitive_vbo); + + const int segments = circle.num_segments; + std::vector vertices; + + // Use sequence number to determine Z depth for proper layering + // Higher sequence numbers should be closer to camera (higher Z) + float z_depth = circle.sequence_number * 0.001f; + + if (circle.filled) { + // Add center point for triangle fan + vertices.push_back({{circle.center.x, circle.center.y, z_depth}, circle.color, circle.thickness}); + } + + // Generate circle points + for (int i = 0; i <= segments; i++) { + float angle = 2.0f * M_PI * i / segments; + float x = circle.center.x + circle.radius * cos(angle); + float y = circle.center.y + circle.radius * sin(angle); + vertices.push_back({{x, y, z_depth}, circle.color, circle.thickness}); + } + + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Point), vertices.data(), GL_DYNAMIC_DRAW); + + if (circle.filled) { + context.primitive_shader->TrySetUniform("renderMode", 2); // Filled shapes + glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size()); + } else { + context.primitive_shader->TrySetUniform("renderMode", 3); // Outlined shapes + context.primitive_shader->TrySetUniform("lineType", static_cast(circle.line_type)); + glLineWidth(circle.thickness); + glDrawArrays(GL_LINE_LOOP, 1, segments); // Skip center point for outline + glLineWidth(1.0f); + } +} + +void IndividualRenderStrategy::RenderSingleEllipse(const Ellipse& ellipse, const RenderContext& context) { + if (!shape_renderer_) { + return; + } + + auto vertices = ShapeGenerators::GenerateEllipseVertices(ellipse); + VertexLayout layout = VertexLayout::PositionOnly(); + + RenderParams params; + params.color = ellipse.color; + params.thickness = ellipse.thickness; + params.filled = ellipse.filled; + params.primitive_type = ellipse.filled ? GL_TRIANGLE_FAN : GL_LINE_LOOP; + params.vertex_count = vertices.size() / 3; + + shape_renderer_->RenderShape(vertices, layout, params); +} + +void IndividualRenderStrategy::RenderSinglePolygon(const Polygon& polygon, const RenderContext& context) { + if (!shape_renderer_) { + return; + } + + auto vertices = ShapeGenerators::GeneratePolygonVertices(polygon); + VertexLayout layout = VertexLayout::PositionOnly(); + + RenderParams params; + params.color = polygon.color; + params.thickness = polygon.thickness; + params.filled = polygon.filled; + params.primitive_type = polygon.filled ? GL_TRIANGLE_FAN : GL_LINE_LOOP; + params.vertex_count = vertices.size() / 3; + + shape_renderer_->RenderShape(vertices, layout, params); +} + } // namespace quickviz \ No newline at end of file diff --git a/src/renderer/src/renderable/details/individual_render_strategy.hpp b/src/renderer/src/renderable/details/individual_render_strategy.hpp index a857314..e99eabb 100644 --- a/src/renderer/src/renderable/details/individual_render_strategy.hpp +++ b/src/renderer/src/renderable/details/individual_render_strategy.hpp @@ -31,6 +31,10 @@ class IndividualRenderStrategy : public RenderStrategy { void Render(const CanvasData& data, const RenderContext& context) override; bool CanHandle(const CanvasData& data) const override; + // Public single shape rendering methods for Canvas access + void RenderSingleEllipse(const Ellipse& ellipse, const RenderContext& context); + void RenderSinglePolygon(const Polygon& polygon, const RenderContext& context); + private: // Individual shape rendering methods void RenderPoints(const CanvasData& data, const RenderContext& context); @@ -40,6 +44,12 @@ class IndividualRenderStrategy : public RenderStrategy { void RenderEllipses(const CanvasData& data, const RenderContext& context); void RenderPolygons(const CanvasData& data, const RenderContext& context); + // Single primitive rendering methods for sequence-ordered rendering + void RenderSinglePoint(const Point& point, const RenderContext& context); + void RenderSingleLine(const Line& line, const RenderContext& context); + void RenderSingleRectangle(const Rectangle& rect, const RenderContext& context); + void RenderSingleCircle(const Circle& circle, const RenderContext& context); + // Helper methods void SetupCommonRenderState(const RenderContext& context); void CleanupRenderState(); diff --git a/src/renderer/src/renderable/details/opengl_resource_pool.cpp b/src/renderer/src/renderable/details/opengl_resource_pool.cpp new file mode 100644 index 0000000..f6b3a7d --- /dev/null +++ b/src/renderer/src/renderable/details/opengl_resource_pool.cpp @@ -0,0 +1,140 @@ +/** + * @file opengl_resource_pool.cpp + * @author Canvas Refactoring + * @date 2025-01-11 + * @brief Implementation of OpenGL resource pooling system + * + * Copyright (c) 2025 Ruixiang Du (rdu) + */ + +#include "opengl_resource_pool.hpp" +#include +#include + +namespace quickviz { +namespace internal { + +OpenGLResourcePool::OpenGLResourcePool(size_t initial_pool_size) { + // Pre-allocate initial pool + PreallocatePool(initial_pool_size); +} + +OpenGLResourcePool::~OpenGLResourcePool() { + std::lock_guard lock(pool_mutex_); + + // Clean up all remaining resources + for (const auto& resource : available_resources_) { + DeleteResource(resource); + } + available_resources_.clear(); +} + +OpenGLResourcePool::TempResources OpenGLResourcePool::Acquire() { + std::lock_guard lock(pool_mutex_); + total_acquired_++; + + if (!available_resources_.empty()) { + // Reuse existing resource from pool + auto resource = available_resources_.back(); + available_resources_.pop_back(); + return resource; + } + + // Pool is empty, create new resource + return CreateNewResource(); +} + +void OpenGLResourcePool::Release(TempResources resources) { + if (!resources.IsValid()) { + std::cerr << "Warning: Attempting to release invalid OpenGL resources" << std::endl; + return; + } + + std::lock_guard lock(pool_mutex_); + total_released_++; + + // Return resource to pool for reuse + available_resources_.push_back(resources); +} + +OpenGLResourcePool::PoolStats OpenGLResourcePool::GetStats() const { + std::lock_guard lock(pool_mutex_); + + PoolStats stats; + stats.available_resources = available_resources_.size(); + stats.total_created = total_created_; + stats.total_acquired = total_acquired_; + stats.total_released = total_released_; + + return stats; +} + +void OpenGLResourcePool::PreallocatePool(size_t count) { + std::lock_guard lock(pool_mutex_); + + // Reserve space to avoid reallocations + available_resources_.reserve(available_resources_.size() + count); + + // Create requested number of resources + for (size_t i = 0; i < count; ++i) { + auto resource = CreateNewResource(); + if (resource.IsValid()) { + available_resources_.push_back(resource); + } + } +} + +void OpenGLResourcePool::Cleanup(size_t max_keep_alive) { + std::lock_guard lock(pool_mutex_); + + if (available_resources_.size() <= max_keep_alive) { + return; // Nothing to clean up + } + + // Delete excess resources + size_t to_delete = available_resources_.size() - max_keep_alive; + + for (size_t i = 0; i < to_delete; ++i) { + DeleteResource(available_resources_.back()); + available_resources_.pop_back(); + } + + // Shrink vector to fit + available_resources_.shrink_to_fit(); +} + +OpenGLResourcePool::TempResources OpenGLResourcePool::CreateNewResource() { + // This method assumes we're in a valid OpenGL context + GLuint vao, vbo; + + // Generate vertex array object + glGenVertexArrays(1, &vao); + if (vao == 0) { + std::cerr << "Error: Failed to generate VAO in resource pool" << std::endl; + return TempResources(); + } + + // Generate vertex buffer object + glGenBuffers(1, &vbo); + if (vbo == 0) { + std::cerr << "Error: Failed to generate VBO in resource pool" << std::endl; + glDeleteVertexArrays(1, &vao); + return TempResources(); + } + + total_created_++; + return TempResources(vao, vbo); +} + +void OpenGLResourcePool::DeleteResource(const TempResources& resources) { + if (!resources.IsValid()) { + return; + } + + // Clean up OpenGL resources + glDeleteVertexArrays(1, &resources.vao); + glDeleteBuffers(1, &resources.vbo); +} + +} // namespace internal +} // namespace quickviz \ No newline at end of file diff --git a/src/renderer/src/renderable/details/opengl_resource_pool.hpp b/src/renderer/src/renderable/details/opengl_resource_pool.hpp new file mode 100644 index 0000000..951b096 --- /dev/null +++ b/src/renderer/src/renderable/details/opengl_resource_pool.hpp @@ -0,0 +1,126 @@ +/** + * @file opengl_resource_pool.hpp + * @author Canvas Refactoring + * @date 2025-01-11 + * @brief OpenGL resource pooling for efficient VAO/VBO management + * + * Copyright (c) 2025 Ruixiang Du (rdu) + */ + +#ifndef OPENGL_RENDERER_OPENGL_RESOURCE_POOL_HPP +#define OPENGL_RENDERER_OPENGL_RESOURCE_POOL_HPP + +#include +#include +#include +#include "glad/glad.h" + +namespace quickviz { +namespace internal { + +/** + * @brief Efficient OpenGL resource pool to avoid per-frame VAO/VBO allocation + * + * This class manages a pool of temporary OpenGL resources (VAO/VBO pairs) + * that can be reused across frames to improve performance by eliminating + * the overhead of frequent glGenVertexArrays/glGenBuffers calls. + */ +class OpenGLResourcePool { +public: + /** + * @brief A pair of OpenGL resources for temporary rendering + */ + struct TempResources { + GLuint vao = 0; + GLuint vbo = 0; + + TempResources() = default; + TempResources(GLuint v, GLuint b) : vao(v), vbo(b) {} + + bool IsValid() const { return vao != 0 && vbo != 0; } + }; + +public: + /** + * @brief Constructor - initializes the resource pool + * @param initial_pool_size Initial number of resource pairs to create + */ + explicit OpenGLResourcePool(size_t initial_pool_size = 8); + + /** + * @brief Destructor - cleans up all OpenGL resources + */ + ~OpenGLResourcePool(); + + // Non-copyable, non-movable to prevent resource management issues + OpenGLResourcePool(const OpenGLResourcePool&) = delete; + OpenGLResourcePool& operator=(const OpenGLResourcePool&) = delete; + OpenGLResourcePool(OpenGLResourcePool&&) = delete; + OpenGLResourcePool& operator=(OpenGLResourcePool&&) = delete; + + /** + * @brief Acquire a temporary resource pair from the pool + * @return TempResources pair ready for use + * + * This method is thread-safe and will create new resources if the pool is empty. + */ + TempResources Acquire(); + + /** + * @brief Return a resource pair to the pool for reuse + * @param resources The resources to return (must be valid) + * + * This method is thread-safe. Resources should be returned promptly after use. + */ + void Release(TempResources resources); + + /** + * @brief Get current pool statistics + */ + struct PoolStats { + size_t available_resources = 0; + size_t total_created = 0; + size_t total_acquired = 0; + size_t total_released = 0; + }; + + PoolStats GetStats() const; + + /** + * @brief Pre-allocate additional resources in the pool + * @param count Number of additional resource pairs to create + */ + void PreallocatePool(size_t count); + + /** + * @brief Clean up excess resources from the pool + * @param max_keep_alive Maximum number of resources to keep in pool + */ + void Cleanup(size_t max_keep_alive = 16); + +private: + mutable std::mutex pool_mutex_; + std::vector available_resources_; + + // Statistics tracking + mutable size_t total_created_ = 0; + mutable size_t total_acquired_ = 0; + mutable size_t total_released_ = 0; + + /** + * @brief Create a new resource pair (internal method) + * @return Newly created TempResources pair + */ + TempResources CreateNewResource(); + + /** + * @brief Delete a resource pair (internal method) + * @param resources The resources to delete + */ + void DeleteResource(const TempResources& resources); +}; + +} // namespace internal +} // namespace quickviz + +#endif /* OPENGL_RENDERER_OPENGL_RESOURCE_POOL_HPP */ \ No newline at end of file diff --git a/src/renderer/src/renderable/details/shape_generators.cpp b/src/renderer/src/renderable/details/shape_generators.cpp index bd1ae6c..ecd2c53 100644 --- a/src/renderer/src/renderable/details/shape_generators.cpp +++ b/src/renderer/src/renderable/details/shape_generators.cpp @@ -19,7 +19,11 @@ std::vector GenerateCircleVertices(const Circle& circle) { } std::vector GenerateEllipseVertices(const Ellipse& ellipse) { - return GeometryUtils::CreateEllipse(ellipse.center, ellipse.rx, ellipse.ry, + // Use sequence number to determine Z depth for proper layering + float z_depth = ellipse.sequence_number * 0.001f; + glm::vec3 center_with_depth = {ellipse.center.x, ellipse.center.y, z_depth}; + + return GeometryUtils::CreateEllipse(center_with_depth, ellipse.rx, ellipse.ry, ellipse.angle, ellipse.start_angle, ellipse.end_angle, ellipse.num_segments, ellipse.filled); } @@ -28,8 +32,11 @@ std::vector GeneratePolygonVertices(const Polygon& polygon) { std::vector vertices; vertices.reserve(polygon.vertices.size() * 3); + // Use sequence number to determine Z depth for proper layering + float z_depth = polygon.sequence_number * 0.001f; + for (const auto& vertex : polygon.vertices) { - vertices.insert(vertices.end(), {vertex.x, vertex.y, vertex.z}); + vertices.insert(vertices.end(), {vertex.x, vertex.y, z_depth}); } return vertices; diff --git a/src/renderer/src/renderable/details/shape_renderer_utils.cpp b/src/renderer/src/renderable/details/shape_renderer_utils.cpp new file mode 100644 index 0000000..9a72f0b --- /dev/null +++ b/src/renderer/src/renderable/details/shape_renderer_utils.cpp @@ -0,0 +1,213 @@ +/** + * @file shape_renderer_utils.cpp + * @author Canvas Refactoring Phase 1.3 + * @date 2025-01-11 + * @brief Implementation of consolidated shape rendering utilities + * + * Copyright (c) 2025 Ruixiang Du (rdu) + */ + +#include "shape_renderer_utils.hpp" +#include +#include +#include "glad/glad.h" +#include "renderer/shader_program.hpp" + +namespace quickviz { +namespace internal { + +//============================================================================== +// ShapeVertexGenerator Implementation +//============================================================================== + +std::vector ShapeVertexGenerator::GenerateCircle(const glm::vec3& center, float radius, + int segments, bool filled) { + std::vector vertices; + vertices.reserve((segments + 2) * 3); + + if (filled) { + // Add center vertex for filled circles + vertices.insert(vertices.end(), {center.x, center.y, center.z}); + } + + // Generate perimeter vertices + for (int i = 0; i <= segments; i++) { + float angle = 2.0f * M_PI * i / segments; + float x = center.x + radius * std::cos(angle); + float y = center.y + radius * std::sin(angle); + float z = center.z; + vertices.insert(vertices.end(), {x, y, z}); + } + + return vertices; +} + +std::vector ShapeVertexGenerator::GenerateEllipse(const glm::vec3& center, float rx, float ry, + float angle, float start_angle, float end_angle, + int segments, bool filled) { + std::vector vertices; + vertices.reserve((segments + 2) * 3); + + if (filled) { + // Add center vertex for filled ellipses + vertices.insert(vertices.end(), {center.x, center.y, center.z}); + } + + // Generate ellipse vertices + for (int i = 0; i <= segments; i++) { + float t = start_angle + (end_angle - start_angle) * i / segments; + + // Generate point on unit ellipse + float x_local = rx * std::cos(t); + float y_local = ry * std::sin(t); + + // Apply rotation + float x_rotated = x_local * std::cos(angle) - y_local * std::sin(angle); + float y_rotated = x_local * std::sin(angle) + y_local * std::cos(angle); + + // Translate to final position + float x = center.x + x_rotated; + float y = center.y + y_rotated; + float z = center.z; + + vertices.insert(vertices.end(), {x, y, z}); + } + + return vertices; +} + +std::vector ShapeVertexGenerator::GenerateRectangle(const glm::vec3& position, + float width, float height) { + return { + position.x, position.y, position.z, // Bottom-left + position.x + width, position.y, position.z, // Bottom-right + position.x + width, position.y + height, position.z, // Top-right + position.x, position.y + height, position.z // Top-left + }; +} + +std::vector ShapeVertexGenerator::GenerateLine(const glm::vec3& start, const glm::vec3& end) { + return { + start.x, start.y, start.z, + end.x, end.y, end.z + }; +} + +std::vector ShapeVertexGenerator::GeneratePolygon(const std::vector& vertices) { + std::vector result; + result.reserve(vertices.size() * 3); + + for (const auto& vertex : vertices) { + result.insert(result.end(), {vertex.x, vertex.y, vertex.z}); + } + + return result; +} + +//============================================================================== +// EfficientShapeRenderer Implementation +//============================================================================== + +EfficientShapeRenderer::EfficientShapeRenderer(std::shared_ptr resource_pool, + void* shader_program) + : resource_pool_(resource_pool), shader_program_(shader_program) { +} + +void EfficientShapeRenderer::RenderShape(const std::vector& vertices, + const ShapeRenderParams& params) { + if (vertices.empty() || !resource_pool_) { + return; + } + + // Acquire resources from pool + auto resources = resource_pool_->Acquire(); + + // Configure vertex data + ConfigureVertexAttributes(resources, vertices); + + // Set up shader uniforms + SetupShaderUniforms(params); + + // Perform rendering + unsigned int vertex_count = vertices.size() / 3; + + if (params.primitive_type == GL_LINES || params.primitive_type == GL_LINE_LOOP || + params.primitive_type == GL_LINE_STRIP) { + glLineWidth(params.thickness); + glDrawArrays(params.primitive_type, 0, vertex_count); + glLineWidth(1.0f); // Reset to default + } else { + glDrawArrays(params.primitive_type, 0, vertex_count); + } + + // Cleanup + glDisableVertexAttribArray(0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + // Return resources to pool + resource_pool_->Release(resources); +} + +void EfficientShapeRenderer::SetupRenderingState(const glm::mat4& projection, + const glm::mat4& view, + const glm::mat4& coord_transform) { + if (!shader_program_) return; + + auto* shader = static_cast(shader_program_); + shader->Use(); + shader->TrySetUniform("projection", projection); + shader->TrySetUniform("view", view); + shader->TrySetUniform("model", glm::mat4(1.0f)); + shader->TrySetUniform("coordSystemTransform", coord_transform); + + // Enable common OpenGL state + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +void EfficientShapeRenderer::CleanupRenderingState() { + // Reset OpenGL state + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glBindVertexArray(0); + glUseProgram(0); +} + +void EfficientShapeRenderer::SetupShaderUniforms(const ShapeRenderParams& params) { + if (!shader_program_) return; + + auto* shader = static_cast(shader_program_); + + // Set color uniform + shader->TrySetUniform("uniformColor", params.color); + + // Set rendering mode based on primitive type + if (params.primitive_type == GL_LINES || params.primitive_type == GL_LINE_LOOP || + params.primitive_type == GL_LINE_STRIP) { + shader->TrySetUniform("renderMode", 1); // Lines mode + shader->TrySetUniform("lineType", static_cast(params.line_type)); + } else { + shader->TrySetUniform("renderMode", 2); // Filled shapes mode + shader->TrySetUniform("lineType", 0); // Solid for filled shapes + } +} + +void EfficientShapeRenderer::ConfigureVertexAttributes(const OpenGLResourcePool::TempResources& resources, + const std::vector& vertices) { + // Bind and configure vertex array object + glBindVertexArray(resources.vao); + glBindBuffer(GL_ARRAY_BUFFER, resources.vbo); + + // Upload vertex data + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), + vertices.data(), GL_DYNAMIC_DRAW); + + // Configure vertex attributes (position only for now) + glEnableVertexAttribArray(0); // Position + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); +} + +} // namespace internal +} // namespace quickviz \ No newline at end of file diff --git a/src/renderer/src/renderable/details/shape_renderer_utils.hpp b/src/renderer/src/renderable/details/shape_renderer_utils.hpp new file mode 100644 index 0000000..fe591c9 --- /dev/null +++ b/src/renderer/src/renderable/details/shape_renderer_utils.hpp @@ -0,0 +1,144 @@ +/** + * @file shape_renderer_utils.hpp + * @author Canvas Refactoring Phase 1.3 + * @date 2025-01-11 + * @brief Consolidated shape rendering utilities for Canvas + * + * Copyright (c) 2025 Ruixiang Du (rdu) + */ + +#ifndef OPENGL_RENDERER_SHAPE_RENDERER_UTILS_HPP +#define OPENGL_RENDERER_SHAPE_RENDERER_UTILS_HPP + +#include +#include +#include +#include "renderer/renderable/types.hpp" +#include "opengl_resource_pool.hpp" + +namespace quickviz { +namespace internal { + +/** + * @brief Parameters for shape rendering operations + */ +struct ShapeRenderParams { + glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f}; + float thickness = 1.0f; + LineType line_type = LineType::kSolid; + bool filled = true; + unsigned int primitive_type = 0; // GL_TRIANGLES, GL_LINES, etc. +}; + +/** + * @brief Consolidated shape vertex generators + * + * These functions generate vertex data for common shapes used in Canvas rendering. + * All functions follow the same pattern: return vertices as flat array of floats (x,y,z). + */ +class ShapeVertexGenerator { +public: + /** + * @brief Generate vertices for a circle + * @param center Center point of the circle + * @param radius Circle radius + * @param segments Number of segments for circle approximation + * @param filled Whether to include center vertex for filled rendering + * @return Vertex data as flat array [x,y,z, x,y,z, ...] + */ + static std::vector GenerateCircle(const glm::vec3& center, float radius, + int segments = 32, bool filled = true); + + /** + * @brief Generate vertices for an ellipse + * @param center Center point of the ellipse + * @param rx Radius in x direction + * @param ry Radius in y direction + * @param angle Rotation angle in radians + * @param start_angle Start angle for arc (radians) + * @param end_angle End angle for arc (radians) + * @param segments Number of segments for ellipse approximation + * @param filled Whether to include center vertex for filled rendering + * @return Vertex data as flat array + */ + static std::vector GenerateEllipse(const glm::vec3& center, float rx, float ry, + float angle, float start_angle, float end_angle, + int segments = 32, bool filled = true); + + /** + * @brief Generate vertices for a rectangle + * @param position Bottom-left corner position + * @param width Rectangle width + * @param height Rectangle height + * @return Vertex data as flat array (4 vertices for rectangle corners) + */ + static std::vector GenerateRectangle(const glm::vec3& position, + float width, float height); + + /** + * @brief Generate vertices for a line + * @param start Start point of the line + * @param end End point of the line + * @return Vertex data as flat array (2 vertices) + */ + static std::vector GenerateLine(const glm::vec3& start, const glm::vec3& end); + + /** + * @brief Generate vertices for a polygon + * @param vertices Polygon vertex positions + * @return Vertex data as flat array + */ + static std::vector GeneratePolygon(const std::vector& vertices); +}; + +/** + * @brief Efficient shape renderer using resource pool + * + * This class consolidates all the common OpenGL rendering patterns used throughout + * Canvas shape rendering, reducing code duplication and improving maintainability. + */ +class EfficientShapeRenderer { +public: + /** + * @brief Constructor + * @param resource_pool Shared resource pool for VAO/VBO management + * @param shader_program Shader program for rendering (non-owning pointer) + */ + EfficientShapeRenderer(std::shared_ptr resource_pool, + void* shader_program); // Using void* to avoid circular includes + + /** + * @brief Render a shape with given vertices and parameters + * @param vertices Vertex data as flat array [x,y,z, x,y,z, ...] + * @param params Rendering parameters (color, thickness, etc.) + */ + void RenderShape(const std::vector& vertices, const ShapeRenderParams& params); + + /** + * @brief Set up common rendering state for shape rendering + * @param projection Projection matrix + * @param view View matrix + * @param coord_transform Coordinate transformation matrix + */ + void SetupRenderingState(const glm::mat4& projection, const glm::mat4& view, + const glm::mat4& coord_transform); + + /** + * @brief Clean up rendering state + */ + void CleanupRenderingState(); + +private: + std::shared_ptr resource_pool_; + void* shader_program_; // ShaderProgram* - using void* to avoid circular includes + + // Internal rendering helpers + void SetupShaderUniforms(const ShapeRenderParams& params); + void ConfigureVertexAttributes(const OpenGLResourcePool::TempResources& resources, + const std::vector& vertices); +}; + +} // namespace internal +} // namespace quickviz + +#endif /* OPENGL_RENDERER_SHAPE_RENDERER_UTILS_HPP */ \ No newline at end of file diff --git a/src/renderer/test/test_primitive_drawing.cpp b/src/renderer/test/test_primitive_drawing.cpp index 7a141e8..a1a87d5 100644 --- a/src/renderer/test/test_primitive_drawing.cpp +++ b/src/renderer/test/test_primitive_drawing.cpp @@ -57,6 +57,13 @@ void TestAllCanvasFunctions(Canvas* canvas) { canvas->AddCircle(2.0f, 0.0f, 0.5f, glm::vec4(0.7f, 0.7f, 0.7f, 0.8f), false, 2.0f); // Gray outlined + canvas->AddCircle(1.0f, 2.0f, 0.7f, glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), true, + 2.0f); + canvas->AddCircle(2.0f, 2.0f, 0.7f, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f), true, + 2.0f); + canvas->AddCircle(3.0f, 2.0f, 0.7f, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f), true, + 2.0f); + // Add ellipses - filled and outlined canvas->AddEllipse(0.0f, 3.0f, 1.0f, 0.5f, 0.0f, 0.0f, 6.28f, glm::vec4(0.5f, 0.5f, 0.0f, 0.8f), true, @@ -127,6 +134,10 @@ int main(int argc, char* argv[]) { // now let's do some drawing on the canvas { auto canvas = static_cast(gl_sm->GetOpenGLObject("canvas")); + + // Test batching mode (batching enabled) + std::cout << "\n=== Testing Batching Mode (Batching Enabled) ===" << std::endl; + canvas->SetBatchingEnabled(true); // Add background image first so it's behind all other drawings std::string image_path = "../data/fish.png"; @@ -165,6 +176,8 @@ int main(int argc, char* argv[]) { 0.005f); // Test all canvas drawing functions + std::cout << "Drawing overlapping circles: Red(1,2), Green(2,2), Blue(3,2)" << std::endl; + std::cout << "In batching mode, expect Blue on top (last drawn), Green middle, Red bottom" << std::endl; TestAllCanvasFunctions(canvas); }