Files
yaze/docs/internal/architecture/graphics_system_architecture.md

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_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 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 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

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