178 lines
7.8 KiB
Markdown
178 lines
7.8 KiB
Markdown
# Graphics System Architecture
|
|
|
|
**Status**: Complete
|
|
**Last Updated**: 2025-11-21
|
|
**Related Code**: `src/app/gfx/`, `src/app/editor/graphics/`
|
|
|
|
This document outlines the architecture of the Graphics System in YAZE, including resource management, compression pipelines, and rendering workflows.
|
|
|
|
## Overview
|
|
|
|
The graphics system is designed to handle SNES-specific image formats (indexed color, 2BPP/3BPP) while efficiently rendering them using modern hardware acceleration via SDL2. It uses a centralized resource manager (`Arena`) to pool resources and manage lifecycle.
|
|
|
|
## Core Components
|
|
|
|
### 1. The Arena (`src/app/gfx/resource/arena.h`)
|
|
|
|
The `Arena` is a singleton class that acts as the central resource manager for all graphics.
|
|
|
|
**Responsibilities**:
|
|
* **Resource Management**: Manages the lifecycle of `SDL_Texture` and `SDL_Surface` objects using RAII wrappers with custom deleters
|
|
* **Graphics Sheets**: Holds a fixed array of 223 `Bitmap` objects representing the game's complete graphics space (indexed 0-222)
|
|
* **Background Buffers**: Manages `BackgroundBuffer`s for SNES BG1 and BG2 layer rendering
|
|
* **Deferred Rendering**: Implements a command queue (`QueueTextureCommand`) to batch texture creation/updates, preventing UI freezes during heavy loads
|
|
* **Memory Pooling**: Reuses textures and surfaces to minimize allocation overhead
|
|
|
|
**Key Methods**:
|
|
* `QueueTextureCommand(type, bitmap)`: Queue a texture operation for batch processing
|
|
* `ProcessTextureQueue(renderer)`: Process all queued texture commands
|
|
* `NotifySheetModified(sheet_index)`: Notify when a graphics sheet changes to synchronize editors
|
|
* `gfx_sheets()`: Get all 223 graphics sheets
|
|
* `mutable_gfx_sheet(index)`: Get mutable reference to a specific sheet
|
|
|
|
### 2. Bitmap (`src/app/gfx/core/bitmap.h`)
|
|
|
|
Represents a single graphics sheet or image optimized for SNES ROM editing.
|
|
|
|
**Key Features**:
|
|
* **Data Storage**: Stores raw pixel data as `std::vector<uint8_t>` (indices into a palette)
|
|
* **Palette**: Each bitmap owns a `SnesPalette` (256 colors maximum)
|
|
* **Texture Management**: Manages an `SDL_Texture` handle and syncs CPU pixel data to GPU
|
|
* **Dirty Tracking**: Tracks modified regions to minimize texture upload bandwidth
|
|
* **Tile Extraction**: Provides methods like `Get8x8Tile()`, `Get16x16Tile()` for SNES tile operations
|
|
|
|
**Important Synchronization Rules**:
|
|
* Never modify `SDL_Texture` directly - always modify the `Bitmap` data
|
|
* Use `set_data()` for bulk updates to keep CPU and GPU in sync
|
|
* Use `WriteToPixel()` for single-pixel modifications
|
|
* Call `UpdateTexture()` to sync changes to GPU
|
|
|
|
### 3. Graphics Editor (`src/app/editor/graphics/graphics_editor.cc`)
|
|
|
|
The primary UI for viewing and modifying graphics.
|
|
|
|
* **Sheet Editor**: Pixel-level editing of all 223 sheets
|
|
* **Palette Integration**: Fetches palette groups from the ROM (Overworld, Dungeon, Sprites) to render sheets correctly
|
|
* **Tools**: Pencil, Fill, Select, Zoom
|
|
* **Real-time Display**: Uses `Canvas` class for drawing interface
|
|
|
|
### 4. IRenderer Interface (`src/app/gfx/backend/irenderer.h`)
|
|
|
|
Abstract interface for the rendering backend (currently implemented by `SdlRenderer`). This decouples graphics logic from SDL-specific calls, enabling:
|
|
* Testing with mock renderers
|
|
* Future backend swaps (e.g., Vulkan, Metal)
|
|
|
|
## Rendering Pipeline
|
|
|
|
### 1. Loading Phase
|
|
|
|
**Source**: ROM compressed data
|
|
**Process**:
|
|
1. Iterates through all 223 sheet indices
|
|
2. Determines format based on index range (2BPP, 3BPP compressed, 3BPP uncompressed)
|
|
3. Calls decompression functions
|
|
4. Converts to internal 8-bit indexed format
|
|
5. Stores result in `Arena.gfx_sheets_`
|
|
|
|
**Performance**: Uses deferred loading via texture queue to avoid blocking
|
|
|
|
### 2. Composition Phase (Rooms/Overworld)
|
|
|
|
**Process**:
|
|
1. Room/Overworld logic draws tiles from `gfx_sheets` into a `BackgroundBuffer` (wraps a `Bitmap`)
|
|
2. This drawing happens on CPU, manipulating indexed pixel data
|
|
3. Each room/map maintains its own `Bitmap` of rendered data
|
|
|
|
**Key Classes**:
|
|
* `BackgroundBuffer`: Manages BG1 and BG2 layer rendering for a single room/area
|
|
* Methods like `Room::RenderRoomGraphics()` handle composition
|
|
|
|
### 3. Texture Update Phase
|
|
|
|
**Process**:
|
|
1. Editor checks if bitmaps are marked "dirty" (modified since last render)
|
|
2. Modified bitmaps queue a `TextureCommand::UPDATE` to Arena
|
|
3. Arena processes queue, uploading pixel data to SDL textures
|
|
4. This batching avoids per-frame texture uploads
|
|
|
|
### 4. Display Phase
|
|
|
|
**Process**:
|
|
1. `Canvas` or UI elements request the `SDL_Texture` from a `Bitmap`
|
|
2. Texture is rendered to screen using ImGui or direct SDL calls
|
|
3. Grid, overlays, and selection highlights are drawn on top
|
|
|
|
## Compression Pipeline
|
|
|
|
YAZE uses the **LC-LZ2** algorithm (often called "Hyrule Magic" compression) for ROM I/O.
|
|
|
|
### Supported Formats
|
|
|
|
| Format | Sheets | Bits Per Pixel | Usage | Location |
|
|
|--------|--------|--------|-------|----------|
|
|
| 3BPP (Compressed) | 0-112, 127-217 | 3 | Most graphics | Standard ROM |
|
|
| 2BPP (Compressed) | 113-114, 218-222 | 2 | HUD, Fonts, Effects | Standard ROM |
|
|
| 3BPP (Uncompressed) | 115-126 | 3 | Link Player Sprites | 0x080000 |
|
|
|
|
### Loading Process
|
|
|
|
**Entry Point**: `src/app/rom.cc:Rom::LoadFromFile()`
|
|
|
|
1. Iterates through all 223 sheet indices
|
|
2. Determines format based on index range
|
|
3. Calls `gfx::lc_lz2::DecompressV2()` (or `DecompressV1()` for compatibility)
|
|
4. For uncompressed sheets (115-126), copies raw data directly
|
|
5. Converts result to internal 8-bit indexed format
|
|
6. Stores in `Arena.gfx_sheets_[index]`
|
|
|
|
### Saving Process
|
|
|
|
**Process**:
|
|
1. Get mutable reference: `auto& sheet = Arena::Get().mutable_gfx_sheet(index)`
|
|
2. Make modifications to `sheet.mutable_data()`
|
|
3. Notify Arena: `Arena::Get().NotifySheetModified(index)`
|
|
4. When saving ROM:
|
|
* Convert 8-bit indexed data back to 2BPP/3BPP format
|
|
* Compress using `gfx::lc_lz2::CompressV3()`
|
|
* Write to ROM, handling pointer table updates if sizes change
|
|
|
|
## Link Graphics (Player Sprites)
|
|
|
|
**Location**: ROM offset `0x080000`
|
|
**Format**: Uncompressed 3BPP
|
|
**Sheet Indices**: 115-126
|
|
**Editor**: `GraphicsEditor` provides a "Player Animations" view
|
|
**Structure**: Sheets are assembled into poses using OAM (Object Attribute Memory) tables
|
|
|
|
## Canvas Interactions
|
|
|
|
The `Canvas` class (`src/app/gui/canvas/canvas.h`) is the primary rendering engine.
|
|
|
|
**Drawing Operations**:
|
|
* `DrawBitmap()`: Renders a sheet texture to the canvas
|
|
* `DrawSolidTilePainter()`: Preview of brush before commit
|
|
* `DrawTileOnBitmap()`: Commits pixel changes to Bitmap data
|
|
|
|
**Selection and Tools**:
|
|
* `DrawSelectRect()`: Rectangular region selection
|
|
* Context Menu: Right-click for Zoom, Grid, view resets
|
|
|
|
**Coordinate Systems**:
|
|
* Canvas Pixels: Unscaled (128-512 range depending on sheet)
|
|
* Screen Pixels: Scaled by zoom level
|
|
* Tile Coordinates: 8x8 or 16x16 tiles for SNES editing
|
|
|
|
## Best Practices
|
|
|
|
* **Never modify `SDL_Texture` directly**: Always modify the `Bitmap` data and call `UpdateTexture()` or queue it
|
|
* **Use `QueueTextureCommand`**: For bulk updates, queue commands to avoid stalling the main thread
|
|
* **Respect Palettes**: Remember that `Bitmap` data is just indices. Visual result depends on the associated `SnesPalette`
|
|
* **Sheet Modification**: When modifying a global graphics sheet, notify `Arena` via `NotifySheetModified()` to propagate changes to all editors
|
|
* **Deferred Loading**: Always use the texture queue system for heavy operations to prevent UI freezes
|
|
|
|
## Future Improvements
|
|
|
|
* **Vulkan/Metal Backend**: The `IRenderer` interface allows for potentially swapping SDL2 for a more modern API
|
|
* **Compute Shaders**: Palette swapping could potentially be moved to GPU using shaders instead of CPU-side pixel manipulation
|
|
* **Streaming Graphics**: Load/unload sheets on demand for very large ROM patches
|