7.8 KiB
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_TextureandSDL_Surfaceobjects using RAII wrappers with custom deleters - Graphics Sheets: Holds a fixed array of 223
Bitmapobjects representing the game's complete graphics space (indexed 0-222) - Background Buffers: Manages
BackgroundBuffers 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 processingProcessTextureQueue(renderer): Process all queued texture commandsNotifySheetModified(sheet_index): Notify when a graphics sheet changes to synchronize editorsgfx_sheets(): Get all 223 graphics sheetsmutable_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_Texturehandle 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_Texturedirectly - always modify theBitmapdata - 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
Canvasclass 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:
- Iterates through all 223 sheet indices
- Determines format based on index range (2BPP, 3BPP compressed, 3BPP uncompressed)
- Calls decompression functions
- Converts to internal 8-bit indexed format
- Stores result in
Arena.gfx_sheets_
Performance: Uses deferred loading via texture queue to avoid blocking
2. Composition Phase (Rooms/Overworld)
Process:
- Room/Overworld logic draws tiles from
gfx_sheetsinto aBackgroundBuffer(wraps aBitmap) - This drawing happens on CPU, manipulating indexed pixel data
- Each room/map maintains its own
Bitmapof 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:
- Editor checks if bitmaps are marked "dirty" (modified since last render)
- Modified bitmaps queue a
TextureCommand::UPDATEto Arena - Arena processes queue, uploading pixel data to SDL textures
- This batching avoids per-frame texture uploads
4. Display Phase
Process:
Canvasor UI elements request theSDL_Texturefrom aBitmap- Texture is rendered to screen using ImGui or direct SDL calls
- 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()
- Iterates through all 223 sheet indices
- Determines format based on index range
- Calls
gfx::lc_lz2::DecompressV2()(orDecompressV1()for compatibility) - For uncompressed sheets (115-126), copies raw data directly
- Converts result to internal 8-bit indexed format
- Stores in
Arena.gfx_sheets_[index]
Saving Process
Process:
- Get mutable reference:
auto& sheet = Arena::Get().mutable_gfx_sheet(index) - Make modifications to
sheet.mutable_data() - Notify Arena:
Arena::Get().NotifySheetModified(index) - 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 canvasDrawSolidTilePainter(): Preview of brush before commitDrawTileOnBitmap(): 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_Texturedirectly: Always modify theBitmapdata and callUpdateTexture()or queue it - Use
QueueTextureCommand: For bulk updates, queue commands to avoid stalling the main thread - Respect Palettes: Remember that
Bitmapdata is just indices. Visual result depends on the associatedSnesPalette - Sheet Modification: When modifying a global graphics sheet, notify
ArenaviaNotifySheetModified()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
IRendererinterface 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