2026-01-19 07:28:14 +00:00

600 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

< [Back](chapter-3.md) | [Summary](table-of-contents.md) | [Next](chapter-5.md) >
# 🎨 **GFX Peripheral (Graphics System)**
## 1. Overview
The **GFX** peripheral is responsible for generating images in PROMETEU.
It models **simple graphics hardware**, inspired by classic consoles
(SNES, CPS-2, Neo-Geo), prioritizing:
- determinism
- low computational cost
- didactics
- portability
The GFX **is not a modern GPU**.
It is an explicit device, based on:
- framebuffer
- tilemaps
- tile banks
- priority-based sprites
- composition by drawing order
---
## 2. Resolution and Framebuffer
### Base resolution
- **320 × 180 pixels**
- aspect ratio close to 16:9
- scalable by the host (nearest-neighbor)
### Pixel format
- **RGB565**
- 5 bits Red
- 6 bits Green
- 5 bits Blue
- no alpha channel
Transparency is handled via **color key**.
---
## 3. Double Buffering
The GFX maintains two buffers:
- **Back Buffer** — where the frame is built
- **Front Buffer** — where the frame is displayed
Per-frame flow:
1. The system draws to the back buffer
2. Calls `present()`
3. Buffers are swapped
4. The host displays the front buffer
This guarantees:
- no tearing
- clear per-frame synchronization
- deterministic behavior
---
## 4. PROMETEU Graphical Structure
The graphical world is composed of:
- Up to **16 Tile Banks**
- **4 Tile Layers** (scrollable)
- **1 HUD Layer** (fixed, always on top)
- Sprites with priority between layers
### 4.1 Tile Banks
- There are up to **16 banks**
- Each bank has a fixed tile size:
- 8×8, 16×16, or 32×32
- A bank is a graphics library:
- environment
- characters
- UI
- effects
### 4.2 Layers
- There are:
- 4 Tile Layers
- 1 HUD Layer
- Each layer points to **a single bank**
- Sprites can use **any bank**
- HUD:
- does not scroll
- maximum priority
- generally uses 8×8 tiles
---
## 5. Internal Model of a Tile Layer
A Tile Layer **is not a bitmap of pixels**.
It is composed of:
- A **logical Tilemap** (tile indices)
- A **Border Cache** (window of visible tiles)
- A **Scroll Offset**
### Structure:
- `bank_id`
- `tile_size`
- `tilemap` (large matrix)
- `scroll_x`, `scroll_y`
- `cache_origin_x`, `cache_origin_y`
- `cache_tiles[w][h]`
---
## 6. Logical Tilemap
The tilemap represents the world:
Each cell contains:
- `tile_id`
- `flip_x`
- `flip_y`
- `priority` (optional)
- `palette_id` (optional)
The tilemap can be much larger than the screen.
---
## 7. Border Cache (Tile Cache)
The cache is a window of tiles around the camera.
Example:
- Screen: 320×180
- 16×16 tiles → 20×12 visible
- Cache: 22×14 (1-tile margin)
It stores tiles already resolved from the tilemap.
---
## 8. Cache Update
Every frame:
1. Calculate:
- `tile_x = scroll_x / tile_size`
- `tile_y = scroll_y / tile_size`
- `offset_x = scroll_x % tile_size`
- `offset_y = scroll_y % tile_size`
2. If `tile_x` changed:
- Advance `cache_origin_x`
- Reload only the new column
3. If `tile_y` changed:
- Advance `cache_origin_y`
- Reload only the new line
Only **one row and/or column** is updated per frame.
---
## 9. Cache as Ring Buffer
The cache is circular:
- Does not physically move data
- Only moves logical indices
Access:
- `real_x = (cache_origin_x + logical_x) % cache_width`
- `real_y = (cache_origin_y + logical_y) % cache_height`
---
## 10. Projection to the Back Buffer
For each frame:
1. For each Tile Layer, in order:
- Rasterize visible tiles from the cache
- Apply scroll, flip, and transparency
- Write to the back buffer
2. Draw sprites:
- With priority between layers
- Drawing order defines depth
3. Draw HUD layer last
---
## 11. Drawing Order and Priority
- There is no Z-buffer
- There is no automatic sorting
- Whoever draws later is in front
Base order:
1. Tile Layer 0
2. Tile Layer 1
3. Tile Layer 2
4. Tile Layer 3
5. Sprites (by priority between layers)
6. HUD Layer
---
## 12. Transparency (Color Key)
- One RGB565 value is reserved as TRANSPARENT_KEY
- Pixels with this color are not drawn
```
if src == TRANSPARENT_KEY:
skip
else:
draw
```
---
## 13. Color Math (Discrete Blending)
Inspired by the SNES.
Official modes:
- `BLEND_NONE`
- `BLEND_HALF`
- `BLEND_HALF_PLUS`
- `BLEND_HALF_MINUS`
- `BLEND_FULL`
No continuous alpha.
No arbitrary blending.
Everything is:
- integer
- cheap
- deterministic
---
## 14. Where Blend is Applied
- Blending occurs during drawing
- The result goes directly to the back buffer
- There is no automatic post-composition
---
## 15. What the GFX DOES NOT support
By design:
- Continuous alpha
- RGBA framebuffer
- Shaders
- Modern GPU pipeline
- HDR
- Gamma correction
---
## 16. Performance Rule
- Layers:
- only update the border when crossing a tile
- never redraw the entire world
- Rasterization:
- always per frame, only the visible area
- Sprites:
- always redrawn per frame
---
## 17. Special PostFX — Fade (Scene and HUD)
PROMETEU supports **gradual fade** as a special PostFX, with two independent
controls:
- **Scene Fade**: affects the entire scene (Tile Layers 03 + Sprites)
- **HUD Fade**: affects only the HUD Layer (always composed last)
The fade is implemented without continuous per-pixel alpha and without floats.
It uses a **discrete integer level** (0..31), which in practice produces an
"almost continuous" visual result in 320×180 pixel art.
---
### 17.1 Fade Representation
Each fade is represented by:
- `fade_level: u8` in the range **[0..31]**
- `0` → fully replaced by the fade color
- `31` → fully visible (no fade)
- `fade_color: RGB565`
- color the image will be blended into
Registers:
- `SCENE_FADE_LEVEL` (0..31)
- `SCENE_FADE_COLOR` (RGB565)
- `HUD_FADE_LEVEL` (0..31)
- `HUD_FADE_COLOR` (RGB565)
Common cases:
- Fade-out: `fade_color = BLACK`
- Flash/teleport: `fade_color = WHITE`
- Special effects: any RGB565 color
---
### 17.2 Fade Operation (Blending with Arbitrary Color)
For each RGB565 pixel `src` and fade color `fc`, the final pixel `dst` is calculated per channel.
1) Extract components:
- `src_r5`, `src_g6`, `src_b5`
- `fc_r5`, `fc_g6`, `fc_b5`
2) Apply integer blending:
```
src_weight = fade_level // 0..31
fc_weight = 31 - fade_level
r5 = (src_r5 * src_weight + fc_r5 * fc_weight) / 31
g6 = (src_g6 * src_weight + fc_g6 * fc_weight) / 31
b5 = (src_b5 * src_weight + fc_b5 * fc_weight) / 31
```
- `src_r5`, `src_g6`, `src_b5`
- `fc_r5`, `fc_g6`, `fc_b5`
3) Repack:
```
dst = pack_rgb565(r5, g6, b5)
```
Notes:
- Deterministic operation
- Integers only
- Can be optimized via LUT
---
### 17.3 Order of Application in the Frame
The frame composition follows this order:
1. Rasterize **Tile Layers 03** → Back Buffer
2. Rasterize **Sprites** according to priority
3. (Optional) Extra pipeline (Emission/Light/Glow etc.)
4. Apply **Scene Fade** using:
- `SCENE_FADE_LEVEL`
- `SCENE_FADE_COLOR`
5. Rasterize **HUD Layer**
6. Apply **HUD Fade** using:
- `HUD_FADE_LEVEL`
- `HUD_FADE_COLOR`
7. `present()`
Rules:
- Scene Fade never affects the HUD
- HUD Fade never affects the scene
---
### 17.4 Use Cases
- HUD Switch:
- decrease `HUD_FADE_LEVEL` to 0
- switch HUD/tilemap
- increase `HUD_FADE_LEVEL` to 31
- Area Switch:
- decrease `SCENE_FADE_LEVEL` to 0
- switch scenery
- increase `SCENE_FADE_LEVEL` to 31
- Flash / damage / teleport:
- use `fade_color = WHITE` or another thematic color
---
## 18. Palette System
### 18.1. Overview
PROMETEU uses **exclusively** palette-indexed graphics.
There is no direct RGB-per-pixel mode.
Every graphical pixel is an **index** pointing to a real color in a palette.
Objectives:
- reduce RAM and storage usage
- allow color swapping without shaders
- maintain retro identity
- facilitate effects like variation, damage, day/night
---
### 18.2. Pixel Format
Each pixel of a tile or sprite is:
- **4 bits per pixel (4bpp)**
- values: `0..15`
Fixed rule:
- Index `0` = TRANSPARENT
- Indices `1..15` = valid palette colors
---
### 18.3. Palette Structure
Each **Tile Bank** contains:
- Up to **256 palettes**
- Each palette has:
- **16 colors**
- each color in **RGB565 (u16)**
Size:
- 1 palette = 16 × 2 bytes = **32 bytes**
- 256 palettes = **8 KB per bank**
- 16 banks = **128 KB maximum palettes**
---
### 18.4. Palette Association
#### Fundamental Rule
- Each **tile** uses **a single palette**
- Each **sprite** uses **a single palette**
- The palette must be provided **explicitly** in every draw
There is no palette swap within the same tile or sprite.
---
### 18.5. Where the Palette is Defined
#### Tilemap
Each tilemap cell contains:
- `tile_id`
- `palette_id (u8)`
- `flip_x`
- `flip_y`
#### Sprite
Each sprite draw contains:
- `bank_id`
- `tile_id`
- `palette_id (u8)`
- `x`, `y`
- `flip_x`, `flip_y`
- `priority`
---
### 18.6. Color Resolution
The pipeline works like this:
1. Read indexed pixel from tile (value 0..15)
2. If index == 0 → transparent pixel
3. Otherwise:
- real_color = palette[palette_id][index]
4. Apply:
- flip
- discrete blend
- writing to back buffer
In other words:
```
pixel_index = tile_pixel(x,y)
if pixel_index == 0:
skip
else:
color = bank.palettes[palette_id][pixel_index]
draw(color)
```
---
### 18.7. Organization of Tile Banks
Tile Banks are "strong assets":
- Tiles and palettes live together
- Export/import always carries:
- tiles + palettes
- The hardware does not impose semantic organization:
- grouping is the creator's decision
- Tooling and scripts can create conventions:
- e.g.: palettes 0..15 = enemies
- 16..31 = scenery
- etc.
---
### 18.8. Possible Effects with Palettes
Without shaders, it is possible to:
- Palette swap:
- enemies with color variation
- States:
- damage, ice, poison, power-up
- Day / night:
- swap palettes globally
- Biomes:
- same art, different climate
- UI themes
All this without changing tiles.
---
### 18.9. Artistic Limitations
- Each tile/sprite:
- maximum of 16 colors
- Smooth gradients require:
- dithering
- discrete blend
- glow/emission
This limitation is intentional and part of the PROMETEU identity.
---
### 18.10. Metrics for Certification (CAP)
The system can measure:
- `palettes_loaded_total`
- `palettes_referenced_this_frame`
- `tiles_drawn_by_palette_id`
- `sprites_drawn_by_palette_id`
This allows:
- analyzing artistic cost
- teaching the impact of excessive variety
- suggesting best practices for visual cohesion
---
## 19. Summary
PROMETEU's GFX is simple **by choice**, not by limitation.
- RGB565 Framebuffer with double buffer
- Color key for transparency
- SNES-style discrete blending
- Up to 16 tile banks
- 4 Tile Layers + 1 HUD
- Layer = tilemap + cache + scroll
- Rasterized projection per frame
- Depth defined by drawing order
< [Back](chapter-3.md) | [Summary](table-of-contents.md) | [Next](chapter-5.md) >