Auditoria de core/rendering: neteja general aplicada (rama audit/core-rendering)
Recull de 19 commits d'auditoria + 1 doc amb les troballes i el seu estat. Veure docs/audit-core-rendering.md per al detall per-finding. Resum per categoria: - Duplicació: 7 items (surface blit, shaders MSL + factory, Text loops, Text glyph resolution, GIF extension switch, GIF double-parse, CRT-Pi defaults). - Hardcoding: 9 items amb nom (INK_COLOR_INDEX, DEFAULT_TEXT_COLOR, OPAQUE_ALPHA, LZW/GCT constants, segment indices, position count, shadow offset, FONT_SIZE_PX, CRT reference height/subpixel div). - Parxes: 5 neteja (readBytes comment, Sprite::update inline, mono_digits dedup init, GIF extension labels, NOLINTs eliminats). - Acoblament: 4 desacoblaments (Notifier→game/ui; RenderInfo→Options; Screen→SDL3GPUShader concret; Surface→Screen; font+palette injectats). - Codi mort: 9 items (writeShadowed/writeCentered/glyphWidth/ getGlyphClip/codepointToUtf8/getSprite/getRect/incX/incY/ getCurrentAnimationSize/getSurfaceData/Sprite::update/GIF extension structs). - Convencions: shaders MSL a fitxers dedicats amb naming conforme. Build verificat a cada commit; smoke test de 5s sota xvfb-run a cada finding (exit 124 = corregut sencer, sense crash). Items restants DEFERRED (no es tanquen com a cleanup perquè són features o decisions d'arquitectura global): HC-04 (YAML HUD config), PATCH-01 (RAII Wayland), PATCH-02 (FSM RenderInfo), COUP-05 (AnimationLoader), COUP-06 (reemplaçar singletons), INC-01/-02/-03 (polítiques globals de smart-pointers, errors i logging).
This commit is contained in:
commit
0a3ef447ea
36 files changed
+875
-777
No files matched your search
@@ -39,6 +39,7 @@ set(APP_SOURCES
|
|||||||
source/core/input/key_config.cpp
|
source/core/input/key_config.cpp
|
||||||
source/core/input/global_inputs.cpp
|
source/core/input/global_inputs.cpp
|
||||||
source/core/rendering/screen.cpp
|
source/core/rendering/screen.cpp
|
||||||
|
source/core/rendering/canvas.cpp
|
||||||
source/core/rendering/notifier.cpp
|
source/core/rendering/notifier.cpp
|
||||||
source/core/rendering/render_info.cpp
|
source/core/rendering/render_info.cpp
|
||||||
source/core/rendering/gif.cpp
|
source/core/rendering/gif.cpp
|
||||||
@@ -207,6 +208,7 @@ if(CPPCHECK_EXE)
|
|||||||
--suppress=toomanyconfigs
|
--suppress=toomanyconfigs
|
||||||
--suppress=*:*source/external/*
|
--suppress=*:*source/external/*
|
||||||
--suppress=*:*source/core/rendering/sdl3gpu/spv/*
|
--suppress=*:*source/core/rendering/sdl3gpu/spv/*
|
||||||
|
--suppress=*:*source/core/rendering/sdl3gpu/msl/*
|
||||||
--suppress=normalCheckLevelMaxBranches
|
--suppress=normalCheckLevelMaxBranches
|
||||||
-D_DEBUG
|
-D_DEBUG
|
||||||
-DLINUX_BUILD
|
-DLINUX_BUILD
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Auditoría `core/rendering/`
|
||||||
|
|
||||||
|
Rama: `audit/core-rendering`
|
||||||
|
Estado global: neteja completada. Restant: 3 ítems `DEFERRED` i 4 `WONTFIX` — tots documentats amb justificació.
|
||||||
|
|
||||||
|
Estados posibles:
|
||||||
|
- `PENDING` — identificado, sin tocar
|
||||||
|
- `WIP` — diff propuesto, pendiente de aplicar
|
||||||
|
- `DONE` — aplicado y commiteado
|
||||||
|
- `WONTFIX` — se decide no tocar (justificación en la nota)
|
||||||
|
- `DEFERRED` — se hará en fase posterior
|
||||||
|
|
||||||
|
Los IDs se mantienen estables una vez asignados. Cada entrada apunta al(los) fichero(s) y líneas afectados en la rama de origen (`main` en el momento del clone).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Top 5 prioritario
|
||||||
|
|
||||||
|
| ID | Título | Estado |
|
||||||
|
|----|--------|--------|
|
||||||
|
| COUP-01 | Inversión de dependencia `core/rendering → game/*` | DONE |
|
||||||
|
| DUP-02 | `Surface::render` / `renderWithColorReplace` duplicados | DONE |
|
||||||
|
| CONV-03 | Shaders MSL como raw strings en `.cpp` + helpers duplicados | DONE |
|
||||||
|
| DUP-03 | Familia `Text::write*` con bucle UTF-8 repetido 5 veces | DONE |
|
||||||
|
| COUP-02 | `Screen` instancia `SDL3GPUShader` directamente + `Surface` → `Screen::get()` | DONE |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Duplicación
|
||||||
|
|
||||||
|
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|
||||||
|
|----|---------------|----------|-----------|--------|-------|
|
||||||
|
| DUP-01 | `gif.cpp:238-276` | 3 ramas del switch extension saltan bloque+subloops idénticos | Importante | DONE | `skipExtensionBlock` i switch eliminat |
|
||||||
|
| DUP-02 | `surface.cpp:80-137` | `render` vs `renderWithColorReplace` ~90% idénticos | Crítico | DONE | `render()` delega amb source==target==transparent |
|
||||||
|
| DUP-03 | `text.cpp:145-289` | bucle UTF-8 repetido en write/writeColored/writeMonoDigits/length/lengthMonoDigits | Importante | DONE | `resolveGlyph` + `write` delega a `writeColored` |
|
||||||
|
| DUP-04 | `sdl3gpu_shader.cpp:165-201` + `crtpi_pipeline.cpp:165-202` | `createShaderMSL/SPIRV` duplicados | Importante | DONE | `sdl3gpu/shader_factory.hpp` |
|
||||||
|
| DUP-05 | `sdl3gpu_shader.cpp:18-35` + `crtpi_pipeline.cpp:17-34` | vertex shader MSL duplicado | Menor | DONE | `sdl3gpu/msl/postfx_vert.msl.h` compartit |
|
||||||
|
| DUP-06 | `surface.cpp:14-43` | constructor Surface abre y parsea GIF 2 veces | Importante | DONE | buffer carregat una volta, pixels+palette del mateix |
|
||||||
|
| DUP-07 | `crtpi_pipeline.hpp:70-87` + `shader_backend.hpp:30-45` + `crtpi_pipeline.cpp:45-62` | defaults CRT-Pi triples | Importante | DONE | `CrtPiUniforms{}` value-init; init() aplica `CrtPiParams{}` com a única font |
|
||||||
|
|
||||||
|
## 2. Hardcoding
|
||||||
|
|
||||||
|
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|
||||||
|
|----|---------------|----------|-----------|--------|-------|
|
||||||
|
| HC-01 | `text.cpp:159` | `render(1, 15)` — `1` (tinta) y `15` (color por defecto) literales | Importante | DONE | `Text::INK_COLOR_INDEX` i `DEFAULT_TEXT_COLOR` |
|
||||||
|
| HC-02 | `surface.cpp:29`, `screen.cpp:56,218,244` | `0xFF000000U`/`0xFF` alfa repetido | Menor | DONE | `Rendering::OPAQUE_ALPHA` a `canvas.hpp` |
|
||||||
|
| HC-03 | `gif.cpp:71,95,…` | LZW `<11`/`<12`/`<2` y máscaras `0x80/0x70/0x07…` sin nombre | Menor | DONE | `LZW_MIN/MAX_CODE_LENGTH`, `SCREEN_GCT_FLAG`, `SCREEN_GCT_SIZE_MASK`, etc. |
|
||||||
|
| HC-04 | `notifier.hpp:41-46`, `render_info.hpp:38-40` | HUD constants hardcoded en clase, no en `defaults`/YAML | Importante | DEFERRED | Moure a YAML és una feature; de moment consolidades a `hud_constants.hpp` (via HC-05) |
|
||||||
|
| HC-05 | `render_info.cpp:19` + `notifier.hpp:43` | `TEXT_SIZE=8` duplicado | Menor | DONE | `Rendering::Hud::FONT_SIZE_PX` a `hud_constants.hpp` |
|
||||||
|
| HC-06 | `render_info.cpp:64,138-139,193-195` | `segments_[3]` hardcode del segmento tiempo | Importante | DONE | constants `FPS/DRIVER/SHADER/TIME_SEGMENT` |
|
||||||
|
| HC-07 | `render_info.cpp:207` | `% 3` hardcode nº de entradas enum | Menor | DONE | `Defaults::RENDER_INFO_POSITION_COUNT` |
|
||||||
|
| HC-08 | `crtpi_pipeline.cpp:97` | `768.0f / h / 3.0f` magic en shader | Menor | DONE | `CRT_REFERENCE_HEIGHT` / `SUBPIXEL_DIV` a l'MSL amb explicació |
|
||||||
|
| HC-09 | `render_info.cpp:179-183,194-195` | shadow offset +1,+1 magic | Menor | DONE | `SHADOW_OFFSET_PX` |
|
||||||
|
| HC-10 | `gif.cpp:162` | `processImageDescriptor` params `[[maybe_unused]]` vestigiales | Menor | DONE | signatura sense `gct` ni `resolution_bits`; `RGB` també esborrat |
|
||||||
|
|
||||||
|
## 3. Parches e improvisaciones
|
||||||
|
|
||||||
|
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|
||||||
|
|----|---------------|----------|-----------|--------|-------|
|
||||||
|
| PATCH-01 | `screen.cpp:71-92,210-214`, `sdl3gpu_shader.cpp:445-493` | workarounds Wayland/Vulkan | Importante | WONTFIX (de momento) | RAII para forzar orden — deferred |
|
||||||
|
| PATCH-02 | `render_info.cpp:85-88` | guard bug-fix animation | Menor | DEFERRED | El guard és ara la única línia "sense refactor" de l'animació; una màquina d'estats neta és feature, no cleanup |
|
||||||
|
| PATCH-03 | `notifier.cpp:78-85` | TODO implícito de cola FIFO | Menor | WONTFIX | documentar decisión |
|
||||||
|
| PATCH-04 | `sprite.cpp:47-49` | `Sprite::update` vacío hook no invocado | Menor | DONE | resolt amb DEAD-10 |
|
||||||
|
| PATCH-05 | `gif.cpp:12-15` | comentario "reemplaçar macro READ" | Menor | DONE | comentari neutre nou |
|
||||||
|
| PATCH-06 | `gif.cpp:83-86` | guard self-reference defensivo | Menor | WONTFIX | dejar, cambiar `cerr` → logger futuro |
|
||||||
|
| PATCH-07 | `sdl3gpu_shader.cpp:17,162` + `crtpi_pipeline.cpp:16,162` | `NOLINTBEGIN` gigantes sobre MSL | Importante | DONE | eliminats amb CONV-03 |
|
||||||
|
| PATCH-08 | `render_info.cpp:64,138-139` | `mono_digits=true` asignado 2 veces | Menor | DONE | inicialització única via refreshSegments |
|
||||||
|
|
||||||
|
## 4. Acoplamiento innecesario
|
||||||
|
|
||||||
|
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|
||||||
|
|----|---------------|----------|-----------|--------|-------|
|
||||||
|
| COUP-01a | `notifier.hpp:9` | `core/rendering/notifier.hpp` incluye `game/ui/notification_presets.hpp` | Crítico | DONE | helper `Notification::notify()` en `game/ui` |
|
||||||
|
| COUP-01b | `render_info.cpp:12-13` | `core/rendering/render_info.cpp` incluye `game/options.hpp` + `game/ui/ui_palette.hpp` | Crítico | DONE | `RenderInfo::Frame` injectada; Options.buildRenderInfoFrame() |
|
||||||
|
| COUP-02a | `screen.cpp:13-14,109` | `Screen` instancia `SDL3GPUShader` concreto | Importante | DONE | `Rendering::makeDefaultShaderBackend()` factory |
|
||||||
|
| COUP-03 | `surface.cpp:81-83,110-112` | `Surface::render` usa `Screen::get()` global | Importante | DONE | `Rendering::activeCanvas()` intermediari |
|
||||||
|
| COUP-04 | `notifier.cpp:19`, `render_info.cpp:52` | `Resource::Cache::get().getSurface("8bithud.gif")` con string literal | Importante | DONE | Director construeix un `shared_ptr<Text>` i l'injecta a tots dos; Notifier també rep `Palette ui_palette` |
|
||||||
|
| COUP-05 | `animated_sprite.cpp:9,95-116` | parser YAML incrustado en sprite | Importante | DEFERRED | Requereix crear `AnimationLoader` a `core/resources` i reescriure el ctor; fora del scope de neteja d'aquesta auditoria |
|
||||||
|
| COUP-06 | `notifier.hpp:22`, `render_info.hpp:17`, `screen.hpp:13` | 3 Meyers singletons en mismo subsistema | Importante | DEFERRED | requiere tocar Director |
|
||||||
|
|
||||||
|
## 5. Inconsistencias internas
|
||||||
|
|
||||||
|
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|
||||||
|
|----|---------------|----------|-----------|--------|-------|
|
||||||
|
| INC-01 | todo el subsistema | smart pointers mezclados (shared/unique/raw sin política) | Importante | DEFERRED | requiere decisión de política |
|
||||||
|
| INC-02 | `screen.cpp:52,104,125` vs `gif.cpp:96` vs `sdl3gpu_shader.cpp:214` | `exit`/throw/`bool` mezclados | Importante | DEFERRED | decisión global |
|
||||||
|
| INC-03 | `screen.cpp`, `sdl3gpu_shader.cpp`, `surface.cpp`, `gif.cpp` | `fprintf`/`cerr`/`cout` mezclados | Menor | DEFERRED | logger inexistente |
|
||||||
|
| INC-04 | `text.hpp:48-57` | API discreta + `writeDX(flags)` coexisten | Importante | WONTFIX | `writeDX` és un orquestrador prim sobre `write`/`writeColored`; Render_info usa la via directa, Notifier la via flags. Coexistència justificada, no doblegat. |
|
||||||
|
| INC-05 | `moving_sprite.cpp:39-40,58-72` | `pos_.x = static_cast<int>(x_)` sobre `SDL_FRect` | Importante | WONTFIX | És snap intencional a píxel per al pixel-art; setPos(SDL_FRect) es reserva per a posicionament directe sense arrodoniment. La divergència és una decisió, no un bug |
|
||||||
|
| INC-06 | `moving_sprite.cpp:12-21` | `Sprite::pos_ = pos;` redundante tras ctor delegado | Menor | DONE | línies eliminades |
|
||||||
|
| INC-07 | `sprite.cpp:19-22` | initializer posicional vs designated | Menor | DONE | designated initializer uniforme |
|
||||||
|
|
||||||
|
## 6. Código muerto
|
||||||
|
|
||||||
|
| ID | Fichero:línea | Hallazgo | Severidad | Estado | Notas |
|
||||||
|
|----|---------------|----------|-----------|--------|-------|
|
||||||
|
| DEAD-01 | `text.hpp:50-51` | `writeShadowed`, `writeCentered` sin callers externos | Importante | DONE | esborrats |
|
||||||
|
| DEAD-02 | `text.hpp:62-63,67` | `glyphWidth`, `getGlyphClip`, `codepointToUtf8` sin callers | Importante | DONE | esborrats |
|
||||||
|
| DEAD-03 | `text.hpp:64` | `getSprite()` sin callers | Menor | DONE | esborrat amb DEAD-01/02 |
|
||||||
|
| DEAD-04 | `sprite.hpp:33` | `getRect()` sin callers | Menor | DONE | esborrat |
|
||||||
|
| DEAD-05 | `sprite.hpp:42-43` | `incX`, `incY` sin callers | Menor | DONE | esborrats |
|
||||||
|
| DEAD-06 | `sprite.hpp:12`, `moving_sprite.hpp:13` | ctors default sin callers | Menor | WONTFIX | usats transitivament pel ctor de `AnimatedSprite` (base-class-construction) |
|
||||||
|
| DEAD-07 | `animated_sprite.hpp:30` | `getCurrentAnimationSize()` sin callers | Menor | DONE | esborrat |
|
||||||
|
| DEAD-08 | `surface.hpp:61` | `getSurfaceData()` sin callers externos | Menor | DONE | esborrat |
|
||||||
|
| DEAD-09 | `gif.hpp:45-65` | `Extension`, `ApplicationExtension`, `PlaintextExtension`, `GraphicControlExtension` no instanciados | Menor | DONE | esborrats (el parser processa bytes cru) |
|
||||||
|
| DEAD-10 | `sprite.cpp:47-49` | `Sprite::update` hook no invocado | Menor | DONE | inlined buit al header (resol PATCH-04) |
|
||||||
|
|
||||||
|
## 7. Violaciones de convenciones del proyecto
|
||||||
|
|
||||||
|
| ID | Hallazgo | Cruza con | Estado |
|
||||||
|
|----|----------|-----------|--------|
|
||||||
|
| CONV-01 | Magic numbers vs "sin magic numbers" | HC-* | cubierto por HC |
|
||||||
|
| CONV-02 | HUD hardcoded vs "configurabilidad desde ficheros" | HC-04 | cubierto por HC-04 |
|
||||||
|
| CONV-03 | Shaders MSL como raw strings en `.cpp` | DUP-04/05, PATCH-07 | DONE |
|
||||||
|
| CONV-04 | Singletons vs "interfaces + callbacks" | COUP-06 | DEFERRED |
|
||||||
|
| CONV-05 | `core → game` deps | COUP-01 | cubierto por COUP-01 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Registro de commits
|
||||||
|
|
||||||
|
- `9243db1` — docs: full de treball inicial (aquest fitxer)
|
||||||
|
- `9434e4e` — COUP-01a: `Notifier` desacoblat de `game/ui/notification_presets`
|
||||||
|
- `9395520` — COUP-01b: `RenderInfo` desacoblat de `game/options` + `game/ui/ui_palette`
|
||||||
|
- `d479475` — DUP-02: `Surface::render` delega a `renderWithColorReplace`
|
||||||
|
- `5f46fb2` — CONV-03 + DUP-04 + DUP-05 + PATCH-07: shaders MSL i helpers extrets a `sdl3gpu/msl/` i `sdl3gpu/shader_factory.hpp`
|
||||||
|
- `a63deac` — DUP-03 + HC-01: `Text` amb `resolveGlyph` i constants `INK_COLOR_INDEX` / `DEFAULT_TEXT_COLOR`
|
||||||
|
- `029907d` — COUP-02a: `Screen` desacoblat de `SDL3GPUShader` via `makeDefaultShaderBackend()`
|
||||||
|
- `81fe826` — COUP-02b (= COUP-03): `Surface` usa `Rendering::activeCanvas()` en lloc de `Screen::get()`
|
||||||
|
- `b51109e` — DEAD-01 + DEAD-02 + DEAD-03: esborrats mètodes mort de `Text`
|
||||||
|
- `597402a` — DEAD-04/05/07/08/10 + PATCH-04: neteja mort a `Sprite`/`AnimatedSprite`/`Surface` + `Sprite::update` inline
|
||||||
|
- `ce3b6da` — DEAD-09: esborrats structs d'extensions GIF no usats
|
||||||
|
- `bbb397a` — HC-06 + HC-07 + HC-09 + PATCH-08: constants nominals a `render_info` (segments, shadow offset, position count) i dedup init
|
||||||
|
- `be94a43` — DUP-01 + DUP-06 + HC-03 + HC-10 + PATCH-05: neteja parser GIF (extension single-skip, constants nominals LZW/GCT, signatura sense args morts, un sol parse per construcció de `Surface`)
|
||||||
|
- `8b413af` — HC-02: `Rendering::OPAQUE_ALPHA` a `canvas.hpp` reemplaça `0xFF000000U` repetit
|
||||||
|
- `da66a7c` — INC-06 + INC-07: eliminats assignaments redundants `Sprite::pos_` a `MovingSprite`; designated initializer uniforme
|
||||||
|
- `2350f40` — HC-08: constants nominals a l'MSL de CRT-Pi amb explicació del 768/3
|
||||||
|
- `e1e8bc1` — DUP-07: `CrtPiParams{}` com a única font de veritat dels defaults de CRT-Pi
|
||||||
|
- `f692739` — HC-05: `Rendering::Hud::FONT_SIZE_PX` a `hud_constants.hpp` (consolida `TEXT_SIZE=8`)
|
||||||
|
- `<pendent>` — COUP-04 + tancament: injecció de `shared_ptr<Text>` i `Palette` als overlays des del Director; estat final de l'auditoria documentat
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
|
#include "game/ui/notification_presets.hpp"
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
|
|
||||||
namespace GlobalInputs {
|
namespace GlobalInputs {
|
||||||
@@ -37,7 +38,7 @@ namespace GlobalInputs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void notifyLocale(const std::string& key) {
|
void notifyLocale(const std::string& key) {
|
||||||
Notifier::get().notify(Locale::get().text(key));
|
Notification::notify(Locale::get().text(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDecZoom() {
|
void onDecZoom() {
|
||||||
@@ -46,7 +47,7 @@ namespace GlobalInputs {
|
|||||||
Options::window.zoom--;
|
Options::window.zoom--;
|
||||||
Screen::get().setWindowZoom(Options::window.zoom);
|
Screen::get().setWindowZoom(Options::window.zoom);
|
||||||
Options::saveToFile();
|
Options::saveToFile();
|
||||||
Notifier::get().notify(Locale::get().text("input.zoom") + " x" + std::to_string(Options::window.zoom));
|
Notification::notify(Locale::get().text("input.zoom") + " x" + std::to_string(Options::window.zoom));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onIncZoom() {
|
void onIncZoom() {
|
||||||
@@ -55,7 +56,7 @@ namespace GlobalInputs {
|
|||||||
Options::window.zoom++;
|
Options::window.zoom++;
|
||||||
Screen::get().setWindowZoom(Options::window.zoom);
|
Screen::get().setWindowZoom(Options::window.zoom);
|
||||||
Options::saveToFile();
|
Options::saveToFile();
|
||||||
Notifier::get().notify(Locale::get().text("input.zoom") + " x" + std::to_string(Options::window.zoom));
|
Notification::notify(Locale::get().text("input.zoom") + " x" + std::to_string(Options::window.zoom));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFullscreen() {
|
void onFullscreen() {
|
||||||
@@ -83,7 +84,7 @@ namespace GlobalInputs {
|
|||||||
Options::video.shader.current_shader = (Options::video.shader.current_shader == "crtpi") ? "postfx" : "crtpi";
|
Options::video.shader.current_shader = (Options::video.shader.current_shader == "crtpi") ? "postfx" : "crtpi";
|
||||||
reapplyShader();
|
reapplyShader();
|
||||||
Options::saveToFile();
|
Options::saveToFile();
|
||||||
Notifier::get().notify(Locale::get().text("input.shader") + ": " + Options::video.shader.current_shader);
|
Notification::notify(Locale::get().text("input.shader") + ": " + Options::video.shader.current_shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Busca l'entrada actual al vector de presets i passa a la següent
|
// Busca l'entrada actual al vector de presets i passa a la següent
|
||||||
@@ -108,7 +109,7 @@ namespace GlobalInputs {
|
|||||||
reapplyShader();
|
reapplyShader();
|
||||||
Options::saveToFile();
|
Options::saveToFile();
|
||||||
const std::string& name = IS_CRTPI ? Options::video.shader.current_crtpi_preset : Options::video.shader.current_postfx_preset;
|
const std::string& name = IS_CRTPI ? Options::video.shader.current_crtpi_preset : Options::video.shader.current_postfx_preset;
|
||||||
Notifier::get().notify(Locale::get().text("input.preset") + ": " + name);
|
Notification::notify(Locale::get().text("input.preset") + ": " + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onNextScalingMode() {
|
void onNextScalingMode() {
|
||||||
@@ -138,11 +139,14 @@ namespace GlobalInputs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onCycleRenderInfo() {
|
void onCycleRenderInfo() {
|
||||||
RenderInfo::cyclePosition();
|
const int NEXT = (static_cast<int>(Options::video.render_info_position) + 1) % Defaults::RENDER_INFO_POSITION_COUNT;
|
||||||
|
Options::video.render_info_position = static_cast<Defaults::RenderInfoPosition>(NEXT);
|
||||||
|
Options::saveToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onToggleRenderTime() {
|
void onToggleRenderTime() {
|
||||||
RenderInfo::toggleShowTime();
|
Options::video.render_info_show_time = !Options::video.render_info_show_time;
|
||||||
|
Options::saveToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onExit() {
|
void onExit() {
|
||||||
@@ -151,7 +155,7 @@ namespace GlobalInputs {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
exit_window_s = EXIT_WINDOW_S;
|
exit_window_s = EXIT_WINDOW_S;
|
||||||
Notifier::get().notify(Locale::get().text("input.press_again_exit"), Notification::EXIT);
|
Notification::notify(Locale::get().text("input.press_again_exit"), Notification::EXIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include "core/rendering/canvas.hpp"
|
||||||
|
|
||||||
|
#include "core/rendering/screen.hpp"
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
auto activeCanvas() -> Canvas {
|
||||||
|
auto& screen = Screen::get();
|
||||||
|
return {
|
||||||
|
.pixels = screen.getCanvasArgb(),
|
||||||
|
.width = screen.getCanvasWidth(),
|
||||||
|
.height = screen.getCanvasHeight(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// Abstracció mínima del destí de renderitzat CPU. Tots els blitters del
|
||||||
|
// projecte (Surface, els HUDs via Screen::fillRect, ...) emeten píxels en
|
||||||
|
// format ARGB8888 sobre una única superfície lògica que el backend GPU
|
||||||
|
// després transfereix a la textura "scene". L'accés va per la funció
|
||||||
|
// activeCanvas() en lloc de Screen::get() directament, per a que els
|
||||||
|
// consumidors no depenguen del cicle de vida de Screen ni del seu header.
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
// Bit alpha completament opac, ja preposicionat al canal alt del
|
||||||
|
// píxel ARGB8888. Qualsevol RGB | OPAQUE_ALPHA produeix el píxel final.
|
||||||
|
inline constexpr std::uint32_t OPAQUE_ALPHA = 0xFF000000U;
|
||||||
|
|
||||||
|
struct Canvas {
|
||||||
|
std::uint32_t* pixels{nullptr};
|
||||||
|
int width{0};
|
||||||
|
int height{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Retorna el canvas actiu del joc. En aquesta fase del projecte n'hi ha
|
||||||
|
// sempre un i només un (el de Screen); en el futur, si cal renderitzar
|
||||||
|
// off-screen (miniatures, efectes), es podrà pushejar/popar per ací.
|
||||||
|
[[nodiscard]] auto activeCanvas() -> Canvas;
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
@@ -1,19 +1,46 @@
|
|||||||
#include "core/rendering/gif.hpp"
|
#include "core/rendering/gif.hpp"
|
||||||
|
|
||||||
#include <cstring> // Para memcpy, size_t
|
#include <cstring>
|
||||||
#include <iostream> // Para std::cout
|
#include <iostream>
|
||||||
#include <stdexcept> // Para runtime_error
|
#include <stdexcept>
|
||||||
#include <string> // Para allocator, char_traits, operator==, basic_string
|
#include <string>
|
||||||
|
|
||||||
namespace GIF {
|
namespace GIF {
|
||||||
|
|
||||||
// Función inline para reemplazar el macro READ.
|
namespace {
|
||||||
// Actualiza el puntero 'buffer' tras copiar 'size' bytes a 'dst'.
|
// Paràmetres del Screen Descriptor (byte de flags).
|
||||||
|
constexpr uint8_t SCREEN_GCT_FLAG = 0x80;
|
||||||
|
constexpr uint8_t SCREEN_GCT_SIZE_MASK = 0x07;
|
||||||
|
|
||||||
|
// Paràmetres del decompressor LZW.
|
||||||
|
constexpr int LZW_MIN_CODE_LENGTH = 2;
|
||||||
|
constexpr int LZW_MAX_CODE_LENGTH = 12;
|
||||||
|
constexpr unsigned int LZW_MASK_INITIAL = 0x01;
|
||||||
|
constexpr unsigned int LZW_MASK_OVERFLOW = 0x100;
|
||||||
|
|
||||||
|
// Mida de l'RGB de la GCT (bytes per entrada).
|
||||||
|
constexpr int GCT_BYTES_PER_ENTRY = 3;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Copia 'size' bytes del buffer a 'dst' avançant el punter del buffer.
|
||||||
inline void readBytes(const uint8_t*& buffer, void* dst, size_t size) {
|
inline void readBytes(const uint8_t*& buffer, void* dst, size_t size) {
|
||||||
std::memcpy(dst, buffer, size);
|
std::memcpy(dst, buffer, size);
|
||||||
buffer += size;
|
buffer += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Salta un bloc d'extensió: byte de mida + contingut fix + sub-blocs
|
||||||
|
// acabats amb mida 0. Totes les extensions del GIF (graphic control,
|
||||||
|
// application, comment, plaintext, ...) comparteixen aquest layout.
|
||||||
|
inline void skipExtensionBlock(const uint8_t*& buffer) {
|
||||||
|
uint8_t block_size = *buffer++;
|
||||||
|
buffer += block_size;
|
||||||
|
uint8_t sub_block_size = *buffer++;
|
||||||
|
while (sub_block_size != 0) {
|
||||||
|
buffer += sub_block_size;
|
||||||
|
sub_block_size = *buffer++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Inicializa el diccionario LZW con los valores iniciales
|
// Inicializa el diccionario LZW con los valores iniciales
|
||||||
inline void initializeDictionary(std::vector<DictionaryEntry>& dictionary, int code_length, int& dictionary_ind) { // NOLINT(readability-identifier-naming)
|
inline void initializeDictionary(std::vector<DictionaryEntry>& dictionary, int code_length, int& dictionary_ind) { // NOLINT(readability-identifier-naming)
|
||||||
int size = 1 << code_length;
|
int size = 1 << code_length;
|
||||||
@@ -35,8 +62,8 @@ namespace GIF {
|
|||||||
}
|
}
|
||||||
int bit = ((*input & mask) != 0) ? 1 : 0;
|
int bit = ((*input & mask) != 0) ? 1 : 0;
|
||||||
mask <<= 1;
|
mask <<= 1;
|
||||||
if (mask == 0x100) {
|
if (mask == LZW_MASK_OVERFLOW) {
|
||||||
mask = 0x01;
|
mask = LZW_MASK_INITIAL;
|
||||||
input++;
|
input++;
|
||||||
input_length--;
|
input_length--;
|
||||||
}
|
}
|
||||||
@@ -68,7 +95,10 @@ namespace GIF {
|
|||||||
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
|
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
|
||||||
dictionary_ind++;
|
dictionary_ind++;
|
||||||
|
|
||||||
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
|
// Només podem créixer mentre no hagem arribat al màxim del decoder
|
||||||
|
// (12 bits); el `- 1` compensa el +1 que aplicarem tot seguit per
|
||||||
|
// a que code_length no sobrepasse LZW_MAX_CODE_LENGTH.
|
||||||
|
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < LZW_MAX_CODE_LENGTH - 1)) {
|
||||||
code_length++;
|
code_length++;
|
||||||
dictionary.resize(1 << (code_length + 1));
|
dictionary.resize(1 << (code_length + 1));
|
||||||
}
|
}
|
||||||
@@ -92,14 +122,14 @@ namespace GIF {
|
|||||||
|
|
||||||
void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) {
|
void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) {
|
||||||
// Verifica que el code_length tenga un rango razonable.
|
// Verifica que el code_length tenga un rango razonable.
|
||||||
if (code_length < 2 || code_length > 12) {
|
if (code_length < LZW_MIN_CODE_LENGTH || code_length > LZW_MAX_CODE_LENGTH) {
|
||||||
throw std::runtime_error("Invalid LZW code length");
|
throw std::runtime_error("Invalid LZW code length");
|
||||||
}
|
}
|
||||||
|
|
||||||
int prev = -1;
|
int prev = -1;
|
||||||
std::vector<DictionaryEntry> dictionary;
|
std::vector<DictionaryEntry> dictionary;
|
||||||
int dictionary_ind;
|
int dictionary_ind;
|
||||||
unsigned int mask = 0x01;
|
unsigned int mask = LZW_MASK_INITIAL;
|
||||||
int reset_code_length = code_length;
|
int reset_code_length = code_length;
|
||||||
int clear_code = 1 << code_length;
|
int clear_code = 1 << code_length;
|
||||||
int stop_code = clear_code + 1;
|
int stop_code = clear_code + 1;
|
||||||
@@ -123,7 +153,7 @@ namespace GIF {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev > -1 && code_length < 12) {
|
if (prev > -1 && code_length < LZW_MAX_CODE_LENGTH) {
|
||||||
if (code > dictionary_ind) {
|
if (code > dictionary_ind) {
|
||||||
std::cerr << "code = " << std::hex << code
|
std::cerr << "code = " << std::hex << code
|
||||||
<< ", but dictionary_ind = " << dictionary_ind << '\n';
|
<< ", but dictionary_ind = " << dictionary_ind << '\n';
|
||||||
@@ -159,7 +189,7 @@ namespace GIF {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Gif::processImageDescriptor(const uint8_t*& buffer, [[maybe_unused]] const std::vector<RGB>& gct, [[maybe_unused]] int resolution_bits) -> std::vector<uint8_t> {
|
auto Gif::processImageDescriptor(const uint8_t*& buffer) -> std::vector<uint8_t> {
|
||||||
ImageDescriptor image_descriptor;
|
ImageDescriptor image_descriptor;
|
||||||
// Lee 9 bytes para el image descriptor.
|
// Lee 9 bytes para el image descriptor.
|
||||||
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
||||||
@@ -185,15 +215,15 @@ namespace GIF {
|
|||||||
buffer += sizeof(ScreenDescriptor);
|
buffer += sizeof(ScreenDescriptor);
|
||||||
|
|
||||||
std::vector<uint32_t> global_color_table;
|
std::vector<uint32_t> global_color_table;
|
||||||
if ((screen_descriptor.fields & 0x80) != 0) {
|
if ((screen_descriptor.fields & SCREEN_GCT_FLAG) != 0) {
|
||||||
int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
|
int global_color_table_size = 1 << ((screen_descriptor.fields & SCREEN_GCT_SIZE_MASK) + 1);
|
||||||
global_color_table.resize(global_color_table_size);
|
global_color_table.resize(global_color_table_size);
|
||||||
for (int i = 0; i < global_color_table_size; ++i) {
|
for (int i = 0; i < global_color_table_size; ++i) {
|
||||||
uint8_t r = buffer[0];
|
uint8_t r = buffer[0];
|
||||||
uint8_t g = buffer[1];
|
uint8_t g = buffer[1];
|
||||||
uint8_t b = buffer[2];
|
uint8_t b = buffer[2];
|
||||||
global_color_table[i] = (r << 16) | (g << 8) | b;
|
global_color_table[i] = (r << 16) | (g << 8) | b;
|
||||||
buffer += 3;
|
buffer += GCT_BYTES_PER_ENTRY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return global_color_table;
|
return global_color_table;
|
||||||
@@ -205,79 +235,34 @@ namespace GIF {
|
|||||||
std::memcpy(header, buffer, 6);
|
std::memcpy(header, buffer, 6);
|
||||||
buffer += 6;
|
buffer += 6;
|
||||||
|
|
||||||
// Opcional: Validar header
|
|
||||||
std::string header_str(reinterpret_cast<char*>(header), 6);
|
std::string header_str(reinterpret_cast<char*>(header), 6);
|
||||||
if (header_str != "GIF87a" && header_str != "GIF89a") {
|
if (header_str != "GIF87a" && header_str != "GIF89a") {
|
||||||
throw std::runtime_error("Formato de archivo GIF inválido.");
|
throw std::runtime_error("Formato de archivo GIF inválido.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leer el Screen Descriptor (7 bytes, empaquetado sin padding)
|
// Screen Descriptor (7 bytes, empaquetado sin padding).
|
||||||
ScreenDescriptor screen_descriptor;
|
ScreenDescriptor screen_descriptor;
|
||||||
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
||||||
|
|
||||||
// Asigna ancho y alto
|
|
||||||
w = screen_descriptor.width;
|
w = screen_descriptor.width;
|
||||||
h = screen_descriptor.height;
|
h = screen_descriptor.height;
|
||||||
|
|
||||||
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
// Saltar la Global Color Table si existeix; els colors els extrau
|
||||||
std::vector<RGB> global_color_table;
|
// loadPalette() en una passada separada sobre el mateix buffer.
|
||||||
if ((screen_descriptor.fields & 0x80) != 0) {
|
if ((screen_descriptor.fields & SCREEN_GCT_FLAG) != 0) {
|
||||||
int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
|
const int GCT_SIZE = 1 << ((screen_descriptor.fields & SCREEN_GCT_SIZE_MASK) + 1);
|
||||||
global_color_table.resize(global_color_table_size);
|
buffer += static_cast<ptrdiff_t>(GCT_BYTES_PER_ENTRY) * GCT_SIZE;
|
||||||
std::memcpy(global_color_table.data(), buffer, static_cast<size_t>(3) * static_cast<size_t>(global_color_table_size));
|
|
||||||
buffer += static_cast<ptrdiff_t>(3) * global_color_table_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B
|
// Recorregut de blocs fins al TRAILER (0x3B). Les extensions comparteixen
|
||||||
|
// layout (mida + contingut fix + sub-blocs), per tant totes les saltem
|
||||||
|
// igual — no cal distingir per extension_label.
|
||||||
uint8_t block_type = *buffer++;
|
uint8_t block_type = *buffer++;
|
||||||
while (block_type != TRAILER) {
|
while (block_type != TRAILER) {
|
||||||
if (block_type == EXTENSION_INTRODUCER) // 0x21
|
if (block_type == EXTENSION_INTRODUCER) {
|
||||||
{
|
++buffer; // extension_label (ignorat)
|
||||||
// Se lee la etiqueta de extensión, la cual indica el tipo de extensión.
|
skipExtensionBlock(buffer);
|
||||||
uint8_t extension_label = *buffer++;
|
|
||||||
switch (extension_label) {
|
|
||||||
case GRAPHIC_CONTROL: // 0xF9
|
|
||||||
{
|
|
||||||
// Procesar Graphic Control Extension:
|
|
||||||
uint8_t block_size = *buffer++; // Normalmente, blockSize == 4
|
|
||||||
buffer += block_size; // Saltamos los 4 bytes del bloque fijo
|
|
||||||
// Saltar los sub-bloques
|
|
||||||
uint8_t sub_block_size = *buffer++;
|
|
||||||
while (sub_block_size != 0) {
|
|
||||||
buffer += sub_block_size;
|
|
||||||
sub_block_size = *buffer++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case APPLICATION_EXTENSION: // 0xFF
|
|
||||||
case COMMENT_EXTENSION: // 0xFE
|
|
||||||
case PLAINTEXT_EXTENSION: // 0x01
|
|
||||||
{
|
|
||||||
// Para estas extensiones, saltamos el bloque fijo y los sub-bloques.
|
|
||||||
uint8_t block_size = *buffer++;
|
|
||||||
buffer += block_size;
|
|
||||||
uint8_t sub_block_size = *buffer++;
|
|
||||||
while (sub_block_size != 0) {
|
|
||||||
buffer += sub_block_size;
|
|
||||||
sub_block_size = *buffer++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
// Si la etiqueta de extensión es desconocida, saltarla también:
|
|
||||||
uint8_t block_size = *buffer++;
|
|
||||||
buffer += block_size;
|
|
||||||
uint8_t sub_block_size = *buffer++;
|
|
||||||
while (sub_block_size != 0) {
|
|
||||||
buffer += sub_block_size;
|
|
||||||
sub_block_size = *buffer++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (block_type == IMAGE_DESCRIPTOR) {
|
} else if (block_type == IMAGE_DESCRIPTOR) {
|
||||||
// Procesar el Image Descriptor y retornar los datos de imagen
|
return processImageDescriptor(buffer);
|
||||||
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "Unrecognized block type " << std::hex << static_cast<int>(block_type) << '\n';
|
std::cerr << "Unrecognized block type " << std::hex << static_cast<int>(block_type) << '\n';
|
||||||
return std::vector<uint8_t>{};
|
return std::vector<uint8_t>{};
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ namespace GIF {
|
|||||||
uint8_t pixel_aspect_ratio;
|
uint8_t pixel_aspect_ratio;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RGB {
|
|
||||||
uint8_t r, g, b;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ImageDescriptor {
|
struct ImageDescriptor {
|
||||||
uint16_t image_left_position;
|
uint16_t image_left_position;
|
||||||
uint16_t image_top_position;
|
uint16_t image_top_position;
|
||||||
@@ -42,28 +38,6 @@ namespace GIF {
|
|||||||
int len;
|
int len;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Extension {
|
|
||||||
uint8_t extension_code;
|
|
||||||
uint8_t block_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GraphicControlExtension {
|
|
||||||
uint8_t fields;
|
|
||||||
uint16_t delay_time;
|
|
||||||
uint8_t transparent_color_index;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ApplicationExtension {
|
|
||||||
uint8_t application_id[8];
|
|
||||||
uint8_t version[3];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PlaintextExtension {
|
|
||||||
uint16_t left, top, width, height;
|
|
||||||
uint8_t cell_width, cell_height;
|
|
||||||
uint8_t foreground_color, background_color;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Gif {
|
class Gif {
|
||||||
public:
|
public:
|
||||||
// Descompone (uncompress) el bloque comprimido usando LZW.
|
// Descompone (uncompress) el bloque comprimido usando LZW.
|
||||||
@@ -83,7 +57,7 @@ namespace GIF {
|
|||||||
static auto readSubBlocks(const uint8_t*& buffer) -> std::vector<uint8_t>;
|
static auto readSubBlocks(const uint8_t*& buffer) -> std::vector<uint8_t>;
|
||||||
|
|
||||||
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
||||||
static auto processImageDescriptor(const uint8_t*& buffer, const std::vector<RGB>& gct, int resolution_bits) -> std::vector<uint8_t>;
|
static auto processImageDescriptor(const uint8_t*& buffer) -> std::vector<uint8_t>;
|
||||||
|
|
||||||
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
||||||
static auto processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector<uint8_t>;
|
static auto processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector<uint8_t>;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Constants compartides per als overlays HUD del projecte (Notifier,
|
||||||
|
// RenderInfo, ...). Són compile-time i depenen de la geometria del bitmap
|
||||||
|
// font 8bithud; en una fase futura es podran fer runtime llegint el .fnt,
|
||||||
|
// però de moment evitar duplicar-les ja és guany net.
|
||||||
|
namespace Rendering::Hud {
|
||||||
|
|
||||||
|
// Alçada del glif de la font 8bithud en píxels del canvas. Coincideix
|
||||||
|
// amb box_height al .fnt i s'usa com a base de càlcul d'alçades HUD.
|
||||||
|
constexpr int FONT_SIZE_PX = 8;
|
||||||
|
|
||||||
|
} // namespace Rendering::Hud
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#include "core/rendering/notifier.hpp"
|
#include "core/rendering/notifier.hpp"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/surface.hpp"
|
|
||||||
#include "core/rendering/text.hpp"
|
#include "core/rendering/text.hpp"
|
||||||
#include "core/resources/resource_cache.hpp"
|
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
auto Notifier::get() -> Notifier& {
|
auto Notifier::get() -> Notifier& {
|
||||||
@@ -13,11 +13,9 @@ auto Notifier::get() -> Notifier& {
|
|||||||
|
|
||||||
Notifier::~Notifier() = default;
|
Notifier::~Notifier() = default;
|
||||||
|
|
||||||
void Notifier::init() {
|
void Notifier::init(std::shared_ptr<Text> text, const Palette& ui_palette) {
|
||||||
// El Director preloade la paleta "ui" i la surface "8bithud.gif" abans
|
text_ = std::move(text);
|
||||||
// de cridar aquest init(); ací només cal construir el Text.
|
ui_palette_ = ui_palette;
|
||||||
auto font_surface = Resource::Cache::get().getSurface("8bithud.gif");
|
|
||||||
text_ = std::make_unique<Text>(font_surface, "data/font/8bithud.fnt");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Notifier::update(int delta_ms) {
|
void Notifier::update(int delta_ms) {
|
||||||
@@ -54,10 +52,9 @@ void Notifier::render() {
|
|||||||
const int BOX_X = (GameCanvas::WIDTH - BOX_W) / 2;
|
const int BOX_X = (GameCanvas::WIDTH - BOX_W) / 2;
|
||||||
const int BOX_Y = static_cast<int>(y_);
|
const int BOX_Y = static_cast<int>(y_);
|
||||||
|
|
||||||
// Fons sòlid a través de la paleta "ui" (ARGB directe al canvas, sense
|
// Fons sòlid amb la paleta UI que ens ha injectat el caller a init()
|
||||||
// tocar cap surface paletada).
|
// (ARGB directe al canvas, sense tocar cap surface paletada).
|
||||||
const auto& ui_palette = Resource::Cache::get().getPalette("ui");
|
Screen::get().fillRect(BOX_X, BOX_Y, BOX_W, HEIGHT, ui_palette_[current_bg_color_]);
|
||||||
Screen::get().fillRect(BOX_X, BOX_Y, BOX_W, HEIGHT, ui_palette[current_bg_color_]);
|
|
||||||
|
|
||||||
text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
text_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||||
GameCanvas::CENTER_X,
|
GameCanvas::CENTER_X,
|
||||||
@@ -67,14 +64,6 @@ void Notifier::render() {
|
|||||||
current_text_color_);
|
current_text_color_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Notifier::notify(const std::string& text) {
|
|
||||||
notify(text, Notification::DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Notifier::notify(const std::string& text, const Notification::Preset& preset) {
|
|
||||||
notify(text, preset.text_color, preset.bg_color, preset.time_ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Notifier::notify(const std::string& text, Uint8 text_color, Uint8 bg_color, int time_ms) {
|
void Notifier::notify(const std::string& text, Uint8 text_color, Uint8 bg_color, int time_ms) {
|
||||||
// Política simple: la nova notificació reemplaça la que hi havia. Per
|
// Política simple: la nova notificació reemplaça la que hi havia. Per
|
||||||
// gestionar-ne una cua, canviar ací per una FIFO quan calga.
|
// gestionar-ne una cua, canviar ací per una FIFO quan calga.
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "game/ui/notification_presets.hpp"
|
#include "core/rendering/hud_constants.hpp"
|
||||||
|
#include "core/rendering/surface.hpp"
|
||||||
|
|
||||||
class Text;
|
class Text;
|
||||||
|
|
||||||
@@ -15,10 +16,9 @@ class Text;
|
|||||||
// 8bithud i la paleta "ui" amb la resta d'overlays (carregades per RenderInfo
|
// 8bithud i la paleta "ui" amb la resta d'overlays (carregades per RenderInfo
|
||||||
// a l'arrencada; si Notifier::init() es crida primer, les carrega ell).
|
// a l'arrencada; si Notifier::init() es crida primer, les carrega ell).
|
||||||
//
|
//
|
||||||
// Ús típic:
|
// API crua: Notifier::get().notify(text, text_color, bg_color, time_ms).
|
||||||
// Notifier::get().notify("Text ràpid"); // preset DEFAULT
|
// Per a treballar amb presets del joc, vegeu Notification::notify() a
|
||||||
// Notifier::get().notify("Text ràpid", miPreset); // preset custom
|
// game/ui/notification_presets.hpp.
|
||||||
// Notifier::get().notify("Urgent", 11, 3, 800); // colors i durada explícits
|
|
||||||
class Notifier {
|
class Notifier {
|
||||||
public:
|
public:
|
||||||
static auto get() -> Notifier&;
|
static auto get() -> Notifier&;
|
||||||
@@ -28,20 +28,18 @@ class Notifier {
|
|||||||
auto operator=(const Notifier&) -> Notifier& = delete;
|
auto operator=(const Notifier&) -> Notifier& = delete;
|
||||||
auto operator=(Notifier&&) -> Notifier& = delete;
|
auto operator=(Notifier&&) -> Notifier& = delete;
|
||||||
|
|
||||||
void init();
|
// Injecta la font compartida i la paleta UI que usa la caixa de fons.
|
||||||
|
// Així Notifier no depèn de cap string de recurs ni del cache global.
|
||||||
|
void init(std::shared_ptr<Text> text, const Palette& ui_palette);
|
||||||
|
|
||||||
void update(int delta_ms);
|
void update(int delta_ms);
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
// Tres overloads; els dos simples deleguen al tercer.
|
|
||||||
void notify(const std::string& text);
|
|
||||||
void notify(const std::string& text, const Notification::Preset& preset);
|
|
||||||
void notify(const std::string& text, Uint8 text_color, Uint8 bg_color, int time_ms);
|
void notify(const std::string& text, Uint8 text_color, Uint8 bg_color, int time_ms);
|
||||||
|
|
||||||
static constexpr int PADDING_H = 6;
|
static constexpr int PADDING_H = 6;
|
||||||
static constexpr int PADDING_V = 3;
|
static constexpr int PADDING_V = 3;
|
||||||
static constexpr int TEXT_SIZE = 8;
|
static constexpr int HEIGHT = Rendering::Hud::FONT_SIZE_PX + (2 * PADDING_V);
|
||||||
static constexpr int HEIGHT = TEXT_SIZE + (2 * PADDING_V);
|
|
||||||
static constexpr int MARGIN_TOP = 2; // Separació amb la vora superior del canvas.
|
static constexpr int MARGIN_TOP = 2; // Separació amb la vora superior del canvas.
|
||||||
static constexpr float SLIDE_SPEED = 120.0F;
|
static constexpr float SLIDE_SPEED = 120.0F;
|
||||||
|
|
||||||
@@ -54,7 +52,8 @@ class Notifier {
|
|||||||
Notifier() = default;
|
Notifier() = default;
|
||||||
~Notifier();
|
~Notifier();
|
||||||
|
|
||||||
std::unique_ptr<Text> text_;
|
std::shared_ptr<Text> text_;
|
||||||
|
Palette ui_palette_{};
|
||||||
Status status_{Status::HIDDEN};
|
Status status_{Status::HIDDEN};
|
||||||
float y_{static_cast<float>(-HEIGHT - MARGIN_TOP)};
|
float y_{static_cast<float>(-HEIGHT - MARGIN_TOP)};
|
||||||
std::string current_text_;
|
std::string current_text_;
|
||||||
|
|||||||
@@ -5,24 +5,33 @@
|
|||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "core/rendering/hud_constants.hpp"
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/text.hpp"
|
#include "core/rendering/text.hpp"
|
||||||
#include "core/resources/resource_cache.hpp"
|
|
||||||
#include "game/options.hpp"
|
|
||||||
#include "game/ui/ui_palette.hpp"
|
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
#include "utils/easing.hpp"
|
#include "utils/easing.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr int TEXT_SIZE = 8;
|
constexpr int TEXT_SIZE = Rendering::Hud::FONT_SIZE_PX;
|
||||||
constexpr int PADDING_V = (RenderInfo::HEIGHT - TEXT_SIZE) / 2;
|
constexpr int PADDING_V = (RenderInfo::HEIGHT - TEXT_SIZE) / 2;
|
||||||
|
|
||||||
// Llindar per saltar el dibuixat d'un segment quasi totalment transparent.
|
// Llindar per saltar el dibuixat d'un segment quasi totalment transparent.
|
||||||
// Evita una fileta d'un sol píxel quan acaba de començar a aparèixer.
|
// Evita una fileta d'un sol píxel quan acaba de començar a aparèixer.
|
||||||
constexpr float VISIBLE_EPSILON = 0.01F;
|
constexpr float VISIBLE_EPSILON = 0.01F;
|
||||||
|
|
||||||
|
// Desplaçament en píxels de la ombra respecte del text principal.
|
||||||
|
constexpr int SHADOW_OFFSET_PX = 1;
|
||||||
|
|
||||||
|
// Índex de cada segment dins de segments_. Si canvia SEGMENT_COUNT o
|
||||||
|
// l'ordre, actualitzar ací.
|
||||||
|
constexpr int FPS_SEGMENT = 0;
|
||||||
|
constexpr int DRIVER_SEGMENT = 1;
|
||||||
|
constexpr int SHADER_SEGMENT = 2;
|
||||||
|
constexpr int TIME_SEGMENT = 3;
|
||||||
|
|
||||||
auto prettify(std::string s) -> std::string {
|
auto prettify(std::string s) -> std::string {
|
||||||
std::ranges::replace(s, '-', ' ');
|
std::ranges::replace(s, '-', ' ');
|
||||||
std::ranges::transform(s, s.begin(), [](unsigned char c) { return std::tolower(c); });
|
std::ranges::transform(s, s.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||||
@@ -48,27 +57,25 @@ auto RenderInfo::get() -> RenderInfo& {
|
|||||||
|
|
||||||
RenderInfo::~RenderInfo() = default;
|
RenderInfo::~RenderInfo() = default;
|
||||||
|
|
||||||
void RenderInfo::init() {
|
void RenderInfo::init(const Frame& initial, std::shared_ptr<Text> text) {
|
||||||
auto font_surface = Resource::Cache::get().getSurface("8bithud.gif");
|
text_ = std::move(text);
|
||||||
text_ = std::make_unique<Text>(font_surface, "data/font/8bithud.fnt");
|
|
||||||
|
current_ = initial;
|
||||||
|
|
||||||
// Restaura l'estat guardat: si l'overlay estava engegat, l'activem ja
|
// Restaura l'estat guardat: si l'overlay estava engegat, l'activem ja
|
||||||
// plenament visible (sense animació d'entrada inicial).
|
// plenament visible (sense animació d'entrada inicial). refreshSegments
|
||||||
if (Options::video.render_info_position != Defaults::RenderInfoPosition::OFF) {
|
// és qui marca mono_digits al segment de temps; si no s'engega ara,
|
||||||
visible_pos_ = Options::video.render_info_position;
|
// la primera crida a update() també ho farà abans de qualsevol render.
|
||||||
|
if (initial.target_position != Defaults::RenderInfoPosition::OFF) {
|
||||||
|
visible_pos_ = initial.target_position;
|
||||||
info_anim_ = 1.0F;
|
info_anim_ = 1.0F;
|
||||||
}
|
|
||||||
|
|
||||||
// El segment de temps pot estar activat per persistència: si la barra
|
|
||||||
// apareix ja plena, el seu segment també ha d'estar-ho.
|
|
||||||
segments_[3].mono_digits = true;
|
|
||||||
if (info_anim_ >= 1.0F) {
|
|
||||||
refreshSegments();
|
refreshSegments();
|
||||||
for (auto& seg : segments_) { seg.anim = seg.visible ? 1.0F : 0.0F; }
|
for (auto& seg : segments_) { seg.anim = seg.visible ? 1.0F : 0.0F; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderInfo::update(int delta_ms) {
|
void RenderInfo::update(int delta_ms, const Frame& frame) {
|
||||||
|
current_ = frame;
|
||||||
elapsed_ms_ += delta_ms;
|
elapsed_ms_ += delta_ms;
|
||||||
const float DT = static_cast<float>(delta_ms) / 1000.0F;
|
const float DT = static_cast<float>(delta_ms) / 1000.0F;
|
||||||
|
|
||||||
@@ -76,7 +83,7 @@ void RenderInfo::update(int delta_ms) {
|
|||||||
// visible, entrem (anim→1). Si no, primer eixim (anim→0) del lloc actual;
|
// visible, entrem (anim→1). Si no, primer eixim (anim→0) del lloc actual;
|
||||||
// quan arriba a 0 commutem visible_pos_ i deixem que el proper frame
|
// quan arriba a 0 commutem visible_pos_ i deixem que el proper frame
|
||||||
// dispari l'entrada. Excepció: de OFF a TOP/BOTTOM commutem en sec.
|
// dispari l'entrada. Excepció: de OFF a TOP/BOTTOM commutem en sec.
|
||||||
const auto DESIRED = Options::video.render_info_position;
|
const auto DESIRED = current_.target_position;
|
||||||
if (DESIRED == visible_pos_) {
|
if (DESIRED == visible_pos_) {
|
||||||
// Si el destí és OFF, no cal animar cap a un lloc — la barra ja ha
|
// Si el destí és OFF, no cal animar cap a un lloc — la barra ja ha
|
||||||
// d'estar fora. Sense aquest guard, en arribar de BOTTOM→OFF la
|
// d'estar fora. Sense aquest guard, en arribar de BOTTOM→OFF la
|
||||||
@@ -111,38 +118,35 @@ void RenderInfo::update(int delta_ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RenderInfo::refreshSegments() {
|
void RenderInfo::refreshSegments() {
|
||||||
// [0] FPS (sempre visible mentre l'overlay apareix)
|
// FPS (sempre visible mentre l'overlay apareix)
|
||||||
segments_[0].text = std::to_string(Screen::get().getLastFPS()) + " fps";
|
segments_[FPS_SEGMENT].text = std::to_string(Screen::get().getLastFPS()) + " fps";
|
||||||
segments_[0].visible = true;
|
segments_[FPS_SEGMENT].visible = true;
|
||||||
segments_[0].mono_digits = false;
|
segments_[FPS_SEGMENT].mono_digits = false;
|
||||||
|
|
||||||
// [1] Driver GPU (sempre visible)
|
// Driver GPU (sempre visible)
|
||||||
const auto& driver = Screen::get().getGPUDriver();
|
const auto& driver = Screen::get().getGPUDriver();
|
||||||
segments_[1].text = std::string(" | ") + (driver.empty() ? std::string("sdl") : driver);
|
segments_[DRIVER_SEGMENT].text = std::string(" | ") + (driver.empty() ? std::string("sdl") : driver);
|
||||||
segments_[1].visible = true;
|
segments_[DRIVER_SEGMENT].visible = true;
|
||||||
segments_[1].mono_digits = false;
|
segments_[DRIVER_SEGMENT].mono_digits = false;
|
||||||
|
|
||||||
// [2] Shader + preset (només amb shader activat)
|
// Shader + preset (només amb shader activat). El caller ha triat el
|
||||||
if (Options::video.shader.enabled) {
|
// preset actiu segons el shader; ací només el formatem.
|
||||||
const std::string& shader = Options::video.shader.current_shader;
|
if (current_.shader_enabled) {
|
||||||
const std::string& preset = (shader == "crtpi")
|
segments_[SHADER_SEGMENT].text = " | " + current_.shader_name + " " + prettify(current_.shader_preset);
|
||||||
? Options::video.shader.current_crtpi_preset
|
|
||||||
: Options::video.shader.current_postfx_preset;
|
|
||||||
segments_[2].text = " | " + shader + " " + prettify(preset);
|
|
||||||
}
|
}
|
||||||
segments_[2].visible = Options::video.shader.enabled;
|
segments_[SHADER_SEGMENT].visible = current_.shader_enabled;
|
||||||
segments_[2].mono_digits = false;
|
segments_[SHADER_SEGMENT].mono_digits = false;
|
||||||
|
|
||||||
// [3] Temps de joc (togglejable i persistent)
|
// Temps de joc (togglejable i persistent)
|
||||||
segments_[3].text = " | " + formatTime(elapsed_ms_);
|
segments_[TIME_SEGMENT].text = " | " + formatTime(elapsed_ms_);
|
||||||
segments_[3].visible = Options::video.render_info_show_time;
|
segments_[TIME_SEGMENT].visible = current_.show_time;
|
||||||
segments_[3].mono_digits = true;
|
segments_[TIME_SEGMENT].mono_digits = true;
|
||||||
|
|
||||||
// Els tres primers segments baixem a minúscules per coherència estètica
|
// Els tres primers segments baixem a minúscules per coherència estètica
|
||||||
// amb la resta de la línia; el [3] (temps) manté dígits i puntuació.
|
// amb la resta de la línia; el de temps manté dígits i puntuació.
|
||||||
segments_[0].text = prettify(segments_[0].text);
|
segments_[FPS_SEGMENT].text = prettify(segments_[FPS_SEGMENT].text);
|
||||||
segments_[1].text = prettify(segments_[1].text);
|
segments_[DRIVER_SEGMENT].text = prettify(segments_[DRIVER_SEGMENT].text);
|
||||||
segments_[2].text = prettify(segments_[2].text);
|
segments_[SHADER_SEGMENT].text = prettify(segments_[SHADER_SEGMENT].text);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto RenderInfo::segmentWidth(const Segment& seg, int digit_cell_w) const -> int {
|
auto RenderInfo::segmentWidth(const Segment& seg, int digit_cell_w) const -> int {
|
||||||
@@ -191,25 +195,13 @@ void RenderInfo::render() {
|
|||||||
const int SEG_W = segmentWidth(seg, DIGIT_CELL);
|
const int SEG_W = segmentWidth(seg, DIGIT_CELL);
|
||||||
|
|
||||||
if (seg.mono_digits) {
|
if (seg.mono_digits) {
|
||||||
text_->writeMonoDigits(XI + 1, Y + 1, seg.text, UiColor::RENDER_INFO_SHADOW, DIGIT_CELL);
|
text_->writeMonoDigits(XI + SHADOW_OFFSET_PX, Y + SHADOW_OFFSET_PX, seg.text, current_.shadow_color, DIGIT_CELL);
|
||||||
text_->writeMonoDigits(XI, Y, seg.text, UiColor::RENDER_INFO_TEXT, DIGIT_CELL);
|
text_->writeMonoDigits(XI, Y, seg.text, current_.text_color, DIGIT_CELL);
|
||||||
} else {
|
} else {
|
||||||
text_->writeColored(XI + 1, Y + 1, seg.text, UiColor::RENDER_INFO_SHADOW);
|
text_->writeColored(XI + SHADOW_OFFSET_PX, Y + SHADOW_OFFSET_PX, seg.text, current_.shadow_color);
|
||||||
text_->writeColored(XI, Y, seg.text, UiColor::RENDER_INFO_TEXT);
|
text_->writeColored(XI, Y, seg.text, current_.text_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
cur_x += static_cast<float>(SEG_W) * Easing::outQuad(seg.anim);
|
cur_x += static_cast<float>(SEG_W) * Easing::outQuad(seg.anim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderInfo::cyclePosition() {
|
|
||||||
const auto CURRENT = Options::video.render_info_position;
|
|
||||||
const int NEXT = (static_cast<int>(CURRENT) + 1) % 3;
|
|
||||||
Options::video.render_info_position = static_cast<Defaults::RenderInfoPosition>(NEXT);
|
|
||||||
Options::saveToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderInfo::toggleShowTime() {
|
|
||||||
Options::video.render_info_show_time = !Options::video.render_info_show_time;
|
|
||||||
Options::saveToFile();
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -11,9 +13,9 @@ class Text;
|
|||||||
|
|
||||||
// Overlay de debug amb tres estats (OFF / TOP / BOTTOM). Mostra una barra
|
// Overlay de debug amb tres estats (OFF / TOP / BOTTOM). Mostra una barra
|
||||||
// d'info dividida en segments independents (fps, driver, shader, temps) que
|
// d'info dividida en segments independents (fps, driver, shader, temps) que
|
||||||
// apareixen i desapareixen suaument. La posició es commuta amb una tecla
|
// apareixen i desapareixen suaument. No llig cap configuració; el caller
|
||||||
// (cicle OFF → TOP → BOTTOM) i el segment de temps es pot mostrar o amagar
|
// (Director a init, GameScene a update) injecta cada frame l'estat desitjat
|
||||||
// per separat; les dos opcions persisteixen a config.yaml.
|
// via Frame.
|
||||||
class RenderInfo {
|
class RenderInfo {
|
||||||
public:
|
public:
|
||||||
static auto get() -> RenderInfo&;
|
static auto get() -> RenderInfo&;
|
||||||
@@ -23,18 +25,26 @@ class RenderInfo {
|
|||||||
auto operator=(const RenderInfo&) -> RenderInfo& = delete;
|
auto operator=(const RenderInfo&) -> RenderInfo& = delete;
|
||||||
auto operator=(RenderInfo&&) -> RenderInfo& = delete;
|
auto operator=(RenderInfo&&) -> RenderInfo& = delete;
|
||||||
|
|
||||||
void init();
|
// Estat extern que dirigeix què ha de mostrar RenderInfo. La posició
|
||||||
|
// determina si la barra entra per dalt o per baix (o s'amaga). El
|
||||||
|
// shader_preset ja ha d'estar pre-seleccionat (crtpi/postfx) pel caller.
|
||||||
|
struct Frame {
|
||||||
|
Defaults::RenderInfoPosition target_position{Defaults::RenderInfoPosition::OFF};
|
||||||
|
bool show_time{false};
|
||||||
|
bool shader_enabled{false};
|
||||||
|
std::string shader_name;
|
||||||
|
std::string shader_preset;
|
||||||
|
Uint8 text_color{0};
|
||||||
|
Uint8 shadow_color{0};
|
||||||
|
};
|
||||||
|
|
||||||
void update(int delta_ms);
|
// Injecta la font compartida a init(). La resta de l'estat visual ve
|
||||||
|
// pel Frame a cada frame, així que RenderInfo no depèn de cap recurs
|
||||||
|
// global.
|
||||||
|
void init(const Frame& initial, std::shared_ptr<Text> text);
|
||||||
|
void update(int delta_ms, const Frame& frame);
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
// Avança la posició OFF → TOP → BOTTOM → OFF i persisteix.
|
|
||||||
static void cyclePosition();
|
|
||||||
|
|
||||||
// Mostra/oculta el segment de temps i persisteix. Només té efecte visual
|
|
||||||
// quan la barra és visible (TOP/BOTTOM).
|
|
||||||
static void toggleShowTime();
|
|
||||||
|
|
||||||
static constexpr int HEIGHT = 10;
|
static constexpr int HEIGHT = 10;
|
||||||
static constexpr float SLIDE_SPEED = 5.0F; // posició global (1/s)
|
static constexpr float SLIDE_SPEED = 5.0F; // posició global (1/s)
|
||||||
static constexpr float SEG_SPEED = 6.0F; // segments individuals (1/s)
|
static constexpr float SEG_SPEED = 6.0F; // segments individuals (1/s)
|
||||||
@@ -55,9 +65,10 @@ class RenderInfo {
|
|||||||
void refreshSegments();
|
void refreshSegments();
|
||||||
[[nodiscard]] auto segmentWidth(const Segment& seg, int digit_cell_w) const -> int;
|
[[nodiscard]] auto segmentWidth(const Segment& seg, int digit_cell_w) const -> int;
|
||||||
|
|
||||||
std::unique_ptr<Text> text_;
|
std::shared_ptr<Text> text_;
|
||||||
Defaults::RenderInfoPosition visible_pos_{Defaults::RenderInfoPosition::OFF};
|
Defaults::RenderInfoPosition visible_pos_{Defaults::RenderInfoPosition::OFF};
|
||||||
float info_anim_{0.0F}; // 0..1, suavitzat amb outQuad al render
|
float info_anim_{0.0F}; // 0..1, suavitzat amb outQuad al render
|
||||||
std::array<Segment, SEGMENT_COUNT> segments_{};
|
std::array<Segment, SEGMENT_COUNT> segments_{};
|
||||||
int elapsed_ms_{0};
|
int elapsed_ms_{0};
|
||||||
|
Frame current_{};
|
||||||
};
|
};
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
|
||||||
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"
|
#include "core/rendering/canvas.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -53,7 +53,7 @@ void Screen::configure(const Config& config) {
|
|||||||
subsystem_initialized_ = true;
|
subsystem_initialized_ = true;
|
||||||
canvas_width_ = GameCanvas::WIDTH;
|
canvas_width_ = GameCanvas::WIDTH;
|
||||||
canvas_height_ = GameCanvas::HEIGHT;
|
canvas_height_ = GameCanvas::HEIGHT;
|
||||||
canvas_.assign(static_cast<std::size_t>(GameCanvas::WIDTH) * static_cast<std::size_t>(GameCanvas::HEIGHT), 0xFF000000U);
|
canvas_.assign(static_cast<std::size_t>(GameCanvas::WIDTH) * static_cast<std::size_t>(GameCanvas::HEIGHT), Rendering::OPAQUE_ALPHA);
|
||||||
}
|
}
|
||||||
destroySdl();
|
destroySdl();
|
||||||
config_ = config;
|
config_ = config;
|
||||||
@@ -74,7 +74,7 @@ void Screen::destroySdl() {
|
|||||||
// 2) Destruir el GPU device (vkDestroyInstance amb Wayland encara viu).
|
// 2) Destruir el GPU device (vkDestroyInstance amb Wayland encara viu).
|
||||||
// 3) Destruir la finestra SDL (tanca la surface de Wayland al final).
|
// 3) Destruir la finestra SDL (tanca la surface de Wayland al final).
|
||||||
if (shader_backend_ != nullptr) {
|
if (shader_backend_ != nullptr) {
|
||||||
static_cast<Rendering::SDL3GPUShader*>(shader_backend_.get())->releaseWindow();
|
shader_backend_->releaseWindow();
|
||||||
}
|
}
|
||||||
shader_backend_.reset();
|
shader_backend_.reset();
|
||||||
if (game_texture_ != nullptr) {
|
if (game_texture_ != nullptr) {
|
||||||
@@ -106,7 +106,7 @@ void Screen::createSdl() {
|
|||||||
|
|
||||||
// Primer intent: path SDL3 GPU. Si falla, fallback automàtic a SDL_Renderer.
|
// Primer intent: path SDL3 GPU. Si falla, fallback automàtic a SDL_Renderer.
|
||||||
if (config_.gpu_acceleration) {
|
if (config_.gpu_acceleration) {
|
||||||
auto backend = std::make_unique<Rendering::SDL3GPUShader>();
|
auto backend = Rendering::makeDefaultShaderBackend();
|
||||||
backend->setPreferredDriver(config_.preferred_driver);
|
backend->setPreferredDriver(config_.preferred_driver);
|
||||||
backend->setVSync(config_.vsync);
|
backend->setVSync(config_.vsync);
|
||||||
if (backend->init(window_, canvas_width_, canvas_height_)) {
|
if (backend->init(window_, canvas_width_, canvas_height_)) {
|
||||||
@@ -215,7 +215,7 @@ void Screen::adjustWindowSize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Screen::clear(std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
void Screen::clear(std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
||||||
const std::uint32_t COLOR = (0xFFU << 24) | (static_cast<std::uint32_t>(r) << 16) | (static_cast<std::uint32_t>(g) << 8) | static_cast<std::uint32_t>(b);
|
const std::uint32_t COLOR = Rendering::OPAQUE_ALPHA | (static_cast<std::uint32_t>(r) << 16) | (static_cast<std::uint32_t>(g) << 8) | static_cast<std::uint32_t>(b);
|
||||||
std::ranges::fill(canvas_, COLOR);
|
std::ranges::fill(canvas_, COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
|
||||||
|
// Fragment shader del shader "crtpi" (algoritme CRT-Pi): scanlines amb
|
||||||
|
// pesos gaussians, multisample opcional, gamma i màscara de subpíxels.
|
||||||
|
namespace Rendering::Msl {
|
||||||
|
|
||||||
|
inline constexpr const char* kCrtpiFrag = R"(
|
||||||
|
#include <metal_stdlib>
|
||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
struct PostVOut {
|
||||||
|
float4 pos [[position]];
|
||||||
|
float2 uv;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CrtPiUniforms {
|
||||||
|
float scanline_weight;
|
||||||
|
float scanline_gap_brightness;
|
||||||
|
float bloom_factor;
|
||||||
|
float input_gamma;
|
||||||
|
float output_gamma;
|
||||||
|
float mask_brightness;
|
||||||
|
float curvature_x;
|
||||||
|
float curvature_y;
|
||||||
|
int mask_type;
|
||||||
|
int enable_scanlines;
|
||||||
|
int enable_multisample;
|
||||||
|
int enable_gamma;
|
||||||
|
int enable_curvature;
|
||||||
|
int enable_sharper;
|
||||||
|
float texture_width;
|
||||||
|
float texture_height;
|
||||||
|
};
|
||||||
|
|
||||||
|
static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) {
|
||||||
|
float2 curvature = float2(cx, cy);
|
||||||
|
float2 barrel_scale = 1.0f - (0.23f * curvature);
|
||||||
|
coord *= screen_scale;
|
||||||
|
coord -= 0.5f;
|
||||||
|
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||||
|
coord += coord * (curvature * rsq);
|
||||||
|
coord *= barrel_scale;
|
||||||
|
if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); }
|
||||||
|
coord += 0.5f;
|
||||||
|
coord /= screen_scale;
|
||||||
|
return coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float crtpi_scan_weight(float dist, float sw, float gap) {
|
||||||
|
return max(1.0f - dist * dist * sw, gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) {
|
||||||
|
float w = crtpi_scan_weight(dy, sw, gap);
|
||||||
|
if (ms) {
|
||||||
|
w += crtpi_scan_weight(dy - filter_w, sw, gap);
|
||||||
|
w += crtpi_scan_weight(dy + filter_w, sw, gap);
|
||||||
|
w *= 0.3333333f;
|
||||||
|
}
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 crtpi_fs(PostVOut in [[stage_in]],
|
||||||
|
texture2d<float> tex [[texture(0)]],
|
||||||
|
sampler samp [[sampler(0)]],
|
||||||
|
constant CrtPiUniforms& u [[buffer(0)]]) {
|
||||||
|
float2 tex_size = float2(u.texture_width, u.texture_height);
|
||||||
|
// Amplada del filtre de scanline analític. 768 = alçada de referència
|
||||||
|
// CRT a la qual es va tarar l'algoritme original; 3 = divisió per
|
||||||
|
// subpíxel (R/G/B) del multisample. El resultat escala amb la textura
|
||||||
|
// d'entrada, de manera que més alçada → filtre més fi.
|
||||||
|
const float CRT_REFERENCE_HEIGHT = 768.0f;
|
||||||
|
const float SUBPIXEL_DIV = 3.0f;
|
||||||
|
float filter_width = (CRT_REFERENCE_HEIGHT / u.texture_height) / SUBPIXEL_DIV;
|
||||||
|
float2 texcoord = in.uv;
|
||||||
|
|
||||||
|
if (u.enable_curvature != 0) {
|
||||||
|
texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y);
|
||||||
|
if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); }
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 coord_in_pixels = texcoord * tex_size;
|
||||||
|
float2 tc;
|
||||||
|
float scan_weight;
|
||||||
|
|
||||||
|
if (u.enable_sharper != 0) {
|
||||||
|
float2 temp = floor(coord_in_pixels) + 0.5f;
|
||||||
|
tc = temp / tex_size;
|
||||||
|
float2 deltas = coord_in_pixels - temp;
|
||||||
|
scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||||
|
float2 signs = sign(deltas);
|
||||||
|
deltas.x *= 2.0f;
|
||||||
|
deltas = deltas * deltas;
|
||||||
|
deltas.y = deltas.y * deltas.y;
|
||||||
|
deltas.x *= 0.5f;
|
||||||
|
deltas.y *= 8.0f;
|
||||||
|
deltas /= tex_size;
|
||||||
|
deltas *= signs;
|
||||||
|
tc = tc + deltas;
|
||||||
|
} else {
|
||||||
|
float temp_y = floor(coord_in_pixels.y) + 0.5f;
|
||||||
|
float y_coord = temp_y / tex_size.y;
|
||||||
|
float dy = coord_in_pixels.y - temp_y;
|
||||||
|
scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||||
|
float sign_y = sign(dy);
|
||||||
|
dy = dy * dy;
|
||||||
|
dy = dy * dy;
|
||||||
|
dy *= 8.0f;
|
||||||
|
dy /= tex_size.y;
|
||||||
|
dy *= sign_y;
|
||||||
|
tc = float2(texcoord.x, y_coord + dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 colour = tex.sample(samp, tc).rgb;
|
||||||
|
|
||||||
|
if (u.enable_scanlines != 0) {
|
||||||
|
if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); }
|
||||||
|
colour *= scan_weight * u.bloom_factor;
|
||||||
|
if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u.mask_type == 1) {
|
||||||
|
float wm = fract(in.pos.x * 0.5f);
|
||||||
|
float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness)
|
||||||
|
: float3(1.0f, u.mask_brightness, 1.0f);
|
||||||
|
colour *= mask;
|
||||||
|
} else if (u.mask_type == 2) {
|
||||||
|
float wm = fract(in.pos.x * 0.3333333f);
|
||||||
|
float3 mask = float3(u.mask_brightness);
|
||||||
|
if (wm < 0.3333333f) mask.x = 1.0f;
|
||||||
|
else if (wm < 0.6666666f) mask.y = 1.0f;
|
||||||
|
else mask.z = 1.0f;
|
||||||
|
colour *= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return float4(colour, 1.0f);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
} // namespace Rendering::Msl
|
||||||
|
|
||||||
|
#endif // __APPLE__
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
|
||||||
|
// Fragment shader del shader "postfx": vignette, chroma, scanlines, mask,
|
||||||
|
// gamma, curvature, bleeding i flicker. Els paràmetres venen via uniforms.
|
||||||
|
namespace Rendering::Msl {
|
||||||
|
|
||||||
|
inline constexpr const char* kPostfxFrag = R"(
|
||||||
|
#include <metal_stdlib>
|
||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
struct PostVOut {
|
||||||
|
float4 pos [[position]];
|
||||||
|
float2 uv;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PostFXUniforms {
|
||||||
|
float vignette_strength;
|
||||||
|
float chroma_strength;
|
||||||
|
float scanline_strength;
|
||||||
|
float screen_height;
|
||||||
|
float mask_strength;
|
||||||
|
float gamma_strength;
|
||||||
|
float curvature;
|
||||||
|
float bleeding;
|
||||||
|
float pixel_scale;
|
||||||
|
float time;
|
||||||
|
float oversample;
|
||||||
|
float flicker;
|
||||||
|
};
|
||||||
|
|
||||||
|
static float3 rgb_to_ycc(float3 rgb) {
|
||||||
|
return float3(
|
||||||
|
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
|
||||||
|
-0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f,
|
||||||
|
0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
static float3 ycc_to_rgb(float3 ycc) {
|
||||||
|
float y = ycc.x;
|
||||||
|
float cb = ycc.y - 0.5f;
|
||||||
|
float cr = ycc.z - 0.5f;
|
||||||
|
return clamp(float3(
|
||||||
|
y + 1.402f*cr,
|
||||||
|
y - 0.344f*cb - 0.714f*cr,
|
||||||
|
y + 1.772f*cb
|
||||||
|
), 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||||
|
texture2d<float> scene [[texture(0)]],
|
||||||
|
sampler samp [[sampler(0)]],
|
||||||
|
constant PostFXUniforms& u [[buffer(0)]]) {
|
||||||
|
float2 uv = in.uv;
|
||||||
|
|
||||||
|
if (u.curvature > 0.0f) {
|
||||||
|
float2 c = uv - 0.5f;
|
||||||
|
float rsq = dot(c, c);
|
||||||
|
float2 dist = float2(0.05f, 0.1f) * u.curvature;
|
||||||
|
float2 barrelScale = 1.0f - 0.23f * dist;
|
||||||
|
c += c * (dist * rsq);
|
||||||
|
c *= barrelScale;
|
||||||
|
if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) {
|
||||||
|
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
uv = c + 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 base = scene.sample(samp, uv).rgb;
|
||||||
|
|
||||||
|
float3 colour;
|
||||||
|
if (u.bleeding > 0.0f) {
|
||||||
|
float tw = float(scene.get_width());
|
||||||
|
float step = u.oversample / tw;
|
||||||
|
float3 ycc = rgb_to_ycc(base);
|
||||||
|
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
|
||||||
|
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
|
||||||
|
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
|
||||||
|
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
|
||||||
|
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
|
||||||
|
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||||
|
} else {
|
||||||
|
colour = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f));
|
||||||
|
colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r;
|
||||||
|
colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b;
|
||||||
|
|
||||||
|
if (u.gamma_strength > 0.0f) {
|
||||||
|
float3 lin = pow(colour, float3(2.4f));
|
||||||
|
colour = mix(colour, lin, u.gamma_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float SCAN_DARK_RATIO = 0.333f;
|
||||||
|
const float SCAN_DARK_FLOOR = 0.42f;
|
||||||
|
if (u.scanline_strength > 0.0f) {
|
||||||
|
float ps = max(1.0f, round(u.pixel_scale));
|
||||||
|
float frac_in_row = fract(uv.y * u.screen_height);
|
||||||
|
float row_pos = floor(frac_in_row * ps);
|
||||||
|
float bright_rows = (ps < 2.0f) ? ps : ((ps < 3.0f) ? 1.0f : floor(ps * (1.0f - SCAN_DARK_RATIO)));
|
||||||
|
float is_dark = step(bright_rows, row_pos);
|
||||||
|
float scan = mix(1.0f, SCAN_DARK_FLOOR, is_dark);
|
||||||
|
colour *= mix(1.0f, scan, u.scanline_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u.gamma_strength > 0.0f) {
|
||||||
|
float3 enc = pow(colour, float3(1.0f/2.2f));
|
||||||
|
colour = mix(colour, enc, u.gamma_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 d = uv - 0.5f;
|
||||||
|
float vignette = 1.0f - dot(d, d) * u.vignette_strength;
|
||||||
|
colour *= clamp(vignette, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
if (u.mask_strength > 0.0f) {
|
||||||
|
float whichMask = fract(in.pos.x * 0.3333333f);
|
||||||
|
float3 mask = float3(0.80f);
|
||||||
|
if (whichMask < 0.3333333f) mask.x = 1.0f;
|
||||||
|
else if (whichMask < 0.6666667f) mask.y = 1.0f;
|
||||||
|
else mask.z = 1.0f;
|
||||||
|
colour = mix(colour, colour * mask, u.mask_strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u.flicker > 0.0f) {
|
||||||
|
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
|
||||||
|
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
|
||||||
|
}
|
||||||
|
|
||||||
|
return float4(colour, 1.0f);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
} // namespace Rendering::Msl
|
||||||
|
|
||||||
|
#endif // __APPLE__
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
|
||||||
|
// Vertex shader compartit per tots els pipelines de post-procés:
|
||||||
|
// fullscreen-triangle que cobreix tota l'àrea del swapchain amb UVs a [0,1].
|
||||||
|
namespace Rendering::Msl {
|
||||||
|
|
||||||
|
inline constexpr const char* kPostfxVert = R"(
|
||||||
|
#include <metal_stdlib>
|
||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
struct PostVOut {
|
||||||
|
float4 pos [[position]];
|
||||||
|
float2 uv;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
||||||
|
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
||||||
|
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
||||||
|
PostVOut out;
|
||||||
|
out.pos = float4(positions[vid], 0.0, 1.0);
|
||||||
|
out.uv = uvs[vid];
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
} // namespace Rendering::Msl
|
||||||
|
|
||||||
|
#endif // __APPLE__
|
||||||
@@ -3,206 +3,17 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/rendering/sdl3gpu/shader_factory.hpp"
|
||||||
#include "core/rendering/sdl3gpu/viewport.hpp"
|
#include "core/rendering/sdl3gpu/viewport.hpp"
|
||||||
|
|
||||||
#ifndef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
#include "core/rendering/sdl3gpu/msl/crtpi_frag.msl.h"
|
||||||
|
#include "core/rendering/sdl3gpu/msl/postfx_vert.msl.h"
|
||||||
|
#else
|
||||||
#include "core/rendering/sdl3gpu/spv/crtpi_frag_spv.h"
|
#include "core/rendering/sdl3gpu/spv/crtpi_frag_spv.h"
|
||||||
#include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
|
#include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
// NOLINTBEGIN(readability-identifier-naming)
|
|
||||||
const char* CRTPI_VERT_MSL_ = R"(
|
|
||||||
#include <metal_stdlib>
|
|
||||||
using namespace metal;
|
|
||||||
|
|
||||||
struct PostVOut {
|
|
||||||
float4 pos [[position]];
|
|
||||||
float2 uv;
|
|
||||||
};
|
|
||||||
|
|
||||||
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
|
||||||
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
|
||||||
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
|
||||||
PostVOut out;
|
|
||||||
out.pos = float4(positions[vid], 0.0, 1.0);
|
|
||||||
out.uv = uvs[vid];
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
const char* CRTPI_FRAG_MSL_ = R"(
|
|
||||||
#include <metal_stdlib>
|
|
||||||
using namespace metal;
|
|
||||||
|
|
||||||
struct PostVOut {
|
|
||||||
float4 pos [[position]];
|
|
||||||
float2 uv;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CrtPiUniforms {
|
|
||||||
float scanline_weight;
|
|
||||||
float scanline_gap_brightness;
|
|
||||||
float bloom_factor;
|
|
||||||
float input_gamma;
|
|
||||||
float output_gamma;
|
|
||||||
float mask_brightness;
|
|
||||||
float curvature_x;
|
|
||||||
float curvature_y;
|
|
||||||
int mask_type;
|
|
||||||
int enable_scanlines;
|
|
||||||
int enable_multisample;
|
|
||||||
int enable_gamma;
|
|
||||||
int enable_curvature;
|
|
||||||
int enable_sharper;
|
|
||||||
float texture_width;
|
|
||||||
float texture_height;
|
|
||||||
};
|
|
||||||
|
|
||||||
static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) {
|
|
||||||
float2 curvature = float2(cx, cy);
|
|
||||||
float2 barrel_scale = 1.0f - (0.23f * curvature);
|
|
||||||
coord *= screen_scale;
|
|
||||||
coord -= 0.5f;
|
|
||||||
float rsq = coord.x * coord.x + coord.y * coord.y;
|
|
||||||
coord += coord * (curvature * rsq);
|
|
||||||
coord *= barrel_scale;
|
|
||||||
if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); }
|
|
||||||
coord += 0.5f;
|
|
||||||
coord /= screen_scale;
|
|
||||||
return coord;
|
|
||||||
}
|
|
||||||
|
|
||||||
static float crtpi_scan_weight(float dist, float sw, float gap) {
|
|
||||||
return max(1.0f - dist * dist * sw, gap);
|
|
||||||
}
|
|
||||||
|
|
||||||
static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) {
|
|
||||||
float w = crtpi_scan_weight(dy, sw, gap);
|
|
||||||
if (ms) {
|
|
||||||
w += crtpi_scan_weight(dy - filter_w, sw, gap);
|
|
||||||
w += crtpi_scan_weight(dy + filter_w, sw, gap);
|
|
||||||
w *= 0.3333333f;
|
|
||||||
}
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment float4 crtpi_fs(PostVOut in [[stage_in]],
|
|
||||||
texture2d<float> tex [[texture(0)]],
|
|
||||||
sampler samp [[sampler(0)]],
|
|
||||||
constant CrtPiUniforms& u [[buffer(0)]]) {
|
|
||||||
float2 tex_size = float2(u.texture_width, u.texture_height);
|
|
||||||
float filter_width = (768.0f / u.texture_height) / 3.0f;
|
|
||||||
float2 texcoord = in.uv;
|
|
||||||
|
|
||||||
if (u.enable_curvature != 0) {
|
|
||||||
texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y);
|
|
||||||
if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); }
|
|
||||||
}
|
|
||||||
|
|
||||||
float2 coord_in_pixels = texcoord * tex_size;
|
|
||||||
float2 tc;
|
|
||||||
float scan_weight;
|
|
||||||
|
|
||||||
if (u.enable_sharper != 0) {
|
|
||||||
float2 temp = floor(coord_in_pixels) + 0.5f;
|
|
||||||
tc = temp / tex_size;
|
|
||||||
float2 deltas = coord_in_pixels - temp;
|
|
||||||
scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
|
||||||
float2 signs = sign(deltas);
|
|
||||||
deltas.x *= 2.0f;
|
|
||||||
deltas = deltas * deltas;
|
|
||||||
deltas.y = deltas.y * deltas.y;
|
|
||||||
deltas.x *= 0.5f;
|
|
||||||
deltas.y *= 8.0f;
|
|
||||||
deltas /= tex_size;
|
|
||||||
deltas *= signs;
|
|
||||||
tc = tc + deltas;
|
|
||||||
} else {
|
|
||||||
float temp_y = floor(coord_in_pixels.y) + 0.5f;
|
|
||||||
float y_coord = temp_y / tex_size.y;
|
|
||||||
float dy = coord_in_pixels.y - temp_y;
|
|
||||||
scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
|
||||||
float sign_y = sign(dy);
|
|
||||||
dy = dy * dy;
|
|
||||||
dy = dy * dy;
|
|
||||||
dy *= 8.0f;
|
|
||||||
dy /= tex_size.y;
|
|
||||||
dy *= sign_y;
|
|
||||||
tc = float2(texcoord.x, y_coord + dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 colour = tex.sample(samp, tc).rgb;
|
|
||||||
|
|
||||||
if (u.enable_scanlines != 0) {
|
|
||||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); }
|
|
||||||
colour *= scan_weight * u.bloom_factor;
|
|
||||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u.mask_type == 1) {
|
|
||||||
float wm = fract(in.pos.x * 0.5f);
|
|
||||||
float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness)
|
|
||||||
: float3(1.0f, u.mask_brightness, 1.0f);
|
|
||||||
colour *= mask;
|
|
||||||
} else if (u.mask_type == 2) {
|
|
||||||
float wm = fract(in.pos.x * 0.3333333f);
|
|
||||||
float3 mask = float3(u.mask_brightness);
|
|
||||||
if (wm < 0.3333333f) mask.x = 1.0f;
|
|
||||||
else if (wm < 0.6666666f) mask.y = 1.0f;
|
|
||||||
else mask.z = 1.0f;
|
|
||||||
colour *= mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
return float4(colour, 1.0f);
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
// NOLINTEND(readability-identifier-naming)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Helpers per a crear shaders. Duplicats en cada pipeline per mantindre els
|
|
||||||
// fitxers autònoms — són funcions trivials i no val la pena un header compartit.
|
|
||||||
#ifdef __APPLE__
|
|
||||||
auto createShaderMSL(SDL_GPUDevice* device,
|
|
||||||
const char* msl_source,
|
|
||||||
const char* entrypoint,
|
|
||||||
SDL_GPUShaderStage stage,
|
|
||||||
Uint32 num_samplers,
|
|
||||||
Uint32 num_uniform_buffers) -> SDL_GPUShader* {
|
|
||||||
SDL_GPUShaderCreateInfo info{};
|
|
||||||
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
|
||||||
info.code_size = std::strlen(msl_source) + 1;
|
|
||||||
info.entrypoint = entrypoint;
|
|
||||||
info.format = SDL_GPU_SHADERFORMAT_MSL;
|
|
||||||
info.stage = stage;
|
|
||||||
info.num_samplers = num_samplers;
|
|
||||||
info.num_uniform_buffers = num_uniform_buffers;
|
|
||||||
return SDL_CreateGPUShader(device, &info);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
auto createShaderSPIRV(SDL_GPUDevice* device,
|
|
||||||
const std::uint8_t* spv_code,
|
|
||||||
std::size_t spv_size,
|
|
||||||
const char* entrypoint,
|
|
||||||
SDL_GPUShaderStage stage,
|
|
||||||
Uint32 num_samplers,
|
|
||||||
Uint32 num_uniform_buffers) -> SDL_GPUShader* {
|
|
||||||
SDL_GPUShaderCreateInfo info{};
|
|
||||||
info.code = spv_code;
|
|
||||||
info.code_size = spv_size;
|
|
||||||
info.entrypoint = entrypoint;
|
|
||||||
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
|
||||||
info.stage = stage;
|
|
||||||
info.num_samplers = num_samplers;
|
|
||||||
info.num_uniform_buffers = num_uniform_buffers;
|
|
||||||
return SDL_CreateGPUShader(device, &info);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
CrtPiPipeline::~CrtPiPipeline() { cleanup(); }
|
CrtPiPipeline::~CrtPiPipeline() { cleanup(); }
|
||||||
@@ -211,8 +22,8 @@ namespace Rendering {
|
|||||||
device_ = device;
|
device_ = device;
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
SDL_GPUShader* vert = createShaderMSL(device_, CRTPI_VERT_MSL_, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
SDL_GPUShader* frag = createShaderMSL(device_, CRTPI_FRAG_MSL_, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
SDL_GPUShader* frag = createShaderMSL(device_, Msl::kCrtpiFrag, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||||
#else
|
#else
|
||||||
SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||||
@@ -249,6 +60,10 @@ namespace Rendering {
|
|||||||
std::cerr << "CrtPiPipeline: creació del pipeline ha fallat: " << SDL_GetError() << '\n';
|
std::cerr << "CrtPiPipeline: creació del pipeline ha fallat: " << SDL_GetError() << '\n';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixem els defaults canònics abans del primer render; si Screen
|
||||||
|
// crida setParams més tard els sobreescriurà.
|
||||||
|
setParams(CrtPiParams{});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,24 +67,9 @@ namespace Rendering {
|
|||||||
SDL_GPUDevice* device_{nullptr};
|
SDL_GPUDevice* device_{nullptr};
|
||||||
SDL_GPUGraphicsPipeline* pipeline_{nullptr};
|
SDL_GPUGraphicsPipeline* pipeline_{nullptr};
|
||||||
|
|
||||||
CrtPiUniforms uniforms_{
|
// Inicialitzat amb value-init; init() el sobreescriu aplicant el
|
||||||
.scanline_weight = 6.0F,
|
// CrtPiParams{} (defaults canònics, una sola font de veritat).
|
||||||
.scanline_gap_brightness = 0.12F,
|
CrtPiUniforms uniforms_{};
|
||||||
.bloom_factor = 3.5F,
|
|
||||||
.input_gamma = 2.4F,
|
|
||||||
.output_gamma = 2.2F,
|
|
||||||
.mask_brightness = 0.80F,
|
|
||||||
.curvature_x = 0.05F,
|
|
||||||
.curvature_y = 0.10F,
|
|
||||||
.mask_type = 2,
|
|
||||||
.enable_scanlines = 1,
|
|
||||||
.enable_multisample = 1,
|
|
||||||
.enable_gamma = 1,
|
|
||||||
.enable_curvature = 0,
|
|
||||||
.enable_sharper = 0,
|
|
||||||
.texture_width = 0.0F,
|
|
||||||
.texture_height = 0.0F,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
@@ -4,203 +4,17 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/rendering/sdl3gpu/shader_factory.hpp"
|
||||||
#include "core/rendering/sdl3gpu/viewport.hpp"
|
#include "core/rendering/sdl3gpu/viewport.hpp"
|
||||||
|
|
||||||
#ifndef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
#include "core/rendering/sdl3gpu/msl/postfx_frag.msl.h"
|
||||||
|
#include "core/rendering/sdl3gpu/msl/postfx_vert.msl.h"
|
||||||
|
#else
|
||||||
#include "core/rendering/sdl3gpu/spv/postfx_frag_spv.h"
|
#include "core/rendering/sdl3gpu/spv/postfx_frag_spv.h"
|
||||||
#include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
|
#include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
// NOLINTBEGIN(readability-identifier-naming)
|
|
||||||
const char* POSTFX_VERT_MSL_ = R"(
|
|
||||||
#include <metal_stdlib>
|
|
||||||
using namespace metal;
|
|
||||||
|
|
||||||
struct PostVOut {
|
|
||||||
float4 pos [[position]];
|
|
||||||
float2 uv;
|
|
||||||
};
|
|
||||||
|
|
||||||
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
|
||||||
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
|
||||||
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
|
||||||
PostVOut out;
|
|
||||||
out.pos = float4(positions[vid], 0.0, 1.0);
|
|
||||||
out.uv = uvs[vid];
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
const char* POSTFX_FRAG_MSL_ = R"(
|
|
||||||
#include <metal_stdlib>
|
|
||||||
using namespace metal;
|
|
||||||
|
|
||||||
struct PostVOut {
|
|
||||||
float4 pos [[position]];
|
|
||||||
float2 uv;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PostFXUniforms {
|
|
||||||
float vignette_strength;
|
|
||||||
float chroma_strength;
|
|
||||||
float scanline_strength;
|
|
||||||
float screen_height;
|
|
||||||
float mask_strength;
|
|
||||||
float gamma_strength;
|
|
||||||
float curvature;
|
|
||||||
float bleeding;
|
|
||||||
float pixel_scale;
|
|
||||||
float time;
|
|
||||||
float oversample;
|
|
||||||
float flicker;
|
|
||||||
};
|
|
||||||
|
|
||||||
static float3 rgb_to_ycc(float3 rgb) {
|
|
||||||
return float3(
|
|
||||||
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
|
|
||||||
-0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f,
|
|
||||||
0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f
|
|
||||||
);
|
|
||||||
}
|
|
||||||
static float3 ycc_to_rgb(float3 ycc) {
|
|
||||||
float y = ycc.x;
|
|
||||||
float cb = ycc.y - 0.5f;
|
|
||||||
float cr = ycc.z - 0.5f;
|
|
||||||
return clamp(float3(
|
|
||||||
y + 1.402f*cr,
|
|
||||||
y - 0.344f*cb - 0.714f*cr,
|
|
||||||
y + 1.772f*cb
|
|
||||||
), 0.0f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
|
||||||
texture2d<float> scene [[texture(0)]],
|
|
||||||
sampler samp [[sampler(0)]],
|
|
||||||
constant PostFXUniforms& u [[buffer(0)]]) {
|
|
||||||
float2 uv = in.uv;
|
|
||||||
|
|
||||||
if (u.curvature > 0.0f) {
|
|
||||||
float2 c = uv - 0.5f;
|
|
||||||
float rsq = dot(c, c);
|
|
||||||
float2 dist = float2(0.05f, 0.1f) * u.curvature;
|
|
||||||
float2 barrelScale = 1.0f - 0.23f * dist;
|
|
||||||
c += c * (dist * rsq);
|
|
||||||
c *= barrelScale;
|
|
||||||
if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) {
|
|
||||||
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
|
||||||
}
|
|
||||||
uv = c + 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 base = scene.sample(samp, uv).rgb;
|
|
||||||
|
|
||||||
float3 colour;
|
|
||||||
if (u.bleeding > 0.0f) {
|
|
||||||
float tw = float(scene.get_width());
|
|
||||||
float step = u.oversample / tw;
|
|
||||||
float3 ycc = rgb_to_ycc(base);
|
|
||||||
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
|
|
||||||
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
|
|
||||||
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
|
|
||||||
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
|
|
||||||
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
|
|
||||||
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
|
||||||
} else {
|
|
||||||
colour = base;
|
|
||||||
}
|
|
||||||
|
|
||||||
float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f));
|
|
||||||
colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r;
|
|
||||||
colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b;
|
|
||||||
|
|
||||||
if (u.gamma_strength > 0.0f) {
|
|
||||||
float3 lin = pow(colour, float3(2.4f));
|
|
||||||
colour = mix(colour, lin, u.gamma_strength);
|
|
||||||
}
|
|
||||||
|
|
||||||
const float SCAN_DARK_RATIO = 0.333f;
|
|
||||||
const float SCAN_DARK_FLOOR = 0.42f;
|
|
||||||
if (u.scanline_strength > 0.0f) {
|
|
||||||
float ps = max(1.0f, round(u.pixel_scale));
|
|
||||||
float frac_in_row = fract(uv.y * u.screen_height);
|
|
||||||
float row_pos = floor(frac_in_row * ps);
|
|
||||||
float bright_rows = (ps < 2.0f) ? ps : ((ps < 3.0f) ? 1.0f : floor(ps * (1.0f - SCAN_DARK_RATIO)));
|
|
||||||
float is_dark = step(bright_rows, row_pos);
|
|
||||||
float scan = mix(1.0f, SCAN_DARK_FLOOR, is_dark);
|
|
||||||
colour *= mix(1.0f, scan, u.scanline_strength);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u.gamma_strength > 0.0f) {
|
|
||||||
float3 enc = pow(colour, float3(1.0f/2.2f));
|
|
||||||
colour = mix(colour, enc, u.gamma_strength);
|
|
||||||
}
|
|
||||||
|
|
||||||
float2 d = uv - 0.5f;
|
|
||||||
float vignette = 1.0f - dot(d, d) * u.vignette_strength;
|
|
||||||
colour *= clamp(vignette, 0.0f, 1.0f);
|
|
||||||
|
|
||||||
if (u.mask_strength > 0.0f) {
|
|
||||||
float whichMask = fract(in.pos.x * 0.3333333f);
|
|
||||||
float3 mask = float3(0.80f);
|
|
||||||
if (whichMask < 0.3333333f) mask.x = 1.0f;
|
|
||||||
else if (whichMask < 0.6666667f) mask.y = 1.0f;
|
|
||||||
else mask.z = 1.0f;
|
|
||||||
colour = mix(colour, colour * mask, u.mask_strength);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u.flicker > 0.0f) {
|
|
||||||
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
|
|
||||||
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
|
|
||||||
}
|
|
||||||
|
|
||||||
return float4(colour, 1.0f);
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
// NOLINTEND(readability-identifier-naming)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
auto createShaderMSL(SDL_GPUDevice* device,
|
|
||||||
const char* msl_source,
|
|
||||||
const char* entrypoint,
|
|
||||||
SDL_GPUShaderStage stage,
|
|
||||||
std::uint32_t num_samplers,
|
|
||||||
std::uint32_t num_uniform_buffers) -> SDL_GPUShader* {
|
|
||||||
SDL_GPUShaderCreateInfo info{};
|
|
||||||
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
|
||||||
info.code_size = std::strlen(msl_source) + 1;
|
|
||||||
info.entrypoint = entrypoint;
|
|
||||||
info.format = SDL_GPU_SHADERFORMAT_MSL;
|
|
||||||
info.stage = stage;
|
|
||||||
info.num_samplers = num_samplers;
|
|
||||||
info.num_uniform_buffers = num_uniform_buffers;
|
|
||||||
return SDL_CreateGPUShader(device, &info);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
auto createShaderSPIRV(SDL_GPUDevice* device,
|
|
||||||
const std::uint8_t* spv_code,
|
|
||||||
std::size_t spv_size,
|
|
||||||
const char* entrypoint,
|
|
||||||
SDL_GPUShaderStage stage,
|
|
||||||
std::uint32_t num_samplers,
|
|
||||||
std::uint32_t num_uniform_buffers) -> SDL_GPUShader* {
|
|
||||||
SDL_GPUShaderCreateInfo info{};
|
|
||||||
info.code = spv_code;
|
|
||||||
info.code_size = spv_size;
|
|
||||||
info.entrypoint = entrypoint;
|
|
||||||
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
|
||||||
info.stage = stage;
|
|
||||||
info.num_samplers = num_samplers;
|
|
||||||
info.num_uniform_buffers = num_uniform_buffers;
|
|
||||||
return SDL_CreateGPUShader(device, &info);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
SDL3GPUShader::~SDL3GPUShader() { cleanup(); }
|
SDL3GPUShader::~SDL3GPUShader() { cleanup(); }
|
||||||
@@ -261,8 +75,8 @@ namespace Rendering {
|
|||||||
|
|
||||||
auto SDL3GPUShader::createShaders() -> bool {
|
auto SDL3GPUShader::createShaders() -> bool {
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
vertex_shader_ = createShaderMSL(device_, POSTFX_VERT_MSL_, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
vertex_shader_ = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
fragment_shader_ = createShaderMSL(device_, POSTFX_FRAG_MSL_, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
fragment_shader_ = createShaderMSL(device_, Msl::kPostfxFrag, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||||
#else
|
#else
|
||||||
vertex_shader_ = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
vertex_shader_ = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||||
fragment_shader_ = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
fragment_shader_ = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||||
@@ -493,4 +307,8 @@ namespace Rendering {
|
|||||||
window_ = nullptr;
|
window_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto makeDefaultShaderBackend() -> std::unique_ptr<ShaderBackend> {
|
||||||
|
return std::make_unique<SDL3GPUShader>();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
@@ -60,7 +60,7 @@ namespace Rendering {
|
|||||||
// Allibera la finestra del device sense destruir el device. Cal cridar-ho
|
// Allibera la finestra del device sense destruir el device. Cal cridar-ho
|
||||||
// abans de SDL_DestroyWindow perquè el VkSurface tancat no sobrevisca la
|
// abans de SDL_DestroyWindow perquè el VkSurface tancat no sobrevisca la
|
||||||
// finestra de Wayland (vkDestroyInstance cridaria Wayland amb surface invàlida).
|
// finestra de Wayland (vkDestroyInstance cridaria Wayland amb surface invàlida).
|
||||||
void releaseWindow();
|
void releaseWindow() override;
|
||||||
|
|
||||||
[[nodiscard]] auto isReady() const -> bool override { return is_ready_; }
|
[[nodiscard]] auto isReady() const -> bool override { return is_ready_; }
|
||||||
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// Helpers de creació de shaders SDL3 GPU. Inline per evitar un .cpp
|
||||||
|
// addicional per a funcions trivials.
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
inline auto createShaderMSL(SDL_GPUDevice* device,
|
||||||
|
const char* msl_source,
|
||||||
|
const char* entrypoint,
|
||||||
|
SDL_GPUShaderStage stage,
|
||||||
|
Uint32 num_samplers,
|
||||||
|
Uint32 num_uniform_buffers) -> SDL_GPUShader* {
|
||||||
|
SDL_GPUShaderCreateInfo info{};
|
||||||
|
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
||||||
|
info.code_size = std::strlen(msl_source) + 1;
|
||||||
|
info.entrypoint = entrypoint;
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_MSL;
|
||||||
|
info.stage = stage;
|
||||||
|
info.num_samplers = num_samplers;
|
||||||
|
info.num_uniform_buffers = num_uniform_buffers;
|
||||||
|
return SDL_CreateGPUShader(device, &info);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline auto createShaderSPIRV(SDL_GPUDevice* device,
|
||||||
|
const std::uint8_t* spv_code,
|
||||||
|
std::size_t spv_size,
|
||||||
|
const char* entrypoint,
|
||||||
|
SDL_GPUShaderStage stage,
|
||||||
|
Uint32 num_samplers,
|
||||||
|
Uint32 num_uniform_buffers) -> SDL_GPUShader* {
|
||||||
|
SDL_GPUShaderCreateInfo info{};
|
||||||
|
info.code = spv_code;
|
||||||
|
info.code_size = spv_size;
|
||||||
|
info.entrypoint = entrypoint;
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||||
|
info.stage = stage;
|
||||||
|
info.num_samplers = num_samplers;
|
||||||
|
info.num_uniform_buffers = num_uniform_buffers;
|
||||||
|
return SDL_CreateGPUShader(device, &info);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
@@ -90,8 +91,17 @@ namespace Rendering {
|
|||||||
// Destrueix tots els recursos GPU (pipeline, textura, buffer, sampler, device).
|
// Destrueix tots els recursos GPU (pipeline, textura, buffer, sampler, device).
|
||||||
virtual void cleanup() = 0;
|
virtual void cleanup() = 0;
|
||||||
|
|
||||||
|
// Allibera la finestra del device sense destruir el device. Cal cridar-ho
|
||||||
|
// ABANS de SDL_DestroyWindow per evitar que vkDestroyInstance intente
|
||||||
|
// alliberar una Wayland surface ja desapareguda.
|
||||||
|
virtual void releaseWindow() = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual auto isReady() const -> bool = 0;
|
[[nodiscard]] virtual auto isReady() const -> bool = 0;
|
||||||
[[nodiscard]] virtual auto getDriverName() const -> std::string = 0;
|
[[nodiscard]] virtual auto getDriverName() const -> std::string = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fàbrica: construeix el backend per defecte del projecte (SDL3 GPU).
|
||||||
|
// Screen l'invoca sense conèixer la classe concreta.
|
||||||
|
[[nodiscard]] auto makeDefaultShaderBackend() -> std::unique_ptr<ShaderBackend>;
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
@@ -27,7 +27,6 @@ class AnimatedSprite : public MovingSprite {
|
|||||||
void update(float delta_time) override;
|
void update(float delta_time) override;
|
||||||
|
|
||||||
auto animationIsCompleted() -> bool;
|
auto animationIsCompleted() -> bool;
|
||||||
auto getCurrentAnimationSize() -> int { return static_cast<int>(animations_[current_animation_].frames.size()); }
|
|
||||||
|
|
||||||
void setCurrentAnimation(const std::string& name = "default");
|
void setCurrentAnimation(const std::string& name = "default");
|
||||||
void setCurrentAnimation(int index = 0);
|
void setCurrentAnimation(int index = 0);
|
||||||
|
|||||||
@@ -9,16 +9,18 @@ MovingSprite::MovingSprite() { Sprite::clear(); }
|
|||||||
MovingSprite::MovingSprite(std::shared_ptr<Surface> surface)
|
MovingSprite::MovingSprite(std::shared_ptr<Surface> surface)
|
||||||
: Sprite(std::move(surface)) { Sprite::clear(); }
|
: Sprite(std::move(surface)) { Sprite::clear(); }
|
||||||
|
|
||||||
|
// pos_ ja queda inicialitzat pel ctor delegat Sprite(surface, pos); només
|
||||||
|
// cal sincronitzar la posició float x_/y_ (i el flip si ve).
|
||||||
MovingSprite::MovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
MovingSprite::MovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
||||||
: Sprite(std::move(surface), pos),
|
: Sprite(std::move(surface), pos),
|
||||||
x_(pos.x),
|
x_(pos.x),
|
||||||
y_(pos.y) { Sprite::pos_ = pos; }
|
y_(pos.y) {}
|
||||||
|
|
||||||
MovingSprite::MovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip)
|
MovingSprite::MovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip)
|
||||||
: Sprite(std::move(surface), pos),
|
: Sprite(std::move(surface), pos),
|
||||||
x_(pos.x),
|
x_(pos.x),
|
||||||
y_(pos.y),
|
y_(pos.y),
|
||||||
flip_(flip) { Sprite::pos_ = pos; }
|
flip_(flip) {}
|
||||||
|
|
||||||
void MovingSprite::clear() {
|
void MovingSprite::clear() {
|
||||||
x_ = 0.0F;
|
x_ = 0.0F;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Sprite::Sprite(std::shared_ptr<Surface> surface, SDL_FRect rect)
|
|||||||
|
|
||||||
Sprite::Sprite(std::shared_ptr<Surface> surface)
|
Sprite::Sprite(std::shared_ptr<Surface> surface)
|
||||||
: surface_(std::move(surface)),
|
: surface_(std::move(surface)),
|
||||||
pos_{0.0F, 0.0F, static_cast<float>(surface_->getWidth()), static_cast<float>(surface_->getHeight())},
|
pos_{.x = 0.0F, .y = 0.0F, .w = static_cast<float>(surface_->getWidth()), .h = static_cast<float>(surface_->getHeight())},
|
||||||
clip_(pos_) {}
|
clip_(pos_) {}
|
||||||
|
|
||||||
void Sprite::render() {
|
void Sprite::render() {
|
||||||
@@ -43,7 +43,3 @@ void Sprite::clear() {
|
|||||||
pos_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
|
pos_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
|
||||||
clip_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
|
clip_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sprite::update(float delta_time) {
|
|
||||||
(void)delta_time;
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,9 @@ class Sprite {
|
|||||||
|
|
||||||
virtual ~Sprite() = default;
|
virtual ~Sprite() = default;
|
||||||
|
|
||||||
virtual void update(float delta_time);
|
// Hook d'actualització per frame. Base buida (no hi ha Sprite pur a
|
||||||
|
// actualitzar); les subclasses mòbils/animades fan override.
|
||||||
|
virtual void update(float /*delta_time*/) {}
|
||||||
virtual void render();
|
virtual void render();
|
||||||
// Renderitza substituint l'índex source_color per target_color (ús típic:
|
// Renderitza substituint l'índex source_color per target_color (ús típic:
|
||||||
// la classe Text, que recolora glifs monocroms per paràmetre).
|
// la classe Text, que recolora glifs monocroms per paràmetre).
|
||||||
@@ -30,7 +32,6 @@ class Sprite {
|
|||||||
[[nodiscard]] auto getPosition() const -> SDL_FRect { return pos_; }
|
[[nodiscard]] auto getPosition() const -> SDL_FRect { return pos_; }
|
||||||
[[nodiscard]] auto getClip() const -> SDL_FRect { return clip_; }
|
[[nodiscard]] auto getClip() const -> SDL_FRect { return clip_; }
|
||||||
[[nodiscard]] auto getSurface() const -> std::shared_ptr<Surface> { return surface_; }
|
[[nodiscard]] auto getSurface() const -> std::shared_ptr<Surface> { return surface_; }
|
||||||
auto getRect() -> SDL_FRect& { return pos_; }
|
|
||||||
|
|
||||||
void setX(float x) { pos_.x = x; }
|
void setX(float x) { pos_.x = x; }
|
||||||
void setY(float y) { pos_.y = y; }
|
void setY(float y) { pos_.y = y; }
|
||||||
@@ -39,8 +40,6 @@ class Sprite {
|
|||||||
void setPosition(float x, float y);
|
void setPosition(float x, float y);
|
||||||
void setPosition(SDL_FPoint p);
|
void setPosition(SDL_FPoint p);
|
||||||
void setPosition(SDL_FRect r) { pos_ = r; }
|
void setPosition(SDL_FRect r) { pos_ = r; }
|
||||||
void incX(float value) { pos_.x += value; }
|
|
||||||
void incY(float value) { pos_.y += value; }
|
|
||||||
|
|
||||||
void setClip(SDL_FRect rect) { clip_ = rect; }
|
void setClip(SDL_FRect rect) { clip_ = rect; }
|
||||||
void setClip(float x, float y, float w, float h) { clip_ = SDL_FRect{.x = x, .y = y, .w = w, .h = h}; }
|
void setClip(float x, float y, float w, float h) { clip_ = SDL_FRect{.x = x, .y = y, .w = w, .h = h}; }
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/rendering/canvas.hpp"
|
||||||
#include "core/rendering/gif.hpp"
|
#include "core/rendering/gif.hpp"
|
||||||
#include "core/rendering/screen.hpp"
|
|
||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "utils/utils.hpp"
|
#include "utils/utils.hpp"
|
||||||
|
|
||||||
@@ -23,10 +23,10 @@ auto loadPaletteFromGif(const std::string& file_path) -> Palette {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Palette palette = {};
|
Palette palette = {};
|
||||||
// Convertim RGB → ARGB amb canal alpha 0xFF.
|
// Convertim RGB → ARGB aplicant-hi el canal alpha opac constant.
|
||||||
const std::size_t COUNT = std::min(rgb.size(), palette.size());
|
const std::size_t COUNT = std::min(rgb.size(), palette.size());
|
||||||
for (std::size_t i = 0; i < COUNT; ++i) {
|
for (std::size_t i = 0; i < COUNT; ++i) {
|
||||||
palette[i] = 0xFF000000U | rgb[i];
|
palette[i] = Rendering::OPAQUE_ALPHA | rgb[i];
|
||||||
}
|
}
|
||||||
return palette;
|
return palette;
|
||||||
}
|
}
|
||||||
@@ -37,13 +37,9 @@ Surface::Surface(int w, int h)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Surface::Surface(const std::string& file_path) {
|
Surface::Surface(const std::string& file_path) {
|
||||||
SurfaceData loaded = loadSurface(file_path);
|
// Carreguem el fitxer una única vegada i n'extraiem pixels i paleta de
|
||||||
surface_data_ = std::make_shared<SurfaceData>(std::move(loaded));
|
// passades independents sobre el mateix buffer en memòria — abans es
|
||||||
initializeSubPalette(sub_palette_);
|
// parsejava dues voltes (loadSurface + loadPaletteFromGif).
|
||||||
palette_ = loadPaletteFromGif(file_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Surface::loadSurface(const std::string& file_path) -> SurfaceData {
|
|
||||||
auto buffer = Resource::Helper::loadFile(file_path);
|
auto buffer = Resource::Helper::loadFile(file_path);
|
||||||
if (buffer.empty()) {
|
if (buffer.empty()) {
|
||||||
throw std::runtime_error("Surface: cannot open " + file_path);
|
throw std::runtime_error("Surface: cannot open " + file_path);
|
||||||
@@ -58,9 +54,16 @@ auto Surface::loadSurface(const std::string& file_path) -> SurfaceData {
|
|||||||
|
|
||||||
auto pixels = std::shared_ptr<Uint8[]>(new Uint8[raw_pixels.size()], std::default_delete<Uint8[]>());
|
auto pixels = std::shared_ptr<Uint8[]>(new Uint8[raw_pixels.size()], std::default_delete<Uint8[]>());
|
||||||
std::memcpy(pixels.get(), raw_pixels.data(), raw_pixels.size());
|
std::memcpy(pixels.get(), raw_pixels.data(), raw_pixels.size());
|
||||||
|
surface_data_ = std::make_shared<SurfaceData>(static_cast<int>(w), static_cast<int>(h), pixels);
|
||||||
|
initializeSubPalette(sub_palette_);
|
||||||
|
|
||||||
|
std::vector<std::uint32_t> rgb = GIF::Gif::loadPalette(buffer.data());
|
||||||
|
const std::size_t COUNT = std::min(rgb.size(), palette_.size());
|
||||||
|
for (std::size_t i = 0; i < COUNT; ++i) {
|
||||||
|
palette_[i] = Rendering::OPAQUE_ALPHA | rgb[i];
|
||||||
|
}
|
||||||
|
|
||||||
printWithDots("Surface : ", getFileName(file_path), "[ LOADED ]");
|
printWithDots("Surface : ", getFileName(file_path), "[ LOADED ]");
|
||||||
return {static_cast<int>(w), static_cast<int>(h), pixels};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Surface::clear(Uint8 color) {
|
void Surface::clear(Uint8 color) {
|
||||||
@@ -78,38 +81,18 @@ auto Surface::getPixel(int x, int y) const -> Uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Surface::render(int x, int y, const SDL_FRect* src_rect, SDL_FlipMode flip) const {
|
void Surface::render(int x, int y, const SDL_FRect* src_rect, SDL_FlipMode flip) const {
|
||||||
Uint32* dst = Screen::get().getCanvasArgb();
|
// Delegem a la versió amb remap: fent source==target==transparent_color_,
|
||||||
const int DST_W = Screen::get().getCanvasWidth();
|
// qualsevol píxel opaC tindrà COLOR != source_color (els transparents ja
|
||||||
const int DST_H = Screen::get().getCanvasHeight();
|
// s'han filtrat), així que REMAPPED = sub_palette_[COLOR] — idèntic al
|
||||||
|
// camí sense remap. Això evita duplicar el bucle de blit.
|
||||||
const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0;
|
renderWithColorReplace(x, y, transparent_color_, transparent_color_, src_rect, flip);
|
||||||
const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0;
|
|
||||||
int sw = (src_rect != nullptr) ? static_cast<int>(src_rect->w) : surface_data_->width;
|
|
||||||
int sh = (src_rect != nullptr) ? static_cast<int>(src_rect->h) : surface_data_->height;
|
|
||||||
|
|
||||||
sw = std::min(sw, surface_data_->width - SX);
|
|
||||||
sh = std::min(sh, surface_data_->height - SY);
|
|
||||||
|
|
||||||
const Uint8* src = surface_data_->data.get();
|
|
||||||
for (int iy = 0; iy < sh; ++iy) {
|
|
||||||
for (int ix = 0; ix < sw; ++ix) {
|
|
||||||
const int SRC_X = (flip == SDL_FLIP_HORIZONTAL) ? (SX + sw - 1 - ix) : (SX + ix);
|
|
||||||
const int SRC_Y = (flip == SDL_FLIP_VERTICAL) ? (SY + sh - 1 - iy) : (SY + iy);
|
|
||||||
const int DST_X = x + ix;
|
|
||||||
const int DST_Y = y + iy;
|
|
||||||
if (DST_X < 0 || DST_X >= DST_W || DST_Y < 0 || DST_Y >= DST_H) { continue; }
|
|
||||||
|
|
||||||
const Uint8 COLOR = src[SRC_X + (SRC_Y * surface_data_->width)];
|
|
||||||
if (COLOR == transparent_color_) { continue; }
|
|
||||||
dst[DST_X + (DST_Y * DST_W)] = palette_[sub_palette_[COLOR]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect, SDL_FlipMode flip) const {
|
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, const SDL_FRect* src_rect, SDL_FlipMode flip) const {
|
||||||
Uint32* dst = Screen::get().getCanvasArgb();
|
const auto CANVAS = Rendering::activeCanvas();
|
||||||
const int DST_W = Screen::get().getCanvasWidth();
|
Uint32* dst = CANVAS.pixels;
|
||||||
const int DST_H = Screen::get().getCanvasHeight();
|
const int DST_W = CANVAS.width;
|
||||||
|
const int DST_H = CANVAS.height;
|
||||||
|
|
||||||
const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0;
|
const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0;
|
||||||
const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0;
|
const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0;
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ class Surface {
|
|||||||
explicit Surface(const std::string& file_path);
|
explicit Surface(const std::string& file_path);
|
||||||
~Surface() = default;
|
~Surface() = default;
|
||||||
|
|
||||||
static auto loadSurface(const std::string& file_path) -> SurfaceData;
|
|
||||||
|
|
||||||
// Vobla la regió indicada al framebuffer ARGB del joc usant la paleta propia.
|
// Vobla la regió indicada al framebuffer ARGB del joc usant la paleta propia.
|
||||||
void render(int x, int y, const SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const;
|
void render(int x, int y, const SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const;
|
||||||
// Igual que render() però substituint els píxels amb valor source_color per
|
// Igual que render() però substituint els píxels amb valor source_color per
|
||||||
@@ -58,7 +56,6 @@ class Surface {
|
|||||||
void putPixel(int x, int y, Uint8 color);
|
void putPixel(int x, int y, Uint8 color);
|
||||||
[[nodiscard]] auto getPixel(int x, int y) const -> Uint8;
|
[[nodiscard]] auto getPixel(int x, int y) const -> Uint8;
|
||||||
|
|
||||||
[[nodiscard]] auto getSurfaceData() const -> std::shared_ptr<SurfaceData> { return surface_data_; }
|
|
||||||
[[nodiscard]] auto getWidth() const -> int { return surface_data_->width; }
|
[[nodiscard]] auto getWidth() const -> int { return surface_data_->width; }
|
||||||
[[nodiscard]] auto getHeight() const -> int { return surface_data_->height; }
|
[[nodiscard]] auto getHeight() const -> int { return surface_data_->height; }
|
||||||
|
|
||||||
|
|||||||
@@ -47,26 +47,6 @@ auto Text::nextCodepoint(const std::string& s, std::size_t& pos) -> std::uint32_
|
|||||||
return cp;
|
return cp;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Text::codepointToUtf8(std::uint32_t cp) -> std::string {
|
|
||||||
std::string result;
|
|
||||||
if (cp < 0x80) {
|
|
||||||
result += static_cast<char>(cp);
|
|
||||||
} else if (cp < 0x800) {
|
|
||||||
result += static_cast<char>(0xC0 | (cp >> 6));
|
|
||||||
result += static_cast<char>(0x80 | (cp & 0x3F));
|
|
||||||
} else if (cp < 0x10000) {
|
|
||||||
result += static_cast<char>(0xE0 | (cp >> 12));
|
|
||||||
result += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
|
||||||
result += static_cast<char>(0x80 | (cp & 0x3F));
|
|
||||||
} else {
|
|
||||||
result += static_cast<char>(0xF0 | (cp >> 18));
|
|
||||||
result += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
|
|
||||||
result += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
|
||||||
result += static_cast<char>(0x80 | (cp & 0x3F));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format .fnt: claus "box_width/box_height/columns/cell_spacing/row_spacing"
|
// Format .fnt: claus "box_width/box_height/columns/cell_spacing/row_spacing"
|
||||||
// i una línia per glif amb "codepoint_decimal amplada_visual". Els glifs
|
// i una línia per glif amb "codepoint_decimal amplada_visual". Els glifs
|
||||||
// s'ordenen al png per ordre d'aparició al .fnt (glyph_index).
|
// s'ordenen al png per ordre d'aparició al .fnt (glyph_index).
|
||||||
@@ -140,27 +120,10 @@ Text::Text(const std::shared_ptr<Surface>& surface, const std::shared_ptr<File>&
|
|||||||
offset_(text_file->offset) {
|
offset_(text_file->offset) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dibuixa la cadena amb la tinta per defecte (codi 15 a la paleta del png).
|
// Dibuixa la cadena amb la tinta per defecte. Els glifs amb codepoint no
|
||||||
// Els glifs amb codepoint no registrat es substitueixen pel glif '?'.
|
// registrat es substitueixen pel glif '?'.
|
||||||
void Text::write(int x, int y, const std::string& text, int kerning, int length) {
|
void Text::write(int x, int y, const std::string& text, int kerning, int length) {
|
||||||
int shift = 0;
|
writeColored(x, y, text, DEFAULT_TEXT_COLOR, kerning, length);
|
||||||
int glyphs_done = 0;
|
|
||||||
std::size_t pos = 0;
|
|
||||||
|
|
||||||
sprite_->setY(static_cast<float>(y));
|
|
||||||
while (pos < text.size()) {
|
|
||||||
if (length != -1 && glyphs_done >= length) { break; }
|
|
||||||
std::uint32_t cp = nextCodepoint(text, pos);
|
|
||||||
auto it = offset_.find(cp);
|
|
||||||
if (it == offset_.end()) { it = offset_.find('?'); }
|
|
||||||
if (it != offset_.end()) {
|
|
||||||
sprite_->setClip(static_cast<float>(it->second.x), static_cast<float>(it->second.y), static_cast<float>(box_width_), static_cast<float>(box_height_));
|
|
||||||
sprite_->setX(static_cast<float>(x + shift));
|
|
||||||
sprite_->render(1, 15);
|
|
||||||
shift += it->second.w + kerning;
|
|
||||||
}
|
|
||||||
++glyphs_done;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int length) {
|
void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int length) {
|
||||||
@@ -172,28 +135,16 @@ void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int
|
|||||||
while (pos < text.size()) {
|
while (pos < text.size()) {
|
||||||
if (length != -1 && glyphs_done >= length) { break; }
|
if (length != -1 && glyphs_done >= length) { break; }
|
||||||
std::uint32_t cp = nextCodepoint(text, pos);
|
std::uint32_t cp = nextCodepoint(text, pos);
|
||||||
auto it = offset_.find(cp);
|
if (const Offset* off = resolveGlyph(cp); off != nullptr) {
|
||||||
if (it == offset_.end()) { it = offset_.find('?'); }
|
sprite_->setClip(static_cast<float>(off->x), static_cast<float>(off->y), static_cast<float>(box_width_), static_cast<float>(box_height_));
|
||||||
if (it != offset_.end()) {
|
|
||||||
sprite_->setClip(static_cast<float>(it->second.x), static_cast<float>(it->second.y), static_cast<float>(box_width_), static_cast<float>(box_height_));
|
|
||||||
sprite_->setX(static_cast<float>(x + shift));
|
sprite_->setX(static_cast<float>(x + shift));
|
||||||
sprite_->render(1, color);
|
sprite_->render(INK_COLOR_INDEX, color);
|
||||||
shift += it->second.w + kerning;
|
shift += off->w + kerning;
|
||||||
}
|
}
|
||||||
++glyphs_done;
|
++glyphs_done;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Text::writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance, int kerning, int length) {
|
|
||||||
writeColored(x + shadow_distance, y + shadow_distance, text, color, kerning, length);
|
|
||||||
write(x, y, text, kerning, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Text::writeCentered(int x, int y, const std::string& text, int kerning, int length) {
|
|
||||||
x -= (Text::length(text, kerning) / 2);
|
|
||||||
write(x, y, text, kerning, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int length) {
|
void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int length) {
|
||||||
const bool CENTERED = ((flags & CENTER_FLAG) == CENTER_FLAG);
|
const bool CENTERED = ((flags & CENTER_FLAG) == CENTER_FLAG);
|
||||||
const bool SHADOWED = ((flags & SHADOW_FLAG) == SHADOW_FLAG);
|
const bool SHADOWED = ((flags & SHADOW_FLAG) == SHADOW_FLAG);
|
||||||
@@ -235,21 +186,20 @@ void Text::writeMonoDigits(int x, int y, const std::string& text, Uint8 color, i
|
|||||||
sprite_->setY(static_cast<float>(y));
|
sprite_->setY(static_cast<float>(y));
|
||||||
while (pos < text.size()) {
|
while (pos < text.size()) {
|
||||||
std::uint32_t cp = nextCodepoint(text, pos);
|
std::uint32_t cp = nextCodepoint(text, pos);
|
||||||
auto it = offset_.find(cp);
|
const Offset* off = resolveGlyph(cp);
|
||||||
if (it == offset_.end()) { it = offset_.find('?'); }
|
if (off == nullptr) { continue; }
|
||||||
if (it == offset_.end()) { continue; }
|
|
||||||
|
|
||||||
const bool IS_DIGIT = (cp >= '0' && cp <= '9');
|
const bool IS_DIGIT = (cp >= '0' && cp <= '9');
|
||||||
const int GLYPH_W = it->second.w;
|
const int GLYPH_W = off->w;
|
||||||
const int CELL_W = IS_DIGIT ? digit_cell_w : GLYPH_W;
|
const int CELL_W = IS_DIGIT ? digit_cell_w : GLYPH_W;
|
||||||
const int GLYPH_X = IS_DIGIT ? x + shift + ((CELL_W - GLYPH_W) / 2) : x + shift;
|
const int GLYPH_X = IS_DIGIT ? x + shift + ((CELL_W - GLYPH_W) / 2) : x + shift;
|
||||||
|
|
||||||
sprite_->setClip(static_cast<float>(it->second.x),
|
sprite_->setClip(static_cast<float>(off->x),
|
||||||
static_cast<float>(it->second.y),
|
static_cast<float>(off->y),
|
||||||
static_cast<float>(box_width_),
|
static_cast<float>(box_width_),
|
||||||
static_cast<float>(box_height_));
|
static_cast<float>(box_height_));
|
||||||
sprite_->setX(static_cast<float>(GLYPH_X));
|
sprite_->setX(static_cast<float>(GLYPH_X));
|
||||||
sprite_->render(1, color);
|
sprite_->render(INK_COLOR_INDEX, color);
|
||||||
|
|
||||||
shift += CELL_W + kerning;
|
shift += CELL_W + kerning;
|
||||||
}
|
}
|
||||||
@@ -261,12 +211,11 @@ auto Text::lengthMonoDigits(const std::string& text, int digit_cell_w, int kerni
|
|||||||
|
|
||||||
while (pos < text.size()) {
|
while (pos < text.size()) {
|
||||||
std::uint32_t cp = nextCodepoint(text, pos);
|
std::uint32_t cp = nextCodepoint(text, pos);
|
||||||
auto it = offset_.find(cp);
|
const Offset* off = resolveGlyph(cp);
|
||||||
if (it == offset_.end()) { it = offset_.find('?'); }
|
if (off == nullptr) { continue; }
|
||||||
if (it == offset_.end()) { continue; }
|
|
||||||
|
|
||||||
const bool IS_DIGIT = (cp >= '0' && cp <= '9');
|
const bool IS_DIGIT = (cp >= '0' && cp <= '9');
|
||||||
shift += (IS_DIGIT ? digit_cell_w : it->second.w) + kerning;
|
shift += (IS_DIGIT ? digit_cell_w : off->w) + kerning;
|
||||||
}
|
}
|
||||||
|
|
||||||
return shift > 0 ? shift - kerning : 0;
|
return shift > 0 ? shift - kerning : 0;
|
||||||
@@ -278,29 +227,16 @@ auto Text::length(const std::string& text, int kerning) const -> int {
|
|||||||
|
|
||||||
while (pos < text.size()) {
|
while (pos < text.size()) {
|
||||||
std::uint32_t cp = nextCodepoint(text, pos);
|
std::uint32_t cp = nextCodepoint(text, pos);
|
||||||
auto it = offset_.find(cp);
|
if (const Offset* off = resolveGlyph(cp); off != nullptr) {
|
||||||
if (it == offset_.end()) { it = offset_.find('?'); }
|
shift += off->w + kerning;
|
||||||
if (it != offset_.end()) {
|
|
||||||
shift += it->second.w + kerning;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return shift > 0 ? shift - kerning : 0;
|
return shift > 0 ? shift - kerning : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Text::glyphWidth(std::uint32_t codepoint, int kerning) const -> int {
|
auto Text::resolveGlyph(std::uint32_t cp) const -> const Offset* {
|
||||||
auto it = offset_.find(codepoint);
|
auto it = offset_.find(cp);
|
||||||
if (it == offset_.end()) { it = offset_.find('?'); }
|
if (it == offset_.end()) { it = offset_.find('?'); }
|
||||||
if (it != offset_.end()) { return it->second.w + kerning; }
|
return (it != offset_.end()) ? &it->second : nullptr;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Text::getGlyphClip(std::uint32_t codepoint) const -> SDL_FRect {
|
|
||||||
auto it = offset_.find(codepoint);
|
|
||||||
if (it == offset_.end()) { it = offset_.find('?'); }
|
|
||||||
if (it == offset_.end()) { return {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; }
|
|
||||||
return {.x = static_cast<float>(it->second.x),
|
|
||||||
.y = static_cast<float>(it->second.y),
|
|
||||||
.w = static_cast<float>(box_width_),
|
|
||||||
.h = static_cast<float>(box_height_)};
|
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,13 @@ class Text {
|
|||||||
static constexpr Uint8 CENTER_FLAG = 4;
|
static constexpr Uint8 CENTER_FLAG = 4;
|
||||||
static constexpr Uint8 STROKE_FLAG = 8;
|
static constexpr Uint8 STROKE_FLAG = 8;
|
||||||
|
|
||||||
|
// Índex del píxel "tinta" al PNG de la font. Els glifs són monocroms
|
||||||
|
// sobre transparent i tots els píxels opacs usen aquest índex; la Surface
|
||||||
|
// el recolora al vol per a emetre el color destí.
|
||||||
|
static constexpr Uint8 INK_COLOR_INDEX = 1;
|
||||||
|
// Color per defecte a la paleta quan s'usa write() sense especificar-lo.
|
||||||
|
static constexpr Uint8 DEFAULT_TEXT_COLOR = 15;
|
||||||
|
|
||||||
Text(const std::shared_ptr<Surface>& surface, const std::string& text_file);
|
Text(const std::shared_ptr<Surface>& surface, const std::string& text_file);
|
||||||
Text(const std::shared_ptr<Surface>& surface, const std::shared_ptr<File>& text_file);
|
Text(const std::shared_ptr<Surface>& surface, const std::shared_ptr<File>& text_file);
|
||||||
~Text() = default;
|
~Text() = default;
|
||||||
@@ -47,8 +54,6 @@ class Text {
|
|||||||
|
|
||||||
void write(int x, int y, const std::string& text, int kerning = 1, int length = -1);
|
void write(int x, int y, const std::string& text, int kerning = 1, int length = -1);
|
||||||
void writeColored(int x, int y, const std::string& text, Uint8 color, int kerning = 1, int length = -1);
|
void writeColored(int x, int y, const std::string& text, Uint8 color, int kerning = 1, int length = -1);
|
||||||
void writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance = 1, int kerning = 1, int length = -1);
|
|
||||||
void writeCentered(int x, int y, const std::string& text, int kerning = 1, int length = -1);
|
|
||||||
void writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning = 1, Uint8 text_color = 0, Uint8 shadow_distance = 1, Uint8 shadow_color = 0, int length = -1);
|
void writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning = 1, Uint8 text_color = 0, Uint8 shadow_distance = 1, Uint8 shadow_color = 0, int length = -1);
|
||||||
|
|
||||||
// Dibuixa amb amplada fixa només per als dígits '0'-'9' (cadascun centrat
|
// Dibuixa amb amplada fixa només per als dígits '0'-'9' (cadascun centrat
|
||||||
@@ -59,15 +64,15 @@ class Text {
|
|||||||
[[nodiscard]] auto length(const std::string& text, int kerning = 1) const -> int;
|
[[nodiscard]] auto length(const std::string& text, int kerning = 1) const -> int;
|
||||||
[[nodiscard]] auto lengthMonoDigits(const std::string& text, int digit_cell_w, int kerning = 1) const -> int;
|
[[nodiscard]] auto lengthMonoDigits(const std::string& text, int digit_cell_w, int kerning = 1) const -> int;
|
||||||
[[nodiscard]] auto getCharacterSize() const -> int { return box_width_; }
|
[[nodiscard]] auto getCharacterSize() const -> int { return box_width_; }
|
||||||
[[nodiscard]] auto glyphWidth(std::uint32_t codepoint, int kerning = 0) const -> int;
|
|
||||||
[[nodiscard]] auto getGlyphClip(std::uint32_t codepoint) const -> SDL_FRect;
|
|
||||||
[[nodiscard]] auto getSprite() const -> Sprite* { return sprite_.get(); }
|
|
||||||
|
|
||||||
static auto loadTextFile(const std::string& file_path) -> std::shared_ptr<File>;
|
static auto loadTextFile(const std::string& file_path) -> std::shared_ptr<File>;
|
||||||
static auto codepointToUtf8(std::uint32_t cp) -> std::string;
|
|
||||||
static auto nextCodepoint(const std::string& s, std::size_t& pos) -> std::uint32_t;
|
static auto nextCodepoint(const std::string& s, std::size_t& pos) -> std::uint32_t;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Busca l'Offset del codepoint; si no hi és, cau al glif '?' com a
|
||||||
|
// substitut. Retorna nullptr si tampoc hi ha '?'.
|
||||||
|
[[nodiscard]] auto resolveGlyph(std::uint32_t cp) const -> const Offset*;
|
||||||
|
|
||||||
std::unique_ptr<Sprite> sprite_;
|
std::unique_ptr<Sprite> sprite_;
|
||||||
int box_width_{0};
|
int box_width_{0};
|
||||||
int box_height_{0};
|
int box_height_{0};
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
#include "core/rendering/render_info.hpp"
|
#include "core/rendering/render_info.hpp"
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/surface.hpp"
|
#include "core/rendering/surface.hpp"
|
||||||
|
#include "core/rendering/text.hpp"
|
||||||
#include "core/resources/resource_cache.hpp"
|
#include "core/resources/resource_cache.hpp"
|
||||||
#include "core/scene/scene.hpp"
|
#include "core/scene/scene.hpp"
|
||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/scenes/game_scene.hpp"
|
#include "game/scenes/game_scene.hpp"
|
||||||
|
#include "game/ui/ui_palette.hpp"
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
|
|
||||||
Director::Director() {
|
Director::Director() {
|
||||||
@@ -79,8 +81,15 @@ Director::Director() {
|
|||||||
// JA_Music_t que continga, així que no queden streams orfes.
|
// JA_Music_t que continga, així que no queden streams orfes.
|
||||||
Audio::init(Options::buildAudioConfig());
|
Audio::init(Options::buildAudioConfig());
|
||||||
|
|
||||||
RenderInfo::get().init();
|
// Font compartida pels overlays: construïm una sola instància de Text
|
||||||
Notifier::get().init();
|
// i la injectem als consumidors (Notifier, RenderInfo) per a que cap
|
||||||
|
// d'ells hagi de conèixer els noms dels recursos concrets.
|
||||||
|
auto hud_font_surface = Resource::Cache::get().getSurface("8bithud.gif");
|
||||||
|
auto hud_text = std::make_shared<Text>(hud_font_surface, "data/font/8bithud.fnt");
|
||||||
|
const auto& ui_palette = Resource::Cache::get().getPalette("ui");
|
||||||
|
|
||||||
|
RenderInfo::get().init(Options::buildRenderInfoFrame(UiColor::RENDER_INFO_TEXT, UiColor::RENDER_INFO_SHADOW), hud_text);
|
||||||
|
Notifier::get().init(hud_text, ui_palette);
|
||||||
|
|
||||||
current_scene_ = std::make_unique<GameScene>();
|
current_scene_ = std::make_unique<GameScene>();
|
||||||
last_ticks_ms_ = SDL_GetTicks();
|
last_ticks_ms_ = SDL_GetTicks();
|
||||||
|
|||||||
@@ -553,6 +553,21 @@ namespace Options {
|
|||||||
return Rendering::ShaderType::POSTFX;
|
return Rendering::ShaderType::POSTFX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto buildRenderInfoFrame(Uint8 text_color, Uint8 shadow_color) -> RenderInfo::Frame {
|
||||||
|
const std::string& active_preset = (video.shader.current_shader == "crtpi")
|
||||||
|
? video.shader.current_crtpi_preset
|
||||||
|
: video.shader.current_postfx_preset;
|
||||||
|
return {
|
||||||
|
.target_position = video.render_info_position,
|
||||||
|
.show_time = video.render_info_show_time,
|
||||||
|
.shader_enabled = video.shader.enabled,
|
||||||
|
.shader_name = video.shader.current_shader,
|
||||||
|
.shader_preset = active_preset,
|
||||||
|
.text_color = text_color,
|
||||||
|
.shadow_color = shadow_color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
auto buildCrtPiParams() -> Rendering::CrtPiParams {
|
auto buildCrtPiParams() -> Rendering::CrtPiParams {
|
||||||
// Si el shader està deshabilitat, retornem defaults (el dispatch anirà per
|
// Si el shader està deshabilitat, retornem defaults (el dispatch anirà per
|
||||||
// PostFX passthrough, però sent nets per si algú consulta aquests params).
|
// PostFX passthrough, però sent nets per si algú consulta aquests params).
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "core/audio/audio.hpp"
|
#include "core/audio/audio.hpp"
|
||||||
|
#include "core/rendering/render_info.hpp"
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/shader_backend.hpp"
|
#include "core/rendering/shader_backend.hpp"
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
@@ -121,6 +122,9 @@ namespace Options {
|
|||||||
[[nodiscard]] auto buildPostFXParams() -> Rendering::PostFXParams;
|
[[nodiscard]] auto buildPostFXParams() -> Rendering::PostFXParams;
|
||||||
[[nodiscard]] auto buildCrtPiParams() -> Rendering::CrtPiParams;
|
[[nodiscard]] auto buildCrtPiParams() -> Rendering::CrtPiParams;
|
||||||
[[nodiscard]] auto buildActiveShader() -> Rendering::ShaderType;
|
[[nodiscard]] auto buildActiveShader() -> Rendering::ShaderType;
|
||||||
|
// Els colors de text/ombra els decideix la UI del joc (no Options), per
|
||||||
|
// això es passen com a paràmetres.
|
||||||
|
[[nodiscard]] auto buildRenderInfoFrame(Uint8 text_color, Uint8 shadow_color) -> RenderInfo::Frame;
|
||||||
// `::Audio` és la classe global d'àudio (no `Options::Audio`, que és la
|
// `::Audio` és la classe global d'àudio (no `Options::Audio`, que és la
|
||||||
// struct serialitzable). Retornem la seua Config per passar-la a init().
|
// struct serialitzable). Retornem la seua Config per passar-la a init().
|
||||||
[[nodiscard]] auto buildAudioConfig() -> ::Audio::Config;
|
[[nodiscard]] auto buildAudioConfig() -> ::Audio::Config;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "core/resources/resource_cache.hpp"
|
#include "core/resources/resource_cache.hpp"
|
||||||
#include "game/entities/player.hpp"
|
#include "game/entities/player.hpp"
|
||||||
#include "game/map/map.hpp"
|
#include "game/map/map.hpp"
|
||||||
|
#include "game/options.hpp"
|
||||||
#include "game/ui/ui_palette.hpp"
|
#include "game/ui/ui_palette.hpp"
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
@@ -60,7 +61,7 @@ void GameScene::update(int delta_ms) {
|
|||||||
|
|
||||||
const float DT = static_cast<float>(delta_ms) / 1000.0F;
|
const float DT = static_cast<float>(delta_ms) / 1000.0F;
|
||||||
for (auto& player : players_) { player->update(DT); }
|
for (auto& player : players_) { player->update(DT); }
|
||||||
RenderInfo::get().update(delta_ms);
|
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiColor::RENDER_INFO_TEXT, UiColor::RENDER_INFO_SHADOW));
|
||||||
Notifier::get().update(delta_ms);
|
Notifier::get().update(delta_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/rendering/notifier.hpp"
|
||||||
|
|
||||||
// Temes per a les notificacions de pantalla. Un "preset" captura els dos
|
// Temes per a les notificacions de pantalla. Un "preset" captura els dos
|
||||||
// colors (text i fons, índexs sobre la paleta "ui") i el temps de visibilitat
|
// colors (text i fons, índexs sobre la paleta "ui") i el temps de visibilitat
|
||||||
// en mil·lisegons. Qualsevol component que cride Notifier::notify(text)
|
// en mil·lisegons. Per a llançar una notificació amb un preset concret,
|
||||||
// rebrà l'estil DEFAULT; si necessita un altre look, pot passar el preset per
|
// cridar Notification::notify(text, preset); si s'omet, s'usa DEFAULT.
|
||||||
// nom o especificar els camps un a un.
|
|
||||||
namespace Notification {
|
namespace Notification {
|
||||||
|
|
||||||
struct Preset {
|
struct Preset {
|
||||||
@@ -34,4 +37,10 @@ namespace Notification {
|
|||||||
.time_ms = 4000,
|
.time_ms = 4000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper de conveniència: aplica un preset (per defecte DEFAULT) cridant
|
||||||
|
// la API crua de Notifier. La direcció game → core és la correcta.
|
||||||
|
inline void notify(const std::string& text, const Preset& preset = DEFAULT) {
|
||||||
|
Notifier::get().notify(text, preset.text_color, preset.bg_color, preset.time_ms);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Notification
|
} // namespace Notification
|
||||||
@@ -30,6 +30,7 @@ namespace Defaults {
|
|||||||
TOP = 1,
|
TOP = 1,
|
||||||
BOTTOM = 2,
|
BOTTOM = 2,
|
||||||
};
|
};
|
||||||
|
constexpr int RENDER_INFO_POSITION_COUNT = 3;
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
constexpr int ZOOM = 3;
|
constexpr int ZOOM = 3;
|
||||||
|
|||||||
Reference in new issue
Block a user