From 9a0c226c42431401039a3fbfaf26afe2e56f853e Mon Sep 17 00:00:00 2001 From: rijads Date: Sat, 3 Oct 2026 17:35:54 +0200 Subject: [PATCH] Normalize line endings to LF Twenty files had been committed with CRLF endings. .gitattributes now keeps text files LF. --- .gitattributes | 2 + DEVELOPMENT.md | 280 +++++------ Makefile | 232 ++++----- src/audio/audio.c | 470 +++++++++--------- src/audio/music.c | 1022 +++++++++++++++++++------------------- src/audio/opl.c | 816 +++++++++++++++--------------- src/platform/platform.h | 112 ++--- src/platform/test_plan.c | 292 +++++------ src/platform/test_plan.h | 28 +- src/port/i_sound.c | 338 ++++++------- src/ps5/audio_out.c | 126 ++--- src/ps5/capture.c | 292 +++++------ src/ps5/gpu.h | 42 +- src/ps5/pad.c | 174 +++---- src/ps5/present.cl | 128 ++--- src/ps5/ps5.h | 36 +- src/ps5/sce.h | 384 +++++++------- src/ps5/system.c | 404 +++++++-------- src/ps5/video.c | 408 +++++++-------- test/present_preview.c | 116 ++--- tools/console_test.py | 320 ++++++------ 21 files changed, 3012 insertions(+), 3010 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d0d6371 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto eol=lf +*.png binary diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 4581ad7..4d437d4 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,140 +1,140 @@ -# DOOM for PS5 (native) - -id Software's original `linuxdoom-1.10` source running as a native PS5 title: its own home-screen -icon, launched like a game, drawing through Sony's VideoOut and AGC (GPU) drivers, reading the -DualSense through ScePad and playing sound through AudioOut. Title ID `PPSA99666`. - -All PS5, platform, audio and test code here is written for this project. The only external code -is id's game source and the build tools listed at the end. - -## Layout - -| Path | What | -| --- | --- | -| `src/doom/` | id's original game code with the 64-bit and portability fixes listed below | -| `src/port/` | Doom's `i_*` layer (main, system, video, sound, network, controller mapping) on top of the platform API | -| `src/platform/platform.h` | The platform API: time, log, files, video present, pad, rumble, audio output | -| `src/audio/` | Sound engine: SFX mixer, MUS sequencer with DMX-style voice allocation, OPL FM synth | -| `src/ps5/` | PS5 backend: startup (`crt0.c`), system, VideoOut, AGC compute presenter, pad, AudioOut | -| `src/ps5/present.cl` | GPU kernel: palette lookup, sharp-bilinear scaling to 1080p, tiled scanout writes (gfx1010, wave64) | -| `src/host/` | Headless Linux backend for testing on the PC: PNG frames, scripted pad, WAV audio | -| `test/` | `render_music` (MUS lump to WAV) and `present_preview` (runs the GPU kernel's math on the CPU) | -| `title/` | `param.json` and the icon | -| `tools/` | Tool fetch, shareware WAD fetch, kernel embedding, deploy | - -## Build - -Everything builds in the `ps5-doom-build` image (`docker/Dockerfile`: clang/lld/llvm 18, ninja, gdb). - -```bash -docker build -t ps5-doom-build docker/ -MSYS_NO_PATHCONV=1 docker run --rm -v "$(cygpath -w "$PWD"):/src" -w /src ps5-doom-build make -j16 ps5 host -``` - -`make ps5` fetches the pinned tools and the shareware WAD on first use, then: - -1. compiles the game and the PS5 layer for `x86_64-sie-ps5` against the PS5 payload SDK headers; -2. compiles `present.cl` for `gfx1010` (wave64), links it and embeds it with `tools/embed-kernel.py`, which - reads the register values from the compiler's kernel descriptor and fails the build if the - kernel needs anything the presenter does not set up; -3. generates `libSceAgc` / `libSceAgcDriver` import stubs from `src/ps5/stubs/*.txt` (the SDK has none); -4. links a PIE, converts it to a PS5 module, signs the FSELF and assembles `dist/PPSA99666/`. - -## Testing on the PC - -`build/host/bin/doom` is the same game on the headless backend. Environment variables: -`DOOM_WADDIR`, `DOOM_SAVEDIR`, `DOOM_OUT` (frame output dir), `DOOM_CAPTURE` (frame numbers to -save as PNG plus raw indices and palette), `DOOM_FRAMES` (exit after N frames), `DOOM_AUDIO` -(absolute WAV path), `DOOM_INPUT` (pad script: `frame:BUTTON+BUTTON+LX=-32000:frames;...`). - -```bash -DOOM_OUT=out DOOM_CAPTURE=120,900 build/host/bin/doom -timedemo demo1 -``` - -For memory errors, build with AddressSanitizer: -`make host HOST_DIR=build/asan HOST_FLAGS="-O1 -g -fsanitize=address" HOST_CC="clang -fsanitize=address"`. - -## On the console - -`uv run --no-project python tools/deploy.py` uploads `dist/PPSA99666` to `/data/homebrew/PPSA99666` -(PS5Upload helper must be running), where ShadowMountPlus registers it. The package bundles Freedoom -(`tools/fetch-freedoom.sh`). id's IWADs (`DOOM.WAD`, `DOOM2.WAD`, `PLUTONIA.WAD`, `TNT.WAD`) dropped -into `/data/homebrew/PPSA99666/wads/` take priority; order: DOOM II-style id WADs, DOOM-style id -WADs, Freedoom 2, Freedoom 1; holding L2 at launch prefers the DOOM-style game. `make release` writes -`dist/PPSA99666.zip` and its `.sha256`. - -The app is sandboxed: it reads `/app0` (its folder) and writes `/download0` (config, saves and -`doom.log`). Log lines also go to the kernel log (`sceKernelDebugOutText`), readable with klogsrv. -Fatal errors show as a system notification. - -Presentation uses the AGC compute path by default. Hold **L1+R1** while the game starts to force the -CPU scaler. If a GPU frame does not complete within 500 ms, the game switches to the CPU scaler by -itself and logs why. - -## Console testing - -`tools/console_test.py ` runs the deployed title hands-free: it writes the plan as `test.cfg` -into the title folder, launches through the `doomlaunch` payload (`tools/launcher`, put into -`/data/pldmgr/payloads/doomlaunch/`), pulls captured frames over TCP from the running game (the -app listens on 9119; the PC firewall blocks the other direction), de-tiles them to PNG in -`build/test/run/`, prints the log from klogsrv (port 3232, captured to `build/test/klog.txt`), then -deletes `test.cfg`. Plan lines: `input `, `capture `, `frames ` (clean exit), -`present cpu`. Plans: `test/console-play.cfg` (menus, play, save, load), `console-cpu.cfg`, -`console-soak.cfg` (6 minutes). - -## PS5 facts learned on hardware (FW 13.00) - -- `downloadDataSize` 64 is rejected (`0x80a40087`, launch fails); 256 works. -- The libc heap of a native app is small: large blocks must come from `mmap` (the zone does). -- The sandbox refuses `access`, `chdir`, `opendir`, `dup`, `dup2` (EPERM). `open` works. Saves use - explicit `/download0/...` paths; output is captured by pointing `stdout`/`stderr` at a pipe - (`fdopen`), and klog wants one line per `sceKernelDebugOutText`. -- Scanout must be tiled (`sceVideoOutSetBufferAttribute2` tiling 0; 1 is `0x80290007`), 16 MB per - 1080p buffer. Pixel layout: 512x128 tiles with the bit interleave in `src/ps5/tiling.h`. -- GPU memory: direct memory type 12, protection 0x33, CPU writes flushed with `clflush` before GPU - use and invalidated before CPU reads of GPU output (`ps5_cache_flush`). -- Compute runs as wave64 whatever the dispatch flag says, and the GPU mishandles the gfx10.3 `null` - carry-out operand (writes are dropped). The kernel is built for gfx1010, wave64; `embed-kernel.py` - rejects wave32. GPU page faults appear in klog as `GPU Protection fault ... addr(VA)`. -- After rest mode ShadowMountPlus can lose its install hook ("TitleDir bridge unavailable"); - restarting the ShadowMountPlus payload through the Payload Manager fixes it without a reboot. - -## Controls - -| Input | In game | In menus | -| --- | --- | --- | -| Left stick | Move and strafe (analog) | Navigate | -| Right stick | Turn (analog; speed follows the Mouse Sensitivity slider) | | -| D-pad | Move and turn (digital) | Navigate (repeats when held) | -| R2 | Fire | | -| Cross / Square | Use | Select (Cross), answers yes to prompts | -| Circle | Strafe modifier | Back, answers no to prompts | -| L1 / R1 | Previous / next weapon (zoom on the automap) | | -| L2 | Run | | -| Triangle / touchpad | Automap (Square toggles follow mode on the map) | | -| Options | Menu | Close menu | - -Saving needs no keyboard: an empty slot is pre-filled with the level name, Cross saves. - -## Changes to id's code - -- 64-bit: pointers stored in `int` (config string defaults, save game pointer fields, the - `columndirectory` field of the on-disk texture struct), pointer arrays sized `count*4` - (`r_data.c`, `p_setup.c`), table alignment through `int` casts, 16-byte zone alignment. -- Undefined behaviour and latent bugs: the unterminated `sprnames` list, unsequenced event queue - updates, button array resets that cleared 8 bytes of pointers, save games written into the - screen buffers (now a dedicated 4 MB buffer). -- Portability: no `values.h`/`alloca.h`/sound server; IWAD search through `I_GetWadDirs`; config in - the working directory (the port changes into the save directory at start). -- Behaviour: 1.9 demos play (the WADs' attract demos); empty save slots get a default name; - default SFX channels 8 (the DOS default) instead of 3; Freedoom IWADs recognised; static limits - raised (visplanes, drawsegs, sprites, openings, intercepts, plats, ceilings, buttons, scrollers) - so limit-removing maps such as Freedoom's run. - -## External tools (pinned) - -- [ps5-native-app-boilerplate](https://github.com/blackbearreloaded/ps5-native-app-boilerplate) - `b1315a9`: its `ps5-native-tool` (PIE to PS5 module, FSELF signing), its clean-room `libc.prx`, - its intermediate PIE linker script, and the PS5 payload SDK v0.42 it fetches. Built in - `.deps/native-app` by `tools/fetch-native-tools.sh`; nothing from it is copied into `src/`. -- clang/lld/llvm 18 (x86_64-sie-ps5 and amdgcn targets). +# DOOM for PS5 (native) + +id Software's original `linuxdoom-1.10` source running as a native PS5 title: its own home-screen +icon, launched like a game, drawing through Sony's VideoOut and AGC (GPU) drivers, reading the +DualSense through ScePad and playing sound through AudioOut. Title ID `PPSA99666`. + +All PS5, platform, audio and test code here is written for this project. The only external code +is id's game source and the build tools listed at the end. + +## Layout + +| Path | What | +| --- | --- | +| `src/doom/` | id's original game code with the 64-bit and portability fixes listed below | +| `src/port/` | Doom's `i_*` layer (main, system, video, sound, network, controller mapping) on top of the platform API | +| `src/platform/platform.h` | The platform API: time, log, files, video present, pad, rumble, audio output | +| `src/audio/` | Sound engine: SFX mixer, MUS sequencer with DMX-style voice allocation, OPL FM synth | +| `src/ps5/` | PS5 backend: startup (`crt0.c`), system, VideoOut, AGC compute presenter, pad, AudioOut | +| `src/ps5/present.cl` | GPU kernel: palette lookup, sharp-bilinear scaling to 1080p, tiled scanout writes (gfx1010, wave64) | +| `src/host/` | Headless Linux backend for testing on the PC: PNG frames, scripted pad, WAV audio | +| `test/` | `render_music` (MUS lump to WAV) and `present_preview` (runs the GPU kernel's math on the CPU) | +| `title/` | `param.json` and the icon | +| `tools/` | Tool fetch, shareware WAD fetch, kernel embedding, deploy | + +## Build + +Everything builds in the `ps5-doom-build` image (`docker/Dockerfile`: clang/lld/llvm 18, ninja, gdb). + +```bash +docker build -t ps5-doom-build docker/ +MSYS_NO_PATHCONV=1 docker run --rm -v "$(cygpath -w "$PWD"):/src" -w /src ps5-doom-build make -j16 ps5 host +``` + +`make ps5` fetches the pinned tools and the shareware WAD on first use, then: + +1. compiles the game and the PS5 layer for `x86_64-sie-ps5` against the PS5 payload SDK headers; +2. compiles `present.cl` for `gfx1010` (wave64), links it and embeds it with `tools/embed-kernel.py`, which + reads the register values from the compiler's kernel descriptor and fails the build if the + kernel needs anything the presenter does not set up; +3. generates `libSceAgc` / `libSceAgcDriver` import stubs from `src/ps5/stubs/*.txt` (the SDK has none); +4. links a PIE, converts it to a PS5 module, signs the FSELF and assembles `dist/PPSA99666/`. + +## Testing on the PC + +`build/host/bin/doom` is the same game on the headless backend. Environment variables: +`DOOM_WADDIR`, `DOOM_SAVEDIR`, `DOOM_OUT` (frame output dir), `DOOM_CAPTURE` (frame numbers to +save as PNG plus raw indices and palette), `DOOM_FRAMES` (exit after N frames), `DOOM_AUDIO` +(absolute WAV path), `DOOM_INPUT` (pad script: `frame:BUTTON+BUTTON+LX=-32000:frames;...`). + +```bash +DOOM_OUT=out DOOM_CAPTURE=120,900 build/host/bin/doom -timedemo demo1 +``` + +For memory errors, build with AddressSanitizer: +`make host HOST_DIR=build/asan HOST_FLAGS="-O1 -g -fsanitize=address" HOST_CC="clang -fsanitize=address"`. + +## On the console + +`uv run --no-project python tools/deploy.py` uploads `dist/PPSA99666` to `/data/homebrew/PPSA99666` +(PS5Upload helper must be running), where ShadowMountPlus registers it. The package bundles Freedoom +(`tools/fetch-freedoom.sh`). id's IWADs (`DOOM.WAD`, `DOOM2.WAD`, `PLUTONIA.WAD`, `TNT.WAD`) dropped +into `/data/homebrew/PPSA99666/wads/` take priority; order: DOOM II-style id WADs, DOOM-style id +WADs, Freedoom 2, Freedoom 1; holding L2 at launch prefers the DOOM-style game. `make release` writes +`dist/PPSA99666.zip` and its `.sha256`. + +The app is sandboxed: it reads `/app0` (its folder) and writes `/download0` (config, saves and +`doom.log`). Log lines also go to the kernel log (`sceKernelDebugOutText`), readable with klogsrv. +Fatal errors show as a system notification. + +Presentation uses the AGC compute path by default. Hold **L1+R1** while the game starts to force the +CPU scaler. If a GPU frame does not complete within 500 ms, the game switches to the CPU scaler by +itself and logs why. + +## Console testing + +`tools/console_test.py ` runs the deployed title hands-free: it writes the plan as `test.cfg` +into the title folder, launches through the `doomlaunch` payload (`tools/launcher`, put into +`/data/pldmgr/payloads/doomlaunch/`), pulls captured frames over TCP from the running game (the +app listens on 9119; the PC firewall blocks the other direction), de-tiles them to PNG in +`build/test/run/`, prints the log from klogsrv (port 3232, captured to `build/test/klog.txt`), then +deletes `test.cfg`. Plan lines: `input `, `capture `, `frames ` (clean exit), +`present cpu`. Plans: `test/console-play.cfg` (menus, play, save, load), `console-cpu.cfg`, +`console-soak.cfg` (6 minutes). + +## PS5 facts learned on hardware (FW 13.00) + +- `downloadDataSize` 64 is rejected (`0x80a40087`, launch fails); 256 works. +- The libc heap of a native app is small: large blocks must come from `mmap` (the zone does). +- The sandbox refuses `access`, `chdir`, `opendir`, `dup`, `dup2` (EPERM). `open` works. Saves use + explicit `/download0/...` paths; output is captured by pointing `stdout`/`stderr` at a pipe + (`fdopen`), and klog wants one line per `sceKernelDebugOutText`. +- Scanout must be tiled (`sceVideoOutSetBufferAttribute2` tiling 0; 1 is `0x80290007`), 16 MB per + 1080p buffer. Pixel layout: 512x128 tiles with the bit interleave in `src/ps5/tiling.h`. +- GPU memory: direct memory type 12, protection 0x33, CPU writes flushed with `clflush` before GPU + use and invalidated before CPU reads of GPU output (`ps5_cache_flush`). +- Compute runs as wave64 whatever the dispatch flag says, and the GPU mishandles the gfx10.3 `null` + carry-out operand (writes are dropped). The kernel is built for gfx1010, wave64; `embed-kernel.py` + rejects wave32. GPU page faults appear in klog as `GPU Protection fault ... addr(VA)`. +- After rest mode ShadowMountPlus can lose its install hook ("TitleDir bridge unavailable"); + restarting the ShadowMountPlus payload through the Payload Manager fixes it without a reboot. + +## Controls + +| Input | In game | In menus | +| --- | --- | --- | +| Left stick | Move and strafe (analog) | Navigate | +| Right stick | Turn (analog; speed follows the Mouse Sensitivity slider) | | +| D-pad | Move and turn (digital) | Navigate (repeats when held) | +| R2 | Fire | | +| Cross / Square | Use | Select (Cross), answers yes to prompts | +| Circle | Strafe modifier | Back, answers no to prompts | +| L1 / R1 | Previous / next weapon (zoom on the automap) | | +| L2 | Run | | +| Triangle / touchpad | Automap (Square toggles follow mode on the map) | | +| Options | Menu | Close menu | + +Saving needs no keyboard: an empty slot is pre-filled with the level name, Cross saves. + +## Changes to id's code + +- 64-bit: pointers stored in `int` (config string defaults, save game pointer fields, the + `columndirectory` field of the on-disk texture struct), pointer arrays sized `count*4` + (`r_data.c`, `p_setup.c`), table alignment through `int` casts, 16-byte zone alignment. +- Undefined behaviour and latent bugs: the unterminated `sprnames` list, unsequenced event queue + updates, button array resets that cleared 8 bytes of pointers, save games written into the + screen buffers (now a dedicated 4 MB buffer). +- Portability: no `values.h`/`alloca.h`/sound server; IWAD search through `I_GetWadDirs`; config in + the working directory (the port changes into the save directory at start). +- Behaviour: 1.9 demos play (the WADs' attract demos); empty save slots get a default name; + default SFX channels 8 (the DOS default) instead of 3; Freedoom IWADs recognised; static limits + raised (visplanes, drawsegs, sprites, openings, intercepts, plats, ceilings, buttons, scrollers) + so limit-removing maps such as Freedoom's run. + +## External tools (pinned) + +- [ps5-native-app-boilerplate](https://github.com/blackbearreloaded/ps5-native-app-boilerplate) + `b1315a9`: its `ps5-native-tool` (PIE to PS5 module, FSELF signing), its clean-room `libc.prx`, + its intermediate PIE linker script, and the PS5 payload SDK v0.42 it fetches. Built in + `.deps/native-app` by `tools/fetch-native-tools.sh`; nothing from it is copied into `src/`. +- clang/lld/llvm 18 (x86_64-sie-ps5 and amdgcn targets). diff --git a/Makefile b/Makefile index 534b5d6..0c52dc1 100644 --- a/Makefile +++ b/Makefile @@ -1,116 +1,116 @@ -DOOM_SRC := $(wildcard src/doom/*.c) -PORT_SRC := $(wildcard src/port/*.c) $(wildcard src/audio/*.c) $(wildcard src/platform/*.c) -HOST_SRC := $(wildcard src/host/*.c) -PS5_SRC := $(wildcard src/ps5/*.c) - -INCLUDES := -Isrc/doom -Isrc/port -Isrc/platform -Isrc/audio -DOOM_WARNINGS := -Wall -Wno-unused-const-variable -Wno-unused-but-set-variable \ - -Wno-unused-variable -Wno-logical-not-parentheses -Wno-missing-braces -OWN_WARNINGS := -Wall -Wextra -Wno-unused-parameter - -HOST_CC := clang -HOST_FLAGS := -O2 -g -fno-strict-aliasing -MMD -MP - -HOST_DIR := build/host -HOST_OBJ := $(patsubst src/%.c,$(HOST_DIR)/%.o,$(DOOM_SRC) $(PORT_SRC) $(HOST_SRC)) - -TITLE := PPSA99666 -NATIVE := .deps/native-app -SDK := $(NATIVE)/.deps/native/ps5-payload-sdk -NATIVE_TOOL := $(NATIVE)/build/host/ps5-native-tool -RUNTIME := $(NATIVE)/runtime/libc.prx - -PS5_DIR := build/ps5 -PS5_GEN := $(PS5_DIR)/gen -PS5_STUBS := $(PS5_DIR)/stubs -PS5_OBJ := $(patsubst src/%.c,$(PS5_DIR)/%.o,$(DOOM_SRC) $(PORT_SRC) $(PS5_SRC)) -AGC_STUBS := $(patsubst src/ps5/stubs/%.txt,$(PS5_STUBS)/%.so,$(wildcard src/ps5/stubs/*.txt)) -PS5_CC := clang -target x86_64-sie-ps5 -fvisibility-nodllstorageclass=default \ - -isysroot $(SDK) -isystem $(SDK)/target/include \ - -fno-stack-protector -fno-plt -femulated-tls -ffunction-sections -fdata-sections -PS5_FLAGS := -O2 -fno-strict-aliasing -MMD -MP -APP := dist/$(TITLE) - -.PHONY: host ps5 release tools clean - -host: $(HOST_DIR)/bin/doom - -$(HOST_DIR)/bin/doom: $(HOST_OBJ) - @mkdir -p $(dir $@) - $(HOST_CC) -o $@ $^ -lm -lpthread - -$(HOST_DIR)/doom/%.o: src/doom/%.c - @mkdir -p $(dir $@) - $(HOST_CC) -std=gnu99 $(HOST_FLAGS) $(DOOM_WARNINGS) $(INCLUDES) -c $< -o $@ - -$(HOST_DIR)/%.o: src/%.c - @mkdir -p $(dir $@) - $(HOST_CC) -std=gnu11 $(HOST_FLAGS) $(OWN_WARNINGS) $(INCLUDES) -c $< -o $@ - -tools: $(NATIVE_TOOL) - -$(NATIVE_TOOL) $(RUNTIME) $(SDK)/target/lib/libkernel.so: - bash tools/fetch-native-tools.sh - -wads/DOOM1.WAD: - bash tools/fetch-shareware.sh $@ - -wads/freedoom2.wad: - bash tools/fetch-freedoom.sh wads - -ps5: $(APP)/eboot.bin - -$(PS5_STUBS)/.sdk: $(SDK)/target/lib/libkernel.so - @mkdir -p $(PS5_STUBS) - cp $(SDK)/target/lib/*.so $(PS5_STUBS)/ - @touch $@ - -$(PS5_STUBS)/%.so: src/ps5/stubs/%.txt $(PS5_STUBS)/.sdk - sed 's/.*/void &(void) {}/' $< > $(PS5_STUBS)/$*.c - $(PS5_CC) -fPIC -c $(PS5_STUBS)/$*.c -o $(PS5_STUBS)/$*.o - $(SDK)/bin/prospero-lld --shared -soname $*.sprx -o $@ $(PS5_STUBS)/$*.o - -$(PS5_GEN)/present_kernel.h: src/ps5/present.cl tools/embed-kernel.py - @mkdir -p $(PS5_GEN) - clang -target amdgcn-amd-amdhsa -mcpu=gfx1010 -mwavefrontsize64 -x cl -cl-std=CL1.2 -O3 -nogpulib \ - -c $< -o $(PS5_GEN)/present.o - ld.lld -shared $(PS5_GEN)/present.o -o $(PS5_GEN)/present.co - python3 tools/embed-kernel.py $(PS5_GEN)/present.co present $@ - -$(PS5_DIR)/ps5/gpu.o: $(PS5_GEN)/present_kernel.h - -$(PS5_DIR)/doom/%.o: src/doom/%.c $(SDK)/target/lib/libkernel.so - @mkdir -p $(dir $@) - $(PS5_CC) -std=gnu99 $(PS5_FLAGS) $(DOOM_WARNINGS) $(INCLUDES) -c $< -o $@ - -$(PS5_DIR)/%.o: src/%.c $(SDK)/target/lib/libkernel.so - @mkdir -p $(dir $@) - $(PS5_CC) -std=gnu11 $(PS5_FLAGS) $(OWN_WARNINGS) $(INCLUDES) -I$(PS5_GEN) -c $< -o $@ - -$(PS5_DIR)/llvm-pie.elf: $(PS5_OBJ) $(AGC_STUBS) src/ps5/symbols.map - $(SDK)/bin/prospero-lld -T $(NATIVE)/tooling/native/ps5-pie.ld --eh-frame-hdr --gc-sections \ - --version-script src/ps5/symbols.map -e _start -o $@ $(PS5_OBJ) \ - --as-needed $(PS5_STUBS)/*.so - -$(PS5_DIR)/eboot.elf: $(PS5_DIR)/llvm-pie.elf $(NATIVE_TOOL) - $(NATIVE_TOOL) link --in $< --out $@ --stub-dir $(PS5_STUBS) \ - --module-sdk 0x02000009 --companion-sdk 0x08050001 --file-name eboot.elf - -$(APP)/eboot.bin: $(PS5_DIR)/eboot.elf $(RUNTIME) title/param.json title/icon0.png wads/freedoom2.wad - rm -rf $(APP) - mkdir -p $(APP)/sce_sys $(APP)/sce_module $(APP)/wads - $(NATIVE_TOOL) self --sign --in $< --out $@ --magic 0x1D3D154F - cp $(RUNTIME) $(APP)/sce_module/libc.prx - cp title/param.json title/icon0.png $(APP)/sce_sys/ - cp wads/freedoom1.wad wads/freedoom2.wad wads/FREEDOOM-COPYING.txt wads/FREEDOOM-CREDITS.txt $(APP)/wads/ - $(NATIVE_TOOL) self --inspect --file $@ - $(NATIVE_TOOL) self --inspect --file $(APP)/sce_module/libc.prx - -release: $(APP)/eboot.bin - rm -f dist/$(TITLE).zip dist/$(TITLE).zip.sha256 - cd dist && python3 -m zipfile -c $(TITLE).zip $(TITLE) && sha256sum $(TITLE).zip > $(TITLE).zip.sha256 - --include $(HOST_OBJ:.o=.d) $(PS5_OBJ:.o=.d) - -clean: - rm -rf build dist +DOOM_SRC := $(wildcard src/doom/*.c) +PORT_SRC := $(wildcard src/port/*.c) $(wildcard src/audio/*.c) $(wildcard src/platform/*.c) +HOST_SRC := $(wildcard src/host/*.c) +PS5_SRC := $(wildcard src/ps5/*.c) + +INCLUDES := -Isrc/doom -Isrc/port -Isrc/platform -Isrc/audio +DOOM_WARNINGS := -Wall -Wno-unused-const-variable -Wno-unused-but-set-variable \ + -Wno-unused-variable -Wno-logical-not-parentheses -Wno-missing-braces +OWN_WARNINGS := -Wall -Wextra -Wno-unused-parameter + +HOST_CC := clang +HOST_FLAGS := -O2 -g -fno-strict-aliasing -MMD -MP + +HOST_DIR := build/host +HOST_OBJ := $(patsubst src/%.c,$(HOST_DIR)/%.o,$(DOOM_SRC) $(PORT_SRC) $(HOST_SRC)) + +TITLE := PPSA99666 +NATIVE := .deps/native-app +SDK := $(NATIVE)/.deps/native/ps5-payload-sdk +NATIVE_TOOL := $(NATIVE)/build/host/ps5-native-tool +RUNTIME := $(NATIVE)/runtime/libc.prx + +PS5_DIR := build/ps5 +PS5_GEN := $(PS5_DIR)/gen +PS5_STUBS := $(PS5_DIR)/stubs +PS5_OBJ := $(patsubst src/%.c,$(PS5_DIR)/%.o,$(DOOM_SRC) $(PORT_SRC) $(PS5_SRC)) +AGC_STUBS := $(patsubst src/ps5/stubs/%.txt,$(PS5_STUBS)/%.so,$(wildcard src/ps5/stubs/*.txt)) +PS5_CC := clang -target x86_64-sie-ps5 -fvisibility-nodllstorageclass=default \ + -isysroot $(SDK) -isystem $(SDK)/target/include \ + -fno-stack-protector -fno-plt -femulated-tls -ffunction-sections -fdata-sections +PS5_FLAGS := -O2 -fno-strict-aliasing -MMD -MP +APP := dist/$(TITLE) + +.PHONY: host ps5 release tools clean + +host: $(HOST_DIR)/bin/doom + +$(HOST_DIR)/bin/doom: $(HOST_OBJ) + @mkdir -p $(dir $@) + $(HOST_CC) -o $@ $^ -lm -lpthread + +$(HOST_DIR)/doom/%.o: src/doom/%.c + @mkdir -p $(dir $@) + $(HOST_CC) -std=gnu99 $(HOST_FLAGS) $(DOOM_WARNINGS) $(INCLUDES) -c $< -o $@ + +$(HOST_DIR)/%.o: src/%.c + @mkdir -p $(dir $@) + $(HOST_CC) -std=gnu11 $(HOST_FLAGS) $(OWN_WARNINGS) $(INCLUDES) -c $< -o $@ + +tools: $(NATIVE_TOOL) + +$(NATIVE_TOOL) $(RUNTIME) $(SDK)/target/lib/libkernel.so: + bash tools/fetch-native-tools.sh + +wads/DOOM1.WAD: + bash tools/fetch-shareware.sh $@ + +wads/freedoom2.wad: + bash tools/fetch-freedoom.sh wads + +ps5: $(APP)/eboot.bin + +$(PS5_STUBS)/.sdk: $(SDK)/target/lib/libkernel.so + @mkdir -p $(PS5_STUBS) + cp $(SDK)/target/lib/*.so $(PS5_STUBS)/ + @touch $@ + +$(PS5_STUBS)/%.so: src/ps5/stubs/%.txt $(PS5_STUBS)/.sdk + sed 's/.*/void &(void) {}/' $< > $(PS5_STUBS)/$*.c + $(PS5_CC) -fPIC -c $(PS5_STUBS)/$*.c -o $(PS5_STUBS)/$*.o + $(SDK)/bin/prospero-lld --shared -soname $*.sprx -o $@ $(PS5_STUBS)/$*.o + +$(PS5_GEN)/present_kernel.h: src/ps5/present.cl tools/embed-kernel.py + @mkdir -p $(PS5_GEN) + clang -target amdgcn-amd-amdhsa -mcpu=gfx1010 -mwavefrontsize64 -x cl -cl-std=CL1.2 -O3 -nogpulib \ + -c $< -o $(PS5_GEN)/present.o + ld.lld -shared $(PS5_GEN)/present.o -o $(PS5_GEN)/present.co + python3 tools/embed-kernel.py $(PS5_GEN)/present.co present $@ + +$(PS5_DIR)/ps5/gpu.o: $(PS5_GEN)/present_kernel.h + +$(PS5_DIR)/doom/%.o: src/doom/%.c $(SDK)/target/lib/libkernel.so + @mkdir -p $(dir $@) + $(PS5_CC) -std=gnu99 $(PS5_FLAGS) $(DOOM_WARNINGS) $(INCLUDES) -c $< -o $@ + +$(PS5_DIR)/%.o: src/%.c $(SDK)/target/lib/libkernel.so + @mkdir -p $(dir $@) + $(PS5_CC) -std=gnu11 $(PS5_FLAGS) $(OWN_WARNINGS) $(INCLUDES) -I$(PS5_GEN) -c $< -o $@ + +$(PS5_DIR)/llvm-pie.elf: $(PS5_OBJ) $(AGC_STUBS) src/ps5/symbols.map + $(SDK)/bin/prospero-lld -T $(NATIVE)/tooling/native/ps5-pie.ld --eh-frame-hdr --gc-sections \ + --version-script src/ps5/symbols.map -e _start -o $@ $(PS5_OBJ) \ + --as-needed $(PS5_STUBS)/*.so + +$(PS5_DIR)/eboot.elf: $(PS5_DIR)/llvm-pie.elf $(NATIVE_TOOL) + $(NATIVE_TOOL) link --in $< --out $@ --stub-dir $(PS5_STUBS) \ + --module-sdk 0x02000009 --companion-sdk 0x08050001 --file-name eboot.elf + +$(APP)/eboot.bin: $(PS5_DIR)/eboot.elf $(RUNTIME) title/param.json title/icon0.png wads/freedoom2.wad + rm -rf $(APP) + mkdir -p $(APP)/sce_sys $(APP)/sce_module $(APP)/wads + $(NATIVE_TOOL) self --sign --in $< --out $@ --magic 0x1D3D154F + cp $(RUNTIME) $(APP)/sce_module/libc.prx + cp title/param.json title/icon0.png $(APP)/sce_sys/ + cp wads/freedoom1.wad wads/freedoom2.wad wads/FREEDOOM-COPYING.txt wads/FREEDOOM-CREDITS.txt $(APP)/wads/ + $(NATIVE_TOOL) self --inspect --file $@ + $(NATIVE_TOOL) self --inspect --file $(APP)/sce_module/libc.prx + +release: $(APP)/eboot.bin + rm -f dist/$(TITLE).zip dist/$(TITLE).zip.sha256 + cd dist && python3 -m zipfile -c $(TITLE).zip $(TITLE) && sha256sum $(TITLE).zip > $(TITLE).zip.sha256 + +-include $(HOST_OBJ:.o=.d) $(PS5_OBJ:.o=.d) + +clean: + rm -rf build dist diff --git a/src/audio/audio.c b/src/audio/audio.c index 786c892..6e80572 100644 --- a/src/audio/audio.c +++ b/src/audio/audio.c @@ -1,235 +1,235 @@ -#include -#include -#include - -#include "audio.h" -#include "music.h" -#include "platform.h" - -#define SFX_CHANNELS 16 -#define BLOCK_FRAMES 512 -#define MUSIC_GAIN 0.7f - -typedef struct -{ - const uint8_t *samples; - uint32_t length; - uint64_t position, step; - int rate, left, right; - int handle; -} sfx_t; - -static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; -static sfx_t sfx[SFX_CHANNELS]; -static int serial; -static int music_ready; -static float music_block[BLOCK_FRAMES]; - -static void set_params(sfx_t *s, int volume, int sep, int pitch) -{ - int left, right, swing; - - s->step = (uint64_t)(s->rate * pow(2.0, (pitch - 128) / 64.0) / AUDIO_RATE * 4294967296.0); - - swing = sep + 1; - left = volume - ((volume * swing * swing) >> 16); - swing -= 257; - right = volume - ((volume * swing * swing) >> 16); - s->left = left < 0 ? 0 : left; - s->right = right < 0 ? 0 : right; -} - -static sfx_t *find(int handle) -{ - sfx_t *s; - - if (handle < 0) - return NULL; - s = &sfx[handle % SFX_CHANNELS]; - return s->samples && s->handle == handle ? s : NULL; -} - -static int clip(int32_t value) -{ - return value > 32767 ? 32767 : value < -32768 ? -32768 : value; -} - -static void mix_block(int16_t *out, int frames) -{ - int i, c; - - if (music_ready) - music_render(music_block, frames); - else - memset(music_block, 0, frames * sizeof(*music_block)); - - for (i = 0; i < frames; i++) - { - int32_t music = (int32_t)(music_block[i] * MUSIC_GAIN * 32767.0f); - int32_t left = music, right = music; - - for (c = 0; c < SFX_CHANNELS; c++) - { - sfx_t *s = &sfx[c]; - uint32_t index; - int32_t a, b, sample; - - if (!s->samples) - continue; - index = (uint32_t)(s->position >> 32); - if (index >= s->length) - { - s->samples = NULL; - continue; - } - a = s->samples[index] - 128; - b = index + 1 < s->length ? s->samples[index + 1] - 128 : a; - sample = (a << 8) + (int32_t)(((int64_t)(b - a) * (uint32_t)s->position) >> 24); - left += sample * s->left / 127; - right += sample * s->right / 127; - s->position += s->step; - } - out[i * 2] = (int16_t)clip(left); - out[i * 2 + 1] = (int16_t)clip(right); - } -} - -static void render(int16_t *stereo, int frames, void *user) -{ - (void)user; - while (frames > 0) - { - int count = frames > BLOCK_FRAMES ? BLOCK_FRAMES : frames; - - pthread_mutex_lock(&lock); - mix_block(stereo, count); - pthread_mutex_unlock(&lock); - stereo += count * 2; - frames -= count; - } -} - -int audio_init(const uint8_t *genmidi, int genmidi_length) -{ - music_ready = music_init(AUDIO_RATE, genmidi, genmidi_length) == 0; - if (!music_ready) - plat_log("audio: no usable GENMIDI, music disabled\n"); - if (plat_audio_init(AUDIO_RATE, render, NULL)) - { - plat_log("audio: no output device, sound disabled\n"); - return -1; - } - return 0; -} - -void audio_shutdown(void) -{ - plat_audio_shutdown(); - if (music_ready) - music_shutdown(); - music_ready = 0; -} - -int audio_sfx_start(const uint8_t *samples, int length, int rate, int volume, int sep, int pitch) -{ - sfx_t *s = NULL; - int c, handle; - - pthread_mutex_lock(&lock); - for (c = 0; c < SFX_CHANNELS; c++) - if (!sfx[c].samples) - { - s = &sfx[c]; - break; - } - if (!s) - { - s = &sfx[0]; - for (c = 1; c < SFX_CHANNELS; c++) - if (sfx[c].handle < s->handle) - s = &sfx[c]; - } - - serial++; - handle = serial * SFX_CHANNELS + (int)(s - sfx); - s->samples = samples; - s->length = (uint32_t)length; - s->position = 0; - s->handle = handle; - s->rate = rate; - set_params(s, volume, sep, pitch); - pthread_mutex_unlock(&lock); - return handle; -} - -void audio_sfx_stop(int handle) -{ - sfx_t *s; - - pthread_mutex_lock(&lock); - if ((s = find(handle))) - s->samples = NULL; - pthread_mutex_unlock(&lock); -} - -int audio_sfx_playing(int handle) -{ - int playing; - - pthread_mutex_lock(&lock); - playing = find(handle) != NULL; - pthread_mutex_unlock(&lock); - return playing; -} - -void audio_sfx_update(int handle, int volume, int sep, int pitch) -{ - sfx_t *s; - - pthread_mutex_lock(&lock); - if ((s = find(handle))) - set_params(s, volume, sep, pitch); - pthread_mutex_unlock(&lock); -} - -int audio_music_load(const uint8_t *mus) -{ - int result; - - pthread_mutex_lock(&lock); - result = music_ready ? music_load(mus) : -1; - pthread_mutex_unlock(&lock); - return result; -} - -void audio_music_play(int looping) -{ - pthread_mutex_lock(&lock); - if (music_ready) - music_play(looping); - pthread_mutex_unlock(&lock); -} - -void audio_music_stop(void) -{ - pthread_mutex_lock(&lock); - if (music_ready) - music_stop(); - pthread_mutex_unlock(&lock); -} - -void audio_music_pause(int paused) -{ - pthread_mutex_lock(&lock); - if (music_ready) - music_pause(paused); - pthread_mutex_unlock(&lock); -} - -void audio_music_volume(int volume) -{ - pthread_mutex_lock(&lock); - if (music_ready) - music_volume(volume); - pthread_mutex_unlock(&lock); -} +#include +#include +#include + +#include "audio.h" +#include "music.h" +#include "platform.h" + +#define SFX_CHANNELS 16 +#define BLOCK_FRAMES 512 +#define MUSIC_GAIN 0.7f + +typedef struct +{ + const uint8_t *samples; + uint32_t length; + uint64_t position, step; + int rate, left, right; + int handle; +} sfx_t; + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static sfx_t sfx[SFX_CHANNELS]; +static int serial; +static int music_ready; +static float music_block[BLOCK_FRAMES]; + +static void set_params(sfx_t *s, int volume, int sep, int pitch) +{ + int left, right, swing; + + s->step = (uint64_t)(s->rate * pow(2.0, (pitch - 128) / 64.0) / AUDIO_RATE * 4294967296.0); + + swing = sep + 1; + left = volume - ((volume * swing * swing) >> 16); + swing -= 257; + right = volume - ((volume * swing * swing) >> 16); + s->left = left < 0 ? 0 : left; + s->right = right < 0 ? 0 : right; +} + +static sfx_t *find(int handle) +{ + sfx_t *s; + + if (handle < 0) + return NULL; + s = &sfx[handle % SFX_CHANNELS]; + return s->samples && s->handle == handle ? s : NULL; +} + +static int clip(int32_t value) +{ + return value > 32767 ? 32767 : value < -32768 ? -32768 : value; +} + +static void mix_block(int16_t *out, int frames) +{ + int i, c; + + if (music_ready) + music_render(music_block, frames); + else + memset(music_block, 0, frames * sizeof(*music_block)); + + for (i = 0; i < frames; i++) + { + int32_t music = (int32_t)(music_block[i] * MUSIC_GAIN * 32767.0f); + int32_t left = music, right = music; + + for (c = 0; c < SFX_CHANNELS; c++) + { + sfx_t *s = &sfx[c]; + uint32_t index; + int32_t a, b, sample; + + if (!s->samples) + continue; + index = (uint32_t)(s->position >> 32); + if (index >= s->length) + { + s->samples = NULL; + continue; + } + a = s->samples[index] - 128; + b = index + 1 < s->length ? s->samples[index + 1] - 128 : a; + sample = (a << 8) + (int32_t)(((int64_t)(b - a) * (uint32_t)s->position) >> 24); + left += sample * s->left / 127; + right += sample * s->right / 127; + s->position += s->step; + } + out[i * 2] = (int16_t)clip(left); + out[i * 2 + 1] = (int16_t)clip(right); + } +} + +static void render(int16_t *stereo, int frames, void *user) +{ + (void)user; + while (frames > 0) + { + int count = frames > BLOCK_FRAMES ? BLOCK_FRAMES : frames; + + pthread_mutex_lock(&lock); + mix_block(stereo, count); + pthread_mutex_unlock(&lock); + stereo += count * 2; + frames -= count; + } +} + +int audio_init(const uint8_t *genmidi, int genmidi_length) +{ + music_ready = music_init(AUDIO_RATE, genmidi, genmidi_length) == 0; + if (!music_ready) + plat_log("audio: no usable GENMIDI, music disabled\n"); + if (plat_audio_init(AUDIO_RATE, render, NULL)) + { + plat_log("audio: no output device, sound disabled\n"); + return -1; + } + return 0; +} + +void audio_shutdown(void) +{ + plat_audio_shutdown(); + if (music_ready) + music_shutdown(); + music_ready = 0; +} + +int audio_sfx_start(const uint8_t *samples, int length, int rate, int volume, int sep, int pitch) +{ + sfx_t *s = NULL; + int c, handle; + + pthread_mutex_lock(&lock); + for (c = 0; c < SFX_CHANNELS; c++) + if (!sfx[c].samples) + { + s = &sfx[c]; + break; + } + if (!s) + { + s = &sfx[0]; + for (c = 1; c < SFX_CHANNELS; c++) + if (sfx[c].handle < s->handle) + s = &sfx[c]; + } + + serial++; + handle = serial * SFX_CHANNELS + (int)(s - sfx); + s->samples = samples; + s->length = (uint32_t)length; + s->position = 0; + s->handle = handle; + s->rate = rate; + set_params(s, volume, sep, pitch); + pthread_mutex_unlock(&lock); + return handle; +} + +void audio_sfx_stop(int handle) +{ + sfx_t *s; + + pthread_mutex_lock(&lock); + if ((s = find(handle))) + s->samples = NULL; + pthread_mutex_unlock(&lock); +} + +int audio_sfx_playing(int handle) +{ + int playing; + + pthread_mutex_lock(&lock); + playing = find(handle) != NULL; + pthread_mutex_unlock(&lock); + return playing; +} + +void audio_sfx_update(int handle, int volume, int sep, int pitch) +{ + sfx_t *s; + + pthread_mutex_lock(&lock); + if ((s = find(handle))) + set_params(s, volume, sep, pitch); + pthread_mutex_unlock(&lock); +} + +int audio_music_load(const uint8_t *mus) +{ + int result; + + pthread_mutex_lock(&lock); + result = music_ready ? music_load(mus) : -1; + pthread_mutex_unlock(&lock); + return result; +} + +void audio_music_play(int looping) +{ + pthread_mutex_lock(&lock); + if (music_ready) + music_play(looping); + pthread_mutex_unlock(&lock); +} + +void audio_music_stop(void) +{ + pthread_mutex_lock(&lock); + if (music_ready) + music_stop(); + pthread_mutex_unlock(&lock); +} + +void audio_music_pause(int paused) +{ + pthread_mutex_lock(&lock); + if (music_ready) + music_pause(paused); + pthread_mutex_unlock(&lock); +} + +void audio_music_volume(int volume) +{ + pthread_mutex_lock(&lock); + if (music_ready) + music_volume(volume); + pthread_mutex_unlock(&lock); +} diff --git a/src/audio/music.c b/src/audio/music.c index 7519d78..564daac 100644 --- a/src/audio/music.c +++ b/src/audio/music.c @@ -1,511 +1,511 @@ -#include -#include - -#include "music.h" -#include "opl.h" - -#define VOICES 9 -#define MUS_CHANNELS 16 -#define PERCUSSION_CHANNEL 15 -#define PERCUSSION_FIRST 35 -#define PERCUSSION_LAST 81 -#define INSTRUMENTS 175 -#define INSTRUMENT_SIZE 36 -#define GENMIDI_HEADER "#OPL_II#" -#define TICK_RATE 140.0 -#define CHIP_RATE 49716.0 - -#define MAX_EVENTS_WITHOUT_DELAY 65536 - -#define FLAG_FIXED_PITCH 0x0001 -#define FLAG_DOUBLE_VOICE 0x0004 - -typedef struct -{ - uint8_t tremolo, attack, sustain, wave, scale, level; -} operator_t; - -typedef struct -{ - operator_t mod, car; - uint8_t feedback; - int16_t base_note; -} layer_t; - -typedef struct -{ - uint16_t flags; - uint8_t fine_tuning, fixed_note; - layer_t layers[2]; -} instrument_t; - -typedef struct -{ - const instrument_t *instrument; - int volume, bend, velocity; -} channel_t; - -typedef struct -{ - int active, channel, note, velocity, layer; - const instrument_t *instrument; - uint32_t age; -} voice_t; - -static struct -{ - opl_t *opl; - double samples_per_tick; - instrument_t instruments[INSTRUMENTS]; - - const uint8_t *score; - int score_length, position; - int playing, paused, looping; - double wait; - - int volume; - channel_t channels[MUS_CHANNELS]; - voice_t voices[VOICES]; - uint32_t age; -} m; - -static void parse_operator(operator_t *op, const uint8_t *p) -{ - op->tremolo = p[0]; - op->attack = p[1]; - op->sustain = p[2]; - op->wave = p[3]; - op->scale = p[4]; - op->level = p[5]; -} - -static void parse_instrument(instrument_t *ins, const uint8_t *p) -{ - int i; - - ins->flags = p[0] | p[1] << 8; - ins->fine_tuning = p[2]; - ins->fixed_note = p[3]; - for (i = 0; i < 2; i++) - { - const uint8_t *l = p + 4 + i * 16; - - parse_operator(&ins->layers[i].mod, l); - ins->layers[i].feedback = l[6]; - parse_operator(&ins->layers[i].car, l + 7); - ins->layers[i].base_note = (int16_t)(l[14] | l[15] << 8); - } -} - -static int operator_offset(int voice, int carrier) -{ - return (voice / 3) * 8 + voice % 3 + (carrier ? 3 : 0); -} - -static int attenuation(const voice_t *voice) -{ - double gain = voice->velocity / 127.0 * m.channels[voice->channel].volume / 127.0 * - m.volume / 127.0; - int steps; - - if (gain <= 0) - return 63; - steps = (int)(-20.0 * log10(gain) / 0.75 + 0.5); - return steps > 63 ? 63 : steps; -} - -static void write_levels(const voice_t *voice) -{ - const layer_t *layer = &voice->instrument->layers[voice->layer]; - int att = attenuation(voice); - int car = layer->car.level + att; - int mod = layer->mod.level; - - if (layer->feedback & 1) - mod += att; - opl_write(m.opl, 0x40 + operator_offset(voice - m.voices, 1), - (layer->car.scale & 0xc0) | (car > 63 ? 63 : car)); - opl_write(m.opl, 0x40 + operator_offset(voice - m.voices, 0), - (layer->mod.scale & 0xc0) | (mod > 63 ? 63 : mod)); -} - -static void write_operator(int offset, const operator_t *op) -{ - opl_write(m.opl, 0x20 + offset, op->tremolo); - opl_write(m.opl, 0x60 + offset, op->attack); - opl_write(m.opl, 0x80 + offset, op->sustain); - opl_write(m.opl, 0xe0 + offset, op->wave); -} - -static void write_frequency(const voice_t *voice, int key) -{ - const instrument_t *ins = voice->instrument; - const layer_t *layer = &ins->layers[voice->layer]; - int index = voice - m.voices; - double note, freq; - int block = 0, fnum; - - note = (ins->flags & FLAG_FIXED_PITCH) ? ins->fixed_note : voice->note; - note += layer->base_note; - if (voice->layer) - note += (ins->fine_tuning / 2 - 64) / 32.0; - note += m.channels[voice->channel].bend / 64.0; - - freq = 440.0 * pow(2.0, (note - 69.0) / 12.0); - while (block < 7 && freq * (1 << (20 - block)) / CHIP_RATE >= 1024) - block++; - fnum = (int)(freq * (1 << (20 - block)) / CHIP_RATE + 0.5); - if (fnum > 1023) - fnum = 1023; - - opl_write(m.opl, 0xa0 + index, fnum & 0xff); - opl_write(m.opl, 0xb0 + index, (key ? 0x20 : 0) | block << 2 | fnum >> 8); -} - -static void release_voice(voice_t *voice) -{ - if (!voice->active) - return; - voice->active = 0; - write_frequency(voice, 0); -} - -static voice_t *find_voice(int may_steal) -{ - voice_t *best = NULL; - int i; - - for (i = 0; i < VOICES; i++) - { - voice_t *v = &m.voices[i]; - - if (!v->active && (!best || v->age < best->age)) - best = v; - } - if (best || !may_steal) - return best; - - for (i = 0; i < VOICES; i++) - { - voice_t *v = &m.voices[i]; - - if (!best || v->channel > best->channel || - (v->channel == best->channel && v->age < best->age)) - best = v; - } - release_voice(best); - return best; -} - -static void start_voice(voice_t *voice, int channel, int note, int velocity, - const instrument_t *ins, int layer) -{ - const layer_t *l = &ins->layers[layer]; - int index = voice - m.voices; - - voice->active = 1; - voice->channel = channel; - voice->note = note; - voice->velocity = velocity; - voice->instrument = ins; - voice->layer = layer; - voice->age = ++m.age; - - write_operator(operator_offset(index, 0), &l->mod); - write_operator(operator_offset(index, 1), &l->car); - opl_write(m.opl, 0xc0 + index, l->feedback | 0x30); - write_levels(voice); - write_frequency(voice, 1); -} - -static void note_on(int channel, int note, int velocity) -{ - const instrument_t *ins; - voice_t *voice; - - if (channel == PERCUSSION_CHANNEL) - { - if (note < PERCUSSION_FIRST || note > PERCUSSION_LAST) - return; - ins = &m.instruments[128 + note - PERCUSSION_FIRST]; - } - else - ins = m.channels[channel].instrument; - - voice = find_voice(1); - start_voice(voice, channel, note, velocity, ins, 0); - if ((ins->flags & FLAG_DOUBLE_VOICE) && (voice = find_voice(0))) - start_voice(voice, channel, note, velocity, ins, 1); -} - -static void note_off(int channel, int note) -{ - int i; - - for (i = 0; i < VOICES; i++) - if (m.voices[i].active && m.voices[i].channel == channel && m.voices[i].note == note) - release_voice(&m.voices[i]); -} - -static void channel_off(int channel) -{ - int i; - - for (i = 0; i < VOICES; i++) - if (m.voices[i].active && (channel < 0 || m.voices[i].channel == channel)) - release_voice(&m.voices[i]); -} - -static void refresh_channel(int channel, int frequency) -{ - int i; - - for (i = 0; i < VOICES; i++) - { - voice_t *v = &m.voices[i]; - - if (!v->active || (channel >= 0 && v->channel != channel)) - continue; - if (frequency) - write_frequency(v, 1); - else - write_levels(v); - } -} - -static void reset_channels(void) -{ - int i; - - for (i = 0; i < MUS_CHANNELS; i++) - { - m.channels[i].instrument = &m.instruments[0]; - m.channels[i].volume = 127; - m.channels[i].bend = 0; - m.channels[i].velocity = 127; - } -} - -static void controller(int channel, int number, int value) -{ - switch (number) - { - case 0: - m.channels[channel].instrument = &m.instruments[value & 127]; - break; - case 3: - m.channels[channel].volume = value > 127 ? 127 : value; - refresh_channel(channel, 0); - break; - default: - break; - } -} - -static void system_event(int channel, int number) -{ - switch (number) - { - case 10: - case 11: - channel_off(channel); - break; - case 14: - m.channels[channel].volume = 127; - m.channels[channel].bend = 0; - refresh_channel(channel, 1); - refresh_channel(channel, 0); - break; - default: - break; - } -} - -static int next_byte(void) -{ - if (m.position >= m.score_length) - return -1; - return m.score[m.position++]; -} - -static void end_of_score(void) -{ - if (m.looping) - { - m.position = 0; - return; - } - channel_off(-1); - m.playing = 0; -} - -static void run_events(void) -{ - int budget = MAX_EVENTS_WITHOUT_DELAY; - - while (m.playing) - { - int event = next_byte(); - int channel, a, b; - - if (!budget--) - { - channel_off(-1); - m.playing = 0; - return; - } - - if (event < 0) - { - end_of_score(); - continue; - } - channel = event & 15; - - switch ((event >> 4) & 7) - { - case 0: - note_off(channel, next_byte() & 127); - break; - case 1: - a = next_byte(); - if (a & 128) - m.channels[channel].velocity = next_byte() & 127; - note_on(channel, a & 127, m.channels[channel].velocity); - break; - case 2: - m.channels[channel].bend = (next_byte() & 255) - 128; - refresh_channel(channel, 1); - break; - case 3: - system_event(channel, next_byte()); - break; - case 4: - a = next_byte(); - b = next_byte(); - controller(channel, a, b); - break; - case 6: - end_of_score(); - continue; - default: - break; - } - - if (event & 128) - { - int delay = 0; - - do - { - b = next_byte(); - if (b < 0) - break; - delay = delay * 128 + (b & 127); - } while (b & 128); - if (delay) - { - m.wait += delay * m.samples_per_tick; - return; - } - } - } -} - -int music_init(int sample_rate, const uint8_t *genmidi, int length) -{ - int i; - - if (!genmidi || length < 8 + INSTRUMENTS * INSTRUMENT_SIZE || - memcmp(genmidi, GENMIDI_HEADER, 8)) - return -1; - for (i = 0; i < INSTRUMENTS; i++) - parse_instrument(&m.instruments[i], genmidi + 8 + i * INSTRUMENT_SIZE); - - m.opl = opl_create(sample_rate); - if (!m.opl) - return -1; - m.samples_per_tick = sample_rate / TICK_RATE; - m.volume = 127; - reset_channels(); - return 0; -} - -void music_shutdown(void) -{ - opl_destroy(m.opl); - m.opl = NULL; - m.playing = 0; -} - -int music_load(const uint8_t *mus) -{ - int start; - - if (!m.opl || memcmp(mus, "MUS\x1a", 4)) - return -1; - music_stop(); - m.score_length = mus[4] | mus[5] << 8; - start = mus[6] | mus[7] << 8; - m.score = mus + start; - return 0; -} - -void music_play(int looping) -{ - if (!m.score) - return; - channel_off(-1); - reset_channels(); - m.position = 0; - m.wait = 0; - m.looping = looping; - m.paused = 0; - m.playing = 1; -} - -void music_stop(void) -{ - if (!m.opl) - return; - channel_off(-1); - m.playing = 0; -} - -void music_pause(int paused) -{ - m.paused = paused; -} - -void music_volume(int volume) -{ - m.volume = volume < 0 ? 0 : volume > 127 ? 127 : volume; - if (m.opl) - refresh_channel(-1, 0); -} - -void music_render(float *out, int frames) -{ - int done = 0; - - if (!m.opl || m.paused) - { - memset(out, 0, frames * sizeof(*out)); - return; - } - - while (done < frames) - { - int chunk = frames - done; - - while (m.playing && m.wait < 1) - run_events(); - if (m.playing && m.wait < chunk) - chunk = (int)m.wait; - - opl_render(m.opl, out + done, chunk); - done += chunk; - if (m.playing) - m.wait -= chunk; - } -} +#include +#include + +#include "music.h" +#include "opl.h" + +#define VOICES 9 +#define MUS_CHANNELS 16 +#define PERCUSSION_CHANNEL 15 +#define PERCUSSION_FIRST 35 +#define PERCUSSION_LAST 81 +#define INSTRUMENTS 175 +#define INSTRUMENT_SIZE 36 +#define GENMIDI_HEADER "#OPL_II#" +#define TICK_RATE 140.0 +#define CHIP_RATE 49716.0 + +#define MAX_EVENTS_WITHOUT_DELAY 65536 + +#define FLAG_FIXED_PITCH 0x0001 +#define FLAG_DOUBLE_VOICE 0x0004 + +typedef struct +{ + uint8_t tremolo, attack, sustain, wave, scale, level; +} operator_t; + +typedef struct +{ + operator_t mod, car; + uint8_t feedback; + int16_t base_note; +} layer_t; + +typedef struct +{ + uint16_t flags; + uint8_t fine_tuning, fixed_note; + layer_t layers[2]; +} instrument_t; + +typedef struct +{ + const instrument_t *instrument; + int volume, bend, velocity; +} channel_t; + +typedef struct +{ + int active, channel, note, velocity, layer; + const instrument_t *instrument; + uint32_t age; +} voice_t; + +static struct +{ + opl_t *opl; + double samples_per_tick; + instrument_t instruments[INSTRUMENTS]; + + const uint8_t *score; + int score_length, position; + int playing, paused, looping; + double wait; + + int volume; + channel_t channels[MUS_CHANNELS]; + voice_t voices[VOICES]; + uint32_t age; +} m; + +static void parse_operator(operator_t *op, const uint8_t *p) +{ + op->tremolo = p[0]; + op->attack = p[1]; + op->sustain = p[2]; + op->wave = p[3]; + op->scale = p[4]; + op->level = p[5]; +} + +static void parse_instrument(instrument_t *ins, const uint8_t *p) +{ + int i; + + ins->flags = p[0] | p[1] << 8; + ins->fine_tuning = p[2]; + ins->fixed_note = p[3]; + for (i = 0; i < 2; i++) + { + const uint8_t *l = p + 4 + i * 16; + + parse_operator(&ins->layers[i].mod, l); + ins->layers[i].feedback = l[6]; + parse_operator(&ins->layers[i].car, l + 7); + ins->layers[i].base_note = (int16_t)(l[14] | l[15] << 8); + } +} + +static int operator_offset(int voice, int carrier) +{ + return (voice / 3) * 8 + voice % 3 + (carrier ? 3 : 0); +} + +static int attenuation(const voice_t *voice) +{ + double gain = voice->velocity / 127.0 * m.channels[voice->channel].volume / 127.0 * + m.volume / 127.0; + int steps; + + if (gain <= 0) + return 63; + steps = (int)(-20.0 * log10(gain) / 0.75 + 0.5); + return steps > 63 ? 63 : steps; +} + +static void write_levels(const voice_t *voice) +{ + const layer_t *layer = &voice->instrument->layers[voice->layer]; + int att = attenuation(voice); + int car = layer->car.level + att; + int mod = layer->mod.level; + + if (layer->feedback & 1) + mod += att; + opl_write(m.opl, 0x40 + operator_offset(voice - m.voices, 1), + (layer->car.scale & 0xc0) | (car > 63 ? 63 : car)); + opl_write(m.opl, 0x40 + operator_offset(voice - m.voices, 0), + (layer->mod.scale & 0xc0) | (mod > 63 ? 63 : mod)); +} + +static void write_operator(int offset, const operator_t *op) +{ + opl_write(m.opl, 0x20 + offset, op->tremolo); + opl_write(m.opl, 0x60 + offset, op->attack); + opl_write(m.opl, 0x80 + offset, op->sustain); + opl_write(m.opl, 0xe0 + offset, op->wave); +} + +static void write_frequency(const voice_t *voice, int key) +{ + const instrument_t *ins = voice->instrument; + const layer_t *layer = &ins->layers[voice->layer]; + int index = voice - m.voices; + double note, freq; + int block = 0, fnum; + + note = (ins->flags & FLAG_FIXED_PITCH) ? ins->fixed_note : voice->note; + note += layer->base_note; + if (voice->layer) + note += (ins->fine_tuning / 2 - 64) / 32.0; + note += m.channels[voice->channel].bend / 64.0; + + freq = 440.0 * pow(2.0, (note - 69.0) / 12.0); + while (block < 7 && freq * (1 << (20 - block)) / CHIP_RATE >= 1024) + block++; + fnum = (int)(freq * (1 << (20 - block)) / CHIP_RATE + 0.5); + if (fnum > 1023) + fnum = 1023; + + opl_write(m.opl, 0xa0 + index, fnum & 0xff); + opl_write(m.opl, 0xb0 + index, (key ? 0x20 : 0) | block << 2 | fnum >> 8); +} + +static void release_voice(voice_t *voice) +{ + if (!voice->active) + return; + voice->active = 0; + write_frequency(voice, 0); +} + +static voice_t *find_voice(int may_steal) +{ + voice_t *best = NULL; + int i; + + for (i = 0; i < VOICES; i++) + { + voice_t *v = &m.voices[i]; + + if (!v->active && (!best || v->age < best->age)) + best = v; + } + if (best || !may_steal) + return best; + + for (i = 0; i < VOICES; i++) + { + voice_t *v = &m.voices[i]; + + if (!best || v->channel > best->channel || + (v->channel == best->channel && v->age < best->age)) + best = v; + } + release_voice(best); + return best; +} + +static void start_voice(voice_t *voice, int channel, int note, int velocity, + const instrument_t *ins, int layer) +{ + const layer_t *l = &ins->layers[layer]; + int index = voice - m.voices; + + voice->active = 1; + voice->channel = channel; + voice->note = note; + voice->velocity = velocity; + voice->instrument = ins; + voice->layer = layer; + voice->age = ++m.age; + + write_operator(operator_offset(index, 0), &l->mod); + write_operator(operator_offset(index, 1), &l->car); + opl_write(m.opl, 0xc0 + index, l->feedback | 0x30); + write_levels(voice); + write_frequency(voice, 1); +} + +static void note_on(int channel, int note, int velocity) +{ + const instrument_t *ins; + voice_t *voice; + + if (channel == PERCUSSION_CHANNEL) + { + if (note < PERCUSSION_FIRST || note > PERCUSSION_LAST) + return; + ins = &m.instruments[128 + note - PERCUSSION_FIRST]; + } + else + ins = m.channels[channel].instrument; + + voice = find_voice(1); + start_voice(voice, channel, note, velocity, ins, 0); + if ((ins->flags & FLAG_DOUBLE_VOICE) && (voice = find_voice(0))) + start_voice(voice, channel, note, velocity, ins, 1); +} + +static void note_off(int channel, int note) +{ + int i; + + for (i = 0; i < VOICES; i++) + if (m.voices[i].active && m.voices[i].channel == channel && m.voices[i].note == note) + release_voice(&m.voices[i]); +} + +static void channel_off(int channel) +{ + int i; + + for (i = 0; i < VOICES; i++) + if (m.voices[i].active && (channel < 0 || m.voices[i].channel == channel)) + release_voice(&m.voices[i]); +} + +static void refresh_channel(int channel, int frequency) +{ + int i; + + for (i = 0; i < VOICES; i++) + { + voice_t *v = &m.voices[i]; + + if (!v->active || (channel >= 0 && v->channel != channel)) + continue; + if (frequency) + write_frequency(v, 1); + else + write_levels(v); + } +} + +static void reset_channels(void) +{ + int i; + + for (i = 0; i < MUS_CHANNELS; i++) + { + m.channels[i].instrument = &m.instruments[0]; + m.channels[i].volume = 127; + m.channels[i].bend = 0; + m.channels[i].velocity = 127; + } +} + +static void controller(int channel, int number, int value) +{ + switch (number) + { + case 0: + m.channels[channel].instrument = &m.instruments[value & 127]; + break; + case 3: + m.channels[channel].volume = value > 127 ? 127 : value; + refresh_channel(channel, 0); + break; + default: + break; + } +} + +static void system_event(int channel, int number) +{ + switch (number) + { + case 10: + case 11: + channel_off(channel); + break; + case 14: + m.channels[channel].volume = 127; + m.channels[channel].bend = 0; + refresh_channel(channel, 1); + refresh_channel(channel, 0); + break; + default: + break; + } +} + +static int next_byte(void) +{ + if (m.position >= m.score_length) + return -1; + return m.score[m.position++]; +} + +static void end_of_score(void) +{ + if (m.looping) + { + m.position = 0; + return; + } + channel_off(-1); + m.playing = 0; +} + +static void run_events(void) +{ + int budget = MAX_EVENTS_WITHOUT_DELAY; + + while (m.playing) + { + int event = next_byte(); + int channel, a, b; + + if (!budget--) + { + channel_off(-1); + m.playing = 0; + return; + } + + if (event < 0) + { + end_of_score(); + continue; + } + channel = event & 15; + + switch ((event >> 4) & 7) + { + case 0: + note_off(channel, next_byte() & 127); + break; + case 1: + a = next_byte(); + if (a & 128) + m.channels[channel].velocity = next_byte() & 127; + note_on(channel, a & 127, m.channels[channel].velocity); + break; + case 2: + m.channels[channel].bend = (next_byte() & 255) - 128; + refresh_channel(channel, 1); + break; + case 3: + system_event(channel, next_byte()); + break; + case 4: + a = next_byte(); + b = next_byte(); + controller(channel, a, b); + break; + case 6: + end_of_score(); + continue; + default: + break; + } + + if (event & 128) + { + int delay = 0; + + do + { + b = next_byte(); + if (b < 0) + break; + delay = delay * 128 + (b & 127); + } while (b & 128); + if (delay) + { + m.wait += delay * m.samples_per_tick; + return; + } + } + } +} + +int music_init(int sample_rate, const uint8_t *genmidi, int length) +{ + int i; + + if (!genmidi || length < 8 + INSTRUMENTS * INSTRUMENT_SIZE || + memcmp(genmidi, GENMIDI_HEADER, 8)) + return -1; + for (i = 0; i < INSTRUMENTS; i++) + parse_instrument(&m.instruments[i], genmidi + 8 + i * INSTRUMENT_SIZE); + + m.opl = opl_create(sample_rate); + if (!m.opl) + return -1; + m.samples_per_tick = sample_rate / TICK_RATE; + m.volume = 127; + reset_channels(); + return 0; +} + +void music_shutdown(void) +{ + opl_destroy(m.opl); + m.opl = NULL; + m.playing = 0; +} + +int music_load(const uint8_t *mus) +{ + int start; + + if (!m.opl || memcmp(mus, "MUS\x1a", 4)) + return -1; + music_stop(); + m.score_length = mus[4] | mus[5] << 8; + start = mus[6] | mus[7] << 8; + m.score = mus + start; + return 0; +} + +void music_play(int looping) +{ + if (!m.score) + return; + channel_off(-1); + reset_channels(); + m.position = 0; + m.wait = 0; + m.looping = looping; + m.paused = 0; + m.playing = 1; +} + +void music_stop(void) +{ + if (!m.opl) + return; + channel_off(-1); + m.playing = 0; +} + +void music_pause(int paused) +{ + m.paused = paused; +} + +void music_volume(int volume) +{ + m.volume = volume < 0 ? 0 : volume > 127 ? 127 : volume; + if (m.opl) + refresh_channel(-1, 0); +} + +void music_render(float *out, int frames) +{ + int done = 0; + + if (!m.opl || m.paused) + { + memset(out, 0, frames * sizeof(*out)); + return; + } + + while (done < frames) + { + int chunk = frames - done; + + while (m.playing && m.wait < 1) + run_events(); + if (m.playing && m.wait < chunk) + chunk = (int)m.wait; + + opl_render(m.opl, out + done, chunk); + done += chunk; + if (m.playing) + m.wait -= chunk; + } +} diff --git a/src/audio/opl.c b/src/audio/opl.c index 756d8d8..52c769e 100644 --- a/src/audio/opl.c +++ b/src/audio/opl.c @@ -1,408 +1,408 @@ -#include -#include -#include - -#include "opl.h" - -#define CHIP_RATE 49716.0 -#define WAVE_BITS 10 -#define WAVE_SIZE (1 << WAVE_BITS) -#define GAIN_STEPS 1024 -#define ENV_SILENT 511.0f -#define SLOTS (OPL_CHANNELS * 2) -#define CHANNEL_SCALE 0.25f -#define TREMOLO_HZ 3.7 -#define VIBRATO_HZ 6.07 - -enum -{ - EG_OFF, - EG_ATTACK, - EG_DECAY, - EG_SUSTAIN, - EG_RELEASE -}; - -typedef struct -{ - uint8_t tremolo, vibrato, sustained, ksr, mult; - uint8_t ksl, level, attack, decay, sustain, release, wave; - - uint32_t phase, step; - int stage; - float env, ksl_att, sustain_env; - float attack_k, decay_inc, release_inc; - float out, prev; -} slot_t; - -typedef struct -{ - uint16_t fnum; - uint8_t block, key, feedback, additive; - slot_t *mod, *car; -} channel_t; - -struct opl -{ - double rate_scale; - slot_t slots[SLOTS]; - channel_t channels[OPL_CHANNELS]; - uint8_t deep_tremolo, deep_vibrato; - double tremolo_phase, tremolo_step; - double vibrato_phase, vibrato_step; -}; - -static float wave_table[8][WAVE_SIZE]; -static float gain_table[GAIN_STEPS]; -static int tables_ready; - -static const uint8_t mult_x2[16] = {1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30}; -static const uint8_t ksl_rom[16] = {0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64}; -static const uint8_t ksl_shift[4] = {0, 1, 2, 0}; - -static void build_tables(void) -{ - int i; - - for (i = 0; i < WAVE_SIZE; i++) - { - double angle = 2 * M_PI * i / WAVE_SIZE; - float s = (float)sin(angle); - float s2 = (float)sin(2 * angle); - int first_half = i < WAVE_SIZE / 2; - int quarter = (i / (WAVE_SIZE / 4)) & 1; - int folded = first_half ? i : WAVE_SIZE - 1 - i; - - wave_table[0][i] = s; - wave_table[1][i] = first_half ? s : 0; - wave_table[2][i] = fabsf(s); - wave_table[3][i] = quarter ? 0 : fabsf(s); - wave_table[4][i] = first_half ? s2 : 0; - wave_table[5][i] = first_half ? fabsf(s2) : 0; - wave_table[6][i] = first_half ? 1.0f : -1.0f; - wave_table[7][i] = (first_half ? 1.0f : -1.0f) * exp2f(-folded / 32.0f); - } - for (i = 0; i < GAIN_STEPS; i++) - gain_table[i] = exp2f(-i / 32.0f); - tables_ready = 1; -} - -static int effective_rate(int reg_rate, const slot_t *slot, const channel_t *channel) -{ - int ksv = (channel->block << 1) | ((channel->fnum >> 9) & 1); - int rate; - - if (!reg_rate) - return 0; - rate = reg_rate * 4 + (slot->ksr ? ksv : ksv >> 2); - return rate > 63 ? 63 : rate; -} - -static double rate_divisor(int rate) -{ - return (double)(1 << (rate >> 2)) * ((rate & 3) + 4) / 4.0; -} - -static float linear_increment(int rate, double rate_scale) -{ - double seconds; - - if (!rate) - return 0; - seconds = 78.56 / rate_divisor(rate); - return (float)(ENV_SILENT / (seconds * CHIP_RATE) * rate_scale); -} - -static void update_slot(opl_t *opl, slot_t *slot, const channel_t *channel) -{ - int ksl, attack_rate; - uint64_t step; - - step = (uint64_t)((double)((uint32_t)channel->fnum << channel->block) * - mult_x2[slot->mult] * 2048.0 * opl->rate_scale); - slot->step = (uint32_t)step; - - ksl = (ksl_rom[channel->fnum >> 6] << 2) - ((8 - channel->block) << 5); - slot->ksl_att = slot->ksl && ksl > 0 ? (float)(ksl >> ksl_shift[slot->ksl]) : 0; - - attack_rate = effective_rate(slot->attack, slot, channel); - if (attack_rate >= 60) - slot->attack_k = 1; - else if (attack_rate == 0) - slot->attack_k = 0; - else - { - double samples = 5.65248 / rate_divisor(attack_rate) * CHIP_RATE / opl->rate_scale; - slot->attack_k = (float)(1.0 - exp(log(1.0 / ENV_SILENT) / samples)); - } - slot->decay_inc = linear_increment(effective_rate(slot->decay, slot, channel), opl->rate_scale); - slot->release_inc = linear_increment(effective_rate(slot->release, slot, channel), opl->rate_scale); - slot->sustain_env = (slot->sustain == 15 ? 31 : slot->sustain) * 16.0f; -} - -static void update_channel(opl_t *opl, channel_t *channel) -{ - update_slot(opl, channel->mod, channel); - update_slot(opl, channel->car, channel); -} - -static void key_on(slot_t *slot) -{ - slot->phase = 0; - slot->stage = EG_ATTACK; - if (slot->attack_k >= 1) - { - slot->env = 0; - slot->stage = EG_DECAY; - } -} - -static void key_off(slot_t *slot) -{ - if (slot->stage != EG_OFF) - slot->stage = EG_RELEASE; -} - -static void advance_envelope(slot_t *slot) -{ - switch (slot->stage) - { - case EG_ATTACK: - slot->env -= slot->env * slot->attack_k; - if (slot->env < 0.5f) - { - slot->env = 0; - slot->stage = EG_DECAY; - } - break; - case EG_DECAY: - slot->env += slot->decay_inc; - if (slot->env >= slot->sustain_env) - { - slot->env = slot->sustain_env; - slot->stage = slot->sustained ? EG_SUSTAIN : EG_RELEASE; - } - break; - case EG_RELEASE: - slot->env += slot->release_inc; - if (slot->env >= ENV_SILENT) - { - slot->env = ENV_SILENT; - slot->stage = EG_OFF; - } - break; - default: - break; - } -} - -static float slot_output(slot_t *slot, int modulation, float tremolo, double vibrato) -{ - float att; - int index; - - advance_envelope(slot); - slot->phase += slot->vibrato ? (uint32_t)(uint64_t)(slot->step * vibrato) : slot->step; - - att = slot->env + slot->level * 4 + slot->ksl_att + (slot->tremolo ? tremolo : 0); - if (slot->stage == EG_OFF || att >= GAIN_STEPS) - return 0; - index = ((int)(slot->phase >> (32 - WAVE_BITS)) + modulation) & (WAVE_SIZE - 1); - return wave_table[slot->wave][index] * gain_table[(int)att]; -} - -static float channel_output(channel_t *channel, float tremolo, double vibrato) -{ - slot_t *mod = channel->mod, *car = channel->car; - int feedback = 0; - - if (mod->stage == EG_OFF && car->stage == EG_OFF) - { - mod->out = mod->prev = 0; - return 0; - } - - if (channel->feedback) - feedback = (int)((mod->prev + mod->out) * 4095.0f) >> (9 - channel->feedback); - mod->prev = mod->out; - mod->out = slot_output(mod, feedback, tremolo, vibrato); - - if (channel->additive) - return mod->out + slot_output(car, 0, tremolo, vibrato); - return slot_output(car, (int)(mod->out * 4095.0f), tremolo, vibrato); -} - -opl_t *opl_create(int sample_rate) -{ - opl_t *opl; - - if (!tables_ready) - build_tables(); - opl = calloc(1, sizeof(*opl)); - if (!opl) - return NULL; - opl->rate_scale = CHIP_RATE / sample_rate; - opl->tremolo_step = TREMOLO_HZ / sample_rate; - opl->vibrato_step = VIBRATO_HZ / sample_rate; - opl_reset(opl); - return opl; -} - -void opl_destroy(opl_t *opl) -{ - free(opl); -} - -void opl_reset(opl_t *opl) -{ - int c; - - memset(opl->slots, 0, sizeof(opl->slots)); - memset(opl->channels, 0, sizeof(opl->channels)); - for (c = 0; c < OPL_CHANNELS; c++) - { - int bank = c / 9, local = c % 9; - int mod = bank * 18 + (local / 3) * 6 + local % 3; - - opl->channels[c].mod = &opl->slots[mod]; - opl->channels[c].car = &opl->slots[mod + 3]; - } - for (c = 0; c < SLOTS; c++) - { - opl->slots[c].env = ENV_SILENT; - opl->slots[c].stage = EG_OFF; - } - for (c = 0; c < OPL_CHANNELS; c++) - update_channel(opl, &opl->channels[c]); -} - -static channel_t *slot_channel(opl_t *opl, int slot_index) -{ - int bank = slot_index / 18, local = slot_index % 18; - int channel = (local / 6) * 3 + (local % 6) % 3; - - return &opl->channels[bank * 9 + channel]; -} - -static void write_slot(opl_t *opl, int bank, int offset, int group, uint8_t value) -{ - int index; - slot_t *slot; - - if ((offset & 7) >= 6 || offset > 0x15) - return; - index = bank * 18 + (offset >> 3) * 6 + (offset & 7); - slot = &opl->slots[index]; - - switch (group) - { - case 0x20: - slot->tremolo = (value >> 7) & 1; - slot->vibrato = (value >> 6) & 1; - slot->sustained = (value >> 5) & 1; - slot->ksr = (value >> 4) & 1; - slot->mult = value & 15; - break; - case 0x40: - slot->ksl = value >> 6; - slot->level = value & 63; - break; - case 0x60: - slot->attack = value >> 4; - slot->decay = value & 15; - break; - case 0x80: - slot->sustain = value >> 4; - slot->release = value & 15; - break; - case 0xe0: - slot->wave = value & 7; - return; - } - update_slot(opl, slot, slot_channel(opl, index)); -} - -void opl_write(opl_t *opl, int reg, uint8_t value) -{ - int bank = (reg >> 8) & 1; - int r = reg & 0xff; - channel_t *channel; - - switch (r & 0xe0) - { - case 0x20: - case 0x40: - case 0x60: - case 0x80: - case 0xe0: - write_slot(opl, bank, r & 0x1f, r & 0xe0, value); - return; - case 0xa0: - if (r == 0xbd && !bank) - { - opl->deep_tremolo = (value >> 7) & 1; - opl->deep_vibrato = (value >> 6) & 1; - return; - } - if ((r & 0x0f) > 8) - return; - channel = &opl->channels[bank * 9 + (r & 0x0f)]; - if (r < 0xb0) - channel->fnum = (channel->fnum & 0x300) | value; - else - { - uint8_t key = (value >> 5) & 1; - - channel->fnum = (channel->fnum & 0xff) | (value & 3) << 8; - channel->block = (value >> 2) & 7; - update_channel(opl, channel); - if (key && !channel->key) - { - key_on(channel->mod); - key_on(channel->car); - } - else if (!key && channel->key) - { - key_off(channel->mod); - key_off(channel->car); - } - channel->key = key; - return; - } - update_channel(opl, channel); - return; - case 0xc0: - if ((r & 0x0f) > 8 || r >= 0xd0) - return; - channel = &opl->channels[bank * 9 + (r & 0x0f)]; - channel->feedback = (value >> 1) & 7; - channel->additive = value & 1; - return; - } -} - -void opl_render(opl_t *opl, float *out, int frames) -{ - float tremolo_depth = opl->deep_tremolo ? 25.6f : 5.3f; - double vibrato_depth = opl->deep_vibrato ? 0.0081 : 0.0041; - int i, c; - - for (i = 0; i < frames; i++) - { - double tri = opl->tremolo_phase < 0.5 ? opl->tremolo_phase * 2 : 2 - opl->tremolo_phase * 2; - float tremolo = (float)(tri * tremolo_depth); - double vibrato = 1.0 + vibrato_depth * sin(2 * M_PI * opl->vibrato_phase); - float sum = 0; - - for (c = 0; c < OPL_CHANNELS; c++) - sum += channel_output(&opl->channels[c], tremolo, vibrato); - out[i] = sum * CHANNEL_SCALE; - - opl->tremolo_phase += opl->tremolo_step; - if (opl->tremolo_phase >= 1) - opl->tremolo_phase -= 1; - opl->vibrato_phase += opl->vibrato_step; - if (opl->vibrato_phase >= 1) - opl->vibrato_phase -= 1; - } -} +#include +#include +#include + +#include "opl.h" + +#define CHIP_RATE 49716.0 +#define WAVE_BITS 10 +#define WAVE_SIZE (1 << WAVE_BITS) +#define GAIN_STEPS 1024 +#define ENV_SILENT 511.0f +#define SLOTS (OPL_CHANNELS * 2) +#define CHANNEL_SCALE 0.25f +#define TREMOLO_HZ 3.7 +#define VIBRATO_HZ 6.07 + +enum +{ + EG_OFF, + EG_ATTACK, + EG_DECAY, + EG_SUSTAIN, + EG_RELEASE +}; + +typedef struct +{ + uint8_t tremolo, vibrato, sustained, ksr, mult; + uint8_t ksl, level, attack, decay, sustain, release, wave; + + uint32_t phase, step; + int stage; + float env, ksl_att, sustain_env; + float attack_k, decay_inc, release_inc; + float out, prev; +} slot_t; + +typedef struct +{ + uint16_t fnum; + uint8_t block, key, feedback, additive; + slot_t *mod, *car; +} channel_t; + +struct opl +{ + double rate_scale; + slot_t slots[SLOTS]; + channel_t channels[OPL_CHANNELS]; + uint8_t deep_tremolo, deep_vibrato; + double tremolo_phase, tremolo_step; + double vibrato_phase, vibrato_step; +}; + +static float wave_table[8][WAVE_SIZE]; +static float gain_table[GAIN_STEPS]; +static int tables_ready; + +static const uint8_t mult_x2[16] = {1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30}; +static const uint8_t ksl_rom[16] = {0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64}; +static const uint8_t ksl_shift[4] = {0, 1, 2, 0}; + +static void build_tables(void) +{ + int i; + + for (i = 0; i < WAVE_SIZE; i++) + { + double angle = 2 * M_PI * i / WAVE_SIZE; + float s = (float)sin(angle); + float s2 = (float)sin(2 * angle); + int first_half = i < WAVE_SIZE / 2; + int quarter = (i / (WAVE_SIZE / 4)) & 1; + int folded = first_half ? i : WAVE_SIZE - 1 - i; + + wave_table[0][i] = s; + wave_table[1][i] = first_half ? s : 0; + wave_table[2][i] = fabsf(s); + wave_table[3][i] = quarter ? 0 : fabsf(s); + wave_table[4][i] = first_half ? s2 : 0; + wave_table[5][i] = first_half ? fabsf(s2) : 0; + wave_table[6][i] = first_half ? 1.0f : -1.0f; + wave_table[7][i] = (first_half ? 1.0f : -1.0f) * exp2f(-folded / 32.0f); + } + for (i = 0; i < GAIN_STEPS; i++) + gain_table[i] = exp2f(-i / 32.0f); + tables_ready = 1; +} + +static int effective_rate(int reg_rate, const slot_t *slot, const channel_t *channel) +{ + int ksv = (channel->block << 1) | ((channel->fnum >> 9) & 1); + int rate; + + if (!reg_rate) + return 0; + rate = reg_rate * 4 + (slot->ksr ? ksv : ksv >> 2); + return rate > 63 ? 63 : rate; +} + +static double rate_divisor(int rate) +{ + return (double)(1 << (rate >> 2)) * ((rate & 3) + 4) / 4.0; +} + +static float linear_increment(int rate, double rate_scale) +{ + double seconds; + + if (!rate) + return 0; + seconds = 78.56 / rate_divisor(rate); + return (float)(ENV_SILENT / (seconds * CHIP_RATE) * rate_scale); +} + +static void update_slot(opl_t *opl, slot_t *slot, const channel_t *channel) +{ + int ksl, attack_rate; + uint64_t step; + + step = (uint64_t)((double)((uint32_t)channel->fnum << channel->block) * + mult_x2[slot->mult] * 2048.0 * opl->rate_scale); + slot->step = (uint32_t)step; + + ksl = (ksl_rom[channel->fnum >> 6] << 2) - ((8 - channel->block) << 5); + slot->ksl_att = slot->ksl && ksl > 0 ? (float)(ksl >> ksl_shift[slot->ksl]) : 0; + + attack_rate = effective_rate(slot->attack, slot, channel); + if (attack_rate >= 60) + slot->attack_k = 1; + else if (attack_rate == 0) + slot->attack_k = 0; + else + { + double samples = 5.65248 / rate_divisor(attack_rate) * CHIP_RATE / opl->rate_scale; + slot->attack_k = (float)(1.0 - exp(log(1.0 / ENV_SILENT) / samples)); + } + slot->decay_inc = linear_increment(effective_rate(slot->decay, slot, channel), opl->rate_scale); + slot->release_inc = linear_increment(effective_rate(slot->release, slot, channel), opl->rate_scale); + slot->sustain_env = (slot->sustain == 15 ? 31 : slot->sustain) * 16.0f; +} + +static void update_channel(opl_t *opl, channel_t *channel) +{ + update_slot(opl, channel->mod, channel); + update_slot(opl, channel->car, channel); +} + +static void key_on(slot_t *slot) +{ + slot->phase = 0; + slot->stage = EG_ATTACK; + if (slot->attack_k >= 1) + { + slot->env = 0; + slot->stage = EG_DECAY; + } +} + +static void key_off(slot_t *slot) +{ + if (slot->stage != EG_OFF) + slot->stage = EG_RELEASE; +} + +static void advance_envelope(slot_t *slot) +{ + switch (slot->stage) + { + case EG_ATTACK: + slot->env -= slot->env * slot->attack_k; + if (slot->env < 0.5f) + { + slot->env = 0; + slot->stage = EG_DECAY; + } + break; + case EG_DECAY: + slot->env += slot->decay_inc; + if (slot->env >= slot->sustain_env) + { + slot->env = slot->sustain_env; + slot->stage = slot->sustained ? EG_SUSTAIN : EG_RELEASE; + } + break; + case EG_RELEASE: + slot->env += slot->release_inc; + if (slot->env >= ENV_SILENT) + { + slot->env = ENV_SILENT; + slot->stage = EG_OFF; + } + break; + default: + break; + } +} + +static float slot_output(slot_t *slot, int modulation, float tremolo, double vibrato) +{ + float att; + int index; + + advance_envelope(slot); + slot->phase += slot->vibrato ? (uint32_t)(uint64_t)(slot->step * vibrato) : slot->step; + + att = slot->env + slot->level * 4 + slot->ksl_att + (slot->tremolo ? tremolo : 0); + if (slot->stage == EG_OFF || att >= GAIN_STEPS) + return 0; + index = ((int)(slot->phase >> (32 - WAVE_BITS)) + modulation) & (WAVE_SIZE - 1); + return wave_table[slot->wave][index] * gain_table[(int)att]; +} + +static float channel_output(channel_t *channel, float tremolo, double vibrato) +{ + slot_t *mod = channel->mod, *car = channel->car; + int feedback = 0; + + if (mod->stage == EG_OFF && car->stage == EG_OFF) + { + mod->out = mod->prev = 0; + return 0; + } + + if (channel->feedback) + feedback = (int)((mod->prev + mod->out) * 4095.0f) >> (9 - channel->feedback); + mod->prev = mod->out; + mod->out = slot_output(mod, feedback, tremolo, vibrato); + + if (channel->additive) + return mod->out + slot_output(car, 0, tremolo, vibrato); + return slot_output(car, (int)(mod->out * 4095.0f), tremolo, vibrato); +} + +opl_t *opl_create(int sample_rate) +{ + opl_t *opl; + + if (!tables_ready) + build_tables(); + opl = calloc(1, sizeof(*opl)); + if (!opl) + return NULL; + opl->rate_scale = CHIP_RATE / sample_rate; + opl->tremolo_step = TREMOLO_HZ / sample_rate; + opl->vibrato_step = VIBRATO_HZ / sample_rate; + opl_reset(opl); + return opl; +} + +void opl_destroy(opl_t *opl) +{ + free(opl); +} + +void opl_reset(opl_t *opl) +{ + int c; + + memset(opl->slots, 0, sizeof(opl->slots)); + memset(opl->channels, 0, sizeof(opl->channels)); + for (c = 0; c < OPL_CHANNELS; c++) + { + int bank = c / 9, local = c % 9; + int mod = bank * 18 + (local / 3) * 6 + local % 3; + + opl->channels[c].mod = &opl->slots[mod]; + opl->channels[c].car = &opl->slots[mod + 3]; + } + for (c = 0; c < SLOTS; c++) + { + opl->slots[c].env = ENV_SILENT; + opl->slots[c].stage = EG_OFF; + } + for (c = 0; c < OPL_CHANNELS; c++) + update_channel(opl, &opl->channels[c]); +} + +static channel_t *slot_channel(opl_t *opl, int slot_index) +{ + int bank = slot_index / 18, local = slot_index % 18; + int channel = (local / 6) * 3 + (local % 6) % 3; + + return &opl->channels[bank * 9 + channel]; +} + +static void write_slot(opl_t *opl, int bank, int offset, int group, uint8_t value) +{ + int index; + slot_t *slot; + + if ((offset & 7) >= 6 || offset > 0x15) + return; + index = bank * 18 + (offset >> 3) * 6 + (offset & 7); + slot = &opl->slots[index]; + + switch (group) + { + case 0x20: + slot->tremolo = (value >> 7) & 1; + slot->vibrato = (value >> 6) & 1; + slot->sustained = (value >> 5) & 1; + slot->ksr = (value >> 4) & 1; + slot->mult = value & 15; + break; + case 0x40: + slot->ksl = value >> 6; + slot->level = value & 63; + break; + case 0x60: + slot->attack = value >> 4; + slot->decay = value & 15; + break; + case 0x80: + slot->sustain = value >> 4; + slot->release = value & 15; + break; + case 0xe0: + slot->wave = value & 7; + return; + } + update_slot(opl, slot, slot_channel(opl, index)); +} + +void opl_write(opl_t *opl, int reg, uint8_t value) +{ + int bank = (reg >> 8) & 1; + int r = reg & 0xff; + channel_t *channel; + + switch (r & 0xe0) + { + case 0x20: + case 0x40: + case 0x60: + case 0x80: + case 0xe0: + write_slot(opl, bank, r & 0x1f, r & 0xe0, value); + return; + case 0xa0: + if (r == 0xbd && !bank) + { + opl->deep_tremolo = (value >> 7) & 1; + opl->deep_vibrato = (value >> 6) & 1; + return; + } + if ((r & 0x0f) > 8) + return; + channel = &opl->channels[bank * 9 + (r & 0x0f)]; + if (r < 0xb0) + channel->fnum = (channel->fnum & 0x300) | value; + else + { + uint8_t key = (value >> 5) & 1; + + channel->fnum = (channel->fnum & 0xff) | (value & 3) << 8; + channel->block = (value >> 2) & 7; + update_channel(opl, channel); + if (key && !channel->key) + { + key_on(channel->mod); + key_on(channel->car); + } + else if (!key && channel->key) + { + key_off(channel->mod); + key_off(channel->car); + } + channel->key = key; + return; + } + update_channel(opl, channel); + return; + case 0xc0: + if ((r & 0x0f) > 8 || r >= 0xd0) + return; + channel = &opl->channels[bank * 9 + (r & 0x0f)]; + channel->feedback = (value >> 1) & 7; + channel->additive = value & 1; + return; + } +} + +void opl_render(opl_t *opl, float *out, int frames) +{ + float tremolo_depth = opl->deep_tremolo ? 25.6f : 5.3f; + double vibrato_depth = opl->deep_vibrato ? 0.0081 : 0.0041; + int i, c; + + for (i = 0; i < frames; i++) + { + double tri = opl->tremolo_phase < 0.5 ? opl->tremolo_phase * 2 : 2 - opl->tremolo_phase * 2; + float tremolo = (float)(tri * tremolo_depth); + double vibrato = 1.0 + vibrato_depth * sin(2 * M_PI * opl->vibrato_phase); + float sum = 0; + + for (c = 0; c < OPL_CHANNELS; c++) + sum += channel_output(&opl->channels[c], tremolo, vibrato); + out[i] = sum * CHANNEL_SCALE; + + opl->tremolo_phase += opl->tremolo_step; + if (opl->tremolo_phase >= 1) + opl->tremolo_phase -= 1; + opl->vibrato_phase += opl->vibrato_step; + if (opl->vibrato_phase >= 1) + opl->vibrato_phase -= 1; + } +} diff --git a/src/platform/platform.h b/src/platform/platform.h index e54d82b..3e0e50e 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -1,56 +1,56 @@ -#ifndef PLATFORM_H -#define PLATFORM_H - -#include - -enum -{ - PAD_UP = 1 << 0, - PAD_DOWN = 1 << 1, - PAD_LEFT = 1 << 2, - PAD_RIGHT = 1 << 3, - PAD_CROSS = 1 << 4, - PAD_CIRCLE = 1 << 5, - PAD_SQUARE = 1 << 6, - PAD_TRIANGLE = 1 << 7, - PAD_L1 = 1 << 8, - PAD_R1 = 1 << 9, - PAD_L2 = 1 << 10, - PAD_R2 = 1 << 11, - PAD_L3 = 1 << 12, - PAD_R3 = 1 << 13, - PAD_OPTIONS = 1 << 14, - PAD_TOUCHPAD = 1 << 15, -}; - -typedef struct -{ - uint32_t buttons; - int16_t lx, ly, rx, ry; -} pad_state_t; - -typedef void (*audio_render_fn)(int16_t *stereo, int frames, void *user); - -int plat_init(int argc, char **argv); -void plat_shutdown(void); -_Noreturn void plat_exit(int code); - -void plat_log(const char *fmt, ...) __attribute__((format(printf, 1, 2))); -void plat_alert(const char *message); -uint64_t plat_ticks_us(void); -void plat_sleep_us(uint32_t us); - -const char *const *plat_wad_dirs(void); -const char *plat_save_dir(void); - -int plat_video_init(int width, int height); -void plat_video_present(const uint8_t *pixels, const uint32_t *palette); -void plat_video_shutdown(void); - -int plat_pad_read(pad_state_t *state); -void plat_pad_rumble(uint8_t strong, uint8_t weak); - -int plat_audio_init(int rate, audio_render_fn render, void *user); -void plat_audio_shutdown(void); - -#endif +#ifndef PLATFORM_H +#define PLATFORM_H + +#include + +enum +{ + PAD_UP = 1 << 0, + PAD_DOWN = 1 << 1, + PAD_LEFT = 1 << 2, + PAD_RIGHT = 1 << 3, + PAD_CROSS = 1 << 4, + PAD_CIRCLE = 1 << 5, + PAD_SQUARE = 1 << 6, + PAD_TRIANGLE = 1 << 7, + PAD_L1 = 1 << 8, + PAD_R1 = 1 << 9, + PAD_L2 = 1 << 10, + PAD_R2 = 1 << 11, + PAD_L3 = 1 << 12, + PAD_R3 = 1 << 13, + PAD_OPTIONS = 1 << 14, + PAD_TOUCHPAD = 1 << 15, +}; + +typedef struct +{ + uint32_t buttons; + int16_t lx, ly, rx, ry; +} pad_state_t; + +typedef void (*audio_render_fn)(int16_t *stereo, int frames, void *user); + +int plat_init(int argc, char **argv); +void plat_shutdown(void); +_Noreturn void plat_exit(int code); + +void plat_log(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +void plat_alert(const char *message); +uint64_t plat_ticks_us(void); +void plat_sleep_us(uint32_t us); + +const char *const *plat_wad_dirs(void); +const char *plat_save_dir(void); + +int plat_video_init(int width, int height); +void plat_video_present(const uint8_t *pixels, const uint32_t *palette); +void plat_video_shutdown(void); + +int plat_pad_read(pad_state_t *state); +void plat_pad_rumble(uint8_t strong, uint8_t weak); + +int plat_audio_init(int rate, audio_render_fn render, void *user); +void plat_audio_shutdown(void); + +#endif diff --git a/src/platform/test_plan.c b/src/platform/test_plan.c index d5690ec..f8b2f45 100644 --- a/src/platform/test_plan.c +++ b/src/platform/test_plan.c @@ -1,146 +1,146 @@ -#include -#include - -#include "test_plan.h" - -#define MAX_STEPS 128 -#define MAX_CAPTURES 64 - -typedef struct -{ - int start, count; - uint32_t buttons; - int16_t lx, ly, rx, ry; -} step_t; - -static const struct -{ - const char *name; - uint32_t bit; -} names[] = { - {"UP", PAD_UP}, {"DOWN", PAD_DOWN}, {"LEFT", PAD_LEFT}, {"RIGHT", PAD_RIGHT}, - {"CROSS", PAD_CROSS}, {"CIRCLE", PAD_CIRCLE}, {"SQUARE", PAD_SQUARE}, - {"TRIANGLE", PAD_TRIANGLE}, {"L1", PAD_L1}, {"R1", PAD_R1}, {"L2", PAD_L2}, - {"R2", PAD_R2}, {"L3", PAD_L3}, {"R3", PAD_R3}, {"OPTIONS", PAD_OPTIONS}, - {"TOUCHPAD", PAD_TOUCHPAD}, -}; - -static step_t steps[MAX_STEPS]; -static int step_count; -static int captures[MAX_CAPTURES]; -static int capture_count; -static int frame_limit; - -static void parse_token(step_t *step, const char *token) -{ - size_t i; - - for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) - if (!strcmp(token, names[i].name)) - step->buttons |= names[i].bit; - if (!strncmp(token, "LX=", 3)) - step->lx = (int16_t)atoi(token + 3); - if (!strncmp(token, "LY=", 3)) - step->ly = (int16_t)atoi(token + 3); - if (!strncmp(token, "RX=", 3)) - step->rx = (int16_t)atoi(token + 3); - if (!strncmp(token, "RY=", 3)) - step->ry = (int16_t)atoi(token + 3); -} - -void test_plan_input(const char *text) -{ - char *copy, *entry, *save_entry; - - step_count = 0; - if (!text || !(copy = strdup(text))) - return; - for (entry = strtok_r(copy, ";", &save_entry); entry && step_count < MAX_STEPS; - entry = strtok_r(NULL, ";", &save_entry)) - { - step_t *step = &steps[step_count]; - char *field, *save_field; - int index = 0; - - memset(step, 0, sizeof(*step)); - for (field = strtok_r(entry, ":", &save_field); field; - field = strtok_r(NULL, ":", &save_field), index++) - { - if (index == 0) - step->start = atoi(field); - else if (index == 1) - { - char *token, *save_token; - - for (token = strtok_r(field, "+", &save_token); token; - token = strtok_r(NULL, "+", &save_token)) - parse_token(step, token); - } - else - step->count = atoi(field); - } - if (!step->count) - step->count = 1; - step_count++; - } - free(copy); -} - -int test_plan_has_input(void) -{ - return step_count > 0; -} - -void test_plan_apply(int frame, pad_state_t *state) -{ - int i; - - for (i = 0; i < step_count; i++) - { - const step_t *step = &steps[i]; - - if (frame < step->start || frame >= step->start + step->count) - continue; - state->buttons |= step->buttons; - if (step->lx) - state->lx = step->lx; - if (step->ly) - state->ly = step->ly; - if (step->rx) - state->rx = step->rx; - if (step->ry) - state->ry = step->ry; - } -} - -void test_plan_captures(const char *frames) -{ - capture_count = 0; - while (frames && *frames && capture_count < MAX_CAPTURES) - { - captures[capture_count++] = atoi(frames); - frames = strchr(frames, ','); - if (frames) - frames++; - } -} - -int test_plan_captures_frame(int frame) -{ - int i; - - for (i = 0; i < capture_count; i++) - if (captures[i] == frame) - return 1; - return 0; -} - -void test_plan_limit(int frames) -{ - frame_limit = frames; -} - -int test_plan_finished(int frame) -{ - return frame_limit && frame >= frame_limit; -} +#include +#include + +#include "test_plan.h" + +#define MAX_STEPS 128 +#define MAX_CAPTURES 64 + +typedef struct +{ + int start, count; + uint32_t buttons; + int16_t lx, ly, rx, ry; +} step_t; + +static const struct +{ + const char *name; + uint32_t bit; +} names[] = { + {"UP", PAD_UP}, {"DOWN", PAD_DOWN}, {"LEFT", PAD_LEFT}, {"RIGHT", PAD_RIGHT}, + {"CROSS", PAD_CROSS}, {"CIRCLE", PAD_CIRCLE}, {"SQUARE", PAD_SQUARE}, + {"TRIANGLE", PAD_TRIANGLE}, {"L1", PAD_L1}, {"R1", PAD_R1}, {"L2", PAD_L2}, + {"R2", PAD_R2}, {"L3", PAD_L3}, {"R3", PAD_R3}, {"OPTIONS", PAD_OPTIONS}, + {"TOUCHPAD", PAD_TOUCHPAD}, +}; + +static step_t steps[MAX_STEPS]; +static int step_count; +static int captures[MAX_CAPTURES]; +static int capture_count; +static int frame_limit; + +static void parse_token(step_t *step, const char *token) +{ + size_t i; + + for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) + if (!strcmp(token, names[i].name)) + step->buttons |= names[i].bit; + if (!strncmp(token, "LX=", 3)) + step->lx = (int16_t)atoi(token + 3); + if (!strncmp(token, "LY=", 3)) + step->ly = (int16_t)atoi(token + 3); + if (!strncmp(token, "RX=", 3)) + step->rx = (int16_t)atoi(token + 3); + if (!strncmp(token, "RY=", 3)) + step->ry = (int16_t)atoi(token + 3); +} + +void test_plan_input(const char *text) +{ + char *copy, *entry, *save_entry; + + step_count = 0; + if (!text || !(copy = strdup(text))) + return; + for (entry = strtok_r(copy, ";", &save_entry); entry && step_count < MAX_STEPS; + entry = strtok_r(NULL, ";", &save_entry)) + { + step_t *step = &steps[step_count]; + char *field, *save_field; + int index = 0; + + memset(step, 0, sizeof(*step)); + for (field = strtok_r(entry, ":", &save_field); field; + field = strtok_r(NULL, ":", &save_field), index++) + { + if (index == 0) + step->start = atoi(field); + else if (index == 1) + { + char *token, *save_token; + + for (token = strtok_r(field, "+", &save_token); token; + token = strtok_r(NULL, "+", &save_token)) + parse_token(step, token); + } + else + step->count = atoi(field); + } + if (!step->count) + step->count = 1; + step_count++; + } + free(copy); +} + +int test_plan_has_input(void) +{ + return step_count > 0; +} + +void test_plan_apply(int frame, pad_state_t *state) +{ + int i; + + for (i = 0; i < step_count; i++) + { + const step_t *step = &steps[i]; + + if (frame < step->start || frame >= step->start + step->count) + continue; + state->buttons |= step->buttons; + if (step->lx) + state->lx = step->lx; + if (step->ly) + state->ly = step->ly; + if (step->rx) + state->rx = step->rx; + if (step->ry) + state->ry = step->ry; + } +} + +void test_plan_captures(const char *frames) +{ + capture_count = 0; + while (frames && *frames && capture_count < MAX_CAPTURES) + { + captures[capture_count++] = atoi(frames); + frames = strchr(frames, ','); + if (frames) + frames++; + } +} + +int test_plan_captures_frame(int frame) +{ + int i; + + for (i = 0; i < capture_count; i++) + if (captures[i] == frame) + return 1; + return 0; +} + +void test_plan_limit(int frames) +{ + frame_limit = frames; +} + +int test_plan_finished(int frame) +{ + return frame_limit && frame >= frame_limit; +} diff --git a/src/platform/test_plan.h b/src/platform/test_plan.h index 44f140b..bb71ede 100644 --- a/src/platform/test_plan.h +++ b/src/platform/test_plan.h @@ -1,14 +1,14 @@ -#ifndef TEST_PLAN_H -#define TEST_PLAN_H - -#include "platform.h" - -void test_plan_input(const char *steps); -void test_plan_captures(const char *frames); -int test_plan_has_input(void); -void test_plan_apply(int frame, pad_state_t *state); -int test_plan_captures_frame(int frame); -void test_plan_limit(int frames); -int test_plan_finished(int frame); - -#endif +#ifndef TEST_PLAN_H +#define TEST_PLAN_H + +#include "platform.h" + +void test_plan_input(const char *steps); +void test_plan_captures(const char *frames); +int test_plan_has_input(void); +void test_plan_apply(int frame, pad_state_t *state); +int test_plan_captures_frame(int frame); +void test_plan_limit(int frames); +int test_plan_finished(int frame); + +#endif diff --git a/src/port/i_sound.c b/src/port/i_sound.c index 23738e6..9f83f6e 100644 --- a/src/port/i_sound.c +++ b/src/port/i_sound.c @@ -1,169 +1,169 @@ -#include - -#include "doomdef.h" -#include "i_sound.h" -#include "sounds.h" -#include "w_wad.h" -#include "z_zone.h" - -#include "audio.h" - -#define SFX_HEADER 8 -#define SFX_PADDING 16 -#define MUSIC_HANDLE 1 - -typedef struct -{ - const byte *samples; - int length; - int rate; -} sample_t; - -static sample_t samples[NUMSFX]; -static int sound_ready; - -static void cache_sample(int id) -{ - const byte *lump; - int size, length; - - S_sfx[id].lumpnum = I_GetSfxLumpNum(&S_sfx[id]); - lump = W_CacheLumpNum(S_sfx[id].lumpnum, PU_STATIC); - size = W_LumpLength(S_sfx[id].lumpnum); - S_sfx[id].data = (void *)lump; - if (size < SFX_HEADER) - return; - - length = lump[4] | lump[5] << 8 | lump[6] << 16 | lump[7] << 24; - if (length > size - SFX_HEADER) - length = size - SFX_HEADER; - samples[id].samples = lump + SFX_HEADER; - samples[id].length = length; - samples[id].rate = lump[2] | lump[3] << 8; - if (length > 2 * SFX_PADDING) - { - samples[id].samples += SFX_PADDING; - samples[id].length -= 2 * SFX_PADDING; - } -} - -static const sample_t *sample_for(int id) -{ - sfxinfo_t *sfx = &S_sfx[id]; - - while (sfx->link) - sfx = sfx->link; - return &samples[sfx - S_sfx]; -} - -void I_InitSound(void) -{ - int lump, id; - - for (id = 1; id < NUMSFX; id++) - if (!S_sfx[id].link) - cache_sample(id); - for (id = 1; id < NUMSFX; id++) - if (S_sfx[id].link) - S_sfx[id].data = S_sfx[id].link->data; - - lump = W_CheckNumForName("GENMIDI"); - sound_ready = audio_init(lump >= 0 ? W_CacheLumpNum(lump, PU_STATIC) : NULL, - lump >= 0 ? W_LumpLength(lump) : 0) == 0; -} - -void I_ShutdownSound(void) -{ - if (sound_ready) - audio_shutdown(); - sound_ready = 0; -} - -void I_SetChannels(void) -{ -} - -int I_GetSfxLumpNum(sfxinfo_t *sfx) -{ - char name[16]; - - snprintf(name, sizeof(name), "ds%s", sfx->name); - if (W_CheckNumForName(name) < 0) - return W_GetNumForName("dspistol"); - return W_GetNumForName(name); -} - -int I_StartSound(int id, int vol, int sep, int pitch, int priority) -{ - const sample_t *sample = sample_for(id); - - (void)priority; - if (!sound_ready || !sample->samples) - return -1; - return audio_sfx_start(sample->samples, sample->length, sample->rate, - vol * 127 / 15, sep, pitch); -} - -void I_StopSound(int handle) -{ - audio_sfx_stop(handle); -} - -int I_SoundIsPlaying(int handle) -{ - return audio_sfx_playing(handle); -} - -void I_UpdateSoundParams(int handle, int vol, int sep, int pitch) -{ - audio_sfx_update(handle, vol * 127 / 15, sep, pitch); -} - -void I_InitMusic(void) -{ -} - -void I_ShutdownMusic(void) -{ - audio_music_stop(); -} - -void I_SetMusicVolume(int volume) -{ - audio_music_volume(volume * 127 / 15); -} - -void I_PauseSong(int handle) -{ - (void)handle; - audio_music_pause(1); -} - -void I_ResumeSong(int handle) -{ - (void)handle; - audio_music_pause(0); -} - -int I_RegisterSong(void *data) -{ - return audio_music_load(data) == 0 ? MUSIC_HANDLE : 0; -} - -void I_PlaySong(int handle, int looping) -{ - if (handle == MUSIC_HANDLE) - audio_music_play(looping); -} - -void I_StopSong(int handle) -{ - (void)handle; - audio_music_stop(); -} - -void I_UnRegisterSong(int handle) -{ - (void)handle; - audio_music_stop(); -} +#include + +#include "doomdef.h" +#include "i_sound.h" +#include "sounds.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "audio.h" + +#define SFX_HEADER 8 +#define SFX_PADDING 16 +#define MUSIC_HANDLE 1 + +typedef struct +{ + const byte *samples; + int length; + int rate; +} sample_t; + +static sample_t samples[NUMSFX]; +static int sound_ready; + +static void cache_sample(int id) +{ + const byte *lump; + int size, length; + + S_sfx[id].lumpnum = I_GetSfxLumpNum(&S_sfx[id]); + lump = W_CacheLumpNum(S_sfx[id].lumpnum, PU_STATIC); + size = W_LumpLength(S_sfx[id].lumpnum); + S_sfx[id].data = (void *)lump; + if (size < SFX_HEADER) + return; + + length = lump[4] | lump[5] << 8 | lump[6] << 16 | lump[7] << 24; + if (length > size - SFX_HEADER) + length = size - SFX_HEADER; + samples[id].samples = lump + SFX_HEADER; + samples[id].length = length; + samples[id].rate = lump[2] | lump[3] << 8; + if (length > 2 * SFX_PADDING) + { + samples[id].samples += SFX_PADDING; + samples[id].length -= 2 * SFX_PADDING; + } +} + +static const sample_t *sample_for(int id) +{ + sfxinfo_t *sfx = &S_sfx[id]; + + while (sfx->link) + sfx = sfx->link; + return &samples[sfx - S_sfx]; +} + +void I_InitSound(void) +{ + int lump, id; + + for (id = 1; id < NUMSFX; id++) + if (!S_sfx[id].link) + cache_sample(id); + for (id = 1; id < NUMSFX; id++) + if (S_sfx[id].link) + S_sfx[id].data = S_sfx[id].link->data; + + lump = W_CheckNumForName("GENMIDI"); + sound_ready = audio_init(lump >= 0 ? W_CacheLumpNum(lump, PU_STATIC) : NULL, + lump >= 0 ? W_LumpLength(lump) : 0) == 0; +} + +void I_ShutdownSound(void) +{ + if (sound_ready) + audio_shutdown(); + sound_ready = 0; +} + +void I_SetChannels(void) +{ +} + +int I_GetSfxLumpNum(sfxinfo_t *sfx) +{ + char name[16]; + + snprintf(name, sizeof(name), "ds%s", sfx->name); + if (W_CheckNumForName(name) < 0) + return W_GetNumForName("dspistol"); + return W_GetNumForName(name); +} + +int I_StartSound(int id, int vol, int sep, int pitch, int priority) +{ + const sample_t *sample = sample_for(id); + + (void)priority; + if (!sound_ready || !sample->samples) + return -1; + return audio_sfx_start(sample->samples, sample->length, sample->rate, + vol * 127 / 15, sep, pitch); +} + +void I_StopSound(int handle) +{ + audio_sfx_stop(handle); +} + +int I_SoundIsPlaying(int handle) +{ + return audio_sfx_playing(handle); +} + +void I_UpdateSoundParams(int handle, int vol, int sep, int pitch) +{ + audio_sfx_update(handle, vol * 127 / 15, sep, pitch); +} + +void I_InitMusic(void) +{ +} + +void I_ShutdownMusic(void) +{ + audio_music_stop(); +} + +void I_SetMusicVolume(int volume) +{ + audio_music_volume(volume * 127 / 15); +} + +void I_PauseSong(int handle) +{ + (void)handle; + audio_music_pause(1); +} + +void I_ResumeSong(int handle) +{ + (void)handle; + audio_music_pause(0); +} + +int I_RegisterSong(void *data) +{ + return audio_music_load(data) == 0 ? MUSIC_HANDLE : 0; +} + +void I_PlaySong(int handle, int looping) +{ + if (handle == MUSIC_HANDLE) + audio_music_play(looping); +} + +void I_StopSong(int handle) +{ + (void)handle; + audio_music_stop(); +} + +void I_UnRegisterSong(int handle) +{ + (void)handle; + audio_music_stop(); +} diff --git a/src/ps5/audio_out.c b/src/ps5/audio_out.c index ca17a7b..0ef9e42 100644 --- a/src/ps5/audio_out.c +++ b/src/ps5/audio_out.c @@ -1,63 +1,63 @@ -#include - -#include "platform.h" -#include "ps5.h" -#include "sce.h" - -#define GRAIN 256 - -static int port = -1; -static pthread_t thread; -static volatile int running; -static audio_render_fn render; -static void *render_user; - -static void *output_loop(void *arg) -{ - int16_t block[GRAIN * 2]; - - (void)arg; - while (running) - { - render(block, GRAIN, render_user); - sceAudioOutOutput(port, block); - } - return NULL; -} - -int plat_audio_init(int rate, audio_render_fn fn, void *user) -{ - sceAudioOutInit(); - port = sceAudioOutOpen(ps5_user(), SCE_AUDIO_PORT_MAIN, 0, GRAIN, rate, SCE_AUDIO_S16_STEREO); - if (port < 0) - port = sceAudioOutOpen(SCE_USER_SYSTEM, SCE_AUDIO_PORT_MAIN, 0, GRAIN, rate, - SCE_AUDIO_S16_STEREO); - if (port < 0) - { - plat_log("ps5: audio open failed %#x\n", port); - return -1; - } - - render = fn; - render_user = user; - running = 1; - plat_log("audio: output open at %d Hz\n", rate); - if (pthread_create(&thread, NULL, output_loop, NULL)) - { - running = 0; - sceAudioOutClose(port); - port = -1; - return -1; - } - return 0; -} - -void plat_audio_shutdown(void) -{ - if (!running) - return; - running = 0; - pthread_join(thread, NULL); - sceAudioOutClose(port); - port = -1; -} +#include + +#include "platform.h" +#include "ps5.h" +#include "sce.h" + +#define GRAIN 256 + +static int port = -1; +static pthread_t thread; +static volatile int running; +static audio_render_fn render; +static void *render_user; + +static void *output_loop(void *arg) +{ + int16_t block[GRAIN * 2]; + + (void)arg; + while (running) + { + render(block, GRAIN, render_user); + sceAudioOutOutput(port, block); + } + return NULL; +} + +int plat_audio_init(int rate, audio_render_fn fn, void *user) +{ + sceAudioOutInit(); + port = sceAudioOutOpen(ps5_user(), SCE_AUDIO_PORT_MAIN, 0, GRAIN, rate, SCE_AUDIO_S16_STEREO); + if (port < 0) + port = sceAudioOutOpen(SCE_USER_SYSTEM, SCE_AUDIO_PORT_MAIN, 0, GRAIN, rate, + SCE_AUDIO_S16_STEREO); + if (port < 0) + { + plat_log("ps5: audio open failed %#x\n", port); + return -1; + } + + render = fn; + render_user = user; + running = 1; + plat_log("audio: output open at %d Hz\n", rate); + if (pthread_create(&thread, NULL, output_loop, NULL)) + { + running = 0; + sceAudioOutClose(port); + port = -1; + return -1; + } + return 0; +} + +void plat_audio_shutdown(void) +{ + if (!running) + return; + running = 0; + pthread_join(thread, NULL); + sceAudioOutClose(port); + port = -1; +} diff --git a/src/ps5/capture.c b/src/ps5/capture.c index 0a98b6f..c28051b 100644 --- a/src/ps5/capture.c +++ b/src/ps5/capture.c @@ -1,146 +1,146 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "platform.h" -#include "ps5.h" -#include "sce.h" - -#define MAX_QUEUED 16 -#define DRAIN_STEP_US 100000 - -typedef struct -{ - char name[32]; - void *data; - size_t size; -} item_t; - -static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; -static item_t queue[MAX_QUEUED]; -static int queued; -static int listener = -1; - -static int send_all(int fd, const void *data, size_t size) -{ - const uint8_t *p = data; - - while (size) - { - ssize_t sent = send(fd, p, size, 0); - - if (sent <= 0) - return -1; - p += sent; - size -= (size_t)sent; - } - return 0; -} - -static int send_item(int fd, const item_t *item) -{ - uint32_t name_length = (uint32_t)strlen(item->name); - uint64_t size = item->size; - - return send_all(fd, &name_length, sizeof(name_length)) || - send_all(fd, item->name, name_length) || send_all(fd, &size, sizeof(size)) || - send_all(fd, item->data, item->size); -} - -static void *serve(void *arg) -{ - (void)arg; - for (;;) - { - int client = accept(listener, NULL, NULL); - uint32_t end = 0; - - if (client < 0) - continue; - pthread_mutex_lock(&lock); - while (queued && send_item(client, &queue[0]) == 0) - { - munmap(queue[0].data, queue[0].size); - memmove(queue, queue + 1, (size_t)--queued * sizeof(*queue)); - } - pthread_mutex_unlock(&lock); - send_all(client, &end, sizeof(end)); - close(client); - } - return NULL; -} - -void ps5_capture_serve(int port) -{ - struct sockaddr_in address; - pthread_t thread; - int yes = 1; - - listener = socket(AF_INET, SOCK_STREAM, 0); - if (listener < 0) - { - plat_log("capture: no socket\n"); - return; - } - setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - memset(&address, 0, sizeof(address)); - address.sin_family = AF_INET; - address.sin_port = htons((uint16_t)port); - address.sin_addr.s_addr = htonl(INADDR_ANY); - if (bind(listener, (struct sockaddr *)&address, sizeof(address)) || listen(listener, 1) || - pthread_create(&thread, NULL, serve, NULL)) - { - plat_log("capture: cannot serve on port %d\n", port); - close(listener); - listener = -1; - return; - } - plat_log("capture: serving on port %d\n", port); -} - -void ps5_capture_add(const char *name, const void *data, size_t size) -{ - void *copy; - - if (listener < 0) - return; - copy = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (copy == MAP_FAILED) - return; - memcpy(copy, data, size); - - pthread_mutex_lock(&lock); - if (queued < MAX_QUEUED) - { - snprintf(queue[queued].name, sizeof(queue[queued].name), "%s", name); - queue[queued].data = copy; - queue[queued].size = size; - queued++; - copy = NULL; - } - pthread_mutex_unlock(&lock); - if (copy) - munmap(copy, size); -} - -void ps5_capture_drain(uint32_t timeout_us) -{ - uint32_t waited = 0; - int pending = 1; - - while (listener >= 0 && waited < timeout_us) - { - pthread_mutex_lock(&lock); - pending = queued; - pthread_mutex_unlock(&lock); - if (!pending) - return; - sceKernelUsleep(DRAIN_STEP_US); - waited += DRAIN_STEP_US; - } -} +#include +#include +#include +#include +#include +#include +#include +#include + +#include "platform.h" +#include "ps5.h" +#include "sce.h" + +#define MAX_QUEUED 16 +#define DRAIN_STEP_US 100000 + +typedef struct +{ + char name[32]; + void *data; + size_t size; +} item_t; + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static item_t queue[MAX_QUEUED]; +static int queued; +static int listener = -1; + +static int send_all(int fd, const void *data, size_t size) +{ + const uint8_t *p = data; + + while (size) + { + ssize_t sent = send(fd, p, size, 0); + + if (sent <= 0) + return -1; + p += sent; + size -= (size_t)sent; + } + return 0; +} + +static int send_item(int fd, const item_t *item) +{ + uint32_t name_length = (uint32_t)strlen(item->name); + uint64_t size = item->size; + + return send_all(fd, &name_length, sizeof(name_length)) || + send_all(fd, item->name, name_length) || send_all(fd, &size, sizeof(size)) || + send_all(fd, item->data, item->size); +} + +static void *serve(void *arg) +{ + (void)arg; + for (;;) + { + int client = accept(listener, NULL, NULL); + uint32_t end = 0; + + if (client < 0) + continue; + pthread_mutex_lock(&lock); + while (queued && send_item(client, &queue[0]) == 0) + { + munmap(queue[0].data, queue[0].size); + memmove(queue, queue + 1, (size_t)--queued * sizeof(*queue)); + } + pthread_mutex_unlock(&lock); + send_all(client, &end, sizeof(end)); + close(client); + } + return NULL; +} + +void ps5_capture_serve(int port) +{ + struct sockaddr_in address; + pthread_t thread; + int yes = 1; + + listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener < 0) + { + plat_log("capture: no socket\n"); + return; + } + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons((uint16_t)port); + address.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(listener, (struct sockaddr *)&address, sizeof(address)) || listen(listener, 1) || + pthread_create(&thread, NULL, serve, NULL)) + { + plat_log("capture: cannot serve on port %d\n", port); + close(listener); + listener = -1; + return; + } + plat_log("capture: serving on port %d\n", port); +} + +void ps5_capture_add(const char *name, const void *data, size_t size) +{ + void *copy; + + if (listener < 0) + return; + copy = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (copy == MAP_FAILED) + return; + memcpy(copy, data, size); + + pthread_mutex_lock(&lock); + if (queued < MAX_QUEUED) + { + snprintf(queue[queued].name, sizeof(queue[queued].name), "%s", name); + queue[queued].data = copy; + queue[queued].size = size; + queued++; + copy = NULL; + } + pthread_mutex_unlock(&lock); + if (copy) + munmap(copy, size); +} + +void ps5_capture_drain(uint32_t timeout_us) +{ + uint32_t waited = 0; + int pending = 1; + + while (listener >= 0 && waited < timeout_us) + { + pthread_mutex_lock(&lock); + pending = queued; + pthread_mutex_unlock(&lock); + if (!pending) + return; + sceKernelUsleep(DRAIN_STEP_US); + waited += DRAIN_STEP_US; + } +} diff --git a/src/ps5/gpu.h b/src/ps5/gpu.h index 56324ef..343aa93 100644 --- a/src/ps5/gpu.h +++ b/src/ps5/gpu.h @@ -1,21 +1,21 @@ -#ifndef GPU_H -#define GPU_H - -#include - -#define GPU_TARGETS 2 - -typedef struct -{ - int video; - uint32_t *targets[GPU_TARGETS]; - int width, height, pitch; - int view_x, view_width; - int src_width, src_height; -} gpu_config_t; - -int gpu_init(const gpu_config_t *config); -int gpu_present(const uint8_t *pixels, const uint32_t *rgba, int target, int64_t marker); -int gpu_finish(void); - -#endif +#ifndef GPU_H +#define GPU_H + +#include + +#define GPU_TARGETS 2 + +typedef struct +{ + int video; + uint32_t *targets[GPU_TARGETS]; + int width, height, pitch; + int view_x, view_width; + int src_width, src_height; +} gpu_config_t; + +int gpu_init(const gpu_config_t *config); +int gpu_present(const uint8_t *pixels, const uint32_t *rgba, int target, int64_t marker); +int gpu_finish(void); + +#endif diff --git a/src/ps5/pad.c b/src/ps5/pad.c index 3c4efa8..44fd003 100644 --- a/src/ps5/pad.c +++ b/src/ps5/pad.c @@ -1,87 +1,87 @@ -#include - -#include "platform.h" -#include "ps5.h" -#include "sce.h" -#include "test_plan.h" - -static const struct -{ - uint32_t sce; - uint32_t pad; -} button_map[] = { - {SCE_PAD_UP, PAD_UP}, {SCE_PAD_DOWN, PAD_DOWN}, - {SCE_PAD_LEFT, PAD_LEFT}, {SCE_PAD_RIGHT, PAD_RIGHT}, - {SCE_PAD_CROSS, PAD_CROSS}, {SCE_PAD_CIRCLE, PAD_CIRCLE}, - {SCE_PAD_SQUARE, PAD_SQUARE}, {SCE_PAD_TRIANGLE, PAD_TRIANGLE}, - {SCE_PAD_L1, PAD_L1}, {SCE_PAD_R1, PAD_R1}, - {SCE_PAD_L2, PAD_L2}, {SCE_PAD_R2, PAD_R2}, - {SCE_PAD_L3, PAD_L3}, {SCE_PAD_R3, PAD_R3}, - {SCE_PAD_OPTIONS, PAD_OPTIONS}, {SCE_PAD_TOUCHPAD, PAD_TOUCHPAD}, -}; - -static int handle = -1; - -void ps5_pad_open(void) -{ - static const ScePadColor doom_red = {200, 0, 0, 255}; - - if (scePadInit() < 0) - { - plat_log("ps5: pad init failed\n"); - return; - } - handle = scePadOpen(ps5_user(), SCE_PAD_PORT_STANDARD, 0, NULL); - if (handle < 0) - { - plat_log("ps5: pad open failed %#x\n", handle); - return; - } - scePadSetVibrationMode(handle, 2); - scePadSetLightBar(handle, &doom_red); - plat_log("pad: open\n"); -} - -void ps5_pad_close(void) -{ - if (handle < 0) - return; - plat_pad_rumble(0, 0); - scePadClose(handle); - handle = -1; -} - -static int16_t stick(uint8_t raw) -{ - return (int16_t)((raw - 128) * 256); -} - -int plat_pad_read(pad_state_t *state) -{ - ScePadData data; - int connected; - size_t i; - - memset(state, 0, sizeof(*state)); - connected = handle >= 0 && scePadReadState(handle, &data) >= 0 && data.connected; - if (connected) - { - for (i = 0; i < sizeof(button_map) / sizeof(button_map[0]); i++) - if (data.buttons & button_map[i].sce) - state->buttons |= button_map[i].pad; - state->lx = stick(data.lx); - state->ly = stick(data.ly); - state->rx = stick(data.rx); - state->ry = stick(data.ry); - } - test_plan_apply(ps5_frame(), state); - return connected || test_plan_has_input() ? 0 : -1; -} - -void plat_pad_rumble(uint8_t strong, uint8_t weak) -{ - ScePadVibration vibration = {strong, weak}; - - if (handle >= 0) - scePadSetVibration(handle, &vibration); -} +#include + +#include "platform.h" +#include "ps5.h" +#include "sce.h" +#include "test_plan.h" + +static const struct +{ + uint32_t sce; + uint32_t pad; +} button_map[] = { + {SCE_PAD_UP, PAD_UP}, {SCE_PAD_DOWN, PAD_DOWN}, + {SCE_PAD_LEFT, PAD_LEFT}, {SCE_PAD_RIGHT, PAD_RIGHT}, + {SCE_PAD_CROSS, PAD_CROSS}, {SCE_PAD_CIRCLE, PAD_CIRCLE}, + {SCE_PAD_SQUARE, PAD_SQUARE}, {SCE_PAD_TRIANGLE, PAD_TRIANGLE}, + {SCE_PAD_L1, PAD_L1}, {SCE_PAD_R1, PAD_R1}, + {SCE_PAD_L2, PAD_L2}, {SCE_PAD_R2, PAD_R2}, + {SCE_PAD_L3, PAD_L3}, {SCE_PAD_R3, PAD_R3}, + {SCE_PAD_OPTIONS, PAD_OPTIONS}, {SCE_PAD_TOUCHPAD, PAD_TOUCHPAD}, +}; + +static int handle = -1; + +void ps5_pad_open(void) +{ + static const ScePadColor doom_red = {200, 0, 0, 255}; + + if (scePadInit() < 0) + { + plat_log("ps5: pad init failed\n"); + return; + } + handle = scePadOpen(ps5_user(), SCE_PAD_PORT_STANDARD, 0, NULL); + if (handle < 0) + { + plat_log("ps5: pad open failed %#x\n", handle); + return; + } + scePadSetVibrationMode(handle, 2); + scePadSetLightBar(handle, &doom_red); + plat_log("pad: open\n"); +} + +void ps5_pad_close(void) +{ + if (handle < 0) + return; + plat_pad_rumble(0, 0); + scePadClose(handle); + handle = -1; +} + +static int16_t stick(uint8_t raw) +{ + return (int16_t)((raw - 128) * 256); +} + +int plat_pad_read(pad_state_t *state) +{ + ScePadData data; + int connected; + size_t i; + + memset(state, 0, sizeof(*state)); + connected = handle >= 0 && scePadReadState(handle, &data) >= 0 && data.connected; + if (connected) + { + for (i = 0; i < sizeof(button_map) / sizeof(button_map[0]); i++) + if (data.buttons & button_map[i].sce) + state->buttons |= button_map[i].pad; + state->lx = stick(data.lx); + state->ly = stick(data.ly); + state->rx = stick(data.rx); + state->ry = stick(data.ry); + } + test_plan_apply(ps5_frame(), state); + return connected || test_plan_has_input() ? 0 : -1; +} + +void plat_pad_rumble(uint8_t strong, uint8_t weak) +{ + ScePadVibration vibration = {strong, weak}; + + if (handle >= 0) + scePadSetVibration(handle, &vibration); +} diff --git a/src/ps5/present.cl b/src/ps5/present.cl index b9b2397..e000598 100644 --- a/src/ps5/present.cl +++ b/src/ps5/present.cl @@ -1,64 +1,64 @@ -#include "tiling.h" - -#define GROUP 64 - -static unsigned int blend(unsigned int a, unsigned int b, float t) -{ - unsigned int out = 0; - - for (int shift = 0; shift < 24; shift += 8) - { - float ca = (a >> shift) & 0xff, cb = (b >> shift) & 0xff; - - out |= (unsigned int)(ca + (cb - ca) * t + 0.5f) << shift; - } - return out | 0xff000000u; -} - -static int clampi(int v, int lo, int hi) -{ - return v < lo ? lo : v > hi ? hi : v; -} - -static float saturate(float v) -{ - return __builtin_fminf(__builtin_fmaxf(v, 0.0f), 1.0f); -} - -__kernel __attribute__((reqd_work_group_size(GROUP, 1, 1))) void -present(__global const unsigned char *src, __global const unsigned int *palette, - __global unsigned int *dst, unsigned int pitch, unsigned int out_width, - unsigned int out_height, unsigned int view_x, unsigned int view_width, - unsigned int src_width, unsigned int src_height) -{ - unsigned int y = __builtin_amdgcn_workgroup_id_x(); - float scale_x = (float)view_width / src_width; - float scale_y = (float)out_height / src_height; - float fy = (y + 0.5f) / scale_y - 0.5f; - float iy = __builtin_floorf(fy); - float ty = saturate((fy - iy - 0.5f) * scale_y + 0.5f); - int y0 = clampi((int)iy, 0, src_height - 1), y1 = clampi((int)iy + 1, 0, src_height - 1); - __global const unsigned char *row0 = src + y0 * src_width; - __global const unsigned char *row1 = src + y1 * src_width; - - if (y >= out_height) - return; - - for (unsigned int x = __builtin_amdgcn_workitem_id_x(); x < out_width; x += GROUP) - { - unsigned int color = 0xff000000u; - - if (x >= view_x && x < view_x + view_width) - { - float fx = (x - view_x + 0.5f) / scale_x - 0.5f; - float ix = __builtin_floorf(fx); - float tx = saturate((fx - ix - 0.5f) * scale_x + 0.5f); - int x0 = clampi((int)ix, 0, src_width - 1), x1 = clampi((int)ix + 1, 0, src_width - 1); - unsigned int top = blend(palette[row0[x0]], palette[row0[x1]], tx); - unsigned int bottom = blend(palette[row1[x0]], palette[row1[x1]], tx); - - color = blend(top, bottom, ty); - } - dst[tiled_index(x, y, pitch)] = color; - } -} +#include "tiling.h" + +#define GROUP 64 + +static unsigned int blend(unsigned int a, unsigned int b, float t) +{ + unsigned int out = 0; + + for (int shift = 0; shift < 24; shift += 8) + { + float ca = (a >> shift) & 0xff, cb = (b >> shift) & 0xff; + + out |= (unsigned int)(ca + (cb - ca) * t + 0.5f) << shift; + } + return out | 0xff000000u; +} + +static int clampi(int v, int lo, int hi) +{ + return v < lo ? lo : v > hi ? hi : v; +} + +static float saturate(float v) +{ + return __builtin_fminf(__builtin_fmaxf(v, 0.0f), 1.0f); +} + +__kernel __attribute__((reqd_work_group_size(GROUP, 1, 1))) void +present(__global const unsigned char *src, __global const unsigned int *palette, + __global unsigned int *dst, unsigned int pitch, unsigned int out_width, + unsigned int out_height, unsigned int view_x, unsigned int view_width, + unsigned int src_width, unsigned int src_height) +{ + unsigned int y = __builtin_amdgcn_workgroup_id_x(); + float scale_x = (float)view_width / src_width; + float scale_y = (float)out_height / src_height; + float fy = (y + 0.5f) / scale_y - 0.5f; + float iy = __builtin_floorf(fy); + float ty = saturate((fy - iy - 0.5f) * scale_y + 0.5f); + int y0 = clampi((int)iy, 0, src_height - 1), y1 = clampi((int)iy + 1, 0, src_height - 1); + __global const unsigned char *row0 = src + y0 * src_width; + __global const unsigned char *row1 = src + y1 * src_width; + + if (y >= out_height) + return; + + for (unsigned int x = __builtin_amdgcn_workitem_id_x(); x < out_width; x += GROUP) + { + unsigned int color = 0xff000000u; + + if (x >= view_x && x < view_x + view_width) + { + float fx = (x - view_x + 0.5f) / scale_x - 0.5f; + float ix = __builtin_floorf(fx); + float tx = saturate((fx - ix - 0.5f) * scale_x + 0.5f); + int x0 = clampi((int)ix, 0, src_width - 1), x1 = clampi((int)ix + 1, 0, src_width - 1); + unsigned int top = blend(palette[row0[x0]], palette[row0[x1]], tx); + unsigned int bottom = blend(palette[row1[x0]], palette[row1[x1]], tx); + + color = blend(top, bottom, ty); + } + dst[tiled_index(x, y, pitch)] = color; + } +} diff --git a/src/ps5/ps5.h b/src/ps5/ps5.h index c20a689..64ed038 100644 --- a/src/ps5/ps5.h +++ b/src/ps5/ps5.h @@ -1,18 +1,18 @@ -#ifndef PS5_H -#define PS5_H - -#include -#include - -int ps5_user(void); -int ps5_frame(void); -int ps5_test_cpu_present(void); -void *ps5_gpu_memory(size_t bytes); -void ps5_cache_flush(const volatile void *address, size_t bytes); -void ps5_pad_open(void); -void ps5_pad_close(void); -void ps5_capture_serve(int port); -void ps5_capture_add(const char *name, const void *data, size_t size); -void ps5_capture_drain(uint32_t timeout_us); - -#endif +#ifndef PS5_H +#define PS5_H + +#include +#include + +int ps5_user(void); +int ps5_frame(void); +int ps5_test_cpu_present(void); +void *ps5_gpu_memory(size_t bytes); +void ps5_cache_flush(const volatile void *address, size_t bytes); +void ps5_pad_open(void); +void ps5_pad_close(void); +void ps5_capture_serve(int port); +void ps5_capture_add(const char *name, const void *data, size_t size); +void ps5_capture_drain(uint32_t timeout_us); + +#endif diff --git a/src/ps5/sce.h b/src/ps5/sce.h index 969ab6c..d7f3a19 100644 --- a/src/ps5/sce.h +++ b/src/ps5/sce.h @@ -1,192 +1,192 @@ -#ifndef SCE_H -#define SCE_H - -#include -#include - -#define SCE_USER_SYSTEM 0xff - -#define SCE_MEMORY_GPU_SHARED 12 -#define SCE_PROT_CPU_GPU_RW 0x33 -#define SCE_MEMORY_ALIGN 0x200000 - -#define SCE_VIDEO_BUS_MAIN 0 -#define SCE_VIDEO_FLIP_VSYNC 1 -#define SCE_VIDEO_FORMAT_RGBA8_SRGB UINT64_C(0x8000000022000000) -#define SCE_VIDEO_TILING_TILED 0 - -#define SCE_PAD_PORT_STANDARD 0 -#define SCE_PAD_L3 0x0002 -#define SCE_PAD_R3 0x0004 -#define SCE_PAD_OPTIONS 0x0008 -#define SCE_PAD_UP 0x0010 -#define SCE_PAD_RIGHT 0x0020 -#define SCE_PAD_DOWN 0x0040 -#define SCE_PAD_LEFT 0x0080 -#define SCE_PAD_L2 0x0100 -#define SCE_PAD_R2 0x0200 -#define SCE_PAD_L1 0x0400 -#define SCE_PAD_R1 0x0800 -#define SCE_PAD_TRIANGLE 0x1000 -#define SCE_PAD_CIRCLE 0x2000 -#define SCE_PAD_CROSS 0x4000 -#define SCE_PAD_SQUARE 0x8000 -#define SCE_PAD_TOUCHPAD 0x100000 - -#define SCE_AUDIO_PORT_MAIN 0 -#define SCE_AUDIO_S16_STEREO 1 - -typedef struct -{ - void *data; - void *metadata; - void *reserved[2]; -} SceVideoOutBuffer; - -typedef struct -{ - uint8_t opaque[80]; -} SceVideoOutAttribute; - -typedef struct -{ - uint64_t count; - uint64_t process_time; - uint64_t tsc; - int64_t flip_arg; - uint8_t reserved[96]; -} SceVideoOutFlipStatus; - -typedef struct -{ - uint16_t x, y; - uint8_t finger; - uint8_t reserved[3]; -} ScePadTouch; - -typedef struct -{ - uint8_t fingers; - uint8_t reserved1[3]; - uint32_t reserved2; - ScePadTouch touch[2]; -} ScePadTouchData; - -typedef struct -{ - uint32_t buttons; - uint8_t lx, ly, rx, ry; - uint8_t l2, r2; - uint16_t reserved; - float orientation[4]; - float velocity[3]; - float acceleration[3]; - ScePadTouchData touch; - uint8_t connected; - uint64_t timestamp; - uint8_t extension[16]; - uint8_t count; - uint8_t reserved2[15]; -} ScePadData; - -typedef struct -{ - uint8_t large_motor; - uint8_t small_motor; -} ScePadVibration; - -typedef struct -{ - uint8_t r, g, b, a; -} ScePadColor; - -typedef struct -{ - char reserved[45]; - char message[3075]; -} SceNotificationRequest; - -int64_t sceKernelGetDirectMemorySize(void); -int sceKernelAllocateDirectMemory(int64_t start, int64_t end, size_t length, size_t alignment, - int type, int64_t *physical); -int sceKernelMapDirectMemory(void **address, size_t length, int protection, int flags, - int64_t physical, size_t alignment); -uint64_t sceKernelGetProcessTime(void); -int sceKernelUsleep(uint32_t microseconds); -int sceKernelSendNotificationRequest(int device, SceNotificationRequest *request, size_t size, - int blocking); -int sceKernelDebugOutText(int channel, const char *text); - -int sceSystemServiceHideSplashScreen(void); -int sceSystemServiceLoadExec(const char *path, char *const *argv); - -int sceUserServiceInitialize(void *params); -int sceUserServiceGetInitialUser(int *user); - -int sceVideoOutOpen(int user, int bus, int index, const void *params); -int sceVideoOutClose(int handle); -int sceVideoOutSetFlipRate(int handle, int rate); -void sceVideoOutSetBufferAttribute2(SceVideoOutAttribute *attribute, uint64_t format, - uint32_t tiling, uint32_t width, uint32_t height, - uint64_t option, uint32_t dcc_control, uint64_t dcc_clear); -int sceVideoOutRegisterBuffers2(int handle, int set, int first, SceVideoOutBuffer *buffers, - int count, SceVideoOutAttribute *attribute, int category, - void *option); -int sceVideoOutSubmitFlip(int handle, int index, uint32_t mode, int64_t argument); -int sceVideoOutIsFlipPending(int handle); -int sceVideoOutWaitVblank(int handle); -int sceVideoOutGetFlipStatus(int handle, SceVideoOutFlipStatus *status); - -int scePadInit(void); -int scePadOpen(int user, int type, int index, const void *params); -int scePadReadState(int handle, ScePadData *data); -int scePadSetVibrationMode(int handle, int mode); -int scePadSetVibration(int handle, const ScePadVibration *vibration); -int scePadSetLightBar(int handle, const ScePadColor *color); -int scePadClose(int handle); - -typedef struct SceAgcCommandBuffer SceAgcCommandBuffer; -typedef uint8_t (*SceAgcOutOfSpace)(SceAgcCommandBuffer *buffer, uint32_t words, void *user); - -struct SceAgcCommandBuffer -{ - uint32_t *bottom; - uint32_t *top; - uint32_t *up; - uint32_t *down; - SceAgcOutOfSpace out_of_space; - void *user; - uint32_t reserved_words; - uint32_t padding; -}; - -typedef struct -{ - void *words; - uint32_t word_count; - uint8_t flags; - uint8_t padding[3]; -} SceAgcSubmission; - -int sceAgcInit(uint32_t version); -uint32_t *sceAgcCbSetShRegisterRangeDirect(SceAgcCommandBuffer *cb, uint32_t offset, - const uint32_t *values, uint32_t count); -uint32_t *sceAgcCbDispatch(SceAgcCommandBuffer *cb, uint32_t x, uint32_t y, uint32_t z, - uint32_t initiator); -uint32_t *sceAgcDcbAcquireMem(SceAgcCommandBuffer *cb, uint8_t engine, uint32_t cb_db_op, - uint32_t gcr_control, uint64_t base, uint64_t size, - uint32_t poll_interval); -uint32_t *sceAgcDcbSetFlip(SceAgcCommandBuffer *cb, uint32_t video, int index, uint32_t mode, - int64_t argument); -int sceAgcSuspendPoint(void); -uint32_t sceAgcDriverGetWaitRenderingPacketSizeInDwords(void); -uint32_t sceAgcDriverWaitUntilSafeForRendering(uint32_t **up, uint32_t words, uint32_t reserved, - uint32_t video, int index); -int sceAgcDriverSubmitDcb(SceAgcSubmission *submission); - -int sceAudioOutInit(void); -int sceAudioOutOpen(int user, int port, int index, uint32_t grain, uint32_t rate, uint32_t format); -int sceAudioOutOutput(int handle, const void *samples); -int sceAudioOutClose(int handle); - -#endif +#ifndef SCE_H +#define SCE_H + +#include +#include + +#define SCE_USER_SYSTEM 0xff + +#define SCE_MEMORY_GPU_SHARED 12 +#define SCE_PROT_CPU_GPU_RW 0x33 +#define SCE_MEMORY_ALIGN 0x200000 + +#define SCE_VIDEO_BUS_MAIN 0 +#define SCE_VIDEO_FLIP_VSYNC 1 +#define SCE_VIDEO_FORMAT_RGBA8_SRGB UINT64_C(0x8000000022000000) +#define SCE_VIDEO_TILING_TILED 0 + +#define SCE_PAD_PORT_STANDARD 0 +#define SCE_PAD_L3 0x0002 +#define SCE_PAD_R3 0x0004 +#define SCE_PAD_OPTIONS 0x0008 +#define SCE_PAD_UP 0x0010 +#define SCE_PAD_RIGHT 0x0020 +#define SCE_PAD_DOWN 0x0040 +#define SCE_PAD_LEFT 0x0080 +#define SCE_PAD_L2 0x0100 +#define SCE_PAD_R2 0x0200 +#define SCE_PAD_L1 0x0400 +#define SCE_PAD_R1 0x0800 +#define SCE_PAD_TRIANGLE 0x1000 +#define SCE_PAD_CIRCLE 0x2000 +#define SCE_PAD_CROSS 0x4000 +#define SCE_PAD_SQUARE 0x8000 +#define SCE_PAD_TOUCHPAD 0x100000 + +#define SCE_AUDIO_PORT_MAIN 0 +#define SCE_AUDIO_S16_STEREO 1 + +typedef struct +{ + void *data; + void *metadata; + void *reserved[2]; +} SceVideoOutBuffer; + +typedef struct +{ + uint8_t opaque[80]; +} SceVideoOutAttribute; + +typedef struct +{ + uint64_t count; + uint64_t process_time; + uint64_t tsc; + int64_t flip_arg; + uint8_t reserved[96]; +} SceVideoOutFlipStatus; + +typedef struct +{ + uint16_t x, y; + uint8_t finger; + uint8_t reserved[3]; +} ScePadTouch; + +typedef struct +{ + uint8_t fingers; + uint8_t reserved1[3]; + uint32_t reserved2; + ScePadTouch touch[2]; +} ScePadTouchData; + +typedef struct +{ + uint32_t buttons; + uint8_t lx, ly, rx, ry; + uint8_t l2, r2; + uint16_t reserved; + float orientation[4]; + float velocity[3]; + float acceleration[3]; + ScePadTouchData touch; + uint8_t connected; + uint64_t timestamp; + uint8_t extension[16]; + uint8_t count; + uint8_t reserved2[15]; +} ScePadData; + +typedef struct +{ + uint8_t large_motor; + uint8_t small_motor; +} ScePadVibration; + +typedef struct +{ + uint8_t r, g, b, a; +} ScePadColor; + +typedef struct +{ + char reserved[45]; + char message[3075]; +} SceNotificationRequest; + +int64_t sceKernelGetDirectMemorySize(void); +int sceKernelAllocateDirectMemory(int64_t start, int64_t end, size_t length, size_t alignment, + int type, int64_t *physical); +int sceKernelMapDirectMemory(void **address, size_t length, int protection, int flags, + int64_t physical, size_t alignment); +uint64_t sceKernelGetProcessTime(void); +int sceKernelUsleep(uint32_t microseconds); +int sceKernelSendNotificationRequest(int device, SceNotificationRequest *request, size_t size, + int blocking); +int sceKernelDebugOutText(int channel, const char *text); + +int sceSystemServiceHideSplashScreen(void); +int sceSystemServiceLoadExec(const char *path, char *const *argv); + +int sceUserServiceInitialize(void *params); +int sceUserServiceGetInitialUser(int *user); + +int sceVideoOutOpen(int user, int bus, int index, const void *params); +int sceVideoOutClose(int handle); +int sceVideoOutSetFlipRate(int handle, int rate); +void sceVideoOutSetBufferAttribute2(SceVideoOutAttribute *attribute, uint64_t format, + uint32_t tiling, uint32_t width, uint32_t height, + uint64_t option, uint32_t dcc_control, uint64_t dcc_clear); +int sceVideoOutRegisterBuffers2(int handle, int set, int first, SceVideoOutBuffer *buffers, + int count, SceVideoOutAttribute *attribute, int category, + void *option); +int sceVideoOutSubmitFlip(int handle, int index, uint32_t mode, int64_t argument); +int sceVideoOutIsFlipPending(int handle); +int sceVideoOutWaitVblank(int handle); +int sceVideoOutGetFlipStatus(int handle, SceVideoOutFlipStatus *status); + +int scePadInit(void); +int scePadOpen(int user, int type, int index, const void *params); +int scePadReadState(int handle, ScePadData *data); +int scePadSetVibrationMode(int handle, int mode); +int scePadSetVibration(int handle, const ScePadVibration *vibration); +int scePadSetLightBar(int handle, const ScePadColor *color); +int scePadClose(int handle); + +typedef struct SceAgcCommandBuffer SceAgcCommandBuffer; +typedef uint8_t (*SceAgcOutOfSpace)(SceAgcCommandBuffer *buffer, uint32_t words, void *user); + +struct SceAgcCommandBuffer +{ + uint32_t *bottom; + uint32_t *top; + uint32_t *up; + uint32_t *down; + SceAgcOutOfSpace out_of_space; + void *user; + uint32_t reserved_words; + uint32_t padding; +}; + +typedef struct +{ + void *words; + uint32_t word_count; + uint8_t flags; + uint8_t padding[3]; +} SceAgcSubmission; + +int sceAgcInit(uint32_t version); +uint32_t *sceAgcCbSetShRegisterRangeDirect(SceAgcCommandBuffer *cb, uint32_t offset, + const uint32_t *values, uint32_t count); +uint32_t *sceAgcCbDispatch(SceAgcCommandBuffer *cb, uint32_t x, uint32_t y, uint32_t z, + uint32_t initiator); +uint32_t *sceAgcDcbAcquireMem(SceAgcCommandBuffer *cb, uint8_t engine, uint32_t cb_db_op, + uint32_t gcr_control, uint64_t base, uint64_t size, + uint32_t poll_interval); +uint32_t *sceAgcDcbSetFlip(SceAgcCommandBuffer *cb, uint32_t video, int index, uint32_t mode, + int64_t argument); +int sceAgcSuspendPoint(void); +uint32_t sceAgcDriverGetWaitRenderingPacketSizeInDwords(void); +uint32_t sceAgcDriverWaitUntilSafeForRendering(uint32_t **up, uint32_t words, uint32_t reserved, + uint32_t video, int index); +int sceAgcDriverSubmitDcb(SceAgcSubmission *submission); + +int sceAudioOutInit(void); +int sceAudioOutOpen(int user, int port, int index, uint32_t grain, uint32_t rate, uint32_t format); +int sceAudioOutOutput(int handle, const void *samples); +int sceAudioOutClose(int handle); + +#endif diff --git a/src/ps5/system.c b/src/ps5/system.c index dd8901f..62dfc22 100644 --- a/src/ps5/system.c +++ b/src/ps5/system.c @@ -1,202 +1,202 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "platform.h" -#include "ps5.h" -#include "sce.h" -#include "test_plan.h" - -#define SAVE_DIR "/download0" -#define LOG_PATH SAVE_DIR "/doom.log" -#define WAD_DIR "/app0/wads" -#define TEST_PLAN_PATH "/app0/test.cfg" -#define TEST_PLAN_BYTES 8192 -#define LOG_DRAIN_US 50000 - -static const char *const wad_dirs[] = {WAD_DIR, NULL}; -static int user = SCE_USER_SYSTEM; -static int log_file = -1; -static int test_cpu_present; - -int ps5_user(void) -{ - return user; -} - -static void log_text(const char *text, size_t length) -{ - char line[512]; - size_t start = 0, i; - - if (log_file >= 0) - write(log_file, text, length); - for (i = 0; i < length; i++) - { - if (text[i] != '\n' && i + 1 < length && i - start < sizeof(line) - 2) - continue; - memcpy(line, text + start, i - start + 1); - line[i - start + 1] = 0; - sceKernelDebugOutText(0, line); - start = i + 1; - } -} - -static void *pump_output(void *arg) -{ - int pipe_read = (int)(intptr_t)arg; - char chunk[1024]; - ssize_t length; - - while ((length = read(pipe_read, chunk, sizeof(chunk) - 1)) > 0) - { - chunk[length] = 0; - log_text(chunk, (size_t)length); - } - return NULL; -} - -static void capture_output(void) -{ - pthread_t thread; - FILE *output; - int fds[2]; - - log_file = open(LOG_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (log_file < 0) - plat_log("ps5: cannot write %s (errno %d)\n", LOG_PATH, errno); - if (pipe(fds)) - { - plat_log("ps5: no output pipe (errno %d)\n", errno); - return; - } - output = fdopen(fds[1], "w"); - if (!output) - { - plat_log("ps5: cannot redirect output (errno %d)\n", errno); - return; - } - setvbuf(output, NULL, _IOLBF, 0); - stdout = output; - stderr = output; - pthread_create(&thread, NULL, pump_output, (void *)(intptr_t)fds[0]); -} - -int ps5_test_cpu_present(void) -{ - return test_cpu_present; -} - -static void load_test_plan(void) -{ - static char text[TEST_PLAN_BYTES]; - char *line, *save; - ssize_t length; - int fd = open(TEST_PLAN_PATH, O_RDONLY); - - if (fd < 0) - return; - length = read(fd, text, sizeof(text) - 1); - close(fd); - if (length <= 0) - return; - text[length] = 0; - - for (line = strtok_r(text, "\r\n", &save); line; line = strtok_r(NULL, "\r\n", &save)) - { - if (!strncmp(line, "input ", 6)) - test_plan_input(line + 6); - else if (!strncmp(line, "capture ", 8)) - test_plan_captures(line + 8); - else if (!strncmp(line, "frames ", 7)) - test_plan_limit(atoi(line + 7)); - else if (!strncmp(line, "serve ", 6)) - ps5_capture_serve(atoi(line + 6)); - else if (!strcmp(line, "present cpu")) - test_cpu_present = 1; - } - plat_log("ps5: test plan loaded\n"); -} - -int plat_init(int argc, char **argv) -{ - int initial; - - (void)argc; - (void)argv; - capture_output(); - - if (sceUserServiceInitialize(NULL) < 0) - plat_log("ps5: user service unavailable\n"); - else if (sceUserServiceGetInitialUser(&initial) == 0) - user = initial; - plat_log("ps5: user %d\n", user); - load_test_plan(); - - ps5_pad_open(); - return 0; -} - -void plat_shutdown(void) -{ - ps5_pad_close(); -} - -_Noreturn void plat_exit(int code) -{ - plat_log("ps5: exit %d\n", code); - fflush(NULL); - sceKernelUsleep(LOG_DRAIN_US); - sceSystemServiceLoadExec("exit", NULL); - for (;;) - sceKernelUsleep(1000000); -} - -void plat_log(const char *fmt, ...) -{ - char line[512]; - va_list args; - int length; - - va_start(args, fmt); - length = vsnprintf(line, sizeof(line), fmt, args); - va_end(args); - if (length < 0) - return; - log_text(line, (size_t)length < sizeof(line) ? (size_t)length : sizeof(line) - 1); -} - -void plat_alert(const char *message) -{ - SceNotificationRequest request; - - memset(&request, 0, sizeof(request)); - snprintf(request.message, sizeof(request.message), "DOOM: %s", message); - sceKernelSendNotificationRequest(0, &request, sizeof(request), 0); -} - -uint64_t plat_ticks_us(void) -{ - return sceKernelGetProcessTime(); -} - -void plat_sleep_us(uint32_t us) -{ - sceKernelUsleep(us); -} - -const char *const *plat_wad_dirs(void) -{ - return wad_dirs; -} - -const char *plat_save_dir(void) -{ - return SAVE_DIR; -} +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "platform.h" +#include "ps5.h" +#include "sce.h" +#include "test_plan.h" + +#define SAVE_DIR "/download0" +#define LOG_PATH SAVE_DIR "/doom.log" +#define WAD_DIR "/app0/wads" +#define TEST_PLAN_PATH "/app0/test.cfg" +#define TEST_PLAN_BYTES 8192 +#define LOG_DRAIN_US 50000 + +static const char *const wad_dirs[] = {WAD_DIR, NULL}; +static int user = SCE_USER_SYSTEM; +static int log_file = -1; +static int test_cpu_present; + +int ps5_user(void) +{ + return user; +} + +static void log_text(const char *text, size_t length) +{ + char line[512]; + size_t start = 0, i; + + if (log_file >= 0) + write(log_file, text, length); + for (i = 0; i < length; i++) + { + if (text[i] != '\n' && i + 1 < length && i - start < sizeof(line) - 2) + continue; + memcpy(line, text + start, i - start + 1); + line[i - start + 1] = 0; + sceKernelDebugOutText(0, line); + start = i + 1; + } +} + +static void *pump_output(void *arg) +{ + int pipe_read = (int)(intptr_t)arg; + char chunk[1024]; + ssize_t length; + + while ((length = read(pipe_read, chunk, sizeof(chunk) - 1)) > 0) + { + chunk[length] = 0; + log_text(chunk, (size_t)length); + } + return NULL; +} + +static void capture_output(void) +{ + pthread_t thread; + FILE *output; + int fds[2]; + + log_file = open(LOG_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (log_file < 0) + plat_log("ps5: cannot write %s (errno %d)\n", LOG_PATH, errno); + if (pipe(fds)) + { + plat_log("ps5: no output pipe (errno %d)\n", errno); + return; + } + output = fdopen(fds[1], "w"); + if (!output) + { + plat_log("ps5: cannot redirect output (errno %d)\n", errno); + return; + } + setvbuf(output, NULL, _IOLBF, 0); + stdout = output; + stderr = output; + pthread_create(&thread, NULL, pump_output, (void *)(intptr_t)fds[0]); +} + +int ps5_test_cpu_present(void) +{ + return test_cpu_present; +} + +static void load_test_plan(void) +{ + static char text[TEST_PLAN_BYTES]; + char *line, *save; + ssize_t length; + int fd = open(TEST_PLAN_PATH, O_RDONLY); + + if (fd < 0) + return; + length = read(fd, text, sizeof(text) - 1); + close(fd); + if (length <= 0) + return; + text[length] = 0; + + for (line = strtok_r(text, "\r\n", &save); line; line = strtok_r(NULL, "\r\n", &save)) + { + if (!strncmp(line, "input ", 6)) + test_plan_input(line + 6); + else if (!strncmp(line, "capture ", 8)) + test_plan_captures(line + 8); + else if (!strncmp(line, "frames ", 7)) + test_plan_limit(atoi(line + 7)); + else if (!strncmp(line, "serve ", 6)) + ps5_capture_serve(atoi(line + 6)); + else if (!strcmp(line, "present cpu")) + test_cpu_present = 1; + } + plat_log("ps5: test plan loaded\n"); +} + +int plat_init(int argc, char **argv) +{ + int initial; + + (void)argc; + (void)argv; + capture_output(); + + if (sceUserServiceInitialize(NULL) < 0) + plat_log("ps5: user service unavailable\n"); + else if (sceUserServiceGetInitialUser(&initial) == 0) + user = initial; + plat_log("ps5: user %d\n", user); + load_test_plan(); + + ps5_pad_open(); + return 0; +} + +void plat_shutdown(void) +{ + ps5_pad_close(); +} + +_Noreturn void plat_exit(int code) +{ + plat_log("ps5: exit %d\n", code); + fflush(NULL); + sceKernelUsleep(LOG_DRAIN_US); + sceSystemServiceLoadExec("exit", NULL); + for (;;) + sceKernelUsleep(1000000); +} + +void plat_log(const char *fmt, ...) +{ + char line[512]; + va_list args; + int length; + + va_start(args, fmt); + length = vsnprintf(line, sizeof(line), fmt, args); + va_end(args); + if (length < 0) + return; + log_text(line, (size_t)length < sizeof(line) ? (size_t)length : sizeof(line) - 1); +} + +void plat_alert(const char *message) +{ + SceNotificationRequest request; + + memset(&request, 0, sizeof(request)); + snprintf(request.message, sizeof(request.message), "DOOM: %s", message); + sceKernelSendNotificationRequest(0, &request, sizeof(request), 0); +} + +uint64_t plat_ticks_us(void) +{ + return sceKernelGetProcessTime(); +} + +void plat_sleep_us(uint32_t us) +{ + sceKernelUsleep(us); +} + +const char *const *plat_wad_dirs(void) +{ + return wad_dirs; +} + +const char *plat_save_dir(void) +{ + return SAVE_DIR; +} diff --git a/src/ps5/video.c b/src/ps5/video.c index 4676849..a3e51fa 100644 --- a/src/ps5/video.c +++ b/src/ps5/video.c @@ -1,204 +1,204 @@ -#include -#include - -#include "gpu.h" -#include "platform.h" -#include "ps5.h" -#include "sce.h" -#include "test_plan.h" -#include "tiling.h" - -#define OUT_WIDTH 1920 -#define OUT_HEIGHT 1080 -#define VIEW_WIDTH 1440 -#define VIEW_X ((OUT_WIDTH - VIEW_WIDTH) / 2) -#define BUFFER_BYTES 0x1000000 -#define FRAME_PIXELS tiled_pixels(OUT_WIDTH, OUT_HEIGHT) -#define FRAME_BYTES ((size_t)FRAME_PIXELS * 4) -#define FORCE_CPU_BUTTONS (PAD_L1 | PAD_R1) -#define CAPTURE_DRAIN_US 20000000 - -static int video = -1; -static uint32_t *buffers[GPU_TARGETS]; -static int next_buffer; -static int64_t flips; -static int use_gpu; -static int splash_hidden; - -static int src_width, src_height; -static uint16_t column_of[VIEW_WIDTH]; -static uint16_t row_of[OUT_HEIGHT]; -static uint32_t tile_columns[VIEW_WIDTH]; -static uint32_t rgba[256]; - -static int open_video(void) -{ - SceVideoOutBuffer out[GPU_TARGETS]; - SceVideoOutAttribute attribute = {0}; - uint8_t *memory; - int i, result; - - video = sceVideoOutOpen(SCE_USER_SYSTEM, SCE_VIDEO_BUS_MAIN, 0, NULL); - if (video < 0) - { - plat_log("video: open %#x\n", video); - return -1; - } - memory = ps5_gpu_memory(BUFFER_BYTES * GPU_TARGETS); - if (!memory) - return -1; - - memset(out, 0, sizeof(out)); - for (i = 0; i < GPU_TARGETS; i++) - { - uint32_t *p; - - buffers[i] = (uint32_t *)(memory + i * BUFFER_BYTES); - for (p = buffers[i]; p < buffers[i] + FRAME_PIXELS; p++) - *p = 0xff000000u; - ps5_cache_flush(buffers[i], FRAME_BYTES); - out[i].data = buffers[i]; - } - - sceVideoOutSetFlipRate(video, 0); - sceVideoOutSetBufferAttribute2(&attribute, SCE_VIDEO_FORMAT_RGBA8_SRGB, SCE_VIDEO_TILING_TILED, - OUT_WIDTH, OUT_HEIGHT, 0, 0, 0); - result = sceVideoOutRegisterBuffers2(video, 0, 0, out, GPU_TARGETS, &attribute, 0, NULL); - if (result < 0) - { - plat_log("video: register buffers %#x\n", result); - return -1; - } - return 0; -} - -static int gpu_wanted(void) -{ - pad_state_t pad; - - if (plat_pad_read(&pad) == 0 && (pad.buttons & FORCE_CPU_BUTTONS) == FORCE_CPU_BUTTONS) - { - plat_log("video: L1+R1 held, using the CPU scaler\n"); - return 0; - } - return !ps5_test_cpu_present(); -} - -int ps5_frame(void) -{ - return (int)flips; -} - -static void capture(int buffer) -{ - char name[32]; - - if (use_gpu && gpu_finish()) - return; - snprintf(name, sizeof(name), "shot%05d", (int)flips); - ps5_cache_flush(buffers[buffer], FRAME_BYTES); - ps5_capture_add(name, buffers[buffer], FRAME_BYTES); - plat_log("video: captured frame %d\n", (int)flips); -} - -int plat_video_init(int width, int height) -{ - gpu_config_t config; - int i; - - src_width = width; - src_height = height; - for (i = 0; i < VIEW_WIDTH; i++) - { - column_of[i] = (uint16_t)(i * width / VIEW_WIDTH); - tile_columns[i] = tile_column(VIEW_X + i); - } - for (i = 0; i < OUT_HEIGHT; i++) - row_of[i] = (uint16_t)(i * height / OUT_HEIGHT); - - if (open_video()) - return -1; - - config.video = video; - for (i = 0; i < GPU_TARGETS; i++) - config.targets[i] = buffers[i]; - config.width = OUT_WIDTH; - config.height = OUT_HEIGHT; - config.pitch = OUT_WIDTH; - config.view_x = VIEW_X; - config.view_width = VIEW_WIDTH; - config.src_width = width; - config.src_height = height; - use_gpu = gpu_wanted() && gpu_init(&config) == 0; - plat_log("video: %s presentation\n", use_gpu ? "AGC compute" : "CPU"); - return 0; -} - -void plat_video_shutdown(void) -{ - if (video < 0) - return; - sceVideoOutClose(video); - video = -1; -} - -static void cpu_scale(const uint8_t *pixels, uint32_t *target) -{ - int x, y; - - for (y = 0; y < OUT_HEIGHT; y++) - { - const uint8_t *src = pixels + row_of[y] * src_width; - uint32_t *band = target + tile_row(y, OUT_WIDTH); - unsigned int row = tile_offset(0, y % TILE_HEIGHT); - - for (x = 0; x < VIEW_WIDTH; x++) - band[tile_columns[x] ^ row] = rgba[src[column_of[x]]]; - } - ps5_cache_flush(target, FRAME_BYTES); -} - -static void cpu_present(const uint8_t *pixels) -{ - while (sceVideoOutIsFlipPending(video) > 0) - sceKernelUsleep(500); - cpu_scale(pixels, buffers[next_buffer]); - sceVideoOutSubmitFlip(video, next_buffer, SCE_VIDEO_FLIP_VSYNC, flips); -} - -void plat_video_present(const uint8_t *pixels, const uint32_t *palette) -{ - int i; - - if (video < 0) - return; - for (i = 0; i < 256; i++) - { - uint32_t c = palette[i]; - - rgba[i] = (c & 0xff00ff00u) | (c >> 16 & 0xff) | (c & 0xff) << 16; - } - - flips++; - if (use_gpu && gpu_present(pixels, rgba, next_buffer, flips)) - { - plat_log("video: GPU presentation failed, switching to the CPU scaler\n"); - use_gpu = 0; - } - if (!use_gpu) - cpu_present(pixels); - if (test_plan_captures_frame((int)flips)) - capture(next_buffer); - next_buffer = (next_buffer + 1) % GPU_TARGETS; - if (test_plan_finished((int)flips)) - { - ps5_capture_drain(CAPTURE_DRAIN_US); - plat_exit(0); - } - - if (!splash_hidden) - { - sceSystemServiceHideSplashScreen(); - splash_hidden = 1; - } -} +#include +#include + +#include "gpu.h" +#include "platform.h" +#include "ps5.h" +#include "sce.h" +#include "test_plan.h" +#include "tiling.h" + +#define OUT_WIDTH 1920 +#define OUT_HEIGHT 1080 +#define VIEW_WIDTH 1440 +#define VIEW_X ((OUT_WIDTH - VIEW_WIDTH) / 2) +#define BUFFER_BYTES 0x1000000 +#define FRAME_PIXELS tiled_pixels(OUT_WIDTH, OUT_HEIGHT) +#define FRAME_BYTES ((size_t)FRAME_PIXELS * 4) +#define FORCE_CPU_BUTTONS (PAD_L1 | PAD_R1) +#define CAPTURE_DRAIN_US 20000000 + +static int video = -1; +static uint32_t *buffers[GPU_TARGETS]; +static int next_buffer; +static int64_t flips; +static int use_gpu; +static int splash_hidden; + +static int src_width, src_height; +static uint16_t column_of[VIEW_WIDTH]; +static uint16_t row_of[OUT_HEIGHT]; +static uint32_t tile_columns[VIEW_WIDTH]; +static uint32_t rgba[256]; + +static int open_video(void) +{ + SceVideoOutBuffer out[GPU_TARGETS]; + SceVideoOutAttribute attribute = {0}; + uint8_t *memory; + int i, result; + + video = sceVideoOutOpen(SCE_USER_SYSTEM, SCE_VIDEO_BUS_MAIN, 0, NULL); + if (video < 0) + { + plat_log("video: open %#x\n", video); + return -1; + } + memory = ps5_gpu_memory(BUFFER_BYTES * GPU_TARGETS); + if (!memory) + return -1; + + memset(out, 0, sizeof(out)); + for (i = 0; i < GPU_TARGETS; i++) + { + uint32_t *p; + + buffers[i] = (uint32_t *)(memory + i * BUFFER_BYTES); + for (p = buffers[i]; p < buffers[i] + FRAME_PIXELS; p++) + *p = 0xff000000u; + ps5_cache_flush(buffers[i], FRAME_BYTES); + out[i].data = buffers[i]; + } + + sceVideoOutSetFlipRate(video, 0); + sceVideoOutSetBufferAttribute2(&attribute, SCE_VIDEO_FORMAT_RGBA8_SRGB, SCE_VIDEO_TILING_TILED, + OUT_WIDTH, OUT_HEIGHT, 0, 0, 0); + result = sceVideoOutRegisterBuffers2(video, 0, 0, out, GPU_TARGETS, &attribute, 0, NULL); + if (result < 0) + { + plat_log("video: register buffers %#x\n", result); + return -1; + } + return 0; +} + +static int gpu_wanted(void) +{ + pad_state_t pad; + + if (plat_pad_read(&pad) == 0 && (pad.buttons & FORCE_CPU_BUTTONS) == FORCE_CPU_BUTTONS) + { + plat_log("video: L1+R1 held, using the CPU scaler\n"); + return 0; + } + return !ps5_test_cpu_present(); +} + +int ps5_frame(void) +{ + return (int)flips; +} + +static void capture(int buffer) +{ + char name[32]; + + if (use_gpu && gpu_finish()) + return; + snprintf(name, sizeof(name), "shot%05d", (int)flips); + ps5_cache_flush(buffers[buffer], FRAME_BYTES); + ps5_capture_add(name, buffers[buffer], FRAME_BYTES); + plat_log("video: captured frame %d\n", (int)flips); +} + +int plat_video_init(int width, int height) +{ + gpu_config_t config; + int i; + + src_width = width; + src_height = height; + for (i = 0; i < VIEW_WIDTH; i++) + { + column_of[i] = (uint16_t)(i * width / VIEW_WIDTH); + tile_columns[i] = tile_column(VIEW_X + i); + } + for (i = 0; i < OUT_HEIGHT; i++) + row_of[i] = (uint16_t)(i * height / OUT_HEIGHT); + + if (open_video()) + return -1; + + config.video = video; + for (i = 0; i < GPU_TARGETS; i++) + config.targets[i] = buffers[i]; + config.width = OUT_WIDTH; + config.height = OUT_HEIGHT; + config.pitch = OUT_WIDTH; + config.view_x = VIEW_X; + config.view_width = VIEW_WIDTH; + config.src_width = width; + config.src_height = height; + use_gpu = gpu_wanted() && gpu_init(&config) == 0; + plat_log("video: %s presentation\n", use_gpu ? "AGC compute" : "CPU"); + return 0; +} + +void plat_video_shutdown(void) +{ + if (video < 0) + return; + sceVideoOutClose(video); + video = -1; +} + +static void cpu_scale(const uint8_t *pixels, uint32_t *target) +{ + int x, y; + + for (y = 0; y < OUT_HEIGHT; y++) + { + const uint8_t *src = pixels + row_of[y] * src_width; + uint32_t *band = target + tile_row(y, OUT_WIDTH); + unsigned int row = tile_offset(0, y % TILE_HEIGHT); + + for (x = 0; x < VIEW_WIDTH; x++) + band[tile_columns[x] ^ row] = rgba[src[column_of[x]]]; + } + ps5_cache_flush(target, FRAME_BYTES); +} + +static void cpu_present(const uint8_t *pixels) +{ + while (sceVideoOutIsFlipPending(video) > 0) + sceKernelUsleep(500); + cpu_scale(pixels, buffers[next_buffer]); + sceVideoOutSubmitFlip(video, next_buffer, SCE_VIDEO_FLIP_VSYNC, flips); +} + +void plat_video_present(const uint8_t *pixels, const uint32_t *palette) +{ + int i; + + if (video < 0) + return; + for (i = 0; i < 256; i++) + { + uint32_t c = palette[i]; + + rgba[i] = (c & 0xff00ff00u) | (c >> 16 & 0xff) | (c & 0xff) << 16; + } + + flips++; + if (use_gpu && gpu_present(pixels, rgba, next_buffer, flips)) + { + plat_log("video: GPU presentation failed, switching to the CPU scaler\n"); + use_gpu = 0; + } + if (!use_gpu) + cpu_present(pixels); + if (test_plan_captures_frame((int)flips)) + capture(next_buffer); + next_buffer = (next_buffer + 1) % GPU_TARGETS; + if (test_plan_finished((int)flips)) + { + ps5_capture_drain(CAPTURE_DRAIN_US); + plat_exit(0); + } + + if (!splash_hidden) + { + sceSystemServiceHideSplashScreen(); + splash_hidden = 1; + } +} diff --git a/test/present_preview.c b/test/present_preview.c index 616822c..eb4ee3d 100644 --- a/test/present_preview.c +++ b/test/present_preview.c @@ -1,58 +1,58 @@ -#include -#include -#include - -#include "png.h" - -static unsigned group_x, item_x; - -#define __kernel -#define __global -#define __attribute__(x) -#define __builtin_amdgcn_workgroup_id_x() group_x -#define __builtin_amdgcn_workitem_id_x() item_x -#include "present.cl" - -#define SRC_W 320 -#define SRC_H 200 -#define OUT_W 1920 -#define OUT_H 1080 -#define VIEW_W 1440 - -int main(int argc, char **argv) -{ - static uint8_t pixels[SRC_W * SRC_H]; - static uint32_t palette[256], rgba[256]; - uint32_t *out = calloc(tiled_pixels(OUT_W, OUT_H), 4); - uint8_t *rgb = malloc(OUT_W * OUT_H * 3); - FILE *f; - int i; - - if (argc != 3 || !(f = fopen(argv[1], "rb"))) - { - fprintf(stderr, "usage: present_preview \n"); - return 2; - } - if (fread(pixels, 1, sizeof(pixels), f) != sizeof(pixels) || - fread(palette, 4, 256, f) != 256) - return 1; - fclose(f); - - for (i = 0; i < 256; i++) - rgba[i] = (palette[i] & 0xff00ff00u) | (palette[i] >> 16 & 0xff) | (palette[i] & 0xff) << 16; - - for (group_x = 0; group_x < OUT_H; group_x++) - for (item_x = 0; item_x < GROUP; item_x++) - present(pixels, rgba, out, OUT_W, OUT_W, OUT_H, (OUT_W - VIEW_W) / 2, VIEW_W, - SRC_W, SRC_H); - - for (i = 0; i < OUT_W * OUT_H; i++) - { - uint32_t c = out[tiled_index(i % OUT_W, i / OUT_W, OUT_W)]; - - rgb[i * 3] = c & 0xff; - rgb[i * 3 + 1] = c >> 8 & 0xff; - rgb[i * 3 + 2] = c >> 16 & 0xff; - } - return png_write_rgb(argv[2], rgb, OUT_W, OUT_H); -} +#include +#include +#include + +#include "png.h" + +static unsigned group_x, item_x; + +#define __kernel +#define __global +#define __attribute__(x) +#define __builtin_amdgcn_workgroup_id_x() group_x +#define __builtin_amdgcn_workitem_id_x() item_x +#include "present.cl" + +#define SRC_W 320 +#define SRC_H 200 +#define OUT_W 1920 +#define OUT_H 1080 +#define VIEW_W 1440 + +int main(int argc, char **argv) +{ + static uint8_t pixels[SRC_W * SRC_H]; + static uint32_t palette[256], rgba[256]; + uint32_t *out = calloc(tiled_pixels(OUT_W, OUT_H), 4); + uint8_t *rgb = malloc(OUT_W * OUT_H * 3); + FILE *f; + int i; + + if (argc != 3 || !(f = fopen(argv[1], "rb"))) + { + fprintf(stderr, "usage: present_preview \n"); + return 2; + } + if (fread(pixels, 1, sizeof(pixels), f) != sizeof(pixels) || + fread(palette, 4, 256, f) != 256) + return 1; + fclose(f); + + for (i = 0; i < 256; i++) + rgba[i] = (palette[i] & 0xff00ff00u) | (palette[i] >> 16 & 0xff) | (palette[i] & 0xff) << 16; + + for (group_x = 0; group_x < OUT_H; group_x++) + for (item_x = 0; item_x < GROUP; item_x++) + present(pixels, rgba, out, OUT_W, OUT_W, OUT_H, (OUT_W - VIEW_W) / 2, VIEW_W, + SRC_W, SRC_H); + + for (i = 0; i < OUT_W * OUT_H; i++) + { + uint32_t c = out[tiled_index(i % OUT_W, i / OUT_W, OUT_W)]; + + rgb[i * 3] = c & 0xff; + rgb[i * 3 + 1] = c >> 8 & 0xff; + rgb[i * 3 + 2] = c >> 16 & 0xff; + } + return png_write_rgb(argv[2], rgb, OUT_W, OUT_H); +} diff --git a/tools/console_test.py b/tools/console_test.py index 9cec4fc..6a98f76 100644 --- a/tools/console_test.py +++ b/tools/console_test.py @@ -1,160 +1,160 @@ -"""Runs the deployed title on the PS5 with a test plan and collects what happened. - - uv run --no-project --with pillow python tools/console_test.py test/console-play.cfg [--timeout S] - -Writes the plan to the title folder as test.cfg (plus a capture port), launches PPSA99666 with -the doomlaunch payload (tools/launcher, loaded through the Payload Manager), pulls the frames the -plan captures from the running title over TCP, converts them to PNG under build/test/run/, -prints the title's log lines from the kernel log (klogsrv capture in build/test/klog.txt) and -removes test.cfg again. The PS5Upload helper must be running. -""" -import argparse -import base64 -import socket -import struct -import sys -import time -import urllib.request -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -sys.path.insert(0, str(ROOT.parent / "tools")) -import ps5 # noqa: E402 -import push_ffpkg as push # noqa: E402 - -TITLE = "PPSA99666" -PLDMGR = f"http://{ps5.PS5_IP}:8084" -LAUNCHER = ROOT / "build" / "launcher" / "doomlaunch.elf" -LAUNCHER_DIR = "/data/pldmgr/payloads/doomlaunch" -TEST_PLAN = f"/data/homebrew/{TITLE}/test.cfg" -CAPTURE_PORT = 9119 -KLOG = ROOT / "build" / "test" / "klog.txt" -OUT = ROOT / "build" / "test" / "run" -FRAME = (1920, 1080) -NOISE = ("PFAuth", "FMEM", "PSM.UI", "catalog", "SHELLFLAG", "LOGIN MGR", "CloudMessaging", - "ResArbitrator", "PlayGo", "AsyncStorage", "a53mm", "BAPM", "<118>", "[KERNEL]", "###", - "num clients", "Amm config", "wt_application", "appmgr_create") - - -def get(url, timeout=10): - with urllib.request.urlopen(url, timeout=timeout) as r: - return r.read().decode(errors="replace") - - -def engine(): - push.LOG = ROOT / "build" / "deploy.log" - push.start_engine() - - -def write_remote(path, data): - r = push.http("POST", "/api/ps5/fs/write-bytes", - {"addr": push.XFER, "path": path, "bytes_b64": base64.b64encode(data).decode()}) - if "http_error" in r: - sys.exit(f"writing {path} failed: {r}") - - -def install_launcher(): - data = LAUNCHER.read_bytes() - try: - if any(e["name"] == LAUNCHER.name and e["size"] == len(data) - for e in ps5.list_dir(LAUNCHER_DIR)["entries"]): - return - except Exception: - pass - push.http("POST", "/api/ps5/fs/mkdir", {"addr": push.XFER, "path": LAUNCHER_DIR}) - write_remote(f"{LAUNCHER_DIR}/{LAUNCHER.name}", data) - - -def receive_exactly(sock, size): - data = bytearray() - while len(data) < size: - chunk = sock.recv(min(1 << 20, size - len(data))) - if not chunk: - raise ConnectionError("capture stream ended early") - data += chunk - return bytes(data) - - -def tile_offset(x, y): - bit = lambda v, n: (v >> n) & 1 - return (bit(x, 0) | bit(x, 1) << 1 | bit(y, 0) << 2 | bit(y, 1) << 3 | bit(y, 2) << 4 | - bit(x, 2) << 5 | (bit(x, 3) ^ bit(y, 3)) << 6 | (bit(x, 4) ^ bit(y, 4)) << 7 | - (bit(x, 6) ^ bit(y, 5)) << 8 | (bit(x, 5) ^ bit(y, 6)) << 9 | bit(y, 3) << 10 | - bit(x, 4) << 11 | bit(y, 6) << 12 | bit(x, 6) << 13 | bit(x, 7) << 14 | bit(x, 8) << 15) - - -def detile(data): - import numpy as np - - width, height = FRAME - x = np.arange(width, dtype=np.int64) - y = np.arange(height, dtype=np.int64) - columns = (x // 512) * 65536 + tile_offset(x % 512, 0) - rows = (y // 128) * 128 * width - index = rows[:, None] + (columns[None, :] ^ tile_offset(0, y % 128)[:, None]) - pixels = np.frombuffer(data, dtype=np.uint32)[index] - return pixels.astype("", "[KERNEL]", "###", + "num clients", "Amm config", "wt_application", "appmgr_create") + + +def get(url, timeout=10): + with urllib.request.urlopen(url, timeout=timeout) as r: + return r.read().decode(errors="replace") + + +def engine(): + push.LOG = ROOT / "build" / "deploy.log" + push.start_engine() + + +def write_remote(path, data): + r = push.http("POST", "/api/ps5/fs/write-bytes", + {"addr": push.XFER, "path": path, "bytes_b64": base64.b64encode(data).decode()}) + if "http_error" in r: + sys.exit(f"writing {path} failed: {r}") + + +def install_launcher(): + data = LAUNCHER.read_bytes() + try: + if any(e["name"] == LAUNCHER.name and e["size"] == len(data) + for e in ps5.list_dir(LAUNCHER_DIR)["entries"]): + return + except Exception: + pass + push.http("POST", "/api/ps5/fs/mkdir", {"addr": push.XFER, "path": LAUNCHER_DIR}) + write_remote(f"{LAUNCHER_DIR}/{LAUNCHER.name}", data) + + +def receive_exactly(sock, size): + data = bytearray() + while len(data) < size: + chunk = sock.recv(min(1 << 20, size - len(data))) + if not chunk: + raise ConnectionError("capture stream ended early") + data += chunk + return bytes(data) + + +def tile_offset(x, y): + bit = lambda v, n: (v >> n) & 1 + return (bit(x, 0) | bit(x, 1) << 1 | bit(y, 0) << 2 | bit(y, 1) << 3 | bit(y, 2) << 4 | + bit(x, 2) << 5 | (bit(x, 3) ^ bit(y, 3)) << 6 | (bit(x, 4) ^ bit(y, 4)) << 7 | + (bit(x, 6) ^ bit(y, 5)) << 8 | (bit(x, 5) ^ bit(y, 6)) << 9 | bit(y, 3) << 10 | + bit(x, 4) << 11 | bit(y, 6) << 12 | bit(x, 6) << 13 | bit(x, 7) << 14 | bit(x, 8) << 15) + + +def detile(data): + import numpy as np + + width, height = FRAME + x = np.arange(width, dtype=np.int64) + y = np.arange(height, dtype=np.int64) + columns = (x // 512) * 65536 + tile_offset(x % 512, 0) + rows = (y // 128) * 128 * width + index = rows[:, None] + (columns[None, :] ^ tile_offset(0, y % 128)[:, None]) + pixels = np.frombuffer(data, dtype=np.uint32)[index] + return pixels.astype("