diff --git a/Grid-Rendering-Pipeline.-.md b/Grid-Rendering-Pipeline.md similarity index 95% rename from Grid-Rendering-Pipeline.-.md rename to Grid-Rendering-Pipeline.md index f8c1408..16fda11 100644 --- a/Grid-Rendering-Pipeline.-.md +++ b/Grid-Rendering-Pipeline.md @@ -1,390 +1,390 @@ -# Grid Rendering Pipeline - -## Overview - -The Grid rendering pipeline handles multi-layer tilemap rendering with chunk-based caching for optimal performance. It supports arbitrary numbers of rendering layers, viewport culling, zoom, and entity rendering. - -**Parent Page:** [[Grid-System]] - -**Related Pages:** -- [[Performance-and-Profiling]] - Metrics and profiling tools -- [[Entity-Management]] - Entity rendering within grids -- [[Grid-TCOD-Integration]] - FOV and pathfinding - -**Key Files:** -- `src/UIGrid.cpp::render()` - Main rendering orchestration -- `src/GridLayers.cpp` - ColorLayer and TileLayer rendering -- `src/UIGrid.cpp::renderChunk()` - Per-chunk RenderTexture management - ---- - -## Architecture Overview - -### Layer-Based Rendering - -Grids render content through **layers** rather than per-cell properties. Each layer is either a `ColorLayer` (solid colors) or `TileLayer` (texture sprites). - -**Render order is determined by z_index:** -- Layers with `z_index < 0` render **below** entities -- Entities render at the z=0 boundary -- Layers with `z_index >= 0` render **above** entities (overlays) - -Within each group, lower z_index values render first (behind higher values). - -``` -z_index: -3 -2 -1 0 +1 +2 - | | | | | | - [background] [tiles] [ENTITIES] [fog] [UI overlay] -``` - -### Chunk-Based Caching - -Large grids are divided into **chunks**. Each chunk maintains its own `sf::RenderTexture` that caches the rendered result. - -**Key concepts:** -- Only chunks intersecting the viewport are considered for rendering -- Each chunk tracks whether its content is "dirty" (needs redraw) -- Static content renders once, then the cached texture is reused - -### Dirty Flag Propagation - -When layer content changes, only affected chunks are marked dirty: - -1. `layer.set((x, y), value)` marks the containing chunk as dirty -2. On next render, dirty chunks redraw to their RenderTexture -3. Clean chunks simply blit their cached texture - -This means a 1000x1000 grid with one changing cell redraws only one chunk, not 1,000,000 cells. - ---- - -## Render Pipeline Stages - -### Stage 1: Viewport Calculation - -Calculate which chunks and cells are visible based on camera position, zoom, and grid dimensions. - -**Key properties:** -- `center` - Camera position in pixel coordinates (Vector) -- `zoom` - Scale factor (1.0 = normal, 2.0 = 2x magnification) -- `size` - Viewport dimensions in screen pixels - -### Stage 2: Below-Entity Layers - -For each layer with `z_index < 0`, sorted by z_index: -1. Determine which chunks intersect viewport -2. For each visible chunk: - - If dirty: redraw layer content to chunk's RenderTexture - - Draw chunk's cached texture to output - -### Stage 3: Entity Rendering - -Entities render at the z=0 boundary: -1. Iterate entity collection -2. Cull entities outside viewport bounds -3. Draw visible entity sprites at interpolated positions - -### Stage 4: Above-Entity Layers - -For each layer with `z_index >= 0`, sorted by z_index: -- Same chunk-based rendering as Stage 2 -- These layers appear as overlays (fog, highlights, UI elements) - -### Stage 5: Final Compositing - -All rendered content is drawn to the window. - ---- - -## Creating and Managing Layers - -### Standalone Layer Objects - -Layers are created as standalone objects, then attached to grids: - -```python -grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[]) - -# Create layers as standalone objects -background = mcrfpy.ColorLayer(name="background", z_index=-2) -grid.add_layer(background) - -terrain = mcrfpy.TileLayer(name="terrain", z_index=-1, texture=tileset) -grid.add_layer(terrain) - -# Overlay above entities (z_index >= 0) -fog = mcrfpy.ColorLayer(name="fog", z_index=1) -grid.add_layer(fog) -``` - -### Passing Layers at Construction - -You can also pass layers during Grid construction: - -```python -bg = mcrfpy.ColorLayer(name="background", z_index=-2) -tiles = mcrfpy.TileLayer(name="terrain", z_index=-1, texture=tileset) -fog = mcrfpy.ColorLayer(name="fog", z_index=1) - -grid = mcrfpy.Grid( - grid_size=(50, 50), - pos=(0, 0), - size=(800, 600), - layers=[bg, tiles, fog] -) -``` - -### Accessing Layers - -```python -# By name -fog_layer = grid.layer("fog") - -# All layers (returns tuple) -all_layers = grid.layers -print(f"Grid has {len(all_layers)} layers") -``` - -### Removing Layers - -```python -grid.remove_layer(fog_layer) -``` - ---- - -## Layer Operations - -### ColorLayer - -```python -cl = mcrfpy.ColorLayer(name="highlights", z_index=1) -grid.add_layer(cl) - -# Fill all cells with one color -cl.fill(mcrfpy.Color(0, 0, 0, 255)) - -# Set individual cell -cl.set((5, 5), mcrfpy.Color(255, 0, 0, 128)) - -# Read cell value -color = cl.at((5, 5)) -print(f"Color: ({color.r}, {color.g}, {color.b}, {color.a})") -``` - -### TileLayer - -```python -tl = mcrfpy.TileLayer(name="terrain", z_index=-1, texture=tileset) -grid.add_layer(tl) - -# Set tile sprite index -tl.set((5, 5), 42) - -# Read tile index -idx = tl.at((5, 5)) - -# Fill all cells -tl.fill(0) # All floor tiles -``` - -### Layer Properties - -```python -layer = grid.layer("fog") - -# Visibility toggle -layer.visible = False # Hide layer -layer.visible = True # Show layer - -# Z-index (render order) -layer.z_index = 2 # Move to front - -# Grid dimensions (read-only) -w, h = layer.grid_size -``` - ---- - -## Performance Characteristics - -**Static Grids:** -- Near-zero CPU cost after initial render -- Cached chunk textures reused frame-to-frame -- Only viewport calculation and texture blitting - -**Dynamic Grids:** -- Cost proportional to number of dirty chunks -- Single-cell changes affect only one chunk -- Bulk operations should batch changes before render - -**Large Grids:** -- 1000x1000+ grids render efficiently -- Only visible chunks processed -- Memory scales with grid size (chunk textures) - ---- - -## FOV Overlay Pattern - -The most common overlay pattern is fog of war using a ColorLayer: - -```python -grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[]) - -# Background and terrain layers (below entities) -bg = mcrfpy.ColorLayer(name="bg", z_index=-2) -grid.add_layer(bg) -bg.fill(mcrfpy.Color(20, 20, 30, 255)) - -# Fog overlay (above entities, z_index >= 0) -fog = mcrfpy.ColorLayer(name="fog", z_index=1) -grid.add_layer(fog) -fog.fill(mcrfpy.Color(0, 0, 0, 255)) - -# Make cells traversable -for x in range(50): - for y in range(50): - grid.at(x, y).transparent = True - grid.at(x, y).walkable = True - -# After computing FOV, update fog -grid.compute_fov((25, 25), radius=10) -w, h = grid.grid_size -for x in range(w): - for y in range(h): - if grid.is_in_fov((x, y)): - fog.set((x, y), mcrfpy.Color(0, 0, 0, 0)) # Transparent - else: - fog.set((x, y), mcrfpy.Color(0, 0, 0, 192)) # Dimmed -``` - ---- - -## Multiple Overlay Layers - -Pre-create multiple overlay layers and toggle between them: - -```python -grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[]) - -highlight = mcrfpy.ColorLayer(name="highlight", z_index=1) -danger = mcrfpy.ColorLayer(name="danger", z_index=2) -fog = mcrfpy.ColorLayer(name="fog", z_index=3) -grid.add_layer(highlight) -grid.add_layer(danger) -grid.add_layer(fog) - -def show_danger_zones(): - highlight.visible = False - danger.visible = True - fog.visible = False - -def show_fog_of_war(): - highlight.visible = False - danger.visible = False - fog.visible = True -``` - ---- - -## Recreating Legacy Three-Layer Rendering - -The old system rendered: background color, tile sprite, FOV overlay. Here's the modern equivalent: - -```python -grid = mcrfpy.Grid( - grid_size=(50, 50), - pos=(100, 100), - size=(400, 400), - layers=[] -) - -# Layer 1: Background colors (z=-2, behind everything) -background = mcrfpy.ColorLayer(name="background", z_index=-2) -grid.add_layer(background) -background.fill(mcrfpy.Color(20, 20, 30, 255)) - -# Layer 2: Tile sprites (z=-1, above background, below entities) -tiles = mcrfpy.TileLayer(name="tiles", z_index=-1, texture=tileset) -grid.add_layer(tiles) - -# Layer 3: FOV overlay (z=+1, above entities) -fog = mcrfpy.ColorLayer(name="fog", z_index=1) -grid.add_layer(fog) - -# Populate layers -for x in range(50): - for y in range(50): - tiles.set((x, y), calculate_tile_index(x, y)) -``` - ---- - -## Common Issues - -### Issue: Layer Changes Don't Appear - -**Cause:** Use layer methods (`set()`, `fill()`) which automatically mark chunks dirty. - -### Issue: Overlay Appears Behind Entities - -**Cause:** Layer z_index is negative. - -**Fix:** Use `z_index >= 0` for overlays: -```python -overlay = mcrfpy.ColorLayer(name="overlay", z_index=1) # Not z_index=-1 -``` - -### Issue: Performance Degrades with Many Changes - -**Cause:** Each `set()` call marks a chunk dirty; scattered changes mean many chunk redraws. - -**Fix:** For bulk updates, use `fill()` for uniform values: -```python -# Single operation - efficient -layer.fill(mcrfpy.Color(0, 0, 0, 0)) - -# Many individual calls - slower but necessary for non-uniform data -for x in range(100): - for y in range(100): - layer.set((x, y), compute_value(x, y)) -``` - ---- - -## API Quick Reference - -**Grid Properties:** -- `center` - Camera position (Vector, pixels in grid space) -- `zoom` - Scale factor (float) -- `layers` - All layers (tuple, sorted by z_index) -- `fill_color` - Grid background color (behind all layers) -- `grid_size` - Grid dimensions (tuple) - -**Grid Methods:** -- `add_layer(layer)` - Attach a layer object -- `remove_layer(layer)` - Detach a layer -- `layer("name")` - Get layer by name - -**Layer Construction:** -- `mcrfpy.ColorLayer(name="...", z_index=N)` - Color overlay -- `mcrfpy.TileLayer(name="...", z_index=N, texture=tex)` - Tile sprites - -**Layer Properties:** -- `z_index` - Render order (int) -- `visible` - Show/hide (bool) -- `grid_size` - Dimensions (tuple, read-only) - -**Layer Methods:** -- `set((x, y), value)` - Set cell (Color or int) -- `at((x, y))` - Get cell value -- `fill(value)` - Fill all cells - ---- - -**Navigation:** -- [[Grid-System]] - Parent page, layer concepts -- [[Grid-TCOD-Integration]] - FOV computation details -- [[Performance-and-Profiling]] - Metrics and optimization +# Grid Rendering Pipeline + +## Overview + +The Grid rendering pipeline handles multi-layer tilemap rendering with chunk-based caching for optimal performance. It supports arbitrary numbers of rendering layers, viewport culling, zoom, and entity rendering. + +**Parent Page:** [[Grid-System]] + +**Related Pages:** +- [[Performance-and-Profiling]] - Metrics and profiling tools +- [[Entity-Management]] - Entity rendering within grids +- [[Grid-TCOD-Integration]] - FOV and pathfinding + +**Key Files:** +- `src/UIGrid.cpp::render()` - Main rendering orchestration +- `src/GridLayers.cpp` - ColorLayer and TileLayer rendering +- `src/UIGrid.cpp::renderChunk()` - Per-chunk RenderTexture management + +--- + +## Architecture Overview + +### Layer-Based Rendering + +Grids render content through **layers** rather than per-cell properties. Each layer is either a `ColorLayer` (solid colors) or `TileLayer` (texture sprites). + +**Render order is determined by z_index:** +- Layers with `z_index < 0` render **below** entities +- Entities render at the z=0 boundary +- Layers with `z_index >= 0` render **above** entities (overlays) + +Within each group, lower z_index values render first (behind higher values). + +``` +z_index: -3 -2 -1 0 +1 +2 + | | | | | | + [background] [tiles] [ENTITIES] [fog] [UI overlay] +``` + +### Chunk-Based Caching + +Large grids are divided into **chunks**. Each chunk maintains its own `sf::RenderTexture` that caches the rendered result. + +**Key concepts:** +- Only chunks intersecting the viewport are considered for rendering +- Each chunk tracks whether its content is "dirty" (needs redraw) +- Static content renders once, then the cached texture is reused + +### Dirty Flag Propagation + +When layer content changes, only affected chunks are marked dirty: + +1. `layer.set((x, y), value)` marks the containing chunk as dirty +2. On next render, dirty chunks redraw to their RenderTexture +3. Clean chunks simply blit their cached texture + +This means a 1000x1000 grid with one changing cell redraws only one chunk, not 1,000,000 cells. + +--- + +## Render Pipeline Stages + +### Stage 1: Viewport Calculation + +Calculate which chunks and cells are visible based on camera position, zoom, and grid dimensions. + +**Key properties:** +- `center` - Camera position in pixel coordinates (Vector) +- `zoom` - Scale factor (1.0 = normal, 2.0 = 2x magnification) +- `size` - Viewport dimensions in screen pixels + +### Stage 2: Below-Entity Layers + +For each layer with `z_index < 0`, sorted by z_index: +1. Determine which chunks intersect viewport +2. For each visible chunk: + - If dirty: redraw layer content to chunk's RenderTexture + - Draw chunk's cached texture to output + +### Stage 3: Entity Rendering + +Entities render at the z=0 boundary: +1. Iterate entity collection +2. Cull entities outside viewport bounds +3. Draw visible entity sprites at interpolated positions + +### Stage 4: Above-Entity Layers + +For each layer with `z_index >= 0`, sorted by z_index: +- Same chunk-based rendering as Stage 2 +- These layers appear as overlays (fog, highlights, UI elements) + +### Stage 5: Final Compositing + +All rendered content is drawn to the window. + +--- + +## Creating and Managing Layers + +### Standalone Layer Objects + +Layers are created as standalone objects, then attached to grids: + +```python +grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[]) + +# Create layers as standalone objects +background = mcrfpy.ColorLayer(name="background", z_index=-2) +grid.add_layer(background) + +terrain = mcrfpy.TileLayer(name="terrain", z_index=-1, texture=tileset) +grid.add_layer(terrain) + +# Overlay above entities (z_index >= 0) +fog = mcrfpy.ColorLayer(name="fog", z_index=1) +grid.add_layer(fog) +``` + +### Passing Layers at Construction + +You can also pass layers during Grid construction: + +```python +bg = mcrfpy.ColorLayer(name="background", z_index=-2) +tiles = mcrfpy.TileLayer(name="terrain", z_index=-1, texture=tileset) +fog = mcrfpy.ColorLayer(name="fog", z_index=1) + +grid = mcrfpy.Grid( + grid_size=(50, 50), + pos=(0, 0), + size=(800, 600), + layers=[bg, tiles, fog] +) +``` + +### Accessing Layers + +```python +# By name +fog_layer = grid.layer("fog") + +# All layers (returns tuple) +all_layers = grid.layers +print(f"Grid has {len(all_layers)} layers") +``` + +### Removing Layers + +```python +grid.remove_layer(fog_layer) +``` + +--- + +## Layer Operations + +### ColorLayer + +```python +cl = mcrfpy.ColorLayer(name="highlights", z_index=1) +grid.add_layer(cl) + +# Fill all cells with one color +cl.fill(mcrfpy.Color(0, 0, 0, 255)) + +# Set individual cell +cl.set((5, 5), mcrfpy.Color(255, 0, 0, 128)) + +# Read cell value +color = cl.at((5, 5)) +print(f"Color: ({color.r}, {color.g}, {color.b}, {color.a})") +``` + +### TileLayer + +```python +tl = mcrfpy.TileLayer(name="terrain", z_index=-1, texture=tileset) +grid.add_layer(tl) + +# Set tile sprite index +tl.set((5, 5), 42) + +# Read tile index +idx = tl.at((5, 5)) + +# Fill all cells +tl.fill(0) # All floor tiles +``` + +### Layer Properties + +```python +layer = grid.layer("fog") + +# Visibility toggle +layer.visible = False # Hide layer +layer.visible = True # Show layer + +# Z-index (render order) +layer.z_index = 2 # Move to front + +# Grid dimensions (read-only) +w, h = layer.grid_size +``` + +--- + +## Performance Characteristics + +**Static Grids:** +- Near-zero CPU cost after initial render +- Cached chunk textures reused frame-to-frame +- Only viewport calculation and texture blitting + +**Dynamic Grids:** +- Cost proportional to number of dirty chunks +- Single-cell changes affect only one chunk +- Bulk operations should batch changes before render + +**Large Grids:** +- 1000x1000+ grids render efficiently +- Only visible chunks processed +- Memory scales with grid size (chunk textures) + +--- + +## FOV Overlay Pattern + +The most common overlay pattern is fog of war using a ColorLayer: + +```python +grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[]) + +# Background and terrain layers (below entities) +bg = mcrfpy.ColorLayer(name="bg", z_index=-2) +grid.add_layer(bg) +bg.fill(mcrfpy.Color(20, 20, 30, 255)) + +# Fog overlay (above entities, z_index >= 0) +fog = mcrfpy.ColorLayer(name="fog", z_index=1) +grid.add_layer(fog) +fog.fill(mcrfpy.Color(0, 0, 0, 255)) + +# Make cells traversable +for x in range(50): + for y in range(50): + grid.at(x, y).transparent = True + grid.at(x, y).walkable = True + +# After computing FOV, update fog +grid.compute_fov((25, 25), radius=10) +w, h = grid.grid_size +for x in range(w): + for y in range(h): + if grid.is_in_fov((x, y)): + fog.set((x, y), mcrfpy.Color(0, 0, 0, 0)) # Transparent + else: + fog.set((x, y), mcrfpy.Color(0, 0, 0, 192)) # Dimmed +``` + +--- + +## Multiple Overlay Layers + +Pre-create multiple overlay layers and toggle between them: + +```python +grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[]) + +highlight = mcrfpy.ColorLayer(name="highlight", z_index=1) +danger = mcrfpy.ColorLayer(name="danger", z_index=2) +fog = mcrfpy.ColorLayer(name="fog", z_index=3) +grid.add_layer(highlight) +grid.add_layer(danger) +grid.add_layer(fog) + +def show_danger_zones(): + highlight.visible = False + danger.visible = True + fog.visible = False + +def show_fog_of_war(): + highlight.visible = False + danger.visible = False + fog.visible = True +``` + +--- + +## Recreating Legacy Three-Layer Rendering + +The old system rendered: background color, tile sprite, FOV overlay. Here's the modern equivalent: + +```python +grid = mcrfpy.Grid( + grid_size=(50, 50), + pos=(100, 100), + size=(400, 400), + layers=[] +) + +# Layer 1: Background colors (z=-2, behind everything) +background = mcrfpy.ColorLayer(name="background", z_index=-2) +grid.add_layer(background) +background.fill(mcrfpy.Color(20, 20, 30, 255)) + +# Layer 2: Tile sprites (z=-1, above background, below entities) +tiles = mcrfpy.TileLayer(name="tiles", z_index=-1, texture=tileset) +grid.add_layer(tiles) + +# Layer 3: FOV overlay (z=+1, above entities) +fog = mcrfpy.ColorLayer(name="fog", z_index=1) +grid.add_layer(fog) + +# Populate layers +for x in range(50): + for y in range(50): + tiles.set((x, y), calculate_tile_index(x, y)) +``` + +--- + +## Common Issues + +### Issue: Layer Changes Don't Appear + +**Cause:** Use layer methods (`set()`, `fill()`) which automatically mark chunks dirty. + +### Issue: Overlay Appears Behind Entities + +**Cause:** Layer z_index is negative. + +**Fix:** Use `z_index >= 0` for overlays: +```python +overlay = mcrfpy.ColorLayer(name="overlay", z_index=1) # Not z_index=-1 +``` + +### Issue: Performance Degrades with Many Changes + +**Cause:** Each `set()` call marks a chunk dirty; scattered changes mean many chunk redraws. + +**Fix:** For bulk updates, use `fill()` for uniform values: +```python +# Single operation - efficient +layer.fill(mcrfpy.Color(0, 0, 0, 0)) + +# Many individual calls - slower but necessary for non-uniform data +for x in range(100): + for y in range(100): + layer.set((x, y), compute_value(x, y)) +``` + +--- + +## API Quick Reference + +**Grid Properties:** +- `center` - Camera position (Vector, pixels in grid space) +- `zoom` - Scale factor (float) +- `layers` - All layers (tuple, sorted by z_index) +- `fill_color` - Grid background color (behind all layers) +- `grid_size` - Grid dimensions (tuple) + +**Grid Methods:** +- `add_layer(layer)` - Attach a layer object +- `remove_layer(layer)` - Detach a layer +- `layer("name")` - Get layer by name + +**Layer Construction:** +- `mcrfpy.ColorLayer(name="...", z_index=N)` - Color overlay +- `mcrfpy.TileLayer(name="...", z_index=N, texture=tex)` - Tile sprites + +**Layer Properties:** +- `z_index` - Render order (int) +- `visible` - Show/hide (bool) +- `grid_size` - Dimensions (tuple, read-only) + +**Layer Methods:** +- `set((x, y), value)` - Set cell (Color or int) +- `at((x, y))` - Get cell value +- `fill(value)` - Fill all cells + +--- + +**Navigation:** +- [[Grid-System]] - Parent page, layer concepts +- [[Grid-TCOD-Integration]] - FOV computation details +- [[Performance-and-Profiling]] - Metrics and optimization - [[Entity-Management]] - Entity rendering \ No newline at end of file