refactor: JA_Sound_t RAII — buffer amb unique_ptr + SDLFreeDeleter, elimina JA_NewSound
This commit is contained in:
110
CLAUDE.md
110
CLAUDE.md
@@ -38,18 +38,25 @@ The five current objectives are:
|
|||||||
|
|
||||||
**Iron rule: zero gameplay regressions.** Each phase of the modernization must leave the game playing identically — same feel, same timings, same collisions, same scoring. See [docs/scenes-migration-plan.md](docs/scenes-migration-plan.md) for the phased plan.
|
**Iron rule: zero gameplay regressions.** Each phase of the modernization must leave the game playing identically — same feel, same timings, same collisions, same scoring. See [docs/scenes-migration-plan.md](docs/scenes-migration-plan.md) for the phased plan.
|
||||||
|
|
||||||
The current emulator-thread architecture (Director + game thread + `publishFrame` mutex/cv) is **transitional**. It will be dismantled in Phase 5 and replaced by a single-threaded `SDL_AppIterate` loop in Phase 7.
|
|
||||||
|
|
||||||
### Migration Status (2026-04-16)
|
### Migration Status (2026-04-16)
|
||||||
|
|
||||||
Phases 0–7b of the original runtime plan are **done**. Current effort is the **scene-by-scene rewrite of `source/game/modulesequence.cpp`** over a `scenes::` layer in [source/scenes/](source/scenes/):
|
**Completat.** Totes les fases del pla original (0–7) i la migració `scenes::` (Steps 0–10) estan fetes, ModuleGame és una `scenes::Scene` tick-based, el cooperative fiber s'ha eliminat, i el build emscripten/WASM arrenca i es publica a maverick.
|
||||||
|
|
||||||
- **Done**: `MortScene` (state 100), `BannerScene` (2..5), `MenuScene` (0), `IntroNewLogoScene` (255 when `use_new_logo`), `SlidesScene` (1, 7), `CreditsScene` (8), `SecretaScene` (6). Each registered in `Director::init` via `SceneRegistry`. Each removed from the legacy `ModuleSequence::Go()` switch and deleted from `modulesequence.cpp`.
|
**Arquitectura actual**:
|
||||||
- **Pending**: `IntroScene` (state 255 when `!use_new_logo` — the old JAILGAMES letter-by-letter), `IntroSpritesScene` (the Sam + momies animation with 3 random variants, hardest of the lot, currently still called from `IntroNewLogoScene::Phase::Delegate` via a temporary `doIntroSprites` exposed as `public` in `ModuleSequence`). Final cleanup of `modulesequence.cpp` comes after those two.
|
- Un sol thread (Director). Main loop via SDL3 Callback API (`SDL_MAIN_USE_CALLBACKS`): `SDL_AppInit/Iterate/Event/Quit` a [main.cpp](source/main.cpp).
|
||||||
- `SceneRegistry` lookup happens inside `gameFiberEntry()` before falling back to legacy `ModuleSequence::Go()`, with a redirect `num_piramide == 6 && diners < 200 → 7` replicated ahead of the lookup to match the legacy flow.
|
- `Director::iterate()` posseeix l'estat d'escena (`current_scene_`, `game_state_`) i fa input → tick de l'escena → `JD8_Flip` (sense yield, només converteix `screen` → `pixel_data`) → overlay → present. Tot en línia recta, zero fibers, zero mutex.
|
||||||
- For quick tests, `Options::game` exposes `piramide_inicial`, `habitacio_inicial`, `vides`, `diamants_inicial`, `diners_inicial`, `use_new_logo`, `show_title_credits` — all persisted in `config.yaml`.
|
- Totes les escenes (inclòs `ModuleGame`) implementen `scenes::Scene` amb `onEnter/tick(delta_ms)/done/nextState`.
|
||||||
|
- `ModuleSequence` (el vell dispatcher) eliminat. Despatxa via `game_state_ == 0` (gameplay → `ModuleGame`) o `game_state_ == 1` (cinemàtica → `SceneRegistry::tryCreate(num_piramide)`).
|
||||||
|
|
||||||
The scenes layer itself lives in [source/scenes/](source/scenes/): `scene.hpp` (interface), `scene_registry.hpp/.cpp`, `timeline.hpp/.cpp`, `sprite_mover.hpp/.cpp`, `frame_animator.hpp/.cpp`, `palette_fade.hpp/.cpp`, `surface_handle.hpp/.cpp`, `scene_utils.hpp/.cpp` (`playMusic`). Scenes are pure tick-based (no fibers, no `while`, no `JG_ShouldUpdate`) — the cooperative fiber still runs underneath them but `JD8_Flip()` inside the mini-while in `gameFiberEntry` is what yields. Once `IntroScene` + `IntroSpritesScene` are migrated, the fiber can be dismantled along with `ModuleGame`.
|
**Escenes migrades** (totes registrades a `Director::init` via `SceneRegistry`):
|
||||||
|
- `MortScene` (state 100) · `BannerScene` (2..5) · `MenuScene` (0) · `SlidesScene` (1, 7)
|
||||||
|
- `CreditsScene` (8) · `SecretaScene` (6) · `IntroNewLogoScene` (255, `use_new_logo=true`)
|
||||||
|
- `IntroScene` (255, `use_new_logo=false`) · `IntroSpritesScene` (sub-escena de les dues intros)
|
||||||
|
|
||||||
|
**Files d'`Options::game` exposats per a tests ràpids** (persistits a `config.yaml`):
|
||||||
|
`piramide_inicial`, `habitacio_inicial`, `vides`, `diamants_inicial`, `diners_inicial`, `use_new_logo`, `show_title_credits`.
|
||||||
|
|
||||||
|
**La capa `scenes::`** ([source/scenes/](source/scenes/)): `scene.hpp` (interfície), `scene_registry.hpp/.cpp`, `timeline`, `sprite_mover`, `frame_animator`, `palette_fade`, `surface_handle`, `scene_utils` (`playMusic`). Pures tick-based, zero while, zero `JG_ShouldUpdate`.
|
||||||
|
|
||||||
### Modernization Targets
|
### Modernization Targets
|
||||||
|
|
||||||
@@ -94,7 +101,7 @@ Flat C-style APIs (no classes), prefixed by subsystem. Being progressively conve
|
|||||||
|
|
||||||
### System Layer (`source/core/system/`)
|
### System Layer (`source/core/system/`)
|
||||||
|
|
||||||
- **Director** (`director.hpp/cpp`) — **Orchestrator singleton**. Owns main thread. Launches game thread that runs `ModuleGame`/`ModuleSequence::Go()`. Emulator-style architecture: Director runs at ~60 FPS independently, polls SDL events, updates overlay, presents frames. Game thread blocks at `JD8_Flip()` → `Director::publishFrame()` until Director consumes the frame. Director is **non-blocking**: if no new frame is available, it re-presents the last known game frame with fresh overlay on top
|
- **Director** (`director.hpp/cpp`) — **Orchestrator singleton**, únic thread del runtime. Posseeix l'estat d'escena (`current_scene_: unique_ptr<Scene>`, `game_state_`, `last_tick_ms_`) directament com a members. `iterate()` fa: poll events (via `SDL_AppEvent`) → input (Gamepad/KeyRemap/GlobalInputs/Mouse) → `JA_Update` → transició d'escena si `done()` → `scene->tick(delta_ms)` → `JD8_Flip` (converteix `screen` → `pixel_data`) → overlay → present → `SDL_Delay` al frame target. Dispatcher: `game_state_ == 0` → `new ModuleGame`, `game_state_ == 1` → `SceneRegistry::tryCreate(info::ctx.num_piramide)` (amb redirect `num_piramide == 6 && diners < 200 → 7` replicant el vell `ModuleSequence::Go`).
|
||||||
|
|
||||||
### Presentation Layer (`source/core/rendering/`)
|
### Presentation Layer (`source/core/rendering/`)
|
||||||
|
|
||||||
@@ -142,7 +149,7 @@ Follows the pattern from `jaildoctors_dilemma`, persists to YAML:
|
|||||||
| F8 | Cycle shader presets |
|
| F8 | Cycle shader presets |
|
||||||
| F9 | Toggle stretch filter (nearest ↔ linear) |
|
| F9 | Toggle stretch filter (nearest ↔ linear) |
|
||||||
| F10 | Cycle render info (off → top → bottom → off) |
|
| F10 | Cycle render info (off → top → bottom → off) |
|
||||||
| F11 | Toggle pause (Director stops resuming the game fiber + `JA_PauseMusic`/`JA_ResumeMusic`) |
|
| F11 | Toggle pause (Director skips `scene->tick()` + `JA_PauseMusic`/`JA_ResumeMusic`) |
|
||||||
| F12 | Toggle floating options menu |
|
| F12 | Toggle floating options menu |
|
||||||
| ESC | Double-press to quit (with overlay notification) / close menu if open |
|
| ESC | Double-press to quit (with overlay notification) / close menu if open |
|
||||||
| Backspace | Go up one menu level / close menu if at root |
|
| Backspace | Go up one menu level / close menu if at root |
|
||||||
@@ -150,45 +157,48 @@ Follows the pattern from `jaildoctors_dilemma`, persists to YAML:
|
|||||||
|
|
||||||
All key bindings are configurable via `Options::keys_gui` and stored in `config.yaml` (section `controls:` with SDL scancode names). Game movement keys (`Options::keys_game.up/down/left/right`) can be remapped via the CONTROLS submenu — the `KeyRemap` module mirrors custom physical keys to virtual standard scancodes so hardcoded game code keeps working.
|
All key bindings are configurable via `Options::keys_gui` and stored in `config.yaml` (section `controls:` with SDL scancode names). Game movement keys (`Options::keys_game.up/down/left/right`) can be remapped via the CONTROLS submenu — the `KeyRemap` module mirrors custom physical keys to virtual standard scancodes so hardcoded game code keeps working.
|
||||||
|
|
||||||
### Execution Model (Single-threaded Fibers)
|
### Execution Model (Single-threaded, Scene-based)
|
||||||
|
|
||||||
Since Phase 4+5, the old game thread + `publishFrame` mutex/cv has been **removed**. The game code (`ModuleGame`, `ModuleSequence`, all their `Go()` methods with internal `while` loops) runs inside a **cooperative fiber** (see [fiber.hpp](source/core/system/fiber.hpp) / [fiber.cpp](source/core/system/fiber.cpp)). The whole process is single-threaded.
|
Zero threads, zero fibers, zero mutex. Main loop via SDL3 Callback API (`SDL_MAIN_USE_CALLBACKS`) a [main.cpp](source/main.cpp). Cada frame entra pel `SDL_AppIterate` → `Director::iterate()`:
|
||||||
|
|
||||||
```
|
```
|
||||||
Main thread (only thread)
|
SDL_AppIterate → Director::iterate() {
|
||||||
─────────────────────────
|
if (quit_requested_) { scene.reset(); return false; }
|
||||||
Director::run() loop {
|
if (!context_initialized_) initGameContext();
|
||||||
SDL_PollEvent()
|
|
||||||
GlobalInputs, Mouse, KeyRemap
|
Gamepad/KeyRemap/GlobalInputs/Mouse::update
|
||||||
JA_Update() ← audio pump
|
JA_Update() ← audio pump
|
||||||
if !paused:
|
|
||||||
GameFiber::resume() ← hands control to game code
|
if (!paused_) {
|
||||||
↓ (runs until next JD8_Flip)
|
if (scene && (scene->done() || JG_Quitting()))
|
||||||
... game code runs ...
|
game_state_ = scene->nextState(); scene.reset();
|
||||||
JD8_Flip():
|
if (!scene) {
|
||||||
palette → ARGB → pixel_data
|
if (game_state_ == -1 || JG_Quitting()) return false;
|
||||||
GameFiber::yield() ← returns control to Director
|
scene = createNextScene(); ← ModuleGame o registry.tryCreate()
|
||||||
↓
|
scene->onEnter();
|
||||||
copy JD8_GetFramebuffer() → game_frame
|
}
|
||||||
|
JI_Update()
|
||||||
|
scene->tick(now - last_tick_ms_)
|
||||||
|
JD8_Flip() ← converteix screen indexat → pixel_data
|
||||||
|
memcpy pixel_data → game_frame
|
||||||
|
}
|
||||||
|
|
||||||
memcpy game_frame → presentation_buffer
|
memcpy game_frame → presentation_buffer
|
||||||
Overlay::render(presentation_buffer)
|
Overlay::render(presentation_buffer)
|
||||||
Screen::present(presentation_buffer)
|
Screen::present(presentation_buffer)
|
||||||
SDL_Delay to hit 60fps
|
SDL_Delay(frame_target - elapsed)
|
||||||
}
|
}
|
||||||
|
SDL_AppEvent → Director::handleEvent() ← events lliurats un a un per SDL
|
||||||
|
SDL_AppQuit → Director::teardown() ← Options::save + descàrrega ordenada
|
||||||
```
|
```
|
||||||
|
|
||||||
**Fiber backend** ([fiber.cpp](source/core/system/fiber.cpp)):
|
|
||||||
- **Linux / macOS**: `ucontext_t` + `makecontext`/`swapcontext` (deprecated in POSIX.1-2008 but still functional in glibc and macOS libc; warning silenced with `#pragma`).
|
|
||||||
- **Windows**: `ConvertThreadToFiber` / `CreateFiber` / `SwitchToFiber` (native Fibers API).
|
|
||||||
- **Emscripten**: not yet. Phase 7 will add an `emscripten_fiber_*` or Asyncify backend.
|
|
||||||
|
|
||||||
**Key points:**
|
**Key points:**
|
||||||
- Single-threaded: zero `std::thread`, zero `std::mutex`, zero `std::condition_variable`.
|
- `Director` posseeix `current_scene_`, `game_state_`, `last_tick_ms_`, `context_initialized_` com a members — abans vivien al stack del fiber.
|
||||||
- `JD8_Flip()` is the natural sync point: it calls `GameFiber::yield()` instead of the old blocking `publishFrame`.
|
- `JD8_Flip()` només converteix paleta + `screen` a ARGB (`pixel_data`). Ja **no fa yield** — tot corre lineal.
|
||||||
- Pause (F11) works by Director skipping `resume()`: the fiber stays frozen at its last yield, and Director keeps repainting the last frame with fresh overlay.
|
- `JG_ShouldUpdate()` encara existeix a `jgame.cpp` com a timing-gate per a `ModuleGame::Update()` (10 ms fix), però ja no fa yield. Cap caller fa spin-wait.
|
||||||
- Double buffer still exists (`game_frame` + `presentation_buffer`) because Director can present multiple frames per game frame during pause or slow sections. Eliminating it is marginal work and the extra 256 KB copy is cheap at 320×200.
|
- Pausa (F11) simplement salta el bloc de tick; overlay i present continuen, es re-presenta l'últim frame congelat.
|
||||||
- The state machine alternating `ModuleSequence` (state=1) and `ModuleGame` (state=0) now lives in `gameFiberEntry()` inside an anonymous namespace in [director.cpp](source/core/system/director.cpp), called once as the fiber entry point.
|
- Doble buffer (`game_frame` + `presentation_buffer`) es manté perquè el Director pot presentar múltiples frames per cada tick durant pausa; el cost (256 KB memcpy) és trivial a 320×200.
|
||||||
- SDL events still processed only on the main thread (which is now the only thread anyway).
|
- SDL3 Callback API compatible amb emscripten: el navegador posseeix el main loop i ens crida via `requestAnimationFrame`. Zero canvis de codi per a portabilitat.
|
||||||
|
|
||||||
### Rendering Pipeline (inside Screen::present)
|
### Rendering Pipeline (inside Screen::present)
|
||||||
|
|
||||||
@@ -238,12 +248,29 @@ JD8_Flip produces ABGR byte order: `0xFF000000 + R + (G<<8) + (B<<16)`. SDL text
|
|||||||
2. ~~**Cheats are broken (`reviu`, `alone`, `obert`)**~~: Fixed. `JI_moveCheats` tradueix `SDL_Scancode` → ASCII via `scancode_to_ascii` abans de ficar-los al buffer ([jinput.cpp:32-37, 55-61](source/core/jail/jinput.cpp#L32-L61)), i `JI_CheatActivated` compara ASCII amb ASCII.
|
2. ~~**Cheats are broken (`reviu`, `alone`, `obert`)**~~: Fixed. `JI_moveCheats` tradueix `SDL_Scancode` → ASCII via `scancode_to_ascii` abans de ficar-los al buffer ([jinput.cpp:32-37, 55-61](source/core/jail/jinput.cpp#L32-L61)), i `JI_CheatActivated` compara ASCII amb ASCII.
|
||||||
3. **No sound effects in game**: Game code never calls `JA_PlaySound*`/`JA_LoadSound` — only music via `JA_PlayMusic`/`JA_FadeOutMusic`. The SONS and VOL SONS menu items control volume of an empty channel pool. Infrastructure ready for future SFX.
|
3. **No sound effects in game**: Game code never calls `JA_PlaySound*`/`JA_LoadSound` — only music via `JA_PlayMusic`/`JA_FadeOutMusic`. The SONS and VOL SONS menu items control volume of an empty channel pool. Infrastructure ready for future SFX.
|
||||||
4. ~~**Raw `malloc`/`free` in gameplay structs**~~: Majoritàriament arreglat. `Sprite`/`Entitat` usen `std::vector<Frame>` i `std::vector<Animacio>` ([sprite.hpp](source/game/sprite.hpp)). `jfile.cpp` ja no té el global `scratch[255]` (substituït per `thread_local std::string`). L'API `file_getfilebuffer` (que tornava raw `char*` amb `malloc`) s'ha substituït per `file_readfile` que retorna `std::vector<char>` RAII — elimina els leaks silenciosos que hi havia a `JD8_LoadPalette`, `ModuleGame::Go()` i `scenes::playMusic`. Queda `jail_audio.hpp` barrejant `new`/`malloc`/`SDL_malloc` de forma pairada i correcta (no leak), pendent de polir amb `std::unique_ptr` quan toque.
|
4. ~~**Raw `malloc`/`free` in gameplay structs**~~: Majoritàriament arreglat. `Sprite`/`Entitat` usen `std::vector<Frame>` i `std::vector<Animacio>` ([sprite.hpp](source/game/sprite.hpp)). `jfile.cpp` ja no té el global `scratch[255]` (substituït per `thread_local std::string`). L'API `file_getfilebuffer` (que tornava raw `char*` amb `malloc`) s'ha substituït per `file_readfile` que retorna `std::vector<char>` RAII — elimina els leaks silenciosos que hi havia a `JD8_LoadPalette`, `ModuleGame::Go()` i `scenes::playMusic`. Queda `jail_audio.hpp` barrejant `new`/`malloc`/`SDL_malloc` de forma pairada i correcta (no leak), pendent de polir amb `std::unique_ptr` quan toque.
|
||||||
5. ~~**Blocking loops in cinematics and fades**~~: Fixed. La migració completa de `ModuleSequence::do*()` a la capa `scenes::` (Steps 1–9) ha eliminat tots els `while(!JG_ShouldUpdate())` i `wait_frame_or_skip()`. Les cinemàtiques ara són tick-based amb acumuladors ms. `JD8_FadeOut`/`JD8_FadeToPal` encara tenen el seu bucle intern de 32 passos (usat per a transicions fora d'escena com al final de `ModuleGame`); el wrapper tick-based `scenes::PaletteFade` el consumeix un pas per tick quan es crida des d'una escena.
|
5. ~~**Blocking loops in cinematics and fades**~~: Fixed. Migració completa de `ModuleSequence::do*()` a la capa `scenes::` (Steps 1–10) + `ModuleGame` també tick-based (Phase A). Tot `while(!JG_ShouldUpdate())` i `wait_frame_or_skip()` eliminat. Els fades bloquejants `JD8_FadeOut`/`JD8_FadeToPal` també eliminats (Phase B.2): només queda l'API tick-step `JD8_FadeStart*` + `JD8_FadeTickStep`, encapsulada pel wrapper `scenes::PaletteFade`. ModuleGame té fases `FadingIn`/`FadingOut` pròpies.
|
||||||
6. ~~**`SDL_AddTimer` in audio**~~: Fixed in Phase 3. `jail_audio` is now a header-only `inline` module (single `.cpp` stub only hosts the `stb_vorbis` implementation to avoid multiple definitions). Music uses true streaming via `stb_vorbis_open_memory` + `JA_PumpMusic` with a 0.5s low-water-mark instead of decoding the whole OGG into RAM. Mixing/fade update runs manually via `JA_Update()` called once per frame from `Director::run()`. Ported from the `jaildoctors_dilemma` codebase.
|
6. ~~**`SDL_AddTimer` in audio**~~: Fixed in Phase 3. `jail_audio` is now a header-only `inline` module (single `.cpp` stub only hosts the `stb_vorbis` implementation to avoid multiple definitions). Music uses true streaming via `stb_vorbis_open_memory` + `JA_PumpMusic` with a 0.5s low-water-mark instead of decoding the whole OGG into RAM. Mixing/fade update runs manually via `JA_Update()` called once per frame from `Director::iterate()`. Ported from the `jaildoctors_dilemma` codebase.
|
||||||
7. ~~**Game thread + `publishFrame` mutex/cv**~~: Fixed in Phase 4+5. Replaced by a cooperative `GameFiber` (ucontext on POSIX, Fibers API on Windows). `JD8_Flip()` calls `GameFiber::yield()`, Director calls `GameFiber::resume()` once per frame. Zero threads, zero mutexes. Emscripten fiber backend still pending for Phase 7.
|
7. ~~**Game thread + `publishFrame` mutex/cv**~~: Fixed in Phase 4+5 via cooperative `GameFiber`; **eliminated entirely in Phase B.2**. `JD8_Flip()` ja no fa yield — només converteix `screen` → `pixel_data`. Director posseeix l'estat d'escena (`current_scene_`, `game_state_`) i crida `scene->tick()` directament des d'`iterate()`. Fitxers `source/core/system/fiber.{hpp,cpp}` esborrats. Zero threads, zero mutex, zero fibers.
|
||||||
|
8. ~~**`ModuleSequence` legacy dispatcher**~~: Eliminated in Step 10. Era el vell switch per `num_piramide`, ara substituït per `SceneRegistry::tryCreate()` i dispatch directe des de `Director::iterate()`. `modulesequence.{hpp,cpp}` esborrats.
|
||||||
|
|
||||||
|
### WebAssembly Build
|
||||||
|
|
||||||
|
`make wasm` genera el build WASM via Docker (`emscripten/emsdk:latest`) i copia els 3 fitxers (`.js`/`.wasm`/`.data`) a `maverick:/home/sergio/gitea/web_jailgames/static/games/aee/wasm/`, amb un `ssh maverick './deploy.sh'` final. Output local a `dist/wasm/`.
|
||||||
|
|
||||||
|
**Diferències respecte build natiu** (a [CMakeLists.txt](CMakeLists.txt) dins `if(EMSCRIPTEN)`):
|
||||||
|
- SDL3 compilat des de font via `FetchContent` (no hi ha paquet de sistema).
|
||||||
|
- Shaders SPIR-V omesos (SDL3 GPU no suportat a WebGL2).
|
||||||
|
- `sdl3gpu_shader.cpp` exclòs dels sources — el fallback `SDL_Renderer` fa tota la presentació.
|
||||||
|
- [screen.cpp](source/core/rendering/screen.cpp) guarda `#ifndef NO_SHADERS` al voltant de l'include i les crides a `SDL3GPUShader` directes. La resta del codi va via interfície base `ShaderBackend`.
|
||||||
|
- Link flags: `--preload-file data@/data`, `-fexceptions`, `-sALLOW_MEMORY_GROWTH=1`, `-sMAX_WEBGL_VERSION=2`, `-sINITIAL_MEMORY=67108864`, `-sASSERTIONS=1`, `-sASYNCIFY=1`.
|
||||||
|
- Defines: `EMSCRIPTEN_BUILD`, `NO_SHADERS`.
|
||||||
|
|
||||||
|
**Filesystem**: MEMFS default — no persistent entre recàrregues. `file_setconfigfolder` té fallbacks robustos (`getpwuid` → `getenv("HOME")` → `/tmp`) perquè no pete quan emscripten no té `/etc/passwd`. La config es carrega per defecte cada vegada. IDBFS pendent si mai volguéssem persistència a web.
|
||||||
|
|
||||||
### Pending / Ideas for Later
|
### Pending / Ideas for Later
|
||||||
|
|
||||||
|
- **Sound effects**: infraestructura `JA_PlaySound*`/`JA_LoadSound` ja preparada. Cablejar events de gameplay (col·lisió momia, mort, recollir objecte, trencar tomba, cheat activat). Menus SONS/VOL SONS ja controlen el volum del pool.
|
||||||
|
- **IDBFS persistence a WASM**: montar `/home/web_user/.config` com a IDBFS a l'init i `FS.syncfs` després de cada save. Opcional — ara per ara la config no persistix entre recàrregues de pàgina.
|
||||||
- **Gamepad**: map Y button (North) to `P` key for Pepe character selection at title screen (only input path not covered by current mapping).
|
- **Gamepad**: map Y button (North) to `P` key for Pepe character selection at title screen (only input path not covered by current mapping).
|
||||||
- **Menu items for game**: habitacio_inicial, piramide_inicial, vides (already in `Options::game`, not exposed).
|
- **Menu items for game**: habitacio_inicial, piramide_inicial, vides (already in `Options::game`, not exposed).
|
||||||
- **Multi-language**: add `data/locale/es.yaml` / `en.yaml` and a language selector item in menu. `Locale::load()` already handles arbitrary files.
|
- **Multi-language**: add `data/locale/es.yaml` / `en.yaml` and a language selector item in menu. `Locale::load()` already handles arbitrary files.
|
||||||
@@ -251,6 +278,7 @@ JD8_Flip produces ABGR byte order: `0xFF000000 + R + (G<<8) + (B<<16)`. SDL text
|
|||||||
- **Notification persistence**: notifications clear on each new one (`showNotification` does `notifications_.clear()`). Could queue instead.
|
- **Notification persistence**: notifications clear on each new one (`showNotification` does `notifications_.clear()`). Could queue instead.
|
||||||
- **FPS counter jitter**: time segment width changes per frame (100 Hz centi updates) causes ~1-2 px horizontal jitter in centered layout. Could lock to max-width or use monospace digits.
|
- **FPS counter jitter**: time segment width changes per frame (100 Hz centi updates) causes ~1-2 px horizontal jitter in centered layout. Could lock to max-width or use monospace digits.
|
||||||
- **Notification messages partially hardcoded**: overlay/global_inputs/director now use Locale, but the window title (`Texts::WINDOW_TITLE`) and some game-layer strings remain hardcoded.
|
- **Notification messages partially hardcoded**: overlay/global_inputs/director now use Locale, but the window title (`Texts::WINDOW_TITLE`) and some game-layer strings remain hardcoded.
|
||||||
|
- **jail_audio `JA_Sound_t` RAII**: `JA_Music_t` ja està net (vector + string), però `JA_Sound_t` encara usa `Uint8*` via `SDL_LoadWAV` out-param. Petit polish per a completar la coherència RAII.
|
||||||
|
|
||||||
### Previously Fixed (kept for reference)
|
### Previously Fixed (kept for reference)
|
||||||
|
|
||||||
|
|||||||
@@ -80,8 +80,22 @@ set(APP_SOURCES
|
|||||||
|
|
||||||
# Configuración de SDL3
|
# Configuración de SDL3
|
||||||
# En macOS bundle mode usamos el xcframework (universal arm64+x86_64).
|
# En macOS bundle mode usamos el xcframework (universal arm64+x86_64).
|
||||||
# En el resto de casos, o en macOS sin bundle, usamos SDL3 del sistema via find_package.
|
# En emscripten compilamos SDL3 desde source con FetchContent (no hi ha paquet de sistema).
|
||||||
if(APPLE AND MACOS_BUNDLE)
|
# En el resto de casos, usamos SDL3 del sistema via find_package.
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
SDL3
|
||||||
|
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
|
||||||
|
GIT_TAG release-3.4.4
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
|
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||||
|
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_MakeAvailable(SDL3)
|
||||||
|
message(STATUS "SDL3: compilat des de source per a Emscripten (FetchContent)")
|
||||||
|
elseif(APPLE AND MACOS_BUNDLE)
|
||||||
set(SDL3_XCFRAMEWORK_SLICE "${CMAKE_SOURCE_DIR}/release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64")
|
set(SDL3_XCFRAMEWORK_SLICE "${CMAKE_SOURCE_DIR}/release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64")
|
||||||
message(STATUS "SDL3: usando xcframework (${SDL3_XCFRAMEWORK_SLICE})")
|
message(STATUS "SDL3: usando xcframework (${SDL3_XCFRAMEWORK_SLICE})")
|
||||||
else()
|
else()
|
||||||
@@ -89,8 +103,8 @@ else()
|
|||||||
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# --- COMPILACIÓ SHADERS SPIR-V (Linux/Windows — macOS usa Metal) ---
|
# --- COMPILACIÓ SHADERS SPIR-V (Linux/Windows — macOS usa Metal, Emscripten no suporta SDL3 GPU) ---
|
||||||
if(NOT APPLE)
|
if(NOT APPLE AND NOT EMSCRIPTEN)
|
||||||
find_program(GLSLC_EXE NAMES glslc)
|
find_program(GLSLC_EXE NAMES glslc)
|
||||||
|
|
||||||
set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
|
set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
|
||||||
@@ -137,15 +151,25 @@ if(NOT APPLE)
|
|||||||
endforeach()
|
endforeach()
|
||||||
message(STATUS "glslc no trobat — usant headers SPIR-V precompilats")
|
message(STATUS "glslc no trobat — usant headers SPIR-V precompilats")
|
||||||
endif()
|
endif()
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
message(STATUS "Emscripten: shaders SPIR-V omesos (SDL3 GPU no suportat a WebGL2)")
|
||||||
else()
|
else()
|
||||||
message(STATUS "macOS: shaders SPIR-V omesos (usa Metal)")
|
message(STATUS "macOS: shaders SPIR-V omesos (usa Metal)")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# --- EJECUTABLE ---
|
# --- EJECUTABLE ---
|
||||||
|
# A emscripten excloïm sdl3gpu_shader.cpp — SDL3 GPU no suporta WebGL2, i el
|
||||||
|
# fallback SDL_Renderer de Screen (amb NO_SHADERS) fa tota la presentació.
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
set(APP_SOURCES_WASM ${APP_SOURCES})
|
||||||
|
list(REMOVE_ITEM APP_SOURCES_WASM source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp)
|
||||||
|
add_executable(${PROJECT_NAME} ${APP_SOURCES_WASM})
|
||||||
|
else()
|
||||||
add_executable(${PROJECT_NAME} ${APP_SOURCES})
|
add_executable(${PROJECT_NAME} ${APP_SOURCES})
|
||||||
|
endif()
|
||||||
|
|
||||||
# Shaders han de compilar-se abans que l'executable (Linux/Windows amb glslc)
|
# Shaders han de compilar-se abans que l'executable (Linux/Windows amb glslc)
|
||||||
if(NOT APPLE AND GLSLC_EXE)
|
if(NOT APPLE AND NOT EMSCRIPTEN AND GLSLC_EXE)
|
||||||
add_dependencies(${PROJECT_NAME} shaders)
|
add_dependencies(${PROJECT_NAME} shaders)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -176,10 +200,30 @@ target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:RELEASE>:-Os -ffunctio
|
|||||||
# --- CONFIGURACIÓN POR PLATAFORMA ---
|
# --- CONFIGURACIÓN POR PLATAFORMA ---
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE mingw32)
|
target_link_libraries(${PROJECT_NAME} PRIVATE mingw32)
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD NO_SHADERS)
|
||||||
|
# -fexceptions: SDL3 i fkyaml llancen std::exception; sense això, `throw`
|
||||||
|
# acaba en `abort()`. També requerit al link per congruència ABI.
|
||||||
|
target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions)
|
||||||
|
target_link_options(${PROJECT_NAME} PRIVATE
|
||||||
|
"SHELL:--preload-file ${CMAKE_SOURCE_DIR}/data@/data"
|
||||||
|
-fexceptions
|
||||||
|
-sALLOW_MEMORY_GROWTH=1
|
||||||
|
-sMAX_WEBGL_VERSION=2
|
||||||
|
-sINITIAL_MEMORY=67108864
|
||||||
|
-sASSERTIONS=1
|
||||||
|
# ASYNCIFY permet que Emscripten gestione yields durant la precarga
|
||||||
|
# d'assets. El main loop del joc ja usa SDL3 Callback API i no depén
|
||||||
|
# d'Asyncify — però el preloader del `.data` sí.
|
||||||
|
-sASYNCIFY=1
|
||||||
|
)
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Ejecutable en la raíz del proyecto
|
# Ejecutable en la raíz del proyecto (solo nativos). A Emscripten queda dins build/.
|
||||||
|
if(NOT EMSCRIPTEN)
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
|
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- CLANG-FORMAT TARGETS ---
|
# --- CLANG-FORMAT TARGETS ---
|
||||||
find_program(CLANG_FORMAT_EXE NAMES clang-format)
|
find_program(CLANG_FORMAT_EXE NAMES clang-format)
|
||||||
|
|||||||
28
Makefile
28
Makefile
@@ -217,6 +217,32 @@ _macos_release:
|
|||||||
$(RMDIR) build/arm
|
$(RMDIR) build/arm
|
||||||
$(RMFILE) "$(DIST_DIR)"/rw.*
|
$(RMFILE) "$(DIST_DIR)"/rw.*
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# COMPILACIÓN PARA WEBASSEMBLY (requiere Docker amb emscripten/emsdk)
|
||||||
|
# ==============================================================================
|
||||||
|
# Genera aee.{html,js,wasm,data} a dist/wasm/. Es pot provar servint amb un
|
||||||
|
# servidor HTTP local (els navegadors no carreguen `file://` WASM):
|
||||||
|
# cd dist/wasm && python3 -m http.server 8000
|
||||||
|
# # després obrir http://localhost:8000/aee.html
|
||||||
|
wasm:
|
||||||
|
@echo "Creando release para WebAssembly - Version: $(VERSION)"
|
||||||
|
docker run --rm \
|
||||||
|
--user $(shell id -u):$(shell id -g) \
|
||||||
|
-v $(DIR_ROOT):/src \
|
||||||
|
-w /src \
|
||||||
|
emscripten/emsdk:latest \
|
||||||
|
bash -c "emcmake cmake -S . -B build/wasm -DCMAKE_BUILD_TYPE=Release && cmake --build build/wasm"
|
||||||
|
@$(MKDIR) "$(DIST_DIR)/wasm"
|
||||||
|
@cp build/wasm/$(TARGET_NAME).html $(DIST_DIR)/wasm/
|
||||||
|
@cp build/wasm/$(TARGET_NAME).js $(DIST_DIR)/wasm/
|
||||||
|
@cp build/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/
|
||||||
|
@cp build/wasm/$(TARGET_NAME).data $(DIST_DIR)/wasm/
|
||||||
|
@echo "Output: $(DIST_DIR)/wasm/$(TARGET_NAME).html"
|
||||||
|
scp $(DIST_DIR)/wasm/$(TARGET_NAME).js $(DIST_DIR)/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/$(TARGET_NAME).data \
|
||||||
|
maverick:/home/sergio/gitea/web_jailgames/static/games/aee/wasm/
|
||||||
|
ssh maverick 'cd /home/sergio/gitea/web_jailgames && ./deploy.sh'
|
||||||
|
@echo "Deployed to maverick"
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN PARA LINUX (RELEASE)
|
# COMPILACIÓN PARA LINUX (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -247,4 +273,4 @@ _linux_release:
|
|||||||
# Elimina la carpeta temporal
|
# Elimina la carpeta temporal
|
||||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
$(RMDIR) "$(RELEASE_FOLDER)"
|
||||||
|
|
||||||
.PHONY: all debug release _windows_release _linux_release _macos_release
|
.PHONY: all debug release wasm _windows_release _linux_release _macos_release
|
||||||
|
|||||||
@@ -7,12 +7,22 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define STB_VORBIS_HEADER_ONLY
|
#define STB_VORBIS_HEADER_ONLY
|
||||||
#include "external/stb_vorbis.h"
|
#include "external/stb_vorbis.h"
|
||||||
|
|
||||||
|
// Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`.
|
||||||
|
// Compatible amb `std::unique_ptr<Uint8[], SDLFreeDeleter>` — zero size
|
||||||
|
// overhead gràcies a EBO, igual que un unique_ptr amb default_delete.
|
||||||
|
struct SDLFreeDeleter {
|
||||||
|
void operator()(Uint8* p) const noexcept {
|
||||||
|
if (p) SDL_free(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// --- Public Enums ---
|
// --- Public Enums ---
|
||||||
enum JA_Channel_state {
|
enum JA_Channel_state {
|
||||||
JA_CHANNEL_INVALID,
|
JA_CHANNEL_INVALID,
|
||||||
@@ -36,7 +46,9 @@ enum JA_Music_state {
|
|||||||
struct JA_Sound_t {
|
struct JA_Sound_t {
|
||||||
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
||||||
Uint32 length{0};
|
Uint32 length{0};
|
||||||
Uint8* buffer{nullptr};
|
// Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV
|
||||||
|
// via SDL_malloc; el deleter `SDLFreeDeleter` allibera amb SDL_free.
|
||||||
|
std::unique_ptr<Uint8[], SDLFreeDeleter> buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct JA_Channel_t {
|
struct JA_Channel_t {
|
||||||
@@ -172,7 +184,7 @@ inline void JA_Update() {
|
|||||||
if (channels[i].state == JA_CHANNEL_PLAYING) {
|
if (channels[i].state == JA_CHANNEL_PLAYING) {
|
||||||
if (channels[i].times != 0) {
|
if (channels[i].times != 0) {
|
||||||
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) {
|
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) {
|
||||||
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length);
|
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer.get(), channels[i].sound->length);
|
||||||
if (channels[i].times > 0) channels[i].times--;
|
if (channels[i].times > 0) channels[i].times--;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -355,31 +367,26 @@ inline void JA_EnableMusic(const bool value) {
|
|||||||
|
|
||||||
// --- Sound Functions ---
|
// --- Sound Functions ---
|
||||||
|
|
||||||
inline JA_Sound_t* JA_NewSound(Uint8* buffer, Uint32 length) {
|
|
||||||
JA_Sound_t* sound = new JA_Sound_t();
|
|
||||||
sound->buffer = buffer;
|
|
||||||
sound->length = length;
|
|
||||||
return sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) {
|
inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) {
|
||||||
JA_Sound_t* sound = new JA_Sound_t();
|
auto sound = std::make_unique<JA_Sound_t>();
|
||||||
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &sound->buffer, &sound->length)) {
|
Uint8* raw = nullptr;
|
||||||
|
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &raw, &sound->length)) {
|
||||||
SDL_Log("Failed to load WAV from memory: %s", SDL_GetError());
|
SDL_Log("Failed to load WAV from memory: %s", SDL_GetError());
|
||||||
delete sound;
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return sound;
|
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
|
||||||
|
return sound.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline JA_Sound_t* JA_LoadSound(const char* filename) {
|
inline JA_Sound_t* JA_LoadSound(const char* filename) {
|
||||||
JA_Sound_t* sound = new JA_Sound_t();
|
auto sound = std::make_unique<JA_Sound_t>();
|
||||||
if (!SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length)) {
|
Uint8* raw = nullptr;
|
||||||
|
if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) {
|
||||||
SDL_Log("Failed to load WAV file: %s", SDL_GetError());
|
SDL_Log("Failed to load WAV file: %s", SDL_GetError());
|
||||||
delete sound;
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return sound;
|
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
|
||||||
|
return sound.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) {
|
inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) {
|
||||||
@@ -411,7 +418,7 @@ inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
|
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer.get(), channels[channel].sound->length);
|
||||||
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
|
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
|
||||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||||
|
|
||||||
@@ -423,7 +430,7 @@ inline void JA_DeleteSound(JA_Sound_t* sound) {
|
|||||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||||
if (channels[i].sound == sound) JA_StopChannel(i);
|
if (channels[i].sound == sound) JA_StopChannel(i);
|
||||||
}
|
}
|
||||||
SDL_free(sound->buffer);
|
// buffer es destrueix automàticament via RAII (SDLFreeDeleter).
|
||||||
delete sound;
|
delete sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,19 +161,32 @@ std::vector<char> file_readfile(const char* resourcename) {
|
|||||||
// Accepta rutes amb subdirectoris (ex: "jailgames/aee") i crea tota la jerarquia.
|
// Accepta rutes amb subdirectoris (ex: "jailgames/aee") i crea tota la jerarquia.
|
||||||
void file_setconfigfolder(const char* foldername) {
|
void file_setconfigfolder(const char* foldername) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
config_folder = std::string(getenv("APPDATA")) + "/" + foldername;
|
const char* base = getenv("APPDATA");
|
||||||
|
if (!base) base = "C:/";
|
||||||
|
config_folder = std::string(base) + "/" + foldername;
|
||||||
#elif __APPLE__
|
#elif __APPLE__
|
||||||
struct passwd* pw = getpwuid(getuid());
|
struct passwd* pw = getpwuid(getuid());
|
||||||
const char* homedir = pw->pw_dir;
|
const char* homedir = (pw && pw->pw_dir) ? pw->pw_dir : nullptr;
|
||||||
|
if (!homedir) homedir = getenv("HOME");
|
||||||
|
if (!homedir) homedir = "/tmp";
|
||||||
config_folder = std::string(homedir) + "/Library/Application Support/" + foldername;
|
config_folder = std::string(homedir) + "/Library/Application Support/" + foldername;
|
||||||
#elif __linux__
|
#elif __linux__
|
||||||
|
// Nota emscripten: `__linux__` també està definit, però `getpwuid` no
|
||||||
|
// troba cap /etc/passwd al MEMFS i retorna nullptr. Amb els fallbacks
|
||||||
|
// HOME → /tmp evitem crashejar al primer arranque dins del navegador.
|
||||||
|
// La config no persistirà entre recàrregues de la pàgina (MEMFS és
|
||||||
|
// volàtil); caldria IDBFS si volguéssem persistència a web.
|
||||||
struct passwd* pw = getpwuid(getuid());
|
struct passwd* pw = getpwuid(getuid());
|
||||||
const char* homedir = pw->pw_dir;
|
const char* homedir = (pw && pw->pw_dir) ? pw->pw_dir : nullptr;
|
||||||
|
if (!homedir) homedir = getenv("HOME");
|
||||||
|
if (!homedir) homedir = "/tmp";
|
||||||
config_folder = std::string(homedir) + "/.config/" + foldername;
|
config_folder = std::string(homedir) + "/.config/" + foldername;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!config_folder.empty()) {
|
||||||
std::filesystem::create_directories(config_folder);
|
std::filesystem::create_directories(config_folder);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const char* file_getconfigfolder() {
|
const char* file_getconfigfolder() {
|
||||||
thread_local std::string folder;
|
thread_local std::string folder;
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
#include "core/locale/locale.hpp"
|
#include "core/locale/locale.hpp"
|
||||||
#include "core/rendering/overlay.hpp"
|
#include "core/rendering/overlay.hpp"
|
||||||
|
#ifndef NO_SHADERS
|
||||||
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"
|
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"
|
||||||
|
#endif
|
||||||
#include "game/defines.hpp"
|
#include "game/defines.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "utils/utils.hpp"
|
#include "utils/utils.hpp"
|
||||||
@@ -56,10 +58,12 @@ Screen::~Screen() {
|
|||||||
Options::window.zoom = zoom_;
|
Options::window.zoom = zoom_;
|
||||||
Options::window.fullscreen = fullscreen_;
|
Options::window.fullscreen = fullscreen_;
|
||||||
|
|
||||||
// Destrueix el backend GPU
|
// Destrueix el backend GPU (només existeix si s'ha compilat amb shaders)
|
||||||
if (shader_backend_) {
|
if (shader_backend_) {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
auto* gpu = dynamic_cast<Rendering::SDL3GPUShader*>(shader_backend_.get());
|
auto* gpu = dynamic_cast<Rendering::SDL3GPUShader*>(shader_backend_.get());
|
||||||
if (gpu) gpu->destroy();
|
if (gpu) gpu->destroy();
|
||||||
|
#endif
|
||||||
shader_backend_.reset();
|
shader_backend_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +73,13 @@ Screen::~Screen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Screen::initShaders() {
|
void Screen::initShaders() {
|
||||||
|
#ifdef NO_SHADERS
|
||||||
|
// Build sense shaders (p.ex. emscripten/WebGL2, on SDL3 GPU no està
|
||||||
|
// disponible). Es salta tota la inicialització — shader_backend_ es
|
||||||
|
// queda nul·lptr i tots els `if (shader_backend_)` del render path
|
||||||
|
// curtcircuiten cap al fallback SDL_Renderer.
|
||||||
|
return;
|
||||||
|
#else
|
||||||
if (!Options::video.gpu_acceleration) return;
|
if (!Options::video.gpu_acceleration) return;
|
||||||
|
|
||||||
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||||
@@ -122,6 +133,7 @@ void Screen::initShaders() {
|
|||||||
|
|
||||||
applyCurrentPostFXPreset();
|
applyCurrentPostFXPreset();
|
||||||
applyCurrentCrtPiPreset();
|
applyCurrentCrtPiPreset();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::present(Uint32* pixel_data) {
|
void Screen::present(Uint32* pixel_data) {
|
||||||
|
|||||||
Reference in New Issue
Block a user