Normalize line endings to LF

Twenty files had been committed with CRLF endings. .gitattributes now keeps text files LF.
This commit is contained in:
rijads committed 2026-10-03 17:35:54 +02:00
1 parent 61c14ce8a2
commit 9a0c226c42
21 files changed
+3012 -3010

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
* text=auto eol=lf
*.png binary
+140 -140
View File
@@ -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 <plan>` 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 <steps>`, `capture <frames>`, `frames <N>` (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 <plan>` 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 <steps>`, `capture <frames>`, `frames <N>` (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).
+116 -116
View File
@@ -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
+235 -235
View File
@@ -1,235 +1,235 @@
#include <math.h>
#include <pthread.h>
#include <string.h>
#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 <math.h>
#include <pthread.h>
#include <string.h>
#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);
}
+511 -511
View File
File diff suppressed because it is too large. Load diff
+408 -408
View File
@@ -1,408 +1,408 @@
#include <math.h>
#include <stdlib.h>
#include <string.h>
#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 <math.h>
#include <stdlib.h>
#include <string.h>
#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;
}
}
+56 -56
View File
@@ -1,56 +1,56 @@
#ifndef PLATFORM_H
#define PLATFORM_H
#include <stdint.h>
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 <stdint.h>
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
+146 -146
View File
@@ -1,146 +1,146 @@
#include <stdlib.h>
#include <string.h>
#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 <stdlib.h>
#include <string.h>
#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;
}
+14 -14
View File
@@ -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
+169 -169
View File
@@ -1,169 +1,169 @@
#include <stdio.h>
#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 <stdio.h>
#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();
}
+63 -63
View File
@@ -1,63 +1,63 @@
#include <pthread.h>
#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 <pthread.h>
#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;
}
+146 -146
View File
@@ -1,146 +1,146 @@
#include <netinet/in.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
#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 <netinet/in.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
#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;
}
}
+21 -21
View File
@@ -1,21 +1,21 @@
#ifndef GPU_H
#define GPU_H
#include <stdint.h>
#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 <stdint.h>
#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
+87 -87
View File
@@ -1,87 +1,87 @@
#include <string.h>
#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 <string.h>
#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);
}
+64 -64
View File
@@ -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;
}
}
+18 -18
View File
@@ -1,18 +1,18 @@
#ifndef PS5_H
#define PS5_H
#include <stddef.h>
#include <stdint.h>
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 <stddef.h>
#include <stdint.h>
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
+192 -192
View File
@@ -1,192 +1,192 @@
#ifndef SCE_H
#define SCE_H
#include <stddef.h>
#include <stdint.h>
#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 <stddef.h>
#include <stdint.h>
#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
+202 -202
View File
@@ -1,202 +1,202 @@
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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 <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}
+204 -204
View File
@@ -1,204 +1,204 @@
#include <stdio.h>
#include <string.h>
#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 <stdio.h>
#include <string.h>
#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;
}
}
+58 -58
View File
@@ -1,58 +1,58 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#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 <frame.raw> <out.png>\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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#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 <frame.raw> <out.png>\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);
}
+160 -160
View File
@@ -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("<u4").tobytes()
def pull_captures(seen):
from PIL import Image
try:
sock = socket.create_connection((ps5.PS5_IP, CAPTURE_PORT), timeout=2)
except OSError:
return
sock.settimeout(30)
try:
while True:
(name_length,) = struct.unpack("<I", receive_exactly(sock, 4))
if not name_length:
break
name = receive_exactly(sock, name_length).decode()
(size,) = struct.unpack("<Q", receive_exactly(sock, 8))
data = receive_exactly(sock, size)
Image.frombuffer("RGBA", FRAME, detile(data), "raw", "RGBA", 0, 1).convert("RGB").save(OUT / f"{name}.png")
seen.add(name)
print(f"captured {name}", flush=True)
except (OSError, ConnectionError) as e:
print(f"capture pull interrupted: {e}", flush=True)
finally:
sock.close()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("plan")
ap.add_argument("--timeout", type=int, default=120)
args = ap.parse_args()
OUT.mkdir(parents=True, exist_ok=True)
for old in OUT.glob("*"):
old.unlink()
engine()
install_launcher()
plan = Path(args.plan).read_text().rstrip("\n") + f"\nserve {CAPTURE_PORT}\n"
write_remote(TEST_PLAN, plan.encode())
start = KLOG.stat().st_size
print("launch:", get(f"{PLDMGR}/loadpayload:{LAUNCHER.name}", timeout=30).strip()[:200], flush=True)
seen = set()
deadline = time.time() + args.timeout
exited = False
try:
while time.time() < deadline and not exited:
pull_captures(seen)
text = KLOG.read_bytes()[start:].decode(errors="replace")
exited = "ps5: exit" in text or "Terminating pid" in text
time.sleep(1)
finally:
push.http("POST", "/api/ps5/fs/delete", {"addr": push.XFER, "path": TEST_PLAN})
text = KLOG.read_bytes()[start:].decode(errors="replace")
lines = [l for l in text.splitlines() if l.strip() and not any(n in l for n in NOISE)]
(OUT / "log.txt").write_text("\n".join(lines), encoding="utf-8")
print("\n".join(lines[-80:]))
print(f"\nexited={exited} captures={sorted(seen)}")
if __name__ == "__main__":
main()
"""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("<u4").tobytes()
def pull_captures(seen):
from PIL import Image
try:
sock = socket.create_connection((ps5.PS5_IP, CAPTURE_PORT), timeout=2)
except OSError:
return
sock.settimeout(30)
try:
while True:
(name_length,) = struct.unpack("<I", receive_exactly(sock, 4))
if not name_length:
break
name = receive_exactly(sock, name_length).decode()
(size,) = struct.unpack("<Q", receive_exactly(sock, 8))
data = receive_exactly(sock, size)
Image.frombuffer("RGBA", FRAME, detile(data), "raw", "RGBA", 0, 1).convert("RGB").save(OUT / f"{name}.png")
seen.add(name)
print(f"captured {name}", flush=True)
except (OSError, ConnectionError) as e:
print(f"capture pull interrupted: {e}", flush=True)
finally:
sock.close()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("plan")
ap.add_argument("--timeout", type=int, default=120)
args = ap.parse_args()
OUT.mkdir(parents=True, exist_ok=True)
for old in OUT.glob("*"):
old.unlink()
engine()
install_launcher()
plan = Path(args.plan).read_text().rstrip("\n") + f"\nserve {CAPTURE_PORT}\n"
write_remote(TEST_PLAN, plan.encode())
start = KLOG.stat().st_size
print("launch:", get(f"{PLDMGR}/loadpayload:{LAUNCHER.name}", timeout=30).strip()[:200], flush=True)
seen = set()
deadline = time.time() + args.timeout
exited = False
try:
while time.time() < deadline and not exited:
pull_captures(seen)
text = KLOG.read_bytes()[start:].decode(errors="replace")
exited = "ps5: exit" in text or "Terminating pid" in text
time.sleep(1)
finally:
push.http("POST", "/api/ps5/fs/delete", {"addr": push.XFER, "path": TEST_PLAN})
text = KLOG.read_bytes()[start:].decode(errors="replace")
lines = [l for l in text.splitlines() if l.strip() and not any(n in l for n in NOISE)]
(OUT / "log.txt").write_text("\n".join(lines), encoding="utf-8")
print("\n".join(lines[-80:]))
print(f"\nexited={exited} captures={sorted(seen)}")
if __name__ == "__main__":
main()