mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
Compare commits
No files matched your search
@@ -23,10 +23,23 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: last-good
|
||||
attributes:
|
||||
label: Last version that worked
|
||||
description: If an earlier version did not crash at this point, which one.
|
||||
- type: textarea
|
||||
id: system
|
||||
attributes:
|
||||
label: GPU, driver and Windows version
|
||||
description: Copy system info in the menu's Advanced tab puts them on the clipboard.
|
||||
label: System info
|
||||
description: Paste what Copy system info (F1, Advanced tab) puts on the clipboard. On a Steam Deck, add LCD or OLED.
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: mods
|
||||
attributes:
|
||||
label: Mods and cheats
|
||||
description: The folders in mods/ with their download links, or "none". Say whether it also crashes without them.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -56,10 +69,32 @@ body:
|
||||
label: What you did before the crash
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### The log
|
||||
|
||||
- The log of the run that crashed is `logs\totk.log` in the folder of `totk.exe` (F1, Advanced tab, Open log folder). Copy it before you start the game again: the next start renames it to `totk.1.log`, and after five starts it is gone.
|
||||
- Attach the whole file. Do not cut or edit it: the crash report is at the end, the GPU and driver lines are at the start.
|
||||
- The log says `device lost` or `VK_ERROR_DEVICE_LOST` and the GPU is an NVIDIA one: set GPU pass marks to On (F1, Advanced tab, Diagnostics), restart the game, make it crash again and attach that log. The `vk: pass marks` lines then name what the GPU was drawing.
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log
|
||||
description: Attach logs\totk.log from the folder of totk.exe. Copy it before you start the game again, because the next start overwrites it.
|
||||
description: Drag totk.log into this field.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Save and input recording
|
||||
|
||||
When the crash comes back at one place in the game (an area, a shrine, a cutscene, a quest step), the save that gets there lets us crash it on our machine.
|
||||
|
||||
1. Zip the `profile` folder next to `totk.exe` and attach it. Say which slot you load, or that you use the autosave.
|
||||
2. If it takes more than walking a few steps from the save: turn on Input recording (F1, Advanced tab, Diagnostics), restart the game, load the save, make it crash, and attach `input-record.txt` from the logs folder together with the `totk.log` of that run.
|
||||
- type: textarea
|
||||
id: save
|
||||
attributes:
|
||||
label: Save and input recording
|
||||
description: Drag the zipped profile folder and input-record.txt into this field, when the crash comes back at one place.
|
||||
@@ -23,24 +23,37 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: last-good
|
||||
attributes:
|
||||
label: Last version that worked
|
||||
description: If an earlier version drew this correctly, which one.
|
||||
- type: textarea
|
||||
id: system
|
||||
attributes:
|
||||
label: GPU, driver and Windows version
|
||||
description: Copy system info in the menu's Advanced tab puts them on the clipboard.
|
||||
label: System info
|
||||
description: Paste what Copy system info (F1, Advanced tab) puts on the clipboard. It lists the settings you changed too. On a Steam Deck, add LCD or OLED.
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: mods
|
||||
attributes:
|
||||
label: Mods and cheats
|
||||
description: The folders in mods/ with their download links, or "none". Say whether the glitch also shows without them. Turn off ReShade and other overlays that draw into the game before you report.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshot
|
||||
attributes:
|
||||
label: Screenshot or video
|
||||
description: Drag the file into this field. Say where in the game it was taken.
|
||||
description: Drag the file into this field. Say where in the game it was taken. Flicker needs a video.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: What looks wrong and when
|
||||
description: Does it show all the time, in one place, or after some time in play? Which settings did you change from the defaults?
|
||||
description: Does it show all the time, in one place, or after some time in play? Does it go away after a restart, or when you change a setting?
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -56,4 +69,6 @@ body:
|
||||
id: log
|
||||
attributes:
|
||||
label: Log
|
||||
description: Attach logs\totk.log from the folder of totk.exe.
|
||||
description: Drag logs\totk.log from the folder of totk.exe into this field, from the run that showed the glitch. Copy it before you start the game again.
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,65 +0,0 @@
|
||||
name: Other bug
|
||||
description: Sound, controls, performance, the settings menu, the installer, or game logic.
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Check the pinned issues at the top of the issue list first.
|
||||
|
||||
Do not attach or link game files, keys, firmware or builds. Issues that do are closed.
|
||||
- type: checkboxes
|
||||
id: duplicates
|
||||
attributes:
|
||||
label: Existing issues
|
||||
options:
|
||||
- label: I read the pinned issues and [searched open and closed issues](https://github.com/valentderah/totk-pc-recomp/issues?q=is%3Aissue) for this bug.
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
options:
|
||||
- Audio
|
||||
- Controls and input
|
||||
- Performance and stutter
|
||||
- Settings menu and overlay
|
||||
- Installer
|
||||
- Game logic and physics
|
||||
- Something else
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: The title line of the settings menu (F1), for example "TotK Recomp 0.1.0, game 1.4.3".
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: system
|
||||
attributes:
|
||||
label: GPU, driver and Windows version
|
||||
description: Copy system info in the menu's Advanced tab puts them on the clipboard.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: blocker
|
||||
attributes:
|
||||
label: Can you keep playing?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No, it stops the game or a quest"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: What happened and how to reproduce it
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log
|
||||
description: Attach logs\totk.log from the folder of totk.exe.
|
||||
@@ -0,0 +1,108 @@
|
||||
name: Performance
|
||||
description: Low frame rate, freezes, hitches, stutter or long loading.
|
||||
labels: [performance]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Check the pinned issues at the top of the issue list first.
|
||||
|
||||
Shaders the setup did not prepare are built the first time they are needed, so a short hitch the first time an area or effect shows is expected. Report it when it happens again in the same place.
|
||||
|
||||
Do not attach or link game files, keys, firmware or builds. Issues that do are closed.
|
||||
- type: checkboxes
|
||||
id: duplicates
|
||||
attributes:
|
||||
label: Existing issues
|
||||
options:
|
||||
- label: I read the pinned issues and [searched open and closed issues](https://github.com/valentderah/totk-pc-recomp/issues?q=is%3Aissue) for this problem.
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: The title line of the settings menu (F1), for example "TotK Recomp 0.1.0, game 1.4.3".
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: last-good
|
||||
attributes:
|
||||
label: Last version that ran better
|
||||
description: If an earlier version was faster in the same place, which one.
|
||||
- type: textarea
|
||||
id: system
|
||||
attributes:
|
||||
label: System info
|
||||
description: Paste what Copy system info (F1, Advanced tab) puts on the clipboard. It lists the settings you changed too. On a Steam Deck, add LCD or OLED.
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: machine
|
||||
attributes:
|
||||
label: Machine
|
||||
options:
|
||||
- Desktop PC
|
||||
- Laptop, on mains power
|
||||
- Laptop, on battery
|
||||
- Steam Deck
|
||||
- Other handheld PC
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: mods
|
||||
attributes:
|
||||
label: Mods and cheats
|
||||
description: The folders in mods/ with their download links, or "none".
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: kind
|
||||
attributes:
|
||||
label: What kind of problem?
|
||||
options:
|
||||
- The frame rate is low all the time
|
||||
- Short freezes or hitches that come back in the same place
|
||||
- The frame rate is fine, but motion is not smooth
|
||||
- It gets slower the longer I play
|
||||
- Loading takes long
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: where
|
||||
attributes:
|
||||
label: Where and what you were doing
|
||||
description: For example "Lookout Landing, walking to the tower", "the Depths below Hyrule Castle", "fighting a Talus in the Sky". How long into the session it started.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: overlay
|
||||
attributes:
|
||||
label: Performance overlay
|
||||
description: Press F10 twice (or both stick clicks) for the full panel. Stay a minute in the place and write down the frame rate, the mean and the longest frame time, or attach a screenshot of the panel.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Recording the log
|
||||
|
||||
1. In the settings menu (F1), Advanced tab, Diagnostics: turn on Performance log. For freezes and hitches, also turn on Flight recorder. Restart the game: both take effect at the next start.
|
||||
2. Play at least three minutes in the place where it happens. The log gets a frame-time summary every 60 s, and a line for every frame over 50 ms that names what the frame waited for.
|
||||
3. With the flight recorder on, press F8 right after a hitch. It also saves on its own after every frame longer than the freeze threshold (100 ms), at most once per 10 s.
|
||||
4. Close the game with Quit game in the menu or the window's close button. Do not end it from Task Manager: the last summary is written when the game exits.
|
||||
5. Copy `logs\totk.log` from the folder of `totk.exe` before you start the game again.
|
||||
6. Each flight recorder save is a folder; the log line `flight: dump` names it. Zip the folders and attach them.
|
||||
7. Turn both off again afterwards: they cost a little frame time.
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log
|
||||
description: Drag totk.log into this field.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: flight
|
||||
attributes:
|
||||
label: Flight recorder saves
|
||||
description: Drag the zipped folders into this field, for freezes and hitches.
|
||||
@@ -0,0 +1,94 @@
|
||||
name: Other bug
|
||||
description: Sound, controls, the settings menu, the installer, or game logic.
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Check the pinned issues at the top of the issue list first. Crashes, graphics glitches and performance have their own templates.
|
||||
|
||||
Do not attach or link game files, keys, firmware or builds. Issues that do are closed.
|
||||
- type: checkboxes
|
||||
id: duplicates
|
||||
attributes:
|
||||
label: Existing issues
|
||||
options:
|
||||
- label: I read the pinned issues and [searched open and closed issues](https://github.com/valentderah/totk-pc-recomp/issues?q=is%3Aissue) for this bug.
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
options:
|
||||
- Audio
|
||||
- Controls and input
|
||||
- Settings menu and overlay
|
||||
- Installer
|
||||
- Game logic and physics
|
||||
- Something else
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: The title line of the settings menu (F1), for example "TotK Recomp 0.1.0, game 1.4.3". For the installer, the version of TotK-Setup.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: last-good
|
||||
attributes:
|
||||
label: Last version that worked
|
||||
description: If an earlier version did this correctly, which one.
|
||||
- type: textarea
|
||||
id: system
|
||||
attributes:
|
||||
label: System info
|
||||
description: Paste what Copy system info (F1, Advanced tab) puts on the clipboard. On a Steam Deck, add LCD or OLED. For the installer, write your OS and the install disk.
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: mods
|
||||
attributes:
|
||||
label: Mods and cheats
|
||||
description: The folders in mods/ with their download links, or "none". Say whether it also happens without them.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: blocker
|
||||
attributes:
|
||||
label: Can you keep playing?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No, it stops the game or a quest"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: What happened and how to reproduce it
|
||||
description: What you expected, what happened instead, and the steps that lead there.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### What to attach for your area
|
||||
|
||||
Close the game with Quit game in the menu or the window's close button, then copy `logs\totk.log` from the folder of `totk.exe` before you start the game again.
|
||||
|
||||
**Audio.** Open `totk.ini` in the folder of `totk.exe` and add the line `trace = audio`; when the file already has `trace = perf` (Performance log on), change it to `trace = perf,audio`. Start the game, play at least two minutes where the sound goes wrong and close the game. The log then has an `audio:` line every 5 s with the buffer underruns. Name the output device and how it is connected (speakers, headphones, USB, Bluetooth, HDMI), and attach a short video with sound if you can. Remove the line afterwards.
|
||||
|
||||
**Controls and input.** Name the controller and how it is connected (USB, Bluetooth, Steam Input). Does it also happen with keyboard and mouse?
|
||||
|
||||
**Controls, game logic, anything you can trigger again.** Turn on Input recording (F1, Advanced tab, Diagnostics), restart the game, load a save, make the bug happen and close the game. Attach `input-record.txt` from the logs folder and a zip of the save you loaded: the `profile` folder next to `totk.exe`. The recording replays what you did, so we can see the bug on our machine.
|
||||
|
||||
**Installer.** Attach the newest `install-*.log` from `temp\logs` in the folder of `TotK-Setup.exe` (on Linux: `.setup-temp/logs` in the install folder), and the log file the error message names.
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log and other files
|
||||
description: Drag totk.log and the files your area asks for into this field.
|
||||
validations:
|
||||
required: true
|
||||
File renamed without changes.
@@ -17,14 +17,19 @@ Report problems as issues, following [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
- Native code, not emulation
|
||||
- Game updates 1.1.0, 1.2.1 and 1.4.3; 1.2.1 and 1.4.3 saves carry over between them
|
||||
- Takes NSP and XCI game files, and compressed NSZ and XCZ as they are: the setup unpacks them itself
|
||||
- 60 fps by default; up to 144 fps or the monitor's refresh rate (experimental)
|
||||
- Custom resolutions (1080p, 2k...), supersampling and AMD FSR 1 upscaling
|
||||
- NVIDIA DLSS / DLAA on RTX GPUs: the setup downloads `nvngx_dlss.dll` from NVIDIA's DLSS SDK repository
|
||||
(github.com/NVIDIA/DLSS, NVIDIA RTX SDK license) when it finds an RTX GPU; untick it in the setup to skip it
|
||||
- HDR10 output with peak, game and HUD brightness settings
|
||||
- 16:10 (Steam Deck, handhelds), 21:9 and 32:9 (ultrawide) game resolutions, with the camera and the interface in that shape
|
||||
- 16:10 (Steam Deck, handhelds), 3:2 (Surface, Framework), 21:9 and 32:9 (ultrawide) game resolutions, with the camera and the interface in that shape
|
||||
- Anisotropic texture filtering up to 16x, for sharp ground into the distance
|
||||
- In-game settings menu (F1) and performance overlay (F10)
|
||||
- Discord Rich Presence ("Playing TotK Recomp" with the play time), off by default, own application id supported
|
||||
- Setup and in-game menu in English, Russian, Brazilian Portuguese and Simplified Chinese
|
||||
- Keyboard, mouse and gamepad with remapping, rumble and gyro
|
||||
- Stereo or 5.1 surround sound output
|
||||
- Xbox button prompts: LB, RB, LT, RT, Menu and View, and A, B, X and Y as labelled on the button to press, with either button layout
|
||||
- Shaders prepared at install instead of during play
|
||||
- Mods made for emulators: unpack a mod into the `mods` folder next to `totk.exe`; switch and order them in the menu. RomFS
|
||||
@@ -44,7 +49,9 @@ Requirements: Windows 10 or later, an x86-64 CPU with SSE4.2 (AVX2/FMA machines
|
||||
|
||||
1. Download `TotK-Setup-<version>-windows.zip` from [Releases](../../releases/latest) and unpack it.
|
||||
2. Start `TotK-Setup.exe`. If SmartScreen warns about an unsigned app, choose *More info*, then *Run anyway*.
|
||||
3. Pick your game files, the language and an install folder, and press *Start*. The setup builds the game once.
|
||||
3. Pick your game files, the language and an install folder, and press *Start*. The setup builds the game once. With
|
||||
an NVIDIA RTX GPU it also downloads NVIDIA DLSS, unless you untick *Download NVIDIA DLSS*; turn DLSS on in the
|
||||
menu (F1).
|
||||
4. Play `totk.exe` from the install folder.
|
||||
|
||||
### Linux
|
||||
|
||||
+11
-5
@@ -84,7 +84,7 @@ Every guest thread is a host thread. TotK assumes Horizon's scheduler: three app
|
||||
- NVN front end from MacNativeGames (`nvn_api_*.c`, `nvn_obj.h`, texture layout and deswizzle). The game records command buffers through it; nothing reaches the GPU until the game submits.
|
||||
- Vulkan 1.3 back end in place of MacNativeGames' Metal one. At `QueueSubmitCommands` the recorded NVN commands are replayed into Vulkan command buffers (`vk_exec*.c`).
|
||||
- Memory pools. Apple's unified memory let MacNativeGames use the game's pools zero-copy. The back end imports the pools as host memory (`VK_EXT_external_memory_host`). On a discrete GPU, pools bound as buffers are mirrored in device-local memory and the pages the CPU wrote (write watch, above) are uploaded before a replay; idle pool regions are made read-only so the write-watch queries skip them (`vk_memory.c`). GPU-rendered textures are written back when the CPU reads them: on a pool invalidate, on a CPU copy, and for small targets once the CPU waits for a fence (`vk_texture.c`).
|
||||
- Replay thread. `TOTK_REPLAY_ASYNC=1` moves the replay off the submitting thread onto a queue at most one frame behind the game; reads of GPU results wait for the newest writer instead of draining the queue (`vk_backend.c`). It is off by default because no timing has shown a gain yet.
|
||||
- Replay thread. `TOTK_REPLAY_ASYNC=1` moves the replay off the submitting thread onto a queue at most one frame behind the game; reads of GPU results wait for the newest writer instead of draining the queue (`vk_backend.c`). It is on by default: where the game waits for the GPU it overlaps the replay with the game threads (a GTX 1650 in the Depths, issue #88), elsewhere it is neutral; it adds up to a frame of latency, and `replay_async = 0` turns it off.
|
||||
- Recorder thread. The Vulkan calls the replay makes into its command buffer are made by a thread of their own (`vk_rec.c`, after Savage Wind Recomp and LAS Recomp): each `vkCmd*` copies its arguments into a packet of a ring and the recorder makes the calls in order; calls into other command buffers are made at once; the recorder also ends the command buffer, and the submit waits until it has. In the town on an RX 9070 the replay takes 17% less time on the submitting thread. It is off by default on AMD GPUs, where the driver loses the device or hangs the GPU at startup with it (`TOTK_VK_RECORDER=1` forces it on, `=0` makes every call on the submitting thread).
|
||||
- Uniform snapshot. The game rewrites per-object uniform blocks on other threads soon after it submits the frame
|
||||
that reads them, while the replay still records it. At the start of a submission's replay every uniform range it
|
||||
@@ -99,8 +99,9 @@ Every guest thread is a host thread. TotK assumes Horizon's scheduler: three app
|
||||
RCAS) on the present. An `output_res` that is not 16:9 (more than 1% off, `res_is_16x9`) also switches on the
|
||||
`aspect` patch set, which hands the output's ratio to the camera, and `romfs.c` `overlay_init` loads the
|
||||
`romfs-patches-<w>x<h>` overlay for it when it exists (`tools/make_romfs_patches.py --aspect`, laid out by
|
||||
`tools/ultrawide_layout.py`; the installer builds ten sizes: 21:9 2560x1080, 3440x1440, 3840x1600, 5120x2160, 32:9
|
||||
3840x1080, 5120x1440 and 16:10 1280x800, 1680x1050, 1920x1200, 2560x1600). A wider layout moves the HUD to the
|
||||
`tools/ultrawide_layout.py`; the installer builds fifteen sizes: 21:9 2560x1080, 3440x1440, 3840x1600, 5120x2160,
|
||||
32:9 3840x1080, 5120x1440, 16:10 1280x800, 1680x1050, 1920x1200, 2560x1600 and 3:2 1920x1280, 2160x1440, 2256x1504,
|
||||
2880x1920, 3000x2000). A wider layout moves the HUD to the
|
||||
screen's corners, a narrower one to its top and bottom edges. Other sizes get the camera of their shape and the 16:9
|
||||
HUD stretched over it. The `aspect` setting (`fit`, the default; `stretch`) decides what the present does
|
||||
when the window's shape differs from the output's: `fit` letterboxes, where a 16:9 game in a wider window used to be
|
||||
@@ -117,8 +118,13 @@ Every guest thread is a host thread. TotK assumes Horizon's scheduler: three app
|
||||
- DLAA (`vk_dlaa.c`, `dlaa`, off by default). NVIDIA DLSS at the scene's own size, through the driver's NGX core loaded
|
||||
at run time (no SDK code compiled in). It runs on the R11G11B10F light target right before the bloom chain's first
|
||||
pass, so post effects and the HUD read the anti-aliased picture. The scene's draws get a sub-pixel Halton viewport
|
||||
jitter; motion vectors are camera-only, reprojected from the scene depth through this and last frame's
|
||||
view-projection of the scene camera block (constant bank c4). `fxaa` switches the game's own FXAA pass.
|
||||
jitter; camera motion vectors are reprojected from the scene depth through this and last frame's view-projection of
|
||||
the scene camera block (constant bank c4), in double precision on the CPU. Per-object motion (`dlaa_objmv`,
|
||||
`vk_mvprev.c`, the per-object section of `vk_exec_draw.c`, translator patch 0015): solid scene draws whose position
|
||||
reads an object's own uniform blocks (bones c5, place c7, c6) are drawn a second time with a vertex shader variant
|
||||
that computes last frame's position from last frame's blocks (the game writes them into two copies that take turns;
|
||||
the uniform snapshot keeps both) and writes the difference into the motion image. `TOTK_MV_CHECK` measures the
|
||||
vectors against the picture, `TOTK_DLAA_TIMES` times the passes on the GPU. `fxaa` switches the game's own FXAA pass.
|
||||
- Occlusion queries never block the CPU; a result that is not ready counts as visible.
|
||||
- ASTC textures are uploaded as they are where the device reports `textureCompressionASTC_LDR`, and decoded by a compute shader everywhere else (`vk_astc.c`, `TOTK_ASTC`). The CPU decoder (`tex_astc*.c`) is the fallback and the test oracle.
|
||||
- Draw state. A draw whose program, bound state and textures repeat reuses the previous draw's shaders and skips rebuilding its descriptor sets; descriptor sets are written through update templates (`vk_exec_draw.c`, `vk_pipeline.c`).
|
||||
|
||||
+20
-5
@@ -9,19 +9,21 @@ holds only the game; the installer and its tools stay outside it.
|
||||
1. Downloads `TotK-Setup-<version>-windows.zip` from GitHub Releases (none is published yet), unpacks it anywhere and
|
||||
starts `TotK-Setup.exe`.
|
||||
2. Picks:
|
||||
- the base game (XCI or NSP) and the 1.1.0, 1.2.1 or 1.4.3 update (NSP);
|
||||
- the base game (XCI or NSP) and the 1.1.0, 1.2.1 or 1.4.3 update (NSP); an NSZ or XCZ (compressed by the nsz
|
||||
tool) works as it is, the setup unpacks it itself;
|
||||
- their `prod.keys`;
|
||||
- optionally the firmware folder (`system/Contents/registered` of an unpacked firmware or an emulator's install),
|
||||
needed for Chinese, Korean and Traditional Chinese text;
|
||||
- the game language and the install folder.
|
||||
3. Waits for the setup, one time. The window shows each step and its progress.
|
||||
4. Plays `totk.exe` from the install folder. The installer and the source NSP or XCI can be deleted.
|
||||
4. Plays `totk.exe` from the install folder. The installer and the source NSP or XCI (or NSZ, XCZ) can be deleted.
|
||||
|
||||
The install folder (`main.c` `portable_paths()` reads this layout; it stays portable):
|
||||
|
||||
```
|
||||
<install folder>/
|
||||
totk.exe, SDL3.dll, libopus-0.dll, totk_xlate.exe
|
||||
nvngx_dlss.dll NVIDIA DLSS, downloaded by the setup's DLSS step when the player keeps it ticked
|
||||
totk.ini language; the other keys as commented defaults (totk.ini.example)
|
||||
exefs/, romfs/ extracted from the player's files
|
||||
shared-fonts/ the Switch system fonts, with Chinese, Korean or Traditional Chinese text
|
||||
@@ -44,7 +46,7 @@ loads the saves 1.4.3 wrote.
|
||||
|
||||
## Setup steps
|
||||
|
||||
1. **Check** before writing anything: the files open with the keys, the update is 1.1.0, 1.2.1 or 1.4.3 (by hash, as
|
||||
1. **Check** before writing anything: an NSZ or XCZ input is unpacked first (below), the files open with the keys, the update is 1.1.0, 1.2.1 or 1.4.3 (by hash, as
|
||||
`tools/extract.py`), the keys are new enough for the update, enough space is free on the target disk (about 36 GiB at the peak of a
|
||||
first install, 20 GiB after it), the CPU is at least x86-64-v2 (SSE4.2; with AVX2/FMA the faster `v3` build is used,
|
||||
otherwise the `v2` one), a Vulkan 1.3 GPU exists. Each failure is one clear message.
|
||||
@@ -71,7 +73,13 @@ loads the saves 1.4.3 wrote.
|
||||
When the graphics driver crashes while building the shaders, `prepare_shaders.py` runs that part again without the
|
||||
variants it was building ([building.md](building.md), step 6), and the setup finishes with a warning that names how
|
||||
many were left out.
|
||||
7. **Clean up** the temp folder, except the generated C and the objects (about 1.3 GB), and write
|
||||
7. **DLSS** (the window's tick box, on by default, or `--dlss`; Windows with an NVIDIA RTX GPU): download
|
||||
`nvngx_dlss.dll` from NVIDIA's own DLSS SDK repository (github.com/NVIDIA/DLSS, a pinned tag, NVIDIA RTX SDK
|
||||
license) into the install folder and check its size and SHA-256 (`steps.py`, `Dlss`). The release does not ship
|
||||
the library; NGX loads it from the folder of totk.exe for `dlaa` and the DLSS modes. A DLSS library already in the
|
||||
folder (a newer one the player put there) is kept. Without an RTX GPU the step only logs and does nothing; a failed
|
||||
download warns, the game runs without DLSS, and the next run of the setup tries again.
|
||||
8. **Clean up** the temp folder, except the generated C and the objects (about 1.3 GB), and write
|
||||
`build-cache/rebuild.txt` in the install folder.
|
||||
|
||||
A cancelled or failed setup resumes from the last finished step (`totk-install.json`).
|
||||
@@ -213,8 +221,15 @@ bin/ totk_xlate, hactool, ninja, libSDL3.so.0
|
||||
the file and asks to copy it again (`extract.DamagedFile`). A failed merge checks the base Program NCA alone to tell
|
||||
which file is damaged.
|
||||
- Unsigned exe: SmartScreen warns on first start.
|
||||
- NSZ and XCZ input (game files compressed by the nsz tool: NCAs stored as NCZ, their sections decrypted and
|
||||
compressed with zstd): the check step tells them by their content, not their name (an NSP or XCI holding NCZ files),
|
||||
so the file dialog has one filter for all four. It unpacks each into `<temp>/unpacked/<base|update>.nsp`
|
||||
(`tools/ncz.py`), an NSP of the NSZ's files or of the XCZ's secure partition, and the extraction reads that.
|
||||
Unpacking encrypts the sections again, so every NCA comes back byte for byte, and checks each against the hash its
|
||||
name carries; a mismatch is `check.input_damaged`. It needs the unpacked size free in the temp folder (checked
|
||||
first), runs at about 500 MB/s, and an interrupted setup reuses a finished NSP of the same file. The extract step
|
||||
deletes the folder when it is done. An XCZ update is refused like an XCI one.
|
||||
|
||||
## Later
|
||||
|
||||
- Re-warm the driver cache on start after a move, GPU or driver change.
|
||||
- NSZ and XCZ input.
|
||||
@@ -86,6 +86,9 @@ Source shorthands:
|
||||
| 3.7 | **Integer division** does not exist (compiled to `XMAD`/`IMAD` sequences); MUFU.RCP(0) = +Inf, LG2(0) = -Inf. | Same IEEE edge cases through `1/x`, `Log2`. | Low risk. | — |
|
||||
| 3.8 | **FP64** in some TotK cutscene shaders. | No FP64 on some hosts. | yuzu FP64 → FP32 (YZ-2306, PR 10693); ours: `softfloat64` job flag (`tools/xlate/Program.cs` `QueryHostSupportsShaderFloat64`). | — |
|
||||
| 3.9 | **Integer vertex inputs read as raw bits** (bone indices `R8UI`). | Float-declared inputs convert integers. | NXN Mario: stretched characters; re-translated with `uint` inputs. Ours: attribute types passed to `QueryGraphicsState` (`Program.cs`). | NXN |
|
||||
| 3.10 | **BFE / BFI fields past bit 31** (as PTX `bfe` / `bfi`): a field that runs past bit 31 is cut there; one that starts past it extracts 0 (signed: bit 31 repeated) and inserts nothing. TotK 1.4.3's Depths terrain vertex shaders (42 variants) take the position from a register, `((2y + x) * 8) & 0xff` with 5-bit x and y, so up to 248. | `OpBitField*Extract` / `OpBitFieldInsert` undefined when offset or offset + count exceeds 32; AMD's bitfield instructions take offset and width modulo 32. | Ryujinx passes the fields through. UltraTears saw the Depths walls and ground missing on the Steam Deck and a Linux RX 570 (RADV). Ours: clamped where the field is not a constant inside the word (translator patch 0012). RX 9070 on Windows drew the Depths the same before and after. | PTX ISA `bfe`, `bfi` |
|
||||
| 3.11 | **SHF by 0** gives the unshifted word (C for `SHF.L`, A for `SHF.R`). | Ryujinx's funnel shifts the other word by 32: undefined in SPIR-V; AMD shifts take the low 5 bits and give A \| C. | yuzu fixed Depths terrain gaps in its funnel shift (PR 10459). Ours: a select on a zero amount (translator patch 0013). In 1.4.3 every register SHF pairs its word with 0, so the result was right already. | — |
|
||||
| 3.12 | **Registers read before any write** on a path (paths the structurizer adds included) have no defined value in the game's code; UltraTears reports 0 from the NVIDIA driver. | An `OpVariable` without an initializer loads an undefined value; Mesa folds undefined values freely. | Ours: every function-local variable starts at 0 (`OpConstantNull`, translator patch 0014). | — |
|
||||
|
||||
## 4. Memory model
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[--temp DIR]
|
||||
|
||||
Modules: layout (where the bundled tools are, release or repository), events (JSON progress on stdout and the log
|
||||
file), state (totk-install.json), runner (which steps run: resume and update), steps (the seven steps), ini (totk.ini),
|
||||
file), state (totk-install.json), runner (which steps run: resume and update), steps (the eight steps), ini (totk.ini),
|
||||
cli (the command line). text/en.json holds the English text of every message id the events carry.
|
||||
|
||||
Standard library only, apart from what the extraction tools import (cryptography). Runs on Python 3.14.
|
||||
|
||||
+10
-4
@@ -1,5 +1,5 @@
|
||||
"""Command line: python -m installer --base FILE --update FILE --keys FILE --lang CODE --dest DIR [--firmware DIR]
|
||||
[--root DIR] [--temp DIR] [--verify]
|
||||
[--root DIR] [--temp DIR] [--verify] [--dlss]
|
||||
|
||||
A first install needs every option. --firmware names the Switch firmware folder (an unpacked firmware or an emulator's
|
||||
Contents/registered); the setup extracts the system shared fonts from it when the language is Chinese, Korean or
|
||||
@@ -7,6 +7,9 @@ Traditional Chinese. On a folder with totk-install.json (resume after a cancel o
|
||||
release) only --dest is needed: --lang defaults to the recorded language, and the game files are needed only when the
|
||||
extraction has to run again. Prints JSON events on stdout (events.py). Exit code: 0 done, 1 failed, 2 bad command
|
||||
line, 3 cancelled (Ctrl+C or Ctrl+Break).
|
||||
|
||||
--dlss downloads NVIDIA's DLSS library (nvngx_dlss.dll) from NVIDIA's DLSS SDK repository into the install folder on
|
||||
Windows with an NVIDIA RTX GPU (steps.py, Dlss).
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
@@ -19,7 +22,7 @@ from . import layout as layouts
|
||||
from . import state as statefile
|
||||
from .events import Reporter
|
||||
from .runner import Context, run_steps
|
||||
from .steps import all_steps
|
||||
from .steps import all_steps, migrate_state
|
||||
|
||||
EXIT = {'ok': 0, 'failed': 1, 'cancelled': 3}
|
||||
|
||||
@@ -47,8 +50,8 @@ def cancel(signum, frame):
|
||||
|
||||
def main(argv=None):
|
||||
ap = Parser(prog='python -m installer', description=__doc__.splitlines()[0])
|
||||
ap.add_argument('--base', type=Path, help='base game, XCI or NSP')
|
||||
ap.add_argument('--update', type=Path, help='the update NSP: 1.1.0, 1.2.1 or 1.4.3')
|
||||
ap.add_argument('--base', type=Path, help='base game, XCI or NSP (or NSZ, XCZ: unpacked first)')
|
||||
ap.add_argument('--update', type=Path, help='the update NSP or NSZ: 1.1.0, 1.2.1 or 1.4.3')
|
||||
ap.add_argument('--keys', type=Path, help='prod.keys')
|
||||
ap.add_argument('--firmware', type=Path, help='Switch firmware folder (Contents/registered) for the shared fonts '
|
||||
'of Chinese, Korean and Traditional Chinese text')
|
||||
@@ -58,6 +61,8 @@ def main(argv=None):
|
||||
ap.add_argument('--temp', type=Path, help='temp folder (default: temp/ of the release on Windows, .setup-temp in '
|
||||
'the install folder on Linux, build/install-temp in a repository)')
|
||||
ap.add_argument('--verify', action='store_true', help='hash every kept file, also those this release made')
|
||||
ap.add_argument('--dlss', action='store_true', help="download NVIDIA's nvngx_dlss.dll (NVIDIA RTX SDK license) next "
|
||||
'to the game, for dlaa and the DLSS modes')
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
# The window cancels with Ctrl+Break to the installer's process group (CREATE_NEW_PROCESS_GROUP), which also reaches
|
||||
@@ -79,6 +84,7 @@ def main(argv=None):
|
||||
rep.emit('end', result='failed', seconds=0)
|
||||
return 1
|
||||
ctx = Context(layout, dest, temp, rep, args, state=state, verify=args.verify)
|
||||
migrate_state(ctx)
|
||||
steps = all_steps()
|
||||
rep.emit('start', release=layout.release, mode=ctx.mode, dev=layout.dev, dest=str(dest), log=str(log),
|
||||
steps=[s.id for s in steps])
|
||||
|
||||
+12
-5
@@ -225,7 +225,14 @@ class App:
|
||||
self.label(grid, 'ui.dest').grid(row=i + 1, column=0, sticky='w', pady=4)
|
||||
ttk.Entry(grid, textvariable=self.vars['dest']).grid(row=i + 1, column=1, sticky='ew', pady=4)
|
||||
self.button(grid, 'ui.browse', self.pick_dest).grid(row=i + 1, column=2, padx=(8, 0))
|
||||
self.label(f, 'ui.compressed_help', wraplength=620, justify='left').pack(anchor='w', pady=(8, 0))
|
||||
self.label(f, 'ui.firmware_help', wraplength=620, justify='left').pack(anchor='w', pady=(8, 0))
|
||||
# On by default and named with its source and licence: the player sees what is downloaded and can untick it
|
||||
self.dlss_var = tk.BooleanVar(value=True)
|
||||
dlss = ttk.Checkbutton(f, variable=self.dlss_var)
|
||||
self.bind_text(dlss, 'text', 'ui.dlss')
|
||||
dlss.pack(anchor='w', pady=(8, 0))
|
||||
self.label(f, 'ui.dlss_help', wraplength=620, justify='left').pack(anchor='w', padx=(22, 0))
|
||||
self.update_hint = ttk.Label(f, wraplength=620, justify='left')
|
||||
self.update_hint.pack(anchor='w', pady=(8, 0))
|
||||
self.form_error = ttk.Label(f, style='Err.TLabel', wraplength=620, justify='left')
|
||||
@@ -288,7 +295,8 @@ class App:
|
||||
|
||||
def form(self):
|
||||
return Form(base=self.vars['base'].get(), update=self.vars['update'].get(), keys=self.vars['keys'].get(),
|
||||
firmware=self.vars['firmware'].get(), lang=self.selected_lang(), dest=self.vars['dest'].get())
|
||||
firmware=self.vars['firmware'].get(), lang=self.selected_lang(), dest=self.vars['dest'].get(),
|
||||
dlss=self.dlss_var.get())
|
||||
|
||||
def refresh_form(self):
|
||||
form = self.form()
|
||||
@@ -303,9 +311,8 @@ class App:
|
||||
self.fit()
|
||||
|
||||
def pick_file(self, name, pick_id):
|
||||
types = [(self.cat('ui.type_game'), '*.nsp *.xci'), (self.cat('ui.type_all'), '*.*')]
|
||||
if name == 'update':
|
||||
types = [(self.cat('ui.type_nsp'), '*.nsp'), (self.cat('ui.type_all'), '*.*')]
|
||||
# One filter for every game file: the setup tells an NSZ or XCZ from a plain file by its content (Check.unpack)
|
||||
types = [(self.cat('ui.type_game'), '*.nsp *.xci *.nsz *.xcz'), (self.cat('ui.type_all'), '*.*')]
|
||||
if name == 'keys':
|
||||
types = [(self.cat('ui.type_keys'), '*.keys'), (self.cat('ui.type_all'), '*.*')]
|
||||
path = filedialog.askopenfilename(title=self.cat(pick_id), filetypes=types)
|
||||
@@ -537,7 +544,7 @@ class App:
|
||||
# ---- self test ---------------------------------------------------------------------------------------------------
|
||||
|
||||
SAMPLE_START = {'type': 'start', 'release': 'selftest', 'mode': 'install', 'dest': 'Z:/selftest/TotK', 'log': 'D:\\log.txt',
|
||||
'steps': ['check', 'fonts', 'extract', 'lift', 'compile', 'prepare', 'cleanup']}
|
||||
'steps': ['check', 'fonts', 'extract', 'lift', 'compile', 'prepare', 'dlss', 'cleanup']}
|
||||
SAMPLE_RUN = [
|
||||
SAMPLE_START,
|
||||
{'type': 'plan', 'run': ['check', 'fonts', 'extract'], 'skip': {}},
|
||||
|
||||
@@ -27,7 +27,7 @@ TEXT = Path(__file__).resolve().parent / 'text'
|
||||
UI_LANGUAGES = ('en', 'ru', 'pt-BR', 'zh-Hans')
|
||||
# what the language switch in the window's corner shows for each
|
||||
UI_LANGUAGE_LABELS = {'en': 'English', 'ru': 'Русский', 'pt-BR': 'Português (Brasil)', 'zh-Hans': '简体中文'}
|
||||
STEP_ORDER = ('check', 'fonts', 'extract', 'lift', 'compile', 'prepare', 'cleanup')
|
||||
STEP_ORDER = ('check', 'fonts', 'extract', 'lift', 'compile', 'prepare', 'dlss', 'cleanup')
|
||||
# message parameter -> catalog id prefix of the names that replace its raw values
|
||||
PARAM_NAMES = {'lang': 'lang.', 'known': 'lang.', 'step': 'step.', 'input': 'param.input.'}
|
||||
LANG_RU = 0x19 # primary language id of Russian in a Windows LANGID
|
||||
@@ -137,6 +137,7 @@ class Form:
|
||||
firmware: str = ''
|
||||
lang: str = ''
|
||||
dest: str = ''
|
||||
dlss: bool = False
|
||||
|
||||
def installed_state(self):
|
||||
"""The recorded state of the install folder (totk-install.json), or None. Never raises."""
|
||||
@@ -187,6 +188,8 @@ class Form:
|
||||
cmd += ['--temp', str(temp)]
|
||||
if verify_hashes:
|
||||
cmd.append('--verify')
|
||||
if self.dlss:
|
||||
cmd.append('--dlss')
|
||||
return cmd
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -90,7 +90,8 @@ class Layout:
|
||||
def missing(self, cpu='v3'):
|
||||
"""Paths the steps need that do not exist. cpu picks the runtime library the host will use."""
|
||||
lib = self.runtime_lib_for(cpu)
|
||||
need = [self.python, self.tools / 'extract.py', self.tools / 'build_game.py', self.tools / 'prepare_shaders.py',
|
||||
need = [self.python, self.tools / 'extract.py', self.tools / 'ncz.py', self.tools / 'build_game.py',
|
||||
self.tools / 'prepare_shaders.py',
|
||||
self.tools / 'pack_shader_cache.py', self.tools / 'make_romfs_patches.py', self.tools / 'ultrawide_layout.py',
|
||||
self.tools / 'button_icons.py',
|
||||
self.tools / 'vendor' / 'macnativegames' / 'extract_shared_fonts.py',
|
||||
|
||||
+187
-18
@@ -1,4 +1,4 @@
|
||||
"""The seven setup steps of docs/installer.md: check, fonts, extract, lift, compile, prepare, cleanup.
|
||||
"""The eight setup steps of docs/installer.md: check, fonts, extract, lift, compile, prepare, dlss, cleanup.
|
||||
|
||||
Extraction runs in this process through tools/extract.py (the package's sibling tools/, which ships with it); fonts
|
||||
runs the vendored extract_shared_fonts.py on the firmware folder; lift, compile and prepare run the bundled Python on
|
||||
@@ -7,18 +7,20 @@ recomp/gen.py, tools/build_game.py, tools/make_romfs_patches.py and tools/prepar
|
||||
with the bundled clang (hostcheck.c).
|
||||
|
||||
Install folder: exefs/, romfs/ (romfs.bin, index.json), profile/, shared-fonts/ (Chinese and Korean text), totk.exe
|
||||
and its DLLs, romfs-index.bin, romfs-patches/, romfs-patches-xbox/, romfs-patches-xbox-label/, totk_xlate.exe, the
|
||||
and its DLLs, nvngx_dlss.dll (with --dlss), romfs-index.bin, romfs-patches/, romfs-patches-xbox/, romfs-patches-xbox-label/, totk_xlate.exe, the
|
||||
shader state of totk.exe --prepare-shaders, totk.ini, totk-install.json.
|
||||
Temp folder (<temp>/<dest name>-<hash>): check/ (ExeFS of both inputs, the host check), gen/ (C), build/ (objects),
|
||||
logs/. The cleanup keeps gen/, build/ and logs/: the next run lifts into the same gen/, where recomp/gen.py rewrites
|
||||
only the chunks whose C changed, and Ninja in build/ recompiles only those. A change of the mods' code cheats then
|
||||
costs the lift (about a minute) and the chunks it touches, not a full compile.
|
||||
Temp folder (<temp>/<dest name>-<hash>): check/ (ExeFS of both inputs, the host check), unpacked/ (the NSP of an NSZ
|
||||
or XCZ input until the extraction is done), gen/ (C), build/ (objects), logs/. The cleanup keeps gen/, build/ and
|
||||
logs/: the next run lifts into the same gen/, where recomp/gen.py rewrites only the chunks whose C changed, and Ninja
|
||||
in build/ recompiles only those. A change of the mods' code cheats then costs the lift (about a minute) and the chunks
|
||||
it touches, not a full compile.
|
||||
|
||||
Install folder build-cache/: cheats.status (tools/cheats.py), exefs.status (tools/exefs_patches.py) and rebuild.txt, the command that starts this setup in
|
||||
its rebuild mode on the install folder (installer/gui.py --rebuild), one argument per line; totk.exe runs it at start
|
||||
when an enabled mod has code cheats the build lacks (runtime/cheats.c).
|
||||
"""
|
||||
import hashlib
|
||||
import http.client
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -28,6 +30,7 @@ import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
from . import ini
|
||||
@@ -36,6 +39,7 @@ from .runner import InstallError, Step
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'tools'))
|
||||
import extract # noqa: E402
|
||||
import ncz # noqa: E402
|
||||
import shader_list # noqa: E402
|
||||
from extract_exefs import (common_tickets, container_entries, load_keys, nca_ctr_key, nca_inventory, # noqa: E402
|
||||
program_nca)
|
||||
@@ -174,6 +178,12 @@ def raise_if_damaged(error, name, path):
|
||||
raise InstallError('check.input_damaged', input=name, path=str(path), detail=str(error)) from None
|
||||
|
||||
|
||||
def game_file(ctx, name):
|
||||
"""The file the extraction reads for the base or the update: the NSP that Check.unpack made from an NSZ or XCZ,
|
||||
else the chosen file itself."""
|
||||
return getattr(ctx, 'unpacked', {}).get(name) or Path(getattr(ctx.options, name))
|
||||
|
||||
|
||||
def game_version(ctx):
|
||||
"""The update this install is built from: what the check identified, else the recorded one. An install made before
|
||||
1.4.3 support records no version, and that install is 1.2.1: name it here rather than leaning on SUPPORTED's order."""
|
||||
@@ -184,6 +194,25 @@ def expected_update(ctx):
|
||||
return extract.load_expected()[game_version(ctx)]
|
||||
|
||||
|
||||
# Releases before 0.3.1 recorded the 0x20000 update as 1.1.1 (pull request #220); it is 1.1.0 (#371), with the same
|
||||
# files, hashes and patch sites under new names.
|
||||
RENAMED_VERSIONS = {'1.1.1': '1.1.0'}
|
||||
|
||||
|
||||
def migrate_state(ctx):
|
||||
"""Read a recorded update by its current name. The extraction it recorded stays: its record gets the key of the
|
||||
renamed expected file, and Extract.current and the full check of output another release kept
|
||||
(runner.Context.verify_kept) still test the files against the expected hashes."""
|
||||
old = ctx.state.get('version')
|
||||
if old not in RENAMED_VERSIONS:
|
||||
return
|
||||
ctx.state['version'] = RENAMED_VERSIONS[old]
|
||||
record = ctx.state['steps'].get('extract')
|
||||
if record:
|
||||
record['key'] = Extract().key(ctx)
|
||||
ctx.reporter.log(f'state: the update recorded as {old} is {ctx.state["version"]}')
|
||||
|
||||
|
||||
def shader_list_text(layout):
|
||||
if layout.shader_list:
|
||||
return layout.shader_list.read_text(encoding='utf-8')
|
||||
@@ -432,14 +461,73 @@ class Check(Step):
|
||||
raise InstallError('check.keys_unreadable') from None
|
||||
if 'header_key' not in keys:
|
||||
raise InstallError('check.keys_no_header_key')
|
||||
self.unpack(ctx)
|
||||
total = 0
|
||||
for name, is_update in (('base', False), ('update', True)):
|
||||
path = Path(getattr(o, name))
|
||||
path = game_file(ctx, name)
|
||||
with path.open('rb') as f:
|
||||
total += self.open_input(f, path, keys, name, is_update)
|
||||
del keys
|
||||
return total
|
||||
|
||||
def unpack(self, ctx):
|
||||
"""Unpack an NSZ or XCZ (a game file compressed by the nsz tool) into <temp>/unpacked/<base|update>.nsp with
|
||||
tools/ncz.py, which checks every NCA against its hash; the extraction then reads that NSP. An earlier run's NSP
|
||||
of the same file is reused. The extract step removes the folder once it is done."""
|
||||
o, rep, folder = ctx.options, ctx.reporter, ctx.temp / 'unpacked'
|
||||
ctx.unpacked = {}
|
||||
jobs = []
|
||||
for name in ('base', 'update'):
|
||||
path = Path(getattr(o, name))
|
||||
try:
|
||||
found = ncz.compressed(path)
|
||||
if found is None:
|
||||
continue
|
||||
if name == 'update' and found[0] != 'nsz':
|
||||
raise InstallError('check.update_not_nsp')
|
||||
size = ncz.unpacked_size(path)
|
||||
except ValueError as e:
|
||||
raise_if_damaged(e, name, path)
|
||||
raise InstallError('check.not_game_file', input=name) from None
|
||||
st = path.stat()
|
||||
jobs.append((name, path, size, f'{path.resolve()}\n{st.st_size}\n{st.st_mtime_ns}\n{size}\n'))
|
||||
if not jobs:
|
||||
remove(folder)
|
||||
return
|
||||
done = []
|
||||
for name, path, size, tag in jobs:
|
||||
target, stamp = folder / f'{name}.nsp', folder / f'{name}.source'
|
||||
try:
|
||||
if target.stat().st_size == size and stamp.read_text(encoding='utf-8') == tag:
|
||||
done.append(name)
|
||||
ctx.unpacked[name] = target
|
||||
except OSError:
|
||||
pass
|
||||
need = sum(size for name, _, size, _ in jobs if name not in done)
|
||||
free, _ = free_bytes(folder)
|
||||
rep.log(f'unpack: {", ".join(name for name, *_ in jobs)}: {need / GiB:.1f} GiB to write, '
|
||||
f'{free / GiB:.1f} free')
|
||||
if need > free:
|
||||
raise InstallError('check.disk_space', path=str(folder), need_gb=round(need / 1e9, 1),
|
||||
free_gb=round(free / 1e9, 1))
|
||||
for name, path, size, tag in jobs:
|
||||
if name in done:
|
||||
continue
|
||||
target, stamp = folder / f'{name}.nsp', folder / f'{name}.source'
|
||||
remove(stamp)
|
||||
try:
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
ncz.unpack(path, target, lambda d, t, n=name: rep.progress('check', 'unpack_' + n, d, t, 'bytes'))
|
||||
stamp.write_text(tag, encoding='utf-8')
|
||||
except ncz.DamagedFile as e:
|
||||
raise InstallError('check.input_damaged', input=name, path=str(e.path), detail=e.detail) from None
|
||||
except ValueError as e:
|
||||
raise_if_damaged(e, name, path)
|
||||
raise InstallError('check.unpack_failed', input=name, detail=str(e)) from None
|
||||
except OSError as e:
|
||||
raise InstallError('check.unpack_failed', input=name, detail=str(e)) from None
|
||||
ctx.unpacked[name] = target
|
||||
|
||||
@staticmethod
|
||||
def open_input(f, path, keys, name, is_update):
|
||||
try:
|
||||
@@ -520,9 +608,10 @@ class Check(Step):
|
||||
out = work / name
|
||||
remove(out)
|
||||
try:
|
||||
extract.extract_exefs(Path(getattr(o, name)), Path(o.keys), out, logs, update=is_update)
|
||||
extract.extract_exefs(game_file(ctx, name), Path(o.keys), out, logs, update=is_update)
|
||||
except extract.DamagedFile as e:
|
||||
raise InstallError('check.input_damaged', input=name, path=str(e.path), detail=e.detail) from None
|
||||
raise InstallError('check.input_damaged', input=name, path=str(getattr(o, name)),
|
||||
detail=e.detail) from None
|
||||
except ValueError:
|
||||
raise InstallError('check.exefs_failed', input=name, log=str(logs / f'extract_exefs-{name}.log')) \
|
||||
from None
|
||||
@@ -641,9 +730,9 @@ class Extract(Step):
|
||||
def run(self, ctx):
|
||||
o, dest, rep = ctx.options, ctx.dest, ctx.reporter
|
||||
expected = expected_update(ctx)
|
||||
base, update, keys = Path(o.base), Path(o.update), Path(o.keys)
|
||||
keys = Path(o.keys)
|
||||
inputs = {}
|
||||
for name, path in (('base', base), ('update', update)):
|
||||
for name, path in (('base', Path(o.base)), ('update', Path(o.update))):
|
||||
known = ctx.state['inputs'].get(name, {})
|
||||
st = path.stat()
|
||||
if known.get('size') == st.st_size and known.get('mtime_ns') == st.st_mtime_ns and known.get('sha256'):
|
||||
@@ -653,6 +742,7 @@ class Extract(Step):
|
||||
inputs[name] = {'name': path.name, 'size': st.st_size, 'mtime_ns': st.st_mtime_ns, 'sha256': digest}
|
||||
ctx.state['inputs'] = inputs
|
||||
ctx.save()
|
||||
base, update = game_file(ctx, 'base'), game_file(ctx, 'update')
|
||||
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
for stale in dest.glob('update-merge-*'): # a killed merge leaves its NCA copies (and key copies) behind
|
||||
@@ -664,7 +754,7 @@ class Extract(Step):
|
||||
try:
|
||||
extract.extract_exefs(update, keys, ctx.temp / 'check' / 'update', ctx.temp / 'logs', update=True)
|
||||
except extract.DamagedFile as e:
|
||||
raise InstallError('check.input_damaged', input='update', path=str(e.path), detail=e.detail) from None
|
||||
raise InstallError('check.input_damaged', input='update', path=str(o.update), detail=e.detail) from None
|
||||
except ValueError:
|
||||
raise InstallError('check.exefs_failed', input='update',
|
||||
log=str(ctx.temp / 'logs' / 'extract_exefs-update.log')) from None
|
||||
@@ -686,6 +776,7 @@ class Extract(Step):
|
||||
raise InstallError('extract.romfs_mismatch', version=game_version(ctx), size=report['romfs_size'],
|
||||
sha256=report['romfs_sha256'][:16])
|
||||
(dest / 'profile').mkdir(exist_ok=True)
|
||||
remove(ctx.temp / 'unpacked') # the NSPs unpacked from an NSZ or XCZ (Check.unpack)
|
||||
st = (romfs / 'romfs.bin').stat()
|
||||
return {'romfs_size': st.st_size, 'romfs_mtime_ns': st.st_mtime_ns}
|
||||
|
||||
@@ -707,7 +798,8 @@ class Extract(Step):
|
||||
try:
|
||||
extract.merge_update_romfs(base, update, keys, ctx.layout.hactool, romfs, logs, phase=phase)
|
||||
except extract.DamagedFile as e:
|
||||
raise InstallError('check.input_damaged', input='base' if e.path == base else 'update', path=str(e.path),
|
||||
name = 'base' if e.path == base else 'update'
|
||||
raise InstallError('check.input_damaged', input=name, path=str(getattr(ctx.options, name)),
|
||||
detail=e.detail) from None
|
||||
except ValueError as e:
|
||||
raise InstallError('extract.romfs_merge_failed', detail=str(e), log=str(logs / 'merge_romfs-'
|
||||
@@ -907,9 +999,11 @@ SKIPPED = re.compile(r'prepare-shaders: (\d+) variants skipped: the graphics dri
|
||||
|
||||
|
||||
# The game resolutions of another shape than 16:9 that runtime/ui/menu.cpp row_output_res lists: 21:9 and 32:9 monitors,
|
||||
# and 16:10 screens (the Steam Deck's 1280x800, handhelds and laptops)
|
||||
# 16:10 screens (the Steam Deck's 1280x800, handhelds and laptops) and 3:2 screens (Surface and Framework laptops and
|
||||
# tablets)
|
||||
ASPECT_SIZES = ('2560x1080', '3440x1440', '3840x1600', '5120x2160', '3840x1080', '5120x1440',
|
||||
'1280x800', '1680x1050', '1920x1200', '2560x1600')
|
||||
'1280x800', '1680x1050', '1920x1200', '2560x1600',
|
||||
'1920x1280', '2160x1440', '2256x1504', '2880x1920', '3000x2000')
|
||||
|
||||
|
||||
class Prepare(Step):
|
||||
@@ -1022,7 +1116,82 @@ class Prepare(Step):
|
||||
return {'xlate_sha256': file_sha256(xlate)}
|
||||
|
||||
|
||||
# ---- 7. clean up
|
||||
# ---- 7. NVIDIA DLSS
|
||||
|
||||
# NVIDIA's DLSS library, which NGX loads from the folder of totk.exe (runtime/vk_dlaa.c). The setup does not ship it:
|
||||
# with --dlss it downloads NVIDIA's own copy from the DLSS SDK repository (NVIDIA RTX SDK license) at a pinned tag and
|
||||
# checks its hash. The game runs without it; dlaa and the DLSS modes then do nothing.
|
||||
DLSS_DLL = 'nvngx_dlss.dll'
|
||||
DLSS_VERSION = '310.9.1'
|
||||
DLSS_URL = f'https://raw.githubusercontent.com/NVIDIA/DLSS/v{DLSS_VERSION}/lib/Windows_x86_64/rel/{DLSS_DLL}'
|
||||
DLSS_SIZE = 58956912
|
||||
DLSS_SHA256 = '3975567b8943c53acce397f2b72380092f84f162d00b0d2c7d08a1025c563983'
|
||||
DLSS_LICENSE = 'https://github.com/NVIDIA/DLSS/blob/main/LICENSE.txt'
|
||||
|
||||
|
||||
def nvidia_rtx(host):
|
||||
"""An NVIDIA RTX GPU among the host check's Vulkan devices: the GPUs DLSS runs on."""
|
||||
return any('NVIDIA' in d['name'].upper() and 'RTX' in d['name'].upper() for d in (host or {}).get('devices', []))
|
||||
|
||||
|
||||
class Dlss(Step):
|
||||
"""With --dlss on Windows and an NVIDIA RTX GPU: nvngx_dlss.dll next to totk.exe. A DLSS library the player put
|
||||
there (another version) is kept. A failed download warns and is tried again on the next run; the game runs
|
||||
without it."""
|
||||
|
||||
id = 'dlss'
|
||||
|
||||
def key(self, ctx):
|
||||
if not getattr(ctx.options, 'dlss', False):
|
||||
return fingerprint('dlss-1', 'off')
|
||||
return fingerprint('dlss-1', DLSS_VERSION, DLSS_SHA256)
|
||||
|
||||
def current(self, ctx, record):
|
||||
if record.get('failed'):
|
||||
return False
|
||||
return not record.get('installed') or (ctx.dest / DLSS_DLL).is_file()
|
||||
|
||||
def run(self, ctx):
|
||||
if not getattr(ctx.options, 'dlss', False):
|
||||
return {}
|
||||
if os.name != 'nt':
|
||||
return {'skipped': 'not_windows'}
|
||||
if not nvidia_rtx(getattr(ctx, 'host', None)):
|
||||
# The box is ticked by default, so most players without an RTX GPU never chose it: a log line, no warning
|
||||
ctx.reporter.log('dlss: no NVIDIA RTX GPU among the Vulkan devices, nothing downloaded')
|
||||
return {'skipped': 'no_rtx'}
|
||||
rep, target = ctx.reporter, ctx.dest / DLSS_DLL
|
||||
if target.is_file():
|
||||
if file_sha256(target) == DLSS_SHA256:
|
||||
return {'installed': DLSS_VERSION}
|
||||
rep.warning('warn.dlss_kept', path=str(target))
|
||||
return {'kept': True}
|
||||
part = ctx.temp / (DLSS_DLL + '.part')
|
||||
rep.progress('dlss', 'download', 0, DLSS_SIZE, 'bytes', force=True)
|
||||
rep.log(f'dlss: {DLSS_URL} (license {DLSS_LICENSE})')
|
||||
try:
|
||||
part.parent.mkdir(parents=True, exist_ok=True)
|
||||
h, done = hashlib.sha256(), 0
|
||||
with urllib.request.urlopen(DLSS_URL, timeout=60) as r, open(part, 'wb') as f:
|
||||
while block := r.read(1 << 20):
|
||||
f.write(block)
|
||||
h.update(block)
|
||||
done += len(block)
|
||||
rep.progress('dlss', 'download', min(done, DLSS_SIZE), DLSS_SIZE, 'bytes')
|
||||
if done != DLSS_SIZE or h.hexdigest() != DLSS_SHA256:
|
||||
raise OSError(f'{done} bytes, SHA-256 {h.hexdigest()[:16]}, not the expected file')
|
||||
replace_file(part, target)
|
||||
remove(part)
|
||||
# urllib's errors, timeouts and a bad file; a connection cut mid-file raises http.client.IncompleteRead,
|
||||
# which is not an OSError
|
||||
except (OSError, http.client.HTTPException) as e:
|
||||
remove(part)
|
||||
rep.warning('warn.dlss_download_failed', url=DLSS_URL, detail=str(e)[:300])
|
||||
return {'failed': True}
|
||||
return {'installed': DLSS_VERSION}
|
||||
|
||||
|
||||
# ---- 8. clean up
|
||||
|
||||
class Cleanup(Step):
|
||||
id = 'cleanup'
|
||||
@@ -1040,7 +1209,7 @@ class Cleanup(Step):
|
||||
rep.progress('cleanup', 'temp', force=True)
|
||||
for stale in dest.glob('update-merge-*'):
|
||||
remove(stale)
|
||||
for name in ('check', 'shader_list.tsv', 'shared-fonts'): # gen/ and build/ stay for the next rebuild
|
||||
for name in ('check', 'unpacked', 'shader_list.tsv', 'shared-fonts'): # gen/ and build/ stay for the next rebuild
|
||||
remove(ctx.temp / name)
|
||||
romfs_problem(dest / 'romfs', expected_update(ctx))
|
||||
write_rebuild_command(ctx)
|
||||
@@ -1090,4 +1259,4 @@ def romfs_problem(romfs, expected):
|
||||
|
||||
|
||||
def all_steps():
|
||||
return [Check(), Fonts(), Extract(), Lift(), Compile(), Prepare(), Cleanup()]
|
||||
return [Check(), Fonts(), Extract(), Lift(), Compile(), Prepare(), Dlss(), Cleanup()]
|
||||
+15
-6
@@ -5,12 +5,15 @@
|
||||
"step.lift": "Translating the game code to C",
|
||||
"step.compile": "Compiling {game_exe}",
|
||||
"step.prepare": "Preparing the shaders",
|
||||
"step.dlss": "Downloading NVIDIA DLSS",
|
||||
"step.cleanup": "Cleaning up",
|
||||
|
||||
"phase.check.host": "Checking the processor and the graphics card",
|
||||
"phase.check.verify": "Checking the files already installed",
|
||||
"phase.check.verify_romfs": "Checking the installed game data",
|
||||
"phase.check.inputs": "Opening the game files with the keys",
|
||||
"phase.check.unpack_base": "Unpacking the compressed base game",
|
||||
"phase.check.unpack_update": "Unpacking the compressed update",
|
||||
"phase.check.versions": "Checking the game versions",
|
||||
"phase.fonts.extract": "Extracting the shared fonts from the firmware",
|
||||
"phase.fonts.copy": "Copying the shared fonts into the install folder",
|
||||
@@ -30,6 +33,7 @@
|
||||
"phase.prepare.translate": "Translating the shaders",
|
||||
"phase.prepare.libraries": "Building the shaders for the graphics driver",
|
||||
"phase.prepare.pack": "Packing the shader cache into one file",
|
||||
"phase.dlss.download": "Downloading nvngx_dlss.dll from NVIDIA's DLSS SDK",
|
||||
"phase.cleanup.ini": "Writing totk.ini",
|
||||
"phase.cleanup.temp": "Removing temporary files",
|
||||
|
||||
@@ -58,14 +62,15 @@
|
||||
"check.keys_no_header_key": "The keys file has no header_key. Dump prod.keys from your console again.",
|
||||
"check.keys_wrong": "The keys cannot open the {input} file. Use the prod.keys of your console.",
|
||||
"check.keys_too_old": "The keys file is too old for the {input} file (needs key generation {generation}). Dump prod.keys from a console with newer firmware.",
|
||||
"check.not_game_file": "The {input} file is not an XCI or NSP game file.",
|
||||
"check.not_game_file": "The {input} file is not an XCI, NSP, XCZ or NSZ game file.",
|
||||
"check.file_unreadable": "The {input} file cannot be read: {detail}",
|
||||
"check.unpack_failed": "The compressed {input} file could not be unpacked: {detail}",
|
||||
"check.ticket_personalized": "The {input} file carries a personalized ticket; a common ticket is needed.",
|
||||
"check.ticket_missing": "The {input} file lacks the ticket its content needs.",
|
||||
"check.base_not_totk": "The base game file is not The Legend of Zelda: Tears of the Kingdom ({detail}).",
|
||||
"check.base_is_update": "The base game file is an update. Choose the base game NSP or XCI (version 1.0.0) here and this file as the update.",
|
||||
"check.base_combined": "The base game file holds several packages (base game, update or DLC in one file). Choose a base game dump that contains only the game.",
|
||||
"check.update_not_nsp": "The update must be an NSP file.",
|
||||
"check.update_not_nsp": "The update must be an NSP or NSZ file.",
|
||||
"check.update_is_base": "The update file holds a base game. Choose the 1.1.0 or 1.2.1 or 1.4.3 update NSP.",
|
||||
"check.update_not_totk": "The update file is not a Tears of the Kingdom update ({detail}).",
|
||||
"check.exefs_failed": "The game code of the {input} file failed its checks; see {log}.",
|
||||
@@ -101,6 +106,8 @@
|
||||
"warn.xbox_prompts_failed": "The Xbox button prompts could not be prepared (exit code {code}); the game keeps the Switch prompts. Details: {log}",
|
||||
"warn.shaders_skipped": "The graphics driver crashed while building some shaders, so {count} were left out of the preparation. The game builds them when it first needs them, and the driver may crash there too; a driver update may fix it. Details: {log}",
|
||||
"warn.kernel_old": "Linux {version} lacks the write tracking the game relies on (6.7 or later); the game may run very slowly. Updating the system fixes this.",
|
||||
"warn.dlss_kept": "A DLSS library is already in the install folder ({path}); the setup keeps it.",
|
||||
"warn.dlss_download_failed": "NVIDIA DLSS could not be downloaded from {url}: {detail}. The game runs without it; the next setup run tries again.",
|
||||
|
||||
"ui.title": "TotK Setup",
|
||||
"ui.rebuild_title": "Rebuilding the game for your mods",
|
||||
@@ -109,8 +116,8 @@
|
||||
"ui.intro": "Builds a playable Tears of the Kingdom folder from your own game files. This takes a while and runs once.",
|
||||
"ui.notice": "The folder this setup builds contains the game. It is for your own use only; do not share or upload it.",
|
||||
"ui.keys_private": "The keys file is read in place. Its contents are never shown, copied or stored.",
|
||||
"ui.base": "Base game (NSP or XCI)",
|
||||
"ui.update": "Update 1.1.0 or 1.2.1 or 1.4.3 (NSP)",
|
||||
"ui.base": "Base game (NSP, XCI, NSZ or XCZ)",
|
||||
"ui.update": "Update 1.1.0 or 1.2.1 or 1.4.3 (NSP or NSZ)",
|
||||
"ui.keys": "prod.keys",
|
||||
"ui.firmware": "Firmware folder (optional)",
|
||||
"ui.game_language": "Game language",
|
||||
@@ -122,8 +129,10 @@
|
||||
"ui.pick_firmware": "Choose the Switch firmware folder (Contents/registered)",
|
||||
"ui.pick_dest": "Choose the install folder",
|
||||
"ui.firmware_help": "For Chinese, Korean and Traditional Chinese text: the firmware's Contents/registered folder, for example from Ryujinx or Yuzu. The setup copies the Switch system fonts from it into the install folder.",
|
||||
"ui.type_game": "Game files (NSP, XCI)",
|
||||
"ui.type_nsp": "NSP files",
|
||||
"ui.dlss": "Download NVIDIA DLSS (nvngx_dlss.dll) for NVIDIA RTX graphics cards",
|
||||
"ui.dlss_help": "From NVIDIA's DLSS SDK on GitHub, under NVIDIA's RTX SDK license (github.com/NVIDIA/DLSS). It makes the dlaa and DLSS settings work; without it they do nothing.",
|
||||
"ui.compressed_help": "Compressed NSZ and XCZ files work as they are: the setup unpacks them by itself (until the game data is extracted, that needs as much free space as the unpacked files take).",
|
||||
"ui.type_game": "Game files (NSP, XCI, NSZ, XCZ)",
|
||||
"ui.type_keys": "Key files (*.keys)",
|
||||
"ui.type_all": "All files",
|
||||
"ui.start": "Start",
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
"step.lift": "Traduzindo o código do jogo para C",
|
||||
"step.compile": "Compilando o {game_exe}",
|
||||
"step.prepare": "Preparando os shaders",
|
||||
"step.dlss": "Baixando o NVIDIA DLSS",
|
||||
"step.cleanup": "Finalizando",
|
||||
|
||||
"phase.check.host": "Verificando o processador e a placa de vídeo",
|
||||
"phase.check.verify": "Verificando os arquivos já instalados",
|
||||
"phase.check.verify_romfs": "Verificando os dados do jogo instalados",
|
||||
"phase.check.inputs": "Abrindo os arquivos do jogo com as chaves",
|
||||
"phase.check.unpack_base": "Descompactando o jogo base compactado",
|
||||
"phase.check.unpack_update": "Descompactando a atualização compactada",
|
||||
"phase.check.versions": "Verificando as versões do jogo",
|
||||
"phase.fonts.extract": "Extraindo as fontes compartilhadas do firmware",
|
||||
"phase.fonts.copy": "Copiando as fontes compartilhadas para a pasta de instalação",
|
||||
@@ -30,6 +33,7 @@
|
||||
"phase.prepare.translate": "Traduzindo os shaders",
|
||||
"phase.prepare.libraries": "Montando os shaders para o driver de vídeo",
|
||||
"phase.prepare.pack": "Juntando o cache de shaders num único arquivo",
|
||||
"phase.dlss.download": "Baixando o nvngx_dlss.dll do DLSS SDK da NVIDIA",
|
||||
"phase.cleanup.ini": "Gravando o totk.ini",
|
||||
"phase.cleanup.temp": "Removendo os arquivos temporários",
|
||||
|
||||
@@ -58,14 +62,15 @@
|
||||
"check.keys_no_header_key": "O arquivo de chaves não tem header_key. Extraia o prod.keys do seu console de novo.",
|
||||
"check.keys_wrong": "As chaves não abrem o arquivo de {input}. Use o prod.keys do seu console.",
|
||||
"check.keys_too_old": "O arquivo de chaves é antigo demais para o arquivo de {input} (precisa da geração de chaves {generation}). Extraia o prod.keys de um console com firmware mais novo.",
|
||||
"check.not_game_file": "O arquivo de {input} não é um arquivo de jogo XCI ou NSP.",
|
||||
"check.not_game_file": "O arquivo de {input} não é um arquivo de jogo XCI, NSP, XCZ ou NSZ.",
|
||||
"check.file_unreadable": "Não foi possível ler o arquivo de {input}: {detail}",
|
||||
"check.unpack_failed": "Não foi possível descompactar o arquivo compactado de {input}: {detail}",
|
||||
"check.ticket_personalized": "O arquivo de {input} tem um ticket personalizado; é preciso um ticket comum.",
|
||||
"check.ticket_missing": "O arquivo de {input} não tem o ticket de que o conteúdo precisa.",
|
||||
"check.base_not_totk": "O arquivo do jogo base não é The Legend of Zelda: Tears of the Kingdom ({detail}).",
|
||||
"check.base_is_update": "O arquivo do jogo base é uma atualização. Escolha aqui o NSP ou XCI do jogo base (versão 1.0.0) e este arquivo como a atualização.",
|
||||
"check.base_combined": "O arquivo do jogo base contém vários pacotes (jogo base, atualização ou DLC em um só arquivo). Escolha um dump do jogo base que contenha só o jogo.",
|
||||
"check.update_not_nsp": "A atualização precisa ser um arquivo NSP.",
|
||||
"check.update_not_nsp": "A atualização precisa ser um arquivo NSP ou NSZ.",
|
||||
"check.update_is_base": "O arquivo de atualização contém um jogo base. Escolha o NSP da atualização 1.1.0, 1.2.1 ou 1.4.3.",
|
||||
"check.update_not_totk": "O arquivo de atualização não é uma atualização de Tears of the Kingdom ({detail}).",
|
||||
"check.exefs_failed": "O código do jogo no arquivo de {input} falhou nas verificações; veja {log}.",
|
||||
@@ -101,6 +106,8 @@
|
||||
"warn.xbox_prompts_failed": "Não foi possível preparar os ícones de botão do Xbox (código de saída {code}); o jogo mantém os ícones do Switch. Detalhes: {log}",
|
||||
"warn.shaders_skipped": "O driver gráfico travou ao compilar alguns shaders, então {count} ficaram fora da preparação. O jogo os compila quando precisar deles pela primeira vez, e o driver pode travar ali também; uma atualização do driver pode resolver. Detalhes: {log}",
|
||||
"warn.kernel_old": "O Linux {version} não tem o rastreamento de escrita de que o jogo depende (6.7 ou mais novo); o jogo pode rodar muito devagar. Atualizar o sistema resolve.",
|
||||
"warn.dlss_kept": "Já existe uma biblioteca DLSS na pasta de instalação ({path}); a instalação a mantém.",
|
||||
"warn.dlss_download_failed": "Não foi possível baixar o NVIDIA DLSS de {url}: {detail}. O jogo funciona sem ele; a próxima execução da instalação tenta de novo.",
|
||||
|
||||
"ui.title": "Instalação do TotK",
|
||||
"ui.rebuild_title": "Recompilando o jogo para os seus mods",
|
||||
@@ -109,8 +116,8 @@
|
||||
"ui.intro": "Monta uma pasta jogável de Tears of the Kingdom a partir dos seus próprios arquivos do jogo. Isso demora um pouco e só precisa ser feito uma vez.",
|
||||
"ui.notice": "A pasta que esta instalação monta contém o jogo. Ela é só para seu uso pessoal; não compartilhe nem envie para a internet.",
|
||||
"ui.keys_private": "O arquivo de chaves é lido no lugar. O conteúdo dele nunca é mostrado, copiado nem guardado.",
|
||||
"ui.base": "Jogo base (NSP ou XCI)",
|
||||
"ui.update": "Atualização 1.1.0, 1.2.1 ou 1.4.3 (NSP)",
|
||||
"ui.base": "Jogo base (NSP, XCI, NSZ ou XCZ)",
|
||||
"ui.update": "Atualização 1.1.0, 1.2.1 ou 1.4.3 (NSP ou NSZ)",
|
||||
"ui.keys": "prod.keys",
|
||||
"ui.firmware": "Pasta de firmware (opcional)",
|
||||
"ui.game_language": "Idioma do jogo",
|
||||
@@ -122,8 +129,10 @@
|
||||
"ui.pick_firmware": "Escolha a pasta de firmware do Switch (Contents/registered)",
|
||||
"ui.pick_dest": "Escolha a pasta de instalação",
|
||||
"ui.firmware_help": "Para textos em chinês, coreano e chinês tradicional: a pasta Contents/registered do firmware, por exemplo do Ryujinx ou do Yuzu. A instalação copia dela as fontes do sistema do Switch para a pasta de instalação.",
|
||||
"ui.type_game": "Arquivos de jogo (NSP, XCI)",
|
||||
"ui.type_nsp": "Arquivos NSP",
|
||||
"ui.dlss": "Baixar o NVIDIA DLSS (nvngx_dlss.dll) para placas de vídeo NVIDIA RTX",
|
||||
"ui.dlss_help": "Do DLSS SDK da NVIDIA no GitHub, sob a licença NVIDIA RTX SDK (github.com/NVIDIA/DLSS). Sem ele as opções dlaa e DLSS não fazem nada.",
|
||||
"ui.compressed_help": "Arquivos compactados NSZ e XCZ funcionam do jeito que estão: a instalação os descompacta sozinha (para isso, até os dados do jogo serem extraídos, ela precisa de tanto espaço livre quanto ocupam os arquivos descompactados).",
|
||||
"ui.type_game": "Arquivos de jogo (NSP, XCI, NSZ, XCZ)",
|
||||
"ui.type_keys": "Arquivos de chaves (*.keys)",
|
||||
"ui.type_all": "Todos os arquivos",
|
||||
"ui.start": "Iniciar",
|
||||
|
||||
+15
-6
@@ -5,12 +5,15 @@
|
||||
"step.lift": "Перевод кода игры в C",
|
||||
"step.compile": "Сборка {game_exe}",
|
||||
"step.prepare": "Подготовка шейдеров",
|
||||
"step.dlss": "Загрузка NVIDIA DLSS",
|
||||
"step.cleanup": "Уборка",
|
||||
|
||||
"phase.check.host": "Проверка процессора и видеокарты",
|
||||
"phase.check.verify": "Проверка уже установленных файлов",
|
||||
"phase.check.verify_romfs": "Проверка установленных данных игры",
|
||||
"phase.check.inputs": "Открытие игровых файлов с помощью ключей",
|
||||
"phase.check.unpack_base": "Распаковка сжатой базовой игры",
|
||||
"phase.check.unpack_update": "Распаковка сжатого обновления",
|
||||
"phase.check.versions": "Проверка версий игры",
|
||||
"phase.fonts.extract": "Извлечение общих шрифтов из прошивки",
|
||||
"phase.fonts.copy": "Копирование общих шрифтов в папку установки",
|
||||
@@ -30,6 +33,7 @@
|
||||
"phase.prepare.translate": "Трансляция шейдеров",
|
||||
"phase.prepare.libraries": "Сборка шейдеров для драйвера видеокарты",
|
||||
"phase.prepare.pack": "Упаковка кэша шейдеров в один файл",
|
||||
"phase.dlss.download": "Загрузка nvngx_dlss.dll из DLSS SDK от NVIDIA",
|
||||
"phase.cleanup.ini": "Запись totk.ini",
|
||||
"phase.cleanup.temp": "Удаление временных файлов",
|
||||
|
||||
@@ -58,14 +62,15 @@
|
||||
"check.keys_no_header_key": "В файле ключей нет header_key. Снова получите prod.keys со своей консоли.",
|
||||
"check.keys_wrong": "Эти ключи не открывают файл «{input}». Используйте prod.keys со своей консоли.",
|
||||
"check.keys_too_old": "Файл ключей слишком старый для файла «{input}» (нужно поколение ключей {generation}). Получите prod.keys с консоли с более новой прошивкой.",
|
||||
"check.not_game_file": "Файл «{input}» не является игровым файлом XCI или NSP.",
|
||||
"check.not_game_file": "Файл «{input}» не является игровым файлом XCI, NSP, XCZ или NSZ.",
|
||||
"check.file_unreadable": "Не удаётся прочитать файл «{input}»: {detail}",
|
||||
"check.unpack_failed": "Не удалось распаковать сжатый файл «{input}»: {detail}",
|
||||
"check.ticket_personalized": "В файле «{input}» личный тикет; нужен общий тикет.",
|
||||
"check.ticket_missing": "В файле «{input}» нет тикета, который нужен его содержимому.",
|
||||
"check.base_not_totk": "Файл базовой игры — не The Legend of Zelda: Tears of the Kingdom ({detail}).",
|
||||
"check.base_is_update": "Файл базовой игры — это обновление. Выберите здесь NSP или XCI базовой игры (версия 1.0.0), а этот файл укажите как обновление.",
|
||||
"check.base_combined": "Файл базовой игры содержит несколько пакетов (игра, обновление или DLC в одном файле). Выберите дамп, в котором только базовая игра.",
|
||||
"check.update_not_nsp": "Обновление должно быть файлом NSP.",
|
||||
"check.update_not_nsp": "Обновление должно быть файлом NSP или NSZ.",
|
||||
"check.update_is_base": "Файл обновления содержит базовую игру. Выберите NSP с обновлением 1.1.0 или 1.2.1 или 1.4.3.",
|
||||
"check.update_not_totk": "Файл обновления — не обновление Tears of the Kingdom ({detail}).",
|
||||
"check.exefs_failed": "Код игры из файла «{input}» не прошёл проверку; подробности в {log}.",
|
||||
@@ -101,6 +106,8 @@
|
||||
"warn.xbox_prompts_failed": "Не удалось подготовить подсказки кнопок Xbox (код выхода {code}); игра оставит подсказки Switch. Подробности: {log}",
|
||||
"warn.shaders_skipped": "Графический драйвер аварийно завершился при сборке части шейдеров, поэтому {count} из них пропущены при подготовке. Игра соберёт их, когда они впервые понадобятся, и драйвер может упасть и там; обновление драйвера может это исправить. Подробности: {log}",
|
||||
"warn.kernel_old": "В Linux {version} нет отслеживания записи, на которое опирается игра (нужна версия 6.7 или новее); игра может работать очень медленно. Обновление системы это исправит.",
|
||||
"warn.dlss_kept": "В папке установки уже есть библиотека DLSS ({path}); установка её оставляет.",
|
||||
"warn.dlss_download_failed": "Не удалось загрузить NVIDIA DLSS с {url}: {detail}. Игра работает и без него; при следующем запуске установка попробует снова.",
|
||||
|
||||
"ui.title": "Установка TotK",
|
||||
"ui.rebuild_title": "Пересборка игры под ваши моды",
|
||||
@@ -109,8 +116,8 @@
|
||||
"ui.intro": "Собирает готовую к запуску папку с Tears of the Kingdom из ваших игровых файлов. Занимает много времени и выполняется один раз.",
|
||||
"ui.notice": "Папка, которую создаёт установка, содержит игру. Она предназначена только для личного использования: не передавайте и не выкладывайте её.",
|
||||
"ui.keys_private": "Файл ключей читается на месте. Его содержимое не показывается, не копируется и не сохраняется.",
|
||||
"ui.base": "Базовая игра (NSP или XCI)",
|
||||
"ui.update": "Обновление 1.1.0 или 1.2.1 или 1.4.3 (NSP)",
|
||||
"ui.base": "Базовая игра (NSP, XCI, NSZ или XCZ)",
|
||||
"ui.update": "Обновление 1.1.0 или 1.2.1 или 1.4.3 (NSP или NSZ)",
|
||||
"ui.keys": "prod.keys",
|
||||
"ui.firmware": "Папка прошивки (необязательно)",
|
||||
"ui.game_language": "Язык игры",
|
||||
@@ -122,8 +129,10 @@
|
||||
"ui.pick_firmware": "Выберите папку прошивки Switch (Contents/registered)",
|
||||
"ui.pick_dest": "Выберите папку установки",
|
||||
"ui.firmware_help": "Для текста на китайском и корейском: папка Contents/registered прошивки, например из Ryujinx или Yuzu. Установка скопирует из неё системные шрифты Switch в папку установки.",
|
||||
"ui.type_game": "Файлы игры (NSP, XCI)",
|
||||
"ui.type_nsp": "Файлы NSP",
|
||||
"ui.dlss": "Загрузить NVIDIA DLSS (nvngx_dlss.dll) для видеокарт NVIDIA RTX",
|
||||
"ui.dlss_help": "Из DLSS SDK от NVIDIA на GitHub, по лицензии NVIDIA RTX SDK (github.com/NVIDIA/DLSS). Без него настройки dlaa и DLSS ничего не делают.",
|
||||
"ui.compressed_help": "Сжатые файлы NSZ и XCZ подходят как есть: установка сама их распакует (для этого, пока извлекаются данные игры, нужно столько свободного места, сколько занимают распакованные файлы).",
|
||||
"ui.type_game": "Файлы игры (NSP, XCI, NSZ, XCZ)",
|
||||
"ui.type_keys": "Файлы ключей (*.keys)",
|
||||
"ui.type_all": "Все файлы",
|
||||
"ui.start": "Начать",
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
"step.lift": "将游戏代码转换为 C 代码",
|
||||
"step.compile": "编译 {game_exe}",
|
||||
"step.prepare": "准备着色器",
|
||||
"step.dlss": "下载 NVIDIA DLSS",
|
||||
"step.cleanup": "清理",
|
||||
|
||||
"phase.check.host": "检查处理器和显卡",
|
||||
"phase.check.verify": "检查已安装的文件",
|
||||
"phase.check.verify_romfs": "检查已安装的游戏数据",
|
||||
"phase.check.inputs": "用密钥打开游戏文件",
|
||||
"phase.check.unpack_base": "正在解压压缩的游戏本体",
|
||||
"phase.check.unpack_update": "正在解压压缩的更新",
|
||||
"phase.check.versions": "检查游戏版本",
|
||||
"phase.fonts.extract": "正在从固件中提取共享字体",
|
||||
"phase.fonts.copy": "正在将共享字体复制到安装文件夹",
|
||||
@@ -30,6 +33,7 @@
|
||||
"phase.prepare.translate": "转译着色器",
|
||||
"phase.prepare.libraries": "为显卡驱动构建着色器",
|
||||
"phase.prepare.pack": "将着色器缓存打包成一个文件",
|
||||
"phase.dlss.download": "从 NVIDIA 的 DLSS SDK 下载 nvngx_dlss.dll",
|
||||
"phase.cleanup.ini": "写入 totk.ini",
|
||||
"phase.cleanup.temp": "删除临时文件",
|
||||
|
||||
@@ -58,14 +62,15 @@
|
||||
"check.keys_no_header_key": "密钥文件中没有 header_key。请从你的主机重新导出 prod.keys。",
|
||||
"check.keys_wrong": "这些密钥无法打开{input}文件。请使用你自己主机的 prod.keys。",
|
||||
"check.keys_too_old": "密钥文件对{input}文件来说太旧(需要密钥代数 {generation})。请从固件更新的主机导出 prod.keys。",
|
||||
"check.not_game_file": "{input}文件不是 XCI 或 NSP 游戏文件。",
|
||||
"check.not_game_file": "{input}文件不是 XCI、NSP、XCZ 或 NSZ 游戏文件。",
|
||||
"check.file_unreadable": "无法读取{input}文件:{detail}",
|
||||
"check.unpack_failed": "无法解压压缩的{input}文件:{detail}",
|
||||
"check.ticket_personalized": "{input}文件带有个人化票据(personalized ticket);需要通用票据(common ticket)。",
|
||||
"check.ticket_missing": "{input}文件缺少其内容所需的票据(ticket)。",
|
||||
"check.base_not_totk": "游戏本体文件不是《塞尔达传说 王国之泪》({detail})。",
|
||||
"check.base_is_update": "基础游戏文件是一个更新。请在这里选择基础游戏的 NSP 或 XCI(版本 1.0.0),并把此文件选为更新。",
|
||||
"check.base_combined": "基础游戏文件包含多个内容包(基础游戏、更新或 DLC 合在一个文件中)。请选择只包含游戏本体的基础游戏转储。",
|
||||
"check.update_not_nsp": "更新必须是 NSP 文件。",
|
||||
"check.update_not_nsp": "更新必须是 NSP 或 NSZ 文件。",
|
||||
"check.update_is_base": "更新文件里是游戏本体。请选择 1.1.0、1.2.1 或 1.4.3 的更新 NSP。",
|
||||
"check.update_not_totk": "更新文件不是《王国之泪》的更新({detail})。",
|
||||
"check.exefs_failed": "{input}文件的游戏代码未通过检查;详见 {log}。",
|
||||
@@ -101,6 +106,8 @@
|
||||
"warn.xbox_prompts_failed": "无法准备 Xbox 按键提示(退出代码 {code});游戏将继续使用 Switch 按键提示。详情:{log}",
|
||||
"warn.shaders_skipped": "显卡驱动在编译部分着色器时崩溃,因此有 {count} 个着色器未被预先准备。游戏会在首次需要时编译它们,驱动在那时也可能崩溃;更新驱动可能会解决。详情:{log}",
|
||||
"warn.kernel_old": "Linux {version} 缺少游戏依赖的写入跟踪功能(需要 6.7 或更新版本);游戏可能运行得很慢。更新系统即可解决。",
|
||||
"warn.dlss_kept": "安装文件夹中已有 DLSS 库({path});安装程序将保留它。",
|
||||
"warn.dlss_download_failed": "无法从 {url} 下载 NVIDIA DLSS:{detail}。游戏无需它也能运行;下次运行安装程序时会重试。",
|
||||
|
||||
"ui.title": "TotK 安装程序",
|
||||
"ui.rebuild_title": "正在为你的模组重新构建游戏",
|
||||
@@ -109,8 +116,8 @@
|
||||
"ui.intro": "用你自己的游戏文件构建一个可以游玩的《王国之泪》文件夹。耗时较长,只需运行一次。",
|
||||
"ui.notice": "此安装程序构建的文件夹包含游戏本身,仅供你个人使用;请勿分享或上传。",
|
||||
"ui.keys_private": "密钥文件只在原位置读取,其内容绝不会被显示、复制或保存。",
|
||||
"ui.base": "游戏本体(NSP 或 XCI)",
|
||||
"ui.update": "1.1.0、1.2.1 或 1.4.3 更新(NSP)",
|
||||
"ui.base": "游戏本体(NSP、XCI、NSZ 或 XCZ)",
|
||||
"ui.update": "1.1.0、1.2.1 或 1.4.3 更新(NSP 或 NSZ)",
|
||||
"ui.keys": "prod.keys",
|
||||
"ui.firmware": "固件文件夹(可选)",
|
||||
"ui.game_language": "游戏语言",
|
||||
@@ -122,8 +129,10 @@
|
||||
"ui.pick_firmware": "选择 Switch 固件文件夹(Contents/registered)",
|
||||
"ui.pick_dest": "选择安装文件夹",
|
||||
"ui.firmware_help": "用于中文、韩文和繁体中文文本:固件的 Contents/registered 文件夹,例如来自 Ryujinx 或 Yuzu。安装程序会从中把 Switch 系统字体复制到安装文件夹。",
|
||||
"ui.type_game": "游戏文件(NSP、XCI)",
|
||||
"ui.type_nsp": "NSP 文件",
|
||||
"ui.dlss": "为 NVIDIA RTX 显卡下载 NVIDIA DLSS(nvngx_dlss.dll)",
|
||||
"ui.dlss_help": "来自 GitHub 上 NVIDIA 的 DLSS SDK,遵循 NVIDIA RTX SDK 许可(github.com/NVIDIA/DLSS)。没有它,dlaa 和 DLSS 设置不起作用。",
|
||||
"ui.compressed_help": "压缩的 NSZ 和 XCZ 文件可以直接使用:安装程序会自行解压(在游戏数据提取完成之前,需要与解压后文件大小相同的可用空间)。",
|
||||
"ui.type_game": "游戏文件(NSP、XCI、NSZ、XCZ)",
|
||||
"ui.type_keys": "密钥文件(*.keys)",
|
||||
"ui.type_all": "所有文件",
|
||||
"ui.start": "开始",
|
||||
|
||||
+22
-17
@@ -56,6 +56,12 @@ static bool amiibo_validate_dump(const uint8_t *data, uint64_t size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The 7-byte UID of an NTAG215 dump: bytes 0..2, then 4..7. Byte 3 is the check byte BCC0.
|
||||
static void copy_uid(uint8_t out[7], const uint8_t *dump) {
|
||||
memcpy(out, dump, 3);
|
||||
memcpy(out + 3, dump + 4, 4);
|
||||
}
|
||||
|
||||
static void parse_dump_info(const char *path) {
|
||||
memset(&g_info, 0, sizeof(g_info));
|
||||
if (path) {
|
||||
@@ -69,15 +75,15 @@ static void parse_dump_info(const char *path) {
|
||||
snprintf(g_info.filename, sizeof(g_info.filename), "%s", name);
|
||||
}
|
||||
|
||||
// UID: bytes 0..6 (7 bytes) or up to 9
|
||||
memcpy(g_info.uuid, g_dump, 7);
|
||||
copy_uid(g_info.uuid, g_dump);
|
||||
g_info.uuid_len = 7;
|
||||
|
||||
// Amiibo ID at offset 0x54 (8 bytes)
|
||||
// Amiibo ID at offset 0x54 (8 bytes, 3dbrew "Amiibo"): character ID and variant, figure type, model number
|
||||
// (big-endian), series, format version 02
|
||||
memcpy(g_info.character_id, g_dump + 0x54, 3);
|
||||
g_info.series_id = g_dump[0x57];
|
||||
g_info.nfp_type = g_dump[0x57];
|
||||
g_info.numbering_id = ((uint16_t)g_dump[0x58] << 8) | g_dump[0x59];
|
||||
g_info.nfp_type = g_dump[0x5A];
|
||||
g_info.series_id = g_dump[0x5A];
|
||||
g_info.version = g_dump[0x5B];
|
||||
}
|
||||
|
||||
@@ -321,7 +327,7 @@ void amiibo_get_tag_info(void *out_tag_info) {
|
||||
uint8_t *out = (uint8_t *)out_tag_info;
|
||||
memset(out, 0, 0x58);
|
||||
// UUID: offset 0x00, 10 bytes max
|
||||
memcpy(out + 0x00, g_dump, 7);
|
||||
copy_uid(out, g_dump);
|
||||
out[0x0A] = 7; // UUID length
|
||||
// Protocol at offset 0x20: TypeA = 1
|
||||
*(uint32_t *)(out + 0x20) = 1;
|
||||
@@ -334,15 +340,14 @@ void amiibo_get_model_info(void *out_model_info) {
|
||||
plat_mutex_lock(&g_amiibo_lock);
|
||||
uint8_t *out = (uint8_t *)out_model_info;
|
||||
memset(out, 0, 0x40);
|
||||
// Character ID at 0x00 (3 bytes)
|
||||
memcpy(out + 0x00, g_dump + 0x54, 3);
|
||||
// Series ID at 0x03 (1 byte)
|
||||
out[0x03] = g_dump[0x57];
|
||||
// Numbering ID at 0x04 (2 bytes, big-endian from 0x58)
|
||||
out[0x04] = g_dump[0x58];
|
||||
out[0x05] = g_dump[0x59];
|
||||
// NFP Type at 0x06 (1 byte)
|
||||
out[0x06] = g_dump[0x5A];
|
||||
// nn::nfp::ModelInfo from the amiibo ID at 0x54: character ID and variant in the tag's byte order, series, model
|
||||
// number little-endian (big-endian on the tag), figure type. The game tells figures of one character apart by the
|
||||
// model number.
|
||||
memcpy(out + 0x00, g_dump + 0x54, 3); // character ID, variant
|
||||
out[0x03] = g_dump[0x5A]; // series
|
||||
out[0x04] = g_dump[0x59]; // model number
|
||||
out[0x05] = g_dump[0x58];
|
||||
out[0x06] = g_dump[0x57]; // figure type
|
||||
plat_mutex_unlock(&g_amiibo_lock);
|
||||
}
|
||||
|
||||
@@ -351,7 +356,7 @@ void amiibo_get_common_info(void *out_common_info) {
|
||||
uint8_t *out = (uint8_t *)out_common_info;
|
||||
memset(out, 0, 0x40);
|
||||
// Last write date: 0x00..0x03
|
||||
out[0x00] = 0x07; out[0x01] = 0xE7; // 2023
|
||||
out[0x00] = 0xE7; out[0x01] = 0x07; // 2023, u16 little-endian
|
||||
out[0x02] = 0x05; out[0x03] = 0x0C; // May 12
|
||||
// Write counter: 0x04..0x05
|
||||
out[0x04] = 0x00; out[0x05] = 0x01;
|
||||
@@ -369,7 +374,7 @@ void amiibo_get_register_info(void *out_register_info) {
|
||||
// Mii CharInfo at 0x00..0x57 (dummy valid mii)
|
||||
out[0x00] = 0x03; // version 3
|
||||
// First write date at 0x58..0x5B
|
||||
out[0x58] = 0x07; out[0x59] = 0xE7;
|
||||
out[0x58] = 0xE7; out[0x59] = 0x07; // 2023, u16 little-endian
|
||||
out[0x5A] = 0x05; out[0x5B] = 0x0C;
|
||||
// Amiibo Name at 0x5C: copy up to 40 bytes NUL-terminated
|
||||
const char *name = g_info.filename[0] ? g_info.filename : "Amiibo";
|
||||
|
||||
+84
-42
@@ -5,8 +5,9 @@
|
||||
//
|
||||
// Per 5 ms frame the render thread (audren.c, renderer lock held) decodes every started voice (PCM8/16/24/32, float,
|
||||
// DSP-ADPCM) along the sample path the voice clock consumes, resamples it linearly, runs the voice biquads, scales it
|
||||
// into its destination mix, folds the submixes, runs the mix effects and writes the device sink's stereo mix into a
|
||||
// single-producer single-consumer ring. The SDL audio thread drains that ring without taking any lock: an empty ring
|
||||
// into its destination mix, folds the submixes, runs the mix effects and writes the device sink's mix (stereo, or 5.1
|
||||
// with TOTK_AUDIO_CHANNELS, output_channels) into a single-producer single-consumer ring. The SDL audio thread drains
|
||||
// that ring without taking any lock: an empty ring
|
||||
// plays silence and never blocks the render thread or the guest. TOTK_AUDIO=none (or TOTK_AUDIO_OFF) mixes without a
|
||||
// device; TOTK_AUDIO_PREFILL_MS (0..500, default 20, 60 on Linux) is the queued audio the output waits for after start or underrun.
|
||||
// The device's clock paces the render thread, as the audio hardware paces the Switch's DSP (audio_mixer_pace_ns): a
|
||||
@@ -32,7 +33,8 @@
|
||||
#include "ipc.h"
|
||||
|
||||
#define MAX_FRAME 1024
|
||||
#define RING_FRAMES 16384 // stereo frames, about 340 ms at 48 kHz
|
||||
#define RING_FRAMES 16384 // output frames, about 340 ms at 48 kHz
|
||||
#define OUT_MAX 6 // output channels at most: 5.1
|
||||
#define QUEUE_SLACK_MS 40 // queued audio above the target plus the device's request that ring_pull skips
|
||||
#define STALL_MS 50 // the device took nothing this long (at least 4 periods): the timer paces
|
||||
#define SPLITTER_UNUSED 0xffffffffu
|
||||
@@ -51,7 +53,8 @@ typedef struct Mixer {
|
||||
int *offset; // first mix buffer of each mix id
|
||||
SplitInfo *split; int nsplit; SplitDest *dest; int ndest;
|
||||
SDL_AudioStream *stream; int device_ok, rate;
|
||||
float ring[RING_FRAMES * 2];
|
||||
int channels; // output channels, 2 or 6 (output_channels)
|
||||
float ring[RING_FRAMES * OUT_MAX];
|
||||
_Atomic uint64_t wr, rd, underruns, overruns, missing_frames, callback_frames, callback_nonzero, skipped_frames;
|
||||
_Atomic int playing; int ready; // ready: the callback's own state
|
||||
int target, period; // output target and device period, frames (output_open)
|
||||
@@ -61,7 +64,7 @@ typedef struct Mixer {
|
||||
EffectState *effect; int *effect_order; unsigned effect_reported;
|
||||
uint64_t *sink_address; uint32_t *sink_size, *sink_offset, *sink_old, *sink_written;
|
||||
unsigned reported; // one-time reports
|
||||
float voice_scratch[6][MAX_FRAME], filter_scratch[MAX_FRAME], output_scratch[MAX_FRAME * 2];
|
||||
float voice_scratch[6][MAX_FRAME], filter_scratch[MAX_FRAME], output_scratch[MAX_FRAME * OUT_MAX];
|
||||
double level_sum, level_peak; uint64_t level_count, level_frames; int first_sound_logged;
|
||||
int prefill; // render blocks
|
||||
} Mixer;
|
||||
@@ -105,8 +108,32 @@ static int prefill_blocks_for(const char *e) {
|
||||
return (int)ceil(ms / 5.0);
|
||||
}
|
||||
|
||||
// Fills `frames` stereo frames from the ring (zeros for what is missing). Lock-free; runs on SDL's audio thread.
|
||||
// TOTK_AUDIO_CHANNELS: the output channels of this run, decided once. stereo gives 2; 5.1 gives 6 (FL FR C LFE RL RR,
|
||||
// the device sink's order and SDL's); auto gives 6 when the default playback device has at least six channels (a 5.1
|
||||
// or 7.1 speaker setup in Windows; SDL places 5.1 on 7.1) and 2 otherwise. IAudioDevice reports the same count to the
|
||||
// game (GetActiveChannelCount), which picks its sink layout from it. TOTK_AUDIO=none: 2, without touching SDL.
|
||||
static int output_channels(void) {
|
||||
static _Atomic int decided;
|
||||
int n = atomic_load(&decided);
|
||||
if (n) return n;
|
||||
const char *want = cfg_value("TOTK_AUDIO_CHANNELS"), *mode = cfg_get("TOTK_AUDIO");
|
||||
if ((mode && !strcmp(mode, "none")) || cfg_get("TOTK_AUDIO_OFF")) n = 2;
|
||||
else if (!strcmp(want, "5.1")) n = 6;
|
||||
else if (!strcmp(want, "stereo")) n = 2;
|
||||
else {
|
||||
SDL_AudioSpec dev = {0};
|
||||
n = SDL_InitSubSystem(SDL_INIT_AUDIO) && SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &dev, NULL) &&
|
||||
dev.channels >= 6 ? 6 : 2;
|
||||
rt_log("audio: TOTK_AUDIO_CHANNELS=auto: the default device has %d channels, output %s", dev.channels,
|
||||
n == 6 ? "5.1" : "stereo");
|
||||
}
|
||||
int expected = 0;
|
||||
return atomic_compare_exchange_strong(&decided, &expected, n) ? n : expected;
|
||||
}
|
||||
|
||||
// Fills `frames` output frames from the ring (zeros for what is missing). Lock-free; runs on SDL's audio thread.
|
||||
static void ring_pull(Mixer *m, float *out, uint32_t frames) {
|
||||
const int oc = m->channels;
|
||||
atomic_fetch_add(&m->callback_frames, frames);
|
||||
unsigned epoch = atomic_load_explicit(&m->stop_epoch, memory_order_acquire);
|
||||
uint64_t rd = atomic_load(&m->rd), wr = atomic_load_explicit(&m->wr, memory_order_acquire), have = wr - rd;
|
||||
@@ -121,7 +148,7 @@ static void ring_pull(Mixer *m, float *out, uint32_t frames) {
|
||||
m->ready = 0;
|
||||
}
|
||||
if (!atomic_load_explicit(&m->playing, memory_order_acquire)) {
|
||||
memset(out, 0, (size_t)frames * 2 * sizeof(float));
|
||||
memset(out, 0, (size_t)frames * oc * sizeof(float));
|
||||
m->ready = 0;
|
||||
atomic_store_explicit(&m->rd, wr, memory_order_release);
|
||||
return;
|
||||
@@ -131,7 +158,7 @@ static void ring_pull(Mixer *m, float *out, uint32_t frames) {
|
||||
if (prefill > RING_FRAMES) prefill = RING_FRAMES;
|
||||
if (!m->ready) {
|
||||
if (have < prefill) {
|
||||
memset(out, 0, (size_t)frames * 2 * sizeof(float));
|
||||
memset(out, 0, (size_t)frames * oc * sizeof(float));
|
||||
return;
|
||||
}
|
||||
m->ready = 1;
|
||||
@@ -147,13 +174,16 @@ static void ring_pull(Mixer *m, float *out, uint32_t frames) {
|
||||
uint64_t nonzero = 0;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
uint64_t k = (rd + i) % RING_FRAMES;
|
||||
out[2 * i] = m->ring[2 * k];
|
||||
out[2 * i + 1] = m->ring[2 * k + 1];
|
||||
if (out[2 * i] != 0 || out[2 * i + 1] != 0) nonzero++;
|
||||
int sound = 0;
|
||||
for (int c = 0; c < oc; c++) {
|
||||
out[oc * i + c] = m->ring[oc * k + c];
|
||||
sound |= out[oc * i + c] != 0;
|
||||
}
|
||||
nonzero += sound;
|
||||
}
|
||||
atomic_fetch_add(&m->callback_nonzero, nonzero);
|
||||
if (n < frames) {
|
||||
memset(out + 2 * n, 0, (size_t)(frames - n) * 2 * sizeof(float));
|
||||
memset(out + (size_t)oc * n, 0, (size_t)(frames - n) * oc * sizeof(float));
|
||||
atomic_fetch_add(&m->underruns, 1);
|
||||
atomic_fetch_add(&m->missing_frames, frames - n);
|
||||
m->ready = 0;
|
||||
@@ -164,15 +194,16 @@ static void ring_pull(Mixer *m, float *out, uint32_t frames) {
|
||||
static void SDLCALL sdl_pull(void *user, SDL_AudioStream *stream, int additional, int total) {
|
||||
(void)total;
|
||||
Mixer *m = user;
|
||||
float tmp[2 * 1024];
|
||||
float tmp[OUT_MAX * 1024];
|
||||
const uint32_t frame_bytes = (uint32_t)m->channels * sizeof(float);
|
||||
atomic_store_explicit(&m->pulled_ns, kern_now_ns(), memory_order_release);
|
||||
while (additional > 0) {
|
||||
uint32_t frames = (uint32_t)additional / (2 * sizeof(float));
|
||||
uint32_t frames = (uint32_t)additional / frame_bytes;
|
||||
if (!frames) break;
|
||||
if (frames > 1024) frames = 1024;
|
||||
ring_pull(m, tmp, frames);
|
||||
if (!SDL_PutAudioStreamData(stream, tmp, (int)(frames * 2 * sizeof(float)))) break;
|
||||
additional -= (int)(frames * 2 * sizeof(float));
|
||||
if (!SDL_PutAudioStreamData(stream, tmp, (int)(frames * frame_bytes))) break;
|
||||
additional -= (int)(frames * frame_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +217,7 @@ static void output_open(Mixer *m, int rate) {
|
||||
rt_log("audio: SDL audio init failed (%s): mixing without sound", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
SDL_AudioSpec spec = {SDL_AUDIO_F32, 2, rate};
|
||||
SDL_AudioSpec spec = {SDL_AUDIO_F32, m->channels, rate};
|
||||
m->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, sdl_pull, m);
|
||||
if (!m->stream) {
|
||||
rt_log("audio: no output device (%s): mixing without sound", SDL_GetError());
|
||||
@@ -199,7 +230,7 @@ static void output_open(Mixer *m, int rate) {
|
||||
return;
|
||||
}
|
||||
m->device_ok = 1;
|
||||
SDL_AudioSpec dev;
|
||||
SDL_AudioSpec dev = {0};
|
||||
int period = 0;
|
||||
if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(m->stream), &dev, &period) || period <= 0) period = m->frame;
|
||||
m->period = period;
|
||||
@@ -208,8 +239,9 @@ static void output_open(Mixer *m, int rate) {
|
||||
uint64_t period_ns = (uint64_t)period * 1000000000ull / (uint64_t)rate;
|
||||
m->stall_ns = 4 * period_ns > STALL_MS * 1000000ull ? 4 * period_ns : STALL_MS * 1000000ull;
|
||||
const char *name = SDL_GetAudioDeviceName(SDL_GetAudioStreamDevice(m->stream));
|
||||
rt_log("audio: SDL device '%s' opened, %d Hz stereo float, prefill %d ms, device period %d frames; the device paces "
|
||||
"rendering at %.1f ms queued", name ? name : "?", rate, m->prefill * 5, period, m->target * 1000.0 / rate);
|
||||
rt_log("audio: SDL device '%s' opened, %d Hz %s float (device %d channels), prefill %d ms, device period %d frames; "
|
||||
"the device paces rendering at %.1f ms queued", name ? name : "?", rate, m->channels == 6 ? "5.1" : "stereo",
|
||||
dev.channels, m->prefill * 5, period, m->target * 1000.0 / rate);
|
||||
}
|
||||
|
||||
int64_t audio_mixer_pace_ns(AudioRenderer *r, uint64_t now) {
|
||||
@@ -224,7 +256,8 @@ int64_t audio_mixer_pace_ns(AudioRenderer *r, uint64_t now) {
|
||||
}
|
||||
|
||||
// Render thread only.
|
||||
static void output_push(Mixer *m, const float *lr, int frames) {
|
||||
static void output_push(Mixer *m, const float *out, int frames) {
|
||||
const int oc = m->channels;
|
||||
uint64_t wr = atomic_load(&m->wr), rd = atomic_load_explicit(&m->rd, memory_order_acquire);
|
||||
if (m->device_ok && wr + (uint64_t)frames - rd > RING_FRAMES) { // device stalled: drop this frame
|
||||
atomic_fetch_add(&m->overruns, 1);
|
||||
@@ -232,8 +265,7 @@ static void output_push(Mixer *m, const float *lr, int frames) {
|
||||
}
|
||||
for (int i = 0; i < frames; i++) {
|
||||
uint64_t k = (wr + (uint64_t)i) % RING_FRAMES;
|
||||
m->ring[2 * k] = lr[2 * i];
|
||||
m->ring[2 * k + 1] = lr[2 * i + 1];
|
||||
memcpy(&m->ring[oc * k], &out[oc * i], (size_t)oc * sizeof(float));
|
||||
}
|
||||
atomic_store_explicit(&m->wr, wr + (uint64_t)frames, memory_order_release);
|
||||
if (!m->device_ok) atomic_store(&m->rd, wr + (uint64_t)frames); // no device: the ring only keeps the latest frames
|
||||
@@ -268,16 +300,16 @@ static void sink_dump(const float *raw, int frames) {
|
||||
}
|
||||
|
||||
// Level meter (TOTK_TRACE=audio): one line per 5 s of output, and the first nonzero frame.
|
||||
static void level_note(AudioRenderer *r, Mixer *m, const float *lr, int frames) {
|
||||
static void level_note(AudioRenderer *r, Mixer *m, const float *out, int frames) {
|
||||
if (!(rt_trace & RT_TRACE_AUDIO)) return;
|
||||
int sound = 0;
|
||||
for (int k = 0; k < 2 * frames; k++) {
|
||||
double x = lr[k];
|
||||
for (int k = 0; k < m->channels * frames; k++) {
|
||||
double x = out[k];
|
||||
m->level_sum += x * x;
|
||||
if (fabs(x) > m->level_peak) m->level_peak = fabs(x);
|
||||
if (x != 0) sound = 1;
|
||||
}
|
||||
m->level_count += 2 * (uint64_t)frames;
|
||||
m->level_count += (uint64_t)m->channels * (uint64_t)frames;
|
||||
m->level_frames += (uint64_t)frames;
|
||||
if (sound && !m->first_sound_logged) {
|
||||
m->first_sound_logged = 1;
|
||||
@@ -328,6 +360,7 @@ void audio_mixer_open(AudioRenderer *r) {
|
||||
rt_log("audio: TOTK_AUDIO_PREFILL_MS=%s is not 0..500 ms: using %d", pe, PREFILL_DEFAULT_MS);
|
||||
m->prefill = PREFILL_DEFAULT_MS / 5;
|
||||
}
|
||||
m->channels = output_channels();
|
||||
r->mixer = m;
|
||||
atomic_store(&m->playing, r->state == 0);
|
||||
output_open(m, r->param.sample_rate);
|
||||
@@ -846,33 +879,42 @@ void audio_mixer_render(AudioRenderer *r, double seconds) {
|
||||
if (r->sinks[i].type == SINK_DEVICE && !sink) sink = &r->sinks[i];
|
||||
else if (r->sinks[i].type == SINK_CIRCULAR){mix_circular_sink(r,i);circular=1;}
|
||||
} else {m->sink_address[i]=0;m->sink_size[i]=m->sink_offset[i]=m->sink_old[i]=m->sink_written[i]=0;}
|
||||
float *lr=m->output_scratch;
|
||||
memset(lr, 0, sizeof(float) * 2 * (size_t)frames);
|
||||
const int oc = m->channels;
|
||||
float *out=m->output_scratch;
|
||||
memset(out, 0, sizeof(float) * (size_t)oc * (size_t)frames);
|
||||
if (sink && sink->input_count >= 1 && sink->input_count <= 6) {
|
||||
if (!(m->reported & (1u << 6))) {
|
||||
m->reported |= 1u << 6;
|
||||
rt_log("audio: device sink has %u inputs, output %s", sink->input_count, oc == 6 ? "5.1" : "stereo");
|
||||
}
|
||||
const float gain = host_volume() * atomic_load(&device_volume) * (r->mixes[0].in_use ? r->mixes[0].volume : 1.0f);
|
||||
const float *ch[6] = {0};
|
||||
float raw[2 * MAX_FRAME];
|
||||
for (uint32_t c = 0; c < sink->input_count; c++) if (sink->inputs[c] >= 0 && sink->inputs[c] < m->nbuf) ch[c] = mixbuf(m, sink->inputs[c]);
|
||||
for (int k = 0; k < frames; k++) {
|
||||
float l, rr;
|
||||
if (sink->input_count >= 6) { /* FL FR C LFE RL RR -> stereo */
|
||||
float s[OUT_MAX] = {0}; /* FL FR C LFE RL RR */
|
||||
if (sink->input_count >= 6 && oc == 6) {
|
||||
for (int c = 0; c < 6; c++) s[c] = ch[c] ? ch[c][k] : 0;
|
||||
} else if (sink->input_count >= 6) { /* 5.1 -> stereo; the LFE is left out, as the Switch's downmix does */
|
||||
float ff = sink->downmix_enabled ? sink->downmix[0] : 1.0f, cf = sink->downmix_enabled ? sink->downmix[1] : 0.7071f, sf = sink->downmix_enabled ? sink->downmix[2] : 0.7071f;
|
||||
float C = ch[2] ? ch[2][k] : 0;
|
||||
l = ff * (ch[0] ? ch[0][k] : 0) + cf * C + sf * (ch[4] ? ch[4][k] : 0);
|
||||
rr = ff * (ch[1] ? ch[1][k] : 0) + cf * C + sf * (ch[5] ? ch[5][k] : 0);
|
||||
} else if (sink->input_count >= 2) { l = ch[0] ? ch[0][k] : 0; rr = ch[1] ? ch[1][k] : 0; }
|
||||
else l = rr = ch[0] ? ch[0][k] : 0;
|
||||
l *= gain; rr *= gain;
|
||||
raw[2 * k] = l; raw[2 * k + 1] = rr;
|
||||
lr[2 * k] = l > 1 ? 1 : l < -1 ? -1 : l; lr[2 * k + 1] = rr > 1 ? 1 : rr < -1 ? -1 : rr;
|
||||
s[0] = ff * (ch[0] ? ch[0][k] : 0) + cf * C + sf * (ch[4] ? ch[4][k] : 0);
|
||||
s[1] = ff * (ch[1] ? ch[1][k] : 0) + cf * C + sf * (ch[5] ? ch[5][k] : 0);
|
||||
} else if (sink->input_count >= 2) { s[0] = ch[0] ? ch[0][k] : 0; s[1] = ch[1] ? ch[1][k] : 0; }
|
||||
else s[0] = s[1] = ch[0] ? ch[0][k] : 0;
|
||||
raw[2 * k] = s[0] * gain; raw[2 * k + 1] = s[1] * gain; /* TOTK_AUDIO_DUMP: the front pair before clipping */
|
||||
for (int c = 0; c < oc; c++) {
|
||||
float x = s[c] * gain;
|
||||
out[oc * k + c] = x > 1 ? 1 : x < -1 ? -1 : x;
|
||||
}
|
||||
}
|
||||
sink_dump(raw, frames);
|
||||
} else if (!sink && !circular) report_once(m, 5, "no device sink in use", r->param.sink_count);
|
||||
level_note(r, m, lr, frames);
|
||||
output_push(m, lr, frames);
|
||||
level_note(r, m, out, frames);
|
||||
output_push(m, out, frames);
|
||||
}
|
||||
|
||||
// ---- IAudioDevice: one stereo output device
|
||||
// ---- IAudioDevice: one output device, stereo or 5.1 (output_channels)
|
||||
static const char device_name[] = "AudioTvOutput";
|
||||
|
||||
static void put_name(const IpcBuffer *b, IpcResponse *rs, int pointer) {
|
||||
@@ -911,7 +953,7 @@ void audio_device_handler(IpcSession *s, const IpcRequest *rq, IpcResponse *rs)
|
||||
put_name(&b, rs, pointer);
|
||||
return;
|
||||
}
|
||||
case 5: { int32_t n = 2; IPC_OUT(rs, n); return; } // GetActiveChannelCount
|
||||
case 5: { int32_t n = output_channels(); IPC_OUT(rs, n); return; } // GetActiveChannelCount
|
||||
case 4: case 11: case 12: { // QueryAudioDeviceSystemEvent, ...InputEvent, ...OutputEvent
|
||||
uint32_t ev = __atomic_load_n(&change_event, __ATOMIC_ACQUIRE);
|
||||
if (!ev) {
|
||||
|
||||
+23
-13
@@ -30,20 +30,22 @@ static int save_ok = 1; // the last cfg_save_to wrote (o
|
||||
// Every name the runtime reads (and the patch sets in generated/), without the prefix. A file key outside this list is
|
||||
// logged as unknown. A new variable goes here too, or its ini key logs a harmless "unknown key" line.
|
||||
static const char *const known[] = {
|
||||
"AB", "AMIIBO_DIR", "ANISO", "APP_POOL", "ASPECT", "ASPECT_PATCH", "ASSUME_FOCUS", "ASTC", "ASTC_BC", "ASTC_TIME", "ASYNC_PREEMPT", "AUDIO", "AUDIO_OFF", "AUDIO_PREFILL_MS",
|
||||
"AB", "AMIIBO_DIR", "ANISO", "APP_POOL", "ASPECT", "ASPECT_PATCH", "ASSUME_FOCUS", "ASTC", "ASTC_BC", "ASTC_TIME", "ASYNC_PREEMPT", "AUDIO", "AUDIO_CHANNELS", "AUDIO_OFF", "AUDIO_PREFILL_MS",
|
||||
"BACKGROUND_WINDOW", "BIND_A", "BIND_B", "BIND_DOWN", "BIND_L", "BIND_LCLICK", "BIND_LEFT", "BIND_LSTICK_DOWN", "BIND_LSTICK_LEFT",
|
||||
"BIND_LSTICK_RIGHT", "BIND_LSTICK_UP", "BIND_MINUS",
|
||||
"BIND_PLUS", "BIND_R", "BIND_RCLICK", "BIND_RIGHT", "BIND_RSTICK_DOWN", "BIND_RSTICK_LEFT", "BIND_RSTICK_RIGHT", "BIND_RSTICK_UP",
|
||||
"BIND_SKIP", "BIND_STATS", "BIND_UP", "BIND_X", "BIND_Y",
|
||||
"BIND_ZL", "BIND_ZR", "BLOCK_VIEWS_3D", "CLOTH_REALTIME", "COLOR_BRIGHTNESS", "COLOR_CONTRAST", "COLOR_GAMMA",
|
||||
"COLOR_PRESET", "COLOR_SATURATION", "CONTACT_DEBUG", "CONTACT_SHADOWS", "CORE_DIAG", "CRIT", "CRIT_OUT",
|
||||
"DESC_TEMPLATE", "DEVICE_PROFILE", "DLAA", "DLAA_JITTER", "DLAA_MV", "DLSS", "DUMP", "DUMP_BUF", "DUMP_DIR",
|
||||
"DESC_TEMPLATE", "DEVICE_PROFILE", "DISCORD_APP_ID", "DISCORD_IMAGE", "DISCORD_RPC", "DLAA", "DLAA_JITTER", "DLAA_MV", "DLAA_MASKDEBUG", "DLAA_MVDEBUG", "DLAA_OBJMV", "DLAA_SURVEY", "DLAA_TIMES", "DLSS", "DLSS_CHAR_MASK", "DLSS_PRESET", "DLSS_REACTIVE", "DUMP", "DUMP_BUF", "DUMP_DIR",
|
||||
"DUMP_LOG", "EXEFS", "FEEDBACK", "FIX_C0", "FIX_ZERO", "FLIGHT", "FLIGHT_AT", "FLIGHT_DIR", "FLIGHT_FRAMES",
|
||||
"FLIGHT_SPIKE_MS", "FLIGHT_THUMBS", "FOV", "FOV_PATCH", "FPS60", "FPS_LIMIT", "FREEZE_IDLE", "FREE_CORES",
|
||||
"FULLSCREEN", "FXAA", "GPL", "GPL_DRAW_BUILD", "GPU", "GPU_INDEX", "GPU_NAME", "HDR", "HDR_GAME_NITS",
|
||||
"FULLSCREEN", "FXAA", "GPL", "GPL_DRAW_BUILD", "GPU", "GPU_INDEX", "GPU_NAME", "HDR", "HDR_GAME_NITS", "HDR_HUE",
|
||||
"HDR_PEAK_NITS", "HDR_UI_NITS", "HIGHFPS_FIX", "INPUT", "INPUT_RECORD", "INPUT_SCRIPT", "LANG", "LIB_PREBUILD",
|
||||
"LIB_PREBUILD_KEEP", "LIB_PREBUILD_THREADS", "LOCALE", "LOD_GUARD", "LSTICK_DEADZONE", "MEMORY", "MENU_LANG", "MENU_TEST",
|
||||
"MENU_TEST_DIR", "MENU_TEST_SET", "MINIMAL_LAYOUT", "MIRROR_GAP", "MODS_DIR", "MODS_DISABLED", "MODS_ORDER", "MODS_REBUILD", "MOTION", "MOUSE_SENS", "MOVIE_HOST", "MOVIE_PACE",
|
||||
"MVOBJ_ALL", "MVOBJ_BANKS", "MVOBJ_EQUAL", "MVOBJ_EXACT", "MVOBJ_EXEC", "MVOBJ_NOC4", "MVOBJ_ONLY", "MVOBJ_TIMES", "MVPREV_CHECK", "MVPREV_GROW", "MVPREV_SKIP", "MVPREV_TRUST", "MV_CHECK",
|
||||
"MV_SURVEY",
|
||||
"ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PASS_CRUMBS", "PATCH_OUTPUT", "PATCH_SCENE", "PCSS", "PCSS_K", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT",
|
||||
"PREWARM_XLATE", "PROBE_MOVIE", "PROBE_MOVIE_SHOT", "PROFILE", "PROFILE_CHAINS", "PROFILE_DIR", "PROFILE_FRAMES",
|
||||
"PROFILE_MAIN", "PROFILE_RENDER", "PROMPTS", "PUSH_SKIP", "PUSH_STAGE", "RDOC", "RDOC_DIR", "RDOC_DLL", "REALTIME", "REC_END", "REEXEC",
|
||||
@@ -55,7 +57,7 @@ static const char *const known[] = {
|
||||
"SWKBD_ANSWER",
|
||||
"SWKBD_TEST", "TEST_PAD", "TEST_PREPARE_SHADERS", "TEX_ALIAS", "TEX_MEMO", "TEX_VRAM_PRESSURE", "TIMER_1MS", "TRACE", "TXD_TRANSPOSE", "UBO_SNAPSHOT", "UPSCALER", "VK_DYN_A2C", "VK_DYN_VERTEX", "VK_NO_FLOAT64",
|
||||
"VK_PRESENT", "VK_RECORDER", "VK_SSBO_ALIGN", "VK_SUBGROUP", "VK_SUBGROUP_PIN", "VK_VALIDATION", "VOLUME", "VRAM_HEADROOM", "VS_OUTS",
|
||||
"WALK_KEY", "WALK_SCALE", "WB_EXACT", "WB_FENCE_LIMIT", "WINDOW_SIZE", "XLATE", "XLATE_ID", "XLATE_PROCS",
|
||||
"WALK_KEY", "WALK_SCALE", "WB_EXACT", "WB_FENCE_LIMIT", "WINDOW_SIZE", "XLATE", "XLATE_ID", "XLATE_KEEP", "XLATE_PROCS",
|
||||
"ZSTD_CHECK", "ZSTD_NATIVE",
|
||||
};
|
||||
|
||||
@@ -69,14 +71,15 @@ static const struct { const char *key, *section; } sections[] = {
|
||||
{"COLOR_PRESET", "display"},
|
||||
{"COLOR_BRIGHTNESS", "display"}, {"COLOR_CONTRAST", "display"}, {"COLOR_SATURATION", "display"},
|
||||
{"COLOR_GAMMA", "display"}, {"HDR", "display"}, {"HDR_PEAK_NITS", "display"}, {"HDR_GAME_NITS", "display"},
|
||||
{"HDR_UI_NITS", "display"}, {"SHADOW_RES", "display"},
|
||||
{"DLAA", "display"}, {"DLSS", "display"}, {"FXAA", "display"},
|
||||
{"HDR_UI_NITS", "display"}, {"HDR_HUE", "display"}, {"SHADOW_RES", "display"},
|
||||
{"DLAA", "display"}, {"DLSS", "display"}, {"DLSS_PRESET", "display"}, {"DLSS_CHAR_MASK", "display"}, {"FXAA", "display"},
|
||||
{"DEVICE_PROFILE", "display"}, {"ASSUME_FOCUS", "display"},
|
||||
{"DISCORD_RPC", "discord"}, {"DISCORD_APP_ID", "discord"}, {"DISCORD_IMAGE", "discord"},
|
||||
{"SHADOW_SCALE", "display"}, {"SHADOW_RANGE", "display"}, {"CONTACT_SHADOWS", "display"},
|
||||
{"PCSS", "display"},
|
||||
{"MODS_DIR", "mods"}, {"MODS_ORDER", "mods"}, {"MODS_DISABLED", "mods"}, {"MODS_REBUILD", "mods"},
|
||||
{"MENU_LANG", "language"}, {"LANG", "language"}, {"OPERATION_MODE", "language"},
|
||||
{"VOLUME", "audio"},
|
||||
{"VOLUME", "audio"}, {"AUDIO_CHANNELS", "audio"},
|
||||
{"MOTION", "input"}, {"RUMBLE_STRENGTH", "input"}, {"RUMBLE_CURVE", "input"}, {"RUMBLE_TRIGGERS", "input"},
|
||||
{"PAD_LAYOUT", "input"}, {"PROMPTS", "input"}, {"STICK_DEADZONE", "input"}, {"LSTICK_DEADZONE", "input"}, {"RSTICK_DEADZONE", "input"},
|
||||
{"STICK_SENS", "input"},
|
||||
@@ -97,6 +100,7 @@ static const struct { const char *key, *section; } sections[] = {
|
||||
{"VK_DYN_VERTEX", "performance"}, {"VK_DYN_A2C", "performance"}, {"VK_SUBGROUP_PIN", "performance"},
|
||||
{"TRACE", "diagnostics"}, {"FLIGHT", "diagnostics"}, {"PASS_CRUMBS", "diagnostics"}, {"FLIGHT_SPIKE_MS", "diagnostics"},
|
||||
{"WB_FENCE_LIMIT", "diagnostics"}, {"WB_EXACT", "diagnostics"},
|
||||
{"MV_SURVEY", "diagnostics"},
|
||||
};
|
||||
|
||||
// The settings the menu shows: the value in effect when the key is unset, and what the key accepts. The only place a
|
||||
@@ -111,7 +115,9 @@ static const CfgMeta metas[] = {
|
||||
{"PERF_OVERLAY", "off", "off|fps|full", LIVE},
|
||||
{"OUTPUT_RES", "1920x1080", NULL, RESTART}, {"RENDER_SCALE", "game", "game|50|67|75|100|125|150|200", RESTART},
|
||||
{"UPSCALER", "fsr", "fsr|bilinear", LIVE}, {"SHARPNESS", "80", "0..100", LIVE},
|
||||
{"DLAA", "0", "0|1", RESTART}, {"DLSS", "dlaa", "dlaa|quality|balanced|performance", RESTART}, {"FXAA", "1", "0|1", LIVE},
|
||||
{"DLAA", "0", "0|1", RESTART}, {"DLSS", "dlaa", "dlaa|quality|balanced|performance|ultra_performance", LIVE},
|
||||
{"DLSS_PRESET", "default", "default|k|j", LIVE},
|
||||
{"DLSS_CHAR_MASK", "65", "0|35|65|100", LIVE}, {"FXAA", "1", "0|1", LIVE},
|
||||
{"ASPECT", "fit", "fit|stretch", LIVE}, {"FOV", "100", "70..140", LIVE},
|
||||
{"ANISO", "game", "game|2|4|8|16", RESTART}, // vk_texture.c sampler_features
|
||||
// colour adjustment (vk_color.c); the preset is the menu's, the four values are what the present uses
|
||||
@@ -120,7 +126,7 @@ static const CfgMeta metas[] = {
|
||||
{"COLOR_GAMMA", "1", "0.8..1.2", LIVE},
|
||||
// HDR output (vk_hdr.c): on at start (the tonemap shader is swapped as shaders load); the nits apply at once
|
||||
{"HDR", "0", "0|1", RESTART}, {"HDR_PEAK_NITS", "1000", "400..10000", LIVE}, {"HDR_GAME_NITS", "203", "80..500", LIVE},
|
||||
{"HDR_UI_NITS", "203", "80..500", LIVE},
|
||||
{"HDR_UI_NITS", "203", "80..500", LIVE}, {"HDR_HUE", "80", "0..100", LIVE},
|
||||
{"DEVICE_PROFILE", "auto", "auto|desktop|deck", RESTART},
|
||||
{"SHADOW_RES", "game", "game|256|512|768|1024|1536|2048", RESTART},
|
||||
{"SHADOW_SCALE", "1", "1..4", RESTART},
|
||||
@@ -135,7 +141,7 @@ static const CfgMeta metas[] = {
|
||||
{"OPERATION_MODE", "docked", "docked|handheld", RESTART},
|
||||
{"ASSUME_FOCUS", "1", "0|1", LIVE}, // read on every focus change (ov_system.c)
|
||||
// Sound, Controls, Menu
|
||||
{"VOLUME", "100", "0..100", LIVE},
|
||||
{"VOLUME", "100", "0..100", LIVE}, {"AUDIO_CHANNELS", "stereo", "auto|stereo|5.1", RESTART},
|
||||
{"MOTION", "1", "0|1", LIVE}, {"RUMBLE_STRENGTH", "130", "0..300", LIVE}, {"RUMBLE_CURVE", "0.6", "0.2..1.5", LIVE},
|
||||
{"RUMBLE_TRIGGERS", "0", "0|1", LIVE}, {"PAD_LAYOUT", "auto", "auto|nintendo|xbox", LIVE},
|
||||
{"PROMPTS", "nintendo", "nintendo|xbox", RESTART},
|
||||
@@ -145,16 +151,20 @@ static const CfgMeta metas[] = {
|
||||
{"WALK_KEY", "none", NULL, LIVE}, {"WALK_SCALE", "0.51", "0.2..0.9", LIVE},
|
||||
{"MENU_LANG", "", "en|ru|pt-BR|zh-Hans", LIVE},
|
||||
{"AMIIBO_DIR", "", NULL, LIVE},
|
||||
// Discord Rich Presence (discord_rpc.c): off by default, the Menu tab of the settings menu switches it.
|
||||
{"DISCORD_RPC", "0", "0|1", LIVE},
|
||||
{"DISCORD_APP_ID", "1567232599846293504", NULL, LIVE},
|
||||
{"DISCORD_IMAGE", "totk_512x512", NULL, LIVE},
|
||||
// Advanced: scheduling
|
||||
{"ASYNC_PREEMPT", "1", "0|1|2", RESTART}, {"REPLAY_ASYNC", "0", "0|1", RESTART},
|
||||
{"ASYNC_PREEMPT", "1", "0|1|2", RESTART}, {"REPLAY_ASYNC", "1", "0|1", RESTART},
|
||||
{"FREE_CORES", "0", "0|1", RESTART}, {"TIMER_1MS", "1", "0|1", RESTART},
|
||||
// shaders and pipelines
|
||||
{"PIPE_WAIT", "default", "default|draw", RESTART}, {"PIPE_SKIP", "fullscreen", "strict|any|fullscreen", RESTART},
|
||||
{"LIB_PREBUILD", "1", "0|1", RESTART}, {"LIB_PREBUILD_THREADS", "", "1..64", RESTART},
|
||||
{"PIPE_PREBUILD", "1", "0|1", RESTART}, {"PREWARM_XLATE", "", "1..64", RESTART}, {"GPL", "", "0|1", RESTART},
|
||||
{"PIPE_PREBUILD", "1", "0|1", RESTART}, {"PREWARM_XLATE", "", "1..64", RESTART}, {"GPL", "1", "0|1", RESTART},
|
||||
// textures and memory
|
||||
{"ASTC", "auto", "auto|gpu|native|cpu", RESTART}, {"MEMORY", "auto", "auto|import|mirror|verify", RESTART},
|
||||
{"TEX_ALIAS", "1", "0|1", RESTART}, {"TEX_VRAM_PRESSURE", "90", "0..100", RESTART}, {"ASTC_BC", "0", "0|1", RESTART}, {"ZSTD_NATIVE", "1", "0|1", RESTART}, {"ZSTD_CHECK", "0", "0|1", RESTART},
|
||||
{"TEX_ALIAS", "1", "0|1", RESTART}, {"TEX_VRAM_PRESSURE", "90", "0..100", RESTART}, {"ASTC_BC", "", "0|1", RESTART}, {"ZSTD_NATIVE", "1", "0|1", RESTART}, {"ZSTD_CHECK", "0", "0|1", RESTART},
|
||||
// render fixes
|
||||
{"LOD_GUARD", "1", "0|1", RESTART}, {"FIX_C0", "1", "0|1", RESTART}, {"FIX_ZERO", "1", "0|1", RESTART},
|
||||
{"BIND_SKIP", "1", "0|1", RESTART}, {"SHADER_MEMO", "1", "0|1", RESTART}, {"PIPE_MEMO", "1", "0|1", RESTART}, {"TEX_MEMO", "1", "0|1", RESTART},
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
// Discord Rich Presence: "Playing TotK Recomp" with the elapsed play time in the player's Discord status, through
|
||||
// the local IPC of a running Discord client (platform.h's plat_ipc_*). Nothing leaves the machine but the activity
|
||||
// itself, and nothing is sent while TOTK_DISCORD_RPC is off (the default: the status is information about the
|
||||
// player's session). TOTK_DISCORD_APP_ID names the application whose name and artwork Discord shows — the default
|
||||
// is the project's own application, registered as "TotK Recomp"; a player who wants their own name or
|
||||
// art registers an application at discord.com/developers and puts its id in totk.ini — and TOTK_DISCORD_IMAGE the
|
||||
// key of its uploaded art asset.
|
||||
//
|
||||
// One host thread owns the connection and never holds a virtual core (kernel.c: host work runs without one). It
|
||||
// re-reads the settings when cfg_version changes (a menu change reaches it within half a second), connects to the
|
||||
// first of Discord's discord-ipc-0..9 pipes that answers, and writes the activity again every 60 s: that write is
|
||||
// the keep-alive, and one that fails closes the connection and tries again 15 s later. Discord clears the status by
|
||||
// itself when the process ends and the pipe closes, so there is no shutdown path. Every answer frame is read and
|
||||
// discarded: unread, they would fill the pipe's buffer and block the client's writes.
|
||||
//
|
||||
// The protocol is the documented IPC one (discord-rpc's src/rpc_connection.h): a frame is <opcode u32 little
|
||||
// endian><length u32><length bytes of JSON>, with Handshake 0, Frame 1, Close 2, Ping 3 and Pong 4. The handshake
|
||||
// (opcode 0) carries {"v":1,"client_id":...} and is answered by a Ready frame (opcode 1); every command,
|
||||
// SET_ACTIVITY here, is a frame (opcode 1) answered by one, and a Ping the client sends is echoed as a Pong. A
|
||||
// client that speaks another numbering (an early arRPC did) answers the handshake with a Close. --probe-discord
|
||||
// runs the same exchange once, without the game and without the setting being on.
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "host.h"
|
||||
#include "platform/platform.h"
|
||||
|
||||
enum { OP_HANDSHAKE = 0, OP_FRAME = 1, OP_CLOSE = 2, OP_PING = 3, OP_PONG = 4 };
|
||||
|
||||
#define APP_ID_CAP 24 // a Discord snowflake: at most 20 digits
|
||||
#define IMAGE_KEY_CAP 64
|
||||
#define ANSWER_CAP 8192 // the Ready frame carries the client's config; a few KiB
|
||||
|
||||
// A settings value that fits the JSON below without escaping: the app id is digits, an asset key letters, digits,
|
||||
// '_' and '-' (what Discord's developer portal accepts for an asset key).
|
||||
static int valid_app_id(const char *s) {
|
||||
size_t n = strlen(s);
|
||||
if (!n || n > 20) return 0;
|
||||
for (const char *p = s; *p; p++)
|
||||
if (*p < '0' || *p > '9') return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int valid_image_key(const char *s) {
|
||||
size_t n = strlen(s);
|
||||
if (!n || n >= IMAGE_KEY_CAP) return 0;
|
||||
for (const char *p = s; *p; p++)
|
||||
if ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z') && (*p < '0' || *p > '9') && *p != '_' && *p != '-')
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The setting's value, or its default when the value does not fit the protocol (logged: the player sees why their
|
||||
// own app does not show). Called on the presence thread and by the probe, after cfg_version changed or once.
|
||||
static void read_setting(const char *name, char *out, size_t cap, int (*valid)(const char *)) {
|
||||
const char *v = cfg_value(name);
|
||||
if (valid(v)) {
|
||||
snprintf(out, cap, "%s", v);
|
||||
return;
|
||||
}
|
||||
snprintf(out, cap, "%s", cfg_meta(name)->def);
|
||||
rt_log("discord: %s does not fit what the Rich Presence protocol takes; using %s", name + strlen("TOTK_"), out);
|
||||
}
|
||||
|
||||
static void le32(uint8_t *at, uint32_t v) {
|
||||
at[0] = (uint8_t)v, at[1] = (uint8_t)(v >> 8), at[2] = (uint8_t)(v >> 16), at[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
|
||||
static int write_frame(PlatIpc *p, uint32_t op, const char *json) {
|
||||
uint8_t head[8];
|
||||
le32(head, op);
|
||||
le32(head + 4, (uint32_t)strlen(json));
|
||||
return plat_ipc_write(p, head, sizeof head) && plat_ipc_write(p, json, strlen(json));
|
||||
}
|
||||
|
||||
// One frame off the wire, its 8-byte header in as many reads as the peer made, then its payload; a payload longer
|
||||
// than cap-1 fills buf with its first cap-1 bytes and the rest is still consumed. *op gets the opcode; buf is
|
||||
// NUL-terminated. 1 on a whole frame, 0 when the deadline passed, -1 when the connection broke.
|
||||
static int read_one_frame(PlatIpc *p, uint32_t *op, char *buf, size_t cap, uint64_t deadline) {
|
||||
uint8_t head[8];
|
||||
size_t got = 0;
|
||||
while (got < sizeof head) {
|
||||
uint32_t left = deadline > plat_now_ms() ? (uint32_t)(deadline - plat_now_ms()) : 0;
|
||||
int n = plat_ipc_read(p, head + got, sizeof head - got, left);
|
||||
if (n < 0) return -1;
|
||||
if (!n) {
|
||||
if (plat_now_ms() >= deadline) return 0;
|
||||
continue;
|
||||
}
|
||||
got += (size_t)n;
|
||||
}
|
||||
*op = head[0] | head[1] << 8 | head[2] << 16 | (uint32_t)head[3] << 24;
|
||||
uint32_t len = head[4] | head[5] << 8 | head[6] << 16 | (uint32_t)head[7] << 24;
|
||||
size_t taken = 0;
|
||||
while (taken < len) {
|
||||
size_t want = len - taken > cap - 1 ? cap - 1 : len - taken;
|
||||
uint32_t left = deadline > plat_now_ms() ? (uint32_t)(deadline - plat_now_ms()) : 0;
|
||||
int n = plat_ipc_read(p, buf, want, left);
|
||||
if (n < 0) return -1;
|
||||
if (!n) {
|
||||
if (plat_now_ms() >= deadline) return 0;
|
||||
continue;
|
||||
}
|
||||
taken += (size_t)n;
|
||||
}
|
||||
buf[taken < cap - 1 ? taken : cap - 1] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The next frame that is not a Ping: a Ping the client sent to see we are alive is echoed as a Pong with its own
|
||||
// payload, a Close (the client told us it is done) counts as a broken connection so the caller connects again
|
||||
// later. Same return values as read_one_frame; the deadline covers the whole exchange.
|
||||
static int read_frame(PlatIpc *p, uint32_t *op, char *buf, size_t cap, uint32_t ms) {
|
||||
uint64_t deadline = plat_now_ms() + ms;
|
||||
for (;;) {
|
||||
int r = read_one_frame(p, op, buf, cap, deadline);
|
||||
if (r != 1) return r;
|
||||
if (*op == OP_PING) {
|
||||
uint8_t head[8];
|
||||
size_t n = strlen(buf);
|
||||
le32(head, OP_PONG);
|
||||
le32(head + 4, (uint32_t)n);
|
||||
if (!plat_ipc_write(p, head, sizeof head) || !plat_ipc_write(p, buf, n)) return -1;
|
||||
continue;
|
||||
}
|
||||
if (*op == OP_CLOSE) return -1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The first of Discord's pipes that completes a handshake. NULL when no client is running or none answered in time;
|
||||
// the caller tries again later.
|
||||
static PlatIpc *connect_client(const char *app_id) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
char name[32], json[160];
|
||||
snprintf(name, sizeof name, "discord-ipc-%d", i);
|
||||
PlatIpc *p = plat_ipc_connect(name);
|
||||
if (!p) continue;
|
||||
snprintf(json, sizeof json, "{\"v\":1,\"client_id\":\"%s\"}", app_id);
|
||||
if (!write_frame(p, OP_HANDSHAKE, json)) {
|
||||
plat_ipc_close(p);
|
||||
continue;
|
||||
}
|
||||
uint32_t op = 0;
|
||||
char answer[ANSWER_CAP];
|
||||
if (read_frame(p, &op, answer, sizeof answer, 2000) == 1 && op == OP_FRAME) return p; // the Ready event
|
||||
plat_ipc_close(p); // this pipe answered but not with a Ready frame in 2 s: the next may belong to a client
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static uint32_t next_nonce(void) {
|
||||
static volatile int32_t n;
|
||||
return (uint32_t)plat_inc32(&n);
|
||||
}
|
||||
|
||||
// The activity as one SET_ACTIVITY frame: the game update and the port version as the details line ("Game 1.4.3 ·
|
||||
// Recomp 0.3.7": the port version without its +commit suffix, the art's hover text keeps the full one; the dot goes
|
||||
// as a JSON escape), the process's start as the timestamp Discord counts the elapsed time from, and the application's
|
||||
// art asset. 1 when the client answered (a slow answer that ran out of the 2 s read is fine: the next drain takes
|
||||
// it), 0 when the connection broke.
|
||||
static int send_activity(PlatIpc *p, const char *image) {
|
||||
char json[512];
|
||||
int v = rt_game_version;
|
||||
uint64_t start = (plat_unix_time_ns() - plat_process_uptime_ns()) / 1000000000ull;
|
||||
int port_len = (int)strcspn(rt_port_version, "+");
|
||||
snprintf(json, sizeof json,
|
||||
"{\"cmd\":\"SET_ACTIVITY\",\"nonce\":\"%u\",\"args\":{\"pid\":%lu,\"activity\":{\"details\":\"Game "
|
||||
"%d.%d.%d \\u00b7 Recomp %.*s\",\"timestamps\":{\"start\":%llu},\"assets\":{\"large_image\":\"%s\","
|
||||
"\"large_text\":\"TotK Recomp %s\"}}}}",
|
||||
next_nonce(), (unsigned long)plat_process_id(), v / 100, v / 10 % 10, v % 10, port_len, rt_port_version,
|
||||
(unsigned long long)start, image, rt_port_version);
|
||||
if (!write_frame(p, OP_FRAME, json)) return 0;
|
||||
uint32_t op = 0;
|
||||
char answer[ANSWER_CAP];
|
||||
return read_frame(p, &op, answer, sizeof answer, 2000) >= 0;
|
||||
}
|
||||
|
||||
// ---- the presence thread
|
||||
|
||||
static void discord_main(void *arg) {
|
||||
(void)arg;
|
||||
unsigned seen = 0;
|
||||
char app_id[APP_ID_CAP], image[IMAGE_KEY_CAP];
|
||||
read_setting("TOTK_DISCORD_APP_ID", app_id, sizeof app_id, valid_app_id);
|
||||
read_setting("TOTK_DISCORD_IMAGE", image, sizeof image, valid_image_key);
|
||||
PlatIpc *ipc = NULL;
|
||||
uint64_t retry_at = 0, send_at = 0;
|
||||
int said_no_client = 0;
|
||||
for (;;) {
|
||||
unsigned v = cfg_version();
|
||||
if (v != seen) { // the settings changed (the menu writes them): pick the new ones up at once
|
||||
seen = v;
|
||||
read_setting("TOTK_DISCORD_APP_ID", app_id, sizeof app_id, valid_app_id);
|
||||
read_setting("TOTK_DISCORD_IMAGE", image, sizeof image, valid_image_key);
|
||||
if (ipc) plat_ipc_close(ipc), ipc = NULL; // the new app id needs a new handshake; the image is resent
|
||||
said_no_client = 0;
|
||||
}
|
||||
if (!cfg_bool("TOTK_DISCORD_RPC")) {
|
||||
if (ipc) plat_ipc_close(ipc), ipc = NULL;
|
||||
plat_sleep_ms(500);
|
||||
continue;
|
||||
}
|
||||
if (!ipc && plat_now_ms() >= retry_at) {
|
||||
ipc = connect_client(app_id);
|
||||
if (!ipc) {
|
||||
retry_at = plat_now_ms() + 15000;
|
||||
if (!said_no_client)
|
||||
rt_log("discord: no client is running; trying again every 15 s while the setting is on");
|
||||
said_no_client = 1;
|
||||
} else {
|
||||
rt_log("discord: connected (app %s)", app_id);
|
||||
send_at = 0;
|
||||
said_no_client = 0;
|
||||
}
|
||||
}
|
||||
if (ipc && plat_now_ms() >= send_at) {
|
||||
if (send_activity(ipc, image)) {
|
||||
send_at = plat_now_ms() + 60000;
|
||||
} else {
|
||||
rt_log("discord: the connection broke; reconnecting");
|
||||
plat_ipc_close(ipc);
|
||||
ipc = NULL;
|
||||
retry_at = plat_now_ms() + 15000;
|
||||
}
|
||||
}
|
||||
plat_sleep_ms(500);
|
||||
}
|
||||
}
|
||||
|
||||
void discord_init(void) {
|
||||
if (!plat_thread_spawn(discord_main, NULL, "discord", 0)) rt_log("discord: cannot start the presence thread");
|
||||
}
|
||||
|
||||
// ---- --probe-discord: the exchange above without the game and the setting; needs no game files
|
||||
|
||||
int probe_discord(void) {
|
||||
static int fails;
|
||||
#define CHECK(c, what) do { if (!(c)) { fails++; fprintf(stderr, "FAIL discord: %s\n", what); } } while (0)
|
||||
char app_id[APP_ID_CAP], image[IMAGE_KEY_CAP];
|
||||
read_setting("TOTK_DISCORD_APP_ID", app_id, sizeof app_id, valid_app_id);
|
||||
read_setting("TOTK_DISCORD_IMAGE", image, sizeof image, valid_image_key);
|
||||
PlatIpc *p = connect_client(app_id);
|
||||
if (!p) {
|
||||
fprintf(stderr, "discord: probe skipped: no client on discord-ipc-0..9\n");
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "discord: connected (app %s)\n", app_id);
|
||||
CHECK(send_activity(p, image), "SET_ACTIVITY written and answered");
|
||||
fprintf(stderr, "discord: activity set; the status shows it for ten seconds, then the connection closes\n");
|
||||
plat_sleep_ms(10000);
|
||||
plat_ipc_close(p);
|
||||
fprintf(stderr, "discord: probe %s\n", fails ? "FAILED" : "passed");
|
||||
return fails != 0;
|
||||
#undef CHECK
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Generated by tools/make_dlaa_mask.py: DLSS's reactive mask pass (vk_dlaa.c mask_pass). Do not edit.
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
static const uint32_t dlaa_mask_spv[] = {
|
||||
0x07230203, 0x00010000, 0x00000000, 0x00000055, 0x00000000, 0x00020011, 0x00000001, 0x00020011,
|
||||
0x00000031, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e,
|
||||
0x00000000, 0x00000001, 0x0006000f, 0x00000005, 0x00000002, 0x6e69616d, 0x00000000, 0x0000001b,
|
||||
0x00060010, 0x00000002, 0x00000011, 0x00000008, 0x00000008, 0x00000001, 0x00040047, 0x0000001b,
|
||||
0x0000000b, 0x0000001c, 0x00040047, 0x00000018, 0x00000022, 0x00000000, 0x00040047, 0x00000018,
|
||||
0x00000021, 0x00000000, 0x00040047, 0x00000019, 0x00000022, 0x00000000, 0x00040047, 0x00000019,
|
||||
0x00000021, 0x00000001, 0x00040047, 0x0000001a, 0x00000022, 0x00000000, 0x00040047, 0x0000001a,
|
||||
0x00000021, 0x00000002, 0x00030047, 0x0000001a, 0x00000019, 0x00030047, 0x00000015, 0x00000002,
|
||||
0x00050048, 0x00000015, 0x00000000, 0x00000023, 0x00000000, 0x00020013, 0x00000003, 0x00030021,
|
||||
0x00000004, 0x00000003, 0x00020014, 0x00000005, 0x00030016, 0x00000006, 0x00000020, 0x00040015,
|
||||
0x00000007, 0x00000020, 0x00000001, 0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x00040017,
|
||||
0x00000009, 0x00000006, 0x00000002, 0x00040017, 0x0000000a, 0x00000006, 0x00000003, 0x00040017,
|
||||
0x0000000b, 0x00000006, 0x00000004, 0x00040017, 0x0000000c, 0x00000007, 0x00000002, 0x00040017,
|
||||
0x0000000d, 0x00000008, 0x00000002, 0x00040017, 0x0000000e, 0x00000008, 0x00000003, 0x00090019,
|
||||
0x0000000f, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000,
|
||||
0x0003001b, 0x00000010, 0x0000000f, 0x00090019, 0x00000011, 0x00000006, 0x00000001, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000002, 0x0000000f, 0x00040020, 0x00000012, 0x00000000, 0x00000010,
|
||||
0x00040020, 0x00000013, 0x00000000, 0x00000011, 0x00040020, 0x00000014, 0x00000001, 0x0000000e,
|
||||
0x0003001e, 0x00000015, 0x0000000b, 0x00040020, 0x00000016, 0x00000009, 0x00000015, 0x00040020,
|
||||
0x00000017, 0x00000009, 0x0000000b, 0x0004002b, 0x00000007, 0x0000001d, 0x00000000, 0x0004002b,
|
||||
0x00000006, 0x0000001e, 0x00000000, 0x0004002b, 0x00000006, 0x0000001f, 0x3f800000, 0x0004002b,
|
||||
0x00000006, 0x00000020, 0x38d1b717, 0x0004003b, 0x00000012, 0x00000018, 0x00000000, 0x0004003b,
|
||||
0x00000012, 0x00000019, 0x00000000, 0x0004003b, 0x00000013, 0x0000001a, 0x00000000, 0x0004003b,
|
||||
0x00000014, 0x0000001b, 0x00000001, 0x0004003b, 0x00000016, 0x0000001c, 0x00000009, 0x00050036,
|
||||
0x00000003, 0x00000002, 0x00000000, 0x00000004, 0x000200f8, 0x00000021, 0x00050041, 0x00000017,
|
||||
0x00000024, 0x0000001c, 0x0000001d, 0x0004003d, 0x0000000b, 0x00000025, 0x00000024, 0x0004003d,
|
||||
0x0000000e, 0x00000026, 0x0000001b, 0x0007004f, 0x0000000d, 0x00000027, 0x00000026, 0x00000026,
|
||||
0x00000000, 0x00000001, 0x00050051, 0x00000008, 0x00000028, 0x00000026, 0x00000000, 0x00040070,
|
||||
0x00000006, 0x00000029, 0x00000028, 0x00050051, 0x00000008, 0x0000002a, 0x00000026, 0x00000001,
|
||||
0x00040070, 0x00000006, 0x0000002b, 0x0000002a, 0x00050051, 0x00000006, 0x0000002c, 0x00000025,
|
||||
0x00000000, 0x00050051, 0x00000006, 0x0000002d, 0x00000025, 0x00000001, 0x000500b8, 0x00000005,
|
||||
0x0000002e, 0x00000029, 0x0000002c, 0x000500b8, 0x00000005, 0x0000002f, 0x0000002b, 0x0000002d,
|
||||
0x000500a7, 0x00000005, 0x00000030, 0x0000002e, 0x0000002f, 0x000300f7, 0x00000023, 0x00000000,
|
||||
0x000400fa, 0x00000030, 0x00000022, 0x00000023, 0x000200f8, 0x00000022, 0x0004007c, 0x0000000c,
|
||||
0x00000031, 0x00000027, 0x0004003d, 0x00000010, 0x00000032, 0x00000018, 0x00040064, 0x0000000f,
|
||||
0x00000033, 0x00000032, 0x0007005f, 0x0000000b, 0x00000034, 0x00000033, 0x00000031, 0x00000002,
|
||||
0x0000001d, 0x0008004f, 0x0000000a, 0x00000035, 0x00000034, 0x00000034, 0x00000000, 0x00000001,
|
||||
0x00000002, 0x0004003d, 0x00000010, 0x00000036, 0x00000019, 0x00040064, 0x0000000f, 0x00000037,
|
||||
0x00000036, 0x0007005f, 0x0000000b, 0x00000038, 0x00000037, 0x00000031, 0x00000002, 0x0000001d,
|
||||
0x0008004f, 0x0000000a, 0x00000039, 0x00000038, 0x00000038, 0x00000000, 0x00000001, 0x00000002,
|
||||
0x00050083, 0x0000000a, 0x0000003a, 0x00000039, 0x00000035, 0x0006000c, 0x0000000a, 0x0000003b,
|
||||
0x00000001, 0x00000004, 0x0000003a, 0x00050051, 0x00000006, 0x0000003c, 0x0000003b, 0x00000000,
|
||||
0x00050051, 0x00000006, 0x0000003d, 0x0000003b, 0x00000001, 0x00050051, 0x00000006, 0x0000003e,
|
||||
0x0000003b, 0x00000002, 0x0007000c, 0x00000006, 0x0000003f, 0x00000001, 0x00000028, 0x0000003c,
|
||||
0x0000003d, 0x0007000c, 0x00000006, 0x00000040, 0x00000001, 0x00000028, 0x0000003f, 0x0000003e,
|
||||
0x00050051, 0x00000006, 0x00000041, 0x00000039, 0x00000000, 0x00050051, 0x00000006, 0x00000042,
|
||||
0x00000039, 0x00000001, 0x00050051, 0x00000006, 0x00000043, 0x00000039, 0x00000002, 0x0007000c,
|
||||
0x00000006, 0x00000044, 0x00000001, 0x00000028, 0x00000041, 0x00000042, 0x0007000c, 0x00000006,
|
||||
0x00000045, 0x00000001, 0x00000028, 0x00000044, 0x00000043, 0x00050051, 0x00000006, 0x00000046,
|
||||
0x00000035, 0x00000000, 0x00050051, 0x00000006, 0x00000047, 0x00000035, 0x00000001, 0x00050051,
|
||||
0x00000006, 0x00000048, 0x00000035, 0x00000002, 0x0007000c, 0x00000006, 0x00000049, 0x00000001,
|
||||
0x00000028, 0x00000046, 0x00000047, 0x0007000c, 0x00000006, 0x0000004a, 0x00000001, 0x00000028,
|
||||
0x00000049, 0x00000048, 0x0007000c, 0x00000006, 0x0000004b, 0x00000001, 0x00000028, 0x00000045,
|
||||
0x0000004a, 0x0007000c, 0x00000006, 0x0000004c, 0x00000001, 0x00000028, 0x0000004b, 0x00000020,
|
||||
0x00050088, 0x00000006, 0x0000004d, 0x00000040, 0x0000004c, 0x00050051, 0x00000006, 0x0000004e,
|
||||
0x00000025, 0x00000002, 0x00050051, 0x00000006, 0x0000004f, 0x00000025, 0x00000003, 0x00050083,
|
||||
0x00000006, 0x00000050, 0x0000004d, 0x0000004e, 0x00050085, 0x00000006, 0x00000051, 0x00000050,
|
||||
0x0000004f, 0x0008000c, 0x00000006, 0x00000052, 0x00000001, 0x0000002b, 0x00000051, 0x0000001e,
|
||||
0x0000001f, 0x00070050, 0x0000000b, 0x00000053, 0x00000052, 0x00000052, 0x00000052, 0x00000052,
|
||||
0x0004003d, 0x00000011, 0x00000054, 0x0000001a, 0x00040063, 0x00000054, 0x00000031, 0x00000053,
|
||||
0x000200f9, 0x00000023, 0x000200f8, 0x00000023, 0x000100fd, 0x00010038
|
||||
};
|
||||
+156
-113
@@ -1,119 +1,162 @@
|
||||
// Generated by tools/build_shaders.py from runtime/shaders/dlaa_mv.comp; do not edit.
|
||||
// source sha256 bfa9b6d9e0f9d5cfd4382fa5080fa46f007a1a4e2b886b8fecd9adba11767686
|
||||
// source sha256 e96a6d2d0cec26c47a76280363077c5982919bedc984f20ae9f1d185dc6ae674
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
static const uint32_t dlaa_mv_spv[] = {
|
||||
0x07230203, 0x00010600, 0x0008000b, 0x00000131, 0x00000000, 0x00020011, 0x00000001, 0x00020011,
|
||||
0x07230203, 0x00010300, 0x000d000b, 0x0000019a, 0x00000000, 0x00020011, 0x00000001, 0x00020011,
|
||||
0x00000031, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e,
|
||||
0x00000000, 0x00000001, 0x000b000f, 0x00000005, 0x00000004, 0x6e69616d, 0x00000000, 0x00000036,
|
||||
0x00000043, 0x0000005d, 0x00000089, 0x000000a0, 0x000000e7, 0x00060010, 0x00000004, 0x00000011,
|
||||
0x00000008, 0x00000008, 0x00000001, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004,
|
||||
0x6e69616d, 0x00000000, 0x00080005, 0x00000036, 0x475f6c67, 0x61626f6c, 0x766e496c, 0x7461636f,
|
||||
0x496e6f69, 0x00000044, 0x00030005, 0x00000041, 0x00000050, 0x00050006, 0x00000041, 0x00000000,
|
||||
0x657a6973, 0x00000000, 0x00050006, 0x00000041, 0x00000001, 0x7474696a, 0x00007265, 0x00030005,
|
||||
0x00000043, 0x00006370, 0x00050005, 0x0000005d, 0x74706564, 0x65745f68, 0x00000078, 0x00030005,
|
||||
0x00000087, 0x00727543, 0x00040006, 0x00000087, 0x00000000, 0x00000072, 0x00030005, 0x00000089,
|
||||
0x00727563, 0x00040005, 0x0000009e, 0x76657250, 0x00000000, 0x00040006, 0x0000009e, 0x00000000,
|
||||
0x00000072, 0x00040005, 0x000000a0, 0x76657270, 0x00000000, 0x00040005, 0x000000e7, 0x6f5f766d,
|
||||
0x00007475, 0x00040047, 0x00000036, 0x0000000b, 0x0000001c, 0x00030047, 0x00000041, 0x00000002,
|
||||
0x00050048, 0x00000041, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000041, 0x00000001,
|
||||
0x00000023, 0x00000008, 0x00040047, 0x0000005d, 0x00000021, 0x00000000, 0x00040047, 0x0000005d,
|
||||
0x00000022, 0x00000000, 0x00040047, 0x00000086, 0x00000006, 0x00000010, 0x00030047, 0x00000087,
|
||||
0x00000002, 0x00050048, 0x00000087, 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x00000089,
|
||||
0x00000021, 0x00000002, 0x00040047, 0x00000089, 0x00000022, 0x00000000, 0x00040047, 0x0000009d,
|
||||
0x00000006, 0x00000010, 0x00030047, 0x0000009e, 0x00000002, 0x00050048, 0x0000009e, 0x00000000,
|
||||
0x00000023, 0x00000000, 0x00040047, 0x000000a0, 0x00000021, 0x00000003, 0x00040047, 0x000000a0,
|
||||
0x00000022, 0x00000000, 0x00030047, 0x000000e7, 0x00000019, 0x00040047, 0x000000e7, 0x00000021,
|
||||
0x00000001, 0x00040047, 0x000000e7, 0x00000022, 0x00000000, 0x00020013, 0x00000002, 0x00030021,
|
||||
0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006,
|
||||
0x00000004, 0x00040018, 0x00000009, 0x00000007, 0x00000004, 0x0004002b, 0x00000006, 0x00000015,
|
||||
0x3f800000, 0x0004002b, 0x00000006, 0x00000016, 0x00000000, 0x00040015, 0x0000002f, 0x00000020,
|
||||
0x00000001, 0x00040017, 0x00000030, 0x0000002f, 0x00000002, 0x00040015, 0x00000033, 0x00000020,
|
||||
0x00000000, 0x00040017, 0x00000034, 0x00000033, 0x00000003, 0x00040020, 0x00000035, 0x00000001,
|
||||
0x00000034, 0x0004003b, 0x00000035, 0x00000036, 0x00000001, 0x00040017, 0x00000037, 0x00000033,
|
||||
0x00000002, 0x00020014, 0x0000003b, 0x0004002b, 0x00000033, 0x0000003c, 0x00000000, 0x00040017,
|
||||
0x00000040, 0x00000006, 0x00000002, 0x0004001e, 0x00000041, 0x00000040, 0x00000040, 0x00040020,
|
||||
0x00000042, 0x00000009, 0x00000041, 0x0004003b, 0x00000042, 0x00000043, 0x00000009, 0x0004002b,
|
||||
0x0000002f, 0x00000044, 0x00000000, 0x00040020, 0x00000045, 0x00000009, 0x00000006, 0x0004002b,
|
||||
0x00000033, 0x0000004d, 0x00000001, 0x00090019, 0x0000005a, 0x00000006, 0x00000001, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x0000005b, 0x0000005a, 0x00040020,
|
||||
0x0000005c, 0x00000000, 0x0000005b, 0x0004003b, 0x0000005c, 0x0000005d, 0x00000000, 0x0004002b,
|
||||
0x00000006, 0x00000067, 0x3f000000, 0x0004002b, 0x0000002f, 0x0000006a, 0x00000001, 0x00040020,
|
||||
0x0000006b, 0x00000009, 0x00000040, 0x0004002b, 0x00000006, 0x00000075, 0x40000000, 0x0004002b,
|
||||
0x00000033, 0x00000085, 0x00000010, 0x0004001c, 0x00000086, 0x00000007, 0x00000085, 0x0003001e,
|
||||
0x00000087, 0x00000086, 0x00040020, 0x00000088, 0x00000002, 0x00000087, 0x0004003b, 0x00000088,
|
||||
0x00000089, 0x00000002, 0x0004002b, 0x0000002f, 0x0000008a, 0x00000003, 0x0004002b, 0x0000002f,
|
||||
0x0000008b, 0x00000004, 0x0004002b, 0x0000002f, 0x0000008c, 0x00000005, 0x0004002b, 0x0000002f,
|
||||
0x0000008d, 0x00000006, 0x00040020, 0x0000008f, 0x00000002, 0x00000007, 0x0004001c, 0x0000009d,
|
||||
0x00000007, 0x00000085, 0x0003001e, 0x0000009e, 0x0000009d, 0x00040020, 0x0000009f, 0x00000002,
|
||||
0x0000009e, 0x0004003b, 0x0000009f, 0x000000a0, 0x00000002, 0x0005002c, 0x00000040, 0x000000b4,
|
||||
0x00000016, 0x00000016, 0x0004002b, 0x00000006, 0x000000b9, 0x2b8cbccc, 0x0004002b, 0x00000006,
|
||||
0x000000c7, 0x358637bd, 0x00090019, 0x000000e5, 0x00000006, 0x00000001, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000002, 0x00000007, 0x00040020, 0x000000e6, 0x00000000, 0x000000e5, 0x0004003b,
|
||||
0x000000e6, 0x000000e7, 0x00000000, 0x0005002c, 0x00000040, 0x00000130, 0x00000067, 0x00000067,
|
||||
0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x000300f7,
|
||||
0x000000f0, 0x00000000, 0x000300fb, 0x0000003c, 0x000000f1, 0x000200f8, 0x000000f1, 0x0004003d,
|
||||
0x00000034, 0x00000038, 0x00000036, 0x0007004f, 0x00000037, 0x00000039, 0x00000038, 0x00000038,
|
||||
0x00000000, 0x00000001, 0x0004007c, 0x00000030, 0x0000003a, 0x00000039, 0x00050051, 0x0000002f,
|
||||
0x0000003f, 0x0000003a, 0x00000000, 0x00060041, 0x00000045, 0x00000046, 0x00000043, 0x00000044,
|
||||
0x0000003c, 0x0004003d, 0x00000006, 0x00000047, 0x00000046, 0x0004006e, 0x0000002f, 0x00000048,
|
||||
0x00000047, 0x000500af, 0x0000003b, 0x00000049, 0x0000003f, 0x00000048, 0x000400a8, 0x0000003b,
|
||||
0x0000004a, 0x00000049, 0x000300f7, 0x0000004c, 0x00000000, 0x000400fa, 0x0000004a, 0x0000004b,
|
||||
0x0000004c, 0x000200f8, 0x0000004b, 0x00050051, 0x0000002f, 0x0000004f, 0x0000003a, 0x00000001,
|
||||
0x00060041, 0x00000045, 0x00000050, 0x00000043, 0x00000044, 0x0000004d, 0x0004003d, 0x00000006,
|
||||
0x00000051, 0x00000050, 0x0004006e, 0x0000002f, 0x00000052, 0x00000051, 0x000500af, 0x0000003b,
|
||||
0x00000053, 0x0000004f, 0x00000052, 0x000200f9, 0x0000004c, 0x000200f8, 0x0000004c, 0x000700f5,
|
||||
0x0000003b, 0x00000054, 0x00000049, 0x000000f1, 0x00000053, 0x0000004b, 0x000300f7, 0x00000056,
|
||||
0x00000000, 0x000400fa, 0x00000054, 0x00000055, 0x00000056, 0x000200f8, 0x00000055, 0x000200f9,
|
||||
0x000000f0, 0x000200f8, 0x00000056, 0x0004003d, 0x0000005b, 0x0000005e, 0x0000005d, 0x00040064,
|
||||
0x0000005a, 0x00000060, 0x0000005e, 0x0007005f, 0x00000007, 0x00000061, 0x00000060, 0x0000003a,
|
||||
0x00000002, 0x00000044, 0x00050051, 0x00000006, 0x00000062, 0x00000061, 0x00000000, 0x0004006f,
|
||||
0x00000040, 0x00000066, 0x0000003a, 0x00050081, 0x00000040, 0x00000069, 0x00000066, 0x00000130,
|
||||
0x00050041, 0x0000006b, 0x0000006c, 0x00000043, 0x0000006a, 0x0004003d, 0x00000040, 0x0000006d,
|
||||
0x0000006c, 0x00050083, 0x00000040, 0x0000006e, 0x00000069, 0x0000006d, 0x00050051, 0x00000006,
|
||||
0x00000071, 0x0000006e, 0x00000000, 0x00050088, 0x00000006, 0x00000074, 0x00000071, 0x00000047,
|
||||
0x00050085, 0x00000006, 0x00000076, 0x00000074, 0x00000075, 0x00050083, 0x00000006, 0x00000077,
|
||||
0x00000076, 0x00000015, 0x00050051, 0x00000006, 0x00000079, 0x0000006e, 0x00000001, 0x00060041,
|
||||
0x00000045, 0x0000007a, 0x00000043, 0x00000044, 0x0000004d, 0x0004003d, 0x00000006, 0x0000007b,
|
||||
0x0000007a, 0x00050088, 0x00000006, 0x0000007c, 0x00000079, 0x0000007b, 0x00050085, 0x00000006,
|
||||
0x0000007d, 0x0000007c, 0x00000075, 0x00050083, 0x00000006, 0x0000007e, 0x00000015, 0x0000007d,
|
||||
0x00050085, 0x00000006, 0x00000080, 0x00000062, 0x00000075, 0x00050083, 0x00000006, 0x00000081,
|
||||
0x00000080, 0x00000015, 0x00070050, 0x00000007, 0x00000082, 0x00000077, 0x0000007e, 0x00000081,
|
||||
0x00000015, 0x00060041, 0x0000008f, 0x00000090, 0x00000089, 0x00000044, 0x0000008a, 0x0004003d,
|
||||
0x00000007, 0x00000091, 0x00000090, 0x00060041, 0x0000008f, 0x00000093, 0x00000089, 0x00000044,
|
||||
0x0000008b, 0x0004003d, 0x00000007, 0x00000094, 0x00000093, 0x00060041, 0x0000008f, 0x00000096,
|
||||
0x00000089, 0x00000044, 0x0000008c, 0x0004003d, 0x00000007, 0x00000097, 0x00000096, 0x00060041,
|
||||
0x0000008f, 0x00000099, 0x00000089, 0x00000044, 0x0000008d, 0x0004003d, 0x00000007, 0x0000009a,
|
||||
0x00000099, 0x00070050, 0x00000009, 0x00000110, 0x00000091, 0x00000094, 0x00000097, 0x0000009a,
|
||||
0x00040054, 0x00000009, 0x00000111, 0x00000110, 0x00060041, 0x0000008f, 0x000000a2, 0x000000a0,
|
||||
0x00000044, 0x0000008a, 0x0004003d, 0x00000007, 0x000000a3, 0x000000a2, 0x00060041, 0x0000008f,
|
||||
0x000000a5, 0x000000a0, 0x00000044, 0x0000008b, 0x0004003d, 0x00000007, 0x000000a6, 0x000000a5,
|
||||
0x00060041, 0x0000008f, 0x000000a8, 0x000000a0, 0x00000044, 0x0000008c, 0x0004003d, 0x00000007,
|
||||
0x000000a9, 0x000000a8, 0x00060041, 0x0000008f, 0x000000ab, 0x000000a0, 0x00000044, 0x0000008d,
|
||||
0x0004003d, 0x00000007, 0x000000ac, 0x000000ab, 0x00070050, 0x00000009, 0x0000012c, 0x000000a3,
|
||||
0x000000a6, 0x000000a9, 0x000000ac, 0x00040054, 0x00000009, 0x0000012d, 0x0000012c, 0x0006000c,
|
||||
0x00000009, 0x000000b0, 0x00000001, 0x00000022, 0x00000111, 0x00050091, 0x00000007, 0x000000b2,
|
||||
0x000000b0, 0x00000082, 0x00050051, 0x00000006, 0x000000b7, 0x000000b2, 0x00000003, 0x0006000c,
|
||||
0x00000006, 0x000000b8, 0x00000001, 0x00000004, 0x000000b7, 0x000500ba, 0x0000003b, 0x000000ba,
|
||||
0x000000b8, 0x000000b9, 0x000300f7, 0x000000bc, 0x00000000, 0x000400fa, 0x000000ba, 0x000000bb,
|
||||
0x000000bc, 0x000200f8, 0x000000bb, 0x00070050, 0x00000007, 0x000000c2, 0x000000b7, 0x000000b7,
|
||||
0x000000b7, 0x000000b7, 0x00050088, 0x00000007, 0x000000c3, 0x000000b2, 0x000000c2, 0x00050091,
|
||||
0x00000007, 0x000000c4, 0x0000012d, 0x000000c3, 0x00050051, 0x00000006, 0x000000c6, 0x000000c4,
|
||||
0x00000003, 0x000500ba, 0x0000003b, 0x000000c8, 0x000000c6, 0x000000c7, 0x000300f7, 0x000000ca,
|
||||
0x00000000, 0x000400fa, 0x000000c8, 0x000000c9, 0x000000ca, 0x000200f8, 0x000000c9, 0x0007004f,
|
||||
0x00000040, 0x000000cd, 0x000000c4, 0x000000c4, 0x00000000, 0x00000001, 0x00050050, 0x00000040,
|
||||
0x000000d0, 0x000000c6, 0x000000c6, 0x00050088, 0x00000040, 0x000000d1, 0x000000cd, 0x000000d0,
|
||||
0x00050051, 0x00000006, 0x000000d4, 0x000000d1, 0x00000000, 0x00050085, 0x00000006, 0x000000d5,
|
||||
0x000000d4, 0x00000067, 0x00050081, 0x00000006, 0x000000d6, 0x000000d5, 0x00000067, 0x00050085,
|
||||
0x00000006, 0x000000d9, 0x000000d6, 0x00000047, 0x00050051, 0x00000006, 0x000000db, 0x000000d1,
|
||||
0x00000001, 0x00050085, 0x00000006, 0x000000dc, 0x000000db, 0x00000067, 0x00050083, 0x00000006,
|
||||
0x000000dd, 0x00000067, 0x000000dc, 0x00050085, 0x00000006, 0x000000e0, 0x000000dd, 0x0000007b,
|
||||
0x00050050, 0x00000040, 0x000000e1, 0x000000d9, 0x000000e0, 0x00050083, 0x00000040, 0x000000e4,
|
||||
0x000000e1, 0x0000006e, 0x000200f9, 0x000000ca, 0x000200f8, 0x000000ca, 0x000700f5, 0x00000040,
|
||||
0x0000012f, 0x000000b4, 0x000000bb, 0x000000e4, 0x000000c9, 0x000200f9, 0x000000bc, 0x000200f8,
|
||||
0x000000bc, 0x000700f5, 0x00000040, 0x0000012e, 0x000000b4, 0x00000056, 0x0000012f, 0x000000ca,
|
||||
0x0004003d, 0x000000e5, 0x000000e8, 0x000000e7, 0x00050051, 0x00000006, 0x000000eb, 0x0000012e,
|
||||
0x00000000, 0x00050051, 0x00000006, 0x000000ec, 0x0000012e, 0x00000001, 0x00070050, 0x00000007,
|
||||
0x000000ed, 0x000000eb, 0x000000ec, 0x00000016, 0x00000016, 0x00040063, 0x000000e8, 0x0000003a,
|
||||
0x000000ed, 0x000200f9, 0x000000f0, 0x000200f8, 0x000000f0, 0x000100fd, 0x00010038,
|
||||
0x00000000, 0x00000001, 0x0006000f, 0x00000005, 0x00000004, 0x6e69616d, 0x00000000, 0x00000036,
|
||||
0x00060010, 0x00000004, 0x00000011, 0x00000008, 0x00000008, 0x00000001, 0x00040047, 0x00000036,
|
||||
0x0000000b, 0x0000001c, 0x00030047, 0x00000041, 0x00000002, 0x00050048, 0x00000041, 0x00000000,
|
||||
0x00000023, 0x00000000, 0x00050048, 0x00000041, 0x00000001, 0x00000023, 0x00000008, 0x00050048,
|
||||
0x00000041, 0x00000002, 0x00000023, 0x00000010, 0x00050048, 0x00000041, 0x00000003, 0x00000023,
|
||||
0x00000014, 0x00040047, 0x0000005d, 0x00000021, 0x00000000, 0x00040047, 0x0000005d, 0x00000022,
|
||||
0x00000000, 0x00040047, 0x00000086, 0x00000006, 0x00000010, 0x00030047, 0x00000087, 0x00000002,
|
||||
0x00050048, 0x00000087, 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x00000089, 0x00000021,
|
||||
0x00000002, 0x00040047, 0x00000089, 0x00000022, 0x00000000, 0x00040047, 0x0000009d, 0x00000006,
|
||||
0x00000010, 0x00030047, 0x0000009e, 0x00000002, 0x00050048, 0x0000009e, 0x00000000, 0x00000023,
|
||||
0x00000000, 0x00040047, 0x000000a0, 0x00000021, 0x00000003, 0x00040047, 0x000000a0, 0x00000022,
|
||||
0x00000000, 0x00030047, 0x000000e7, 0x00000019, 0x00040047, 0x000000e7, 0x00000021, 0x00000001,
|
||||
0x00040047, 0x000000e7, 0x00000022, 0x00000000, 0x00040047, 0x000000f6, 0x00000021, 0x00000004,
|
||||
0x00040047, 0x000000f6, 0x00000022, 0x00000000, 0x00030047, 0x00000101, 0x00000019, 0x00040047,
|
||||
0x00000101, 0x00000021, 0x00000007, 0x00040047, 0x00000101, 0x00000022, 0x00000000, 0x00030047,
|
||||
0x0000012a, 0x00000018, 0x00040047, 0x0000012a, 0x00000021, 0x00000006, 0x00040047, 0x0000012a,
|
||||
0x00000022, 0x00000000, 0x00030047, 0x0000013b, 0x00000019, 0x00040047, 0x0000013b, 0x00000021,
|
||||
0x00000005, 0x00040047, 0x0000013b, 0x00000022, 0x00000000, 0x00040047, 0x00000146, 0x0000000b,
|
||||
0x00000019, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006,
|
||||
0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040018, 0x00000009, 0x00000007,
|
||||
0x00000004, 0x0004002b, 0x00000006, 0x00000015, 0x3f800000, 0x0004002b, 0x00000006, 0x00000016,
|
||||
0x00000000, 0x00040015, 0x0000002f, 0x00000020, 0x00000001, 0x00040017, 0x00000030, 0x0000002f,
|
||||
0x00000002, 0x00040015, 0x00000033, 0x00000020, 0x00000000, 0x00040017, 0x00000034, 0x00000033,
|
||||
0x00000003, 0x00040020, 0x00000035, 0x00000001, 0x00000034, 0x0004003b, 0x00000035, 0x00000036,
|
||||
0x00000001, 0x00040017, 0x00000037, 0x00000033, 0x00000002, 0x00020014, 0x0000003b, 0x0004002b,
|
||||
0x00000033, 0x0000003c, 0x00000000, 0x00040017, 0x00000040, 0x00000006, 0x00000002, 0x0006001e,
|
||||
0x00000041, 0x00000040, 0x00000040, 0x00000006, 0x00000006, 0x00040020, 0x00000042, 0x00000009,
|
||||
0x00000041, 0x0004003b, 0x00000042, 0x00000043, 0x00000009, 0x0004002b, 0x0000002f, 0x00000044,
|
||||
0x00000000, 0x00040020, 0x00000045, 0x00000009, 0x00000006, 0x0004002b, 0x00000033, 0x0000004d,
|
||||
0x00000001, 0x00090019, 0x0000005a, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00000001, 0x00000000, 0x0003001b, 0x0000005b, 0x0000005a, 0x00040020, 0x0000005c, 0x00000000,
|
||||
0x0000005b, 0x0004003b, 0x0000005c, 0x0000005d, 0x00000000, 0x0004002b, 0x00000006, 0x00000067,
|
||||
0x3f000000, 0x0004002b, 0x0000002f, 0x0000006a, 0x00000001, 0x00040020, 0x0000006b, 0x00000009,
|
||||
0x00000040, 0x0004002b, 0x00000006, 0x00000075, 0x40000000, 0x0004002b, 0x00000033, 0x00000085,
|
||||
0x00000010, 0x0004001c, 0x00000086, 0x00000007, 0x00000085, 0x0003001e, 0x00000087, 0x00000086,
|
||||
0x00040020, 0x00000088, 0x00000002, 0x00000087, 0x0004003b, 0x00000088, 0x00000089, 0x00000002,
|
||||
0x0004002b, 0x0000002f, 0x0000008a, 0x00000003, 0x0004002b, 0x0000002f, 0x0000008b, 0x00000004,
|
||||
0x0004002b, 0x0000002f, 0x0000008c, 0x00000005, 0x0004002b, 0x0000002f, 0x0000008d, 0x00000006,
|
||||
0x00040020, 0x0000008f, 0x00000002, 0x00000007, 0x0004001c, 0x0000009d, 0x00000007, 0x00000085,
|
||||
0x0003001e, 0x0000009e, 0x0000009d, 0x00040020, 0x0000009f, 0x00000002, 0x0000009e, 0x0004003b,
|
||||
0x0000009f, 0x000000a0, 0x00000002, 0x0005002c, 0x00000040, 0x000000b4, 0x00000016, 0x00000016,
|
||||
0x0004002b, 0x00000006, 0x000000b9, 0x2b8cbccc, 0x0004002b, 0x00000006, 0x000000c7, 0x358637bd,
|
||||
0x00090019, 0x000000e5, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000002,
|
||||
0x00000007, 0x00040020, 0x000000e6, 0x00000000, 0x000000e5, 0x0004003b, 0x000000e6, 0x000000e7,
|
||||
0x00000000, 0x0004002b, 0x0000002f, 0x000000ee, 0x00000002, 0x0004003b, 0x0000005c, 0x000000f6,
|
||||
0x00000000, 0x0004002b, 0x00000006, 0x000000fc, 0x3e808081, 0x00090019, 0x000000ff, 0x00000006,
|
||||
0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000002, 0x0000000f, 0x00040020, 0x00000100,
|
||||
0x00000000, 0x000000ff, 0x0004003b, 0x00000100, 0x00000101, 0x00000000, 0x0004002b, 0x0000002f,
|
||||
0x00000119, 0xffffffff, 0x0004003b, 0x00000100, 0x0000012a, 0x00000000, 0x0005002c, 0x00000030,
|
||||
0x00000131, 0x00000044, 0x00000044, 0x0004003b, 0x00000100, 0x0000013b, 0x00000000, 0x0004002b,
|
||||
0x00000033, 0x00000145, 0x00000008, 0x0006002c, 0x00000034, 0x00000146, 0x00000145, 0x00000145,
|
||||
0x0000004d, 0x0005002c, 0x00000040, 0x00000197, 0x00000067, 0x00000067, 0x0005002c, 0x00000030,
|
||||
0x00000199, 0x0000006a, 0x0000006a, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003,
|
||||
0x000200f8, 0x00000005, 0x000300f7, 0x00000147, 0x00000000, 0x000300fb, 0x0000003c, 0x00000148,
|
||||
0x000200f8, 0x00000148, 0x0004003d, 0x00000034, 0x00000038, 0x00000036, 0x0007004f, 0x00000037,
|
||||
0x00000039, 0x00000038, 0x00000038, 0x00000000, 0x00000001, 0x0004007c, 0x00000030, 0x0000003a,
|
||||
0x00000039, 0x00050051, 0x0000002f, 0x0000003f, 0x0000003a, 0x00000000, 0x00060041, 0x00000045,
|
||||
0x00000046, 0x00000043, 0x00000044, 0x0000003c, 0x0004003d, 0x00000006, 0x00000047, 0x00000046,
|
||||
0x0004006e, 0x0000002f, 0x00000048, 0x00000047, 0x000500af, 0x0000003b, 0x00000049, 0x0000003f,
|
||||
0x00000048, 0x000400a8, 0x0000003b, 0x0000004a, 0x00000049, 0x000300f7, 0x0000004c, 0x00000000,
|
||||
0x000400fa, 0x0000004a, 0x0000004b, 0x0000004c, 0x000200f8, 0x0000004b, 0x00050051, 0x0000002f,
|
||||
0x0000004f, 0x0000003a, 0x00000001, 0x00060041, 0x00000045, 0x00000050, 0x00000043, 0x00000044,
|
||||
0x0000004d, 0x0004003d, 0x00000006, 0x00000051, 0x00000050, 0x0004006e, 0x0000002f, 0x00000052,
|
||||
0x00000051, 0x000500af, 0x0000003b, 0x00000053, 0x0000004f, 0x00000052, 0x000200f9, 0x0000004c,
|
||||
0x000200f8, 0x0000004c, 0x000700f5, 0x0000003b, 0x00000054, 0x00000049, 0x00000148, 0x00000053,
|
||||
0x0000004b, 0x000300f7, 0x00000056, 0x00000000, 0x000400fa, 0x00000054, 0x00000055, 0x00000056,
|
||||
0x000200f8, 0x00000055, 0x000200f9, 0x00000147, 0x000200f8, 0x00000056, 0x0004003d, 0x0000005b,
|
||||
0x0000005e, 0x0000005d, 0x00040064, 0x0000005a, 0x00000060, 0x0000005e, 0x0007005f, 0x00000007,
|
||||
0x00000061, 0x00000060, 0x0000003a, 0x00000002, 0x00000044, 0x00050051, 0x00000006, 0x00000062,
|
||||
0x00000061, 0x00000000, 0x0004006f, 0x00000040, 0x00000066, 0x0000003a, 0x00050081, 0x00000040,
|
||||
0x00000069, 0x00000066, 0x00000197, 0x00050041, 0x0000006b, 0x0000006c, 0x00000043, 0x0000006a,
|
||||
0x0004003d, 0x00000040, 0x0000006d, 0x0000006c, 0x00050083, 0x00000040, 0x0000006e, 0x00000069,
|
||||
0x0000006d, 0x00050051, 0x00000006, 0x00000071, 0x0000006e, 0x00000000, 0x00050088, 0x00000006,
|
||||
0x00000074, 0x00000071, 0x00000047, 0x00050085, 0x00000006, 0x00000076, 0x00000074, 0x00000075,
|
||||
0x00050083, 0x00000006, 0x00000077, 0x00000076, 0x00000015, 0x00050051, 0x00000006, 0x00000079,
|
||||
0x0000006e, 0x00000001, 0x00060041, 0x00000045, 0x0000007a, 0x00000043, 0x00000044, 0x0000004d,
|
||||
0x0004003d, 0x00000006, 0x0000007b, 0x0000007a, 0x00050088, 0x00000006, 0x0000007c, 0x00000079,
|
||||
0x0000007b, 0x00050085, 0x00000006, 0x0000007d, 0x0000007c, 0x00000075, 0x00050083, 0x00000006,
|
||||
0x0000007e, 0x00000015, 0x0000007d, 0x00050085, 0x00000006, 0x00000080, 0x00000062, 0x00000075,
|
||||
0x00050083, 0x00000006, 0x00000081, 0x00000080, 0x00000015, 0x00070050, 0x00000007, 0x00000082,
|
||||
0x00000077, 0x0000007e, 0x00000081, 0x00000015, 0x00060041, 0x0000008f, 0x00000090, 0x00000089,
|
||||
0x00000044, 0x0000008a, 0x0004003d, 0x00000007, 0x00000091, 0x00000090, 0x00060041, 0x0000008f,
|
||||
0x00000093, 0x00000089, 0x00000044, 0x0000008b, 0x0004003d, 0x00000007, 0x00000094, 0x00000093,
|
||||
0x00060041, 0x0000008f, 0x00000096, 0x00000089, 0x00000044, 0x0000008c, 0x0004003d, 0x00000007,
|
||||
0x00000097, 0x00000096, 0x00060041, 0x0000008f, 0x00000099, 0x00000089, 0x00000044, 0x0000008d,
|
||||
0x0004003d, 0x00000007, 0x0000009a, 0x00000099, 0x00070050, 0x00000009, 0x00000167, 0x00000091,
|
||||
0x00000094, 0x00000097, 0x0000009a, 0x00040054, 0x00000009, 0x00000168, 0x00000167, 0x00060041,
|
||||
0x0000008f, 0x000000a2, 0x000000a0, 0x00000044, 0x0000008a, 0x0004003d, 0x00000007, 0x000000a3,
|
||||
0x000000a2, 0x00060041, 0x0000008f, 0x000000a5, 0x000000a0, 0x00000044, 0x0000008b, 0x0004003d,
|
||||
0x00000007, 0x000000a6, 0x000000a5, 0x00060041, 0x0000008f, 0x000000a8, 0x000000a0, 0x00000044,
|
||||
0x0000008c, 0x0004003d, 0x00000007, 0x000000a9, 0x000000a8, 0x00060041, 0x0000008f, 0x000000ab,
|
||||
0x000000a0, 0x00000044, 0x0000008d, 0x0004003d, 0x00000007, 0x000000ac, 0x000000ab, 0x00070050,
|
||||
0x00000009, 0x00000183, 0x000000a3, 0x000000a6, 0x000000a9, 0x000000ac, 0x00040054, 0x00000009,
|
||||
0x00000184, 0x00000183, 0x0006000c, 0x00000009, 0x000000b0, 0x00000001, 0x00000022, 0x00000168,
|
||||
0x00050091, 0x00000007, 0x000000b2, 0x000000b0, 0x00000082, 0x00050051, 0x00000006, 0x000000b7,
|
||||
0x000000b2, 0x00000003, 0x0006000c, 0x00000006, 0x000000b8, 0x00000001, 0x00000004, 0x000000b7,
|
||||
0x000500ba, 0x0000003b, 0x000000ba, 0x000000b8, 0x000000b9, 0x000300f7, 0x000000bc, 0x00000000,
|
||||
0x000400fa, 0x000000ba, 0x000000bb, 0x000000bc, 0x000200f8, 0x000000bb, 0x00070050, 0x00000007,
|
||||
0x000000c2, 0x000000b7, 0x000000b7, 0x000000b7, 0x000000b7, 0x00050088, 0x00000007, 0x000000c3,
|
||||
0x000000b2, 0x000000c2, 0x00050091, 0x00000007, 0x000000c4, 0x00000184, 0x000000c3, 0x00050051,
|
||||
0x00000006, 0x000000c6, 0x000000c4, 0x00000003, 0x000500ba, 0x0000003b, 0x000000c8, 0x000000c6,
|
||||
0x000000c7, 0x000300f7, 0x000000ca, 0x00000000, 0x000400fa, 0x000000c8, 0x000000c9, 0x000000ca,
|
||||
0x000200f8, 0x000000c9, 0x0007004f, 0x00000040, 0x000000cd, 0x000000c4, 0x000000c4, 0x00000000,
|
||||
0x00000001, 0x00050050, 0x00000040, 0x000000d0, 0x000000c6, 0x000000c6, 0x00050088, 0x00000040,
|
||||
0x000000d1, 0x000000cd, 0x000000d0, 0x00050051, 0x00000006, 0x000000d4, 0x000000d1, 0x00000000,
|
||||
0x00050085, 0x00000006, 0x000000d5, 0x000000d4, 0x00000067, 0x00050081, 0x00000006, 0x000000d6,
|
||||
0x000000d5, 0x00000067, 0x00050085, 0x00000006, 0x000000d9, 0x000000d6, 0x00000047, 0x00050051,
|
||||
0x00000006, 0x000000db, 0x000000d1, 0x00000001, 0x00050085, 0x00000006, 0x000000dc, 0x000000db,
|
||||
0x00000067, 0x00050083, 0x00000006, 0x000000dd, 0x00000067, 0x000000dc, 0x00050085, 0x00000006,
|
||||
0x000000e0, 0x000000dd, 0x0000007b, 0x00050050, 0x00000040, 0x000000e1, 0x000000d9, 0x000000e0,
|
||||
0x00050083, 0x00000040, 0x000000e4, 0x000000e1, 0x0000006e, 0x000200f9, 0x000000ca, 0x000200f8,
|
||||
0x000000ca, 0x000700f5, 0x00000040, 0x00000190, 0x000000b4, 0x000000bb, 0x000000e4, 0x000000c9,
|
||||
0x000200f9, 0x000000bc, 0x000200f8, 0x000000bc, 0x000700f5, 0x00000040, 0x0000018f, 0x000000b4,
|
||||
0x00000056, 0x00000190, 0x000000ca, 0x0004003d, 0x000000e5, 0x000000e8, 0x000000e7, 0x00050051,
|
||||
0x00000006, 0x000000eb, 0x0000018f, 0x00000000, 0x00050051, 0x00000006, 0x000000ec, 0x0000018f,
|
||||
0x00000001, 0x00070050, 0x00000007, 0x000000ed, 0x000000eb, 0x000000ec, 0x00000016, 0x00000016,
|
||||
0x00040063, 0x000000e8, 0x0000003a, 0x000000ed, 0x00050041, 0x00000045, 0x000000ef, 0x00000043,
|
||||
0x000000ee, 0x0004003d, 0x00000006, 0x000000f0, 0x000000ef, 0x000500bc, 0x0000003b, 0x000000f1,
|
||||
0x000000f0, 0x00000016, 0x000300f7, 0x000000f3, 0x00000000, 0x000400fa, 0x000000f1, 0x000000f2,
|
||||
0x000000f3, 0x000200f8, 0x000000f2, 0x000200f9, 0x00000147, 0x000200f8, 0x000000f3, 0x0004003d,
|
||||
0x0000005b, 0x000000f7, 0x000000f6, 0x00040064, 0x0000005a, 0x000000f9, 0x000000f7, 0x0007005f,
|
||||
0x00000007, 0x000000fa, 0x000000f9, 0x0000003a, 0x00000002, 0x00000044, 0x00050051, 0x00000006,
|
||||
0x000000fb, 0x000000fa, 0x00000001, 0x000500ba, 0x0000003b, 0x000000fd, 0x000000fb, 0x000000fc,
|
||||
0x000600a9, 0x00000006, 0x000000fe, 0x000000fd, 0x00000015, 0x00000016, 0x0004003d, 0x000000ff,
|
||||
0x00000102, 0x00000101, 0x00070050, 0x00000007, 0x00000105, 0x000000fe, 0x000000fe, 0x000000fe,
|
||||
0x000000fe, 0x00040063, 0x00000102, 0x0000003a, 0x00000105, 0x00050041, 0x00000045, 0x00000107,
|
||||
0x00000043, 0x0000008a, 0x0004003d, 0x00000006, 0x00000108, 0x00000107, 0x000500ba, 0x0000003b,
|
||||
0x00000109, 0x00000108, 0x00000067, 0x000300f7, 0x0000010b, 0x00000000, 0x000400fa, 0x00000109,
|
||||
0x0000010a, 0x0000010b, 0x000200f8, 0x0000010a, 0x00050081, 0x00000040, 0x0000010f, 0x0000006e,
|
||||
0x0000018f, 0x0006000c, 0x00000040, 0x00000110, 0x00000001, 0x00000008, 0x0000010f, 0x0004006e,
|
||||
0x00000030, 0x00000111, 0x00000110, 0x00050041, 0x0000006b, 0x00000113, 0x00000043, 0x00000044,
|
||||
0x0004003d, 0x00000040, 0x00000114, 0x00000113, 0x0004006e, 0x00000030, 0x00000115, 0x00000114,
|
||||
0x00050082, 0x00000030, 0x00000117, 0x00000115, 0x00000199, 0x000200f9, 0x0000011a, 0x000200f8,
|
||||
0x0000011a, 0x000700f5, 0x00000006, 0x00000193, 0x00000016, 0x0000010a, 0x00000196, 0x0000011d,
|
||||
0x000700f5, 0x0000002f, 0x00000191, 0x00000119, 0x0000010a, 0x0000013a, 0x0000011d, 0x000500b3,
|
||||
0x0000003b, 0x00000120, 0x00000191, 0x0000006a, 0x000400f6, 0x0000011c, 0x0000011d, 0x00000000,
|
||||
0x000400fa, 0x00000120, 0x0000011b, 0x0000011c, 0x000200f8, 0x0000011b, 0x000200f9, 0x00000122,
|
||||
0x000200f8, 0x00000122, 0x000700f5, 0x00000006, 0x00000196, 0x00000193, 0x0000011b, 0x00000136,
|
||||
0x00000123, 0x000700f5, 0x0000002f, 0x00000194, 0x00000119, 0x0000011b, 0x00000138, 0x00000123,
|
||||
0x000500b3, 0x0000003b, 0x00000128, 0x00000194, 0x0000006a, 0x000400f6, 0x00000124, 0x00000123,
|
||||
0x00000000, 0x000400fa, 0x00000128, 0x00000123, 0x00000124, 0x000200f8, 0x00000123, 0x0004003d,
|
||||
0x000000ff, 0x0000012b, 0x0000012a, 0x00050050, 0x00000030, 0x0000012f, 0x00000194, 0x00000191,
|
||||
0x00050080, 0x00000030, 0x00000130, 0x00000111, 0x0000012f, 0x0008000c, 0x00000030, 0x00000133,
|
||||
0x00000001, 0x0000002d, 0x00000130, 0x00000131, 0x00000117, 0x00050062, 0x00000007, 0x00000134,
|
||||
0x0000012b, 0x00000133, 0x00050051, 0x00000006, 0x00000135, 0x00000134, 0x00000000, 0x0007000c,
|
||||
0x00000006, 0x00000136, 0x00000001, 0x00000028, 0x00000196, 0x00000135, 0x00050080, 0x0000002f,
|
||||
0x00000138, 0x00000194, 0x0000006a, 0x000200f9, 0x00000122, 0x000200f8, 0x00000124, 0x000200f9,
|
||||
0x0000011d, 0x000200f8, 0x0000011d, 0x00050080, 0x0000002f, 0x0000013a, 0x00000191, 0x0000006a,
|
||||
0x000200f9, 0x0000011a, 0x000200f8, 0x0000011c, 0x000200f9, 0x0000010b, 0x000200f8, 0x0000010b,
|
||||
0x000700f5, 0x00000006, 0x00000192, 0x00000016, 0x000000f3, 0x00000193, 0x0000011c, 0x0004003d,
|
||||
0x000000ff, 0x0000013c, 0x0000013b, 0x0007000c, 0x00000006, 0x00000140, 0x00000001, 0x00000028,
|
||||
0x000000fe, 0x00000192, 0x00050085, 0x00000006, 0x00000143, 0x00000140, 0x000000f0, 0x00070050,
|
||||
0x00000007, 0x00000144, 0x00000143, 0x00000143, 0x00000143, 0x00000143, 0x00040063, 0x0000013c,
|
||||
0x0000003a, 0x00000144, 0x000200f9, 0x00000147, 0x000200f8, 0x00000147, 0x000100fd, 0x00010038,
|
||||
};
|
||||
+7
-1
@@ -35,6 +35,8 @@ typedef struct { // the translator's a
|
||||
uint8_t attr_type[32], frag_out_type[8]; // GFX_TYPE_*
|
||||
uint16_t local[3]; uint32_t shared_bytes;
|
||||
uint8_t depth_minus_w;
|
||||
uint8_t mvprev; // VS: also write last frame's position (patch 0015,
|
||||
// vk_mvprev.c); packed as bit 7 of the depth_minus_w byte
|
||||
} GfxShaderAnswers;
|
||||
typedef struct GfxShader GfxShader; // SPIR-V words + parsed reflection; owned by the cache
|
||||
const GfxShader *gfx_shader_get(int stage, const uint8_t *control, const uint8_t *code, uint64_t code_size,
|
||||
@@ -73,6 +75,9 @@ size_t gfx_shader_warm_list(GfxWarmEnt **out); // malloc'd; recorde
|
||||
const GfxShader *gfx_shader_load_private(const uint8_t iid[32], int *owned);
|
||||
void gfx_shader_release_private(const GfxShader *s);
|
||||
const uint32_t *gfx_shader_spirv(const GfxShader *s, uint64_t *words);
|
||||
// A shader of the runtime's own (SPIR-V and a .res.json text as the translator writes them), outside the cache and
|
||||
// never freed; its key is the sha256 of both. NULL when the SPIR-V is malformed.
|
||||
const GfxShader *gfx_shader_own(const uint32_t *spirv, uint64_t words, const char *res_json);
|
||||
// vk_hdr.c: with TOTK_HDR=1, replaces a fragment shader's SPIR-V (malloc'd, *words long) by the HDR tonemap when it is
|
||||
// the game's tonemap; gfx_shader.c calls it for every fragment shader entering the cache.
|
||||
void vk_hdr_shader_swap(uint32_t **spirv, uint64_t *words);
|
||||
@@ -97,7 +102,8 @@ int gfx_shader_stage(const GfxShader *s); // GFX_STAGE_VS, GFX
|
||||
int gfx_shader_resources(const GfxShader *s, int kind, const GfxShaderRes **res); // count of GFX_RES_* kind; array in *res
|
||||
uint32_t gfx_shader_slot_mask(const GfxShader *s); // texture/image slots whose type its translation read
|
||||
uint32_t gfx_shader_inputs(const GfxShader *s, GfxShaderIo io[32]); // Input Locations (VS attributes, FS varyings) as a mask
|
||||
uint32_t gfx_shader_outputs(const GfxShader *s, GfxShaderIo io[32]); // Output Locations (VS varyings, FS colour targets)
|
||||
uint32_t gfx_shader_outputs(const GfxShader *s, GfxShaderIo io[32]);
|
||||
uint32_t gfx_shader_mvprev_banks(const GfxShader *s); // a last-frame variant: the banks (bit n: cn) its position reads // Output Locations (VS varyings, FS colour targets)
|
||||
void gfx_shader_local_size(const GfxShader *s, uint32_t local[3]); // CS: the LocalSize execution mode; 1, 1, 1 otherwise
|
||||
|
||||
// ---- back end (gfx_null.c, vk_*.c). Called by the NVN front end (nvn_api_*.c, nvn_ext.c) on guest threads.
|
||||
|
||||
+38
-3
@@ -86,6 +86,7 @@ struct GfxShader {
|
||||
uint32_t in_mask, out_mask; // user Locations of Input / Output variables
|
||||
GfxShaderIo in[32], out[32];
|
||||
uint32_t local[3];
|
||||
uint32_t mvprev_banks; // .res.json "mvprev_banks" (patch 0015), 0 none
|
||||
};
|
||||
|
||||
static PlatMutex cache_lock = PLAT_MUTEX_INIT; // memory cache
|
||||
@@ -593,6 +594,25 @@ static GfxShader *disk_load_from(const uint8_t key[32], const uint8_t *iid, int
|
||||
return s;
|
||||
}
|
||||
|
||||
const GfxShader *gfx_shader_own(const uint32_t *spirv, uint64_t words, const char *res_json) {
|
||||
if (words < 5 || spirv[0] != 0x07230203 || !spirv_complete(spirv, words) ||
|
||||
!reflection_valid(res_json, strlen(res_json)))
|
||||
return NULL;
|
||||
GfxShader *s = calloc(1, sizeof *s);
|
||||
if (!s) return NULL;
|
||||
size_t rn = strlen(res_json);
|
||||
uint8_t *buf = malloc(words * 4 + rn);
|
||||
s->spirv = malloc(words * 4), s->reflection = malloc(rn + 1);
|
||||
if (!buf || !s->spirv || !s->reflection) rt_fatal("gfx_shader: out of memory");
|
||||
memcpy(buf, spirv, words * 4), memcpy(buf + words * 4, res_json, rn);
|
||||
rt_sha256(buf, words * 4 + rn, s->key);
|
||||
free(buf);
|
||||
memcpy(s->spirv, spirv, words * 4), memcpy(s->reflection, res_json, rn + 1);
|
||||
s->words = words, s->reflection_size = rn;
|
||||
reflect(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
// The pack, then the loose files (a newer translation of a key the pack holds a bad copy of); NULL when neither has a
|
||||
// valid pair (the caller translates again and writes loose files).
|
||||
static GfxShader *disk_load(const uint8_t key[32], const uint8_t *iid) {
|
||||
@@ -826,6 +846,7 @@ static void reflect(GfxShader *s) {
|
||||
char stage[8];
|
||||
json_str(js, js + strlen(js), "stage", stage, sizeof stage);
|
||||
s->stage = !strcmp(stage, "cs") ? GFX_STAGE_CS : !strcmp(stage, "ps") ? GFX_STAGE_FS : GFX_STAGE_VS;
|
||||
s->mvprev_banks = (uint32_t)json_int(js, js + strlen(js), "mvprev_banks", 0);
|
||||
for (int k = 0; k < GFX_RES_KINDS; k++) {
|
||||
int count = json_objects(js, kinds[k], NULL, s, k);
|
||||
s->res[k] = calloc(count ? count : 1, sizeof *s->res[k]);
|
||||
@@ -1037,7 +1058,7 @@ static size_t pack_answers(uint8_t *o, const GfxShaderAnswers *a) {
|
||||
memcpy(p, a->frag_out_type, 8), p += 8;
|
||||
for (int i = 0; i < 3; i++) *p++ = (uint8_t)a->local[i], *p++ = (uint8_t)(a->local[i] >> 8);
|
||||
put_u32(&p, a->shared_bytes);
|
||||
*p++ = a->depth_minus_w;
|
||||
*p++ = (uint8_t)(a->depth_minus_w | (a->mvprev ? 0x80 : 0));
|
||||
return (size_t)(p - o);
|
||||
}
|
||||
|
||||
@@ -1069,6 +1090,7 @@ static void mask_answers(const GfxShaderAnswers *a, const UseMask *m, GfxShaderA
|
||||
if (m->fouts >> i & 1) o->frag_out_type[i] = a->frag_out_type[i];
|
||||
memcpy(o->local, a->local, sizeof o->local);
|
||||
o->shared_bytes = a->shared_bytes;
|
||||
o->mvprev = a->mvprev; // another translation, not an answer the use mask covers
|
||||
}
|
||||
|
||||
// The cache key of an input id under the current translator.
|
||||
@@ -1148,6 +1170,7 @@ static size_t build_request(char *o0, const char *hex, const char *stage, const
|
||||
if (!strcmp(stage, "ps") && (control[0x718] & 1)) o += sprintf(o, ",\"early_fragment_tests\":true");
|
||||
int si = !strcmp(stage, "vs") ? 0 : !strcmp(stage, "cs") ? 2 : 1; // gfx_shader_set_host answers
|
||||
if (!host_f64) o += sprintf(o, ",\"softfloat64\":true");
|
||||
if (a && a->mvprev && !strcmp(stage, "vs")) o += sprintf(o, ",\"mvprev\":true");
|
||||
if (host_sbuf) o += sprintf(o, ",\"sbuf_align\":%u", host_sbuf);
|
||||
if (host_sg[si] != 32) o += sprintf(o, ",\"subgroup\":%u", host_sg[si]);
|
||||
if (host_gather) o += sprintf(o, ",\"gather_bias\":%u", host_gather);
|
||||
@@ -1350,8 +1373,18 @@ static const GfxShader *translate_one(const Req *r, int stage, const uint8_t *co
|
||||
if (!req) return plat_file_delete(code_path), fail("out of memory", hex);
|
||||
build_request(req, hex, r->stage_name, code_path, r->c1, r->c1_len, a, control);
|
||||
int ok = svc_request(req, reply, sizeof reply);
|
||||
// TOTK_XLATE_KEEP=1: a last-frame variant's job stays as <key>.job (one request line) with its code, to run the
|
||||
// translator on by hand (totk_xlate --serve < <key>.job)
|
||||
static int keep = -1;
|
||||
if (keep < 0) keep = rt_knob("XLATE_KEEP", "0");
|
||||
int kept = keep && a && a->mvprev;
|
||||
if (kept) {
|
||||
char job_path[PATH_CAP + 80];
|
||||
snprintf(job_path, sizeof job_path, "%s/%s.job", cache_dir, hex);
|
||||
write_file(job_path, req, strlen(req));
|
||||
}
|
||||
free(req);
|
||||
plat_file_delete(code_path);
|
||||
if (!kept) plat_file_delete(code_path);
|
||||
if (!ok) return fail("translator unavailable", hex);
|
||||
if (strncmp(reply, "ok ", 3)) {
|
||||
plat_file_delete(spv_path), plat_file_delete(res_path);
|
||||
@@ -1706,6 +1739,8 @@ uint32_t gfx_shader_inputs(const GfxShader *s, GfxShaderIo io[32]) {
|
||||
return s->in_mask;
|
||||
}
|
||||
|
||||
uint32_t gfx_shader_mvprev_banks(const GfxShader *s) { return s->mvprev_banks; }
|
||||
|
||||
uint32_t gfx_shader_outputs(const GfxShader *s, GfxShaderIo io[32]) {
|
||||
if (io) memcpy(io, s->out, sizeof s->out);
|
||||
return s->out_mask;
|
||||
@@ -1795,7 +1830,7 @@ static void unpack_answers(const uint8_t *p, GfxShaderAnswers *a) {
|
||||
memcpy(a->frag_out_type, p, 8), p += 8;
|
||||
for (int i = 0; i < 3; i++) a->local[i] = (uint16_t)(p[0] | p[1] << 8), p += 2;
|
||||
memcpy(&a->shared_bytes, p, 4), p += 4;
|
||||
a->depth_minus_w = *p;
|
||||
a->depth_minus_w = *p & 0x7f, a->mvprev = *p >> 7;
|
||||
}
|
||||
|
||||
// Reads a record file's body (after its header) into *body; 0 when absent, and a file with another header is removed.
|
||||
|
||||
@@ -279,6 +279,24 @@ static LRESULT CALLBACK wnd_proc(HWND h, UINT m, WPARAM w, LPARAM l) {
|
||||
return DefWindowProcW(h, m, w, l);
|
||||
}
|
||||
|
||||
// Brings the game window to the front. Windows refuses SetForegroundWindow to a process that was not the last to get
|
||||
// input, which is the case for a game started by a frontend; attaching this thread's input queue to the foreground
|
||||
// window's thread for the call is the documented way around it. The window is not left topmost.
|
||||
static void raise_window(HWND h) {
|
||||
HWND fg = GetForegroundWindow();
|
||||
DWORD me = GetCurrentThreadId(), other = fg ? GetWindowThreadProcessId(fg, NULL) : 0;
|
||||
int attached = other && other != me && AttachThreadInput(me, other, TRUE);
|
||||
ShowWindow(h, IsIconic(h) ? SW_RESTORE : SW_SHOW);
|
||||
BringWindowToTop(h);
|
||||
SetForegroundWindow(h);
|
||||
SetActiveWindow(h);
|
||||
if (attached) AttachThreadInput(me, other, FALSE);
|
||||
if (GetForegroundWindow() != h) { // still refused: a topmost toggle puts it above the others without keeping it there
|
||||
SetWindowPos(h, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
||||
SetWindowPos(h, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
||||
}
|
||||
}
|
||||
|
||||
static void window_main(void *arg) {
|
||||
(void)arg;
|
||||
HINSTANCE inst = GetModuleHandleW(NULL);
|
||||
@@ -293,10 +311,10 @@ static void window_main(void *arg) {
|
||||
int cw, ch, ow, oh;
|
||||
cfg_window_size(&cw, &ch);
|
||||
outer_size(NULL, cw, ch, &ow, &oh);
|
||||
// The window opens behind the user's windows without taking focus: it shows the game's blank loading frames for a
|
||||
// while, and must not cover what the player works on meanwhile. The game still counts itself in focus until the
|
||||
// first WM_ACTIVATEAPP, so loading goes on, and keyboard input waits for the foreground (input_win.c). Its taskbar
|
||||
// button flashes until it is brought up, and TOTK_FULLSCREEN waits for that first activation.
|
||||
// The window opens behind the user's windows first and is then raised (raise_window): a game started by a frontend
|
||||
// has no other way to reach it. The game counts itself in focus until the first WM_ACTIVATEAPP, so loading goes on,
|
||||
// and keyboard input waits for the foreground (input_win.c). If Windows still refuses the foreground, the taskbar
|
||||
// button flashes, and TOTK_FULLSCREEN waits for the first activation.
|
||||
// TOTK_BACKGROUND_WINDOW=1 (test runs): no flashing, never fullscreen at start, and F11 keeps the window's place.
|
||||
int background = g_background = cfg_get("TOTK_BACKGROUND_WINDOW") && atoi(cfg_get("TOTK_BACKGROUND_WINDOW"));
|
||||
g_hwnd = CreateWindowExW(0, wc.lpszClassName, L"TotK Recomp", WND_STYLE, CW_USEDEFAULT, CW_USEDEFAULT, ow, oh, NULL,
|
||||
@@ -315,6 +333,7 @@ static void window_main(void *arg) {
|
||||
outer_size(g_hwnd, cw, ch, &ow, &oh);
|
||||
SetWindowPos(g_hwnd, HWND_BOTTOM, 0, 0, ow, oh, SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||
if (!background) {
|
||||
raise_window(g_hwnd); // a frontend's player may have no way to reach a window left behind
|
||||
FLASHWINFO fw = {.cbSize = sizeof fw, .hwnd = g_hwnd, .dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG};
|
||||
FlashWindowEx(&fw);
|
||||
g_fs_pending = cfg_fullscreen();
|
||||
|
||||
+267
-122
@@ -1,12 +1,38 @@
|
||||
// Generated by tools/build_shaders.py from runtime/shaders/hdr_tonemap.frag; do not edit.
|
||||
// source sha256 59af32c0b20e9783c36779cdd379db02b52c0652cc1916ebca1c62a2b38df71e
|
||||
// source sha256 a09ae2b13d844bb6a136d2c3cf38b3ec571191775a4c2f0b43a586bd355381fa
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
static const uint32_t hdr_tonemap_spv[] = {
|
||||
0x07230203, 0x00010300, 0x000d000b, 0x000001bf, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
|
||||
0x07230203, 0x00010300, 0x0008000b, 0x0000018a, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
|
||||
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
|
||||
0x0007000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000053, 0x0000011c, 0x00030010,
|
||||
0x00000004, 0x00000007, 0x00040047, 0x0000004f, 0x00000021, 0x00000001, 0x00040047, 0x0000004f,
|
||||
0x0007000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000053, 0x00000181, 0x00030010,
|
||||
0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d,
|
||||
0x00000000, 0x00050005, 0x0000000a, 0x76727563, 0x31662865, 0x0000003b, 0x00030005, 0x00000009,
|
||||
0x00000078, 0x00050005, 0x00000010, 0x76727563, 0x76283365, 0x003b3366, 0x00030005, 0x0000000f,
|
||||
0x00000078, 0x00050005, 0x00000014, 0x6378616d, 0x33667628, 0x0000003b, 0x00030005, 0x00000013,
|
||||
0x00000063, 0x00030005, 0x00000016, 0x00003079, 0x00040005, 0x0000002e, 0x61726170, 0x0000006d,
|
||||
0x00040005, 0x00000034, 0x61726170, 0x0000006d, 0x00040005, 0x00000039, 0x61726170, 0x0000006d,
|
||||
0x00030005, 0x0000004b, 0x00000078, 0x00050005, 0x0000004f, 0x745f7066, 0x6263745f, 0x0000385f,
|
||||
0x00030005, 0x00000053, 0x00007675, 0x00050005, 0x0000005c, 0x745f7066, 0x6263745f, 0x0000415f,
|
||||
0x00030005, 0x00000064, 0x00000077, 0x00030005, 0x00000067, 0x00003143, 0x00040006, 0x00000067,
|
||||
0x00000000, 0x00000076, 0x00040005, 0x00000069, 0x635f7066, 0x00000031, 0x00030005, 0x0000006f,
|
||||
0x00000067, 0x00030005, 0x00000071, 0x00003343, 0x00040006, 0x00000071, 0x00000000, 0x00000076,
|
||||
0x00040005, 0x00000073, 0x635f7066, 0x00000033, 0x00040005, 0x00000076, 0x656d6167, 0x00000000,
|
||||
0x00060005, 0x00000079, 0x70707553, 0x4274726f, 0x65666675, 0x00000072, 0x00040006, 0x00000079,
|
||||
0x00000000, 0x00646170, 0x00040006, 0x00000079, 0x00000001, 0x00726468, 0x00060005, 0x0000007b,
|
||||
0x70707573, 0x5f74726f, 0x66667562, 0x00007265, 0x00040005, 0x00000089, 0x6b616570, 0x00000000,
|
||||
0x00030005, 0x00000094, 0x006d756c, 0x00030005, 0x000000a6, 0x00726473, 0x00040005, 0x000000ab,
|
||||
0x6c5f7962, 0x00006d75, 0x00040005, 0x000000b2, 0x61726170, 0x0000006d, 0x00030005, 0x000000bb,
|
||||
0x00000063, 0x00040005, 0x000000bd, 0x61726170, 0x0000006d, 0x00030005, 0x000000c7, 0x00000073,
|
||||
0x00040005, 0x000000c8, 0x61726170, 0x0000006d, 0x00030005, 0x000000cc, 0x0000006e, 0x00030005,
|
||||
0x000000d1, 0x0000006d, 0x00040005, 0x000000d2, 0x61726170, 0x0000006d, 0x00030005, 0x000000d5,
|
||||
0x00000064, 0x00040005, 0x000000e4, 0x64617267, 0x00000065, 0x00040005, 0x000000fa, 0x5f726473,
|
||||
0x00000063, 0x00030005, 0x00000115, 0x00006d73, 0x00040005, 0x00000116, 0x61726170, 0x0000006d,
|
||||
0x00030005, 0x00000119, 0x00006473, 0x00030005, 0x00000139, 0x00657568, 0x00030005, 0x0000013d,
|
||||
0x00006379, 0x00030005, 0x00000144, 0x00007379, 0x00040005, 0x00000159, 0x61726170, 0x0000006d,
|
||||
0x00030005, 0x00000160, 0x00706f74, 0x00040005, 0x00000161, 0x61726170, 0x0000006d, 0x00040005,
|
||||
0x00000164, 0x65656e6b, 0x00000000, 0x00030005, 0x0000016c, 0x00000072, 0x00040005, 0x00000181,
|
||||
0x6f6c6f63, 0x00000072, 0x00040047, 0x0000004f, 0x00000021, 0x00000001, 0x00040047, 0x0000004f,
|
||||
0x00000022, 0x00000000, 0x00040047, 0x00000053, 0x0000001e, 0x00000000, 0x00040047, 0x0000005c,
|
||||
0x00000021, 0x00000002, 0x00040047, 0x0000005c, 0x00000022, 0x00000000, 0x00040047, 0x00000066,
|
||||
0x00000006, 0x00000010, 0x00030047, 0x00000067, 0x00000002, 0x00050048, 0x00000067, 0x00000000,
|
||||
@@ -17,129 +43,248 @@ static const uint32_t hdr_tonemap_spv[] = {
|
||||
0x00000006, 0x00000010, 0x00030047, 0x00000079, 0x00000002, 0x00050048, 0x00000079, 0x00000000,
|
||||
0x00000023, 0x00000000, 0x00050048, 0x00000079, 0x00000001, 0x00000023, 0x000005a0, 0x00040047,
|
||||
0x0000007b, 0x00000021, 0x00000000, 0x00040047, 0x0000007b, 0x00000022, 0x00000000, 0x00040047,
|
||||
0x0000011c, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002,
|
||||
0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x0000000c, 0x00000006, 0x00000003, 0x0004002b,
|
||||
0x00000006, 0x00000017, 0x3e28aeed, 0x0004002b, 0x00000006, 0x00000019, 0x3e3851ec, 0x00020014,
|
||||
0x0000001a, 0x0004002b, 0x00000006, 0x0000001f, 0x3f800000, 0x0004002b, 0x00000006, 0x00000026,
|
||||
0x3f55d445, 0x00040015, 0x0000002f, 0x00000020, 0x00000000, 0x0004002b, 0x0000002f, 0x00000030,
|
||||
0x00000000, 0x0004002b, 0x0000002f, 0x00000035, 0x00000001, 0x00090019, 0x0000004c, 0x00000006,
|
||||
0x00000181, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002,
|
||||
0x00030016, 0x00000006, 0x00000020, 0x00040020, 0x00000007, 0x00000007, 0x00000006, 0x00040021,
|
||||
0x00000008, 0x00000006, 0x00000007, 0x00040017, 0x0000000c, 0x00000006, 0x00000003, 0x00040020,
|
||||
0x0000000d, 0x00000007, 0x0000000c, 0x00040021, 0x0000000e, 0x0000000c, 0x0000000d, 0x00040021,
|
||||
0x00000012, 0x00000006, 0x0000000d, 0x0004002b, 0x00000006, 0x00000017, 0x3e28aeed, 0x0004002b,
|
||||
0x00000006, 0x00000019, 0x3e3851ec, 0x00020014, 0x0000001a, 0x0004002b, 0x00000006, 0x0000001f,
|
||||
0x3f800000, 0x0004002b, 0x00000006, 0x00000026, 0x3f55d445, 0x00040015, 0x0000002f, 0x00000020,
|
||||
0x00000000, 0x0004002b, 0x0000002f, 0x00000030, 0x00000000, 0x0004002b, 0x0000002f, 0x00000035,
|
||||
0x00000001, 0x0004002b, 0x0000002f, 0x0000003a, 0x00000002, 0x00090019, 0x0000004c, 0x00000006,
|
||||
0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x0000004d,
|
||||
0x0000004c, 0x00040020, 0x0000004e, 0x00000000, 0x0000004d, 0x0004003b, 0x0000004e, 0x0000004f,
|
||||
0x00000000, 0x00040017, 0x00000051, 0x00000006, 0x00000004, 0x00040020, 0x00000052, 0x00000001,
|
||||
0x00000051, 0x0004003b, 0x00000052, 0x00000053, 0x00000001, 0x00040017, 0x00000054, 0x00000006,
|
||||
0x00000002, 0x0004002b, 0x00000006, 0x00000059, 0x461c4000, 0x0006002c, 0x0000000c, 0x0000005a,
|
||||
0x00000059, 0x00000059, 0x00000059, 0x0004003b, 0x0000004e, 0x0000005c, 0x00000000, 0x0004002b,
|
||||
0x0000002f, 0x00000065, 0x00001000, 0x0004001c, 0x00000066, 0x00000051, 0x00000065, 0x0003001e,
|
||||
0x00000067, 0x00000066, 0x00040020, 0x00000068, 0x00000002, 0x00000067, 0x0004003b, 0x00000068,
|
||||
0x00000069, 0x00000002, 0x00040015, 0x0000006a, 0x00000020, 0x00000001, 0x0004002b, 0x0000006a,
|
||||
0x0000006b, 0x00000000, 0x00040020, 0x0000006c, 0x00000002, 0x00000051, 0x0004001c, 0x00000070,
|
||||
0x00000051, 0x00000065, 0x0003001e, 0x00000071, 0x00000070, 0x00040020, 0x00000072, 0x00000002,
|
||||
0x00000071, 0x0004003b, 0x00000072, 0x00000073, 0x00000002, 0x0004002b, 0x0000002f, 0x00000077,
|
||||
0x0000005a, 0x0004001c, 0x00000078, 0x00000051, 0x00000077, 0x0004001e, 0x00000079, 0x00000078,
|
||||
0x00000051, 0x00040020, 0x0000007a, 0x00000002, 0x00000079, 0x0004003b, 0x0000007a, 0x0000007b,
|
||||
0x00000002, 0x0004002b, 0x0000006a, 0x0000007c, 0x00000001, 0x00040020, 0x0000007d, 0x00000002,
|
||||
0x00000006, 0x0004002b, 0x00000006, 0x00000080, 0x00000000, 0x0004002b, 0x00000006, 0x00000097,
|
||||
0x3e99096c, 0x0006002c, 0x0000000c, 0x000000c5, 0x00000080, 0x00000080, 0x00000080, 0x0004002b,
|
||||
0x00000006, 0x000000d8, 0x3f2aaaab, 0x0004002b, 0x00000006, 0x000000ff, 0x3f000000, 0x00040020,
|
||||
0x0000011b, 0x00000003, 0x00000051, 0x0004003b, 0x0000011b, 0x0000011c, 0x00000003, 0x00050036,
|
||||
0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x0000004d,
|
||||
0x00000050, 0x0000004f, 0x0004003d, 0x00000051, 0x00000055, 0x00000053, 0x0007004f, 0x00000054,
|
||||
0x00000056, 0x00000055, 0x00000055, 0x00000000, 0x00000001, 0x00050057, 0x00000051, 0x00000057,
|
||||
0x00000050, 0x00000056, 0x0008004f, 0x0000000c, 0x00000058, 0x00000057, 0x00000057, 0x00000000,
|
||||
0x00000001, 0x00000002, 0x0007000c, 0x0000000c, 0x0000005b, 0x00000001, 0x00000025, 0x00000058,
|
||||
0x0000005a, 0x0004003d, 0x0000004d, 0x0000005d, 0x0000005c, 0x00050057, 0x00000051, 0x00000060,
|
||||
0x0000005d, 0x00000056, 0x0008004f, 0x0000000c, 0x00000061, 0x00000060, 0x00000060, 0x00000000,
|
||||
0x00000001, 0x00000002, 0x00050081, 0x0000000c, 0x00000062, 0x0000005b, 0x00000061, 0x00060041,
|
||||
0x0000006c, 0x0000006d, 0x00000069, 0x0000006b, 0x0000006b, 0x0004003d, 0x00000051, 0x0000006e,
|
||||
0x0000006d, 0x00060041, 0x0000006c, 0x00000074, 0x00000073, 0x0000006b, 0x0000006b, 0x0004003d,
|
||||
0x00000051, 0x00000075, 0x00000074, 0x00060041, 0x0000007d, 0x0000007e, 0x0000007b, 0x0000007c,
|
||||
0x00000059, 0x00000059, 0x00000059, 0x0004003b, 0x0000004e, 0x0000005c, 0x00000000, 0x00040020,
|
||||
0x00000063, 0x00000007, 0x00000051, 0x0004002b, 0x0000002f, 0x00000065, 0x00001000, 0x0004001c,
|
||||
0x00000066, 0x00000051, 0x00000065, 0x0003001e, 0x00000067, 0x00000066, 0x00040020, 0x00000068,
|
||||
0x00000002, 0x00000067, 0x0004003b, 0x00000068, 0x00000069, 0x00000002, 0x00040015, 0x0000006a,
|
||||
0x00000020, 0x00000001, 0x0004002b, 0x0000006a, 0x0000006b, 0x00000000, 0x00040020, 0x0000006c,
|
||||
0x00000002, 0x00000051, 0x0004001c, 0x00000070, 0x00000051, 0x00000065, 0x0003001e, 0x00000071,
|
||||
0x00000070, 0x00040020, 0x00000072, 0x00000002, 0x00000071, 0x0004003b, 0x00000072, 0x00000073,
|
||||
0x00000002, 0x0004002b, 0x0000002f, 0x00000077, 0x0000005a, 0x0004001c, 0x00000078, 0x00000051,
|
||||
0x00000077, 0x0004001e, 0x00000079, 0x00000078, 0x00000051, 0x00040020, 0x0000007a, 0x00000002,
|
||||
0x00000079, 0x0004003b, 0x0000007a, 0x0000007b, 0x00000002, 0x0004002b, 0x0000006a, 0x0000007c,
|
||||
0x00000001, 0x00040020, 0x0000007d, 0x00000002, 0x00000006, 0x0004002b, 0x00000006, 0x00000080,
|
||||
0x00000000, 0x0004002b, 0x00000006, 0x00000097, 0x3e99096c, 0x0006002c, 0x0000000c, 0x000000c5,
|
||||
0x00000080, 0x00000080, 0x00000080, 0x0004002b, 0x00000006, 0x000000d8, 0x3f2aaaab, 0x0006002c,
|
||||
0x0000000c, 0x00000108, 0x0000001f, 0x0000001f, 0x0000001f, 0x0004002b, 0x00000006, 0x0000013f,
|
||||
0x3e59b3d0, 0x0004002b, 0x00000006, 0x00000140, 0x3f371759, 0x0004002b, 0x00000006, 0x00000141,
|
||||
0x3d93dd98, 0x0006002c, 0x0000000c, 0x00000142, 0x0000013f, 0x00000140, 0x00000141, 0x0004002b,
|
||||
0x00000006, 0x0000014a, 0x3727c5ac, 0x0004002b, 0x00000006, 0x00000157, 0x3f000000, 0x0004002b,
|
||||
0x00000006, 0x00000158, 0x3fc00000, 0x00040020, 0x00000180, 0x00000003, 0x00000051, 0x0004003b,
|
||||
0x00000180, 0x00000181, 0x00000003, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003,
|
||||
0x000200f8, 0x00000005, 0x0004003b, 0x0000000d, 0x0000004b, 0x00000007, 0x0004003b, 0x00000063,
|
||||
0x00000064, 0x00000007, 0x0004003b, 0x00000063, 0x0000006f, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x00000076, 0x00000007, 0x0004003b, 0x00000007, 0x00000082, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x00000089, 0x00000007, 0x0004003b, 0x00000007, 0x0000008d, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x00000094, 0x00000007, 0x0004003b, 0x00000007, 0x000000a6, 0x00000007, 0x0004003b, 0x0000000d,
|
||||
0x000000ab, 0x00000007, 0x0004003b, 0x0000000d, 0x000000ae, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x000000b2, 0x00000007, 0x0004003b, 0x0000000d, 0x000000bb, 0x00000007, 0x0004003b, 0x0000000d,
|
||||
0x000000bd, 0x00000007, 0x0004003b, 0x00000007, 0x000000c7, 0x00000007, 0x0004003b, 0x0000000d,
|
||||
0x000000c8, 0x00000007, 0x0004003b, 0x0000000d, 0x000000cc, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x000000d1, 0x00000007, 0x0004003b, 0x0000000d, 0x000000d2, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x000000d5, 0x00000007, 0x0004003b, 0x00000007, 0x000000e4, 0x00000007, 0x0004003b, 0x0000000d,
|
||||
0x000000fa, 0x00000007, 0x0004003b, 0x0000000d, 0x000000fd, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x00000115, 0x00000007, 0x0004003b, 0x0000000d, 0x00000116, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x00000119, 0x00000007, 0x0004003b, 0x00000007, 0x00000139, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x0000013d, 0x00000007, 0x0004003b, 0x00000007, 0x00000144, 0x00000007, 0x0004003b, 0x0000000d,
|
||||
0x00000159, 0x00000007, 0x0004003b, 0x00000007, 0x00000160, 0x00000007, 0x0004003b, 0x0000000d,
|
||||
0x00000161, 0x00000007, 0x0004003b, 0x00000007, 0x00000164, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x0000016c, 0x00000007, 0x0004003d, 0x0000004d, 0x00000050, 0x0000004f, 0x0004003d, 0x00000051,
|
||||
0x00000055, 0x00000053, 0x0007004f, 0x00000054, 0x00000056, 0x00000055, 0x00000055, 0x00000000,
|
||||
0x00000001, 0x00050057, 0x00000051, 0x00000057, 0x00000050, 0x00000056, 0x0008004f, 0x0000000c,
|
||||
0x00000058, 0x00000057, 0x00000057, 0x00000000, 0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
|
||||
0x0000005b, 0x00000001, 0x00000025, 0x00000058, 0x0000005a, 0x0004003d, 0x0000004d, 0x0000005d,
|
||||
0x0000005c, 0x0004003d, 0x00000051, 0x0000005e, 0x00000053, 0x0007004f, 0x00000054, 0x0000005f,
|
||||
0x0000005e, 0x0000005e, 0x00000000, 0x00000001, 0x00050057, 0x00000051, 0x00000060, 0x0000005d,
|
||||
0x0000005f, 0x0008004f, 0x0000000c, 0x00000061, 0x00000060, 0x00000060, 0x00000000, 0x00000001,
|
||||
0x00000002, 0x00050081, 0x0000000c, 0x00000062, 0x0000005b, 0x00000061, 0x0003003e, 0x0000004b,
|
||||
0x00000062, 0x00060041, 0x0000006c, 0x0000006d, 0x00000069, 0x0000006b, 0x0000006b, 0x0004003d,
|
||||
0x00000051, 0x0000006e, 0x0000006d, 0x0003003e, 0x00000064, 0x0000006e, 0x00060041, 0x0000006c,
|
||||
0x00000074, 0x00000073, 0x0000006b, 0x0000006b, 0x0004003d, 0x00000051, 0x00000075, 0x00000074,
|
||||
0x0003003e, 0x0000006f, 0x00000075, 0x00060041, 0x0000007d, 0x0000007e, 0x0000007b, 0x0000007c,
|
||||
0x00000030, 0x0004003d, 0x00000006, 0x0000007f, 0x0000007e, 0x000500ba, 0x0000001a, 0x00000081,
|
||||
0x0000007f, 0x00000080, 0x000600a9, 0x00000006, 0x000001bd, 0x00000081, 0x0000007f, 0x0000001f,
|
||||
0x00060041, 0x0000007d, 0x0000008a, 0x0000007b, 0x0000007c, 0x00000035, 0x0004003d, 0x00000006,
|
||||
0x0000008b, 0x0000008a, 0x000500ba, 0x0000001a, 0x0000008c, 0x0000008b, 0x0000001f, 0x000600a9,
|
||||
0x00000006, 0x000001be, 0x0000008c, 0x0000008b, 0x0000001f, 0x00050051, 0x00000006, 0x00000096,
|
||||
0x00000062, 0x00000000, 0x00050085, 0x00000006, 0x00000098, 0x00000096, 0x00000097, 0x00050051,
|
||||
0x00000006, 0x0000009a, 0x00000062, 0x00000001, 0x00050051, 0x00000006, 0x0000009c, 0x0000006e,
|
||||
0x00000000, 0x00050085, 0x00000006, 0x0000009d, 0x0000009a, 0x0000009c, 0x00050081, 0x00000006,
|
||||
0x0000009e, 0x00000098, 0x0000009d, 0x00050051, 0x00000006, 0x000000a0, 0x00000062, 0x00000002,
|
||||
0x00050051, 0x00000006, 0x000000a2, 0x0000006e, 0x00000001, 0x00050085, 0x00000006, 0x000000a3,
|
||||
0x0000007f, 0x00000080, 0x000300f7, 0x00000084, 0x00000000, 0x000400fa, 0x00000081, 0x00000083,
|
||||
0x00000087, 0x000200f8, 0x00000083, 0x00060041, 0x0000007d, 0x00000085, 0x0000007b, 0x0000007c,
|
||||
0x00000030, 0x0004003d, 0x00000006, 0x00000086, 0x00000085, 0x0003003e, 0x00000082, 0x00000086,
|
||||
0x000200f9, 0x00000084, 0x000200f8, 0x00000087, 0x0003003e, 0x00000082, 0x0000001f, 0x000200f9,
|
||||
0x00000084, 0x000200f8, 0x00000084, 0x0004003d, 0x00000006, 0x00000088, 0x00000082, 0x0003003e,
|
||||
0x00000076, 0x00000088, 0x00060041, 0x0000007d, 0x0000008a, 0x0000007b, 0x0000007c, 0x00000035,
|
||||
0x0004003d, 0x00000006, 0x0000008b, 0x0000008a, 0x000500ba, 0x0000001a, 0x0000008c, 0x0000008b,
|
||||
0x0000001f, 0x000300f7, 0x0000008f, 0x00000000, 0x000400fa, 0x0000008c, 0x0000008e, 0x00000092,
|
||||
0x000200f8, 0x0000008e, 0x00060041, 0x0000007d, 0x00000090, 0x0000007b, 0x0000007c, 0x00000035,
|
||||
0x0004003d, 0x00000006, 0x00000091, 0x00000090, 0x0003003e, 0x0000008d, 0x00000091, 0x000200f9,
|
||||
0x0000008f, 0x000200f8, 0x00000092, 0x0003003e, 0x0000008d, 0x0000001f, 0x000200f9, 0x0000008f,
|
||||
0x000200f8, 0x0000008f, 0x0004003d, 0x00000006, 0x00000093, 0x0000008d, 0x0003003e, 0x00000089,
|
||||
0x00000093, 0x00050041, 0x00000007, 0x00000095, 0x0000004b, 0x00000030, 0x0004003d, 0x00000006,
|
||||
0x00000096, 0x00000095, 0x00050085, 0x00000006, 0x00000098, 0x00000096, 0x00000097, 0x00050041,
|
||||
0x00000007, 0x00000099, 0x0000004b, 0x00000035, 0x0004003d, 0x00000006, 0x0000009a, 0x00000099,
|
||||
0x00050041, 0x00000007, 0x0000009b, 0x00000064, 0x00000030, 0x0004003d, 0x00000006, 0x0000009c,
|
||||
0x0000009b, 0x00050085, 0x00000006, 0x0000009d, 0x0000009a, 0x0000009c, 0x00050081, 0x00000006,
|
||||
0x0000009e, 0x00000098, 0x0000009d, 0x00050041, 0x00000007, 0x0000009f, 0x0000004b, 0x0000003a,
|
||||
0x0004003d, 0x00000006, 0x000000a0, 0x0000009f, 0x00050041, 0x00000007, 0x000000a1, 0x00000064,
|
||||
0x00000035, 0x0004003d, 0x00000006, 0x000000a2, 0x000000a1, 0x00050085, 0x00000006, 0x000000a3,
|
||||
0x000000a0, 0x000000a2, 0x00050081, 0x00000006, 0x000000a4, 0x0000009e, 0x000000a3, 0x0007000c,
|
||||
0x00000006, 0x000000a5, 0x00000001, 0x00000028, 0x000000a4, 0x00000080, 0x0004007f, 0x00000006,
|
||||
0x000000a8, 0x000000a5, 0x0006000c, 0x00000006, 0x000000a9, 0x00000001, 0x0000001b, 0x000000a8,
|
||||
0x00050083, 0x00000006, 0x000000aa, 0x0000001f, 0x000000a9, 0x000500ba, 0x0000001a, 0x000000ad,
|
||||
0x000000a5, 0x00000080, 0x000300f7, 0x000000b0, 0x00000000, 0x000400fa, 0x000000ad, 0x000000af,
|
||||
0x000000b8, 0x000200f8, 0x000000af, 0x000500bc, 0x0000001a, 0x0000012a, 0x000000a5, 0x00000019,
|
||||
0x000300f7, 0x00000136, 0x00000000, 0x000400fa, 0x0000012a, 0x0000012b, 0x00000130, 0x000200f8,
|
||||
0x0000012b, 0x000200f9, 0x00000136, 0x000200f8, 0x00000130, 0x00050083, 0x00000006, 0x00000133,
|
||||
0x000000a5, 0x00000019, 0x00050085, 0x00000006, 0x00000134, 0x00000026, 0x00000133, 0x00050081,
|
||||
0x00000006, 0x00000135, 0x00000017, 0x00000134, 0x000200f9, 0x00000136, 0x000200f8, 0x00000136,
|
||||
0x000700f5, 0x00000006, 0x000001b7, 0x000000aa, 0x0000012b, 0x00000135, 0x00000130, 0x00050088,
|
||||
0x00000006, 0x000000b6, 0x000001b7, 0x000000a5, 0x0005008e, 0x0000000c, 0x000000b7, 0x00000062,
|
||||
0x000000b6, 0x000200f9, 0x000000b0, 0x000200f8, 0x000000b8, 0x000200f9, 0x000000b0, 0x000200f8,
|
||||
0x000000b0, 0x000700f5, 0x0000000c, 0x000001b8, 0x000000b7, 0x00000136, 0x00000062, 0x000000b8,
|
||||
0x000500bc, 0x0000001a, 0x0000014c, 0x00000096, 0x00000019, 0x000300f7, 0x00000158, 0x00000000,
|
||||
0x000400fa, 0x0000014c, 0x0000014d, 0x00000152, 0x000200f8, 0x0000014d, 0x0004007f, 0x00000006,
|
||||
0x0000014f, 0x00000096, 0x0006000c, 0x00000006, 0x00000150, 0x00000001, 0x0000001b, 0x0000014f,
|
||||
0x00050083, 0x00000006, 0x00000151, 0x0000001f, 0x00000150, 0x000200f9, 0x00000158, 0x000200f8,
|
||||
0x00000152, 0x00050083, 0x00000006, 0x00000155, 0x00000096, 0x00000019, 0x00050085, 0x00000006,
|
||||
0x00000156, 0x00000026, 0x00000155, 0x00050081, 0x00000006, 0x00000157, 0x00000017, 0x00000156,
|
||||
0x000200f9, 0x00000158, 0x000200f8, 0x00000158, 0x000700f5, 0x00000006, 0x000001b9, 0x00000151,
|
||||
0x0000014d, 0x00000157, 0x00000152, 0x000500bc, 0x0000001a, 0x0000015f, 0x0000009a, 0x00000019,
|
||||
0x000300f7, 0x0000016b, 0x00000000, 0x000400fa, 0x0000015f, 0x00000160, 0x00000165, 0x000200f8,
|
||||
0x00000160, 0x0004007f, 0x00000006, 0x00000162, 0x0000009a, 0x0006000c, 0x00000006, 0x00000163,
|
||||
0x00000001, 0x0000001b, 0x00000162, 0x00050083, 0x00000006, 0x00000164, 0x0000001f, 0x00000163,
|
||||
0x000200f9, 0x0000016b, 0x000200f8, 0x00000165, 0x00050083, 0x00000006, 0x00000168, 0x0000009a,
|
||||
0x00000019, 0x00050085, 0x00000006, 0x00000169, 0x00000026, 0x00000168, 0x00050081, 0x00000006,
|
||||
0x0000016a, 0x00000017, 0x00000169, 0x000200f9, 0x0000016b, 0x000200f8, 0x0000016b, 0x000700f5,
|
||||
0x00000006, 0x000001ba, 0x00000164, 0x00000160, 0x0000016a, 0x00000165, 0x000500bc, 0x0000001a,
|
||||
0x00000172, 0x000000a0, 0x00000019, 0x000300f7, 0x0000017e, 0x00000000, 0x000400fa, 0x00000172,
|
||||
0x00000173, 0x00000178, 0x000200f8, 0x00000173, 0x0004007f, 0x00000006, 0x00000175, 0x000000a0,
|
||||
0x0006000c, 0x00000006, 0x00000176, 0x00000001, 0x0000001b, 0x00000175, 0x00050083, 0x00000006,
|
||||
0x00000177, 0x0000001f, 0x00000176, 0x000200f9, 0x0000017e, 0x000200f8, 0x00000178, 0x00050083,
|
||||
0x00000006, 0x0000017b, 0x000000a0, 0x00000019, 0x00050085, 0x00000006, 0x0000017c, 0x00000026,
|
||||
0x0000017b, 0x00050081, 0x00000006, 0x0000017d, 0x00000017, 0x0000017c, 0x000200f9, 0x0000017e,
|
||||
0x000200f8, 0x0000017e, 0x000700f5, 0x00000006, 0x000001bb, 0x00000177, 0x00000173, 0x0000017d,
|
||||
0x00000178, 0x00060050, 0x0000000c, 0x00000146, 0x000001b9, 0x000001ba, 0x000001bb, 0x00050085,
|
||||
0x00000006, 0x000000c2, 0x000000aa, 0x000000aa, 0x00060050, 0x0000000c, 0x000000c3, 0x000000c2,
|
||||
0x000000c2, 0x000000c2, 0x0008000c, 0x0000000c, 0x000000c4, 0x00000001, 0x0000002e, 0x000001b8,
|
||||
0x00000146, 0x000000c3, 0x0007000c, 0x0000000c, 0x000000c6, 0x00000001, 0x00000028, 0x000000c4,
|
||||
0x000000c5, 0x00050051, 0x00000006, 0x00000183, 0x000000c6, 0x00000000, 0x00050051, 0x00000006,
|
||||
0x00000185, 0x000000c6, 0x00000001, 0x00050051, 0x00000006, 0x00000187, 0x000000c6, 0x00000002,
|
||||
0x0007000c, 0x00000006, 0x00000188, 0x00000001, 0x00000028, 0x00000185, 0x00000187, 0x0007000c,
|
||||
0x00000006, 0x00000189, 0x00000001, 0x00000028, 0x00000183, 0x00000188, 0x0007000c, 0x00000006,
|
||||
0x000000cb, 0x00000001, 0x00000028, 0x00000189, 0x0000001f, 0x00060050, 0x0000000c, 0x000000cf,
|
||||
0x000000cb, 0x000000cb, 0x000000cb, 0x00050088, 0x0000000c, 0x000000d0, 0x000000c6, 0x000000cf,
|
||||
0x00050051, 0x00000006, 0x0000018d, 0x000000d0, 0x00000000, 0x00050051, 0x00000006, 0x0000018f,
|
||||
0x000000d0, 0x00000001, 0x00050051, 0x00000006, 0x00000191, 0x000000d0, 0x00000002, 0x0007000c,
|
||||
0x00000006, 0x00000192, 0x00000001, 0x00000028, 0x0000018f, 0x00000191, 0x0007000c, 0x00000006,
|
||||
0x00000193, 0x00000001, 0x00000028, 0x0000018d, 0x00000192, 0x00050085, 0x00000006, 0x000000d9,
|
||||
0x0000018d, 0x000000d8, 0x00050081, 0x00000006, 0x000000de, 0x0000018f, 0x00000191, 0x00050051,
|
||||
0x00000006, 0x000000e0, 0x0000006e, 0x00000002, 0x00050085, 0x00000006, 0x000000e1, 0x000000de,
|
||||
0x000000e0, 0x00050081, 0x00000006, 0x000000e2, 0x000000d9, 0x000000e1, 0x00050083, 0x00000006,
|
||||
0x000000e3, 0x000000e2, 0x0000001f, 0x00050051, 0x00000006, 0x000000e6, 0x00000075, 0x00000001,
|
||||
0x00050085, 0x00000006, 0x000000e9, 0x000000e3, 0x000000e3, 0x00050083, 0x00000006, 0x000000ea,
|
||||
0x0000001f, 0x000000e9, 0x00050085, 0x00000006, 0x000000eb, 0x000000e6, 0x000000ea, 0x00050051,
|
||||
0x00000006, 0x000000ed, 0x00000075, 0x00000000, 0x00050081, 0x00000006, 0x000000ee, 0x000000eb,
|
||||
0x000000ed, 0x00060050, 0x0000000c, 0x000000f2, 0x00000193, 0x00000193, 0x00000193, 0x00050083,
|
||||
0x0000000c, 0x000000f3, 0x000000d0, 0x000000f2, 0x0005008e, 0x0000000c, 0x000000f5, 0x000000f3,
|
||||
0x000000ee, 0x00050081, 0x0000000c, 0x000000f8, 0x000000f5, 0x000000f2, 0x0005008e, 0x0000000c,
|
||||
0x000000f9, 0x000000f8, 0x000000cb, 0x00050051, 0x00000006, 0x00000197, 0x000000f9, 0x00000000,
|
||||
0x00050051, 0x00000006, 0x00000199, 0x000000f9, 0x00000001, 0x00050051, 0x00000006, 0x0000019b,
|
||||
0x000000f9, 0x00000002, 0x0007000c, 0x00000006, 0x0000019c, 0x00000001, 0x00000028, 0x00000199,
|
||||
0x0000019b, 0x0007000c, 0x00000006, 0x0000019d, 0x00000001, 0x00000028, 0x00000197, 0x0000019c,
|
||||
0x00050085, 0x00000006, 0x00000101, 0x000000ff, 0x000001be, 0x000500ba, 0x0000001a, 0x00000104,
|
||||
0x0000019d, 0x00000101, 0x000300f7, 0x00000106, 0x00000000, 0x000400fa, 0x00000104, 0x00000105,
|
||||
0x00000106, 0x000200f8, 0x00000105, 0x00050083, 0x00000006, 0x0000010a, 0x000001be, 0x00000101,
|
||||
0x00050083, 0x00000006, 0x0000010f, 0x0000019d, 0x00000101, 0x0004007f, 0x00000006, 0x00000110,
|
||||
0x0000010f, 0x00050088, 0x00000006, 0x00000112, 0x00000110, 0x0000010a, 0x0006000c, 0x00000006,
|
||||
0x00000113, 0x00000001, 0x0000001b, 0x00000112, 0x00050083, 0x00000006, 0x00000114, 0x0000001f,
|
||||
0x00000113, 0x00050085, 0x00000006, 0x00000115, 0x0000010a, 0x00000114, 0x00050081, 0x00000006,
|
||||
0x00000116, 0x00000101, 0x00000115, 0x00050088, 0x00000006, 0x00000118, 0x00000116, 0x0000019d,
|
||||
0x0005008e, 0x0000000c, 0x0000011a, 0x000000f9, 0x00000118, 0x000200f9, 0x00000106, 0x000200f8,
|
||||
0x00000106, 0x000700f5, 0x0000000c, 0x000001bc, 0x000000f9, 0x0000017e, 0x0000011a, 0x00000105,
|
||||
0x0007000c, 0x0000000c, 0x0000011e, 0x00000001, 0x00000028, 0x000001bc, 0x000000c5, 0x0005008e,
|
||||
0x0000000c, 0x00000120, 0x0000011e, 0x000001bd, 0x00050051, 0x00000006, 0x00000121, 0x00000120,
|
||||
0x00000000, 0x00050051, 0x00000006, 0x00000122, 0x00000120, 0x00000001, 0x00050051, 0x00000006,
|
||||
0x00000123, 0x00000120, 0x00000002, 0x00070050, 0x00000051, 0x00000124, 0x00000121, 0x00000122,
|
||||
0x00000123, 0x00000080, 0x0003003e, 0x0000011c, 0x00000124, 0x000100fd, 0x00010038,
|
||||
0x00000006, 0x000000a5, 0x00000001, 0x00000028, 0x000000a4, 0x00000080, 0x0003003e, 0x00000094,
|
||||
0x000000a5, 0x0004003d, 0x00000006, 0x000000a7, 0x00000094, 0x0004007f, 0x00000006, 0x000000a8,
|
||||
0x000000a7, 0x0006000c, 0x00000006, 0x000000a9, 0x00000001, 0x0000001b, 0x000000a8, 0x00050083,
|
||||
0x00000006, 0x000000aa, 0x0000001f, 0x000000a9, 0x0003003e, 0x000000a6, 0x000000aa, 0x0004003d,
|
||||
0x00000006, 0x000000ac, 0x00000094, 0x000500ba, 0x0000001a, 0x000000ad, 0x000000ac, 0x00000080,
|
||||
0x000300f7, 0x000000b0, 0x00000000, 0x000400fa, 0x000000ad, 0x000000af, 0x000000b8, 0x000200f8,
|
||||
0x000000af, 0x0004003d, 0x0000000c, 0x000000b1, 0x0000004b, 0x0004003d, 0x00000006, 0x000000b3,
|
||||
0x00000094, 0x0003003e, 0x000000b2, 0x000000b3, 0x00050039, 0x00000006, 0x000000b4, 0x0000000a,
|
||||
0x000000b2, 0x0004003d, 0x00000006, 0x000000b5, 0x00000094, 0x00050088, 0x00000006, 0x000000b6,
|
||||
0x000000b4, 0x000000b5, 0x0005008e, 0x0000000c, 0x000000b7, 0x000000b1, 0x000000b6, 0x0003003e,
|
||||
0x000000ae, 0x000000b7, 0x000200f9, 0x000000b0, 0x000200f8, 0x000000b8, 0x0004003d, 0x0000000c,
|
||||
0x000000b9, 0x0000004b, 0x0003003e, 0x000000ae, 0x000000b9, 0x000200f9, 0x000000b0, 0x000200f8,
|
||||
0x000000b0, 0x0004003d, 0x0000000c, 0x000000ba, 0x000000ae, 0x0003003e, 0x000000ab, 0x000000ba,
|
||||
0x0004003d, 0x0000000c, 0x000000bc, 0x000000ab, 0x0004003d, 0x0000000c, 0x000000be, 0x0000004b,
|
||||
0x0003003e, 0x000000bd, 0x000000be, 0x00050039, 0x0000000c, 0x000000bf, 0x00000010, 0x000000bd,
|
||||
0x0004003d, 0x00000006, 0x000000c0, 0x000000a6, 0x0004003d, 0x00000006, 0x000000c1, 0x000000a6,
|
||||
0x00050085, 0x00000006, 0x000000c2, 0x000000c0, 0x000000c1, 0x00060050, 0x0000000c, 0x000000c3,
|
||||
0x000000c2, 0x000000c2, 0x000000c2, 0x0008000c, 0x0000000c, 0x000000c4, 0x00000001, 0x0000002e,
|
||||
0x000000bc, 0x000000bf, 0x000000c3, 0x0007000c, 0x0000000c, 0x000000c6, 0x00000001, 0x00000028,
|
||||
0x000000c4, 0x000000c5, 0x0003003e, 0x000000bb, 0x000000c6, 0x0004003d, 0x0000000c, 0x000000c9,
|
||||
0x000000bb, 0x0003003e, 0x000000c8, 0x000000c9, 0x00050039, 0x00000006, 0x000000ca, 0x00000014,
|
||||
0x000000c8, 0x0007000c, 0x00000006, 0x000000cb, 0x00000001, 0x00000028, 0x000000ca, 0x0000001f,
|
||||
0x0003003e, 0x000000c7, 0x000000cb, 0x0004003d, 0x0000000c, 0x000000cd, 0x000000bb, 0x0004003d,
|
||||
0x00000006, 0x000000ce, 0x000000c7, 0x00060050, 0x0000000c, 0x000000cf, 0x000000ce, 0x000000ce,
|
||||
0x000000ce, 0x00050088, 0x0000000c, 0x000000d0, 0x000000cd, 0x000000cf, 0x0003003e, 0x000000cc,
|
||||
0x000000d0, 0x0004003d, 0x0000000c, 0x000000d3, 0x000000cc, 0x0003003e, 0x000000d2, 0x000000d3,
|
||||
0x00050039, 0x00000006, 0x000000d4, 0x00000014, 0x000000d2, 0x0003003e, 0x000000d1, 0x000000d4,
|
||||
0x00050041, 0x00000007, 0x000000d6, 0x000000cc, 0x00000030, 0x0004003d, 0x00000006, 0x000000d7,
|
||||
0x000000d6, 0x00050085, 0x00000006, 0x000000d9, 0x000000d7, 0x000000d8, 0x00050041, 0x00000007,
|
||||
0x000000da, 0x000000cc, 0x00000035, 0x0004003d, 0x00000006, 0x000000db, 0x000000da, 0x00050041,
|
||||
0x00000007, 0x000000dc, 0x000000cc, 0x0000003a, 0x0004003d, 0x00000006, 0x000000dd, 0x000000dc,
|
||||
0x00050081, 0x00000006, 0x000000de, 0x000000db, 0x000000dd, 0x00050041, 0x00000007, 0x000000df,
|
||||
0x00000064, 0x0000003a, 0x0004003d, 0x00000006, 0x000000e0, 0x000000df, 0x00050085, 0x00000006,
|
||||
0x000000e1, 0x000000de, 0x000000e0, 0x00050081, 0x00000006, 0x000000e2, 0x000000d9, 0x000000e1,
|
||||
0x00050083, 0x00000006, 0x000000e3, 0x000000e2, 0x0000001f, 0x0003003e, 0x000000d5, 0x000000e3,
|
||||
0x00050041, 0x00000007, 0x000000e5, 0x0000006f, 0x00000035, 0x0004003d, 0x00000006, 0x000000e6,
|
||||
0x000000e5, 0x0004003d, 0x00000006, 0x000000e7, 0x000000d5, 0x0004003d, 0x00000006, 0x000000e8,
|
||||
0x000000d5, 0x00050085, 0x00000006, 0x000000e9, 0x000000e7, 0x000000e8, 0x00050083, 0x00000006,
|
||||
0x000000ea, 0x0000001f, 0x000000e9, 0x00050085, 0x00000006, 0x000000eb, 0x000000e6, 0x000000ea,
|
||||
0x00050041, 0x00000007, 0x000000ec, 0x0000006f, 0x00000030, 0x0004003d, 0x00000006, 0x000000ed,
|
||||
0x000000ec, 0x00050081, 0x00000006, 0x000000ee, 0x000000eb, 0x000000ed, 0x0003003e, 0x000000e4,
|
||||
0x000000ee, 0x0004003d, 0x00000006, 0x000000ef, 0x000000c7, 0x0004003d, 0x0000000c, 0x000000f0,
|
||||
0x000000cc, 0x0004003d, 0x00000006, 0x000000f1, 0x000000d1, 0x00060050, 0x0000000c, 0x000000f2,
|
||||
0x000000f1, 0x000000f1, 0x000000f1, 0x00050083, 0x0000000c, 0x000000f3, 0x000000f0, 0x000000f2,
|
||||
0x0004003d, 0x00000006, 0x000000f4, 0x000000e4, 0x0005008e, 0x0000000c, 0x000000f5, 0x000000f3,
|
||||
0x000000f4, 0x0004003d, 0x00000006, 0x000000f6, 0x000000d1, 0x00060050, 0x0000000c, 0x000000f7,
|
||||
0x000000f6, 0x000000f6, 0x000000f6, 0x00050081, 0x0000000c, 0x000000f8, 0x000000f5, 0x000000f7,
|
||||
0x0005008e, 0x0000000c, 0x000000f9, 0x000000f8, 0x000000ef, 0x0003003e, 0x000000bb, 0x000000f9,
|
||||
0x0004003d, 0x00000006, 0x000000fb, 0x00000094, 0x000500ba, 0x0000001a, 0x000000fc, 0x000000fb,
|
||||
0x00000080, 0x000300f7, 0x000000ff, 0x00000000, 0x000400fa, 0x000000fc, 0x000000fe, 0x00000105,
|
||||
0x000200f8, 0x000000fe, 0x0004003d, 0x0000000c, 0x00000100, 0x0000004b, 0x0004003d, 0x00000006,
|
||||
0x00000101, 0x000000a6, 0x0004003d, 0x00000006, 0x00000102, 0x00000094, 0x00050088, 0x00000006,
|
||||
0x00000103, 0x00000101, 0x00000102, 0x0005008e, 0x0000000c, 0x00000104, 0x00000100, 0x00000103,
|
||||
0x0003003e, 0x000000fd, 0x00000104, 0x000200f9, 0x000000ff, 0x000200f8, 0x00000105, 0x0004003d,
|
||||
0x0000000c, 0x00000106, 0x0000004b, 0x0003003e, 0x000000fd, 0x00000106, 0x000200f9, 0x000000ff,
|
||||
0x000200f8, 0x000000ff, 0x0004003d, 0x0000000c, 0x00000107, 0x000000fd, 0x0004003d, 0x0000000c,
|
||||
0x00000109, 0x0000004b, 0x0004007f, 0x0000000c, 0x0000010a, 0x00000109, 0x0006000c, 0x0000000c,
|
||||
0x0000010b, 0x00000001, 0x0000001b, 0x0000010a, 0x00050083, 0x0000000c, 0x0000010c, 0x00000108,
|
||||
0x0000010b, 0x0004003d, 0x00000006, 0x0000010d, 0x000000a6, 0x0004003d, 0x00000006, 0x0000010e,
|
||||
0x000000a6, 0x00050085, 0x00000006, 0x0000010f, 0x0000010d, 0x0000010e, 0x00060050, 0x0000000c,
|
||||
0x00000110, 0x0000010f, 0x0000010f, 0x0000010f, 0x0008000c, 0x0000000c, 0x00000111, 0x00000001,
|
||||
0x0000002e, 0x00000107, 0x0000010c, 0x00000110, 0x00060050, 0x0000000c, 0x00000112, 0x00000080,
|
||||
0x00000080, 0x00000080, 0x00060050, 0x0000000c, 0x00000113, 0x0000001f, 0x0000001f, 0x0000001f,
|
||||
0x0008000c, 0x0000000c, 0x00000114, 0x00000001, 0x0000002b, 0x00000111, 0x00000112, 0x00000113,
|
||||
0x0003003e, 0x000000fa, 0x00000114, 0x0004003d, 0x0000000c, 0x00000117, 0x000000fa, 0x0003003e,
|
||||
0x00000116, 0x00000117, 0x00050039, 0x00000006, 0x00000118, 0x00000014, 0x00000116, 0x0003003e,
|
||||
0x00000115, 0x00000118, 0x00050041, 0x00000007, 0x0000011a, 0x000000fa, 0x00000030, 0x0004003d,
|
||||
0x00000006, 0x0000011b, 0x0000011a, 0x00050085, 0x00000006, 0x0000011c, 0x0000011b, 0x000000d8,
|
||||
0x00050041, 0x00000007, 0x0000011d, 0x000000fa, 0x00000035, 0x0004003d, 0x00000006, 0x0000011e,
|
||||
0x0000011d, 0x00050041, 0x00000007, 0x0000011f, 0x000000fa, 0x0000003a, 0x0004003d, 0x00000006,
|
||||
0x00000120, 0x0000011f, 0x00050081, 0x00000006, 0x00000121, 0x0000011e, 0x00000120, 0x00050041,
|
||||
0x00000007, 0x00000122, 0x00000064, 0x0000003a, 0x0004003d, 0x00000006, 0x00000123, 0x00000122,
|
||||
0x00050085, 0x00000006, 0x00000124, 0x00000121, 0x00000123, 0x00050081, 0x00000006, 0x00000125,
|
||||
0x0000011c, 0x00000124, 0x00050083, 0x00000006, 0x00000126, 0x00000125, 0x0000001f, 0x0003003e,
|
||||
0x00000119, 0x00000126, 0x0004003d, 0x0000000c, 0x00000127, 0x000000fa, 0x0004003d, 0x00000006,
|
||||
0x00000128, 0x00000115, 0x00060050, 0x0000000c, 0x00000129, 0x00000128, 0x00000128, 0x00000128,
|
||||
0x00050083, 0x0000000c, 0x0000012a, 0x00000127, 0x00000129, 0x00050041, 0x00000007, 0x0000012b,
|
||||
0x0000006f, 0x00000035, 0x0004003d, 0x00000006, 0x0000012c, 0x0000012b, 0x0004003d, 0x00000006,
|
||||
0x0000012d, 0x00000119, 0x0004003d, 0x00000006, 0x0000012e, 0x00000119, 0x00050085, 0x00000006,
|
||||
0x0000012f, 0x0000012d, 0x0000012e, 0x00050083, 0x00000006, 0x00000130, 0x0000001f, 0x0000012f,
|
||||
0x00050085, 0x00000006, 0x00000131, 0x0000012c, 0x00000130, 0x00050041, 0x00000007, 0x00000132,
|
||||
0x0000006f, 0x00000030, 0x0004003d, 0x00000006, 0x00000133, 0x00000132, 0x00050081, 0x00000006,
|
||||
0x00000134, 0x00000131, 0x00000133, 0x0005008e, 0x0000000c, 0x00000135, 0x0000012a, 0x00000134,
|
||||
0x0004003d, 0x00000006, 0x00000136, 0x00000115, 0x00060050, 0x0000000c, 0x00000137, 0x00000136,
|
||||
0x00000136, 0x00000136, 0x00050081, 0x0000000c, 0x00000138, 0x00000135, 0x00000137, 0x0003003e,
|
||||
0x000000fa, 0x00000138, 0x00060041, 0x0000007d, 0x0000013a, 0x0000007b, 0x0000007c, 0x0000003a,
|
||||
0x0004003d, 0x00000006, 0x0000013b, 0x0000013a, 0x0008000c, 0x00000006, 0x0000013c, 0x00000001,
|
||||
0x0000002b, 0x0000013b, 0x00000080, 0x0000001f, 0x0003003e, 0x00000139, 0x0000013c, 0x0004003d,
|
||||
0x0000000c, 0x0000013e, 0x000000bb, 0x00050094, 0x00000006, 0x00000143, 0x0000013e, 0x00000142,
|
||||
0x0003003e, 0x0000013d, 0x00000143, 0x0004003d, 0x0000000c, 0x00000145, 0x000000fa, 0x00050094,
|
||||
0x00000006, 0x00000146, 0x00000145, 0x00000142, 0x0003003e, 0x00000144, 0x00000146, 0x0004003d,
|
||||
0x00000006, 0x00000147, 0x00000139, 0x000500ba, 0x0000001a, 0x00000148, 0x00000147, 0x00000080,
|
||||
0x0004003d, 0x00000006, 0x00000149, 0x00000144, 0x000500ba, 0x0000001a, 0x0000014b, 0x00000149,
|
||||
0x0000014a, 0x000500a7, 0x0000001a, 0x0000014c, 0x00000148, 0x0000014b, 0x000300f7, 0x0000014e,
|
||||
0x00000000, 0x000400fa, 0x0000014c, 0x0000014d, 0x0000014e, 0x000200f8, 0x0000014d, 0x0004003d,
|
||||
0x0000000c, 0x0000014f, 0x000000bb, 0x0004003d, 0x0000000c, 0x00000150, 0x000000fa, 0x0007000c,
|
||||
0x0000000c, 0x00000151, 0x00000001, 0x00000028, 0x00000150, 0x000000c5, 0x0004003d, 0x00000006,
|
||||
0x00000152, 0x0000013d, 0x0004003d, 0x00000006, 0x00000153, 0x00000144, 0x00050088, 0x00000006,
|
||||
0x00000154, 0x00000152, 0x00000153, 0x0005008e, 0x0000000c, 0x00000155, 0x00000151, 0x00000154,
|
||||
0x0004003d, 0x00000006, 0x00000156, 0x00000139, 0x0004003d, 0x0000000c, 0x0000015a, 0x000000bb,
|
||||
0x0003003e, 0x00000159, 0x0000015a, 0x00050039, 0x00000006, 0x0000015b, 0x00000014, 0x00000159,
|
||||
0x0008000c, 0x00000006, 0x0000015c, 0x00000001, 0x00000031, 0x00000157, 0x00000158, 0x0000015b,
|
||||
0x00050085, 0x00000006, 0x0000015d, 0x00000156, 0x0000015c, 0x00060050, 0x0000000c, 0x0000015e,
|
||||
0x0000015d, 0x0000015d, 0x0000015d, 0x0008000c, 0x0000000c, 0x0000015f, 0x00000001, 0x0000002e,
|
||||
0x0000014f, 0x00000155, 0x0000015e, 0x0003003e, 0x000000bb, 0x0000015f, 0x000200f9, 0x0000014e,
|
||||
0x000200f8, 0x0000014e, 0x0004003d, 0x0000000c, 0x00000162, 0x000000bb, 0x0003003e, 0x00000161,
|
||||
0x00000162, 0x00050039, 0x00000006, 0x00000163, 0x00000014, 0x00000161, 0x0003003e, 0x00000160,
|
||||
0x00000163, 0x0004003d, 0x00000006, 0x00000165, 0x00000089, 0x00050085, 0x00000006, 0x00000166,
|
||||
0x00000157, 0x00000165, 0x0003003e, 0x00000164, 0x00000166, 0x0004003d, 0x00000006, 0x00000167,
|
||||
0x00000160, 0x0004003d, 0x00000006, 0x00000168, 0x00000164, 0x000500ba, 0x0000001a, 0x00000169,
|
||||
0x00000167, 0x00000168, 0x000300f7, 0x0000016b, 0x00000000, 0x000400fa, 0x00000169, 0x0000016a,
|
||||
0x0000016b, 0x000200f8, 0x0000016a, 0x0004003d, 0x00000006, 0x0000016d, 0x00000089, 0x0004003d,
|
||||
0x00000006, 0x0000016e, 0x00000164, 0x00050083, 0x00000006, 0x0000016f, 0x0000016d, 0x0000016e,
|
||||
0x0003003e, 0x0000016c, 0x0000016f, 0x0004003d, 0x00000006, 0x00000170, 0x00000164, 0x0004003d,
|
||||
0x00000006, 0x00000171, 0x0000016c, 0x0004003d, 0x00000006, 0x00000172, 0x00000160, 0x0004003d,
|
||||
0x00000006, 0x00000173, 0x00000164, 0x00050083, 0x00000006, 0x00000174, 0x00000172, 0x00000173,
|
||||
0x0004007f, 0x00000006, 0x00000175, 0x00000174, 0x0004003d, 0x00000006, 0x00000176, 0x0000016c,
|
||||
0x00050088, 0x00000006, 0x00000177, 0x00000175, 0x00000176, 0x0006000c, 0x00000006, 0x00000178,
|
||||
0x00000001, 0x0000001b, 0x00000177, 0x00050083, 0x00000006, 0x00000179, 0x0000001f, 0x00000178,
|
||||
0x00050085, 0x00000006, 0x0000017a, 0x00000171, 0x00000179, 0x00050081, 0x00000006, 0x0000017b,
|
||||
0x00000170, 0x0000017a, 0x0004003d, 0x00000006, 0x0000017c, 0x00000160, 0x00050088, 0x00000006,
|
||||
0x0000017d, 0x0000017b, 0x0000017c, 0x0004003d, 0x0000000c, 0x0000017e, 0x000000bb, 0x0005008e,
|
||||
0x0000000c, 0x0000017f, 0x0000017e, 0x0000017d, 0x0003003e, 0x000000bb, 0x0000017f, 0x000200f9,
|
||||
0x0000016b, 0x000200f8, 0x0000016b, 0x0004003d, 0x0000000c, 0x00000182, 0x000000bb, 0x0007000c,
|
||||
0x0000000c, 0x00000183, 0x00000001, 0x00000028, 0x00000182, 0x000000c5, 0x0004003d, 0x00000006,
|
||||
0x00000184, 0x00000076, 0x0005008e, 0x0000000c, 0x00000185, 0x00000183, 0x00000184, 0x00050051,
|
||||
0x00000006, 0x00000186, 0x00000185, 0x00000000, 0x00050051, 0x00000006, 0x00000187, 0x00000185,
|
||||
0x00000001, 0x00050051, 0x00000006, 0x00000188, 0x00000185, 0x00000002, 0x00070050, 0x00000051,
|
||||
0x00000189, 0x00000186, 0x00000187, 0x00000188, 0x00000080, 0x0003003e, 0x00000181, 0x00000189,
|
||||
0x000100fd, 0x00010038, 0x00050036, 0x00000006, 0x0000000a, 0x00000000, 0x00000008, 0x00030037,
|
||||
0x00000007, 0x00000009, 0x000200f8, 0x0000000b, 0x0004003b, 0x00000007, 0x00000016, 0x00000007,
|
||||
0x0004003b, 0x00000007, 0x0000001c, 0x00000007, 0x0003003e, 0x00000016, 0x00000017, 0x0004003d,
|
||||
0x00000006, 0x00000018, 0x00000009, 0x000500bc, 0x0000001a, 0x0000001b, 0x00000018, 0x00000019,
|
||||
0x000300f7, 0x0000001e, 0x00000000, 0x000400fa, 0x0000001b, 0x0000001d, 0x00000024, 0x000200f8,
|
||||
0x0000001d, 0x0004003d, 0x00000006, 0x00000020, 0x00000009, 0x0004007f, 0x00000006, 0x00000021,
|
||||
0x00000020, 0x0006000c, 0x00000006, 0x00000022, 0x00000001, 0x0000001b, 0x00000021, 0x00050083,
|
||||
0x00000006, 0x00000023, 0x0000001f, 0x00000022, 0x0003003e, 0x0000001c, 0x00000023, 0x000200f9,
|
||||
0x0000001e, 0x000200f8, 0x00000024, 0x0004003d, 0x00000006, 0x00000025, 0x00000016, 0x0004003d,
|
||||
0x00000006, 0x00000027, 0x00000009, 0x00050083, 0x00000006, 0x00000028, 0x00000027, 0x00000019,
|
||||
0x00050085, 0x00000006, 0x00000029, 0x00000026, 0x00000028, 0x00050081, 0x00000006, 0x0000002a,
|
||||
0x00000025, 0x00000029, 0x0003003e, 0x0000001c, 0x0000002a, 0x000200f9, 0x0000001e, 0x000200f8,
|
||||
0x0000001e, 0x0004003d, 0x00000006, 0x0000002b, 0x0000001c, 0x000200fe, 0x0000002b, 0x00010038,
|
||||
0x00050036, 0x0000000c, 0x00000010, 0x00000000, 0x0000000e, 0x00030037, 0x0000000d, 0x0000000f,
|
||||
0x000200f8, 0x00000011, 0x0004003b, 0x00000007, 0x0000002e, 0x00000007, 0x0004003b, 0x00000007,
|
||||
0x00000034, 0x00000007, 0x0004003b, 0x00000007, 0x00000039, 0x00000007, 0x00050041, 0x00000007,
|
||||
0x00000031, 0x0000000f, 0x00000030, 0x0004003d, 0x00000006, 0x00000032, 0x00000031, 0x0003003e,
|
||||
0x0000002e, 0x00000032, 0x00050039, 0x00000006, 0x00000033, 0x0000000a, 0x0000002e, 0x00050041,
|
||||
0x00000007, 0x00000036, 0x0000000f, 0x00000035, 0x0004003d, 0x00000006, 0x00000037, 0x00000036,
|
||||
0x0003003e, 0x00000034, 0x00000037, 0x00050039, 0x00000006, 0x00000038, 0x0000000a, 0x00000034,
|
||||
0x00050041, 0x00000007, 0x0000003b, 0x0000000f, 0x0000003a, 0x0004003d, 0x00000006, 0x0000003c,
|
||||
0x0000003b, 0x0003003e, 0x00000039, 0x0000003c, 0x00050039, 0x00000006, 0x0000003d, 0x0000000a,
|
||||
0x00000039, 0x00060050, 0x0000000c, 0x0000003e, 0x00000033, 0x00000038, 0x0000003d, 0x000200fe,
|
||||
0x0000003e, 0x00010038, 0x00050036, 0x00000006, 0x00000014, 0x00000000, 0x00000012, 0x00030037,
|
||||
0x0000000d, 0x00000013, 0x000200f8, 0x00000015, 0x00050041, 0x00000007, 0x00000041, 0x00000013,
|
||||
0x00000030, 0x0004003d, 0x00000006, 0x00000042, 0x00000041, 0x00050041, 0x00000007, 0x00000043,
|
||||
0x00000013, 0x00000035, 0x0004003d, 0x00000006, 0x00000044, 0x00000043, 0x00050041, 0x00000007,
|
||||
0x00000045, 0x00000013, 0x0000003a, 0x0004003d, 0x00000006, 0x00000046, 0x00000045, 0x0007000c,
|
||||
0x00000006, 0x00000047, 0x00000001, 0x00000028, 0x00000044, 0x00000046, 0x0007000c, 0x00000006,
|
||||
0x00000048, 0x00000001, 0x00000028, 0x00000042, 0x00000047, 0x000200fe, 0x00000048, 0x00010038,
|
||||
};
|
||||
@@ -262,6 +262,9 @@ int res_heapdef_build(const void *in, size_t n, void **out, size_t *out_n); //
|
||||
void res_heapdef_served(void); // romfs.c serves the grown table
|
||||
void res_init(void); // after rt_patches_init: the patch page and the sets
|
||||
float res_dlss_lod_bias(void); // TOTK_DLSS upscaling: texture LOD bias (<= 0)
|
||||
int res_dlss_mode(const char *mode); // TOTK_DLSS changed while running: new scene size
|
||||
void res_fn_hit(uint64_t addr, uint64_t x0); // main.c fnprof_hit: catches the renderer at start
|
||||
void res_present(Cpu *c); // nvnQueuePresentTexture: applies a pending scene size
|
||||
void res_sizes(unsigned out[2], unsigned scene[2]); // the output and scene sizes in effect (the game's own
|
||||
// until res_init switched the sets on)
|
||||
int res_is_16x9(unsigned w, unsigned h); // within 1% of 16:9 (the test res_init and romfs.c share)
|
||||
@@ -309,6 +312,8 @@ void rt_boot(void); // boot.c
|
||||
int probe_cfg(void); // cfg_probe.c
|
||||
int probe_dpi(void); // dpi_probe_win.c (Windows only)
|
||||
int probe_mods(void); // mods_probe.c
|
||||
void discord_init(void); // discord_rpc.c: the Rich Presence thread, game runs only
|
||||
int probe_discord(void); // discord_rpc.c
|
||||
int probe_loader(void), probe_kernel(void), probe_ipc(void), probe_services(void), probe_fs(void);
|
||||
int probe_gfx_abi(void); // gfx_abi_probe.c
|
||||
int probe_vkdevice(void); // vk_device.c
|
||||
|
||||
+23
-6
@@ -556,6 +556,12 @@ static void poll_merged(uint64_t *buttons, int32_t *lx, int32_t *ly, int32_t *rx
|
||||
// g_block: 0 off, 1 text entry open, 2 closed, waiting for the release. The cursor is released while it is open.
|
||||
static volatile int32_t g_block;
|
||||
static volatile int32_t g_menu; // the in-game menu, the same way (input_menu)
|
||||
// When g_block and g_menu last went to 2 (plat_now_ns). The wait for the release ends after RELEASE_WAIT_NS even with
|
||||
// something held: an input that never reads as released (a button another program holds down, a key whose release
|
||||
// went to another window, a stick that rests outside its dead zone) would otherwise keep the game deaf until restart
|
||||
// (#462, #494). A press that closed the menu is let go well within the wait; one still held after it is meant.
|
||||
static volatile uint64_t g_block_closed, g_menu_closed;
|
||||
#define RELEASE_WAIT_NS 1000000000ull
|
||||
|
||||
int input_cursor_allowed(void) {
|
||||
const char *in = cfg_get("TOTK_INPUT");
|
||||
@@ -563,6 +569,7 @@ int input_cursor_allowed(void) {
|
||||
}
|
||||
|
||||
void input_block(int on) {
|
||||
if (!on) __atomic_store_n(&g_block_closed, plat_now_ns(), __ATOMIC_RELEASE);
|
||||
plat_xchg32(&g_block, on ? 1 : 2);
|
||||
ikb_cursor_update();
|
||||
}
|
||||
@@ -570,25 +577,35 @@ void input_block(int on) {
|
||||
// The in-game menu (ui/menu.cpp) blocks the game's input the same way: g_menu 0 closed, 1 open, 2 closed, waiting for
|
||||
// the release. The cursor is released while it is open, for the menu's mouse.
|
||||
void input_menu(int open) {
|
||||
if (!open) __atomic_store_n(&g_menu_closed, plat_now_ns(), __ATOMIC_RELEASE);
|
||||
plat_xchg32(&g_menu, open ? 1 : 2);
|
||||
if (!open) ikb_menu_closed(); // the capture before Esc comes back
|
||||
ikb_cursor_update();
|
||||
}
|
||||
|
||||
// 1 while flag (g_block, g_menu) blocks the game's input; state 2 ends at the first poll with nothing held. A stick
|
||||
// axis counts as held as well: with the walk key the keyboard's sticks move part way without setting a direction bit, so
|
||||
// a gate that watched the buttons alone would let that movement through the moment the menu or the text entry closes.
|
||||
static int blocked(volatile int32_t *flag, uint64_t buttons, const int32_t *axes) {
|
||||
// 1 while flag (g_block, g_menu) blocks the game's input; state 2 ends at the first poll with nothing held, or with
|
||||
// something still held RELEASE_WAIT_NS after closed. A stick axis counts as held as well: with the walk key the
|
||||
// keyboard's sticks move part way without setting a direction bit, so a gate that watched the buttons alone would let
|
||||
// that movement through the moment the menu or the text entry closes.
|
||||
static int blocked(volatile int32_t *flag, volatile uint64_t *closed, const char *what, uint64_t buttons,
|
||||
const int32_t *axes) {
|
||||
int32_t k = *flag;
|
||||
if (k != 2) return k != 0;
|
||||
int held = buttons != 0;
|
||||
for (int i = 0; i < 4; i++) held |= axes[i] != 0;
|
||||
return k && !(k == 2 && !held && plat_cas32(flag, 0, 2) == 2);
|
||||
if (held && plat_now_ns() - __atomic_load_n(closed, __ATOMIC_ACQUIRE) < RELEASE_WAIT_NS) return 1;
|
||||
if (plat_cas32(flag, 0, 2) != 2) return *flag != 0; // reopened meanwhile
|
||||
if (held)
|
||||
rt_log("input: the %s closed 1 s ago and input is still held (buttons %#llx, sticks %d %d %d %d); the game "
|
||||
"gets it from now on", what, (unsigned long long)buttons, axes[0], axes[1], axes[2], axes[3]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void input_poll(uint64_t *buttons, int32_t *lx, int32_t *ly, int32_t *rx, int32_t *ry) {
|
||||
poll_merged(buttons, lx, ly, rx, ry);
|
||||
const int32_t axes[4] = {*lx, *ly, *rx, *ry};
|
||||
int text = blocked(&g_block, *buttons, axes), menu = blocked(&g_menu, *buttons, axes);
|
||||
int text = blocked(&g_block, &g_block_closed, "text entry", *buttons, axes),
|
||||
menu = blocked(&g_menu, &g_menu_closed, "menu", *buttons, axes);
|
||||
if (!text && !menu) return;
|
||||
*buttons = 0; *lx = *ly = *rx = *ry = 0;
|
||||
}
|
||||
|
||||
+33
-7
@@ -5,6 +5,7 @@
|
||||
// --probe-mods the mods folder scan, order and switches (mods.c); needs no game files
|
||||
// --probe-loader load and relocate the modules, check them against the measured 1.2.1 values
|
||||
// --probe-kernel, --probe-ipc, --probe-services, --probe-fs host probes
|
||||
// --probe-discord the Rich Presence IPC exchange; skips without a Discord client; no game files
|
||||
// --probe-dpi the process DPI awareness (per-monitor v2) and the primary monitor's size; no game files
|
||||
// --probe-gfx-abi NVN object layouts against nvn.h's reserved sizes; needs no game files
|
||||
// --probe-nvn, --probe-nvn-tex, --probe-window, --probe-shader, --probe-vkmem, --probe-vkexec graphics probes
|
||||
@@ -153,8 +154,8 @@ static RtConfig cfg;
|
||||
const RtConfig *rt_config(void) { return &cfg; }
|
||||
|
||||
// ---- runtime services the generated code calls (cpu.h)
|
||||
volatile int g_fnprof = 0;
|
||||
void fnprof_hit(uint64_t addr, uint64_t lr, uint64_t x0) { (void)addr, (void)lr, (void)x0; }
|
||||
volatile int g_fnprof = 0; // resolution.c switches it on until the game sets its render size (res_fn_hit)
|
||||
void fnprof_hit(uint64_t addr, uint64_t lr, uint64_t x0) { (void)lr, res_fn_hit(addr, x0); }
|
||||
|
||||
void rt_brk(Cpu *c, uint32_t imm, uint64_t pc) {
|
||||
char at[128], lr[128];
|
||||
@@ -218,16 +219,21 @@ static void crash_handler(const PlatCrash *x) {
|
||||
if (plat_xchg32(&busy, 1)) plat_exit(71);
|
||||
char d[160];
|
||||
uintptr_t rip = x->rip;
|
||||
rt_logf("FATAL: %s (%#x) at host %p (totk+%#llx)", x->what, x->code, (void *)rip, (unsigned long long)x->offset);
|
||||
rt_logf("FATAL: %s (%#x) at host %p (", x->what, x->code, (void *)rip);
|
||||
if (x->module && !*x->module) rt_logf("outside every module)");
|
||||
else rt_logf("%s+%#llx)", x->module ? x->module : "totk", (unsigned long long)x->offset);
|
||||
if (x->access >= 0)
|
||||
rt_logf(", %s address %#llx", x->access == 1 ? "writing" : x->access == 8 ? "executing" : "reading",
|
||||
(unsigned long long)x->addr);
|
||||
rt_log_raw("\n", 1);
|
||||
uint64_t f = lifted_function(rip);
|
||||
uint64_t f = x->module ? 0 : lifted_function(rip);
|
||||
if (f) rt_logf(" in lifted function f_%llx = %s\n", (unsigned long long)f, ldr_describe(f, d, sizeof d));
|
||||
for (int i = 0; i < x->nframes; i++) { // abort and invalid parameter: where the host code called it
|
||||
// abort and invalid parameter: where the host code called it; a fault in another module: the host calls that led
|
||||
// into it
|
||||
uintptr_t exe = plat_exe_base();
|
||||
for (int i = 0; i < x->nframes; i++) {
|
||||
uint64_t g = lifted_function(x->frames[i]);
|
||||
rt_logf(" host frame %2d: totk+%#llx", i, (unsigned long long)(x->frames[i] - (rip - x->offset)));
|
||||
rt_logf(" host frame %2d: totk+%#llx", i, (unsigned long long)(x->frames[i] - exe));
|
||||
if (g) rt_logf(" in lifted function f_%llx", (unsigned long long)g);
|
||||
rt_log_raw("\n", 1);
|
||||
}
|
||||
@@ -439,11 +445,27 @@ static int selftest(void) {
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
|
||||
// --probe-crash-call and --probe-crash-null crash on purpose, to check the report of a fault outside this executable.
|
||||
// call: strlen in the C runtime library (ucrtbase.dll, libc.so.6) reads address 8, the way a driver faults inside a
|
||||
// call the host made; the report names the library and the host frames that called it. null: a call through a null
|
||||
// function pointer, which leaves rip at 0, outside every module. Both end with exit code 71 and a FATAL line in the
|
||||
// log.
|
||||
static size_t (*volatile probe_strlen)(const char *) = strlen;
|
||||
static void (*volatile probe_null)(void);
|
||||
|
||||
static int probe_crash(int null) {
|
||||
if (null) probe_null();
|
||||
else printf("%zu\n", probe_strlen((const char *)(uintptr_t)8));
|
||||
printf("probe-crash: still running, the crash did not happen\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int usage(const char *argv0) {
|
||||
fprintf(stderr, "usage: %s [--exefs DIR] [--romfs DIR] [--profile DIR] [--selftest | --probe-loader | "
|
||||
"--probe-cfg | --probe-dpi | --probe-mods | --probe-kernel | --probe-ipc | --probe-services | --probe-fs | --probe-gfx-abi | "
|
||||
"--probe-nvn | --probe-nvn-tex | --probe-window | --probe-shader | --probe-vkmem | "
|
||||
"--probe-vkexec | --probe-astc | --probe-movie | --prepare-shaders | --warm-shaders | --boot |--seconds S [--shot PNG]]\n", argv0);
|
||||
"--probe-vkexec | --probe-astc | --probe-movie | --probe-crash-call | --probe-crash-null | --probe-discord | "
|
||||
"--prepare-shaders | --warm-shaders | --boot |--seconds S [--shot PNG]]\n", argv0);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -526,16 +548,20 @@ int main(int argc, char **argv) {
|
||||
log_open(!mode);
|
||||
if (!mode) {
|
||||
cfg.boot = cfg.seconds > 0; // --seconds starts the game like --boot, with its checkpoints
|
||||
discord_init(); // the Rich Presence thread (discord_rpc.c); it reads totk.ini itself and idles while off
|
||||
rt_boot();
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(mode, "--selftest")) return selftest();
|
||||
if (!strcmp(mode, "--probe-loader")) return probe_loader();
|
||||
if (!strcmp(mode, "--probe-crash-call")) return probe_crash(0);
|
||||
if (!strcmp(mode, "--probe-crash-null")) return probe_crash(1);
|
||||
if (!strcmp(mode, "--probe-cfg")) return probe_cfg();
|
||||
#ifdef _WIN32
|
||||
if (!strcmp(mode, "--probe-dpi")) return probe_dpi(); // the Win32 DPI awareness of totk.manifest
|
||||
#endif
|
||||
if (!strcmp(mode, "--probe-mods")) return probe_mods();
|
||||
if (!strcmp(mode, "--probe-discord")) return probe_discord();
|
||||
if (!strcmp(mode, "--probe-kernel")) return probe_kernel();
|
||||
if (!strcmp(mode, "--probe-ipc")) return probe_ipc();
|
||||
if (!strcmp(mode, "--probe-services")) return probe_services();
|
||||
|
||||
@@ -699,6 +699,7 @@ NVN_API(nvnQueuePresentTexture) {
|
||||
win_t *o = arg_p(c, 1);
|
||||
int idx = (int)arg_x(c, 2);
|
||||
movie_tick(c); // ov_movie.c: hand over decoded movie frames that are due; needs the core, so before the wait
|
||||
res_present(c); // resolution.c: a DLSS mode change sets the game's render size again, between frames
|
||||
NVN_BLOCKING();
|
||||
if (o->magic != NVN_WIN_MAGIC || idx < 0 || idx >= o->ntex || idx >= 4 || !o->tex[idx])
|
||||
rt_fatal("nvn: QueuePresentTexture of image %d on window %p", idx, (void *)o);
|
||||
|
||||
+35
-3
@@ -405,8 +405,22 @@ static void probe_applet(void) {
|
||||
0x33 ^ 0x44 ^ 0x55 ^ 0x66, 0x48, 0x0F, 0xE0, 0xF1, 0x10, 0xFF, 0xEE};
|
||||
uint8_t dump[AMIIBO_DUMP_SIZE] = {0};
|
||||
memcpy(dump, head, sizeof head);
|
||||
// Link (Archer): character 0x0100, variant 0, figure type 0, model number 0x0353, series 9, format 02
|
||||
static const uint8_t id[8] = {0x01, 0x00, 0x00, 0x00, 0x03, 0x53, 0x09, 0x02};
|
||||
memcpy(dump + 0x54, id, sizeof id);
|
||||
const char *dump_path = "probe-amiibo.bin";
|
||||
int amiibo_ok = plat_file_write(dump_path, dump, sizeof dump) && amiibo_load_file(dump_path);
|
||||
// TagInfo carries the 7-byte UID without its check byte; ModelInfo has the series at 3, the model number
|
||||
// little-endian at 4 and the figure type at 6
|
||||
uint8_t ti[0x58], mi[0x40];
|
||||
amiibo_get_tag_info(ti);
|
||||
amiibo_get_model_info(mi);
|
||||
static const uint8_t want_uid[7] = {0x04, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
|
||||
static const uint8_t want_model[7] = {0x01, 0x00, 0x00, 0x09, 0x53, 0x03, 0x00};
|
||||
CHECK(amiibo_ok && !memcmp(ti, want_uid, 7) && ti[0x0A] == 7, "amiibo TagInfo UID %02x %02x %02x %02x %02x %02x %02x",
|
||||
ti[0], ti[1], ti[2], ti[3], ti[4], ti[5], ti[6]);
|
||||
CHECK(amiibo_ok && !memcmp(mi, want_model, 7), "amiibo ModelInfo %02x %02x %02x %02x %02x %02x %02x", mi[0], mi[1],
|
||||
mi[2], mi[3], mi[4], mi[5], mi[6]);
|
||||
dump[0x0C] = 0xE1;
|
||||
amiibo_ok = amiibo_ok && plat_file_write(dump_path, dump, sizeof dump) && !amiibo_load_file(dump_path);
|
||||
plat_fs_unlink(dump_path);
|
||||
@@ -875,15 +889,33 @@ static void probe_menu_text(void) {
|
||||
CHECK(!strcmp(ui_text_in("ru", UI_SWKBD_OK), "ОК") && !strcmp(ui_text_in("de", UI_SWKBD_CANCEL), "Abbrechen") &&
|
||||
!strcmp(ui_text_in("en", UI_SWKBD_OK), "OK"), "keyboard texts changed");
|
||||
// language names: the same string in every row
|
||||
for (int id = UI_MENU_LANGNAME_EN; id <= UI_MENU_LANGNAME_PT_BR; id++)
|
||||
for (int id = UI_MENU_LANGNAME_EN; id <= UI_MENU_LANGNAME_ES_419; id++)
|
||||
for (size_t i = 1; i < sizeof langs / sizeof *langs; i++)
|
||||
CHECK(!strcmp(ui_text_in(langs[i], (UiTextId)id), ui_text_in("en", (UiTextId)id)), "language name %d differs in %s", id, langs[i]);
|
||||
CHECK(!strcmp(ui_text_in("en", UI_MENU_LANGNAME_RU), "Русский"), "Russian is not shown as Russian");
|
||||
// game language list: pt-BR only where the build ships USpt; the Switch variants are entries of their own
|
||||
{
|
||||
UiGameLang gl[16];
|
||||
int n121 = ui_text_game_langs(121, gl, 16), has[16] = {0};
|
||||
for (int i = 0; i < n121; i++) CHECK(strcmp(gl[i].code, "pt-BR"), "1.2.1 game language list offers pt-BR");
|
||||
int n143 = ui_text_game_langs(143, gl, 16), pt = 0;
|
||||
for (int i = 0; i < n143; i++) {
|
||||
pt |= !strcmp(gl[i].code, "pt-BR");
|
||||
for (int j = 0; j < i; j++) CHECK(strcmp(gl[i].code, gl[j].code), "duplicate game language %s", gl[i].code);
|
||||
}
|
||||
CHECK(pt && n143 == n121 + 1, "1.4.3 game language list lacks pt-BR");
|
||||
static const char *const want[] = {"en-GB", "fr-CA", "es-419"};
|
||||
for (int w = 0; w < 3; w++)
|
||||
for (int i = 0; i < n143; i++) has[w] |= !strcmp(gl[i].code, want[w]);
|
||||
CHECK(has[0] && has[1] && has[2], "game language list lacks en-GB, fr-CA or es-419");
|
||||
}
|
||||
// the overlay formats keep their conversions
|
||||
CHECK(strstr(ui_text_in("ru", UI_MENU_OV_GAME_PCT), "%.0f%%") && strstr(ui_text_in("ru", UI_MENU_OV_MAX_MS), "%.1f"),
|
||||
CHECK(strstr(ui_text_in("ru", UI_MENU_OV_GAME_PCT), "%.0f%%") && strstr(ui_text_in("ru", UI_MENU_OV_MAX_MS), "%.1f") &&
|
||||
strstr(ui_text_in("ru", UI_MENU_OV_LOW1), "%.0f") && strstr(ui_text_in("ru", UI_MENU_OV_LOW01), "%.0f"),
|
||||
"overlay format lost its conversion");
|
||||
CHECK(strstr(ui_text_in("pt-BR", UI_MENU_OV_GAME_PCT), "%.0f%%") &&
|
||||
strstr(ui_text_in("pt-BR", UI_MENU_OV_MAX_MS), "%.1f"), "pt-BR overlay format lost its conversion");
|
||||
strstr(ui_text_in("pt-BR", UI_MENU_OV_MAX_MS), "%.1f") &&
|
||||
strstr(ui_text_in("pt-BR", UI_MENU_OV_LOW1), "%.0f") && strstr(ui_text_in("zh-Hans", UI_MENU_OV_LOW01), "%.0f"), "pt-BR overlay format lost its conversion");
|
||||
// a Brazilian game language gets the Brazilian keyboard row, Portugal's Portuguese keeps its own
|
||||
CHECK(!strcmp(ui_text_lang("pt-BR"), "pt-BR") && !strcmp(ui_text_lang("pt"), "pt") &&
|
||||
strcmp(ui_text_in("pt-BR", UI_SWKBD_PROMPT), ui_text_in("pt", UI_SWKBD_PROMPT)), "pt-BR keyboard row not used");
|
||||
|
||||
@@ -32,6 +32,14 @@ static float g_ring[PERF_HISTORY];
|
||||
static uint64_t g_ring_at[PERF_HISTORY]; // when each frame ended
|
||||
static int g_ring_n, g_ring_pos;
|
||||
|
||||
// The lows: a histogram of the frame times (perf.c's 0.25 ms bins) over the last LOW_SLICES seconds, kept as one slice
|
||||
// per second so the oldest second drops out whole. Per frame: one increment. The percentiles are read twice a second.
|
||||
#define LOW_BINS 1024
|
||||
#define LOW_SLICES 10
|
||||
static uint16_t g_low_hist[LOW_SLICES][LOW_BINS];
|
||||
static int g_low_cur;
|
||||
static uint64_t g_low_t0;
|
||||
|
||||
// The per-frame counters, summed since t0 (the overlay's lines are averages over half a second).
|
||||
static struct {
|
||||
uint64_t t0, frames, gpu0;
|
||||
@@ -47,6 +55,14 @@ void perfmon_frame(uint64_t now, uint64_t interval_ns, const uint64_t *wait_ns,
|
||||
g_ring_at[g_ring_pos] = now;
|
||||
g_ring_pos = (g_ring_pos + 1) % PERF_HISTORY;
|
||||
if (g_ring_n < PERF_HISTORY) g_ring_n++;
|
||||
if (now - g_low_t0 >= 1000000000ull) { // the next second: its slice starts empty
|
||||
g_low_cur = (g_low_cur + 1) % LOW_SLICES;
|
||||
memset(g_low_hist[g_low_cur], 0, sizeof g_low_hist[0]);
|
||||
g_low_t0 = now;
|
||||
}
|
||||
uint64_t bin = interval_ns / 250000ull;
|
||||
uint16_t *h = &g_low_hist[g_low_cur][bin < LOW_BINS ? bin : LOW_BINS - 1];
|
||||
if (*h < 65535) (*h)++;
|
||||
}
|
||||
g_win.frames++;
|
||||
g_win.replay += replay_ns / 1e6;
|
||||
@@ -76,9 +92,27 @@ static void frames_summary(PerfStats *s, uint64_t now) { // (lock held)
|
||||
}
|
||||
}
|
||||
|
||||
// The fps of the slowest 1% and 0.1% of the frames in the window (the bin's upper edge, as perf.c's summary does); -1
|
||||
// when the window holds too few frames. (lock held)
|
||||
static void lows_summary(PerfStats *s) {
|
||||
uint32_t total = 0;
|
||||
for (int k = 0; k < LOW_SLICES; k++)
|
||||
for (int b = 0; b < LOW_BINS; b++) total += g_low_hist[k][b];
|
||||
s->low1_fps = s->low01_fps = -1;
|
||||
if (total < 100) return;
|
||||
uint32_t cut1 = (total + 99) / 100, cut01 = (total + 999) / 1000, acc = 0;
|
||||
for (int b = LOW_BINS - 1; b >= 0 && s->low1_fps < 0; b--) {
|
||||
for (int k = 0; k < LOW_SLICES; k++) acc += g_low_hist[k][b];
|
||||
float fps = 1000.0f / ((b + 1) * 0.25f);
|
||||
if (s->low01_fps < 0 && acc >= cut01) s->low01_fps = fps;
|
||||
if (acc >= cut1) s->low1_fps = fps;
|
||||
}
|
||||
}
|
||||
|
||||
// Every half second: the window's sums as per-frame averages into g_stats, and a new window. (lock held)
|
||||
static void counters_summary(uint64_t now) {
|
||||
if (now - g_win.t0 < 500000000ull) return;
|
||||
lows_summary(&g_stats);
|
||||
uint64_t gpu = perf_gpu_busy_total();
|
||||
if (g_win.t0 && g_win.frames) {
|
||||
double n = (double)g_win.frames;
|
||||
@@ -342,6 +376,8 @@ void perfmon_mode(int mode) {
|
||||
}
|
||||
if (!mode) { // the next start begins a new graph and a new counter window
|
||||
g_ring_n = g_ring_pos = 0;
|
||||
memset(g_low_hist, 0, sizeof g_low_hist);
|
||||
g_low_t0 = 0;
|
||||
memset(&g_win, 0, sizeof g_win);
|
||||
}
|
||||
plat_mutex_unlock(&g_lock);
|
||||
|
||||
@@ -17,6 +17,8 @@ extern "C" {
|
||||
typedef struct {
|
||||
// the game's frames (presents), over the last second
|
||||
float fps, frame_ms_avg, frame_ms_max;
|
||||
// fps of the slowest 1% and 0.1% of the frames over the last ten seconds (-1: too few frames yet)
|
||||
float low1_fps, low01_fps;
|
||||
float frame_ms[PERF_HISTORY]; // oldest first
|
||||
// CPU: the whole machine and this process, percent of all logical processors
|
||||
char cpu_name[64];
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/random.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
@@ -916,8 +918,9 @@ uint64_t plat_mem_written(uint64_t addr, uint64_t n, uint64_t *pages, uint64_t m
|
||||
}
|
||||
|
||||
// One handler for the synchronous faults. A SIGSEGV whose page-fault error code has the write bit set goes to the
|
||||
// write-fault hook first (memory.c thaws a frozen region); a fault in this executable's code then goes to the crash
|
||||
// hook (main.c); anything else gets the default action. Runs on the thread's signal stack.
|
||||
// write-fault hook first (memory.c thaws a frozen region); any other fault, in this executable or in a library (Mesa,
|
||||
// SDL), then goes to the crash hook (main.c), which reports it and ends the process. Without a crash hook the fault
|
||||
// gets the default action. Runs on the thread's signal stack.
|
||||
static int (*fault_hook)(uint64_t addr);
|
||||
static void (*crash_fn)(const PlatCrash *c);
|
||||
|
||||
@@ -927,10 +930,17 @@ static void fault_handler(int sig, siginfo_t *si, void *uc) {
|
||||
if (sig == SIGSEGV && fault_hook && (err & 2) && fault_hook((uint64_t)(uintptr_t)si->si_addr)) return;
|
||||
uintptr_t rip = (uintptr_t)u->uc_mcontext.gregs[REG_RIP];
|
||||
Dl_info at, self;
|
||||
if (crash_fn && dladdr((void *)rip, &at) && dladdr((void *)plat_crash_hook, &self) && at.dli_fbase == self.dli_fbase) {
|
||||
if (crash_fn) {
|
||||
int found = dladdr((void *)rip, &at) && at.dli_fname;
|
||||
PlatCrash c = {sig == SIGSEGV ? "access violation" : sig == SIGBUS ? "bus error"
|
||||
: sig == SIGILL ? "illegal instruction" : "arithmetic exception",
|
||||
(uint32_t)sig, rip, rip - (uintptr_t)self.dli_fbase, -1, 0};
|
||||
(uint32_t)sig, rip, found ? rip - (uintptr_t)at.dli_fbase : rip, -1, 0};
|
||||
if (!found) {
|
||||
c.module = "";
|
||||
} else if (!dladdr((void *)plat_crash_hook, &self) || at.dli_fbase != self.dli_fbase) {
|
||||
const char *n = strrchr(at.dli_fname, '/');
|
||||
c.module = n ? n + 1 : at.dli_fname;
|
||||
}
|
||||
if (sig == SIGSEGV || sig == SIGBUS)
|
||||
c.access = (err & 16) ? 8 : (err & 2) ? 1 : 0, c.addr = (uint64_t)(uintptr_t)si->si_addr;
|
||||
crash_fn(&c);
|
||||
@@ -1656,6 +1666,62 @@ long plat_child_read(PlatChild *c, void *buf, size_t cap) {
|
||||
return (long)k;
|
||||
}
|
||||
|
||||
// ---- local IPC (platform.h): Discord's Rich Presence Unix domain socket
|
||||
struct PlatIpc { int fd; };
|
||||
|
||||
PlatIpc *plat_ipc_connect(const char *name) {
|
||||
const char *dirs[] = {getenv("DISCORD_IPC_PATH"), getenv("XDG_RUNTIME_DIR"), getenv("TMPDIR"), "/tmp"};
|
||||
for (size_t d = 0; d < sizeof dirs / sizeof *dirs; d++) {
|
||||
if (!dirs[d] || !*dirs[d]) continue;
|
||||
char path[sizeof ((struct sockaddr_un *)0)->sun_path];
|
||||
if ((size_t)snprintf(path, sizeof path, "%s/%s", dirs[d], name) >= sizeof path) continue;
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (fd < 0) return NULL;
|
||||
struct sockaddr_un sa = {0};
|
||||
sa.sun_family = AF_UNIX;
|
||||
strcpy(sa.sun_path, path);
|
||||
if (connect(fd, (struct sockaddr *)&sa, sizeof sa) == 0) {
|
||||
PlatIpc *p = malloc(sizeof *p);
|
||||
if (!p) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
p->fd = fd;
|
||||
return p;
|
||||
}
|
||||
close(fd); // nothing listens at this path: the next directory may have it
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int plat_ipc_read(PlatIpc *p, void *buf, size_t n, uint32_t ms) {
|
||||
struct pollfd pf = {.fd = p->fd, .events = POLLIN};
|
||||
int r = poll(&pf, 1, (int)ms);
|
||||
if (r <= 0) return r < 0 && errno != EINTR ? -1 : 0;
|
||||
ssize_t got = recv(p->fd, buf, n, 0);
|
||||
return got > 0 ? (int)got : -1; // 0 bytes: the peer closed the socket
|
||||
}
|
||||
|
||||
int plat_ipc_write(PlatIpc *p, const void *buf, size_t n) {
|
||||
const uint8_t *at = buf;
|
||||
while (n) {
|
||||
ssize_t put = send(p->fd, at, n, MSG_NOSIGNAL); // a closed peer is an error, not a SIG_PIPE
|
||||
if (put <= 0) {
|
||||
if (put < 0 && errno == EINTR) continue;
|
||||
return 0;
|
||||
}
|
||||
at += put, n -= put;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void plat_ipc_close(PlatIpc *p) {
|
||||
if (!p) return;
|
||||
close(p->fd);
|
||||
free(p);
|
||||
}
|
||||
|
||||
|
||||
// ---- restart
|
||||
// TOTK_RESTART_WAIT names the process that called plat_restart, as its id and start time (field 22 of /proc/<pid>/stat,
|
||||
// clock ticks after boot): a process that has only reused the id is not waited for. Waiting keeps the new process off
|
||||
|
||||
@@ -218,19 +218,25 @@ void plat_mem_fault_hook(int (*fn)(uint64_t addr));
|
||||
// replaces this process with it (Linux). Environment changes made before the call are passed on.
|
||||
_Noreturn void plat_reexec(void);
|
||||
|
||||
// A crash in this executable's own code (an access violation, an illegal instruction, a division by zero, a stack
|
||||
// overflow) goes to fn, which reports it and must end the process. Faults in other modules (drivers) are left to the
|
||||
// OS. The write-fault hook above runs first. On Windows abort() (a failed assert, std::terminate) and an invalid
|
||||
// parameter to a C runtime function go there too, with the host frames in this executable: the C runtime ends the
|
||||
// process for both with 0xc0000409 and no exception.
|
||||
// A crash goes to fn, which reports it and must end the process. The write-fault hook above runs first.
|
||||
// - A fault in this executable's own code (an access violation, an illegal instruction, a division by zero, a stack
|
||||
// overflow) is reported as it happens.
|
||||
// - A fault in another module (a GPU driver, a system library) is reported only when nothing handles it: on Windows
|
||||
// from the unhandled-exception filter, with the return addresses in this executable on the faulting thread's stack,
|
||||
// so the report shows which host call ran into the driver; on Linux from the signal handler, without frames. Drivers
|
||||
// that raise and catch their own exceptions never reach fn.
|
||||
// - On Windows abort() (a failed assert, std::terminate) and an invalid parameter to a C runtime function go there
|
||||
// too, with the host frames in this executable: the C runtime ends the process for both with 0xc0000409 and no
|
||||
// exception.
|
||||
typedef struct {
|
||||
const char *what; // "access violation", "illegal instruction", ...
|
||||
uint32_t code; // the Windows exception code or the signal number
|
||||
uintptr_t rip, offset; // the faulting instruction, and its offset in the executable
|
||||
uintptr_t rip, offset; // the faulting instruction, and its offset in its module
|
||||
int access; // the data access that faulted: 0 read, 1 write, 8 execute; -1 when not an access
|
||||
uint64_t addr; // its address when access >= 0
|
||||
int nframes; // abort and invalid parameter: return addresses in this executable, innermost first
|
||||
uintptr_t frames[16]; // (rip is frames[0]); 0 for a fault, whose rip is exact
|
||||
int nframes; // return addresses in this executable, innermost first: for abort and invalid parameter
|
||||
uintptr_t frames[16]; // (rip is frames[0]) and for a fault in another module; 0 for a fault in this one
|
||||
const char *module; // the module holding rip: NULL for this executable, "" for none (a jump to address 0)
|
||||
} PlatCrash;
|
||||
void plat_crash_hook(void (*fn)(const PlatCrash *c));
|
||||
int plat_mem_read(uint64_t addr, void *out, size_t n); // copies n bytes, 0 instead of faulting on unmapped memory
|
||||
@@ -413,6 +419,17 @@ void plat_child_stop(PlatChild *c); // kills the child and
|
||||
int plat_child_write(PlatChild *c, const void *data, size_t n); // 1 when all n bytes were written
|
||||
long plat_child_read(PlatChild *c, void *buf, size_t cap); // bytes read (at least 1); 0 or negative: pipe broke
|
||||
|
||||
// ---- local IPC: a stream connection to another program of this machine, one listen location per name (Discord's
|
||||
// Rich Presence, discord_rpc.c). On Windows the named pipe \\.\pipe\<name>; on Linux the Unix domain socket
|
||||
// <dir>/<name> with <dir> the first of $DISCORD_IPC_PATH, $XDG_RUNTIME_DIR, $TMPDIR and /tmp that has one.
|
||||
typedef struct PlatIpc PlatIpc;
|
||||
PlatIpc *plat_ipc_connect(const char *name); // NULL when nothing listens under that name
|
||||
// Up to n bytes, waiting at most ms for the first of them: bytes read, 0 none in time, -1 the connection broke.
|
||||
// The rest of what the peer wrote may still be in its buffer: a caller that needs a whole frame loops.
|
||||
int plat_ipc_read(PlatIpc *p, void *buf, size_t n, uint32_t ms);
|
||||
int plat_ipc_write(PlatIpc *p, const void *buf, size_t n); // 1 when all n bytes were written, 0 the connection broke
|
||||
void plat_ipc_close(PlatIpc *p);
|
||||
|
||||
// ---- restart: this program again, in a new process that starts once this one is gone
|
||||
// Starts this executable again with the same command line, environment and working folder, and no handle of this
|
||||
// process. 1 when it started; the caller then exits.
|
||||
|
||||
@@ -665,6 +665,76 @@ __attribute__((noinline)) static void crash_from_runtime(const char *what, uint3
|
||||
crash_fn(&c);
|
||||
}
|
||||
|
||||
// The name a DLL records for itself in its export directory ("amdvlk64.dll"). Read from its mapped headers, not through
|
||||
// the loader, whose lock another thread may hold when the process crashes.
|
||||
static const char *image_name(const char *base) {
|
||||
const IMAGE_NT_HEADERS64 *nt = (const void *)(base + ((const IMAGE_DOS_HEADER *)base)->e_lfanew);
|
||||
const IMAGE_DATA_DIRECTORY *d = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
if (!d->VirtualAddress || !d->Size) return "unnamed module";
|
||||
return base + ((const IMAGE_EXPORT_DIRECTORY *)(base + d->VirtualAddress))->Name;
|
||||
}
|
||||
|
||||
// The return addresses in [lo, lo + size) on the crashed thread's stack, innermost first: unwinds the faulting context
|
||||
// with the x64 unwind tables; a function without a table entry (a leaf, or address 0 after a call through a null
|
||||
// pointer) has its return address on top of the stack. The walk stops at return address 0 (the thread's first frame)
|
||||
// and when the stack pointer leaves the thread's stack, so a damaged stack ends it instead of faulting.
|
||||
static int frames_in(const CONTEXT *at, uintptr_t lo, uintptr_t size, uintptr_t *out, int max) {
|
||||
CONTEXT c = *at;
|
||||
ULONG_PTR low, high;
|
||||
GetCurrentThreadStackLimits(&low, &high);
|
||||
int n = 0;
|
||||
for (int i = 0; i < 256 && n < max && (c.Rip || !i) && c.Rsp >= low && c.Rsp + 8 <= high; i++) {
|
||||
DWORD64 img;
|
||||
RUNTIME_FUNCTION *fe = RtlLookupFunctionEntry(c.Rip, &img, NULL);
|
||||
if (fe) {
|
||||
void *data;
|
||||
DWORD64 frame;
|
||||
RtlVirtualUnwind(UNW_FLAG_NHANDLER, img, c.Rip, fe, &c, &data, &frame, NULL);
|
||||
} else {
|
||||
c.Rip = *(const DWORD64 *)c.Rsp;
|
||||
c.Rsp += 8;
|
||||
}
|
||||
if (c.Rip - lo < size) out[n++] = (uintptr_t)c.Rip;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// The last-chance filter: an exception no handler took, in any module. crash_veh reports faults in this executable as
|
||||
// they happen; this one catches the rest, which used to end the process with no line in the log: a fault in a GPU
|
||||
// driver's own thread or in a driver call (vkQueueSubmit, vkAllocateMemory), a C++ exception a library let escape, a
|
||||
// call through a null pointer (rip 0, outside every module). Windows calls it only after every handler declined, so
|
||||
// the exceptions drivers raise and catch themselves never get here; it costs nothing until a crash.
|
||||
static LONG WINAPI crash_filter(EXCEPTION_POINTERS *ep) {
|
||||
const EXCEPTION_RECORD *er = ep->ExceptionRecord;
|
||||
DWORD code = er->ExceptionCode;
|
||||
uintptr_t rip = (uintptr_t)ep->ContextRecord->Rip;
|
||||
PlatCrash c = {code == EXCEPTION_ACCESS_VIOLATION ? "access violation"
|
||||
: code == EXCEPTION_ILLEGAL_INSTRUCTION ? "illegal instruction"
|
||||
: code == EXCEPTION_INT_DIVIDE_BY_ZERO ? "integer division by zero"
|
||||
: code == EXCEPTION_PRIV_INSTRUCTION ? "privileged instruction"
|
||||
: code == EXCEPTION_STACK_OVERFLOW ? "stack overflow"
|
||||
: code == EXCEPTION_IN_PAGE_ERROR ? "in-page error"
|
||||
: code == EXCEPTION_BREAKPOINT ? "breakpoint"
|
||||
: code == 0xe06d7363u ? "C++ exception"
|
||||
: code == 0xc0000374u ? "heap corruption" // STATUS_HEAP_CORRUPTION
|
||||
: "unhandled exception",
|
||||
(uint32_t)code, rip, 0, -1, 0};
|
||||
if (code == EXCEPTION_ACCESS_VIOLATION && er->NumberParameters >= 2)
|
||||
c.access = (int)er->ExceptionInformation[0], c.addr = er->ExceptionInformation[1];
|
||||
void *base = NULL;
|
||||
RtlPcToFileHeader((void *)rip, &base);
|
||||
MODULEINFO mi;
|
||||
uintptr_t exe = (uintptr_t)GetModuleHandleW(NULL);
|
||||
c.offset = rip - (uintptr_t)base;
|
||||
if (!base) c.module = "";
|
||||
else if ((uintptr_t)base != exe) c.module = image_name(base);
|
||||
if (c.module && GetModuleInformation(GetCurrentProcess(), (HMODULE)exe, &mi, sizeof mi))
|
||||
c.nframes = frames_in(ep->ContextRecord, exe, mi.SizeOfImage, c.frames,
|
||||
(int)(sizeof c.frames / sizeof *c.frames));
|
||||
crash_fn(&c);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
static void abort_signal(int sig) { crash_from_runtime("abort", (uint32_t)sig); }
|
||||
|
||||
static void invalid_parameter(const wchar_t *expr, const wchar_t *func, const wchar_t *file, unsigned line,
|
||||
@@ -676,6 +746,7 @@ static void invalid_parameter(const wchar_t *expr, const wchar_t *func, const wc
|
||||
void plat_crash_hook(void (*fn)(const PlatCrash *c)) {
|
||||
crash_fn = fn;
|
||||
AddVectoredExceptionHandler(0, crash_veh);
|
||||
SetUnhandledExceptionFilter(crash_filter); // replaces the C runtime's filter; crash_fn never returns
|
||||
signal(SIGABRT, abort_signal);
|
||||
_set_invalid_parameter_handler(invalid_parameter);
|
||||
}
|
||||
@@ -1553,6 +1624,56 @@ long plat_child_read(PlatChild *c, void *buf, size_t cap) {
|
||||
return (long)got;
|
||||
}
|
||||
|
||||
// ---- local IPC (platform.h): Discord's Rich Presence named pipe
|
||||
struct PlatIpc { HANDLE h; };
|
||||
|
||||
PlatIpc *plat_ipc_connect(const char *name) {
|
||||
wchar_t wname[128], path[152];
|
||||
if (!widen(name, wname, 128)) return NULL;
|
||||
swprintf(path, 152, L"\\\\.\\pipe\\%ls", wname);
|
||||
HANDLE h = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (h == INVALID_HANDLE_VALUE) return NULL; // no listener, or the pipe is busy: the caller retries later
|
||||
PlatIpc *p = malloc(sizeof *p);
|
||||
if (!p) {
|
||||
CloseHandle(h);
|
||||
return NULL;
|
||||
}
|
||||
p->h = h;
|
||||
return p;
|
||||
}
|
||||
|
||||
int plat_ipc_read(PlatIpc *p, void *buf, size_t n, uint32_t ms) {
|
||||
uint64_t deadline = plat_now_ms() + ms;
|
||||
for (;;) {
|
||||
DWORD have = 0;
|
||||
if (!PeekNamedPipe(p->h, NULL, 0, NULL, &have, NULL)) return -1; // the writer closed the pipe
|
||||
if (have) {
|
||||
DWORD got = 0;
|
||||
if (!ReadFile(p->h, buf, have < n ? have : (DWORD)n, &got, NULL) || !got) return -1;
|
||||
return (int)got;
|
||||
}
|
||||
if (plat_now_ms() >= deadline) return 0;
|
||||
plat_sleep_ms(5);
|
||||
}
|
||||
}
|
||||
|
||||
int plat_ipc_write(PlatIpc *p, const void *buf, size_t n) {
|
||||
const uint8_t *at = buf;
|
||||
while (n) {
|
||||
DWORD put = 0;
|
||||
if (!WriteFile(p->h, at, n > (1u << 30) ? 1u << 30 : (DWORD)n, &put, NULL) || !put) return 0;
|
||||
at += put, n -= put;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void plat_ipc_close(PlatIpc *p) {
|
||||
if (!p) return;
|
||||
CloseHandle(p->h);
|
||||
free(p);
|
||||
}
|
||||
|
||||
|
||||
// ---- restart
|
||||
// TOTK_RESTART_WAIT names the process that called plat_restart, as its id and creation time: a process that has only
|
||||
// reused the id is not waited for. Waiting keeps the new process off the old one's files (the log it moves at start,
|
||||
|
||||
+125
-30
@@ -27,6 +27,9 @@
|
||||
// rewritten table (res_heapdef_build) in place of the original; memory.c adds the amount to the application pool, so
|
||||
// no other heap shrinks. Without the rewritten table (a RomFS that lacks it, a format this code does not know) the
|
||||
// patch sets stay off and the game keeps its own sizes.
|
||||
#ifndef _WIN32
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -53,12 +56,15 @@ static struct {
|
||||
int heapdef_ok; // romfs.c serves the grown HeapDef
|
||||
int on; // res_init switched the patch sets on
|
||||
unsigned shadow; // fixed sun shadow map size (TOTK_SHADOW_RES), 0: the game's own
|
||||
double dlss_k; // TOTK_DLSS quality/balanced/performance: the scene's share of the output, else 0
|
||||
double dlss_k; // TOTK_DLSS quality/balanced/performance/ultra_performance: the scene's share of the output, else 0
|
||||
double base_k; // the scene's share without a DLSS mode (render_scale, or the game's 5/6)
|
||||
unsigned hw, hh; // the largest scene a DLSS mode change can ask for: the heap is sized for it
|
||||
int live; // 1.4.3 with DLSS able to run: the DLSS mode can change while the game runs
|
||||
} R;
|
||||
|
||||
// The scene size is decided before the device exists, so NGX cannot be asked yet. NGX loads DLSS from nvngx_dlss.dll
|
||||
// next to totk.exe (vk_dlaa.c vk_dlaa_init): without that file DLSS never runs, and a DLSS mode would leave the game's
|
||||
// plain scaling at 50..67 % of the output. Off Windows vk_dlaa.c is built as no-op stubs, so there is no DLSS at all.
|
||||
// next to totk.exe on Windows, libnvidia-ngx-dlss.so.<version> next to totk on Linux (vk_dlaa.c vk_dlaa_init):
|
||||
// without that file DLSS never runs, and a DLSS mode would leave the game's plain scaling at 50..67 % of the output.
|
||||
static int dlss_dll(void) {
|
||||
#ifdef _WIN32
|
||||
char exe[1024], path[1024 + 32];
|
||||
@@ -68,10 +74,52 @@ static int dlss_dll(void) {
|
||||
snprintf(path, sizeof path, "%.*s/nvngx_dlss.dll", (int)(last - exe), exe);
|
||||
return plat_file_exists(path);
|
||||
#else
|
||||
return 0;
|
||||
char exe[1024];
|
||||
if (!plat_exe_path(exe, sizeof exe)) return 1;
|
||||
char *last = plat_last_sep(exe);
|
||||
if (!last) return 1;
|
||||
*last = 0;
|
||||
DIR *d = opendir(exe);
|
||||
if (!d) return 1;
|
||||
int found = 0;
|
||||
for (struct dirent *e; !found && (e = readdir(d));)
|
||||
found = !strncmp(e->d_name, "libnvidia-ngx-dlss.so.", 22);
|
||||
closedir(d);
|
||||
return found;
|
||||
#endif
|
||||
}
|
||||
|
||||
// The scene size for a share k of the output, within SCENE_MAX per side.
|
||||
static void scene_for(double k, unsigned *w, unsigned *h) {
|
||||
double sw = R.ow * k, sh = R.oh * k;
|
||||
double fit = fmin(1.0, fmin(SCENE_MAX / sw, SCENE_MAX / sh));
|
||||
*w = (unsigned)(sw * fit + 0.5);
|
||||
*h = (unsigned)(sh * fit + 0.5);
|
||||
}
|
||||
|
||||
// The scene's share of the output for a TOTK_DLSS mode; 0 for dlaa (the scene keeps base_k), while DLSS/DLAA is off
|
||||
// (the mode the menu still holds would shrink the scene for the plain upscaler) and where DLSS cannot run (dlss_dll).
|
||||
static double dlss_share(const char *m) {
|
||||
static const struct { const char *name; double k; } dlss[] = {
|
||||
{"quality", 2.0 / 3.0}, {"balanced", 0.58}, {"performance", 0.5}, {"ultra_performance", 1.0 / 3.0}};
|
||||
static int dll = -1;
|
||||
if (!cfg_bool("TOTK_DLAA")) return 0;
|
||||
for (int i = 0; i < (int)(sizeof dlss / sizeof dlss[0]); i++)
|
||||
if (!strcmp(m, dlss[i].name)) {
|
||||
if (dll < 0 && !(dll = dlss_dll())) {
|
||||
#ifdef _WIN32
|
||||
rt_log("resolution: DLSS %s needs nvngx_dlss.dll next to totk.exe, which is missing: the scene follows "
|
||||
"render_scale", m);
|
||||
#else
|
||||
rt_log("resolution: DLSS %s needs libnvidia-ngx-dlss.so.<version> next to totk, which is missing: the "
|
||||
"scene follows render_scale", m);
|
||||
#endif
|
||||
}
|
||||
return dll ? dlss[i].k : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void decide(void) {
|
||||
if (R.decided) return;
|
||||
R.decided = 1;
|
||||
@@ -88,26 +136,7 @@ static void decide(void) {
|
||||
}
|
||||
const char *s = cfg_value("TOTK_RENDER_SCALE");
|
||||
double k = 5.0 / 6.0;
|
||||
// a DLSS mode other than dlaa sets the scene size, only while DLSS/DLAA is on: switched off, the mode the menu
|
||||
// still holds would shrink the scene for the plain upscaler
|
||||
const char *m = cfg_value("TOTK_DLSS");
|
||||
static const struct { const char *name; double k; } dlss[] = {
|
||||
{"quality", 2.0 / 3.0}, {"balanced", 0.58}, {"performance", 0.5}};
|
||||
for (int i = 0; cfg_bool("TOTK_DLAA") && i < 3; i++)
|
||||
if (!strcmp(m, dlss[i].name)) R.dlss_k = dlss[i].k;
|
||||
if (R.dlss_k && !dlss_dll()) {
|
||||
#ifdef _WIN32
|
||||
rt_log("resolution: DLSS %s needs nvngx_dlss.dll next to totk.exe, which is missing: the scene follows render_scale", m);
|
||||
#else
|
||||
rt_log("resolution: DLSS %s runs on Windows only: the scene follows render_scale", m);
|
||||
#endif
|
||||
R.dlss_k = 0;
|
||||
}
|
||||
if (R.dlss_k) {
|
||||
k = R.dlss_k;
|
||||
R.scene_set = 1;
|
||||
rt_log("resolution: DLSS %s: the scene at %.0f %% of the output (render_scale ignored)", m, k * 100);
|
||||
} else if (strcmp(s, "game")) {
|
||||
if (strcmp(s, "game")) {
|
||||
char *end;
|
||||
double pct = strtod(s, &end);
|
||||
if (end != s && !*end && pct >= 25 && pct <= 400) {
|
||||
@@ -117,11 +146,20 @@ static void decide(void) {
|
||||
rt_log("resolution: TOTK_RENDER_SCALE=%s is not game or a percentage 25..400; game", s);
|
||||
}
|
||||
}
|
||||
double sw = R.ow * k, sh = R.oh * k;
|
||||
double fit = fmin(1.0, fmin(SCENE_MAX / sw, SCENE_MAX / sh));
|
||||
if (fit < 1.0) R.scene_set = 1;
|
||||
R.sw = (unsigned)(sw * fit + 0.5);
|
||||
R.sh = (unsigned)(sh * fit + 0.5);
|
||||
// A DLSS mode other than dlaa sets the scene size instead. On 1.4.3 with DLSS able to run (DLAA on, the DLL there)
|
||||
// the mode can change while the game runs (res_dlss_mode): the scene patch is then always on and the heap is sized
|
||||
// for the largest scene the modes ask for. Everyone else keeps the game's own scene size and its dynamic resolution
|
||||
// unless render_scale or a DLSS mode asks for another, and a heap sized for that scene alone.
|
||||
R.base_k = k;
|
||||
R.live = RT_V143 && cfg_bool("TOTK_DLAA") && dlss_dll();
|
||||
const char *m = cfg_value("TOTK_DLSS");
|
||||
R.dlss_k = dlss_share(m);
|
||||
if (R.dlss_k) rt_log("resolution: DLSS %s: the scene at %.0f %% of the output (render_scale ignored)", m, R.dlss_k * 100);
|
||||
double sk = R.dlss_k ? R.dlss_k : k;
|
||||
if (R.live || R.dlss_k || R.ow * sk > SCENE_MAX || R.oh * sk > SCENE_MAX) R.scene_set = 1;
|
||||
scene_for(sk, &R.sw, &R.sh);
|
||||
if (R.live) scene_for(fmax(k, 2.0 / 3.0), &R.hw, &R.hh);
|
||||
else R.hw = R.sw, R.hh = R.sh;
|
||||
const char *sr = cfg_value("TOTK_SHADOW_RES");
|
||||
if (strcmp(sr, "game")) {
|
||||
unsigned n = (unsigned)atoi(sr);
|
||||
@@ -144,7 +182,7 @@ static int bigger(void) {
|
||||
uint64_t res_heap_extra_kib(void) {
|
||||
if (!bigger()) return 0;
|
||||
double grow = fmax(0.0, (double)R.ow * R.oh / (BASE_W * BASE_H) - 1.0) +
|
||||
fmax(0.0, (double)R.sw * R.sh / (SCENE_W * SCENE_H) - 1.0);
|
||||
fmax(0.0, (double)R.hw * R.hh / (SCENE_W * SCENE_H) - 1.0);
|
||||
uint64_t kib = grow > 0 ? (uint64_t)ceil(DTA_KIB * (1.0 + grow) / 1024.0) * 1024 - DTA_KIB : 0;
|
||||
return kib + ((shadow_kib() + 1023) & ~1023ull); // whole MiB
|
||||
}
|
||||
@@ -309,6 +347,7 @@ void res_init(void) {
|
||||
rt_patch_switch(output, R.ow != BASE_W || R.oh != BASE_H);
|
||||
rt_patch_switch(scene, scene_on);
|
||||
R.on = 1;
|
||||
if (R.live) g_fnprof = 1; // until the game sets its render size: res_fn_hit, or res_present gives up
|
||||
// Another shape than 16:9 (more than 1% off): the camera takes the output's ratio (recomp/patches-v121.txt and
|
||||
// patches-v143.txt, set aspect), so a 21:9 output shows more of the world at the sides instead of a stretched 16:9 view. The HUD's layouts
|
||||
// for that shape come from romfs-patches-<w>x<h> (romfs.c overlay_init, tools/make_romfs_patches.py --aspect).
|
||||
@@ -379,6 +418,62 @@ void res_wanted(unsigned out[2]) {
|
||||
out[0] = R.ow, out[1] = R.oh;
|
||||
}
|
||||
|
||||
// A TOTK_DLSS change while the game runs (vk_dlaa.c, at a present): the new scene size goes to the patch page, where
|
||||
// the game reads it when it sets its render size (main+0x585e48). 1.4.3 does that once, at start: its renderer setup
|
||||
// (main+0x7000e67028) calls main+0x585bd0(renderer, force), which picks the console mode's sizes and flags the render
|
||||
// targets for re-creation. The setup call is caught through the function-entry hook (res_fn_hit, on only until then),
|
||||
// and the next nvnQueuePresentTexture on the game's thread calls it again with force set (res_present), between frames
|
||||
// as at start. The heap was sized for the largest scene a mode asks for. 1 when the size changed.
|
||||
#define SET_RENDER_SIZE 0x7000585bd0ull
|
||||
static uint64_t renderer; // x0 of the game's own call of SET_RENDER_SIZE
|
||||
static volatile int size_pending; // the page holds a size the game has not set yet
|
||||
|
||||
void res_fn_hit(uint64_t addr, uint64_t x0) {
|
||||
if (addr != SET_RENDER_SIZE) return;
|
||||
renderer = x0;
|
||||
g_fnprof = 0;
|
||||
rt_log("resolution: renderer at %#llx; the scene size can change while the game runs", (unsigned long long)x0);
|
||||
}
|
||||
|
||||
void res_present(Cpu *c) {
|
||||
// The hook runs on every guest function entry while it waits: should the game set its render size before
|
||||
// res_init or never, it stops after 1800 presents (half a minute at 60 fps) and modes change at the next start.
|
||||
static unsigned waited;
|
||||
if (g_fnprof && !renderer && ++waited == 1800) {
|
||||
g_fnprof = 0;
|
||||
rt_log("resolution: the game's render size setup was not seen; DLSS modes change at the next start");
|
||||
}
|
||||
if (!size_pending || !renderer) return;
|
||||
size_pending = 0;
|
||||
Cpu keep = *c; // the guest's registers as at the present call: the setup runs as if it never happened
|
||||
rt_call(c, SET_RENDER_SIZE, 2, (uint64_t)renderer, (uint64_t)1);
|
||||
memcpy(c->x, keep.x, sizeof c->x);
|
||||
memcpy(c->v, keep.v, sizeof c->v);
|
||||
c->nzcv = keep.nzcv, c->fpsr = keep.fpsr;
|
||||
rt_log("resolution: the game set the scene size %ux%u", R.sw, R.sh);
|
||||
}
|
||||
|
||||
int res_dlss_mode(const char *m) {
|
||||
decide();
|
||||
double k = dlss_share(m);
|
||||
if (k == R.dlss_k || !R.on) return 0;
|
||||
if (!RT_V143 || !renderer) {
|
||||
rt_log("resolution: DLSS %s takes effect at the next start (the game set its render size already)", m);
|
||||
return 0;
|
||||
}
|
||||
R.dlss_k = k;
|
||||
unsigned w, h;
|
||||
scene_for(k ? k : R.base_k, &w, &h);
|
||||
if (w == R.sw && h == R.sh) return 0;
|
||||
double s = RT_V143 ? (double)R.ow / BASE_W : 1.0;
|
||||
float sc[2] = {(float)(w / s), (float)(h / s)};
|
||||
memcpy((uint8_t *)rt_patch_page() + 16, sc, sizeof sc);
|
||||
R.sw = w, R.sh = h;
|
||||
size_pending = 1;
|
||||
rt_log("resolution: DLSS %s: scene %ux%u", m, w, h);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void res_sizes(unsigned out[2], unsigned scene[2]) {
|
||||
decide();
|
||||
out[0] = R.on ? R.ow : BASE_W, out[1] = R.on ? R.oh : BASE_H;
|
||||
|
||||
@@ -6,14 +6,26 @@
|
||||
// The camera block is TotK's scene uniform block (bound as c4 in the scene's vertex shaders): vec4 rows 3-6 are the
|
||||
// view-projection, rows of a row-major 4x4 with OpenGL clip depth (-w..w). The replay's viewports have negative
|
||||
// heights, so window y grows downwards from ndc y = +1, and depth is z_ndc * 0.5 + 0.5.
|
||||
// With pc.bias > 0 the pass also writes DLSS's bias mask (vk_dlaa.c, "characters"): G-buffer target 0 carries an object
|
||||
// class in green, above 64/255 for characters, creatures and what they hold (Link 253, others 125, held items 160;
|
||||
// terrain 0, foliage 32). A pixel gets pc.bias when a character covers it now, or covered the place its surface came
|
||||
// from last frame (the previous class mask, read through the motion vector, 3x3): there the history holds a moving
|
||||
// character that the camera-only vector does not follow, which is the trail. chars_out keeps this frame's mask for the
|
||||
// next; chars_prev is last frame's (pc.prev_ok 0: none).
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
layout(binding = 0) uniform sampler2D depth_tex;
|
||||
layout(binding = 1, rg16f) uniform writeonly image2D mv_out;
|
||||
layout(binding = 2, std140) uniform Cur { vec4 r[16]; } cur;
|
||||
layout(binding = 3, std140) uniform Prev { vec4 r[16]; } prev;
|
||||
layout(binding = 4) uniform sampler2D ids_tex;
|
||||
layout(binding = 5, r8) uniform writeonly image2D mask_out;
|
||||
layout(binding = 6, r8) uniform readonly image2D chars_prev;
|
||||
layout(binding = 7, r8) uniform writeonly image2D chars_out;
|
||||
layout(push_constant) uniform P {
|
||||
vec2 size; // render size in pixels
|
||||
vec2 jitter; // this frame's jitter, pixels, as the scene was drawn (window x right, y down)
|
||||
float bias; // the mask value for character pixels; 0: no mask (bindings 4-7 unused)
|
||||
float prev_ok; // 1: chars_prev holds last frame's class mask at this size
|
||||
} pc;
|
||||
|
||||
mat4 vp(vec4 a, vec4 b, vec4 c, vec4 d) { return transpose(mat4(a, b, c, d)); }
|
||||
@@ -37,4 +49,15 @@ void main() {
|
||||
}
|
||||
}
|
||||
imageStore(mv_out, p, vec4(mv, 0.0, 0.0));
|
||||
if (pc.bias <= 0.0) return;
|
||||
float ch = texelFetch(ids_tex, p, 0).g > 64.0 / 255.0 ? 1.0 : 0.0;
|
||||
imageStore(chars_out, p, vec4(ch));
|
||||
float was = 0.0;
|
||||
if (pc.prev_ok > 0.5) {
|
||||
ivec2 q = ivec2(floor(win + mv)), hi = ivec2(pc.size) - 1;
|
||||
for (int dy = -1; dy <= 1; dy++)
|
||||
for (int dx = -1; dx <= 1; dx++)
|
||||
was = max(was, imageLoad(chars_prev, clamp(q + ivec2(dx, dy), ivec2(0), hi)).r);
|
||||
}
|
||||
imageStore(mask_out, p, vec4(max(ch, was) * pc.bias));
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
// the game's up to PIVOT and a straight line with its slope above, so mid-greys look as in SDR and highlights keep
|
||||
// going; the grade works on the colour divided by its largest channel above 1; a roll-off on the largest channel stops
|
||||
// at the display's peak. The output is relative to the HUD's white (1.0), which the present maps to TOTK_HDR_UI_NITS.
|
||||
// Hue correction (hdr.z, TOTK_HDR_HUE): the game's SDR clamp turns bright saturated colours towards white (a torch flame
|
||||
// clips red, then green, and reads yellow-white, the colour its emitted light was tuned to); the extended curve keeps
|
||||
// their ratios, so they stay saturated. From the picture's white upwards the colour moves towards the game's own SDR
|
||||
// result at the HDR luminance, by up to hdr.z (RenoDX's TotK mod has the same control).
|
||||
// Approach after RenoDX's TotK mod by souperman9 and Musa (github.com/clshortfuse/renodx, src/games/totk, MIT): its
|
||||
// "extended vanilla" curve.
|
||||
// Bindings are those of the game's translated shader: vk_hdr.c rewrites them from the original's names.
|
||||
@@ -14,7 +18,7 @@ layout(location = 0) out vec4 color;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform SupportBuffer {
|
||||
vec4 pad[90];
|
||||
vec4 hdr; // byte 1440 (vk_exec_draw.c support_fill): game white / HUD white, peak / game white
|
||||
vec4 hdr; // byte 1440 (vk_exec_draw.c support_fill): game white / HUD white, peak / game white, hue correction
|
||||
} support_buffer;
|
||||
layout(set = 0, binding = 1) uniform sampler2D fp_t_tcb_8;
|
||||
layout(set = 0, binding = 2) uniform sampler2D fp_t_tcb_A;
|
||||
@@ -51,6 +55,15 @@ void main() {
|
||||
float grade = g.y * (1.0 - d * d) + g.x;
|
||||
c = s * ((n - m) * grade + m);
|
||||
|
||||
// the game's own SDR result: the same blend clamped to 0..1, graded
|
||||
vec3 sdr_c = clamp(mix(lum > 0.0 ? x * (sdr / lum) : x, vec3(1.0) - exp(-x), sdr * sdr), 0.0, 1.0);
|
||||
float sm = maxc(sdr_c), sd = sdr_c.r * 0.6666667 + (sdr_c.g + sdr_c.b) * w.z - 1.0;
|
||||
sdr_c = (sdr_c - sm) * (g.y * (1.0 - sd * sd) + g.x) + sm;
|
||||
float hue = clamp(support_buffer.hdr.z, 0.0, 1.0);
|
||||
const vec3 Y = vec3(0.2126, 0.7152, 0.0722); // Rec. 709 luminance
|
||||
float yc = dot(c, Y), ys = dot(sdr_c, Y);
|
||||
if (hue > 0.0 && ys > 1e-5) c = mix(c, max(sdr_c, vec3(0.0)) * (yc / ys), hue * smoothstep(0.5, 1.5, maxc(c)));
|
||||
|
||||
// roll-off of the largest channel from half the peak to the peak
|
||||
float top = maxc(c), knee = 0.5 * peak;
|
||||
if (top > knee) {
|
||||
|
||||
+64
-31
@@ -346,6 +346,12 @@ static int fonts_up(float scale) {
|
||||
ImFontGlyphRangesBuilder b;
|
||||
b.AddText(ui_text_in("en", kCjk[i].name));
|
||||
ui_text_visit(kCjk[i].lang, add_row_glyphs, &b);
|
||||
// Text the menu shows that no catalog row holds: a mod's cheat names and its folder name,
|
||||
// both read from disk at runtime. Their glyphs would otherwise fall back to the missing-glyph box.
|
||||
for (int k = 0; k < cheats_count(); k++)
|
||||
if (const CheatInfo *ci = cheats_info(k); ci && ci->name) b.AddText(ci->name);
|
||||
for (int k = 0; k < mods_count(); k++)
|
||||
if (const ModInfo *mi = mods_at(k); mi) b.AddText(mi->name);
|
||||
b.BuildRanges(&ranges[i]);
|
||||
ImFontConfig mc;
|
||||
mc.MergeMode = true;
|
||||
@@ -485,13 +491,15 @@ static void row_window_size(void) {
|
||||
}
|
||||
|
||||
// The game resolution (resolution.c): 16:9 sizes from its smallest, 1280x720 (for weak GPUs), up, the common 21:9 and
|
||||
// 32:9 monitor sizes and the 16:10 screens of the Steam Deck, handhelds and laptops (the camera and the HUD follow
|
||||
// 32:9 monitor sizes, the 16:10 screens of the Steam Deck, handhelds and laptops and the 3:2 screens of laptops and
|
||||
// tablets (Surface, Framework; the camera and the HUD follow
|
||||
// another shape, resolution.c res_init; the installer lays the HUD out for each, installer/steps.py ASPECT_SIZES), and
|
||||
// the current value when it is another size.
|
||||
static void row_output_res(void) {
|
||||
static const char *const vals[] = {"1280x720", "1600x900", "1920x1080", "2560x1440", "3200x1800", "3840x2160",
|
||||
"2560x1080", "3440x1440", "3840x1600", "5120x2160", "3840x1080", "5120x1440",
|
||||
"1280x800", "1680x1050", "1920x1200", "2560x1600"};
|
||||
"1280x800", "1680x1050", "1920x1200", "2560x1600",
|
||||
"1920x1280", "2160x1440", "2256x1504", "2880x1920", "3000x2000"};
|
||||
enum { N = sizeof vals / sizeof *vals };
|
||||
char shown[N][32];
|
||||
const char *names[N];
|
||||
@@ -551,27 +559,25 @@ static void row_gpu(void) {
|
||||
opt_choice_list("TOTK_GPU_INDEX", UI_MENU_GPU, vals, names, g_gpu_n + 1, UI_MENU_GPU_DESC);
|
||||
}
|
||||
|
||||
// The game language: one entry per language TotK ships (a Switch code that names it), each under its own name. The
|
||||
// Switch's Portuguese is not among them.
|
||||
// The game language: one entry per Mals language file the running game build ships (ui_text_game_langs), each under
|
||||
// its own name. A code that names no entry exactly (older "en", "fr-CA" on a build without it) selects the first entry
|
||||
// of the same catalog language.
|
||||
static void row_game_lang(void) {
|
||||
static const struct { const char *code; UiTextId name; } langs[] = {
|
||||
{"en-US", UI_MENU_LANGNAME_EN}, {"ja", UI_MENU_LANGNAME_JA}, {"fr", UI_MENU_LANGNAME_FR},
|
||||
{"de", UI_MENU_LANGNAME_DE}, {"it", UI_MENU_LANGNAME_IT}, {"es", UI_MENU_LANGNAME_ES},
|
||||
{"nl", UI_MENU_LANGNAME_NL}, {"ru", UI_MENU_LANGNAME_RU},
|
||||
{"zh-Hans", UI_MENU_LANGNAME_ZH_HANS}, {"zh-Hant", UI_MENU_LANGNAME_ZH_HANT}, {"ko", UI_MENU_LANGNAME_KO},
|
||||
};
|
||||
enum { N = sizeof langs / sizeof *langs + 1 };
|
||||
const char *vals[N], *names[N];
|
||||
UiGameLang langs[16];
|
||||
int n = ui_text_game_langs(rt_game_version, langs, 16);
|
||||
const char *vals[17], *names[17];
|
||||
vals[0] = "";
|
||||
names[0] = menu_text(UI_MENU_GAME_LANG_AUTO);
|
||||
const char *now = cfg_value("TOTK_LANG");
|
||||
int cur = *now ? -1 : 0;
|
||||
for (int i = 1; i < N; i++) {
|
||||
for (int i = 1; i <= n; i++) {
|
||||
vals[i] = langs[i - 1].code;
|
||||
names[i] = menu_text(langs[i - 1].name);
|
||||
if (*now && !strcmp(ui_text_lang(now), ui_text_lang(vals[i]))) cur = i; // en-GB is English too
|
||||
if (*now && !strcmp(now, vals[i])) cur = i;
|
||||
}
|
||||
opt_choice_list("TOTK_LANG", UI_MENU_GAME_LANG, vals, names, N, UI_MENU_GAME_LANG_DESC, cur);
|
||||
for (int i = 1; i <= n && *now && cur < 0; i++)
|
||||
if (!strcmp(ui_text_lang(now), ui_text_lang(vals[i]))) cur = i;
|
||||
opt_choice_list("TOTK_LANG", UI_MENU_GAME_LANG, vals, names, n + 1, UI_MENU_GAME_LANG_DESC, cur);
|
||||
}
|
||||
|
||||
// The frame limit (frame_rate.c): unset, the older fps60 = 0 still means 30. The monitor choice shows the rate it gives.
|
||||
@@ -672,8 +678,13 @@ static void tab_screen(float scale) {
|
||||
opt_slider("TOTK_CONTACT_SHADOWS", UI_MENU_CONTACT_SHADOWS, 0, 100, UI_MENU_CONTACT_SHADOWS_DESC, 10, "%.0f %%");
|
||||
opt_toggle("TOTK_DLAA", UI_MENU_DLAA, UI_MENU_DLAA_DESC);
|
||||
static const UiTextId dlss[] = {UI_MENU_DLSS_DLAA, UI_MENU_DLSS_QUALITY, UI_MENU_DLSS_BALANCED,
|
||||
UI_MENU_DLSS_PERFORMANCE};
|
||||
opt_choice("TOTK_DLSS", UI_MENU_DLSS, dlss, 4, UI_MENU_DLSS_DESC);
|
||||
UI_MENU_DLSS_PERFORMANCE, UI_MENU_DLSS_ULTRA};
|
||||
opt_choice("TOTK_DLSS", UI_MENU_DLSS, dlss, 5, UI_MENU_DLSS_DESC);
|
||||
static const UiTextId preset[] = {UI_MENU_DLSS_PRESET_DEFAULT, UI_MENU_DLSS_PRESET_K, UI_MENU_DLSS_PRESET_J};
|
||||
opt_choice("TOTK_DLSS_PRESET", UI_MENU_DLSS_PRESET, preset, 3, UI_MENU_DLSS_PRESET_DESC);
|
||||
static const UiTextId chars[] = {UI_MENU_DLSS_CHARS_OFF, UI_MENU_DLSS_CHARS_LIGHT, UI_MENU_DLSS_CHARS_MEDIUM,
|
||||
UI_MENU_DLSS_CHARS_FULL};
|
||||
opt_choice("TOTK_DLSS_CHAR_MASK", UI_MENU_DLSS_CHARS, chars, 4, UI_MENU_DLSS_CHARS_DESC);
|
||||
opt_toggle("TOTK_FXAA", UI_MENU_FXAA, UI_MENU_FXAA_DESC);
|
||||
rows_color();
|
||||
opt_group(UI_MENU_HDR_GROUP);
|
||||
@@ -681,6 +692,7 @@ static void tab_screen(float scale) {
|
||||
opt_slider("TOTK_HDR_PEAK_NITS", UI_MENU_HDR_PEAK, 400, 10000, UI_MENU_HDR_PEAK_DESC, 50, "%.0f");
|
||||
opt_slider("TOTK_HDR_GAME_NITS", UI_MENU_HDR_GAME, 80, 500, UI_MENU_HDR_GAME_DESC, 5, "%.0f");
|
||||
opt_slider("TOTK_HDR_UI_NITS", UI_MENU_HDR_UI, 80, 500, UI_MENU_HDR_UI_DESC, 5, "%.0f");
|
||||
opt_slider("TOTK_HDR_HUE", UI_MENU_HDR_HUE, 0, 100, UI_MENU_HDR_HUE_DESC, 5, "%.0f %%");
|
||||
opt_end();
|
||||
}
|
||||
|
||||
@@ -697,6 +709,8 @@ static void tab_game(float scale) {
|
||||
static void tab_sound(float scale) {
|
||||
opt_begin("##sound", scale);
|
||||
opt_slider("TOTK_VOLUME", UI_MENU_VOLUME, 0, 100, UI_MENU_VOLUME_DESC, 5, "%.0f %%");
|
||||
static const UiTextId channels[] = {UI_MENU_CHANNELS_AUTO, UI_MENU_CHANNELS_STEREO, UI_MENU_CHANNELS_51};
|
||||
opt_choice("TOTK_AUDIO_CHANNELS", UI_MENU_AUDIO_CHANNELS, channels, 3, UI_MENU_AUDIO_CHANNELS_DESC);
|
||||
opt_end();
|
||||
}
|
||||
|
||||
@@ -851,6 +865,7 @@ static void tab_menu(float scale) {
|
||||
static const UiTextId langs[] = {UI_MENU_MENU_LANG_AUTO, UI_MENU_LANGNAME_EN, UI_MENU_LANGNAME_RU,
|
||||
UI_MENU_LANGNAME_PT_BR, UI_MENU_LANGNAME_ZH_HANS};
|
||||
opt_choice("TOTK_MENU_LANG", UI_MENU_MENU_LANG, langs, sizeof langs / sizeof *langs, UI_MENU_MENU_LANG_DESC);
|
||||
opt_toggle("TOTK_DISCORD_RPC", UI_MENU_DISCORD_RPC, UI_MENU_DISCORD_RPC_DESC);
|
||||
opt_end();
|
||||
}
|
||||
|
||||
@@ -936,7 +951,8 @@ static void tab_advanced(float scale) {
|
||||
opt_choice("TOTK_MEMORY", UI_MENU_MEMORY, mem, 4, UI_MENU_MEMORY_DESC);
|
||||
opt_toggle("TOTK_TEX_ALIAS", UI_MENU_TEX_ALIAS, UI_MENU_TEX_ALIAS_DESC);
|
||||
opt_slider("TOTK_TEX_VRAM_PRESSURE", UI_MENU_TEX_VRAM_PRESSURE, 0, 100, UI_MENU_TEX_VRAM_PRESSURE_DESC, 1, "%.0f%%");
|
||||
opt_toggle("TOTK_ASTC_BC", UI_MENU_ASTC_BC, UI_MENU_ASTC_BC_DESC);
|
||||
static const UiTextId astc_bc[] = {UI_MENU_AUTO, UI_MENU_OFF, UI_MENU_ON}; // unset, 0, 1
|
||||
opt_choice("TOTK_ASTC_BC", UI_MENU_ASTC_BC, astc_bc, 3, UI_MENU_ASTC_BC_DESC);
|
||||
|
||||
opt_group(UI_MENU_GROUP_FIXES);
|
||||
opt_toggle("TOTK_LOD_GUARD", UI_MENU_LOD_GUARD, UI_MENU_LOD_GUARD_DESC);
|
||||
@@ -1373,17 +1389,30 @@ static void perf_full_draw(const MenuTarget *t, float scale) {
|
||||
ImGui::SetWindowFontScale(0.72f);
|
||||
const ImVec4 gpu_c(0.55f, 0.83f, 0.31f, 1), cpu_c(0.18f, 0.59f, 0.80f, 1), ram_c(0.80f, 0.45f, 0.70f, 1),
|
||||
fps_c(0.95f, 0.95f, 0.95f, 1), wait_c(0.93f, 0.76f, 0.33f, 1), pic_c(0.62f, 0.70f, 0.95f, 1);
|
||||
// the port version and the game update first, so a screenshot in a bug report carries both; formatted once per
|
||||
// menu language
|
||||
static char ver[96];
|
||||
static const char *ver_fmt;
|
||||
if (ver_fmt != menu_text(UI_MENU_VERSION)) {
|
||||
ver_fmt = menu_text(UI_MENU_VERSION);
|
||||
char game[16];
|
||||
snprintf(game, sizeof game, "%d.%d.%d", rt_game_version / 100, rt_game_version / 10 % 10, rt_game_version % 10);
|
||||
snprintf(ver, sizeof ver, ver_fmt, rt_port_version, game);
|
||||
}
|
||||
ImGui::TextDisabled("%s", ver);
|
||||
if (ImGui::BeginTable("##perft", 4, ImGuiTableFlags_SizingFixedFit)) {
|
||||
static const UiTextId labels[] = {UI_MENU_OV_GPU, UI_MENU_OV_VRAM, UI_MENU_OV_CPU, UI_MENU_OV_RAM};
|
||||
static const PerfCol c0[] = {{UI_MENU_OV_FPS, 888}};
|
||||
static const PerfCol c1[] = {{UI_MENU_OV_FMT_PCT, 100}, {UI_MENU_OV_FMT_GB, 888.8f}, {UI_MENU_OV_FRAME_MS, 888.8f}};
|
||||
static const PerfCol c2[] = {{UI_MENU_OV_FMT_TEMP, 188}, {UI_MENU_OV_FMT_GB_OF, 888.8f}, {UI_MENU_OV_MAX_MS, 8888.8f}};
|
||||
static const UiTextId labels[] = {UI_MENU_OV_GPU, UI_MENU_OV_VRAM, UI_MENU_OV_CPU, UI_MENU_OV_RAM,
|
||||
UI_MENU_OV_FPS_LABEL, UI_MENU_OV_FRAME_LABEL};
|
||||
static const PerfCol c1[] = {{UI_MENU_OV_FMT_PCT, 100}, {UI_MENU_OV_FMT_GB, 888.8f}, {UI_MENU_OV_FMT_FPS, 888},
|
||||
{UI_MENU_OV_FRAME_MS, 888.8f}};
|
||||
static const PerfCol c2[] = {{UI_MENU_OV_FMT_TEMP, 188}, {UI_MENU_OV_FMT_GB_OF, 888.8f}, {UI_MENU_OV_LOW1, 888},
|
||||
{UI_MENU_OV_MAX_MS, 8888.8f}};
|
||||
static const PerfCol c3[] = {{UI_MENU_OV_FMT_POWER, 888}, {UI_MENU_OV_FMT_MHZ, 8888}, {UI_MENU_OV_GAME_PCT, 100},
|
||||
{UI_MENU_OV_GAME_GB, 888.8f}};
|
||||
perf_column("##c0", c0, 1, labels, 4);
|
||||
perf_column("##c1", c1, 3);
|
||||
perf_column("##c2", c2, 3);
|
||||
perf_column("##c3", c3, 4);
|
||||
{UI_MENU_OV_GAME_GB, 888.8f}, {UI_MENU_OV_LOW01, 888}};
|
||||
perf_column("##c0", nullptr, 0, labels, 6);
|
||||
perf_column("##c1", c1, 4);
|
||||
perf_column("##c2", c2, 4);
|
||||
perf_column("##c3", c3, 5);
|
||||
perf_label(UI_MENU_OV_GPU, gpu_c);
|
||||
perf_cell(UI_MENU_OV_FMT_PCT, s.gpu_pct);
|
||||
perf_cell(UI_MENU_OV_FMT_TEMP, s.gpu_temp_c);
|
||||
@@ -1400,12 +1429,16 @@ static void perf_full_draw(const MenuTarget *t, float scale) {
|
||||
perf_cell(UI_MENU_OV_FMT_GB, perf_gb(s.ram_used_mb));
|
||||
perf_cell(UI_MENU_OV_FMT_GB_OF, perf_gb(s.ram_total_mb));
|
||||
perf_cell(UI_MENU_OV_GAME_GB, perf_gb(s.ram_game_mb));
|
||||
ImGui::TableNextColumn(); // the frame row: no label, the rate in its place
|
||||
char b[64];
|
||||
snprintf(b, sizeof b, menu_text(UI_MENU_OV_FPS), s.fps);
|
||||
ImGui::TextColored(fps_c, "%s", b);
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, 6 * scale); // a gap between the machine and the frames
|
||||
// the frames: the rate over the last second with the lows over the last ten seconds, then the frame times
|
||||
perf_label(UI_MENU_OV_FPS_LABEL, fps_c);
|
||||
perf_cell(UI_MENU_OV_FMT_FPS, s.fps);
|
||||
perf_cell(UI_MENU_OV_LOW1, s.low1_fps);
|
||||
perf_cell(UI_MENU_OV_LOW01, s.low01_fps);
|
||||
perf_label(UI_MENU_OV_FRAME_LABEL, fps_c);
|
||||
perf_cell(UI_MENU_OV_FRAME_MS, s.frame_ms_avg);
|
||||
perf_cell(UI_MENU_OV_MAX_MS, s.frame_ms_max);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
float top = s.frame_ms_max > 50 ? s.frame_ms_max : 50;
|
||||
|
||||
+145
-29
@@ -18,13 +18,23 @@ typedef struct { const char *lang; const char *const *text; } Row;
|
||||
[UI_MENU_LANGNAME_DE] = "Deutsch", [UI_MENU_LANGNAME_IT] = "Italiano", [UI_MENU_LANGNAME_ES] = "Español", \
|
||||
[UI_MENU_LANGNAME_NL] = "Nederlands", [UI_MENU_LANGNAME_RU] = "Русский", \
|
||||
[UI_MENU_LANGNAME_ZH_HANS] = "简体中文", [UI_MENU_LANGNAME_ZH_HANT] = "繁體中文", [UI_MENU_LANGNAME_KO] = "한국어", \
|
||||
[UI_MENU_LANGNAME_PT_BR] = "Português (Brasil)"
|
||||
[UI_MENU_LANGNAME_PT_BR] = "Português (Brasil)", [UI_MENU_LANGNAME_EN_GB] = "English (UK)", \
|
||||
[UI_MENU_LANGNAME_FR_CA] = "Français (Canada)", [UI_MENU_LANGNAME_ES_419] = "Español (Latinoamérica)"
|
||||
|
||||
// SWKBD: TITLE, PROMPT, DEFAULT_NAME (at most 9 UTF-16 units, no '@' '%' '\'), OK, CANCEL, PAD_HINT.
|
||||
#define SWKBD(title, prompt, name, ok, cancel, hint) \
|
||||
[UI_SWKBD_TITLE] = title, [UI_SWKBD_PROMPT] = prompt, [UI_SWKBD_DEFAULT_NAME] = name, [UI_SWKBD_OK] = ok, \
|
||||
[UI_SWKBD_CANCEL] = cancel, [UI_SWKBD_PAD_HINT] = hint, LANG_NAMES
|
||||
|
||||
// The DLSS model file and the executable, as the player sees them on this platform.
|
||||
#ifdef _WIN32
|
||||
#define DLSS_FILE "nvngx_dlss.dll"
|
||||
#define EXE_FILE "totk.exe"
|
||||
#else
|
||||
#define DLSS_FILE "libnvidia-ngx-dlss.so"
|
||||
#define EXE_FILE "totk"
|
||||
#endif
|
||||
|
||||
static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
SWKBD("Text entry", "Enter a name", "Horse", "OK", "Cancel", "Controller: A = OK, B = Cancel"),
|
||||
|
||||
@@ -55,7 +65,7 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_OUTPUT_RES] = "Game resolution",
|
||||
[UI_MENU_OUTPUT_RES_DESC] = "The size the game draws its picture and interface in. Above 1920 x 1080 the game needs "
|
||||
"more video memory and GPU time. A wider size (21:9) widens the camera's view and moves "
|
||||
"the HUD to the screen's corners; a taller one (16:10, Steam Deck) moves it to the top "
|
||||
"the HUD to the screen's corners; a taller one (16:10, 3:2) moves it to the top "
|
||||
"and bottom edges.",
|
||||
[UI_MENU_RENDER_SCALE] = "3D render scale",
|
||||
[UI_MENU_RENDER_SCALE_DESC] = "Size of the 3D scene relative to the game resolution; the game's FSR scales it to "
|
||||
@@ -109,6 +119,9 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
"bright room.",
|
||||
[UI_MENU_HDR_UI] = "HUD and menu brightness",
|
||||
[UI_MENU_HDR_UI_DESC] = "Nits of the HUD's and this menu's white.",
|
||||
[UI_MENU_HDR_HUE] = "Highlight hue correction",
|
||||
[UI_MENU_HDR_HUE_DESC] = "Bright colours such as fire and sparks turn towards white as in the game's own SDR picture, "
|
||||
"matching the light they cast; 0 keeps them fully saturated.",
|
||||
[UI_MENU_SHADOW_SCALE] = "Shadow resolution",
|
||||
[UI_MENU_SHADOW_SCALE_DESC] = "Draws the sun's shadow maps this many times larger on each side than the game's 960x960: "
|
||||
"sharper shadow edges and leaf shadows. Takes effect at the next start.",
|
||||
@@ -123,15 +136,28 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
"away. Spreads the shadow map over more ground: raise Shadow resolution with it. "
|
||||
"Takes effect at the next start.",
|
||||
[UI_MENU_DLAA] = "NVIDIA DLSS / DLAA",
|
||||
[UI_MENU_DLAA_DESC] = "NVIDIA DLSS for the 3D scene (NVIDIA RTX GPUs; nvngx_dlss.dll next to totk.exe): smoother edges "
|
||||
"and less shimmer. The HUD is not touched. The mode below sets the resolution it works from. Takes effect "
|
||||
"after a restart.",
|
||||
[UI_MENU_DLAA_DESC] = "NVIDIA DLSS for the 3D scene (NVIDIA RTX GPUs; " DLSS_FILE " next to " EXE_FILE "): smoother "
|
||||
"edges and less shimmer. The HUD is not touched. The mode below sets the resolution it works "
|
||||
"from. Takes effect after a restart.",
|
||||
[UI_MENU_DLSS_PRESET] = "DLSS model",
|
||||
[UI_MENU_DLSS_PRESET_DESC] = "Which DLSS model runs. Automatic follows NVIDIA's advice per mode: K for DLAA, Quality "
|
||||
"and Balanced, M for Performance, L for Ultra Performance. J: slightly less ghosting "
|
||||
"behind moving characters, a little more flicker. K: the sharpest model in every mode. "
|
||||
"Applies at once.",
|
||||
[UI_MENU_DLSS_PRESET_DEFAULT] = "Automatic", [UI_MENU_DLSS_PRESET_K] = "K (sharpest)",
|
||||
[UI_MENU_DLSS_PRESET_J] = "J (less ghosting)",
|
||||
[UI_MENU_DLSS_CHARS] = "DLSS character trails",
|
||||
[UI_MENU_DLSS_CHARS_DESC] = "Reduces the faint trails behind moving characters, animals and what they hold: DLSS takes "
|
||||
"less of the previous frames there. Stronger removes more of the trails and leaves those "
|
||||
"edges a little less smooth. Applies at once.",
|
||||
[UI_MENU_DLSS_CHARS_OFF] = "Off", [UI_MENU_DLSS_CHARS_LIGHT] = "Light", [UI_MENU_DLSS_CHARS_MEDIUM] = "Medium",
|
||||
[UI_MENU_DLSS_CHARS_FULL] = "Full",
|
||||
[UI_MENU_DLSS] = "DLSS mode",
|
||||
[UI_MENU_DLSS_DESC] = "DLAA draws the 3D scene at full resolution. Quality, Balanced and Performance draw it at 67, 58 "
|
||||
"and 50 % and let DLSS scale it up, for more frames per second; they replace the 3D render "
|
||||
"scale. Takes effect after a restart.",
|
||||
[UI_MENU_DLSS_DESC] = "DLAA draws the 3D scene at full resolution. Quality, Balanced, Performance and Ultra "
|
||||
"Performance draw it at 67, 58, 50 and 33 % and let DLSS scale it up, which saves GPU time; "
|
||||
"they replace the 3D render scale. Takes effect at once.",
|
||||
[UI_MENU_DLSS_DLAA] = "DLAA (native)", [UI_MENU_DLSS_QUALITY] = "Quality", [UI_MENU_DLSS_BALANCED] = "Balanced",
|
||||
[UI_MENU_DLSS_PERFORMANCE] = "Performance",
|
||||
[UI_MENU_DLSS_PERFORMANCE] = "Performance", [UI_MENU_DLSS_ULTRA] = "Ultra Performance",
|
||||
[UI_MENU_FXAA] = "Game anti-aliasing (FXAA)",
|
||||
[UI_MENU_FXAA_DESC] = "The game's own FXAA edge smoothing. It blurs fine detail a little; with DLAA on it is not needed.",
|
||||
|
||||
@@ -191,6 +217,10 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
"at 60 FPS.",
|
||||
|
||||
[UI_MENU_VOLUME] = "Volume", [UI_MENU_VOLUME_DESC] = "Master volume of the game.",
|
||||
[UI_MENU_AUDIO_CHANNELS] = "Speakers",
|
||||
[UI_MENU_AUDIO_CHANNELS_DESC] = "Stereo or 5.1 surround output. Automatic picks 5.1 when the Windows speaker setup "
|
||||
"has six or more channels. Applies after a restart.",
|
||||
[UI_MENU_CHANNELS_AUTO] = "Automatic", [UI_MENU_CHANNELS_STEREO] = "Stereo", [UI_MENU_CHANNELS_51] = "5.1 surround",
|
||||
|
||||
[UI_MENU_MOTION] = "Motion controls", [UI_MENU_MOTION_DESC] = "Aiming with the controller's gyroscope.",
|
||||
[UI_MENU_RUMBLE_STRENGTH] = "Vibration strength",
|
||||
@@ -322,6 +352,9 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_AMIIBO_STATUS] = "Status: %s",
|
||||
[UI_MENU_MENU_LANG] = "Menu language", [UI_MENU_MENU_LANG_DESC] = "Language of this menu and the overlay.",
|
||||
[UI_MENU_MENU_LANG_AUTO] = "Automatic (from Windows)",
|
||||
[UI_MENU_DISCORD_RPC] = "Discord Rich Presence",
|
||||
[UI_MENU_DISCORD_RPC_DESC] = "Shows \"Playing TotK Recomp\" with the play time in your Discord status while the "
|
||||
"game runs. discord_app_id and discord_image in totk.ini choose the application.",
|
||||
|
||||
[UI_MENU_ADV_WARNING] = "These settings are for testing and troubleshooting. Change them only when you know why.",
|
||||
[UI_MENU_GROUP_SCHED] = "Scheduling", [UI_MENU_GROUP_SHADERS] = "Shaders and pipelines",
|
||||
@@ -359,7 +392,7 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_MEMORY_VERIFY] = "Verify",
|
||||
[UI_MENU_TEX_ALIAS] = "Texture aliasing", [UI_MENU_TEX_ALIAS_DESC] = "A new texture created over a live texture's memory starts with that content instead of empty.",
|
||||
[UI_MENU_TEX_VRAM_PRESSURE] = "Texture memory limit", [UI_MENU_TEX_VRAM_PRESSURE_DESC] = "Percent of video memory above which textures the game released are dropped (they come back from storage when used again). 0 keeps them.",
|
||||
[UI_MENU_ASTC_BC] = "Compress ASTC to BC3", [UI_MENU_ASTC_BC_DESC] = "With the GPU decoder: re-encodes decoded ASTC textures to BC3, a quarter of the video memory. For GPUs with 4 GB or less; textures look slightly blockier.",
|
||||
[UI_MENU_ASTC_BC] = "Compress ASTC to BC3", [UI_MENU_ASTC_BC_DESC] = "With the GPU decoder: re-encodes decoded ASTC textures to BC3, a quarter of the video memory. Textures look slightly blockier. Auto: on for GPUs with 4 GB of video memory or less.",
|
||||
[UI_MENU_LOD_GUARD] = "LOD guard", [UI_MENU_LOD_GUARD_DESC] = "Prevents objects flashing in the wrong place or a flat far field for a frame, by waiting for the game to finish rewriting level-of-detail data before the GPU reads it.",
|
||||
[UI_MENU_FIX_C0] = "Constant buffer fix",
|
||||
[UI_MENU_FIX_C0_DESC] = "Fills the driver-reserved constant bank c0 (storage sizes, dispatch size, point size, base instance) instead of reading zeros.",
|
||||
@@ -404,6 +437,8 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
|
||||
[UI_MENU_OV_FPS] = "%.0f FPS", [UI_MENU_OV_FRAME_MS] = "%.1f ms", [UI_MENU_OV_GAME_PCT] = "game %.0f%%",
|
||||
[UI_MENU_OV_MAX_MS] = "max %.1f ms",
|
||||
[UI_MENU_OV_FPS_LABEL] = "FPS", [UI_MENU_OV_FRAME_LABEL] = "Frame", [UI_MENU_OV_FMT_FPS] = "%.0f",
|
||||
[UI_MENU_OV_LOW1] = "1%% low %.0f", [UI_MENU_OV_LOW01] = "0.1%% low %.0f",
|
||||
[UI_MENU_OV_GPU] = "GPU", [UI_MENU_OV_CPU] = "CPU", [UI_MENU_OV_RAM] = "RAM", [UI_MENU_OV_VRAM] = "VRAM",
|
||||
[UI_MENU_OV_TEMP] = "Temperature", [UI_MENU_OV_POWER] = "Power", [UI_MENU_OV_CLOCK] = "Clock",
|
||||
[UI_MENU_OV_NONE] = "-",
|
||||
@@ -483,7 +518,7 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_OUTPUT_RES] = "Разрешение игры",
|
||||
[UI_MENU_OUTPUT_RES_DESC] = "Размер, в котором игра рисует картинку и интерфейс. Выше 1920 × 1080 игре нужно больше "
|
||||
"видеопамяти и времени GPU. Более широкий размер (21:9) расширяет обзор камеры и "
|
||||
"переносит интерфейс в углы экрана, более высокий (16:10, Steam Deck) переносит его к "
|
||||
"переносит интерфейс в углы экрана, более высокий (16:10, 3:2) переносит его к "
|
||||
"верхнему и нижнему краям.",
|
||||
[UI_MENU_RENDER_SCALE] = "Масштаб 3D-отрисовки",
|
||||
[UI_MENU_RENDER_SCALE_DESC] = "Размер 3D-сцены относительно разрешения игры; FSR игры доводит её до этого размера. "
|
||||
@@ -538,6 +573,9 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
"поднимите.",
|
||||
[UI_MENU_HDR_UI] = "Яркость интерфейса и меню",
|
||||
[UI_MENU_HDR_UI_DESC] = "Ниты белого цвета интерфейса игры и этого меню.",
|
||||
[UI_MENU_HDR_HUE] = "Коррекция оттенка бликов",
|
||||
[UI_MENU_HDR_HUE_DESC] = "Яркие цвета (огонь, искры) уходят к белому, как в SDR-картинке игры, и совпадают со светом, "
|
||||
"который они дают; 0 оставляет их полностью насыщенными.",
|
||||
[UI_MENU_SHADOW_SCALE] = "Разрешение теней",
|
||||
[UI_MENU_SHADOW_SCALE_DESC] = "Карты теней от солнца рисуются во столько раз больше по каждой стороне, чем 960x960 "
|
||||
"самой игры: края теней и тени листвы чётче. Действует после перезапуска.",
|
||||
@@ -552,15 +590,28 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
"Карта теней растягивается на большую площадь, поэтому вместе с этим поднимайте "
|
||||
"разрешение теней. Действует после перезапуска.",
|
||||
[UI_MENU_DLAA] = "NVIDIA DLSS / DLAA",
|
||||
[UI_MENU_DLAA_DESC] = "NVIDIA DLSS для 3D-сцены (видеокарты NVIDIA RTX; nvngx_dlss.dll рядом с totk.exe): ровнее края и "
|
||||
"меньше мерцания. Интерфейс не затрагивается. Режим ниже задаёт разрешение, с которым он работает. "
|
||||
"Действует после перезапуска.",
|
||||
[UI_MENU_DLAA_DESC] = "NVIDIA DLSS для 3D-сцены (видеокарты NVIDIA RTX; " DLSS_FILE " рядом с " EXE_FILE "): ровнее "
|
||||
"края и меньше мерцания. Интерфейс не затрагивается. Режим ниже задаёт разрешение, с которым он "
|
||||
"работает. Действует после перезапуска.",
|
||||
[UI_MENU_DLSS_PRESET] = "Модель DLSS",
|
||||
[UI_MENU_DLSS_PRESET_DESC] = "Какая модель DLSS работает. «Автоматически» следует совету NVIDIA для каждого режима: K "
|
||||
"для DLAA, «Качества» и «Баланса», M для «Производительности», L для «Макс. "
|
||||
"производительности». J: чуть меньше шлейфов за движущимися персонажами, немного больше "
|
||||
"мерцания. K: самая чёткая модель во всех режимах. Действует сразу.",
|
||||
[UI_MENU_DLSS_PRESET_DEFAULT] = "Автоматически", [UI_MENU_DLSS_PRESET_K] = "K (самая чёткая)",
|
||||
[UI_MENU_DLSS_PRESET_J] = "J (меньше шлейфов)",
|
||||
[UI_MENU_DLSS_CHARS] = "Шлейфы персонажей в DLSS",
|
||||
[UI_MENU_DLSS_CHARS_DESC] = "Убирает слабые шлейфы за движущимися персонажами, животными и тем, что они держат: DLSS "
|
||||
"берёт там меньше от прошлых кадров. Чем сильнее, тем меньше шлейфов и тем чуть менее "
|
||||
"гладкие края. Действует сразу.",
|
||||
[UI_MENU_DLSS_CHARS_OFF] = "Выкл.", [UI_MENU_DLSS_CHARS_LIGHT] = "Слабо", [UI_MENU_DLSS_CHARS_MEDIUM] = "Средне",
|
||||
[UI_MENU_DLSS_CHARS_FULL] = "Полностью",
|
||||
[UI_MENU_DLSS] = "Режим DLSS",
|
||||
[UI_MENU_DLSS_DESC] = "DLAA рисует 3D-сцену в полном разрешении. «Качество», «Баланс» и «Производительность» рисуют её "
|
||||
"в 67, 58 и 50 % и масштабируют через DLSS ради большего числа кадров; они заменяют масштаб "
|
||||
"3D-отрисовки. Действует после перезапуска.",
|
||||
[UI_MENU_DLSS_DESC] = "DLAA рисует 3D-сцену в полном разрешении. «Качество», «Баланс», «Производительность» и "
|
||||
"«Макс. производительность» рисуют её в 67, 58, 50 и 33 % и масштабируют через DLSS, что "
|
||||
"экономит время видеокарты; они заменяют масштаб 3D-отрисовки. Действует сразу.",
|
||||
[UI_MENU_DLSS_DLAA] = "DLAA (родное)", [UI_MENU_DLSS_QUALITY] = "Качество", [UI_MENU_DLSS_BALANCED] = "Баланс",
|
||||
[UI_MENU_DLSS_PERFORMANCE] = "Производительность",
|
||||
[UI_MENU_DLSS_PERFORMANCE] = "Производительность", [UI_MENU_DLSS_ULTRA] = "Макс. производительность",
|
||||
[UI_MENU_FXAA] = "Сглаживание игры (FXAA)",
|
||||
[UI_MENU_FXAA_DESC] = "Собственное сглаживание FXAA. Слегка размывает мелкие детали; с DLAA оно не нужно.",
|
||||
|
||||
@@ -617,6 +668,10 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_MODS_QUICKMENU_DESC] = "Исправление порта: чёрный фон меню оружия, щитов и материалов при 60 FPS.",
|
||||
|
||||
[UI_MENU_VOLUME] = "Громкость", [UI_MENU_VOLUME_DESC] = "Общая громкость игры.",
|
||||
[UI_MENU_AUDIO_CHANNELS] = "Динамики",
|
||||
[UI_MENU_AUDIO_CHANNELS_DESC] = "Стерео или объёмный звук 5.1. Автоматически выбирается 5.1, если в Windows "
|
||||
"настроено шесть каналов или больше. Применяется после перезапуска.",
|
||||
[UI_MENU_CHANNELS_AUTO] = "Автоматически", [UI_MENU_CHANNELS_STEREO] = "Стерео", [UI_MENU_CHANNELS_51] = "Объёмный 5.1",
|
||||
|
||||
[UI_MENU_MOTION] = "Управление движением", [UI_MENU_MOTION_DESC] = "Прицеливание гироскопом контроллера.",
|
||||
[UI_MENU_RUMBLE_STRENGTH] = "Сила вибрации",
|
||||
@@ -752,6 +807,9 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_AMIIBO_STATUS] = "Состояние: %s",
|
||||
[UI_MENU_MENU_LANG] = "Язык меню", [UI_MENU_MENU_LANG_DESC] = "Язык этого меню и индикатора.",
|
||||
[UI_MENU_MENU_LANG_AUTO] = "Автоматически (из Windows)",
|
||||
[UI_MENU_DISCORD_RPC] = "Discord Rich Presence",
|
||||
[UI_MENU_DISCORD_RPC_DESC] = "Пока игра запущена, показывает «Играю в TotK Recomp» со временем игры в статусе "
|
||||
"Discord. Приложение выбирают discord_app_id и discord_image в totk.ini.",
|
||||
|
||||
[UI_MENU_ADV_WARNING] = "Эти настройки нужны для тестов и поиска неполадок. Меняйте их, только если знаете зачем.",
|
||||
[UI_MENU_GROUP_SCHED] = "Планирование", [UI_MENU_GROUP_SHADERS] = "Шейдеры и конвейеры",
|
||||
@@ -789,7 +847,7 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_MEMORY_VERIFY] = "Проверка",
|
||||
[UI_MENU_TEX_ALIAS] = "Наследование содержимого текстур", [UI_MENU_TEX_ALIAS_DESC] = "Новая текстура поверх памяти живой текстуры получает её содержимое, а не пустую.",
|
||||
[UI_MENU_TEX_VRAM_PRESSURE] = "Предел памяти текстур", [UI_MENU_TEX_VRAM_PRESSURE_DESC] = "Процент видеопамяти, выше которого освобождённые игрой текстуры удаляются (при повторном использовании загружаются снова). 0 — не удалять.",
|
||||
[UI_MENU_ASTC_BC] = "Сжатие ASTC в BC3", [UI_MENU_ASTC_BC_DESC] = "С декодером на GPU: декодированные текстуры ASTC пересжимаются в BC3 и занимают вчетверо меньше видеопамяти. Для видеокарт с 4 ГБ и меньше; текстуры чуть более блочные.",
|
||||
[UI_MENU_ASTC_BC] = "Сжатие ASTC в BC3", [UI_MENU_ASTC_BC_DESC] = "С декодером на GPU: декодированные текстуры ASTC пересжимаются в BC3 и занимают вчетверо меньше видеопамяти. Текстуры чуть более блочные. Авто: включено на видеокартах с 4 ГБ видеопамяти и меньше.",
|
||||
[UI_MENU_LOD_GUARD] = "Защита LOD", [UI_MENU_LOD_GUARD_DESC] = "Не даёт объектам мелькать не на своём месте, а дальнему плану — на кадр становиться плоским: ждёт, пока игра допишет данные уровней детализации, и только потом их читает GPU.",
|
||||
[UI_MENU_FIX_C0] = "Исправление константного буфера",
|
||||
[UI_MENU_FIX_C0_DESC] = "Заполняет зарезервированный драйвером банк констант c0 (размеры хранилищ, размер запуска, размер точки, базовый экземпляр) вместо чтения нулей.",
|
||||
@@ -836,6 +894,8 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
|
||||
[UI_MENU_OV_FPS] = "%.0f FPS", [UI_MENU_OV_FRAME_MS] = "%.1f мс", [UI_MENU_OV_GAME_PCT] = "игра %.0f%%",
|
||||
[UI_MENU_OV_MAX_MS] = "макс. %.1f мс",
|
||||
[UI_MENU_OV_FPS_LABEL] = "FPS", [UI_MENU_OV_FRAME_LABEL] = "Кадр", [UI_MENU_OV_FMT_FPS] = "%.0f",
|
||||
[UI_MENU_OV_LOW1] = "1%% худших %.0f", [UI_MENU_OV_LOW01] = "0.1%% худших %.0f",
|
||||
[UI_MENU_OV_GPU] = "GPU", [UI_MENU_OV_CPU] = "ЦП", [UI_MENU_OV_RAM] = "ОЗУ", [UI_MENU_OV_VRAM] = "Видеопамять",
|
||||
[UI_MENU_OV_TEMP] = "Температура", [UI_MENU_OV_POWER] = "Мощность", [UI_MENU_OV_CLOCK] = "Частота",
|
||||
[UI_MENU_OV_NONE] = "-",
|
||||
@@ -945,7 +1005,7 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_OUTPUT_RES] = "Resolução do jogo",
|
||||
[UI_MENU_OUTPUT_RES_DESC] = "O tamanho em que o jogo desenha a imagem e a interface. Acima de 1920 x 1080 o jogo "
|
||||
"precisa de mais memória de vídeo e tempo de GPU. Um tamanho mais largo (21:9) amplia a "
|
||||
"visão da câmera e leva o HUD para os cantos da tela; um mais alto (16:10, Steam Deck) "
|
||||
"visão da câmera e leva o HUD para os cantos da tela; um mais alto (16:10, 3:2) "
|
||||
"leva-o para as bordas de cima e de baixo.",
|
||||
[UI_MENU_RENDER_SCALE] = "Escala da renderização 3D",
|
||||
[UI_MENU_RENDER_SCALE_DESC] = "Tamanho da cena 3D em relação à resolução do jogo; o FSR do jogo a amplia até esse "
|
||||
@@ -1100,6 +1160,10 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_AMIIBO_STATUS] = "Estado: %s",
|
||||
[UI_MENU_MENU_LANG] = "Idioma do menu", [UI_MENU_MENU_LANG_DESC] = "Idioma deste menu e do indicador.",
|
||||
[UI_MENU_MENU_LANG_AUTO] = "Automático (do Windows)",
|
||||
[UI_MENU_DISCORD_RPC] = "Discord Rich Presence",
|
||||
[UI_MENU_DISCORD_RPC_DESC] = "Mostra \"Jogando TotK Recomp\" com o tempo de jogo no seu status do Discord "
|
||||
"enquanto o jogo roda. discord_app_id e discord_image no totk.ini escolhem a "
|
||||
"aplicação.",
|
||||
|
||||
[UI_MENU_ADV_WARNING] = "Estas configurações são para testes e solução de problemas. Altere-as só quando souber "
|
||||
"por quê.",
|
||||
@@ -1140,7 +1204,7 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_MEMORY_VERIFY] = "Verificar",
|
||||
[UI_MENU_TEX_ALIAS] = "Aliasing de texturas", [UI_MENU_TEX_ALIAS_DESC] = "Uma textura nova criada sobre a memória de uma textura viva começa com esse conteúdo em vez de vazia.",
|
||||
[UI_MENU_TEX_VRAM_PRESSURE] = "Limite de memória de texturas", [UI_MENU_TEX_VRAM_PRESSURE_DESC] = "Porcentagem da memória de vídeo acima da qual as texturas liberadas pelo jogo são descartadas (voltam do armazenamento quando usadas de novo). 0 mantém todas.",
|
||||
[UI_MENU_ASTC_BC] = "Comprimir ASTC em BC3", [UI_MENU_ASTC_BC_DESC] = "Com o decodificador da GPU: recodifica as texturas ASTC decodificadas em BC3, que ocupa um quarto da memória de vídeo. Para GPUs com 4 GB ou menos; as texturas ficam um pouco mais quadriculadas.",
|
||||
[UI_MENU_ASTC_BC] = "Comprimir ASTC em BC3", [UI_MENU_ASTC_BC_DESC] = "Com o decodificador da GPU: recodifica as texturas ASTC decodificadas em BC3, que ocupa um quarto da memória de vídeo. As texturas ficam um pouco mais quadriculadas. Automático: ligado em GPUs com 4 GB de memória de vídeo ou menos.",
|
||||
[UI_MENU_LOD_GUARD] = "Proteção de LOD", [UI_MENU_LOD_GUARD_DESC] = "Evita que objetos pisquem no lugar errado ou que o plano distante fique achatado por um quadro, esperando o jogo terminar de reescrever os dados de nível de detalhe antes que a GPU os leia.",
|
||||
[UI_MENU_FIX_C0] = "Correção do buffer de constantes",
|
||||
[UI_MENU_FIX_C0_DESC] = "Preenche o banco de constantes c0 reservado ao driver (tamanhos de armazenamento, tamanho do dispatch, tamanho do ponto, instância base) em vez de ler zeros.",
|
||||
@@ -1186,6 +1250,8 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
|
||||
|
||||
[UI_MENU_OV_FPS] = "%.0f FPS", [UI_MENU_OV_FRAME_MS] = "%.1f ms", [UI_MENU_OV_GAME_PCT] = "jogo %.0f%%",
|
||||
[UI_MENU_OV_MAX_MS] = "máx. %.1f ms",
|
||||
[UI_MENU_OV_FPS_LABEL] = "FPS", [UI_MENU_OV_FRAME_LABEL] = "Quadro", [UI_MENU_OV_FMT_FPS] = "%.0f",
|
||||
[UI_MENU_OV_LOW1] = "1%% piores %.0f", [UI_MENU_OV_LOW01] = "0.1%% piores %.0f",
|
||||
[UI_MENU_OV_GPU] = "GPU", [UI_MENU_OV_CPU] = "CPU", [UI_MENU_OV_RAM] = "RAM", [UI_MENU_OV_VRAM] = "VRAM",
|
||||
[UI_MENU_OV_TEMP] = "Temperatura", [UI_MENU_OV_POWER] = "Consumo", [UI_MENU_OV_CLOCK] = "Frequência",
|
||||
[UI_MENU_OV_NONE] = "-",
|
||||
@@ -1280,6 +1346,9 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_WALK_SCALE_DESC] = "A inclinação dos analógicos do teclado enquanto a tecla de andar está segura. Metade "
|
||||
"é andar, a inclinação total é correr.",
|
||||
[UI_MENU_OV_SCALE_DOWN] = "bilinear (o FSR só amplia)",
|
||||
[UI_MENU_HDR_HUE] = "Correção de matiz dos realces",
|
||||
[UI_MENU_HDR_HUE_DESC] = "Cores fortes como fogo e faíscas vão para o branco como na imagem SDR do próprio jogo e "
|
||||
"combinam com a luz que emitem; 0 as mantém totalmente saturadas.",
|
||||
[UI_MENU_SHADOW_SCALE] = "Resolução das sombras",
|
||||
[UI_MENU_SHADOW_SCALE_DESC] = "Desenha os mapas de sombra do sol essa quantidade de vezes maiores em cada lado "
|
||||
"que os 960x960 do jogo: bordas de sombra e sombras de folhas mais nítidas. Vale no "
|
||||
@@ -1300,13 +1369,26 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
|
||||
"mais suaves e menos cintilação. O HUD não é afetado. O modo abaixo define a resolução de "
|
||||
"onde ele parte. Vale após reiniciar.",
|
||||
[UI_MENU_DLSS] = "Modo do DLSS",
|
||||
[UI_MENU_DLSS_DESC] = "DLAA desenha a cena 3D na resolução cheia. Qualidade, Equilibrado e Desempenho a desenham "
|
||||
"em 67, 58 e 50 % e deixam o DLSS ampliá-la, para mais quadros por segundo; eles substituem "
|
||||
"a escala 3D. Vale após reiniciar.",
|
||||
[UI_MENU_DLSS_DESC] = "DLAA desenha a cena 3D na resolução cheia. Qualidade, Equilibrado, Desempenho e Ultra "
|
||||
"Desempenho a desenham em 67, 58, 50 e 33 % e deixam o DLSS ampliá-la, o que poupa tempo da "
|
||||
"GPU; eles substituem a escala 3D. Vale na hora.",
|
||||
[UI_MENU_DLSS_DLAA] = "DLAA (nativo)",
|
||||
[UI_MENU_DLSS_QUALITY] = "Qualidade",
|
||||
[UI_MENU_DLSS_BALANCED] = "Equilibrado",
|
||||
[UI_MENU_DLSS_PERFORMANCE] = "Desempenho",
|
||||
[UI_MENU_DLSS_PERFORMANCE] = "Desempenho", [UI_MENU_DLSS_ULTRA] = "Ultra Desempenho",
|
||||
[UI_MENU_DLSS_PRESET] = "Modelo do DLSS",
|
||||
[UI_MENU_DLSS_PRESET_DESC] = "Qual modelo do DLSS roda. Automático segue a recomendação da NVIDIA para cada modo: K "
|
||||
"para DLAA, Qualidade e Equilibrado, M para Desempenho, L para Ultra Desempenho. J: um "
|
||||
"pouco menos de rastro atrás de personagens em movimento, um pouco mais de cintilação. K: "
|
||||
"o modelo mais nítido em todos os modos. Vale na hora.",
|
||||
[UI_MENU_DLSS_PRESET_DEFAULT] = "Automático", [UI_MENU_DLSS_PRESET_K] = "K (mais nítido)",
|
||||
[UI_MENU_DLSS_PRESET_J] = "J (menos rastro)",
|
||||
[UI_MENU_DLSS_CHARS] = "Rastros de personagens no DLSS",
|
||||
[UI_MENU_DLSS_CHARS_DESC] = "Reduz os rastros fracos atrás de personagens, animais e do que eles seguram em movimento: "
|
||||
"ali o DLSS usa menos dos quadros anteriores. Mais forte remove mais rastros e deixa essas "
|
||||
"bordas um pouco menos suaves. Vale na hora.",
|
||||
[UI_MENU_DLSS_CHARS_OFF] = "Desligado", [UI_MENU_DLSS_CHARS_LIGHT] = "Leve", [UI_MENU_DLSS_CHARS_MEDIUM] = "Médio",
|
||||
[UI_MENU_DLSS_CHARS_FULL] = "Total",
|
||||
[UI_MENU_FXAA] = "Antisserrilhado do jogo (FXAA)",
|
||||
[UI_MENU_FXAA_DESC] = "A suavização de bordas FXAA do próprio jogo. Borra um pouco os detalhes finos; com o DLAA "
|
||||
"ligado não é necessária.",
|
||||
@@ -1419,7 +1501,7 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_OUTPUT_RES_DESC] = "游戏绘制画面和界面的尺寸。"
|
||||
"高于 1920 x 1080 会占用更多显存和 GPU 时间。"
|
||||
"更宽的尺寸(21:9)会扩大视野,并把界面移到屏幕四角;"
|
||||
"更高的尺寸(16:10、Steam Deck)则移到上下边缘。",
|
||||
"更高的尺寸(16:10、3:2)则移到上下边缘。",
|
||||
[UI_MENU_RENDER_SCALE] = "3D 渲染比例",
|
||||
[UI_MENU_RENDER_SCALE_DESC] = "3D 场景相对于游戏分辨率的尺寸;"
|
||||
"游戏的 FSR 会将其缩放到该尺寸。"
|
||||
@@ -1657,6 +1739,9 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_MENU_LANG] = "菜单语言",
|
||||
[UI_MENU_MENU_LANG_DESC] = "本菜单和性能浮层的语言。",
|
||||
[UI_MENU_MENU_LANG_AUTO] = "自动(跟随 Windows)",
|
||||
[UI_MENU_DISCORD_RPC] = "Discord Rich Presence",
|
||||
[UI_MENU_DISCORD_RPC_DESC] = "游戏运行时在 Discord 状态中显示“正在玩 TotK Recomp”及游戏时长。totk.ini 中的 "
|
||||
"discord_app_id 和 discord_image 选择应用。",
|
||||
[UI_MENU_ADV_WARNING] = "这些设置用于测试和排查问题,请在了解原因后再修改。",
|
||||
[UI_MENU_GROUP_SCHED] = "调度",
|
||||
[UI_MENU_GROUP_SHADERS] = "着色器和管线",
|
||||
@@ -1715,8 +1800,8 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
|
||||
"再次用到时从存储重新加载。0 表示不丢弃。",
|
||||
[UI_MENU_ASTC_BC] = "ASTC 压缩为 BC3",
|
||||
[UI_MENU_ASTC_BC_DESC] = "配合 GPU 解码器使用:把解码后的 ASTC 纹理重新编码为 BC3,"
|
||||
"显存占用只有四分之一。适用于 4 GB 及以下的显卡;"
|
||||
"纹理会出现轻微色块。",
|
||||
"显存占用只有四分之一。纹理会出现轻微色块。"
|
||||
"自动:显存 4 GB 及以下的显卡上开启。",
|
||||
[UI_MENU_LOD_GUARD] = "LOD 保护",
|
||||
[UI_MENU_LOD_GUARD_DESC] = "等待游戏改写完细节层次数据后再让 GPU 读取,"
|
||||
"避免物体错位闪现,或远景有一帧变平。",
|
||||
@@ -1775,6 +1860,8 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_OV_FRAME_MS] = "%.1f ms",
|
||||
[UI_MENU_OV_GAME_PCT] = "游戏 %.0f%%",
|
||||
[UI_MENU_OV_MAX_MS] = "最长 %.1f ms",
|
||||
[UI_MENU_OV_FPS_LABEL] = "FPS", [UI_MENU_OV_FRAME_LABEL] = "帧时间", [UI_MENU_OV_FMT_FPS] = "%.0f",
|
||||
[UI_MENU_OV_LOW1] = "1%% 低 %.0f", [UI_MENU_OV_LOW01] = "0.1%% 低 %.0f",
|
||||
[UI_MENU_OV_GPU] = "GPU",
|
||||
[UI_MENU_OV_CPU] = "CPU",
|
||||
[UI_MENU_OV_RAM] = "内存",
|
||||
@@ -1850,6 +1937,8 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_WALK_SCALE] = "步行幅度",
|
||||
[UI_MENU_WALK_SCALE_DESC] = "按住步行键时键盘摇杆的推动幅度。一半为走路,推满为奔跑。",
|
||||
[UI_MENU_OV_SCALE_DOWN] = "双线性(FSR 只能放大)",
|
||||
[UI_MENU_HDR_HUE] = "高光色调校正",
|
||||
[UI_MENU_HDR_HUE_DESC] = "火焰、火花等明亮色彩像游戏自身的 SDR 画面一样趋向白色,与它们发出的光一致;0 保持完全饱和。",
|
||||
[UI_MENU_SHADOW_SCALE] = "阴影分辨率",
|
||||
[UI_MENU_SHADOW_SCALE_DESC] = "将太阳阴影贴图的每条边绘制为游戏 960x960 的若干倍:阴影边缘和树叶阴影更清晰。下次启动时生效。",
|
||||
[UI_MENU_SHADOW_RANGE] = "阴影级联范围",
|
||||
@@ -1862,11 +1951,22 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_DLAA_DESC] = "用于 3D 场景的 NVIDIA DLSS(NVIDIA RTX 显卡;nvngx_dlss.dll 放在 totk.exe 旁):边缘更平滑,闪烁更少。HUD "
|
||||
"不受影响。下方的模式决定它的工作分辨率。重启后生效。",
|
||||
[UI_MENU_DLSS] = "DLSS 模式",
|
||||
[UI_MENU_DLSS_DESC] = "DLAA 以全分辨率绘制 3D 场景。质量、平衡和性能分别以 67%、58% 和 50% 绘制,再由 DLSS 放大,以获得更高帧率;它们会取代 3D 渲染比例。重启后生效。",
|
||||
[UI_MENU_DLSS_DESC] = "DLAA 以全分辨率绘制 3D 场景。质量、平衡、性能和超级性能分别以 67%、58%、50% 和 33% 绘制,再由 DLSS 放大,以节省 GPU 时间;它们会取代 3D 渲染比例。立即生效。",
|
||||
[UI_MENU_DLSS_DLAA] = "DLAA(原生)",
|
||||
[UI_MENU_DLSS_QUALITY] = "质量",
|
||||
[UI_MENU_DLSS_BALANCED] = "平衡",
|
||||
[UI_MENU_DLSS_PERFORMANCE] = "性能",
|
||||
[UI_MENU_DLSS_ULTRA] = "超级性能",
|
||||
[UI_MENU_DLSS_PRESET] = "DLSS 模型",
|
||||
[UI_MENU_DLSS_PRESET_DESC] = "运行哪个 DLSS 模型。自动按 NVIDIA 对各模式的建议:DLAA、质量和平衡用 K,性能用 M,超级性能用 L。"
|
||||
"J:移动角色身后的拖影略少,闪烁略多。K:所有模式都用最清晰的模型。立即生效。",
|
||||
[UI_MENU_DLSS_PRESET_DEFAULT] = "自动", [UI_MENU_DLSS_PRESET_K] = "K(最清晰)",
|
||||
[UI_MENU_DLSS_PRESET_J] = "J(拖影更少)",
|
||||
[UI_MENU_DLSS_CHARS] = "DLSS 角色拖影",
|
||||
[UI_MENU_DLSS_CHARS_DESC] = "减少移动中的角色、动物及其手持物品身后的淡淡拖影:DLSS 在这些位置少用之前的帧。越强拖影越少,"
|
||||
"这些边缘也略欠平滑。立即生效。",
|
||||
[UI_MENU_DLSS_CHARS_OFF] = "关", [UI_MENU_DLSS_CHARS_LIGHT] = "弱", [UI_MENU_DLSS_CHARS_MEDIUM] = "中",
|
||||
[UI_MENU_DLSS_CHARS_FULL] = "强",
|
||||
[UI_MENU_FXAA] = "游戏抗锯齿(FXAA)",
|
||||
[UI_MENU_FXAA_DESC] = "游戏自身的 FXAA 边缘平滑。会让细节略微模糊;开启 DLAA 时不需要。",
|
||||
[UI_MENU_TAB_CHEATS] = "金手指",
|
||||
@@ -1921,6 +2021,22 @@ const char *ui_text_lang(const char *code) {
|
||||
return code;
|
||||
}
|
||||
|
||||
// Every game language: the Mals files EUen/USen, USfr/EUfr, USes/EUes ship in both builds; USpt only from 1.4.3.
|
||||
int ui_text_game_langs(int game_version, UiGameLang *out, int max) {
|
||||
static const struct { const char *code; UiTextId name; int since; } all[] = {
|
||||
{"en-US", UI_MENU_LANGNAME_EN, 0}, {"en-GB", UI_MENU_LANGNAME_EN_GB, 0}, {"ja", UI_MENU_LANGNAME_JA, 0},
|
||||
{"fr", UI_MENU_LANGNAME_FR, 0}, {"fr-CA", UI_MENU_LANGNAME_FR_CA, 0}, {"de", UI_MENU_LANGNAME_DE, 0},
|
||||
{"it", UI_MENU_LANGNAME_IT, 0}, {"es", UI_MENU_LANGNAME_ES, 0}, {"es-419", UI_MENU_LANGNAME_ES_419, 0},
|
||||
{"pt-BR", UI_MENU_LANGNAME_PT_BR, 143}, {"nl", UI_MENU_LANGNAME_NL, 0}, {"ru", UI_MENU_LANGNAME_RU, 0},
|
||||
{"zh-Hans", UI_MENU_LANGNAME_ZH_HANS, 0}, {"zh-Hant", UI_MENU_LANGNAME_ZH_HANT, 0},
|
||||
{"ko", UI_MENU_LANGNAME_KO, 0},
|
||||
};
|
||||
int n = 0;
|
||||
for (size_t i = 0; i < sizeof all / sizeof *all && n < max; i++)
|
||||
if (game_version >= all[i].since) out[n++] = (UiGameLang){all[i].code, all[i].name};
|
||||
return n;
|
||||
}
|
||||
|
||||
static const Row *find_row(const char *lang) {
|
||||
for (size_t i = 0; i < sizeof rows / sizeof *rows; i++)
|
||||
if (!strcmp(rows[i].lang, lang)) return &rows[i];
|
||||
|
||||
+16
-3
@@ -48,13 +48,15 @@ typedef enum {
|
||||
UI_MENU_COLOR_SATURATION, UI_MENU_COLOR_SATURATION_DESC, UI_MENU_COLOR_GAMMA, UI_MENU_COLOR_GAMMA_DESC,
|
||||
UI_MENU_HDR_GROUP, // group heading over the HDR rows
|
||||
UI_MENU_HDR, UI_MENU_HDR_DESC, UI_MENU_HDR_PEAK, UI_MENU_HDR_PEAK_DESC, UI_MENU_HDR_GAME, UI_MENU_HDR_GAME_DESC,
|
||||
UI_MENU_HDR_UI, UI_MENU_HDR_UI_DESC,
|
||||
UI_MENU_HDR_UI, UI_MENU_HDR_UI_DESC, UI_MENU_HDR_HUE, UI_MENU_HDR_HUE_DESC,
|
||||
UI_MENU_SHADOW_SCALE, UI_MENU_SHADOW_SCALE_DESC,
|
||||
UI_MENU_SHADOW_RANGE, UI_MENU_SHADOW_RANGE_DESC,
|
||||
UI_MENU_CONTACT_SHADOWS, UI_MENU_CONTACT_SHADOWS_DESC,
|
||||
UI_MENU_PCSS, UI_MENU_PCSS_DESC,
|
||||
UI_MENU_DLAA, UI_MENU_DLAA_DESC, UI_MENU_DLSS, UI_MENU_DLSS_DESC, UI_MENU_DLSS_DLAA, UI_MENU_DLSS_QUALITY,
|
||||
UI_MENU_DLSS_BALANCED, UI_MENU_DLSS_PERFORMANCE, UI_MENU_FXAA, UI_MENU_FXAA_DESC,
|
||||
UI_MENU_DLSS_BALANCED, UI_MENU_DLSS_PERFORMANCE, UI_MENU_DLSS_ULTRA, UI_MENU_DLSS_PRESET, UI_MENU_DLSS_PRESET_DESC,
|
||||
UI_MENU_DLSS_PRESET_DEFAULT, UI_MENU_DLSS_PRESET_K, UI_MENU_DLSS_PRESET_J, UI_MENU_DLSS_CHARS, UI_MENU_DLSS_CHARS_DESC,
|
||||
UI_MENU_DLSS_CHARS_OFF, UI_MENU_DLSS_CHARS_LIGHT, UI_MENU_DLSS_CHARS_MEDIUM, UI_MENU_DLSS_CHARS_FULL, UI_MENU_FXAA, UI_MENU_FXAA_DESC,
|
||||
|
||||
// Game
|
||||
UI_MENU_FPS_LIMIT, UI_MENU_FPS_LIMIT_DESC, UI_MENU_FPS_30, UI_MENU_FPS_40, UI_MENU_FPS_45, UI_MENU_FPS_60, UI_MENU_FPS_90,
|
||||
@@ -88,6 +90,7 @@ typedef enum {
|
||||
|
||||
// Sound
|
||||
UI_MENU_VOLUME, UI_MENU_VOLUME_DESC,
|
||||
UI_MENU_AUDIO_CHANNELS, UI_MENU_AUDIO_CHANNELS_DESC, UI_MENU_CHANNELS_AUTO, UI_MENU_CHANNELS_STEREO, UI_MENU_CHANNELS_51,
|
||||
|
||||
// Controls
|
||||
UI_MENU_MOTION, UI_MENU_MOTION_DESC,
|
||||
@@ -150,11 +153,13 @@ typedef enum {
|
||||
|
||||
// Menu tab
|
||||
UI_MENU_MENU_LANG, UI_MENU_MENU_LANG_DESC, UI_MENU_MENU_LANG_AUTO,
|
||||
UI_MENU_DISCORD_RPC, UI_MENU_DISCORD_RPC_DESC,
|
||||
|
||||
// Language names, each in its own language and the same in every row (game language and menu language lists)
|
||||
UI_MENU_LANGNAME_EN, UI_MENU_LANGNAME_JA, UI_MENU_LANGNAME_FR, UI_MENU_LANGNAME_DE, UI_MENU_LANGNAME_IT,
|
||||
UI_MENU_LANGNAME_ES, UI_MENU_LANGNAME_NL, UI_MENU_LANGNAME_RU, UI_MENU_LANGNAME_ZH_HANS,
|
||||
UI_MENU_LANGNAME_ZH_HANT, UI_MENU_LANGNAME_KO, UI_MENU_LANGNAME_PT_BR,
|
||||
UI_MENU_LANGNAME_ZH_HANT, UI_MENU_LANGNAME_KO, UI_MENU_LANGNAME_PT_BR, UI_MENU_LANGNAME_EN_GB,
|
||||
UI_MENU_LANGNAME_FR_CA, UI_MENU_LANGNAME_ES_419,
|
||||
|
||||
// Advanced
|
||||
UI_MENU_ADV_WARNING,
|
||||
@@ -203,6 +208,8 @@ typedef enum {
|
||||
|
||||
// Performance overlay
|
||||
UI_MENU_OV_FPS, UI_MENU_OV_FRAME_MS, UI_MENU_OV_GAME_PCT, UI_MENU_OV_MAX_MS,
|
||||
UI_MENU_OV_FPS_LABEL, UI_MENU_OV_FRAME_LABEL, UI_MENU_OV_FMT_FPS, // the frame rows: their labels, the rate
|
||||
UI_MENU_OV_LOW1, UI_MENU_OV_LOW01, // printf: the fps of the slowest 1% / 0.1% of the frames, no unit (the overlay column stays narrow)
|
||||
UI_MENU_OV_GPU, UI_MENU_OV_CPU, UI_MENU_OV_RAM, UI_MENU_OV_VRAM, UI_MENU_OV_TEMP, UI_MENU_OV_POWER,
|
||||
UI_MENU_OV_CLOCK, UI_MENU_OV_NONE,
|
||||
UI_MENU_OV_REPLAY, UI_MENU_OV_GPU_TIME, UI_MENU_OV_PIPE_WAIT, UI_MENU_OV_SHADER_WAIT, UI_MENU_OV_TEX_UPLOADS,
|
||||
@@ -246,6 +253,12 @@ int ui_text_defined(const char *lang, UiTextId id);
|
||||
// The catalog language for a Switch language code ("en-GB" -> "en", "zh-CN" -> "zh-Hans", ...).
|
||||
const char *ui_text_lang(const char *switch_code);
|
||||
|
||||
// The game languages the F1 menu offers for a game build (rt_game_version): one entry per Mals language file the build
|
||||
// ships, each with the Switch code TOTK_LANG takes and the id of its name. 1.4.3 adds Brazilian Portuguese (USpt).
|
||||
// Fills out (room for max entries) and returns the count.
|
||||
typedef struct { const char *code; UiTextId name; } UiGameLang;
|
||||
int ui_text_game_langs(int game_version, UiGameLang *out, int max);
|
||||
|
||||
// Menu text: ui_text_in(menu language, id). The menu language is TOTK_MENU_LANG ("en", "ru", "pt-BR", "zh-Hans");
|
||||
// unset, the system UI language when the catalog has menu text for it (English, Russian, Brazilian Portuguese,
|
||||
// Simplified Chinese), else English. Follows a live change of the setting.
|
||||
|
||||
@@ -255,9 +255,20 @@ void vk_mem_gpu_wrote(const VkMemSpan *sp, uint64_t size);
|
||||
void vk_usnap_begin(const uint64_t *handles, int n, uint64_t fence);
|
||||
void vk_usnap_end(uint64_t fence);
|
||||
int vk_usnap_find(uint64_t addr, uint64_t n, uint64_t align, VkDescriptorBufferInfo *out);
|
||||
const uint8_t *vk_usnap_map(void); // a host copy of the current arena, at the offsets vk_usnap_find gives
|
||||
const uint8_t *vk_usnap_bytes(uint64_t addr, uint64_t n); // the copy of [addr, addr+n) as of the submit, NULL: none
|
||||
void vk_usnap_drop(uint64_t addr, uint64_t n); // the replay makes the GPU write [addr, addr+n) of guest memory
|
||||
uint64_t vk_usnap_gen(void); // changes whenever a binding may resolve differently
|
||||
void vk_usnap_shutdown(void); // vk_exec_draw_shutdown: frees the arenas
|
||||
// vk_mvprev.c: last frame's copy of the scene's object (c7) and bone (c5) blocks, for per-object motion vectors.
|
||||
// vk_mvprev_step: the distance of the game's other copy of a vertex-stage block the snapshot also copies, 0: none.
|
||||
// vk_mvprev_find: last frame's block of that object (prev, its address; how, MVPREV_*), 0: none.
|
||||
enum { MVPREV_NONE, MVPREV_SELF, MVPREV_SIDE, MVPREV_NEARER };
|
||||
uint64_t vk_mvprev_step(int stage, int index, uint64_t size);
|
||||
int vk_mvprev_on(void);
|
||||
void vk_mvprev_want(uint32_t index_mask); // also keep these binding indices' last-frame copies (bit n: index n) // DLAA on and TOTK_DLAA_OBJMV not 0, as of the last present
|
||||
int vk_mvprev_find(int index, uint64_t addr, uint64_t size, uint64_t *prev, int *how);
|
||||
void vk_mvprev_frame(void); // each present, beside vk_dlaa_frame
|
||||
void vk_mem_mirror_begin(void);
|
||||
int vk_mem_mirror_end(VkCommandBuffer cb, uint64_t fence);
|
||||
int vk_mem_mirror_pending(void);
|
||||
@@ -390,6 +401,7 @@ uint32_t vk_texture_slots_gen(void);
|
||||
// its storage address is created, dropped, released or taken back into use. Unlocked; read it before the lookup it
|
||||
// guards (a lookup may create an entry). 0 when the slot was never registered: nothing is kept under 0.
|
||||
uint64_t vk_texture_slot_token(int id);
|
||||
void vk_sampler_dlss_bias(float bias); // texture LOD bias added for DLSS upscaling (material samplers)
|
||||
uint32_t vk_sampler_gen(void); // changes whenever a sampler slot is registered
|
||||
void vk_texture_gpu_written(const tex_t *t); // rendered or copied into by the GPU: no re-upload from storage
|
||||
void vk_texture_storage_bound_locked(const VkTexImage *img); // bound as a storage image in the recording submission
|
||||
|
||||
+12
-1
@@ -298,9 +298,20 @@ static int create_pipeline(VkCtx *c) {
|
||||
}
|
||||
|
||||
// TOTK_ASTC_BC=1: the BC3 encoder, when the device samples BC3 (every desktop GPU does); 0 leaves ASTC as RGBA8.
|
||||
// Unset: on when the largest device-local heap is under 5 GiB. There RGBA8 ASTC, four times the size of BC3, fills
|
||||
// video memory (a GTX 1650 4 GiB went from 10-23 to 3-12 ms of GPU time a frame, issue #87); BC3 loses some precision
|
||||
// (normal maps), so GPUs with more memory keep RGBA8.
|
||||
static int create_bc_pipeline(VkCtx *c) {
|
||||
const char *e = cfg_get("TOTK_ASTC_BC");
|
||||
if (!e || !*e || *e == '0') return 0;
|
||||
VkDeviceSize vram = 0;
|
||||
for (uint32_t h = 0; h < c->mem_props.memoryHeapCount && h < VK_MAX_MEMORY_HEAPS; h++)
|
||||
if (c->mem_props.memoryHeaps[h].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT && c->mem_props.memoryHeaps[h].size > vram)
|
||||
vram = c->mem_props.memoryHeaps[h].size;
|
||||
int on = e && *e ? *e != '0' : vram < (5ull << 30);
|
||||
if (!(e && *e))
|
||||
rt_log("vk: TOTK_ASTC_BC unset: BC3 %s (largest device-local heap %llu MiB)", on ? "on" : "off",
|
||||
(unsigned long long)(vram >> 20));
|
||||
if (!on) return 0;
|
||||
VkFormatProperties p;
|
||||
vkGetPhysicalDeviceFormatProperties(c->phys, VK_FORMAT_BC3_SRGB_BLOCK, &p);
|
||||
VkFormatFeatureFlags need = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT;
|
||||
|
||||
@@ -102,6 +102,8 @@ static void present_image(const tex_t *t, const int crop[4], uint64_t at_ns, uin
|
||||
static volatile int32_t n_skipped;
|
||||
vk_exec_frame_log(t, frame);
|
||||
vk_dlaa_frame();
|
||||
vk_mvsurvey_frame();
|
||||
vk_mvprev_frame();
|
||||
vk_mem_census(frame);
|
||||
if (vk_dump_armed) vk_dump_present(frame);
|
||||
__atomic_store_n(&gfx_counters.validation_errors, (uint64_t)vk_validation_errors_total(), __ATOMIC_RELAXED);
|
||||
@@ -166,7 +168,10 @@ static uint64_t ck_reads, ck_diff, ck_diff_unexplained, ck_diff_idle;
|
||||
int vk_replay_async(void) {
|
||||
static int on = -1;
|
||||
if (on < 0) {
|
||||
on = cfg_bool("TOTK_REPLAY_ASYNC"); // off by default: no measured gain yet, one more frame of latency
|
||||
// On by default: where the game waits for the GPU it overlaps the replay with the game threads (a GTX 1650 in
|
||||
// the Depths went from 450-686 frames over 50 ms a minute to 10-13, issue #88); where the replay or the game
|
||||
// threads bound the frame it neither gains nor loses. It adds up to a frame of latency.
|
||||
on = cfg_bool("TOTK_REPLAY_ASYNC");
|
||||
rt_log("vk: replay %s (TOTK_REPLAY_ASYNC=%d)",
|
||||
on ? "on its own thread, at most one frame behind the game" : "on the submitting thread", on);
|
||||
}
|
||||
|
||||
+10
-10
@@ -6,8 +6,7 @@
|
||||
// - minStorageBufferOffsetAlignment above 4 (TOTK_VK_SSBO_ALIGN=<power of two> raises it): the translator computes
|
||||
// storage offsets from the guest address rounded down to that alignment (Ryujinx's unaligned storage path), and
|
||||
// vk_exec_draw.c ssbo_info binds the rounded-down address, so shader writes reach guest memory instead of a copy.
|
||||
// - no graphics pipeline library with fast linking (TOTK_GPL=0 forces it; the default on AMD's RDNA 1/2 driver):
|
||||
// vk_caps.gpl = 0, the extension stays disabled, and pipelines are built whole.
|
||||
// - no graphics pipeline library with fast linking (TOTK_GPL=0 forces it): vk_caps.gpl = 0, the extension stays disabled, and pipelines are built whole.
|
||||
// - no 32-lane pin (no subgroupSizeControl, 32 outside min..max, or TOTK_VK_SUBGROUP_PIN=0): stages run at the
|
||||
// device's subgroupSize (SPIR-V 1.3 without a required size) and the translator is told that size.
|
||||
// TOTK_VK_SUBGROUP=<n> pins n instead of 32 where the driver allows it.
|
||||
@@ -46,15 +45,18 @@ void vk_caps_driver(VkPhysicalDevice p) {
|
||||
vk_caps.amd_sc = drv.driverID == VK_DRIVER_ID_AMD_PROPRIETARY && strstr(drv.driverInfo, "shader compiler");
|
||||
}
|
||||
|
||||
// TOTK_GPL=0/1 forces graphics pipeline libraries off or on. Default off on AMD's RDNA 1/2 driver branch: every boot
|
||||
// that lost the device there had a cold pipeline cache and draws waiting on fast-linked pipelines, and a run with GPL
|
||||
// off booted (issue #5); Savage Wind Recomp turns GPL off on RDNA 4 under RADV for a GPU hang at the title screen.
|
||||
// TOTK_GPL=0 turns graphics pipeline libraries off; they are on wherever the driver has fast linking. AMD's RDNA 1/2
|
||||
// driver branch had them off for a while: its device losses at boot (issue #5) came with a cold pipeline cache, but
|
||||
// their cause was GPU writes past 3D BC images (vk_exec.c, "3D BC render targets"). With that fixed, the RDNA 1/2
|
||||
// driver boots with GPL from a cold cache and fast-links without a failure, while with GPL off every new pipeline is
|
||||
// built whole on the draw's path (frames of 0.5-1.2 s on an RX 6600, issue #381). Savage Wind Recomp turns GPL off on
|
||||
// RDNA 4 under RADV for a GPU hang at the title screen.
|
||||
int vk_caps_gpl(const VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT *f,
|
||||
const VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT *p) {
|
||||
gpl_lib = f->graphicsPipelineLibrary;
|
||||
gpl_fast = p->graphicsPipelineLibraryFastLinking;
|
||||
const char *e = cfg_get("TOTK_GPL"); // no fixed default: it depends on the driver, see above
|
||||
vk_caps.gpl = gpl_lib && gpl_fast && (e && *e ? strcmp(e, "0") != 0 : !vk_caps.amd_sc);
|
||||
const char *e = cfg_get("TOTK_GPL");
|
||||
vk_caps.gpl = gpl_lib && gpl_fast && !(e && !strcmp(e, "0"));
|
||||
return vk_caps.gpl;
|
||||
}
|
||||
|
||||
@@ -137,10 +139,8 @@ void vk_caps_init(VkCtx *c) {
|
||||
has_ext(ep, ne, "VK_EXT_graphics_pipeline_library"), depth_ok(p, VK_FORMAT_D16_UNORM),
|
||||
depth_ok(p, VK_FORMAT_D24_UNORM_S8_UINT), depth_ok(p, VK_FORMAT_X8_D24_UNORM_PACK32), depth_ok(p, VK_FORMAT_D32_SFLOAT),
|
||||
depth_ok(p, VK_FORMAT_D32_SFLOAT_S8_UINT));
|
||||
const char *ge = cfg_get("TOTK_GPL");
|
||||
rt_log("vk caps: gpl %s fast-link %s (graphicsPipelineLibrary %u%s)", vk_caps.gpl ? "yes" : "no",
|
||||
gpl_fast ? "yes" : "no", (unsigned)gpl_lib,
|
||||
!gpl_lib || !gpl_fast || vk_caps.gpl ? "" : ge && *ge ? ", off by TOTK_GPL=0" : ", off on AMD's RDNA 1/2 driver");
|
||||
gpl_fast ? "yes" : "no", (unsigned)gpl_lib, !gpl_lib || !gpl_fast || vk_caps.gpl ? "" : ", off by TOTK_GPL=0");
|
||||
rt_log("vk caps: fallbacks: float64 %s, storage buffers %s %u, subgroup pin %u on stages %#x, translator subgroup vs/fs/cs %u/%u/%u",
|
||||
vk_caps.float64 ? "native" : "softfloat64", vk_caps.sbuf_align ? "rounded down to" : "exact, alignment",
|
||||
(unsigned)l->minStorageBufferOffsetAlignment, want, (unsigned)vk_caps.sg_pin_stages, told[0], told[1], told[2]);
|
||||
|
||||
+644
-88
File diff suppressed because it is too large.
Load diff
@@ -2,6 +2,10 @@
|
||||
#pragma once
|
||||
#include "vk.h"
|
||||
|
||||
// The narrowest scene the DLSS pass and the motion passes recognise by its D32F depth: Ultra Performance at a 1080p
|
||||
// output renders 640 wide, smaller windows less.
|
||||
#define DLAA_SCENE_MIN_W 320u
|
||||
|
||||
int vk_dlaa_wanted(void); // the setting is on (the device may still lack DLSS)
|
||||
// vk_device.c: NGX's instance and device extensions join the lists (room for max names); vk_dlaa_init after the
|
||||
// device exists brings NGX up and checks that DLSS is available.
|
||||
@@ -20,6 +24,25 @@ void vk_dlaa_frame(void);
|
||||
// the viewport of the scene's draws by the frame's jitter (vk_dlaa_viewport, when the viewport is recorded).
|
||||
void vk_dlaa_draw(VkExec *ex);
|
||||
void vk_dlaa_viewport(VkExec *ex, VkViewport *v);
|
||||
// vk_mvsurvey.c (TOTK_MV_SURVEY): notes the uniform blocks of each scene draw, beside vk_dlaa_draw; each present
|
||||
// compares them with the previous frames' and logs a summary now and then.
|
||||
void vk_mvsurvey_draw(VkExec *ex);
|
||||
void vk_mvsurvey_frame(void);
|
||||
void vk_mvprev_draw(VkExec *ex); // vk_mvprev.c: notes the scene draw's object and bone blocks
|
||||
// Per-object motion (vk_exec_draw.c): the moving scene draws are listed again during the replay with
|
||||
// their vertex shader's last-frame variant; vk_dlaa.c mv_pass records them over the camera motion into ex->cb (and
|
||||
// leaves every binding of the replay to be set again; draws whose camera block is not cam are left out).
|
||||
// vk_dlaa_prev_camera: last frame's camera block and this frame's jitter, 0 when there is no history.
|
||||
int vk_mvobj_execute(VkExec *ex, uint64_t cam, VkImageView mv, VkImageView depth, VkFormat depth_fmt, uint32_t w,
|
||||
uint32_t h);
|
||||
int vk_dlaa_prev_camera(VkDescriptorBufferInfo *out, float jitter[2]);
|
||||
// vk_mvcheck.c (TOTK_MV_CHECK, test): mv_pass hands it the motion image after the camera pass and, after the object
|
||||
// pass, the motion image again and the scene light target it ran on; it measures how well the vectors fit the picture.
|
||||
void vk_mvcheck_camera(VkCommandBuffer cb, VkImage mv, uint32_t w, uint32_t h, float jx, float jy);
|
||||
void vk_mvcheck_final(VkCommandBuffer cb, uint64_t fence, VkImage mv, VkImage cimg, VkFormat cfmt, uint32_t level,
|
||||
uint32_t layer, uint32_t w, uint32_t h, float jx, float jy);
|
||||
const char *vk_mvobj_frame_masks(void); // vk_exec_draw.c: the last motion list, per position and changed banks
|
||||
void vk_mvcheck_output(VkCommandBuffer cb, VkImage out, uint32_t ow, uint32_t oh); // after DLSS on a check frame
|
||||
// vk_exec_draw.c, once a draw's fragment shader is known: 1 skips the draw (the game's FXAA while TOTK_FXAA is 0, its
|
||||
// scaling draw when DLSS wrote the scaled picture).
|
||||
int vk_dlaa_skip_draw(VkExec *ex, const GfxShader *fs);
|
||||
@@ -188,6 +188,10 @@ VkImageView vk_exec_target_view(const VkTexImage *img, VkFormat format, VkImageV
|
||||
// vk_exec_xfer.c: OP_COPY_B2B, OP_COPY_B2T, OP_COPY_T2B, OP_COPY_T2T, OP_CLEAR_TEX, OP_SIGNAL_EVENT, OP_REPORT_COUNTER,
|
||||
// OP_RESET_COUNTER, OP_RENDER_ENABLE, OP_RENDER_COND, OP_CLEAR_BUF.
|
||||
void vk_exec_op_xfer(VkExec *ex, uint32_t op, const void *payload);
|
||||
// vk_dlaa.c: once DLSS has scaled the scene this frame, passes larger than the scene's light target vk_dlaa_swap_img
|
||||
// sample DLSS's output vk_dlaa_swap_view in its place, so the game's scaling draw scales DLSS's picture.
|
||||
extern const VkTexImage *vk_dlaa_swap_img;
|
||||
extern VkImageView vk_dlaa_swap_view;
|
||||
// vk_contact.c: a pass about to begin (its targets resolved, no pass open), and a dispatch or copy about to be recorded
|
||||
void vk_contact_pass_begin(VkExec *ex);
|
||||
void vk_contact_flush(VkExec *ex);
|
||||
|
||||
+792
-11
@@ -731,6 +731,8 @@ static void answers_for(VkExec *ex, int st, const uint8_t *ctl, uint32_t mask, G
|
||||
// *present: the program has this stage. Returns NULL when it has not or the translation failed. pending (graphics
|
||||
// stages, vk_pipeline_async_on): a shader not translated yet is queued and NULL returned with *pending = 1. await: a
|
||||
// shader queued earlier is waited for (gfx_shader_await) instead.
|
||||
static int sf_mvprev; // shader_for asks for the vertex shader's last-frame variant (per-object motion)
|
||||
|
||||
static const GfxShader *shader_for(VkExec *ex, int st, int *present, int *pending, int await) {
|
||||
const uint8_t *ctl, *code;
|
||||
uint32_t len;
|
||||
@@ -741,6 +743,7 @@ static const GfxShader *shader_for(VkExec *ex, int st, int *present, int *pendin
|
||||
GfxShaderAnswers a;
|
||||
uint64_t serial = ex->prog->serial;
|
||||
answers_for(ex, st, ctl, slot_mask_of(serial, st), &a);
|
||||
a.mvprev = (uint8_t)(sf_mvprev && st == NVN_STAGE_VS);
|
||||
ShaderEnt *set = NULL;
|
||||
if (serial) {
|
||||
uint64_t h = (serial * 0x9E3779B97F4A7C15ull ^ (uint64_t)st) ^ nvn_hm_hash(&a, sizeof a); // a is zero-padded
|
||||
@@ -758,7 +761,7 @@ static const GfxShader *shader_for(VkExec *ex, int st, int *present, int *pendin
|
||||
}
|
||||
int gs = st == NVN_STAGE_VS ? GFX_STAGE_VS : st == NVN_STAGE_FS ? GFX_STAGE_FS : GFX_STAGE_CS;
|
||||
const GfxShader *s = await ? gfx_shader_await(gs, ctl, code, len, &a)
|
||||
: pending && vk_pipeline_async_on() ? gfx_shader_get_async(gs, ctl, code, len, &a, pending)
|
||||
: pending && (vk_pipeline_async_on() || a.mvprev) ? gfx_shader_get_async(gs, ctl, code, len, &a, pending)
|
||||
: gfx_shader_get(gs, ctl, code, len, NULL, &a);
|
||||
if (pending && *pending) return NULL;
|
||||
if (s) slot_mask_learn(serial, st, s);
|
||||
@@ -895,7 +898,14 @@ static void limits_init(void) {
|
||||
__atomic_store_n(&LIM.ubo_max, l->maxUniformBufferRange, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
// Per-object motion (section at the end): while it records a draw again, these replace the uniform bindings of the
|
||||
// vertex stage's slots 18-20 (last frame's camera, bones and place) and the fragment stage's slot 3 (its parameters).
|
||||
static int mo_ovr_on;
|
||||
static VkDescriptorBufferInfo mo_ovr[2][32]; // [vertex, fragment][slot]; buffer 0: not replaced
|
||||
|
||||
static VkDescriptorBufferInfo ubo_info(VkExec *ex, int st, int slot) {
|
||||
if (mo_ovr_on && (st == NVN_STAGE_VS || st == NVN_STAGE_FS) && slot >= 0 && slot < 32 && mo_ovr[st][slot].buffer)
|
||||
return mo_ovr[st][slot];
|
||||
if (!__atomic_load_n(&LIM.ubo_max, __ATOMIC_ACQUIRE)) limits_init();
|
||||
uint64_t max = LIM.ubo_max, align = LIM.ubo_align;
|
||||
VkDescriptorBufferInfo bi;
|
||||
@@ -1398,9 +1408,13 @@ static Twin *twin_update(VkExec *ex, const VkTexImage *img) {
|
||||
}
|
||||
|
||||
// The image descriptor of slot h; in a dispatch, a compare-sampled colour image the driver cannot compare is swapped
|
||||
// for its depth twin.
|
||||
// for its depth twin; in a pass larger than it, the scene's light target, once DLSS scaled it this frame, for DLSS's
|
||||
// output (vk_dlaa.c).
|
||||
static void tex_info(VkExec *ex, const GfxShaderRes *r, uint64_t h, int storage, int separate, VkDescriptorImageInfo *out) {
|
||||
tex_info_lookup(ex, r, h, storage, separate, out);
|
||||
if (h && !storage && vk_dlaa_swap_img && ex->in_pass && ex->pass_w > vk_dlaa_swap_img->width + 1 &&
|
||||
vk_texture_by_id_locked((int)(h & 0xfffff), NULL, NULL) == vk_dlaa_swap_img)
|
||||
out->imageView = vk_dlaa_swap_view, out->imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
if (!twin_dispatch || storage || separate || !h || !(r->type & 0x400)) return;
|
||||
VkImageViewType vt = res_view_type(r->type);
|
||||
if (vt != VK_IMAGE_VIEW_TYPE_2D && vt != VK_IMAGE_VIEW_TYPE_2D_ARRAY) return;
|
||||
@@ -1912,18 +1926,23 @@ static void bind_pipeline(VkExec *ex, VkPipelineBindPoint bp, VkPipeline p) {
|
||||
if (!k) B.st_valid = 0;
|
||||
}
|
||||
|
||||
static VkViewport draw_viewport(VkExec *ex) {
|
||||
float x = 0, y = 0, w = (float)ex->pass_w, h = (float)ex->pass_h;
|
||||
if (ex->has_vp) {
|
||||
float k = ex->pass_scale > 1 ? (float)ex->pass_scale : 1.0f;
|
||||
x = ex->vp[0][0] * k, y = ex->vp[0][1] * k, w = ex->vp[0][2] * k, h = ex->vp[0][3] * k;
|
||||
}
|
||||
if (w < 1) w = 1;
|
||||
if (h < 1) h = 1;
|
||||
VkViewport v = {x, y + h, w, -h, ex->depth_range[0], ex->depth_range[1]}; // the y flip: negative height
|
||||
vk_dlaa_viewport(ex, &v); // the scene's draws move by the DLAA jitter
|
||||
return v;
|
||||
}
|
||||
|
||||
static void record_fixed(VkExec *ex) {
|
||||
VkCommandBuffer cb = ex->cb;
|
||||
if (ex->dirty & VK_EXEC_DIRTY_VIEWPORT) {
|
||||
float x = 0, y = 0, w = (float)ex->pass_w, h = (float)ex->pass_h;
|
||||
if (ex->has_vp) {
|
||||
float k = ex->pass_scale > 1 ? (float)ex->pass_scale : 1.0f;
|
||||
x = ex->vp[0][0] * k, y = ex->vp[0][1] * k, w = ex->vp[0][2] * k, h = ex->vp[0][3] * k;
|
||||
}
|
||||
if (w < 1) w = 1;
|
||||
if (h < 1) h = 1;
|
||||
VkViewport v = {x, y + h, w, -h, ex->depth_range[0], ex->depth_range[1]}; // the y flip: negative height
|
||||
vk_dlaa_viewport(ex, &v); // the scene's draws move by the DLAA jitter
|
||||
VkViewport v = draw_viewport(ex);
|
||||
vkCmdSetViewportWithCount(cb, 1, &v);
|
||||
ex->recorded++;
|
||||
}
|
||||
@@ -2185,6 +2204,8 @@ static int prepare_draw(VkExec *ex, int prim, int32_t count, int restart) {
|
||||
if (ex->mark && vk_exec_frame.draws == ex->mark_draws0) ex->mark->first[0] = vs, ex->mark->first[1] = fs;
|
||||
}
|
||||
vk_dlaa_draw(ex);
|
||||
vk_mvsurvey_draw(ex);
|
||||
vk_mvprev_draw(ex);
|
||||
record_fixed(ex);
|
||||
repeat_stats(ex, info);
|
||||
bind_resources(ex, info);
|
||||
@@ -2197,6 +2218,762 @@ static int prepare_draw(VkExec *ex, int prim, int32_t count, int restart) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---- per-object motion
|
||||
// The camera pass (vk_dlaa.c mv_pass) gives everything the camera's motion, so a character that moves with the camera,
|
||||
// or on its own, gets the wrong one and DLSS throws its history away there. Each solid scene draw whose position reads
|
||||
// an object's own uniform blocks (bones c5, place c7, c6; mvobj_draw_ says which are left out and why) is therefore
|
||||
// drawn a second time (listed during the replay, MQ, and recorded by mv_pass) with its vertex shader's last-frame
|
||||
// variant (translator patch 0015: the position again from last frame's blocks, bound at slot bank + 15, and last
|
||||
// frame's camera, written to a spare output) and the fragment shader of vk_mvobj_fs.h, which writes last frame's window
|
||||
// position minus this frame's. The draw keeps everything else of the original: vertex and index buffers, the other
|
||||
// uniform blocks, the jittered viewport and the scene depth, tested with the game's own test and its equal case
|
||||
// (LEQUAL, GEQUAL) without writes, so only the visible surface writes its vector. Last frame's blocks:
|
||||
// vk_mvprev_find and mo_prev_block; the camera: vk_dlaa_prev_camera. A draw whose variant or pipeline is still
|
||||
// building, or whose last-frame blocks are not in the snapshot, keeps the camera's motion. TOTK_DLAA_OBJMV=0 turns it
|
||||
// off (vk_mvprev.c).
|
||||
#include "vk_mvobj_fs.h"
|
||||
static const char MO_FS_RES[] = "{\"key\":\"mvobj\",\"stage\":\"ps\",\"notes\":[],\"cbufs\":[{\"binding\":1,\"slot\":3}],"
|
||||
"\"sbufs\":[],\"textures\":[],\"images\":[]}";
|
||||
static struct {
|
||||
const GfxShader *fs[16]; // per input Location
|
||||
int failed;
|
||||
uint64_t cand, still, draws, nodata, building, ran, dropped;
|
||||
uint64_t ns_check, ns_rec, n_rec, ns_emit, ns_still;
|
||||
uint64_t ns_part[3], n_part[3], misses, cyc[3], ncyc, big; // checks: filter, data and stillness (memo misses), variant and pipeline // time spent: checks and lookups of every candidate; recording the draws again // cand: draws whose vertex shader reads c5 or c7
|
||||
uint64_t why[5]; // nodata by cause: camera, bones, place, bones copy, place copy
|
||||
uint64_t no_out, storage, no_fs; // variant without the extra output, with storage resources; no fs
|
||||
uint64_t other_cam; // listed draws left out: their camera block is not the scene camera
|
||||
uint64_t lk[14][5]; // last-frame lookups per binding index: none, self, side, nearer, refused (another object)
|
||||
uint64_t e_draws, e_pipe, e_dyn, e_push, e_pushw, e_sets, e_vb, e_setw, e_setreuse; // emitted commands (summary)
|
||||
} MO;
|
||||
|
||||
// The motion draws of the frame, kept as a list while the scene replays and recorded into the replay's own command
|
||||
// buffer by vk_mvobj_execute, through the recorder thread (vk_rec.h) like every other draw: recording them straight
|
||||
// into a secondary command buffer made the driver's work for each one on the replay thread. What a draw shares with
|
||||
// the one before it (pipeline, dynamic state, viewport, buffers, descriptors) is recorded once; a push descriptor set
|
||||
// is pushed again only where a binding changed (pushes update the set in place while its layout stays bound).
|
||||
typedef struct {
|
||||
VkPipeline pipe;
|
||||
const VkPipeInfo *info;
|
||||
uint32_t st, desc, vb; // indices into MQ.st, MQ.desc (the draw's packed descriptors) and MQ.vb
|
||||
VkDescriptorSet set[2]; // allocated sets; VK_NULL_HANDLE for a pushed one
|
||||
VkViewport v;
|
||||
VkRect2D sc;
|
||||
float bias[3];
|
||||
VkBuffer ib;
|
||||
VkDeviceSize ib_off;
|
||||
VkIndexType it;
|
||||
uint32_t count, instances, base_instance;
|
||||
int32_t base_vertex;
|
||||
uint64_t cam; // the draw's camera block (c4), this frame's
|
||||
} MoDraw;
|
||||
typedef struct { VkBuffer b[17]; VkDeviceSize o[17], sz[17], str[17]; } MoVb;
|
||||
static struct {
|
||||
uint64_t frame, fence; // the frame and submission the list belongs to
|
||||
VkFormat depth_fmt;
|
||||
MoDraw *d;
|
||||
VkPipeState *st;
|
||||
VkDescSlot *desc;
|
||||
MoVb *vb;
|
||||
int n, cap, nst, cst, nvb, cvb;
|
||||
uint32_t ndesc, cdesc;
|
||||
NVNbufferRange vin[17]; // the vertex ranges MQ.vb[nvb - 1] was resolved from, under vk_mem_gen vgen
|
||||
uint64_t vgen;
|
||||
float sup_in[3]; // the support buffer last filled for the list and its inputs
|
||||
VkDescriptorBufferInfo sup;
|
||||
float par_in[4]; // the fragment shader's parameters last written and their inputs
|
||||
VkDescriptorBufferInfo par;
|
||||
VkDescriptorSetLayout aset_layout[2]; // the last allocated set per set number, with its descriptors
|
||||
VkDescriptorSet aset[2];
|
||||
uint32_t aset_n[2];
|
||||
VkDescSlot aset_d[2][MAX_DESC];
|
||||
} MQ;
|
||||
|
||||
static void *mo_grow(void *p, int *cap, int need, size_t elem) {
|
||||
if (need <= *cap) return p;
|
||||
int c = *cap ? *cap : 256;
|
||||
while (c < need) c *= 2;
|
||||
if (!(p = realloc(p, elem * (size_t)c))) rt_fatal("vk_exec: out of memory");
|
||||
*cap = c;
|
||||
return p;
|
||||
}
|
||||
|
||||
// The position bank masks of the draws considered (gfx_shader_mvprev_banks), with counts, for the summary.
|
||||
static struct { uint32_t mask; uint64_t n; int ubo, ssbo, tex; } MM[32]; // ubo, ssbo, tex: the first vertex shader's
|
||||
static void mo_mask_note(uint32_t banks, const GfxShader *vs) {
|
||||
for (int i = 0; i < 32; i++)
|
||||
if (MM[i].mask == banks || !MM[i].n) {
|
||||
if (!MM[i].n) {
|
||||
const GfxShaderRes *r;
|
||||
MM[i].ubo = gfx_shader_resources(vs, GFX_RES_UBO, &r), MM[i].ssbo = gfx_shader_resources(vs, GFX_RES_SSBO, &r);
|
||||
MM[i].tex = gfx_shader_resources(vs, GFX_RES_TEX, &r);
|
||||
}
|
||||
MM[i].mask = banks, MM[i].n++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void mo_drop(void) {
|
||||
if (MQ.n) MO.dropped++;
|
||||
MQ.n = MQ.nst = MQ.nvb = 0, MQ.ndesc = 0, MQ.frame = 0;
|
||||
}
|
||||
|
||||
static uint64_t mo_gen = 1; // the list's snapshot (mo_prev_cached), bumped by mo_begin
|
||||
static struct { uint32_t mask, chg, n; } MF[16]; // this list's draws per position banks and changed banks
|
||||
static char fm_last[400]; // the last executed list's, formatted (vk_mvobj_frame_masks)
|
||||
|
||||
// Starts this frame's list (the old one, never executed, is dropped).
|
||||
static void mo_begin(VkExec *ex, uint64_t frame) {
|
||||
mo_drop();
|
||||
mo_gen++;
|
||||
memset(MF, 0, sizeof MF);
|
||||
MQ.frame = frame, MQ.fence = ex->fence, MQ.depth_fmt = ex->depth.format;
|
||||
memset(MQ.vin, 0, sizeof MQ.vin);
|
||||
MQ.vgen = 0, MQ.sup.buffer = MQ.par.buffer = VK_NULL_HANDLE;
|
||||
memset(MQ.aset, 0, sizeof MQ.aset);
|
||||
}
|
||||
|
||||
static const GfxShader *mo_fs(int loc) {
|
||||
if (loc < 0 || loc >= 16) return NULL;
|
||||
if (!MO.fs[loc]) {
|
||||
uint32_t w[sizeof mvobj_fs_spv / 4];
|
||||
memcpy(w, mvobj_fs_spv, sizeof w);
|
||||
w[MVOBJ_FS_LOCATION_WORD] = (uint32_t)loc;
|
||||
MO.fs[loc] = gfx_shader_own(w, sizeof w / 4, MO_FS_RES);
|
||||
if (!MO.fs[loc]) MO.failed = 1, rt_log("vk_exec: the per-object motion fragment shader is malformed: off");
|
||||
}
|
||||
return MO.fs[loc];
|
||||
}
|
||||
|
||||
// The write of binding b from its packed descriptors.
|
||||
static VkWriteDescriptorSet mo_write(const VkPipeBinding *b, const VkDescSlot *d) {
|
||||
VkWriteDescriptorSet w = {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = b->binding,
|
||||
.descriptorCount = b->count ? b->count : 1, .descriptorType = b->type};
|
||||
if (b->type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || b->type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
|
||||
b->type == VK_DESCRIPTOR_TYPE_SAMPLER)
|
||||
w.pImageInfo = &d->image;
|
||||
else
|
||||
w.pBufferInfo = &d->buffer; // VkDescSlot is 24 bytes, as VkDescriptorBufferInfo: arrays line up
|
||||
return w;
|
||||
}
|
||||
|
||||
// The descriptors of a motion draw, packed into MQ.desc at q->desc without the binding caches of the command buffer
|
||||
// being replayed (B, DS, RS), which then stay true for its next draw: uniform buffers (through ubo_info, so with
|
||||
// mo_ovr), the support buffer and sampled textures. An allocated set is written here (or the last one reused when it
|
||||
// would hold the same); a pushed set is pushed by mo_emit. 0 for a pipeline with storage buffers, images or texel
|
||||
// buffers (not drawn again).
|
||||
static int mo_desc(VkExec *ex, const VkPipeInfo *info, MoDraw *q) {
|
||||
if (info->n_bindings > MAX_BINDINGS) return 0;
|
||||
uint32_t total = 0;
|
||||
for (uint32_t i = 0; i < info->n_bindings; i++) total += info->bindings[i].count ? info->bindings[i].count : 1;
|
||||
if (total > MAX_DESC) return 0;
|
||||
int cdesc = (int)MQ.cdesc;
|
||||
MQ.desc = mo_grow(MQ.desc, &cdesc, (int)(MQ.ndesc + total), sizeof *MQ.desc);
|
||||
MQ.cdesc = (uint32_t)cdesc;
|
||||
VkDescSlot *d = MQ.desc + MQ.ndesc;
|
||||
memset(d, 0, total * sizeof *d); // unused bytes zero: equal descriptors compare equal
|
||||
for (uint32_t i = 0, nd = 0; i < info->n_bindings; i++) {
|
||||
const VkPipeBinding *b = &info->bindings[i];
|
||||
const GfxShaderRes *r = b->res;
|
||||
uint32_t cnt = b->count ? b->count : 1;
|
||||
VkDescSlot *o = &d[nd];
|
||||
int st = nvn_stage_of(b->stage);
|
||||
if (b->kind == VK_PIPE_SUPPORT) {
|
||||
float in[3];
|
||||
support_in(ex, in);
|
||||
if (!MQ.sup.buffer || memcmp(in, MQ.sup_in, sizeof in)) {
|
||||
VkBuffer sb;
|
||||
VkDeviceSize so;
|
||||
support_fill(scratch(ex, SUPPORT_BYTES, &sb, &so), in[0], in[1], in[2]);
|
||||
MQ.sup = (VkDescriptorBufferInfo){sb, so, SUPPORT_BYTES};
|
||||
memcpy(MQ.sup_in, in, sizeof in);
|
||||
}
|
||||
o->buffer = MQ.sup;
|
||||
} else if (b->kind == GFX_RES_UBO) {
|
||||
for (uint32_t k = 0; k < cnt; k++) o[k].buffer = ubo_info(ex, st, r->slot + (int)k);
|
||||
} else if (b->kind == GFX_RES_SSBO && !(r->flags & 1)) { // read only: instance data of foliage and such
|
||||
for (uint32_t k = 0; k < cnt; k++) o[k].buffer = ssbo_info(ex, st, r, 0);
|
||||
} else if (b->kind == GFX_RES_TEX && b->type != VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER) {
|
||||
vk_texture_lock();
|
||||
for (uint32_t k = 0; k < cnt; k++) {
|
||||
int slot = r->slot + (int)k;
|
||||
tex_info(ex, r, slot >= 0 && slot < 128 ? ex->tex[st][slot] : 0, 0, r->separate, &o[k].image);
|
||||
}
|
||||
vk_texture_unlock();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
nd += cnt;
|
||||
}
|
||||
uint32_t n_sets = info->n_sets ? info->n_sets : 1;
|
||||
q->set[0] = q->set[1] = VK_NULL_HANDLE;
|
||||
for (uint32_t s = 0, i0 = 0, k0 = 0; s < n_sets && s < 2; s++) {
|
||||
uint32_t i1 = i0, k1 = k0;
|
||||
while (i1 < info->n_bindings && (n_sets == 1 || info->bindings[i1].set == s))
|
||||
k1 += info->bindings[i1].count ? info->bindings[i1].count : 1, i1++;
|
||||
if (i1 > i0 && !(s ? info->push1 : info->push)) {
|
||||
VkDescriptorSetLayout sl = s ? info->set_layout1 : info->set_layout;
|
||||
if (MQ.aset[s] && MQ.aset_layout[s] == sl && MQ.aset_n[s] == k1 - k0 &&
|
||||
!memcmp(MQ.aset_d[s], d + k0, (k1 - k0) * sizeof *d)) {
|
||||
q->set[s] = MQ.aset[s];
|
||||
MO.e_setreuse++;
|
||||
} else {
|
||||
static VkWriteDescriptorSet w[MAX_BINDINGS];
|
||||
VkDescriptorSet set = alloc_set(ex, sl);
|
||||
for (uint32_t i = i0, k = k0; i < i1; k += info->bindings[i].count ? info->bindings[i].count : 1, i++)
|
||||
w[i - i0] = mo_write(&info->bindings[i], d + k), w[i - i0].dstSet = set;
|
||||
vkUpdateDescriptorSets(vk_ctx()->device, i1 - i0, w, 0, NULL);
|
||||
q->set[s] = MQ.aset[s] = set, MQ.aset_layout[s] = sl, MQ.aset_n[s] = k1 - k0;
|
||||
memcpy(MQ.aset_d[s], d + k0, (k1 - k0) * sizeof *d);
|
||||
MO.e_setw++;
|
||||
}
|
||||
}
|
||||
i0 = i1, k0 = k1;
|
||||
}
|
||||
q->desc = MQ.ndesc;
|
||||
MQ.ndesc += total;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The draw's vertex buffers as bind_vertex_buffers resolves them, into MQ.vb; the previous entry again while the
|
||||
// ranges and vk_mem_gen are the same (most motion draws in a row are one model's parts).
|
||||
static uint32_t mo_vertex_buffers(VkExec *ex) {
|
||||
uint64_t gen = vk_mem_gen();
|
||||
if (MQ.nvb && MQ.vgen == gen && !memcmp(MQ.vin, ex->vbuf, sizeof MQ.vin)) {
|
||||
const MoVb *l = &MQ.vb[MQ.nvb - 1];
|
||||
int same = 1;
|
||||
if (!vk_pipeline_vertex_input_dynamic())
|
||||
for (int b = 0; b < 16 && same; b++) same = l->str[b] == ex->pipe.vstream[b].stride;
|
||||
if (same) return (uint32_t)MQ.nvb - 1;
|
||||
}
|
||||
MQ.vb = mo_grow(MQ.vb, &MQ.cvb, MQ.nvb + 1, sizeof *MQ.vb);
|
||||
MoVb *v = &MQ.vb[MQ.nvb];
|
||||
for (int b = 0; b < 17; b++) {
|
||||
v->b[b] = Z.zbuf, v->o[b] = 0, v->sz[b] = ZERO_BYTES, v->str[b] = b < 16 ? ex->pipe.vstream[b].stride : 0;
|
||||
if (b == VK_PIPE_ZERO_BINDING) continue;
|
||||
const NVNbufferRange *r = &ex->vbuf[b];
|
||||
VkMemSpan sp;
|
||||
if (!r->address || !vk_mem_resolve(r->address, 1, &sp)) continue;
|
||||
VK_MEM_USE(sp);
|
||||
v->b[b] = sp.buffer, v->o[b] = sp.offset, v->sz[b] = r->size && r->size < sp.avail ? r->size : sp.avail;
|
||||
}
|
||||
memcpy(MQ.vin, ex->vbuf, sizeof MQ.vin);
|
||||
MQ.vgen = gen;
|
||||
return (uint32_t)MQ.nvb++;
|
||||
}
|
||||
|
||||
// Records the list into cb (the replay's command buffer, inside the motion pass's rendering), skipping what the draw
|
||||
// before already set.
|
||||
static void mo_emit(VkCommandBuffer cb) {
|
||||
int dyn_vi = vk_pipeline_vertex_input_dynamic();
|
||||
VkPipeline pipe = VK_NULL_HANDLE;
|
||||
uint32_t st = UINT32_MAX, vb = UINT32_MAX;
|
||||
VkPipelineLayout lay[2] = {0};
|
||||
const VkPipeInfo *pinfo[2] = {0};
|
||||
int pdesc[2] = {-1, -1}; // the pushed set's descriptors (MQ.desc index) under lay/pinfo, -1 none
|
||||
VkDescriptorSet bound[2] = {0};
|
||||
const MoDraw *p = NULL;
|
||||
for (int n = 0; n < MQ.n; p = &MQ.d[n++]) {
|
||||
const MoDraw *q = &MQ.d[n];
|
||||
const VkPipeInfo *info = q->info;
|
||||
if (q->pipe != pipe) vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, q->pipe), pipe = q->pipe, MO.e_pipe++;
|
||||
if (q->st != st) vk_pipeline_set_dynamic(cb, &MQ.st[q->st]), st = q->st, MO.e_dyn++;
|
||||
if (!p || memcmp(&q->v, &p->v, sizeof q->v)) vkCmdSetViewportWithCount(cb, 1, &q->v);
|
||||
if (!p || memcmp(&q->sc, &p->sc, sizeof q->sc)) vkCmdSetScissorWithCount(cb, 1, &q->sc);
|
||||
if (!p || memcmp(q->bias, p->bias, sizeof q->bias)) vkCmdSetDepthBias(cb, q->bias[0], q->bias[1], q->bias[2]);
|
||||
uint32_t n_sets = info->n_sets ? info->n_sets : 1;
|
||||
const VkDescSlot *d = MQ.desc + q->desc;
|
||||
for (uint32_t s = 0, i0 = 0, k0 = 0; s < n_sets && s < 2; s++) {
|
||||
uint32_t i1 = i0, k1 = k0;
|
||||
while (i1 < info->n_bindings && (n_sets == 1 || info->bindings[i1].set == s))
|
||||
k1 += info->bindings[i1].count ? info->bindings[i1].count : 1, i1++;
|
||||
if (i1 > i0) {
|
||||
if (s ? info->push1 : info->push) {
|
||||
// the bindings that differ from the last push of this layout and pipeline (all after another)
|
||||
int same = lay[s] == info->layout && pinfo[s] == info && pdesc[s] >= 0;
|
||||
const VkDescSlot *pd = same ? MQ.desc + pdesc[s] : NULL;
|
||||
VkWriteDescriptorSet w[MAX_BINDINGS];
|
||||
uint32_t nw = 0;
|
||||
for (uint32_t i = i0, k = k0; i < i1; i++) {
|
||||
uint32_t cnt = info->bindings[i].count ? info->bindings[i].count : 1;
|
||||
if (!same || memcmp(d + k, pd + k, cnt * sizeof *d)) w[nw++] = mo_write(&info->bindings[i], d + k);
|
||||
k += cnt;
|
||||
}
|
||||
if (nw) rec_push(vk_pipeline_push_fn(), cb, VK_PIPELINE_BIND_POINT_GRAPHICS, info->layout, s, nw, w);
|
||||
MO.e_push += nw != 0, MO.e_pushw += nw;
|
||||
lay[s] = info->layout, pinfo[s] = info, pdesc[s] = (int)q->desc, bound[s] = VK_NULL_HANDLE;
|
||||
} else if (q->set[s] != bound[s] || lay[s] != info->layout) {
|
||||
vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, info->layout, s, 1, &q->set[s], 0, NULL);
|
||||
bound[s] = q->set[s], lay[s] = info->layout, pinfo[s] = info, pdesc[s] = -1, MO.e_sets++;
|
||||
}
|
||||
}
|
||||
i0 = i1, k0 = k1;
|
||||
}
|
||||
if (q->vb != vb) {
|
||||
const MoVb *v = &MQ.vb[q->vb];
|
||||
vkCmdBindVertexBuffers2(cb, 0, 17, v->b, v->o, v->sz, dyn_vi ? NULL : v->str);
|
||||
vb = q->vb, MO.e_vb++;
|
||||
}
|
||||
if (!p || q->ib != p->ib || q->ib_off != p->ib_off || q->it != p->it) vkCmdBindIndexBuffer(cb, q->ib, q->ib_off, q->it);
|
||||
vkCmdDrawIndexed(cb, q->count, q->instances, 0, q->base_vertex, q->base_instance);
|
||||
MO.e_draws++;
|
||||
}
|
||||
}
|
||||
|
||||
static void mvobj_draw_(VkExec *ex, const p_draw_idx *d, VkBuffer ib, VkDeviceSize ib_off, uint64_t t0);
|
||||
static void mvobj_draw(VkExec *ex, const p_draw_idx *d, VkBuffer ib, VkDeviceSize ib_off) {
|
||||
if (MO.failed || !vk_mvprev_on() || !B.st_valid) return;
|
||||
static int timed = -1;
|
||||
if (timed < 0) timed = rt_knob("MVOBJ_TIMES", "0"); // TOTK_MVOBJ_TIMES=1: the summary's times (the clock reads cost)
|
||||
if (!timed) return mvobj_draw_(ex, d, ib, ib_off, 0);
|
||||
uint64_t t0 = kern_now_ns();
|
||||
mvobj_draw_(ex, d, ib, ib_off, t0);
|
||||
MO.ns_check += kern_now_ns() - t0;
|
||||
}
|
||||
|
||||
// TOTK_MVOBJ_BANKS=1 (test): for scene draws, the words of each other uniform bank (c3, c6, c8-c16) that changed
|
||||
// since the same shader's draw last frame, logged at frame 3000: where the time that moves wind and water lives
|
||||
static void mvobj_banks(VkExec *ex) {
|
||||
static int on = -1;
|
||||
if (on < 0) on = rt_knob("MVOBJ_BANKS", "0");
|
||||
if (!on || !B.st_valid) return;
|
||||
uint64_t frame = __atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED);
|
||||
if (frame < 2999 || frame > 3000) return;
|
||||
static struct { const GfxShader *vs; int bank; uint64_t frame; float w[64]; int n; } tab[4096];
|
||||
static int logged;
|
||||
const GfxShaderRes *ur;
|
||||
int nu = gfx_shader_resources(B.st.vs, GFX_RES_UBO, &ur);
|
||||
for (int i = 0; i < nu; i++) {
|
||||
int bank = ur[i].slot;
|
||||
if (bank < 3 || bank == 4 || bank == 5 || bank == 7 || bank > 16) continue;
|
||||
const p_bind_buf *u = &ex->ubo[NVN_STAGE_VS][bank - 3];
|
||||
uint32_t n = u->size < 256 ? (uint32_t)u->size / 4 : 64;
|
||||
const float *now = n ? (const float *)vk_usnap_bytes(u->addr, n * 4) : NULL;
|
||||
if (!now) continue;
|
||||
uint32_t k = (uint32_t)(((uintptr_t)B.st.vs ^ (uint64_t)bank * 0x9E3779B97F4A7C15ull) * 0xff51afd7ed558ccdull >> 52);
|
||||
if (frame == 2999) {
|
||||
tab[k].vs = B.st.vs, tab[k].bank = bank, tab[k].frame = frame, tab[k].n = (int)n;
|
||||
memcpy(tab[k].w, now, n * 4);
|
||||
} else if (tab[k].vs == B.st.vs && tab[k].bank == bank && tab[k].frame == 2999 && logged < 80) {
|
||||
char line[700];
|
||||
int o = 0, nd = 0;
|
||||
for (int j = 0; j < tab[k].n && j < (int)n && o < 600; j++)
|
||||
if (memcmp(&tab[k].w[j], &now[j], 4)) nd++, o += snprintf(line + o, sizeof line - o, " [%d] %g>%g", j, tab[k].w[j], now[j]);
|
||||
if (nd) logged++, rt_log("mvbanks: vs %p c%d size %#llx: %d words%s", (const void *)B.st.vs, bank,
|
||||
(unsigned long long)u->size, nd, line);
|
||||
tab[k].frame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1: old is cur a little changed or the same (every float within 10 %, at least 1.0 and at most 4.0); *changed: not
|
||||
// the same. The cap: TotK's world positions are in the thousands, and 10 % of them let another object's block a few
|
||||
// units away pass as this one moved; nothing moves 4 units in a frame.
|
||||
static int mo_similar(const uint8_t *cur, const uint8_t *old, uint64_t n, int *changed) {
|
||||
*changed = 0;
|
||||
if (!memcmp(cur, old, n)) return 1;
|
||||
// word by word as integers; float arithmetic only on two normal floats (the blocks hold integers too, and
|
||||
// denormal arithmetic took this loop 1.9 million cycles a block)
|
||||
const uint32_t *x = (const uint32_t *)cur, *y = (const uint32_t *)old;
|
||||
for (uint64_t w = 0; w < n / 4; w++) {
|
||||
if (x[w] == y[w]) continue;
|
||||
uint32_t ex = x[w] >> 23 & 0xff, ey = y[w] >> 23 & 0xff;
|
||||
if (!ex || ex == 0xff || !ey || ey == 0xff) continue; // not both normal floats: an integer or a tiny value
|
||||
float fx, fy;
|
||||
memcpy(&fx, &x[w], 4), memcpy(&fy, &y[w], 4);
|
||||
float dd = fabsf(fx - fy), tol = 0.1f * fabsf(fy);
|
||||
if (dd > (tol < 1.0f ? 1.0f : tol > 4.0f ? 4.0f : tol)) return 0;
|
||||
}
|
||||
*changed = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Last frame's copy of a block, accepted when it is this frame's block a little changed (mo_similar,
|
||||
// which see): a block of another object the address rule found would make motion up. 1 and *out: found (the block itself
|
||||
// when it is unchanged, *changed 0); 0: none, the draw keeps this frame's block there.
|
||||
static int mo_prev_block(int index, const p_bind_buf *u, VkDescriptorBufferInfo *out, int *changed) {
|
||||
uint64_t max = LIM.ubo_max, n = u->size < max ? u->size : max, pa;
|
||||
int how;
|
||||
*changed = 0;
|
||||
// c9 and c11 keep this frame's block. With the copies the address rule finds for c11 and last frame's c9, the
|
||||
// c4 c7 c9 c11 draws (walls, cliffs, arches) got several pixels of motion under a still camera (TOTK_MV_CHECK,
|
||||
// those draws alone: 34 % of the picture moving; with c11 kept, 0.8 %). With c11 kept, their c9 still changes every
|
||||
// frame while nothing moves (the check's per-draw line: about 120 such draws a frame changed in c9 alone), the
|
||||
// object scale the picture shows is 0.6-0.8 of theirs, and with Link stopped against a cliff 40-77 % of the picture
|
||||
// got 1-3 px of motion that flipped direction, three times the warp error of the camera's vectors. With c9 kept too,
|
||||
// the turning run is as good (object warp error 0.46 of the camera's). Why their copies are wrong is not known.
|
||||
// TOTK_MVPREV_SKIP=<hex bank mask> (test) replaces the default a00 (bit n: cn).
|
||||
static long skip = -2;
|
||||
if (skip == -2) {
|
||||
const char *e = cfg_get("TOTK_MVPREV_SKIP");
|
||||
skip = e && *e ? strtol(e, NULL, 16) : 1L << 9 | 1L << 11;
|
||||
}
|
||||
if (skip >> (index + 3) & 1) return 0;
|
||||
uint64_t c0 = __builtin_ia32_rdtsc();
|
||||
int found = vk_mvprev_find(index, u->addr, u->size, &pa, &how);
|
||||
uint64_t c1 = __builtin_ia32_rdtsc();
|
||||
MO.cyc[0] += c1 - c0, MO.ncyc++, MO.big += n;
|
||||
if (index >= 0 && index < 14) MO.lk[index][found ? how : MVPREV_NONE]++;
|
||||
if (!found || !vk_usnap_find(pa, n, LIM.ubo_align, out)) return 0;
|
||||
if (how == MVPREV_SELF) return 1; // bound at the same address last frame: not written again
|
||||
const uint8_t *map = vk_usnap_map(), *cur = vk_usnap_bytes(u->addr, n);
|
||||
uint64_t c2 = __builtin_ia32_rdtsc();
|
||||
MO.cyc[1] += c2 - c1;
|
||||
if (!cur) return 0;
|
||||
const uint8_t *old = map + out->offset;
|
||||
static long trust = -2; // TOTK_MVPREV_TRUST=<hex bank mask> (test): the copy found is taken without the check
|
||||
if (trust == -2) {
|
||||
const char *e = cfg_get("TOTK_MVPREV_TRUST");
|
||||
trust = e && *e ? strtol(e, NULL, 16) : 0;
|
||||
}
|
||||
if (trust >> (index + 3) & 1) {
|
||||
*changed = memcmp(cur, old, n) != 0;
|
||||
return 1;
|
||||
}
|
||||
int r = mo_similar(cur, old, n, changed);
|
||||
MO.cyc[2] += __builtin_ia32_rdtsc() - c2;
|
||||
if (!r && index >= 0 && index < 14) MO.lk[index][4]++;
|
||||
return r;
|
||||
}
|
||||
|
||||
// mo_prev_block once per block and list: many draws share their blocks (the scene's c14, a model's c7 and c9 across
|
||||
// its parts), and the lookup with its comparison was most of the checks' time.
|
||||
static int mo_prev_cached(int index, const p_bind_buf *u, VkDescriptorBufferInfo *out, int *changed) {
|
||||
static struct { uint64_t gen, addr, size; int index, r, changed; VkDescriptorBufferInfo out; } bc[4096];
|
||||
uint32_t i = (uint32_t)((u->addr ^ (uint64_t)index << 56 ^ u->size << 40) * 0x9E3779B97F4A7C15ull >> 52);
|
||||
if (bc[i].gen == mo_gen && bc[i].addr == u->addr && bc[i].size == u->size && bc[i].index == index) {
|
||||
*out = bc[i].out, *changed = bc[i].changed;
|
||||
return bc[i].r;
|
||||
}
|
||||
int r = mo_prev_block(index, u, out, changed);
|
||||
bc[i].gen = mo_gen, bc[i].addr = u->addr, bc[i].size = u->size, bc[i].index = index;
|
||||
bc[i].r = r, bc[i].changed = *changed, bc[i].out = *out;
|
||||
return r;
|
||||
}
|
||||
|
||||
static void mvobj_draw_(VkExec *ex, const p_draw_idx *d, VkBuffer ib, VkDeviceSize ib_off, uint64_t t0) {
|
||||
mvobj_banks(ex);
|
||||
const VkExecTarget *z = &ex->depth;
|
||||
if (!z->image || z->width < DLAA_SCENE_MIN_W || z->format != VK_FORMAT_D32_SFLOAT || B.st.samples > 1 || B.st.raster_discard)
|
||||
return;
|
||||
// solid geometry only, which tests and writes the scene depth: effects, particles and screen quads write none, and
|
||||
// drawn again they would put their motion on the surfaces behind them (DLSS takes those from the surface)
|
||||
if (!B.st.ds.dt || !B.st.ds.dw) return;
|
||||
// a vertex shader that reads a user bank besides the camera's (c4) may move on its own
|
||||
const GfxShaderRes *ur;
|
||||
int nu = gfx_shader_resources(B.st.vs, GFX_RES_UBO, &ur), reads = 0;
|
||||
for (int i = 0; i < nu; i++) reads |= ur[i].slot >= 3 && ur[i].slot <= 16 && ur[i].slot != 4;
|
||||
if (!reads) return;
|
||||
MO.cand++;
|
||||
uint64_t tp = t0 ? kern_now_ns() : 0;
|
||||
if (t0) MO.ns_part[0] += tp - t0, MO.n_part[0]++;
|
||||
uint64_t frame = __atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED);
|
||||
if (MQ.frame != frame || MQ.fence != ex->fence || MQ.depth_fmt != z->format) mo_begin(ex, frame);
|
||||
if (!__atomic_load_n(&LIM.ubo_max, __ATOMIC_ACQUIRE)) limits_init();
|
||||
// the variant: the translation of the same code and answers, kept per shader once found; it names the banks the
|
||||
// position depends on (0: none, the shader is skipped from then on)
|
||||
static struct { const GfxShader *of, *vs; uint32_t banks; } vmemo[1024];
|
||||
uint32_t vi = (uint32_t)((uintptr_t)B.st.vs * 0x9E3779B97F4A7C15ull >> 54);
|
||||
if (vmemo[vi].of != B.st.vs) {
|
||||
int has, pending = 0;
|
||||
sf_mvprev = 1;
|
||||
const GfxShader *v = shader_for(ex, NVN_STAGE_VS, &has, &pending, 0);
|
||||
sf_mvprev = 0;
|
||||
if (!v || pending) {
|
||||
MO.building++;
|
||||
return;
|
||||
}
|
||||
GfxShaderIo io[32];
|
||||
uint32_t extra = gfx_shader_outputs(v, io) & ~gfx_shader_outputs(B.st.vs, io);
|
||||
uint32_t banks = extra ? gfx_shader_mvprev_banks(v) & 0x1fff8u : 0;
|
||||
vmemo[vi].of = B.st.vs, vmemo[vi].vs = v, vmemo[vi].banks = banks;
|
||||
vk_mvprev_want((banks & ~(1u << 4)) >> 3); // binding index = bank - 3
|
||||
}
|
||||
const GfxShader *vs = vmemo[vi].vs;
|
||||
uint32_t banks = vmemo[vi].banks;
|
||||
if (!banks || !(banks & ~(1u << 4))) {
|
||||
MO.no_out++;
|
||||
return;
|
||||
}
|
||||
// a position without the scene camera (c4) has a view of its own (c8 c9 c10 [c13]: its c8 is at a new address every
|
||||
// frame, last frame's is never found): drawn again it would get this frame's view and no camera motion
|
||||
// (TOTK_MV_CHECK while turning: 6.2 px end-point error against the camera vectors' 1.7)
|
||||
static int noc4 = -1;
|
||||
if (noc4 < 0) noc4 = rt_knob("MVOBJ_NOC4", "0"); // TOTK_MVOBJ_NOC4=1 (test): drawn again all the same
|
||||
if (!(banks >> 4 & 1) && !noc4) {
|
||||
MO.no_out++;
|
||||
return;
|
||||
}
|
||||
// only positions that read an object's own blocks: bones (c5), place (c7) or c6. The rest (terrain and grass: c14
|
||||
// c15 c16, with or without c9) measured no better than the camera's motion (TOTK_MV_CHECK, turning, those draws
|
||||
// alone: object warp error 0.112 against the camera's 0.113, the object part 0.4 of what the picture shows) and
|
||||
// were half the draws checked and listed. TOTK_MVOBJ_ALL=1 (test) takes them again.
|
||||
static int all = -1;
|
||||
if (all < 0) all = rt_knob("MVOBJ_ALL", "0");
|
||||
if (!all && !(banks & (1u << 5 | 1u << 6 | 1u << 7))) {
|
||||
MO.no_out++;
|
||||
return;
|
||||
}
|
||||
// TOTK_MVOBJ_ONLY=<hex bank mask> (test): only draws whose position reads no bank outside the mask (bit n: cn)
|
||||
static long only = -2;
|
||||
if (only == -2) {
|
||||
const char *e = cfg_get("TOTK_MVOBJ_ONLY");
|
||||
only = e && *e ? strtol(e, NULL, 16) : -1;
|
||||
}
|
||||
if (only >= 0 && banks & ~(uint32_t)only & ~(1u << 4)) return;
|
||||
static long exact = -2; // TOTK_MVOBJ_EXACT=<hex bank mask> (test): only draws with exactly these position banks
|
||||
if (exact == -2) {
|
||||
const char *e = cfg_get("TOTK_MVOBJ_EXACT");
|
||||
exact = e && *e ? strtol(e, NULL, 16) : -1;
|
||||
}
|
||||
if (exact >= 0 && banks != (uint32_t)exact) return;
|
||||
mo_mask_note(banks, B.st.vs);
|
||||
// last frame's camera and blocks; held still when none of the position's blocks changed (most of the scenery)
|
||||
VkDescriptorBufferInfo cam, prevb[17];
|
||||
float jit[2];
|
||||
if (!vk_dlaa_prev_camera(&cam, jit)) {
|
||||
MO.nodata++, MO.why[0]++;
|
||||
return;
|
||||
}
|
||||
static struct { uint64_t frame, key; int moving; VkDescriptorBufferInfo b[17]; uint32_t have; } memo[8192];
|
||||
uint64_t key = banks;
|
||||
for (int b = 3; b <= 16; b++)
|
||||
if (banks >> b & 1 && b != 4) key = (key ^ ex->ubo[NVN_STAGE_VS][b - 3].addr) * 0x100000001b3ull;
|
||||
uint32_t mi = (uint32_t)(key * 0xff51afd7ed558ccdull >> 51);
|
||||
uint32_t have = 0;
|
||||
if (memo[mi].frame == frame && memo[mi].key == key) {
|
||||
if (!memo[mi].moving) {
|
||||
if (t0) MO.ns_still += kern_now_ns() - tp;
|
||||
return (void)MO.still++;
|
||||
}
|
||||
memcpy(prevb, memo[mi].b, sizeof prevb), have = memo[mi].have;
|
||||
} else {
|
||||
MO.misses++;
|
||||
int moving = 0;
|
||||
for (int b = 3; b <= 16; b++) {
|
||||
if (!(banks >> b & 1) || b == 4) continue;
|
||||
const p_bind_buf *u = &ex->ubo[NVN_STAGE_VS][b - 3];
|
||||
int changed;
|
||||
if (u->addr && u->size && mo_prev_cached(b - 3, u, &prevb[b], &changed))
|
||||
have |= 1u << b, moving |= changed << b; // the banks whose block changed
|
||||
}
|
||||
memo[mi].frame = frame, memo[mi].key = key, memo[mi].moving = moving;
|
||||
memcpy(memo[mi].b, prevb, sizeof prevb), memo[mi].have = have;
|
||||
if (!moving) {
|
||||
if (t0) MO.ns_still += kern_now_ns() - tp;
|
||||
return (void)MO.still++;
|
||||
}
|
||||
}
|
||||
if (t0) MO.ns_part[1] += kern_now_ns() - tp, MO.n_part[1]++, tp = kern_now_ns();
|
||||
GfxShaderIo io[32];
|
||||
uint32_t extra = gfx_shader_outputs(vs, io) & ~gfx_shader_outputs(B.st.vs, io);
|
||||
const GfxShaderRes *r;
|
||||
if (gfx_shader_resources(vs, GFX_RES_IMG, &r)) {
|
||||
MO.storage++;
|
||||
return;
|
||||
}
|
||||
const GfxShader *fs = mo_fs(31 - __builtin_clz(extra));
|
||||
if (!fs) {
|
||||
MO.no_fs++;
|
||||
return;
|
||||
}
|
||||
VkPipeState ms = B.st;
|
||||
ms.vs = vs, ms.fs = fs, ms.feedback = 0;
|
||||
memset(ms.color_fmt, 0, sizeof ms.color_fmt);
|
||||
ms.color_fmt[0] = NVN_FORMAT_RG16F;
|
||||
// blending off (blend_en below); the factors stay the game's, valid NVN values
|
||||
ms.color.blend_en = 0, ms.color.logic_op = 0;
|
||||
ms.chmask.mask = 0x3; // r and g of target 0
|
||||
ms.ms.a2c = 0;
|
||||
// the game's own test with its equal case (LESS -> LEQUAL, GREATER -> GEQUAL): the surface the draw left in the
|
||||
// scene depth passes, anything nearer hides it (TOTK_MVOBJ_EQUAL=1: EQUAL, for comparison)
|
||||
static int eq = -1;
|
||||
if (eq < 0) eq = rt_knob("MVOBJ_EQUAL", "0");
|
||||
int df = B.st.ds.df;
|
||||
df = eq ? NVN_DEPTH_FUNC_EQUAL : df == NVN_DEPTH_FUNC_LESS ? NVN_DEPTH_FUNC_LEQUAL
|
||||
: df == NVN_DEPTH_FUNC_GREATER ? NVN_DEPTH_FUNC_GEQUAL : df == NVN_DEPTH_FUNC_LEQUAL || df == NVN_DEPTH_FUNC_GEQUAL ? df
|
||||
: NVN_DEPTH_FUNC_EQUAL;
|
||||
ms.ds.dt = 1, ms.ds.dw = 0, ms.ds.df = (uint64_t)df, ms.ds.st = 0;
|
||||
// the pipeline per state, kept: the lookup hashes and locks the whole state
|
||||
static struct { VkPipeState st; VkPipeline p; const VkPipeInfo *info; int used; } pmemo[64];
|
||||
static int plast = -1; // the entry of the last draw: a model's parts in a row share the state, no hash then
|
||||
uint32_t pi;
|
||||
if (plast >= 0 && pmemo[plast].used && !memcmp(&pmemo[plast].st, &ms, sizeof ms)) {
|
||||
pi = (uint32_t)plast;
|
||||
} else {
|
||||
uint64_t ph = 0xcbf29ce484222325ull;
|
||||
for (size_t i = 0; i < sizeof ms; i += 8) {
|
||||
uint64_t w8;
|
||||
memcpy(&w8, (const uint8_t *)&ms + i, sizeof ms - i < 8 ? sizeof ms - i : 8);
|
||||
ph = (ph ^ w8) * 0x100000001b3ull;
|
||||
}
|
||||
pi = (uint32_t)(ph >> 58);
|
||||
}
|
||||
const VkPipeInfo *info = NULL;
|
||||
VkPipeline pipe;
|
||||
int pending = 0;
|
||||
if (pmemo[pi].used && !memcmp(&pmemo[pi].st, &ms, sizeof ms)) {
|
||||
info = pmemo[pi].info;
|
||||
pipe = __atomic_load_n(&info->pipeline, __ATOMIC_ACQUIRE); // its optimised build once swapped in
|
||||
if (!pipe) return;
|
||||
} else {
|
||||
pipe = vk_pipeline_graphics_async(&ms, &info, &pending, 0);
|
||||
if (pending || !pipe || !info) {
|
||||
MO.building += pending != 0;
|
||||
return;
|
||||
}
|
||||
pmemo[pi].st = ms, pmemo[pi].p = pipe, pmemo[pi].info = info, pmemo[pi].used = 1;
|
||||
}
|
||||
plast = (int)pi;
|
||||
uint64_t t1 = kern_now_ns();
|
||||
if (t0) MO.ns_part[2] += t1 - tp, MO.n_part[2]++;
|
||||
if (t0) MO.ns_check -= t1 - t0; // counted as recording below
|
||||
// into the frame's list, past the primary's binding caches (mo_desc); mo_emit records it
|
||||
MQ.d = mo_grow(MQ.d, &MQ.cap, MQ.n + 1, sizeof *MQ.d);
|
||||
MoDraw *q = &MQ.d[MQ.n];
|
||||
float par_in[4] = {(float)z->width, (float)z->height, jit[0], jit[1]};
|
||||
if (!MQ.par.buffer || memcmp(par_in, MQ.par_in, sizeof par_in)) {
|
||||
VkBuffer pbuf;
|
||||
VkDeviceSize poff;
|
||||
memcpy(scratch(ex, 16, &pbuf, &poff), par_in, sizeof par_in);
|
||||
MQ.par = (VkDescriptorBufferInfo){pbuf, poff, 16};
|
||||
memcpy(MQ.par_in, par_in, sizeof par_in);
|
||||
}
|
||||
memset(mo_ovr, 0, sizeof mo_ovr);
|
||||
for (int bk = 3; bk <= 16; bk++) // last frame's block, or this frame's where none was found
|
||||
mo_ovr[NVN_STAGE_VS][bk + 15] = bk == 4 ? cam : have >> bk & 1 ? prevb[bk] : ubo_info(ex, NVN_STAGE_VS, bk);
|
||||
mo_ovr[NVN_STAGE_FS][3] = MQ.par;
|
||||
mo_ovr_on = 1;
|
||||
int bound = mo_desc(ex, info, q);
|
||||
mo_ovr_on = 0;
|
||||
if (!bound) {
|
||||
MO.storage++;
|
||||
return;
|
||||
}
|
||||
q->pipe = pipe, q->info = info;
|
||||
if (!MQ.nst || memcmp(&MQ.st[MQ.nst - 1], &ms, sizeof ms)) {
|
||||
MQ.st = mo_grow(MQ.st, &MQ.cst, MQ.nst + 1, sizeof *MQ.st);
|
||||
MQ.st[MQ.nst++] = ms;
|
||||
}
|
||||
q->st = (uint32_t)MQ.nst - 1;
|
||||
q->v = draw_viewport(ex), q->sc = vk_exec_scissor(ex);
|
||||
q->bias[0] = ex->poly_offset.units, q->bias[1] = ex->poly_offset.clamp, q->bias[2] = ex->poly_offset.factor;
|
||||
q->vb = mo_vertex_buffers(ex);
|
||||
q->ib = ib, q->ib_off = ib_off;
|
||||
q->it = d->itype == NVN_INDEX_TYPE_UNSIGNED_SHORT ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32;
|
||||
q->count = (uint32_t)d->count, q->instances = d->instances > 0 ? (uint32_t)d->instances : 1;
|
||||
q->base_vertex = d->base_vertex, q->base_instance = (uint32_t)d->base_instance;
|
||||
q->cam = ex->ubo[NVN_STAGE_VS][1].addr;
|
||||
MQ.n++;
|
||||
MO.draws++;
|
||||
for (int i = 0; i < 16; i++) // the frame's draws per position banks and changed banks (vk_mvobj_frame_masks)
|
||||
if (!MF[i].n || (MF[i].mask == banks && MF[i].chg == (uint32_t)memo[mi].moving)) {
|
||||
MF[i].mask = banks, MF[i].chg = (uint32_t)memo[mi].moving, MF[i].n++;
|
||||
break;
|
||||
}
|
||||
if (t0) MO.ns_rec += kern_now_ns() - t1, MO.n_rec++;
|
||||
}
|
||||
|
||||
int vk_mvobj_execute(VkExec *ex, uint64_t cam, VkImageView mv, VkImageView depth, VkFormat depth_fmt, uint32_t w,
|
||||
uint32_t h) {
|
||||
static uint64_t frames;
|
||||
if (++frames % 60 == 0)
|
||||
rt_log("vk_exec: per-object motion: %.2f ms a frame checking, %.2f ms listing (%.1f us a draw), %.2f ms recording",
|
||||
MO.ns_check / 1e6 / (frames ? frames : 1), MO.ns_rec / 1e6 / (frames ? frames : 1),
|
||||
MO.n_rec ? MO.ns_rec / 1e3 / MO.n_rec : 0.0, MO.ns_emit / 1e6 / (frames ? frames : 1));
|
||||
if (frames % 600 == 0) {
|
||||
char l[1400];
|
||||
int o = 0;
|
||||
for (int i = 0; i < 32 && MM[i].n && o < 1300; i++)
|
||||
o += snprintf(l + o, sizeof l - o, " %#x:%llu (u%d s%d t%d)", MM[i].mask, (unsigned long long)MM[i].n, MM[i].ubo,
|
||||
MM[i].ssbo, MM[i].tex);
|
||||
if (o) rt_log("vk_exec: per-object motion position banks (bit n: cn) and draws:%s", l);
|
||||
o = 0;
|
||||
for (int i = 0; i < 14 && o < 1300; i++)
|
||||
if (MO.lk[i][0] + MO.lk[i][1] + MO.lk[i][2] + MO.lk[i][3])
|
||||
o += snprintf(l + o, sizeof l - o, " c%d %llu/%llu/%llu/%llu/%llu", i + 3, (unsigned long long)MO.lk[i][0],
|
||||
(unsigned long long)MO.lk[i][1], (unsigned long long)MO.lk[i][2],
|
||||
(unsigned long long)MO.lk[i][3], (unsigned long long)MO.lk[i][4]);
|
||||
if (o) rt_log("vk_exec: per-object motion last-frame lookups (none/self/side/nearer/refused):%s", l);
|
||||
}
|
||||
if (frames % 60 == 0 && MO.ran)
|
||||
rt_log("vk_exec: per-object motion recorded a frame: %.0f draws, %.0f pipeline binds, %.0f dynamic states, %.0f "
|
||||
"pushes of %.1f bindings, %.0f set binds, %.0f vertex buffer binds; sets written %.0f, reused %.0f",
|
||||
(double)MO.e_draws / MO.ran, (double)MO.e_pipe / MO.ran, (double)MO.e_dyn / MO.ran, (double)MO.e_push / MO.ran,
|
||||
MO.e_push ? (double)MO.e_pushw / MO.e_push : 0.0, (double)MO.e_sets / MO.ran, (double)MO.e_vb / MO.ran,
|
||||
(double)MO.e_setw / MO.ran, (double)MO.e_setreuse / MO.ran);
|
||||
if (frames % 60 == 0)
|
||||
rt_log("vk_exec: per-object motion checks: filter %.2f ms (%.0f a frame), data %.2f ms (%.0f), variant and "
|
||||
"pipeline %.2f ms (%.0f); %.0f objects looked up; still draws %.2f ms", MO.ns_part[0] / 1e6 / frames,
|
||||
(double)MO.n_part[0] / frames,
|
||||
MO.ns_part[1] / 1e6 / frames, (double)MO.n_part[1] / frames, MO.ns_part[2] / 1e6 / frames,
|
||||
(double)MO.n_part[2] / frames, (double)MO.misses / frames, MO.ns_still / 1e6 / frames);
|
||||
if (frames % 60 == 0 && MO.ncyc)
|
||||
rt_log("vk_exec: per-object motion lookups: %llu cycles finding last frame's, %llu the copies, %llu comparing, "
|
||||
"a lookup; %llu bytes a lookup", (unsigned long long)(MO.cyc[0] / MO.ncyc),
|
||||
(unsigned long long)(MO.cyc[1] / MO.ncyc), (unsigned long long)(MO.cyc[2] / MO.ncyc),
|
||||
(unsigned long long)(MO.big / MO.ncyc));
|
||||
if (frames % 60 == 0)
|
||||
rt_log("vk_exec: per-object motion: %llu draws read place or bones, %llu of them still, %llu drawn again, %llu without last frame's data, %llu "
|
||||
"waiting for a shader or pipeline; %llu frames ran, %llu dropped (no data: camera %llu, bones %llu, place %llu, "
|
||||
"bones copy %llu, place copy %llu; uniform alignment %llu; variant without its output %llu, with storage %llu, "
|
||||
"no fragment shader %llu; another camera %llu)",
|
||||
(unsigned long long)MO.cand, (unsigned long long)MO.still, (unsigned long long)MO.draws, (unsigned long long)MO.nodata,
|
||||
(unsigned long long)MO.building,
|
||||
(unsigned long long)MO.ran, (unsigned long long)MO.dropped, (unsigned long long)MO.why[0],
|
||||
(unsigned long long)MO.why[1], (unsigned long long)MO.why[2], (unsigned long long)MO.why[3],
|
||||
(unsigned long long)MO.why[4], (unsigned long long)LIM.ubo_align, (unsigned long long)MO.no_out,
|
||||
(unsigned long long)MO.storage, (unsigned long long)MO.no_fs, (unsigned long long)MO.other_cam);
|
||||
if (!MQ.n) return 0;
|
||||
static int exec = -1;
|
||||
if (exec < 0) exec = rt_knob("MVOBJ_EXEC", "1"); // TOTK_MVOBJ_EXEC=0: listed, never recorded (test)
|
||||
if (!exec) {
|
||||
mo_drop();
|
||||
return 0;
|
||||
}
|
||||
uint64_t frame = __atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED);
|
||||
if (MQ.frame != frame || MQ.fence != ex->fence || MQ.depth_fmt != depth_fmt) {
|
||||
mo_drop();
|
||||
return 0;
|
||||
}
|
||||
int fo = 0;
|
||||
for (int i = 0; i < 16 && MF[i].n && fo < (int)sizeof fm_last - 40; i++)
|
||||
fo += snprintf(fm_last + fo, sizeof fm_last - fo, " %#x/%#x:%u", MF[i].mask, MF[i].chg, MF[i].n);
|
||||
if (!fo) fm_last[0] = 0;
|
||||
// only draws with the scene camera block (vk_dlaa.c mv_pass's): last frame's camera they get is that block's, and
|
||||
// a draw of another view would move by the difference between the two views
|
||||
int kept = 0;
|
||||
for (int i = 0; i < MQ.n; i++)
|
||||
if (MQ.d[i].cam == cam) MQ.d[kept++] = MQ.d[i];
|
||||
else MO.other_cam++;
|
||||
MQ.n = kept;
|
||||
if (!MQ.n) {
|
||||
mo_drop();
|
||||
return 0;
|
||||
}
|
||||
uint64_t t0 = kern_now_ns();
|
||||
VkCommandBuffer cb = ex->cb;
|
||||
VkRenderingAttachmentInfo ca = {.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = mv,
|
||||
.imageLayout = VK_IMAGE_LAYOUT_GENERAL, .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE};
|
||||
VkRenderingAttachmentInfo da = {.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = depth,
|
||||
.imageLayout = VK_IMAGE_LAYOUT_GENERAL, .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE};
|
||||
VkRenderingInfo ri = {.sType = VK_STRUCTURE_TYPE_RENDERING_INFO, .renderArea = {{0, 0}, {w, h}}, .layerCount = 1,
|
||||
.colorAttachmentCount = 1, .pColorAttachments = &ca, .pDepthAttachment = &da};
|
||||
vkCmdBeginRendering(cb, &ri); // through the recorder (vk_rec.h), in order with the draws and the end
|
||||
mo_emit(cb);
|
||||
vkCmdEndRendering(cb);
|
||||
// the draws left their pipeline, descriptors, buffers and dynamic state bound: the next game draw sets everything
|
||||
vk_exec_draw_new_cb();
|
||||
ex->dirty = VK_EXEC_DIRTY_ALL;
|
||||
ex->recorded += MQ.n;
|
||||
MQ.n = MQ.nst = MQ.nvb = 0, MQ.ndesc = 0, MQ.frame = 0;
|
||||
MO.ran++;
|
||||
MO.ns_emit += kern_now_ns() - t0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void bind_index(VkExec *ex, const void *data, uint64_t bytes, VkIndexType type) {
|
||||
VkBuffer b;
|
||||
VkDeviceSize o;
|
||||
@@ -2288,6 +3065,7 @@ static void op_draw_idx(VkExec *ex, const p_draw_idx *d) {
|
||||
vkCmdDrawIndexed(ex->cb, (uint32_t)d->count, d->instances > 0 ? (uint32_t)d->instances : 1, 0, d->base_vertex,
|
||||
(uint32_t)d->base_instance);
|
||||
ex->recorded++;
|
||||
if (d->itype != NVN_INDEX_TYPE_UNSIGNED_BYTE) mvobj_draw(ex, d, sp.buffer, sp.offset);
|
||||
}
|
||||
|
||||
// DrawElementsIndirect: the arguments are read by the GPU from the pool buffer (they are often written by an earlier
|
||||
@@ -2495,3 +3273,6 @@ void vk_exec_op_draw(VkExec *ex, uint32_t op, const void *payload) {
|
||||
default: rt_fatal("vk: op %s is not a draw op", nvn_op_name(op));
|
||||
}
|
||||
}
|
||||
|
||||
// The last executed list's draws as " <position banks>/<changed banks>:<draws>" (bit n: cn), for TOTK_MV_CHECK's lines.
|
||||
const char *vk_mvobj_frame_masks(void) { return fm_last; }
|
||||
+78
-3
@@ -8,7 +8,8 @@
|
||||
// 8-bit sRGB window texture it presents. So:
|
||||
// 1. vk_hdr_shader_swap: the tonemap's fragment shader becomes shaders/hdr_tonemap.frag, which keeps values above 1
|
||||
// (relative to the HUD's white) and rolls off at the peak. It is recognised by its constants and resources, not by
|
||||
// a hash, so another game version or translator still finds it; its SPIR-V is replaced in memory only.
|
||||
// a hash, so another game version or translator still finds it; its SPIR-V is replaced in memory only. The game's
|
||||
// FSR 1 upscale after it would clamp the picture to 1 again; fsr_unclamp drops that clamp in its SPIR-V.
|
||||
// 2. vk_hdr_draw / vk_hdr_present_source: a draw into a presented window texture that samples one float texture of
|
||||
// its size is that copy; the present then reads the float texture instead of the window texture. Any other last
|
||||
// draw into it leaves the window texture, decoded from sRGB: menus and movies stay SDR at the HUD's white.
|
||||
@@ -50,7 +51,7 @@ void vk_hdr_off(void) { g_wanted = 0; }
|
||||
|
||||
// The three nits, read again when the settings change. The replay (every draw's support buffer) and the present
|
||||
// thread read them: an update fills the snapshot not in use and then publishes it.
|
||||
typedef struct { float peak, game, ui; } Nits;
|
||||
typedef struct { float peak, game, ui, hue; } Nits;
|
||||
static struct { unsigned seen; int cur; Nits snap[2]; PlatMutex lock; } N = {.seen = ~0u, .lock = PLAT_MUTEX_INIT};
|
||||
|
||||
static Nits nits(void) {
|
||||
@@ -62,6 +63,7 @@ static Nits nits(void) {
|
||||
n->peak = fmaxf((float)cfg_double("TOTK_HDR_PEAK_NITS"), 80.0f);
|
||||
n->game = fmaxf((float)cfg_double("TOTK_HDR_GAME_NITS"), 80.0f);
|
||||
n->ui = fmaxf((float)cfg_double("TOTK_HDR_UI_NITS"), 80.0f);
|
||||
n->hue = fminf(fmaxf((float)cfg_double("TOTK_HDR_HUE"), 0.0f), 100.0f) / 100.0f;
|
||||
__atomic_store_n(&N.cur, !N.cur, __ATOMIC_RELEASE);
|
||||
__atomic_store_n(&N.seen, v, __ATOMIC_RELEASE);
|
||||
}
|
||||
@@ -74,7 +76,8 @@ void vk_hdr_support(float out[4]) {
|
||||
Nits n = nits();
|
||||
out[0] = n.game / n.ui; // the picture's white against the HUD's
|
||||
out[1] = n.peak > n.game ? n.peak / n.game : 1.0f; // the peak against the picture's white
|
||||
out[2] = out[3] = 0;
|
||||
out[2] = n.hue; // hue correction of highlights (hdr_tonemap.frag)
|
||||
out[3] = 0;
|
||||
}
|
||||
|
||||
// ---- 1. the tonemap shader
|
||||
@@ -90,8 +93,80 @@ static int name_is(const uint32_t *a, uint32_t len, const char *s) { // OpName
|
||||
return (len - 2) * 4 >= n + 1 && !memcmp(a + 1, s, n + 1);
|
||||
}
|
||||
|
||||
// The game's FSR 1 pass after the tonemap (some frames take it in place of the plain pass, at 1:1 as well as when it
|
||||
// scales up; what picks it is not known yet) ends with clamp(colour, 0.0, 1.0) per channel, which cuts the HDR
|
||||
// highlights back to the HUD's white for as long as the game takes it. Its own 0..1 clamps of the filter weights each
|
||||
// feed a float bit trick (OpBitcast), so only the clamps whose result is stored to the shader's output change, to
|
||||
// max(x, 0.0): one word shorter, so the words after it move down and the module shrinks. Recognised by FSR's two
|
||||
// reciprocal constants (0x7ef07ebb, 0x7ef19fff) and its one texture fp_t_tcb_8. Returns the clamps changed, -1 for
|
||||
// that shader with no output clamp found (a translator that stores the colour another way), 0 for any other shader.
|
||||
enum { SPV_OP_EXT_INST_IMPORT = 11, SPV_OP_EXT_INST = 12, SPV_OP_VARIABLE = 59, SPV_OP_STORE = 62,
|
||||
SPV_OP_ACCESS_CHAIN = 65, SPV_SC_OUTPUT = 3, GLSL_FMAX = 40, GLSL_FCLAMP = 43 };
|
||||
|
||||
static int fsr_unclamp(uint32_t *w, uint64_t *words) {
|
||||
uint64_t n = *words;
|
||||
uint32_t bound = w[3], glsl = 0, float_type = 0, zero = 0, one = 0;
|
||||
int magic = 0, tex = 0;
|
||||
for (uint64_t i = 5; i < n;) {
|
||||
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
|
||||
if (!len || i + len > n) return 0;
|
||||
const uint32_t *a = w + i + 1;
|
||||
if (op == SPV_OP_EXT_INST_IMPORT && len >= 3 && name_is(a, len, "GLSL.std.450")) glsl = a[0];
|
||||
else if (op == SPV_OP_TYPE_FLOAT && len == 3 && a[1] == 32) float_type = a[0];
|
||||
else if (op == SPV_OP_CONSTANT && len == 4) {
|
||||
magic |= (a[2] == 0x7ef07ebbu) | (a[2] == 0x7ef19fffu) << 1;
|
||||
if (a[0] == float_type && float_type && a[2] == 0) zero = a[1];
|
||||
if (a[0] == float_type && float_type && a[2] == 0x3f800000u) one = a[1];
|
||||
} else if (op == SPV_OP_NAME && len >= 3 && name_is(a, len, "fp_t_tcb_8")) tex = 1;
|
||||
i += len;
|
||||
}
|
||||
if (magic != 3 || !tex) return 0;
|
||||
if (!glsl || !zero || !one || !bound) return -1;
|
||||
uint8_t *out = calloc(bound, 1); // id -> 1: a pointer into an Output variable, 2: a value stored through one
|
||||
if (!out) return 0;
|
||||
for (uint64_t i = 5; i < n; i += w[i] >> 16) {
|
||||
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
|
||||
const uint32_t *a = w + i + 1;
|
||||
if (op == SPV_OP_VARIABLE && len >= 4 && a[2] == SPV_SC_OUTPUT && a[1] < bound) out[a[1]] = 1;
|
||||
else if (op == SPV_OP_ACCESS_CHAIN && len >= 4 && a[2] < bound && out[a[2]] == 1 && a[1] < bound) out[a[1]] = 1;
|
||||
else if (op == SPV_OP_STORE && len >= 3 && a[0] < bound && out[a[0]] == 1 && a[1] < bound) out[a[1]] = 2;
|
||||
}
|
||||
int changed = 0;
|
||||
uint64_t k = 5; // the write position: never past the read position, as instructions only shrink
|
||||
for (uint64_t i = 5; i < n;) {
|
||||
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
|
||||
const uint32_t *a = w + i + 1; // result type, result, set, instruction, x, min, max
|
||||
if (op == SPV_OP_EXT_INST && len == 8 && a[2] == glsl && a[3] == GLSL_FCLAMP && a[1] < bound && out[a[1]] == 2 &&
|
||||
a[5] == zero && a[6] == one) {
|
||||
uint32_t ins[7] = {7u << 16 | SPV_OP_EXT_INST, a[0], a[1], a[2], GLSL_FMAX, a[4], zero};
|
||||
memmove(w + k, ins, sizeof ins);
|
||||
k += 7, changed++;
|
||||
} else {
|
||||
memmove(w + k, w + i, len * sizeof *w);
|
||||
k += len;
|
||||
}
|
||||
i += len;
|
||||
}
|
||||
free(out);
|
||||
*words = k;
|
||||
return changed ? changed : -1;
|
||||
}
|
||||
|
||||
void vk_hdr_shader_swap(uint32_t **spirv, uint64_t *words) {
|
||||
if (!vk_hdr_wanted() || !*spirv || *words < 5) return;
|
||||
int unclamped = fsr_unclamp(*spirv, words);
|
||||
if (unclamped > 0) {
|
||||
static volatile int32_t fsrs;
|
||||
rt_log("hdr: the game's FSR pass keeps values above 1 (%d output clamps; %d so far)", unclamped,
|
||||
plat_inc32(&fsrs));
|
||||
return;
|
||||
}
|
||||
if (unclamped < 0) {
|
||||
static volatile int32_t missed;
|
||||
if (plat_inc32(&missed) == 1)
|
||||
rt_log("hdr: the game's FSR pass was found but no clamp of its output; it still clips HDR highlights");
|
||||
return;
|
||||
}
|
||||
const uint32_t *w = *spirv;
|
||||
uint64_t n = *words;
|
||||
// the game's constants: the BT.601 red weight, -1/ln 2 (e^-x as exp2), 2/3 (the grade's red weight), the bloom clamp
|
||||
|
||||
@@ -1201,6 +1201,11 @@ static void mirror_mode(int discrete) {
|
||||
sh && *sh ? " (TOTK_SHADOW)" : "");
|
||||
rt_log("mem: mode %s (%s): %s", mode == 2 ? "verify" : mode ? "mirror" : "import", why,
|
||||
mode ? "pools used as buffers get a device-local mirror" : "every CPU-visible pool is imported host memory");
|
||||
// Import is the integrated-GPU mode. On a discrete card every draw reads the pools over PCIe (the comment above
|
||||
// this section has the measurements), which is the difference between playable and 5 fps when it is not intended.
|
||||
if (!mode && discrete)
|
||||
rt_log("mem: warning: import on a discrete GPU: the GPU reads the game's pools over PCIe on every draw, far "
|
||||
"slower than mirror (the auto default on a discrete GPU); use memory = auto unless testing");
|
||||
}
|
||||
|
||||
static void range_add(MRange **a, int *n, int *cap, PoolRec *r, uint64_t off, uint64_t size) {
|
||||
|
||||
@@ -0,0 +1,474 @@
|
||||
// TOTK_MV_CHECK=<frames> (test): measures how well DLSS's motion vectors explain the picture, without screenshots or
|
||||
// judging by eye. Every <frames> DLAA frames, vk_dlaa.c mv_pass copies to host memory the scene light target of that
|
||||
// frame and of the frame before (before DLSS, so jittered), and the motion image twice: after the camera pass (camera
|
||||
// only) and after the per-object pass (what DLSS gets). Once the GPU is done, the completion thread compares them:
|
||||
// warp error: the frame before, sampled bilinearly at p + mv + d, against this frame at p, as a relative luminance
|
||||
// difference |a - b| / (a + b) averaged over pixels; d = last frame's jitter minus this frame's (the vectors are
|
||||
// between unjittered positions, the pictures are jittered: content at x is drawn at x + jitter). Lower is better,
|
||||
// and the correct vectors give the lowest. Over every pixel: zero motion, the camera's and the final vectors, and
|
||||
// the final ones with d subtracted or left out (the jitter convention). Over the object pixels (final and camera
|
||||
// differ by more than a quarter pixel): the camera's, the final ones and variants of the object part (final -
|
||||
// camera) scaled by k = -1, 0.5, 1.5, 2 or with one axis negated. The variant with the lowest error names the
|
||||
// correction the object vectors need, if any ("best").
|
||||
// flow: on up to FLOW_MAX object pixels with some texture, the offset whose 5x5 luminance patch of the frame before
|
||||
// matches best, searched over a window around both the camera and the final vector and refined to a sub-pixel by
|
||||
// a parabola: the measured motion. The end-point error of the camera and final vectors (+ d) against it, median in
|
||||
// render pixels, and how often the final vector is the nearer.
|
||||
// output (DLSS's picture, which the vectors are for): on about OUT_SAMPLES pixels picked independently of the vectors,
|
||||
// the picture's own motion is measured with a wide search (12 px around the camera vector); where it differs from
|
||||
// the camera's by over 1.5 px the pixel moves on its own. At each sample, DLSS's output (at the same unjittered
|
||||
// place) against this frame's input, as a relative luminance difference: on the moving samples it grows where
|
||||
// DLSS drags old colour along wrong vectors (ghosting, smearing), on the static ones it is the plain difference
|
||||
// anti-aliasing and upscaling make. Their ratio compares runs with the per-object pass on and off (it does not
|
||||
// depend on the pass's own vectors) and modes.
|
||||
// One "mv_check:" line per check. Disocclusion and changing light add error to every candidate alike, so the comparison
|
||||
// between candidates counts, not the absolute values. Copies and analysis cost a few ms on check frames only.
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "host.h"
|
||||
#include "nvn_api.h"
|
||||
#include "vk.h"
|
||||
#include "vk_exec.h"
|
||||
#include "vk_dlaa.h"
|
||||
|
||||
#define FLOW_MAX 20000
|
||||
#define OBJ_MIN 0.25f // final and camera vectors differ by more than this (render pixels): an object pixel
|
||||
|
||||
#define OUT_SAMPLES 4000
|
||||
enum { PREV, CUR, MV_CAM, MV_FIN, OUT, NBUF };
|
||||
static struct {
|
||||
int period; // 0 off, -1 not read yet
|
||||
VkBuffer buf[NBUF];
|
||||
VmaAllocation mem[NBUF];
|
||||
uint8_t *map[NBUF];
|
||||
uint64_t cap[NBUF];
|
||||
uint32_t w, h, ow, oh; // ow, oh: DLSS's output in OUT, 0 none this check
|
||||
int want_out; // the check frame's output is to be copied (vk_mvcheck_output)
|
||||
char masks[400]; // the check frame's motion draws (vk_mvobj_frame_masks)
|
||||
VkFormat cfmt;
|
||||
uint64_t n; // camera-stage calls (DLAA frames with motion)
|
||||
uint64_t prev_frame; // gfx frame the PREV colour is of, 0 none
|
||||
float prev_u[2], u[2], uprev[2];
|
||||
int check; // this frame is a check frame (camera stage copied MV_CAM)
|
||||
int busy; // analysis pending: the buffers are not touched
|
||||
uint64_t done;
|
||||
} K = {.period = -1};
|
||||
|
||||
static int on(void) {
|
||||
if (K.period < 0) {
|
||||
const char *v = cfg_get("TOTK_MV_CHECK");
|
||||
K.period = v && *v ? atoi(v) : 0;
|
||||
if (K.period < 0) K.period = 0;
|
||||
if (K.period && K.period < 4) K.period = 4;
|
||||
if (K.period) rt_log("mv_check: motion vectors measured every %d frames", K.period);
|
||||
}
|
||||
return K.period > 0;
|
||||
}
|
||||
|
||||
static int cbytes(VkFormat f) {
|
||||
return f == VK_FORMAT_B10G11R11_UFLOAT_PACK32 ? 4 : f == VK_FORMAT_R16G16B16A16_SFLOAT ? 8 : 0;
|
||||
}
|
||||
|
||||
// Host buffers of at least the size asked for; a larger size gets new ones and leaves the old (test only).
|
||||
static int ensure(int i, uint64_t bytes) {
|
||||
if (K.cap[i] >= bytes) return 1;
|
||||
VkBufferCreateInfo bi = {.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = bytes,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT};
|
||||
VmaAllocationCreateInfo ai = {.usage = VMA_MEMORY_USAGE_AUTO,
|
||||
.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT};
|
||||
VmaAllocationInfo info;
|
||||
if (vmaCreateBuffer(vk_ctx()->vma, &bi, &ai, &K.buf[i], &K.mem[i], &info) != VK_SUCCESS || !info.pMappedData) {
|
||||
rt_log("mv_check: host buffer of %llu bytes failed: off", (unsigned long long)bytes);
|
||||
K.period = 0;
|
||||
return 0;
|
||||
}
|
||||
K.map[i] = info.pMappedData, K.cap[i] = bytes;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void barrier(VkCommandBuffer cb) {
|
||||
VkMemoryBarrier2 m = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_2_HOST_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT |
|
||||
VK_ACCESS_2_HOST_READ_BIT};
|
||||
VkDependencyInfo di = {.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .memoryBarrierCount = 1, .pMemoryBarriers = &m};
|
||||
vkCmdPipelineBarrier2(cb, &di);
|
||||
}
|
||||
|
||||
static void copy(VkCommandBuffer cb, int i, VkImage img, uint32_t level, uint32_t layer, uint32_t w, uint32_t h) {
|
||||
barrier(cb);
|
||||
VkBufferImageCopy r = {.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1}, .imageExtent = {w, h, 1}};
|
||||
vkCmdCopyImageToBuffer(cb, img, VK_IMAGE_LAYOUT_GENERAL, K.buf[i], 1, &r);
|
||||
barrier(cb);
|
||||
}
|
||||
|
||||
// ---- decoding
|
||||
|
||||
static float half(uint16_t v) {
|
||||
uint32_t s = (uint32_t)(v >> 15) << 31, e = v >> 10 & 31, m = v & 1023, f;
|
||||
if (!e) return (s ? -1.0f : 1.0f) * ldexpf((float)m, -24);
|
||||
f = e == 31 ? s | 0x7f800000u | m << 13 : s | (e + 112) << 23 | m << 13;
|
||||
float r;
|
||||
memcpy(&r, &f, 4);
|
||||
return r;
|
||||
}
|
||||
|
||||
static float ufloat(uint32_t v, int mbits) { // unsigned 5-bit exponent mini-float
|
||||
uint32_t e = v >> mbits, m = v & ((1u << mbits) - 1);
|
||||
if (e == 31) return 65504.0f;
|
||||
return e ? ldexpf(1.0f + (float)m / (float)(1u << mbits), (int)e - 15) : ldexpf((float)m / (float)(1u << mbits), -14);
|
||||
}
|
||||
|
||||
static void luminance(const uint8_t *src, VkFormat f, uint32_t n, float *out) {
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
float r, g, b;
|
||||
if (f == VK_FORMAT_B10G11R11_UFLOAT_PACK32) {
|
||||
uint32_t v;
|
||||
memcpy(&v, src + 4 * (size_t)i, 4);
|
||||
r = ufloat(v & 0x7ff, 6), g = ufloat(v >> 11 & 0x7ff, 6), b = ufloat(v >> 22, 5);
|
||||
} else {
|
||||
uint16_t c[4];
|
||||
memcpy(c, src + 8 * (size_t)i, 8);
|
||||
r = half(c[0]), g = half(c[1]), b = half(c[2]);
|
||||
}
|
||||
float y = 0.2126f * r + 0.7152f * g + 0.0722f * b;
|
||||
out[i] = isfinite(y) && y > 0 ? y : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- analysis
|
||||
|
||||
typedef struct { const float *cur, *prev; uint32_t w, h; } Pic;
|
||||
|
||||
static int sample(const Pic *p, float x, float y, float *out) { // bilinear, pixel index coordinates
|
||||
if (!(x >= 0 && y >= 0 && x <= (float)(p->w - 1) && y <= (float)(p->h - 1))) return 0;
|
||||
uint32_t x0 = (uint32_t)x, y0 = (uint32_t)y, x1 = x0 + 1 < p->w ? x0 + 1 : x0, y1 = y0 + 1 < p->h ? y0 + 1 : y0;
|
||||
float fx = x - (float)x0, fy = y - (float)y0;
|
||||
const float *a = p->prev + (size_t)y0 * p->w, *b = p->prev + (size_t)y1 * p->w;
|
||||
*out = (a[x0] * (1 - fx) + a[x1] * fx) * (1 - fy) + (b[x0] * (1 - fx) + b[x1] * fx) * fy;
|
||||
return 1;
|
||||
}
|
||||
|
||||
typedef struct { double sum; uint64_t n; } Acc;
|
||||
static void acc(Acc *a, const Pic *p, uint32_t x, uint32_t y, float mx, float my) {
|
||||
float b;
|
||||
if (!sample(p, (float)x + mx, (float)y + my, &b)) return;
|
||||
float c = p->cur[(size_t)y * p->w + x];
|
||||
a->sum += fabsf(c - b) / (c + b + 1e-4f), a->n++;
|
||||
}
|
||||
static double mean(const Acc *a) { return a->n ? a->sum / (double)a->n : NAN; }
|
||||
|
||||
static float sad5(const Pic *p, uint32_t x, uint32_t y, int dx, int dy) {
|
||||
float s = 0;
|
||||
for (int j = -2; j <= 2; j++)
|
||||
for (int i = -2; i <= 2; i++) {
|
||||
int px = (int)x + i + dx, py = (int)y + j + dy;
|
||||
if (px < 0 || py < 0 || px >= (int)p->w || py >= (int)p->h) return INFINITY;
|
||||
float c = p->cur[(size_t)(y + j) * p->w + x + i], b = p->prev[(size_t)py * p->w + px];
|
||||
s += fabsf(c - b) / (c + b + 1e-4f);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static float parabola(float l, float c, float r) { // offset of the minimum of three samples at -1, 0, 1
|
||||
float den = l - 2 * c + r;
|
||||
if (!(den > 1e-9f) || !isfinite(l) || !isfinite(r)) return 0;
|
||||
float o = 0.5f * (l - r) / den;
|
||||
return o < -0.5f ? -0.5f : o > 0.5f ? 0.5f : o; // a flat curve says nothing past the neighbours
|
||||
}
|
||||
|
||||
static int by_float(const void *a, const void *b) {
|
||||
float x = *(const float *)a, y = *(const float *)b;
|
||||
return x < y ? -1 : x > y;
|
||||
}
|
||||
|
||||
// The object pixels' measured motion (sub-pixel), searched over the box around both vectors with a margin of 3;
|
||||
// 0 when the patch is flat or the match is not clearly better than the box's average.
|
||||
static int flow(const Pic *p, uint32_t x, uint32_t y, float ax, float ay, float bx, float by, float out[2]) {
|
||||
float mu = 0, var = 0;
|
||||
for (int j = -2; j <= 2; j++)
|
||||
for (int i = -2; i <= 2; i++) mu += p->cur[(size_t)(y + j) * p->w + x + i];
|
||||
mu /= 25;
|
||||
for (int j = -2; j <= 2; j++)
|
||||
for (int i = -2; i <= 2; i++) {
|
||||
float d = p->cur[(size_t)(y + j) * p->w + x + i] - mu;
|
||||
var += d * d;
|
||||
}
|
||||
if (mu <= 0 || sqrtf(var / 25) < 0.05f * mu) return 0;
|
||||
int x0 = (int)floorf(fminf(ax, bx)) - 3, x1 = (int)ceilf(fmaxf(ax, bx)) + 3;
|
||||
int y0 = (int)floorf(fminf(ay, by)) - 3, y1 = (int)ceilf(fmaxf(ay, by)) + 3;
|
||||
if (x1 - x0 > 40 || y1 - y0 > 40) return 0;
|
||||
float best = INFINITY, total = 0;
|
||||
int bdx = 0, bdy = 0, n = 0;
|
||||
for (int dy = y0; dy <= y1; dy++)
|
||||
for (int dx = x0; dx <= x1; dx++) {
|
||||
float s = sad5(p, x, y, dx, dy);
|
||||
if (!isfinite(s)) continue;
|
||||
total += s, n++;
|
||||
if (s < best) best = s, bdx = dx, bdy = dy;
|
||||
}
|
||||
if (!n || !isfinite(best) || best > 0.5f * total / (float)n) return 0;
|
||||
out[0] = (float)bdx + parabola(sad5(p, x, y, bdx - 1, bdy), best, sad5(p, x, y, bdx + 1, bdy));
|
||||
out[1] = (float)bdy + parabola(sad5(p, x, y, bdx, bdy - 1), best, sad5(p, x, y, bdx, bdy + 1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Totals over every check (logged every TOTAL_EVERY checks): end-point error histograms in HBIN px bins of the object
|
||||
// samples (camera, final) and of the static samples (the measurement's own noise floor), and the object warp errors.
|
||||
#define HBIN 0.05f
|
||||
#define HN 640
|
||||
#define TOTAL_EVERY 20
|
||||
#define KN 120 // histogram of per-sample scale ratios, -2 to 4 in steps of 0.05
|
||||
enum { H_CAM, H_FIN, H_STATIC, NH };
|
||||
static struct { uint64_t h[NH][HN], n[NH], checks, wrong, kh[KN], kn; double obj[2], objn, knum, kden, out[2], outn[2]; uint64_t cal[3]; } T; // cal: checks where k=0.5, k=1 fit the static pixels best; checks
|
||||
|
||||
static void hist_add(int k, float e) {
|
||||
int b = e >= 0 && e < HBIN * HN ? (int)(e / HBIN) : HN - 1; // NaN and huge errors in the last bin
|
||||
T.h[k][b < HN ? b : HN - 1]++, T.n[k]++;
|
||||
}
|
||||
static float hist_q(int k, double q) { // quantile q of histogram k, bin centre
|
||||
uint64_t want = (uint64_t)(q * (double)T.n[k]), s = 0;
|
||||
for (int b = 0; b < HN; b++)
|
||||
if ((s += T.h[k][b]) > want) return ((float)b + 0.5f) * HBIN;
|
||||
return NAN;
|
||||
}
|
||||
|
||||
static float kmedian(void) {
|
||||
uint64_t s = 0;
|
||||
for (int b = 0; b < KN; b++)
|
||||
if ((s += T.kh[b]) * 2 > T.kn) return -2 + ((float)b + 0.5f) * 0.05f;
|
||||
return NAN;
|
||||
}
|
||||
|
||||
static uint32_t mix(uint32_t x, uint32_t y) { // a fixed pseudo-random pick of pixels, spread over the picture
|
||||
uint32_t v = x * 0x9E3779B1u ^ y * 0x85EBCA77u;
|
||||
v ^= v >> 15, v *= 0x2C1B3C6Du, v ^= v >> 12;
|
||||
return v;
|
||||
}
|
||||
|
||||
static void analyse(void *arg) {
|
||||
(void)arg;
|
||||
VmaAllocator vma = vk_ctx()->vma;
|
||||
for (int i = 0; i < NBUF; i++) vmaInvalidateAllocation(vma, K.mem[i], 0, VK_WHOLE_SIZE);
|
||||
uint32_t w = K.w, h = K.h, n = w * h;
|
||||
float *cur = malloc(sizeof(float) * n), *prev = malloc(sizeof(float) * n), *e_cam = NULL, *e_fin = NULL,
|
||||
*e_st = NULL;
|
||||
if (!cur || !prev) goto out;
|
||||
luminance(K.map[CUR], K.cfmt, n, cur);
|
||||
luminance(K.map[PREV], K.cfmt, n, prev);
|
||||
Pic p = {cur, prev, w, h};
|
||||
const uint16_t *A = (const uint16_t *)K.map[MV_CAM], *B = (const uint16_t *)K.map[MV_FIN];
|
||||
float dx = K.uprev[0] - K.u[0], dy = K.uprev[1] - K.u[1];
|
||||
enum { ZERO, CAM, FIN, FIN_NEG_D, FIN_NO_D, NALL };
|
||||
enum { O_CAM, O_FIN, O_KM1, O_K05, O_K15, O_K2, O_NX, O_NY, NOBJ };
|
||||
static const char *const oname[NOBJ] = {"camera", "final", "k=-1", "k=0.5", "k=1.5", "k=2", "-x", "-y"};
|
||||
Acc all[NALL] = {0}, obj[NOBJ] = {0}, cal[3] = {0};
|
||||
// the object pixels first, for the sampling rates: about FLOW_MAX object and FLOW_MAX / 4 static samples
|
||||
uint64_t nobj = 0, huge = 0; // huge: camera vectors over 256 px or not finite
|
||||
double cam_len = 0;
|
||||
for (uint32_t y = 2; y + 2 < h; y++)
|
||||
for (uint32_t x = 2; x + 2 < w; x++) {
|
||||
size_t i = (size_t)y * w + x;
|
||||
float ax = half(A[2 * i]), ay = half(A[2 * i + 1]), bx = half(B[2 * i]), by = half(B[2 * i + 1]);
|
||||
if (isfinite(ax) && isfinite(ay)) cam_len += hypotf(ax, ay);
|
||||
huge += !isfinite(ax) || !isfinite(ay) || hypotf(ax, ay) > 256;
|
||||
nobj += fabsf(bx - ax) > OBJ_MIN || fabsf(by - ay) > OBJ_MIN;
|
||||
}
|
||||
uint32_t rate_obj = nobj > FLOW_MAX ? (uint32_t)(0xffffffffull * FLOW_MAX / nobj) : 0xffffffffu;
|
||||
uint64_t nst = (uint64_t)n - nobj;
|
||||
uint32_t rate_st = nst > FLOW_MAX / 4 ? (uint32_t)(0xffffffffull * (FLOW_MAX / 4) / nst) : 0xffffffffu;
|
||||
e_cam = malloc(sizeof(float) * FLOW_MAX * 2), e_fin = malloc(sizeof(float) * FLOW_MAX * 2),
|
||||
e_st = malloc(sizeof(float) * FLOW_MAX);
|
||||
if (!e_cam || !e_fin || !e_st) goto out;
|
||||
int nflow = 0, nearer = 0, tried = 0, nst_f = 0;
|
||||
double knum = 0, kden = 0;
|
||||
// object samples the final vector misses by more than 1.5 px where the camera's fits better: where and how
|
||||
struct { int n; uint32_t x0, y0, x1, y1; double ox, oy, mx, my; } bad = {0, UINT32_MAX, UINT32_MAX, 0, 0, 0, 0, 0, 0};
|
||||
for (uint32_t y = 2; y + 2 < h; y++)
|
||||
for (uint32_t x = 2; x + 2 < w; x++) {
|
||||
size_t i = (size_t)y * w + x;
|
||||
float ax = half(A[2 * i]), ay = half(A[2 * i + 1]), bx = half(B[2 * i]), by = half(B[2 * i + 1]);
|
||||
if (!isfinite(ax) || !isfinite(ay) || !isfinite(bx) || !isfinite(by)) continue;
|
||||
acc(&all[ZERO], &p, x, y, dx, dy);
|
||||
acc(&all[CAM], &p, x, y, ax + dx, ay + dy);
|
||||
acc(&all[FIN], &p, x, y, bx + dx, by + dy);
|
||||
acc(&all[FIN_NEG_D], &p, x, y, bx - dx, by - dy);
|
||||
acc(&all[FIN_NO_D], &p, x, y, bx, by);
|
||||
float ox = bx - ax, oy = by - ay, f[2];
|
||||
if (fabsf(ox) <= OBJ_MIN && fabsf(oy) <= OBJ_MIN) {
|
||||
// calibration: static pixels with a small camera vector (known right), at k = 0.5, 1, 1.5 of it
|
||||
float al = hypotf(ax, ay);
|
||||
if (al > 0.25f && al < 2) {
|
||||
acc(&cal[0], &p, x, y, 0.5f * ax + dx, 0.5f * ay + dy), acc(&cal[1], &p, x, y, ax + dx, ay + dy);
|
||||
acc(&cal[2], &p, x, y, 1.5f * ax + dx, 1.5f * ay + dy);
|
||||
}
|
||||
if (mix(x, y) > rate_st || nst_f >= FLOW_MAX) continue;
|
||||
if (flow(&p, x, y, ax + dx, ay + dy, ax + dx, ay + dy, f)) e_st[nst_f++] = hypotf(ax + dx - f[0], ay + dy - f[1]);
|
||||
continue;
|
||||
}
|
||||
acc(&obj[O_CAM], &p, x, y, ax + dx, ay + dy);
|
||||
acc(&obj[O_FIN], &p, x, y, bx + dx, by + dy);
|
||||
acc(&obj[O_KM1], &p, x, y, ax - ox + dx, ay - oy + dy);
|
||||
acc(&obj[O_K05], &p, x, y, ax + 0.5f * ox + dx, ay + 0.5f * oy + dy);
|
||||
acc(&obj[O_K15], &p, x, y, ax + 1.5f * ox + dx, ay + 1.5f * oy + dy);
|
||||
acc(&obj[O_K2], &p, x, y, ax + 2 * ox + dx, ay + 2 * oy + dy);
|
||||
acc(&obj[O_NX], &p, x, y, ax - ox + dx, by + dy);
|
||||
acc(&obj[O_NY], &p, x, y, bx + dx, ay - oy + dy);
|
||||
if (mix(x, y) > rate_obj || nflow >= 2 * FLOW_MAX) continue;
|
||||
tried++;
|
||||
if (!flow(&p, x, y, ax + dx, ay + dy, bx + dx, by + dy, f)) continue;
|
||||
float ec = hypotf(ax + dx - f[0], ay + dy - f[1]), ef = hypotf(bx + dx - f[0], by + dy - f[1]);
|
||||
e_cam[nflow] = ec, e_fin[nflow] = ef, nflow++;
|
||||
nearer += ef < ec;
|
||||
if (fabsf(ox) > 0.5f || fabsf(oy) > 0.5f) { // the scale of the object part the measured motion shows
|
||||
float mx = f[0] - ax - dx, my = f[1] - ay - dy, oo = ox * ox + oy * oy, kr = (mx * ox + my * oy) / oo;
|
||||
knum += mx * ox + my * oy, kden += oo;
|
||||
int kb = (int)floorf((kr + 2) / 0.05f);
|
||||
T.kh[kb < 0 ? 0 : kb >= KN ? KN - 1 : kb]++, T.kn++;
|
||||
}
|
||||
if (ef > 1.5f && ef > ec) {
|
||||
bad.n++, bad.ox += ox, bad.oy += oy, bad.mx += f[0] - ax - dx, bad.my += f[1] - ay - dy;
|
||||
if (x < bad.x0) bad.x0 = x;
|
||||
if (y < bad.y0) bad.y0 = y;
|
||||
if (x > bad.x1) bad.x1 = x;
|
||||
if (y > bad.y1) bad.y1 = y;
|
||||
}
|
||||
}
|
||||
int best = 0;
|
||||
for (int k = 1; k < NOBJ; k++)
|
||||
if (obj[k].n && (!obj[best].n || mean(&obj[k]) < mean(&obj[best]))) best = k;
|
||||
char line[512];
|
||||
int o = snprintf(line, sizeof line, "object pixels %.1f%% (%llu):", 100.0 * (double)nobj / (double)n,
|
||||
(unsigned long long)nobj);
|
||||
for (int k = 0; k < NOBJ && o < (int)sizeof line; k++) o += snprintf(line + o, sizeof line - o, " %s %.4f", oname[k], mean(&obj[k]));
|
||||
if (o < (int)sizeof line) snprintf(line + o, sizeof line - o, "; best %s", nobj ? oname[best] : "-");
|
||||
// DLSS's output against this frame's input, on samples moving on their own and static ones (the picture's motion)
|
||||
char outl[200] = "";
|
||||
if (K.ow && K.oh) {
|
||||
float *ol = malloc(sizeof(float) * (size_t)K.ow * K.oh);
|
||||
if (ol) {
|
||||
luminance(K.map[OUT], VK_FORMAT_R16G16B16A16_SFLOAT, K.ow * K.oh, ol);
|
||||
Pic op = {NULL, ol, K.ow, K.oh}; // sample() reads prev
|
||||
float sx = (float)K.ow / (float)w, sy = (float)K.oh / (float)h;
|
||||
uint32_t rate = (uint32_t)(0xffffffffull * OUT_SAMPLES / n);
|
||||
Acc mov = {0}, sta = {0};
|
||||
for (uint32_t y = 2; y + 2 < h; y++)
|
||||
for (uint32_t x = 2; x + 2 < w; x++) {
|
||||
if ((mix(x, y) ^ 0x5bd1e995u) * 0x2C1B3C6Du > rate) continue;
|
||||
size_t i = (size_t)y * w + x;
|
||||
float ax = half(A[2 * i]) + dx, ay = half(A[2 * i + 1]) + dy, f[2], ov;
|
||||
if (!isfinite(ax) || !isfinite(ay) || !flow(&p, x, y, ax - 12, ay - 12, ax + 12, ay + 12, f)) continue;
|
||||
if (!sample(&op, ((float)x + 0.5f - K.u[0]) * sx - 0.5f, ((float)y + 0.5f - K.u[1]) * sy - 0.5f, &ov))
|
||||
continue;
|
||||
float c = cur[i], e = fabsf(ov - c) / (ov + c + 1e-4f);
|
||||
Acc *a = hypotf(f[0] - ax, f[1] - ay) > 1.5f ? &mov : &sta;
|
||||
a->sum += e, a->n++;
|
||||
}
|
||||
snprintf(outl, sizeof outl, "; output against input: moving on their own %.4f (%llu), static %.4f (%llu), "
|
||||
"ratio %.3f", mean(&mov), (unsigned long long)mov.n, mean(&sta), (unsigned long long)sta.n,
|
||||
mean(&mov) / mean(&sta));
|
||||
if (mov.n) T.out[0] += mov.sum, T.outn[0] += (double)mov.n;
|
||||
if (sta.n) T.out[1] += sta.sum, T.outn[1] += (double)sta.n;
|
||||
free(ol);
|
||||
}
|
||||
}
|
||||
float mc = NAN, mf = NAN, ms = NAN;
|
||||
for (int k = 0; k < nflow; k++) hist_add(H_CAM, e_cam[k]), hist_add(H_FIN, e_fin[k]);
|
||||
for (int k = 0; k < nst_f; k++) hist_add(H_STATIC, e_st[k]);
|
||||
if (nflow) {
|
||||
qsort(e_cam, (size_t)nflow, sizeof *e_cam, by_float), qsort(e_fin, (size_t)nflow, sizeof *e_fin, by_float);
|
||||
mc = e_cam[nflow / 2], mf = e_fin[nflow / 2];
|
||||
}
|
||||
if (nst_f) qsort(e_st, (size_t)nst_f, sizeof *e_st, by_float), ms = e_st[nst_f / 2];
|
||||
char badl[200] = "";
|
||||
if (bad.n)
|
||||
snprintf(badl, sizeof badl, "; %d missed (over 1.5 px, camera nearer) in x %u-%u y %u-%u, object part (%.2f %.2f), "
|
||||
"measured (%.2f %.2f)", bad.n, bad.x0, bad.x1, bad.y0, bad.y1, bad.ox / bad.n, bad.oy / bad.n,
|
||||
bad.mx / bad.n, bad.my / bad.n);
|
||||
rt_log("mv_check: #%llu %ux%u, jitter (%.2f %.2f) after (%.2f %.2f), camera motion %.2f px; warp error, every pixel: "
|
||||
"zero %.4f camera %.4f final %.4f (jitter subtracted %.4f, left out %.4f); camera vectors over 256 px %llu; %s; "
|
||||
"flow on %d of %d object "
|
||||
"samples: median error camera %.2f px, final %.2f px, final nearer %.0f%%; static %.2f px (%d); scale %.2f; calibration, static pixels under 2 px of camera motion (%llu): k=0.5 %.4f k=1 %.4f "
|
||||
"k=1.5 %.4f%s%s; motion draws (position banks/changed banks:draws)%s",
|
||||
(unsigned long long)++K.done, w, h, K.u[0], K.u[1], K.uprev[0], K.uprev[1], cam_len / (double)n,
|
||||
mean(&all[ZERO]), mean(&all[CAM]), mean(&all[FIN]), mean(&all[FIN_NEG_D]), mean(&all[FIN_NO_D]), (unsigned long long)huge, line,
|
||||
nflow, tried, mc, mf, nflow ? 100.0 * nearer / nflow : 0.0, ms, nst_f, kden > 0 ? knum / kden : NAN, (unsigned long long)cal[1].n, mean(&cal[0]), mean(&cal[1]), mean(&cal[2]), badl, outl, K.masks);
|
||||
if (cal[1].n > 1000) T.cal[0] += mean(&cal[0]) < mean(&cal[1]) && mean(&cal[0]) < mean(&cal[2]), T.cal[1] += mean(&cal[1]) <= mean(&cal[0]) && mean(&cal[1]) <= mean(&cal[2]), T.cal[2]++;
|
||||
T.knum += knum, T.kden += kden;
|
||||
T.checks++;
|
||||
if (obj[O_FIN].n) {
|
||||
T.obj[0] += obj[O_CAM].sum, T.obj[1] += obj[O_FIN].sum, T.objn += (double)obj[O_FIN].n;
|
||||
T.wrong += nobj * 200 > n && best != O_FIN; // over 0.5 % of the picture and the vectors are not the best fit
|
||||
}
|
||||
if (T.checks % TOTAL_EVERY == 0)
|
||||
rt_log("mv_check total: %llu checks, %llu with over 0.5%% object pixels fit better by a variant; object warp error "
|
||||
"camera %.4f final %.4f; end-point error (median / 90%% / 99%%) object samples camera %.2f / %.2f / %.2f "
|
||||
"px, final %.2f / %.2f / %.2f px (%llu); static samples %.2f / %.2f / %.2f px (%llu); object scale %.3f (median %.2f of %llu); calibration: k=0.5 best in %llu, k=1 in %llu of %llu checks; "
|
||||
"output against input: moving on their own %.4f (%.0f), static %.4f (%.0f), ratio %.3f",
|
||||
(unsigned long long)T.checks, (unsigned long long)T.wrong, T.objn ? T.obj[0] / T.objn : NAN,
|
||||
T.objn ? T.obj[1] / T.objn : NAN, hist_q(H_CAM, 0.5), hist_q(H_CAM, 0.9), hist_q(H_CAM, 0.99),
|
||||
hist_q(H_FIN, 0.5), hist_q(H_FIN, 0.9), hist_q(H_FIN, 0.99), (unsigned long long)T.n[H_FIN],
|
||||
hist_q(H_STATIC, 0.5), hist_q(H_STATIC, 0.9), hist_q(H_STATIC, 0.99), (unsigned long long)T.n[H_STATIC],
|
||||
T.kden > 0 ? T.knum / T.kden : NAN, kmedian(), (unsigned long long)T.kn, (unsigned long long)T.cal[0], (unsigned long long)T.cal[1],
|
||||
(unsigned long long)T.cal[2], T.outn[0] ? T.out[0] / T.outn[0] : NAN, T.outn[0],
|
||||
T.outn[1] ? T.out[1] / T.outn[1] : NAN, T.outn[1],
|
||||
T.outn[0] && T.outn[1] ? (T.out[0] / T.outn[0]) / (T.out[1] / T.outn[1]) : NAN);
|
||||
out:
|
||||
free(cur), free(prev), free(e_cam), free(e_fin), free(e_st);
|
||||
__atomic_store_n(&K.busy, 0, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
// ---- capture (vk_dlaa.c mv_pass)
|
||||
|
||||
void vk_mvcheck_camera(VkCommandBuffer cb, VkImage mv, uint32_t w, uint32_t h, float jx, float jy) {
|
||||
K.check = 0;
|
||||
if (!on()) return;
|
||||
K.n++;
|
||||
uint64_t frame = __atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED);
|
||||
if (K.n % (uint64_t)K.period || __atomic_load_n(&K.busy, __ATOMIC_ACQUIRE) || K.prev_frame + 1 != frame ||
|
||||
K.w != w || K.h != h)
|
||||
return;
|
||||
if (!ensure(MV_CAM, (uint64_t)w * h * 4) || !ensure(MV_FIN, (uint64_t)w * h * 4)) return;
|
||||
copy(cb, MV_CAM, mv, 0, 0, w, h);
|
||||
K.check = 1, K.u[0] = jx, K.u[1] = jy, K.uprev[0] = K.prev_u[0], K.uprev[1] = K.prev_u[1];
|
||||
}
|
||||
|
||||
void vk_mvcheck_final(VkCommandBuffer cb, uint64_t fence, VkImage mv, VkImage cimg, VkFormat cfmt, uint32_t level,
|
||||
uint32_t layer, uint32_t w, uint32_t h, float jx, float jy) {
|
||||
if (!on()) return;
|
||||
uint64_t frame = __atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED);
|
||||
int b = cbytes(cfmt);
|
||||
int want_prev = (K.n + 1) % (uint64_t)K.period == 0 && !__atomic_load_n(&K.busy, __ATOMIC_ACQUIRE);
|
||||
if (!b) return (void)(K.check = 0, K.prev_frame = 0);
|
||||
if (K.check) {
|
||||
if (!ensure(CUR, (uint64_t)w * h * (uint64_t)b)) return;
|
||||
copy(cb, MV_FIN, mv, 0, 0, w, h);
|
||||
copy(cb, CUR, cimg, level, layer, w, h);
|
||||
K.check = 0, K.prev_frame = 0, K.ow = K.oh = 0, K.want_out = 1;
|
||||
snprintf(K.masks, sizeof K.masks, "%s", vk_mvobj_frame_masks());
|
||||
__atomic_store_n(&K.busy, 1, __ATOMIC_RELEASE);
|
||||
vk_completion_add(fence, analyse, NULL);
|
||||
return;
|
||||
}
|
||||
if (!want_prev) return;
|
||||
if (!ensure(PREV, (uint64_t)w * h * (uint64_t)b)) return;
|
||||
copy(cb, PREV, cimg, level, layer, w, h);
|
||||
K.prev_frame = frame, K.prev_u[0] = jx, K.prev_u[1] = jy, K.w = w, K.h = h, K.cfmt = cfmt;
|
||||
}
|
||||
|
||||
// After DLSS ran on a check frame (vk_dlaa.c evaluate): its output (RGBA16F, ow x oh) for the analysis, which runs once
|
||||
// the submission is done.
|
||||
void vk_mvcheck_output(VkCommandBuffer cb, VkImage out, uint32_t ow, uint32_t oh) {
|
||||
if (!K.want_out) return;
|
||||
K.want_out = 0;
|
||||
if (!ensure(OUT, (uint64_t)ow * oh * 8)) return;
|
||||
copy(cb, OUT, out, 0, 0, ow, oh);
|
||||
K.ow = ow, K.oh = oh;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Generated by tools/make_mvobj_fs.py: the per-object motion fragment shader (vk_mvobj.c). Do not edit.
|
||||
#define MVOBJ_FS_LOCATION_WORD 24 // the input Location literal, patched to the variant's
|
||||
static const uint32_t mvobj_fs_spv[] = {
|
||||
0x07230203, 0x00010000, 0x00000000, 0x00000034, 0x00000000, 0x00020011, 0x00000001, 0x0003000e,
|
||||
0x00000000, 0x00000001, 0x0008000f, 0x00000004, 0x00000001, 0x6e69616d, 0x00000000, 0x0000000e,
|
||||
0x0000000f, 0x00000010, 0x00030010, 0x00000001, 0x00000007, 0x00040047, 0x0000000e, 0x0000001e,
|
||||
0x00000000, 0x00040047, 0x0000000f, 0x0000000b, 0x0000000f, 0x00040047, 0x00000010, 0x0000001e,
|
||||
0x00000000, 0x00030047, 0x0000000b, 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x00000023,
|
||||
0x00000000, 0x00040047, 0x00000011, 0x00000022, 0x00000000, 0x00040047, 0x00000011, 0x00000021,
|
||||
0x00000001, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000004,
|
||||
0x00000020, 0x00040017, 0x00000005, 0x00000004, 0x00000002, 0x00040017, 0x00000006, 0x00000004,
|
||||
0x00000004, 0x00040015, 0x00000007, 0x00000020, 0x00000000, 0x00020014, 0x00000008, 0x00040020,
|
||||
0x00000009, 0x00000001, 0x00000006, 0x00040020, 0x0000000a, 0x00000003, 0x00000006, 0x0003001e,
|
||||
0x0000000b, 0x00000006, 0x00040020, 0x0000000c, 0x00000002, 0x0000000b, 0x00040020, 0x00000017,
|
||||
0x00000002, 0x00000006, 0x0004002b, 0x00000004, 0x00000012, 0x3f000000, 0x0004002b, 0x00000004,
|
||||
0x00000013, 0x00000000, 0x0004002b, 0x00000004, 0x00000014, 0x358637bd, 0x0004002b, 0x00000007,
|
||||
0x00000015, 0x00000000, 0x0007002c, 0x00000006, 0x00000016, 0x00000013, 0x00000013, 0x00000013,
|
||||
0x00000013, 0x0004003b, 0x00000009, 0x0000000e, 0x00000001, 0x0004003b, 0x00000009, 0x0000000f,
|
||||
0x00000001, 0x0004003b, 0x0000000a, 0x00000010, 0x00000003, 0x0004003b, 0x0000000c, 0x00000011,
|
||||
0x00000002, 0x00050036, 0x00000002, 0x00000001, 0x00000000, 0x00000003, 0x000200f8, 0x00000018,
|
||||
0x00050041, 0x00000017, 0x00000019, 0x00000011, 0x00000015, 0x0004003d, 0x00000006, 0x0000001a,
|
||||
0x00000019, 0x0004003d, 0x00000006, 0x0000001b, 0x0000000e, 0x0004003d, 0x00000006, 0x0000001c,
|
||||
0x0000000f, 0x0007004f, 0x00000005, 0x0000001d, 0x0000001c, 0x0000001c, 0x00000000, 0x00000001,
|
||||
0x0007004f, 0x00000005, 0x0000001e, 0x0000001a, 0x0000001a, 0x00000002, 0x00000003, 0x00050083,
|
||||
0x00000005, 0x0000001f, 0x0000001d, 0x0000001e, 0x00050051, 0x00000004, 0x00000020, 0x0000001b,
|
||||
0x00000000, 0x00050051, 0x00000004, 0x00000021, 0x0000001b, 0x00000001, 0x00050051, 0x00000004,
|
||||
0x00000022, 0x0000001b, 0x00000003, 0x00050088, 0x00000004, 0x00000023, 0x00000020, 0x00000022,
|
||||
0x00050088, 0x00000004, 0x00000024, 0x00000021, 0x00000022, 0x00050051, 0x00000004, 0x00000025,
|
||||
0x0000001a, 0x00000000, 0x00050051, 0x00000004, 0x00000026, 0x0000001a, 0x00000001, 0x00050085,
|
||||
0x00000004, 0x00000027, 0x00000023, 0x00000012, 0x00050081, 0x00000004, 0x00000028, 0x00000027,
|
||||
0x00000012, 0x00050085, 0x00000004, 0x00000029, 0x00000028, 0x00000025, 0x00050085, 0x00000004,
|
||||
0x0000002a, 0x00000024, 0x00000012, 0x00050083, 0x00000004, 0x0000002b, 0x00000012, 0x0000002a,
|
||||
0x00050085, 0x00000004, 0x0000002c, 0x0000002b, 0x00000026, 0x00050051, 0x00000004, 0x0000002d,
|
||||
0x0000001f, 0x00000000, 0x00050051, 0x00000004, 0x0000002e, 0x0000001f, 0x00000001, 0x00050083,
|
||||
0x00000004, 0x0000002f, 0x00000029, 0x0000002d, 0x00050083, 0x00000004, 0x00000030, 0x0000002c,
|
||||
0x0000002e, 0x00070050, 0x00000006, 0x00000031, 0x0000002f, 0x00000030, 0x00000013, 0x00000013,
|
||||
0x000500ba, 0x00000008, 0x00000032, 0x00000022, 0x00000014, 0x000600a9, 0x00000006, 0x00000033,
|
||||
0x00000032, 0x00000031, 0x00000016, 0x0003003e, 0x00000010, 0x00000033, 0x000100fd, 0x00010038
|
||||
};
|
||||
@@ -0,0 +1,219 @@
|
||||
// Last frame's object and bone blocks, for per-object motion vectors (TOTK_DLAA_OBJMV, on with DLAA). The banks kept
|
||||
// start as c5, c7 and c14 and grow with what the vertex shader variants read (vk_mvprev_want); c7 and c5 are described
|
||||
// here, the others follow the same two-copy rule.
|
||||
//
|
||||
// The scene's vertex shaders read an object's place from bank c7 (gsys_shape: one 3x4 matrix at the start of a
|
||||
// 0x100 block) and a skinned object's bones from c5 (gsys_skeleton: a run of 3x4 matrices), of this frame only. The
|
||||
// game writes both into two copies that take turns, the second right after the first: an object's block is at addr in
|
||||
// one frame and at addr +- step in the next, step its size rounded up to 256 bytes (vk_mvsurvey.c measured it: every
|
||||
// c7 pair 0x100 apart, c5 pairs their rounded size apart, the pair's contents the same object moved a little). The
|
||||
// copy this frame does not write still holds last frame's when the frame is submitted, so the uniform snapshot
|
||||
// (vk_usnap.c) also copies both neighbours of these blocks, and vk_mvprev_find picks last frame's:
|
||||
// self the block was bound at the same address last frame too (an object the game did not write again): itself
|
||||
// side one neighbour was bound in that bank last frame: that one
|
||||
// nearer both were (the copies of adjacent objects interleave): the one with more of its first 16 words equal to
|
||||
// this frame's
|
||||
// none neither: a new object, no motion of its own
|
||||
// The pick is checked against what was snapshotted of that block last frame (intact: the same first CHECK bytes), and
|
||||
// a summary goes to the log every SUMMARY frames:
|
||||
// mv_prev: c<n>: <blocks> blocks: self a %, side b %, nearer c %, none d %; intact e %; moved: still f %, under
|
||||
// one unit g %, more h %
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "host.h"
|
||||
#include "vk.h"
|
||||
#include "vk_exec.h"
|
||||
#include "vk_dlaa.h"
|
||||
|
||||
#define CHECK 128u
|
||||
#define TABLE (1u << 14)
|
||||
#define SUMMARY 600
|
||||
|
||||
enum { BONES = 2, PLACE = 4, CLOCK = 11, BANKS = 14 }; // NVN binding index: banks c5, c7 and c14; c3..c16
|
||||
|
||||
// 16 bytes, so a table of TABLE entries stays in the cache: every scene draw probes it (the bytes of TOTK_MVPREV_CHECK
|
||||
// live in a table of their own, by entry index, allocated only for the check)
|
||||
typedef struct {
|
||||
uint64_t addr; // 0: empty
|
||||
int index;
|
||||
uint32_t have;
|
||||
} Seen;
|
||||
|
||||
typedef struct { uint64_t blocks, self, side, nearer, none, checked, intact, still, small, big; } Stat;
|
||||
|
||||
static struct {
|
||||
int on; // 1 knob on and DLAA on this frame
|
||||
int knob; // -1 not read
|
||||
Seen *t[2]; // this frame's bound blocks, last frame's
|
||||
uint8_t (*bytes[2])[CHECK]; // TOTK_MVPREV_CHECK: the first CHECK bytes of each entry of t[k], NULL without the check
|
||||
int timed; // TOTK_MVOBJ_TIMES: the summary's times
|
||||
uint32_t frames;
|
||||
Stat st[BANKS]; // per binding index
|
||||
uint64_t last[BANKS]; // the block the last scene draw bound per binding index
|
||||
uint32_t mask; // binding indices kept (vk_mvprev_want): bones, place and clock from the start
|
||||
uint64_t ns_draw, ns_frame; // time in vk_mvprev_draw and vk_mvprev_frame since the last summary
|
||||
} P = {.knob = -1, .mask = 1u << BONES | 1u << PLACE | 1u << CLOCK};
|
||||
|
||||
static uint32_t slot_of(uint64_t addr, int index) {
|
||||
uint64_t h = (addr ^ (uint64_t)index * 0x9E3779B97F4A7C15ull) * 0xff51afd7ed558ccdull;
|
||||
return (uint32_t)(h >> 40) & (TABLE - 1);
|
||||
}
|
||||
|
||||
static Seen *find(Seen *t, uint64_t addr, int index, int add) {
|
||||
for (uint32_t i = slot_of(addr, index), k = 0; k < TABLE; i = (i + 1) & (TABLE - 1), k++) {
|
||||
if (t[i].addr == addr && t[i].index == index) return &t[i];
|
||||
if (!t[i].addr) {
|
||||
if (!add) return NULL;
|
||||
t[i].addr = addr, t[i].index = index;
|
||||
return &t[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int vk_mvprev_on(void) { return P.on > 0; }
|
||||
|
||||
void vk_mvprev_want(uint32_t index_mask) {
|
||||
static int fixed = -1;
|
||||
if (fixed < 0) fixed = !rt_knob("MVPREV_GROW", "1"); // TOTK_MVPREV_GROW=0: c5, c7 and c14 only (test)
|
||||
if (!fixed) P.mask |= index_mask & ((1u << BANKS) - 1);
|
||||
}
|
||||
|
||||
uint64_t vk_mvprev_step(int stage, int index, uint64_t size) {
|
||||
if (P.on <= 0 || stage != 0 || index < 0 || index >= BANKS || !(P.mask >> index & 1)) return 0;
|
||||
return (size + 255) & ~(uint64_t)255;
|
||||
}
|
||||
|
||||
// Translation distance between the first matrices of two blocks (rows' fourth floats).
|
||||
static float moved(const uint8_t *a, const uint8_t *b) {
|
||||
const float *x = (const float *)a, *y = (const float *)b;
|
||||
float dx = x[3] - y[3], dy = x[7] - y[7], dz = x[11] - y[11];
|
||||
return sqrtf(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
int vk_mvprev_find(int index, uint64_t addr, uint64_t size, uint64_t *prev, int *how) {
|
||||
*how = MVPREV_NONE;
|
||||
if (P.on <= 0 || !P.t[1]) return 0;
|
||||
if (find(P.t[1], addr, index, 0)) {
|
||||
*prev = addr, *how = MVPREV_SELF;
|
||||
return 1;
|
||||
}
|
||||
uint64_t step = (size + 255) & ~(uint64_t)255, cand[2];
|
||||
int n = 0;
|
||||
if (find(P.t[1], addr + step, index, 0)) cand[n++] = addr + step;
|
||||
if (addr >= step && find(P.t[1], addr - step, index, 0)) cand[n++] = addr - step;
|
||||
if (!n) return 0;
|
||||
if (n == 2) { // the copies of adjacent objects interleave: the one with more words equal to this frame's
|
||||
const uint32_t *c = (const uint32_t *)vk_usnap_bytes(addr, 64), *a = (const uint32_t *)vk_usnap_bytes(cand[0], 64),
|
||||
*b = (const uint32_t *)vk_usnap_bytes(cand[1], 64);
|
||||
if (!c || !a || !b) return 0;
|
||||
int na = 0, nb = 0;
|
||||
for (int w = 0; w < 16; w++) na += c[w] == a[w], nb += c[w] == b[w];
|
||||
if (nb > na) cand[0] = cand[1];
|
||||
*how = MVPREV_NEARER;
|
||||
} else {
|
||||
*how = MVPREV_SIDE;
|
||||
}
|
||||
*prev = cand[0];
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void mvprev_draw_(VkExec *ex);
|
||||
void vk_mvprev_draw(VkExec *ex) {
|
||||
if (P.on <= 0) return;
|
||||
if (!P.timed) return mvprev_draw_(ex); // the clock reads were 1 % of the render thread
|
||||
uint64_t t0 = kern_now_ns();
|
||||
mvprev_draw_(ex);
|
||||
P.ns_draw += kern_now_ns() - t0;
|
||||
}
|
||||
|
||||
static void mvprev_draw_(VkExec *ex) {
|
||||
const VkExecTarget *z = &ex->depth;
|
||||
if (!z->image || z->width < DLAA_SCENE_MIN_W || z->format != VK_FORMAT_D32_SFLOAT) return;
|
||||
int check = P.bytes[0] != NULL; // TOTK_MVPREV_CHECK=1: the summary's checks (3.6 ms a frame)
|
||||
for (int index = 0; index < BANKS; index++) {
|
||||
if (!(P.mask >> index & 1)) continue;
|
||||
const p_bind_buf *u = &ex->ubo[0][index];
|
||||
if (!u->addr || !u->size || u->addr == P.last[index]) continue; // the next draws keep the block: noted already
|
||||
P.last[index] = u->addr;
|
||||
Seen *k = find(P.t[0], u->addr, index, 1);
|
||||
if (!k || k->have || !check) continue; // else only the address is noted
|
||||
int w = index;
|
||||
uint32_t want = u->size < CHECK ? (uint32_t)u->size : CHECK;
|
||||
const uint8_t *cur = vk_usnap_bytes(u->addr, want);
|
||||
if (!cur) continue;
|
||||
memcpy(P.bytes[0][k - P.t[0]], cur, want), k->have = want;
|
||||
Stat *s = &P.st[w];
|
||||
s->blocks++;
|
||||
uint64_t prev;
|
||||
int how;
|
||||
if (!vk_mvprev_find(index, u->addr, u->size, &prev, &how)) {
|
||||
s->none++;
|
||||
continue;
|
||||
}
|
||||
if (how == MVPREV_SELF) s->self++;
|
||||
else if (how == MVPREV_SIDE) s->side++;
|
||||
else s->nearer++;
|
||||
const Seen *last = find(P.t[1], prev, index, 0);
|
||||
const uint8_t *now = vk_usnap_bytes(prev, want);
|
||||
if (last && now && last->have >= want && want >= 48) {
|
||||
s->checked++;
|
||||
if (!memcmp(now, P.bytes[1][last - P.t[1]], want)) s->intact++;
|
||||
float d = moved(cur, now);
|
||||
if (d < 0.01f) s->still++;
|
||||
else if (d < 1.0f) s->small++;
|
||||
else s->big++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vk_mvprev_frame(void) {
|
||||
// without DLSS (no NGX, no RTX GPU, dlaa = 0) nothing reads last frame's blocks: P.on stays 0, no tables
|
||||
if (!vk_dlaa_available()) return;
|
||||
if (P.knob < 0) {
|
||||
P.knob = rt_knob("DLAA_OBJMV", "1");
|
||||
if (P.knob) {
|
||||
P.t[0] = calloc(TABLE, sizeof(Seen)), P.t[1] = calloc(TABLE, sizeof(Seen));
|
||||
if (!P.t[0] || !P.t[1]) P.knob = 0;
|
||||
if (P.knob && rt_knob("MVPREV_CHECK", "0")) {
|
||||
P.bytes[0] = calloc(TABLE, CHECK), P.bytes[1] = calloc(TABLE, CHECK);
|
||||
if (!P.bytes[0] || !P.bytes[1]) free(P.bytes[0]), free(P.bytes[1]), P.bytes[0] = P.bytes[1] = NULL;
|
||||
}
|
||||
P.timed = rt_knob("MVOBJ_TIMES", "0");
|
||||
}
|
||||
}
|
||||
int on = P.knob && cfg_bool("TOTK_DLAA");
|
||||
if (on != (P.on > 0)) {
|
||||
P.on = on ? 1 : -1;
|
||||
if (P.t[0]) memset(P.t[0], 0, TABLE * sizeof(Seen)), memset(P.t[1], 0, TABLE * sizeof(Seen));
|
||||
return;
|
||||
}
|
||||
if (P.on <= 0) return;
|
||||
uint64_t t0 = P.timed ? kern_now_ns() : 0;
|
||||
Seen *t = P.t[1];
|
||||
P.t[1] = P.t[0], P.t[0] = t;
|
||||
uint8_t (*b)[CHECK] = P.bytes[1];
|
||||
P.bytes[1] = P.bytes[0], P.bytes[0] = b;
|
||||
memset(P.t[0], 0, TABLE * sizeof(Seen));
|
||||
memset(P.last, 0, sizeof P.last);
|
||||
if (P.timed) P.ns_frame += kern_now_ns() - t0;
|
||||
if (++P.frames % SUMMARY) return;
|
||||
if (P.timed) rt_log("mv_prev: %.3f ms a frame in the draws, %.3f ms at the present", P.ns_draw / 1e6 / SUMMARY,
|
||||
P.ns_frame / 1e6 / SUMMARY);
|
||||
P.ns_draw = P.ns_frame = 0;
|
||||
for (int w = 0; w < BANKS; w++) {
|
||||
Stat *s = &P.st[w];
|
||||
if (!s->blocks) continue;
|
||||
double b = (double)s->blocks, c = s->checked ? (double)s->checked : 1;
|
||||
rt_log("mv_prev: c%d: %llu blocks: self %.1f %%, side %.1f %%, nearer %.1f %%, none %.1f %%; intact %.1f %% of "
|
||||
"%llu; moved: still %.1f %%, under one unit %.1f %%, more %.1f %%",
|
||||
w + 3, (unsigned long long)s->blocks, 100 * s->self / b, 100 * s->side / b, 100 * s->nearer / b,
|
||||
100 * s->none / b, 100 * s->intact / c, (unsigned long long)s->checked, 100 * s->still / c,
|
||||
100 * s->small / c, 100 * s->big / c);
|
||||
}
|
||||
memset(P.st, 0, sizeof P.st);
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
// Motion-vector survey (TOTK_MV_SURVEY=1, off by default): measures whether per-object motion vectors can be made from
|
||||
// the uniform blocks the scene's draws bind. Per-object vectors need each object's matrices of the previous frame; the
|
||||
// game keeps none (its shaders read gsys_shape, gsys_skeleton and gsys_projection of this frame only), so they would
|
||||
// have to be kept from the previous frame's uniform snapshot (vk_usnap.c). That works when a block's guest address
|
||||
// stands for the same object from one frame to the next, which this survey measures.
|
||||
//
|
||||
// For every scene draw (vertex stage, scene depth bound: D32 at least DLAA_SCENE_MIN_W wide, as vk_dlaa_draw), each user bank
|
||||
// (c3 + index) with a binding is noted: address, size and its first SNAP bytes as of the submit (the snapshot's copy).
|
||||
// At each frame the bank's blocks are compared with the previous frame's and the one before:
|
||||
// repeat1 / repeat2 blocks whose address was bound in that bank one / two frames ago
|
||||
// same / near / far of the blocks repeated from last frame: bytes unchanged / every float within 5 % or 1.0 of
|
||||
// last frame's (the same object, moved a little) / some float further (another object, likely)
|
||||
// The game writes its per-object blocks into two copies that take turns, so a moving object's block is at one address
|
||||
// in even frames and another in odd ones. To find the rule that pairs them, a bank's blocks bound two frames ago but
|
||||
// not last frame (this frame's copy) are sorted by address, and so are last frame's blocks not bound this frame (the
|
||||
// other copy); with equal counts they are paired in order:
|
||||
// pairs, delta the paired blocks and the commonest address differences (this frame's minus last frame's)
|
||||
// psame / pnear / pfar the pairs' contents, as same / near / far above (near: the same object, moved)
|
||||
// unequal frames whose two lists had different lengths (no pairing that frame)
|
||||
// What the bank holds: each block's leading 3x4 matrices (rows of a rotation times a scale: three rows of the same
|
||||
// length, at right angles, finite), counted over the whole block as of the submit:
|
||||
// mats 0 / 1 / 2-7 / 8+ blocks by that count (one: an object's place, gsys_shape; many: bones, gsys_skeleton)
|
||||
// Every SURVEY_FRAMES frames the log gets two lines per bank with the window's totals:
|
||||
// mv_survey: c<n>: <draws> draws, <blocks> blocks (<per frame>), sizes <top three>; repeat1 a %, repeat2 b %;
|
||||
// same c %, near d %, far e %
|
||||
// mv_survey: c<n>: pairs <n> (<unequal> frames unequal), delta <top three>; psame a %, pnear b %, pfar c %;
|
||||
// mats 0 x<n> 1 x<n> 2-7 x<n> 8+ x<n>, at most <m>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "host.h"
|
||||
#include "vk.h"
|
||||
#include "vk_exec.h"
|
||||
#include "vk_dlaa.h"
|
||||
|
||||
#define BANKS 18
|
||||
#define SNAP 256u
|
||||
#define SURVEY_FRAMES 300
|
||||
#define TABLE (1u << 15) // blocks per frame, all banks; a town frame binds about 10,000 uniform ranges
|
||||
|
||||
typedef struct {
|
||||
uint64_t addr; // 0: empty slot
|
||||
uint32_t size, n;
|
||||
int bank;
|
||||
uint8_t bytes[SNAP];
|
||||
uint32_t have; // bytes of `bytes` that were in the snapshot
|
||||
uint32_t mats; // leading 3x4 matrices of the whole block, 0 when it was not in the snapshot
|
||||
} Block;
|
||||
|
||||
typedef struct {
|
||||
uint64_t draws, blocks, rep1, rep2, same, near, far, unread;
|
||||
uint32_t size[4];
|
||||
uint64_t size_n[4];
|
||||
uint64_t pairs, psame, pnear, pfar, unequal, mats[4];
|
||||
uint32_t mats_max;
|
||||
int64_t delta[4];
|
||||
uint64_t delta_n[4];
|
||||
} Stat;
|
||||
|
||||
static struct {
|
||||
int on;
|
||||
Block *t[3]; // this frame, last frame, the one before
|
||||
const Block **x, **y; // pairing: this frame's copy, last frame's
|
||||
uint32_t frames;
|
||||
Stat st[BANKS];
|
||||
} S;
|
||||
|
||||
static uint32_t slot_of(uint64_t addr, int bank) {
|
||||
uint64_t h = (addr ^ (uint64_t)bank * 0x9E3779B97F4A7C15ull) * 0xff51afd7ed558ccdull;
|
||||
return (uint32_t)(h >> 40) & (TABLE - 1);
|
||||
}
|
||||
|
||||
static Block *find(Block *t, uint64_t addr, int bank, int add) {
|
||||
for (uint32_t i = slot_of(addr, bank), k = 0; k < TABLE; i = (i + 1) & (TABLE - 1), k++) {
|
||||
if (t[i].addr == addr && t[i].bank == bank) return &t[i];
|
||||
if (!t[i].addr) {
|
||||
if (!add) return NULL;
|
||||
t[i].addr = addr, t[i].bank = bank;
|
||||
return &t[i];
|
||||
}
|
||||
}
|
||||
return NULL; // full: the block goes uncounted
|
||||
}
|
||||
|
||||
static void note_size(Stat *s, uint32_t size) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (s->size[i] == size || !s->size_n[i]) {
|
||||
s->size[i] = size, s->size_n[i]++;
|
||||
return;
|
||||
}
|
||||
s->size_n[3]++; // a fifth size: counted with the last
|
||||
}
|
||||
|
||||
static void note_delta(Stat *s, int64_t d) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (s->delta[i] == d || !s->delta_n[i]) {
|
||||
s->delta[i] = d, s->delta_n[i]++;
|
||||
return;
|
||||
}
|
||||
s->delta_n[3]++;
|
||||
}
|
||||
|
||||
// Leading 3x4 matrices: three rows of about the same length (a uniform scale), at right angles, all finite.
|
||||
static uint32_t lead_mats(const float *f, uint32_t nf) {
|
||||
uint32_t k = 0;
|
||||
for (; (k + 1) * 12 <= nf && k < 4096; k++) {
|
||||
const float *m = f + k * 12;
|
||||
float l[3];
|
||||
for (int r = 0; r < 3; r++) {
|
||||
const float *v = m + r * 4;
|
||||
if (!isfinite(v[0]) || !isfinite(v[1]) || !isfinite(v[2]) || !isfinite(v[3])) return k;
|
||||
l[r] = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
||||
if (l[r] < 1e-3f || l[r] > 1e3f) return k;
|
||||
}
|
||||
if (fabsf(l[1] - l[0]) > 0.02f * l[0] || fabsf(l[2] - l[0]) > 0.02f * l[0]) return k;
|
||||
for (int a = 0; a < 3; a++)
|
||||
for (int b = a + 1; b < 3; b++) {
|
||||
const float *u = m + a * 4, *v = m + b * 4;
|
||||
if (fabsf(u[0] * v[0] + u[1] * v[1] + u[2] * v[2]) > 0.02f * l[a] * l[b]) return k;
|
||||
}
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
static int by_bank_addr(const void *a, const void *b) {
|
||||
const Block *x = *(const Block *const *)a, *y = *(const Block *const *)b;
|
||||
if (x->bank != y->bank) return x->bank < y->bank ? -1 : 1;
|
||||
return x->addr < y->addr ? -1 : x->addr > y->addr;
|
||||
}
|
||||
|
||||
static void survey_init(void) {
|
||||
S.on = rt_knob("MV_SURVEY", "0") ? 1 : -1;
|
||||
if (S.on < 0) return;
|
||||
for (int i = 0; i < 3; i++) S.t[i] = calloc(TABLE, sizeof(Block));
|
||||
S.x = malloc(TABLE * sizeof *S.x), S.y = malloc(TABLE * sizeof *S.y);
|
||||
if (!S.t[0] || !S.t[1] || !S.t[2] || !S.x || !S.y) {
|
||||
S.on = -1;
|
||||
return;
|
||||
}
|
||||
rt_log("mv_survey: on (scene draws' uniform blocks, a summary every %d frames)", SURVEY_FRAMES);
|
||||
}
|
||||
|
||||
void vk_mvsurvey_draw(VkExec *ex) {
|
||||
if (!S.on) survey_init();
|
||||
if (S.on < 0) return;
|
||||
const VkExecTarget *z = &ex->depth;
|
||||
if (!z->image || z->width < DLAA_SCENE_MIN_W || z->format != VK_FORMAT_D32_SFLOAT) return;
|
||||
for (int b = 0; b < BANKS; b++) {
|
||||
const p_bind_buf *u = &ex->ubo[0][b];
|
||||
if (!u->addr || !u->size) continue;
|
||||
S.st[b].draws++;
|
||||
Block *k = find(S.t[0], u->addr, b, 1);
|
||||
if (!k) continue;
|
||||
if (k->n++) continue;
|
||||
k->size = (uint32_t)u->size;
|
||||
uint32_t want = u->size < SNAP ? (uint32_t)u->size : SNAP;
|
||||
VkDescriptorBufferInfo bi;
|
||||
const uint8_t *src = vk_usnap_find(u->addr, want, 1, &bi) ? vk_usnap_map() + bi.offset : NULL;
|
||||
k->have = src ? want : 0;
|
||||
if (src) memcpy(k->bytes, src, want);
|
||||
const uint8_t *all = vk_usnap_find(u->addr, u->size, 1, &bi) ? vk_usnap_map() + bi.offset : NULL;
|
||||
k->mats = all ? lead_mats((const float *)all, (uint32_t)(u->size / 4)) : 0;
|
||||
if (all) {
|
||||
Stat *s = &S.st[b];
|
||||
s->mats[k->mats == 0 ? 0 : k->mats == 1 ? 1 : k->mats < 8 ? 2 : 3]++;
|
||||
if (k->mats > s->mats_max) s->mats_max = k->mats;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Same object moved a little: every float within 5 % or 1.0 of last frame's.
|
||||
static int compare(const Block *a, const Block *b) {
|
||||
uint32_t n = a->have < b->have ? a->have : b->have;
|
||||
if (!n) return -1;
|
||||
if (!memcmp(a->bytes, b->bytes, n)) return 0;
|
||||
const float *x = (const float *)a->bytes, *y = (const float *)b->bytes;
|
||||
for (uint32_t i = 0; i < n / 4; i++) {
|
||||
float d = fabsf(x[i] - y[i]);
|
||||
if (isnan(d) || (d > 1.0f && d > 0.05f * fabsf(y[i]))) return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void vk_mvsurvey_frame(void) {
|
||||
if (S.on <= 0) return;
|
||||
for (uint32_t i = 0; i < TABLE; i++) {
|
||||
const Block *k = &S.t[0][i];
|
||||
if (!k->addr) continue;
|
||||
Stat *s = &S.st[k->bank];
|
||||
s->blocks++;
|
||||
note_size(s, k->size);
|
||||
const Block *p = find(S.t[1], k->addr, k->bank, 0);
|
||||
if (p) {
|
||||
s->rep1++;
|
||||
int c = compare(k, p);
|
||||
if (c == 0) s->same++;
|
||||
else if (c == 1) s->near++;
|
||||
else if (c == 2) s->far++;
|
||||
else s->unread++;
|
||||
}
|
||||
if (find(S.t[2], k->addr, k->bank, 0)) s->rep2++;
|
||||
}
|
||||
// pairing of the two copies, bank by bank
|
||||
uint32_t nx = 0, ny = 0;
|
||||
for (uint32_t i = 0; i < TABLE; i++) {
|
||||
const Block *k = &S.t[0][i];
|
||||
if (k->addr && !find(S.t[1], k->addr, k->bank, 0) && find(S.t[2], k->addr, k->bank, 0)) S.x[nx++] = k;
|
||||
k = &S.t[1][i];
|
||||
if (k->addr && !find(S.t[0], k->addr, k->bank, 0)) S.y[ny++] = k;
|
||||
}
|
||||
qsort(S.x, nx, sizeof *S.x, by_bank_addr);
|
||||
qsort(S.y, ny, sizeof *S.y, by_bank_addr);
|
||||
for (uint32_t i = 0, j = 0; i < nx;) {
|
||||
int b = S.x[i]->bank;
|
||||
uint32_t i1 = i, j0, j1;
|
||||
while (i1 < nx && S.x[i1]->bank == b) i1++;
|
||||
while (j < ny && S.y[j]->bank < b) j++;
|
||||
for (j0 = j1 = j; j1 < ny && S.y[j1]->bank == b;) j1++;
|
||||
Stat *s = &S.st[b];
|
||||
if (i1 - i != j1 - j0) s->unequal++;
|
||||
else
|
||||
for (uint32_t a = i, c = j0; a < i1; a++, c++) {
|
||||
s->pairs++;
|
||||
note_delta(s, (int64_t)(S.x[a]->addr - S.y[c]->addr));
|
||||
int r = compare(S.x[a], S.y[c]);
|
||||
if (r == 0) s->psame++;
|
||||
else if (r == 1) s->pnear++;
|
||||
else if (r == 2) s->pfar++;
|
||||
}
|
||||
i = i1, j = j1;
|
||||
}
|
||||
Block *old = S.t[2];
|
||||
S.t[2] = S.t[1], S.t[1] = S.t[0], S.t[0] = old;
|
||||
memset(S.t[0], 0, TABLE * sizeof(Block));
|
||||
if (++S.frames % SURVEY_FRAMES) return;
|
||||
for (int b = 0; b < BANKS; b++) {
|
||||
Stat *s = &S.st[b];
|
||||
if (!s->blocks) continue;
|
||||
double bl = (double)s->blocks, r1 = s->rep1 ? (double)s->rep1 : 1;
|
||||
rt_log("mv_survey: c%d: %llu draws, %llu blocks (%.0f a frame), sizes %#x x%llu %#x x%llu %#x x%llu; repeat1 %.1f %%, "
|
||||
"repeat2 %.1f %%; same %.1f %%, near %.1f %%, far %.1f %%, unread %.1f %%",
|
||||
b + 3, (unsigned long long)s->draws, (unsigned long long)s->blocks, bl / SURVEY_FRAMES, s->size[0],
|
||||
(unsigned long long)s->size_n[0], s->size[1], (unsigned long long)s->size_n[1], s->size[2],
|
||||
(unsigned long long)s->size_n[2], 100 * s->rep1 / bl, 100 * s->rep2 / bl, 100 * s->same / r1,
|
||||
100 * s->near / r1, 100 * s->far / r1, 100 * s->unread / r1);
|
||||
double pr = s->pairs ? (double)s->pairs : 1;
|
||||
rt_log("mv_survey: c%d: pairs %llu (%llu frames unequal), delta %#llx x%llu %#llx x%llu %#llx x%llu; psame %.1f %%, "
|
||||
"pnear %.1f %%, pfar %.1f %%; mats 0 x%llu 1 x%llu 2-7 x%llu 8+ x%llu, at most %u",
|
||||
b + 3, (unsigned long long)s->pairs, (unsigned long long)s->unequal, (unsigned long long)s->delta[0],
|
||||
(unsigned long long)s->delta_n[0], (unsigned long long)s->delta[1], (unsigned long long)s->delta_n[1],
|
||||
(unsigned long long)s->delta[2], (unsigned long long)s->delta_n[2], 100 * s->psame / pr,
|
||||
100 * s->pnear / pr, 100 * s->pfar / pr, (unsigned long long)s->mats[0], (unsigned long long)s->mats[1],
|
||||
(unsigned long long)s->mats[2], (unsigned long long)s->mats[3], s->mats_max);
|
||||
}
|
||||
memset(S.st, 0, sizeof S.st);
|
||||
}
|
||||
@@ -1913,6 +1913,8 @@ static void rec_fill(KeyRec *r, const VkPipeState *s) {
|
||||
// A pipeline was built for a draw: queue its record for the file. Caller holds P.lock.
|
||||
static void keys_note(const VkPipeState *s) {
|
||||
if (!A.on || !K.path[0]) return;
|
||||
static const uint8_t none[32];
|
||||
if (s->fs && !memcmp(gfx_shader_input_id(s->fs), none, 32)) return; // the runtime's own shader (gfx_shader_own)
|
||||
KeyRec r;
|
||||
rec_fill(&r, s);
|
||||
if (!seen_add(rec_hash(&r))) return;
|
||||
|
||||
+11
-1
@@ -38,7 +38,7 @@ enum {
|
||||
R_PAD, R_BeginQuery, R_BeginRendering, R_BindDescriptorSets, R_BindIndexBuffer, R_BindPipeline, R_BindVertexBuffers2,
|
||||
R_BlitImage, R_ClearAttachments, R_ClearColorImage, R_ClearDepthStencilImage, R_CopyBuffer, R_CopyBufferToImage,
|
||||
R_CopyImage, R_CopyImageToBuffer, R_Dispatch, R_DispatchIndirect, R_Draw, R_DrawIndexed, R_DrawIndexedIndirect,
|
||||
R_EndQuery, R_EndRendering, R_FillBuffer, R_PipelineBarrier2, R_PushConstants, R_ResetQueryPool, R_ResolveImage,
|
||||
R_EndQuery, R_EndRendering, R_ExecuteCommands, R_FillBuffer, R_PipelineBarrier2, R_PushConstants, R_ResetQueryPool, R_ResolveImage,
|
||||
R_SetBlendConstants, R_SetCullMode, R_SetDepthBias, R_SetDepthBiasEnable, R_SetDepthCompareOp, R_SetDepthTestEnable,
|
||||
R_SetDepthWriteEnable, R_SetFrontFace, R_SetPrimitiveRestartEnable, R_SetPrimitiveTopology,
|
||||
R_SetRasterizerDiscardEnable, R_SetScissorWithCount, R_SetStencilCompareMask, R_SetStencilOp, R_SetStencilReference,
|
||||
@@ -491,6 +491,15 @@ void rc_vkCmdClearDepthStencilImage(VkCommandBuffer cb, VkImage i, VkImageLayout
|
||||
pub();
|
||||
}
|
||||
|
||||
typedef struct { uint32_t n; const VkCommandBuffer *c; } A_ExecuteCommands;
|
||||
void rc_vkCmdExecuteCommands(VkCommandBuffer cb, uint32_t n, const VkCommandBuffer *c) {
|
||||
if (!OWNED(cb)) { vkCmdExecuteCommands(cb, n, c); return; }
|
||||
A_ExecuteCommands *a = put(R_ExecuteCommands, sizeof *a + sizeof *c * n);
|
||||
uint8_t *at = (uint8_t *)(a + 1);
|
||||
a->n = n, a->c = tail_copy(&at, c, sizeof *c * n);
|
||||
pub();
|
||||
}
|
||||
|
||||
typedef struct { VkPipelineLayout l; VkShaderStageFlags st; uint32_t o, n; const void *v; } A_PushConstants;
|
||||
void rc_vkCmdPushConstants(VkCommandBuffer cb, VkPipelineLayout l, VkShaderStageFlags st, uint32_t o, uint32_t n,
|
||||
const void *v) {
|
||||
@@ -677,6 +686,7 @@ static void exec_packet(uint32_t op, void *p) {
|
||||
break;
|
||||
}
|
||||
F(PushConstants, a->l, a->st, a->o, a->n, a->v)
|
||||
F(ExecuteCommands, a->n, a->c)
|
||||
#undef F
|
||||
case R_EndRendering: vkCmdEndRendering(cb); break;
|
||||
case R_BlitImage: { A_Img *a = p; vkCmdBlitImage(cb, a->s, a->sl, a->d, a->dl, a->n, a->r, a->f); break; }
|
||||
|
||||
@@ -80,6 +80,7 @@ void rec_pass_mark(VkCommandBuffer cb, uint32_t id, int end);
|
||||
X(EndRendering, (VkCommandBuffer cb), (cb)) \
|
||||
X(FillBuffer, (VkCommandBuffer cb, VkBuffer b, VkDeviceSize o, VkDeviceSize n, uint32_t v), (cb, b, o, n, v)) \
|
||||
X(PipelineBarrier2, (VkCommandBuffer cb, const VkDependencyInfo *di), (cb, di)) \
|
||||
X(ExecuteCommands, (VkCommandBuffer cb, uint32_t n, const VkCommandBuffer *c), (cb, n, c)) \
|
||||
X(PushConstants, (VkCommandBuffer cb, VkPipelineLayout l, VkShaderStageFlags st, uint32_t o, uint32_t n, \
|
||||
const void *v), (cb, l, st, o, n, v)) \
|
||||
X(ResetQueryPool, (VkCommandBuffer cb, VkQueryPool p, uint32_t f, uint32_t n), (cb, p, f, n)) \
|
||||
@@ -119,6 +120,7 @@ REC_CMDS(REC_DECL)
|
||||
#define vkCmdBindPipeline rc_vkCmdBindPipeline
|
||||
#define vkCmdBindVertexBuffers2 rc_vkCmdBindVertexBuffers2
|
||||
#define vkCmdBlitImage rc_vkCmdBlitImage
|
||||
#define vkCmdExecuteCommands rc_vkCmdExecuteCommands
|
||||
#define vkCmdClearAttachments rc_vkCmdClearAttachments
|
||||
#define vkCmdClearColorImage rc_vkCmdClearColorImage
|
||||
#define vkCmdClearDepthStencilImage rc_vkCmdClearDepthStencilImage
|
||||
|
||||
+23
-9
@@ -1679,15 +1679,8 @@ static VkSampler make_sampler(VkCtx *c, const samp_par *d) {
|
||||
si.maxLod = d->lod_max > 1000 ? VK_LOD_CLAMP_NONE : d->lod_max;
|
||||
if (mf <= NVN_MIN_FILTER_LINEAR) si.maxLod = si.minLod + 0.25f; // not mipmapped: stay on the base level
|
||||
if (si.maxLod < si.minLod) si.maxLod = si.minLod;
|
||||
float lim = c->props.limits.maxSamplerLodBias, bias = d->lod_bias;
|
||||
// DLSS upscaling (resolution.c): the scene's material textures, the filtered mipmapped ones that repeat, are
|
||||
// sampled for the output's resolution; clamped ones (post effects, LUTs, the UI) keep the game's bias. Without DLSS
|
||||
// running (dlaa = 0, or NGX without DLSS) nothing accumulates the extra detail, and the sharper mips only shimmer.
|
||||
if (vk_dlaa_available() && !d->cmp_mode && mf == NVN_MIN_FILTER_LINEAR_MIPMAP_LINEAR &&
|
||||
(si.addressModeU == VK_SAMPLER_ADDRESS_MODE_REPEAT || si.addressModeU == VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT ||
|
||||
si.addressModeV == VK_SAMPLER_ADDRESS_MODE_REPEAT || si.addressModeV == VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT))
|
||||
bias += res_dlss_lod_bias();
|
||||
si.mipLodBias = fmaxf(-lim, fminf(lim, bias));
|
||||
float lim = c->props.limits.maxSamplerLodBias;
|
||||
si.mipLodBias = fmaxf(-lim, fminf(lim, d->lod_bias));
|
||||
// TOTK_ANISO raises the level for filtered, mipmapped colour samplers that repeat on U and V: the surface textures,
|
||||
// which tile, so ground and walls seen at a shallow angle stay sharp into the distance. Depth-compare (shadow)
|
||||
// samplers, nearest or unmipmapped ones (data maps, LUTs) and clamped ones keep the game's level: clamped samplers
|
||||
@@ -1755,11 +1748,32 @@ void vk_sampler_register(int id, const samp_par *s) {
|
||||
unlock();
|
||||
}
|
||||
|
||||
// DLSS upscaling (vk_sampler_dlss_bias): the scene's material textures, the filtered mipmapped ones that repeat, are
|
||||
// sampled for the output's resolution; clamped ones (post effects, LUTs, the UI) keep the game's bias.
|
||||
static float dlss_bias;
|
||||
|
||||
static int dlss_biased(const samp_par *k) {
|
||||
int rep_s = k->ws == NVN_WRAP_MODE_REPEAT || k->ws == NVN_WRAP_MODE_MIRRORED_REPEAT;
|
||||
int rep_t = k->wt == NVN_WRAP_MODE_REPEAT || k->wt == NVN_WRAP_MODE_MIRRORED_REPEAT;
|
||||
return !k->cmp_mode && k->minf == NVN_MIN_FILTER_LINEAR_MIPMAP_LINEAR && (rep_s || rep_t);
|
||||
}
|
||||
|
||||
void vk_sampler_dlss_bias(float b) {
|
||||
lock();
|
||||
if (b != dlss_bias) {
|
||||
dlss_bias = b;
|
||||
for (int i = 0; i < nsslots; i++) sslots[i].s = sslots[i].sp = VK_NULL_HANDLE; // looked up again (the key moves)
|
||||
samp_gen++;
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
// Caller holds the lock.
|
||||
static VkSampler sampler_for(VkCtx *c, const samp_par *s) {
|
||||
samp_par k;
|
||||
memset(&k, 0, sizeof k);
|
||||
k = *s;
|
||||
if (dlss_bias != 0 && dlss_biased(&k)) k.lod_bias += dlss_bias; // part of the key: samplers live until shutdown
|
||||
VkSampler v = (VkSampler)nvn_hm_get(&samplers, &k);
|
||||
if (!v) {
|
||||
v = make_sampler(c, &k);
|
||||
|
||||
+50
-4
@@ -22,6 +22,7 @@
|
||||
#include "host.h"
|
||||
#include "nvn_api.h"
|
||||
#include "vk.h"
|
||||
#include "vk_dlaa.h"
|
||||
#include "platform/platform.h"
|
||||
|
||||
#define ALIGN 256u
|
||||
@@ -34,6 +35,9 @@ typedef struct Arena {
|
||||
VkBuffer buf;
|
||||
VmaAllocation alloc;
|
||||
uint8_t *map;
|
||||
uint8_t *cpu; // with DLSS (vk_dlaa_available), the same bytes in host memory for the CPU's reads (vk_usnap_bytes):
|
||||
// map is often device memory through the BAR, written fast but read back at about a microsecond a
|
||||
// cache line. Without DLSS nothing reads them back, and the bytes go into map in one copy.
|
||||
uint64_t size;
|
||||
} Arena;
|
||||
|
||||
@@ -121,9 +125,35 @@ static void collect(const op_hdr *h, int depth) {
|
||||
if (h->op == OP_BIND_UBO) {
|
||||
const p_bind_buf *b = a;
|
||||
if (b->addr && b->size) {
|
||||
if (U.ncol == U.ccol) U.col = grow(U.col, &U.ccol, sizeof *U.col);
|
||||
uint64_t n = b->size < MAX_RANGE ? b->size : MAX_RANGE;
|
||||
U.col[U.ncol++] = (Rng){b->addr & ~(uint64_t)(ALIGN - 1), b->addr + n};
|
||||
// the vertex stage's object and bone blocks: also the game's other copy on either side, which still
|
||||
// holds last frame's (vk_mvprev.c)
|
||||
uint64_t step = vk_mvprev_step(b->stage, b->index, b->size);
|
||||
// which neighbours lie in an imported pool, per block and vk_mem_gen: the same blocks are bound by
|
||||
// many draws, and two resolves for each binding were a quarter of the snapshot's time
|
||||
static struct { uint64_t addr, size, gen; int ok; } nc[1024];
|
||||
uint32_t ci = (uint32_t)((b->addr ^ n << 48) * 0x9E3779B97F4A7C15ull >> 54);
|
||||
uint64_t gen = vk_mem_gen();
|
||||
int known = step && nc[ci].addr == b->addr && nc[ci].size == n && nc[ci].gen == gen, ok = 0;
|
||||
for (int k = step ? -1 : 0; k <= (step ? 1 : 0); k++) {
|
||||
uint64_t at = b->addr + (uint64_t)((int64_t)k * (int64_t)step);
|
||||
if (k < 0 && b->addr < step) continue;
|
||||
// a neighbour past its pool would make the whole merged region fail the pool check below, and
|
||||
// the bindings in it read live memory again
|
||||
VkMemSpan sp;
|
||||
uint64_t lo = at & ~(uint64_t)(ALIGN - 1);
|
||||
if (k && known) {
|
||||
if (!(nc[ci].ok >> (k + 1) & 1)) continue;
|
||||
} else if (k) {
|
||||
if (!vk_mem_resolve(lo, at + n - lo, &sp) || sp.alloc || !sp.pool || sp.avail < at + n - lo ||
|
||||
(sp.cls != VK_MCLS_HOST_CACHED && sp.cls != VK_MCLS_HOST_UNC))
|
||||
continue;
|
||||
ok |= 1 << (k + 1);
|
||||
}
|
||||
if (U.ncol == U.ccol) U.col = grow(U.col, &U.ccol, sizeof *U.col);
|
||||
U.col[U.ncol++] = (Rng){at & ~(uint64_t)(ALIGN - 1), at + n};
|
||||
}
|
||||
if (step && !known) nc[ci].addr = b->addr, nc[ci].size = n, nc[ci].gen = gen, nc[ci].ok = ok;
|
||||
}
|
||||
}
|
||||
h = nvn_op_next(h);
|
||||
@@ -254,8 +284,14 @@ void vk_usnap_begin(const uint64_t *handles, int n, uint64_t fence) {
|
||||
}
|
||||
if (!U.nreg) return;
|
||||
if (!(U.arena = arena_get(total))) return;
|
||||
uint8_t *dst = U.arena->map;
|
||||
if (vk_dlaa_available()) {
|
||||
if (!U.arena->cpu && !(U.arena->cpu = malloc(U.arena->size))) rt_fatal("vk_usnap: out of memory");
|
||||
dst = U.arena->cpu;
|
||||
}
|
||||
for (int i = 0; i < U.nreg; i++)
|
||||
memcpy(U.arena->map + U.reg[i].off, (const void *)(uintptr_t)U.reg[i].lo, U.reg[i].hi - U.reg[i].lo);
|
||||
memcpy(dst + U.reg[i].off, (const void *)(uintptr_t)U.reg[i].lo, U.reg[i].hi - U.reg[i].lo);
|
||||
if (dst != U.arena->map) memcpy(U.arena->map, dst, total); // one sequential write into the device copy
|
||||
vmaFlushAllocation(vk_ctx()->vma, U.arena->alloc, 0, total); // a no-op on coherent memory
|
||||
U.on = 1;
|
||||
U.subs++, U.regions += (uint64_t)U.nreg, U.bytes += total, U.copy_ns += kern_now_ns() - t0;
|
||||
@@ -301,10 +337,20 @@ void vk_usnap_drop(uint64_t addr, uint64_t n) {
|
||||
|
||||
uint64_t vk_usnap_gen(void) { return U.gen; }
|
||||
|
||||
// the host copy where there is one (DLSS), else the device copy
|
||||
static uint8_t *host_copy(void) { return U.arena->cpu ? U.arena->cpu : U.arena->map; }
|
||||
|
||||
const uint8_t *vk_usnap_map(void) { return U.arena ? host_copy() : NULL; }
|
||||
|
||||
const uint8_t *vk_usnap_bytes(uint64_t addr, uint64_t n) {
|
||||
VkDescriptorBufferInfo bi;
|
||||
return vk_usnap_find(addr, n, 1, &bi) ? host_copy() + bi.offset : NULL;
|
||||
}
|
||||
|
||||
// The arenas back on the free list (vk_exec_draw_shutdown: the device is idle, every completion ran).
|
||||
void vk_usnap_shutdown(void) {
|
||||
plat_mutex_lock(&free_lock);
|
||||
for (Arena *a = free_arenas, *next; a; a = next) next = a->next, vmaDestroyBuffer(vk_ctx()->vma, a->buf, a->alloc), free(a);
|
||||
for (Arena *a = free_arenas, *next; a; a = next) next = a->next, vmaDestroyBuffer(vk_ctx()->vma, a->buf, a->alloc), free(a->cpu), free(a);
|
||||
free_arenas = NULL;
|
||||
plat_mutex_unlock(&free_lock);
|
||||
}
|
||||
+112
-1
@@ -3,6 +3,7 @@ with a newer release, totk.ini and the event stream. No game files, no tools; st
|
||||
|
||||
Run: python tests/installer_test.py
|
||||
"""
|
||||
import http.client
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
@@ -11,6 +12,7 @@ import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT))
|
||||
@@ -209,6 +211,41 @@ class ReleaseFilesTest(unittest.TestCase):
|
||||
self.assertEqual(used - set(make_release.TOOLS_FILES), set())
|
||||
|
||||
|
||||
class RenamedVersionTest(unittest.TestCase):
|
||||
"""An install the setup before 0.3.1 recorded as 1.1.1 is the 1.1.0 update (#332)."""
|
||||
|
||||
def setUp(self):
|
||||
tmp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(tmp.cleanup)
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
layout = Layout(root=root, dev=True, release='9.9', python=Path('python'), tools=root / 'tools',
|
||||
recomp=root / 'recomp', llvm=Path('llvm'), deps=Path('deps'), runtime=Path('rt'),
|
||||
runtime_lib=Path('lib'), ninja=Path('ninja'), hactool=Path('hactool'), xlate=Path('xlate'))
|
||||
state = {'format': 1, 'release': '0.2.8', 'version': '1.1.1', 'inputs': {},
|
||||
'steps': {'extract': {'key': 'recorded by 0.2.8', 'release': '0.2.8'}}}
|
||||
out = io.StringIO()
|
||||
self.ctx = runner.Context(layout, Path(tmp.name), Path(tmp.name), Reporter(out=out), SimpleNamespace(),
|
||||
state=state)
|
||||
|
||||
def test_old_name_is_read_as_1_1_0(self):
|
||||
steps.migrate_state(self.ctx)
|
||||
self.assertEqual(steps.game_version(self.ctx), '1.1.0')
|
||||
self.assertEqual(steps.version_tag(steps.game_version(self.ctx)), 'v110')
|
||||
self.assertEqual(steps.expected_update(self.ctx)['display_version'], '1.1.0')
|
||||
self.assertTrue(self.ctx.layout.patches(steps.game_version(self.ctx)).is_file())
|
||||
|
||||
def test_extraction_is_kept_under_the_new_name(self):
|
||||
steps.migrate_state(self.ctx)
|
||||
self.assertEqual(self.ctx.state['steps']['extract']['key'], steps.Extract().key(self.ctx))
|
||||
self.assertEqual(self.ctx.state['steps']['extract']['release'], '0.2.8') # full check of kept output runs
|
||||
|
||||
def test_other_versions_are_untouched(self):
|
||||
self.ctx.state['version'] = '1.4.3'
|
||||
steps.migrate_state(self.ctx)
|
||||
self.assertEqual(self.ctx.state['version'], '1.4.3')
|
||||
self.assertEqual(self.ctx.state['steps']['extract']['key'], 'recorded by 0.2.8')
|
||||
|
||||
|
||||
class LiftKeyTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
tmp = tempfile.TemporaryDirectory()
|
||||
@@ -446,7 +483,7 @@ class FontsTest(unittest.TestCase):
|
||||
|
||||
def test_all_steps_order(self):
|
||||
self.assertEqual([s.id for s in steps.all_steps()],
|
||||
['check', 'fonts', 'extract', 'lift', 'compile', 'prepare', 'cleanup'])
|
||||
['check', 'fonts', 'extract', 'lift', 'compile', 'prepare', 'dlss', 'cleanup'])
|
||||
|
||||
def test_extracts_into_the_install_folder(self):
|
||||
self.fake_tool()
|
||||
@@ -540,5 +577,79 @@ class EventsTest(unittest.TestCase):
|
||||
self.assertEqual(sorted(i for i in used if i not in catalog), [])
|
||||
|
||||
|
||||
@unittest.skipUnless(steps.os.name == 'nt', 'DLSS is Windows-only')
|
||||
class DlssTest(unittest.TestCase):
|
||||
"""The DLSS step: off without --dlss, needs an RTX GPU, keeps a player's library, verifies the download."""
|
||||
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
base = Path(self.tmp.name)
|
||||
self.dest, temp = base / 'dest', base / 'temp'
|
||||
self.dest.mkdir()
|
||||
temp.mkdir()
|
||||
self.src = base / 'nvngx_dlss.dll'
|
||||
self.src.write_bytes(b'dlss' * 1000)
|
||||
self.saved = steps.DLSS_URL, steps.DLSS_SIZE, steps.DLSS_SHA256
|
||||
steps.DLSS_URL = self.src.as_uri() # urllib reads file: URLs as it reads https: ones
|
||||
steps.DLSS_SIZE, steps.DLSS_SHA256 = 4000, steps.file_sha256(self.src)
|
||||
rtx = {'devices': [{'name': 'NVIDIA GeForce RTX 4080 SUPER', 'api': [1, 4, 0], 'type': 2}]}
|
||||
self.ctx = SimpleNamespace(dest=self.dest, temp=temp, host=rtx, reporter=Reporter(out=io.StringIO()),
|
||||
options=SimpleNamespace(dlss=True))
|
||||
|
||||
def tearDown(self):
|
||||
steps.DLSS_URL, steps.DLSS_SIZE, steps.DLSS_SHA256 = self.saved
|
||||
self.ctx.reporter.close()
|
||||
self.tmp.cleanup()
|
||||
|
||||
def warnings(self):
|
||||
return [json.loads(line)['id'] for line in self.ctx.reporter.out.getvalue().splitlines()
|
||||
if json.loads(line)['type'] == 'warning']
|
||||
|
||||
def test_off_without_the_option(self):
|
||||
self.ctx.options.dlss = False
|
||||
self.assertEqual(steps.Dlss().run(self.ctx), {})
|
||||
self.assertFalse((self.dest / 'nvngx_dlss.dll').exists())
|
||||
self.assertNotEqual(steps.Dlss().key(self.ctx), steps.Dlss().key(SimpleNamespace(options=SimpleNamespace(dlss=True))))
|
||||
|
||||
def test_downloads_and_checks_the_library(self):
|
||||
rec = steps.Dlss().run(self.ctx)
|
||||
self.assertEqual(rec, {'installed': steps.DLSS_VERSION})
|
||||
self.assertEqual((self.dest / 'nvngx_dlss.dll').read_bytes(), self.src.read_bytes())
|
||||
self.assertFalse(list(self.ctx.temp.iterdir()))
|
||||
self.assertTrue(steps.Dlss().current(self.ctx, rec))
|
||||
(self.dest / 'nvngx_dlss.dll').unlink()
|
||||
self.assertFalse(steps.Dlss().current(self.ctx, rec))
|
||||
|
||||
def test_a_wrong_file_is_not_installed_and_tried_again(self):
|
||||
steps.DLSS_SHA256 = '0' * 64
|
||||
rec = steps.Dlss().run(self.ctx)
|
||||
self.assertEqual(rec, {'failed': True})
|
||||
self.assertFalse((self.dest / 'nvngx_dlss.dll').exists())
|
||||
self.assertEqual(self.warnings(), ['warn.dlss_download_failed'])
|
||||
self.assertFalse(steps.Dlss().current(self.ctx, rec))
|
||||
|
||||
def test_a_cut_connection_warns(self):
|
||||
class Cut(io.BytesIO): # a response whose connection drops mid-file
|
||||
def read(self, n=-1):
|
||||
raise http.client.IncompleteRead(b'dl', 3998)
|
||||
|
||||
with mock.patch.object(steps.urllib.request, 'urlopen', return_value=Cut()):
|
||||
self.assertEqual(steps.Dlss().run(self.ctx), {'failed': True})
|
||||
self.assertFalse((self.dest / 'nvngx_dlss.dll').exists())
|
||||
self.assertFalse(list(self.ctx.temp.iterdir()))
|
||||
self.assertEqual(self.warnings(), ['warn.dlss_download_failed'])
|
||||
|
||||
def test_keeps_the_players_library(self):
|
||||
(self.dest / 'nvngx_dlss.dll').write_bytes(b'newer')
|
||||
self.assertEqual(steps.Dlss().run(self.ctx), {'kept': True})
|
||||
self.assertEqual((self.dest / 'nvngx_dlss.dll').read_bytes(), b'newer')
|
||||
self.assertEqual(self.warnings(), ['warn.dlss_kept'])
|
||||
|
||||
def test_skipped_without_an_rtx_gpu(self):
|
||||
self.ctx.host = {'devices': [{'name': 'AMD Radeon RX 7800 XT', 'api': [1, 3, 0], 'type': 2}]}
|
||||
self.assertEqual(steps.Dlss().run(self.ctx), {'skipped': 'no_rtx'})
|
||||
self.assertEqual(self.warnings(), [])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,173 @@
|
||||
"""tools/ncz.py and the installer's unpacking of NSZ input (installer/steps.py, Check.unpack) on small made-up files:
|
||||
an NCA with an AES-CTR section and a plain one, stored as an NCZ with one zstd stream or as NCZBLOCK blocks.
|
||||
|
||||
Run: .venv-py314\\Scripts\\python tests/ncz_test.py
|
||||
"""
|
||||
import hashlib
|
||||
import io
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from compression import zstd
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / 'tools'))
|
||||
import ncz # noqa: E402
|
||||
from installer import steps # noqa: E402
|
||||
from installer.events import Reporter # noqa: E402
|
||||
|
||||
KEY, COUNTER = bytes(range(16)), bytes(range(16, 32))
|
||||
# (offset, size, crypto type): an AES-CTR section, then a plain one
|
||||
SECTIONS = ((0x4000, 0x9000, 3), (0xD000, 0x3200, 1))
|
||||
|
||||
|
||||
def pfs0(files):
|
||||
"""An NSP of (name, data) pairs."""
|
||||
strings = b''.join(name.encode() + b'\0' for name, _ in files)
|
||||
strings += bytes(-(16 + 0x18 * len(files) + len(strings)) % 0x20)
|
||||
head = b'PFS0' + struct.pack('<III', len(files), len(strings), 0)
|
||||
offset = name_at = 0
|
||||
for name, data in files:
|
||||
head += struct.pack('<QQII', offset, len(data), name_at, 0)
|
||||
offset += len(data)
|
||||
name_at += len(name) + 1
|
||||
return head + strings + b''.join(data for _, data in files)
|
||||
|
||||
|
||||
def made_up_nca():
|
||||
"""(the NCA as stored on the console, its body after the header with the sections decrypted)."""
|
||||
rand = os.urandom
|
||||
plain = rand(0x2000) + bytes(0x7000) # half random, half compressible
|
||||
rest = bytes(0x1200) + rand(0x2000)
|
||||
counter = COUNTER[:8] + struct.pack('>Q', 0x4000 >> 4)
|
||||
secret = Cipher(algorithms.AES(KEY), modes.CTR(counter)).encryptor().update(plain)
|
||||
header = rand(0x4000)
|
||||
return header + secret + rest, plain + rest
|
||||
|
||||
|
||||
def ncz_file(nca, body, blocks=False):
|
||||
out = nca[:0x4000] + b'NCZSECTN' + struct.pack('<Q', len(SECTIONS))
|
||||
for offset, size, crypto in SECTIONS:
|
||||
out += struct.pack('<QQQQ', offset, size, crypto, 0) + KEY + COUNTER
|
||||
if not blocks:
|
||||
return out + zstd.compress(body)
|
||||
exponent = 14
|
||||
pieces = [body[i:i + (1 << exponent)] for i in range(0, len(body), 1 << exponent)]
|
||||
stored = [zstd.compress(p) if p.count(0) > len(p) // 2 else p for p in pieces] # random blocks stay as they are
|
||||
out += b'NCZBLOCK' + bytes([2, 1, 0, exponent]) + struct.pack('<IQ', len(pieces), len(body))
|
||||
out += struct.pack(f'<{len(stored)}I', *map(len, stored))
|
||||
return out + b''.join(stored)
|
||||
|
||||
|
||||
class UnpackTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.dir = Path(self.tmp.name)
|
||||
self.nca, self.body = made_up_nca()
|
||||
self.name = hashlib.sha256(self.nca).hexdigest()[:32]
|
||||
self.tik = b'ticket' * 40
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
def nsz(self, blocks=False, damage=None):
|
||||
data = ncz_file(self.nca, self.body, blocks)
|
||||
if damage:
|
||||
data = damage(bytearray(data))
|
||||
path = self.dir / 'game.nsz'
|
||||
path.write_bytes(pfs0([(self.name + '.ncz', bytes(data)), ('a.tik', self.tik)]))
|
||||
return path
|
||||
|
||||
def expected(self):
|
||||
return pfs0([(self.name + '.nca', self.nca), ('a.tik', self.tik)])
|
||||
|
||||
def test_stream_comes_back_byte_for_byte(self):
|
||||
src = self.nsz()
|
||||
self.assertEqual(ncz.compressed(src)[0], 'nsz')
|
||||
self.assertEqual(ncz.unpacked_size(src), len(self.expected()))
|
||||
seen = []
|
||||
self.assertEqual(ncz.unpack(src, self.dir / 'out.nsp', lambda d, t: seen.append((d, t))), 'nsz')
|
||||
self.assertEqual((self.dir / 'out.nsp').read_bytes(), self.expected())
|
||||
self.assertEqual(seen[-1], (len(self.expected()), len(self.expected())))
|
||||
self.assertFalse((self.dir / 'out.nsp.part').exists())
|
||||
|
||||
def test_blocks_come_back_byte_for_byte(self):
|
||||
ncz.unpack(self.nsz(blocks=True), self.dir / 'out.nsp')
|
||||
self.assertEqual((self.dir / 'out.nsp').read_bytes(), self.expected())
|
||||
|
||||
def test_plain_nsp_is_not_compressed(self):
|
||||
path = self.dir / 'plain.nsp'
|
||||
path.write_bytes(self.expected())
|
||||
self.assertIsNone(ncz.compressed(path))
|
||||
|
||||
def test_changed_byte_fails_the_hash(self):
|
||||
def flip(data):
|
||||
data[0x4000 + 16 + 0x40 * len(SECTIONS) + 0x100] ^= 1 # inside a stored (random) block
|
||||
return data
|
||||
with self.assertRaises(ncz.DamagedFile) as e:
|
||||
ncz.unpack(self.nsz(blocks=True, damage=flip), self.dir / 'out.nsp')
|
||||
self.assertIn('hash mismatch', e.exception.detail)
|
||||
|
||||
def test_cut_stream_is_damaged(self):
|
||||
with self.assertRaises(ValueError):
|
||||
ncz.unpack(self.nsz(damage=lambda d: d[:-40]), self.dir / 'out.nsp')
|
||||
self.assertFalse((self.dir / 'out.nsp').exists())
|
||||
|
||||
|
||||
class CheckUnpackTest(unittest.TestCase):
|
||||
"""Check.unpack: an NSZ base becomes <temp>/unpacked/base.nsp, a plain NSP update is read in place, a second run
|
||||
reuses the NSP."""
|
||||
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
base = Path(self.tmp.name)
|
||||
nca, body = made_up_nca()
|
||||
name = hashlib.sha256(nca).hexdigest()[:32]
|
||||
self.base, self.update = base / 'base.nsz', base / 'update.nsp'
|
||||
self.base.write_bytes(pfs0([(name + '.ncz', ncz_file(nca, body))]))
|
||||
self.update.write_bytes(pfs0([('a.tik', b'x' * 64)]))
|
||||
self.expected = pfs0([(name + '.nca', nca)])
|
||||
self.out = io.StringIO()
|
||||
self.ctx = SimpleNamespace(options=SimpleNamespace(base=str(self.base), update=str(self.update)),
|
||||
temp=base / 'temp', reporter=Reporter(out=self.out, log_path=base / 'x.log'))
|
||||
|
||||
def tearDown(self):
|
||||
self.ctx.reporter.close()
|
||||
self.tmp.cleanup()
|
||||
|
||||
def test_unpacks_the_base_and_reuses_it(self):
|
||||
steps.Check().unpack(self.ctx)
|
||||
target = self.ctx.temp / 'unpacked' / 'base.nsp'
|
||||
self.assertEqual(target.read_bytes(), self.expected)
|
||||
self.assertEqual(steps.game_file(self.ctx, 'base'), target)
|
||||
self.assertEqual(steps.game_file(self.ctx, 'update'), self.update)
|
||||
self.assertIn('"phase": "unpack_base"', self.out.getvalue())
|
||||
mtime = target.stat().st_mtime_ns
|
||||
steps.Check().unpack(self.ctx)
|
||||
self.assertEqual(target.stat().st_mtime_ns, mtime)
|
||||
|
||||
def test_content_decides_not_the_name(self):
|
||||
renamed = self.base.with_name('base.nsp')
|
||||
self.base.rename(renamed)
|
||||
self.ctx.options.base = str(renamed)
|
||||
steps.Check().unpack(self.ctx)
|
||||
self.assertEqual((self.ctx.temp / 'unpacked' / 'base.nsp').read_bytes(), self.expected)
|
||||
|
||||
def test_damaged_base_names_the_chosen_file(self):
|
||||
data = bytearray(self.base.read_bytes())
|
||||
data[-100] ^= 0xFF
|
||||
self.base.write_bytes(bytes(data))
|
||||
with self.assertRaises(steps.InstallError) as e:
|
||||
steps.Check().unpack(self.ctx)
|
||||
self.assertIn(e.exception.id, ('check.input_damaged', 'check.unpack_failed'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Build the shader translator: build/xlate/totk_xlate.exe (Ryujinx 1.3.2 + patches, NativeAOT, SPIR-V for Vulkan).
|
||||
|
||||
Copies tools/vendor/ryujinx to build/xlate/ryujinx, applies tools/xlate/patches/*.patch there (tools/vendor stays
|
||||
byte-identical; 0001-0004 are MNG's, 0005-0011 are ours), then runs `dotnet publish`. Needs the .NET 10 SDK, the MSVC build
|
||||
byte-identical; 0001-0004 are MNG's, 0005-0014 are ours), then runs `dotnet publish`. Needs the .NET 10 SDK, the MSVC build
|
||||
tools and vswhere.exe (docs/building.md). Run with .venv. On Linux it builds build/xlate/totk_xlate for linux-x64, which
|
||||
NativeAOT links with the system's clang (no MSVC); NativeAOT builds for the OS it runs on only.
|
||||
|
||||
@@ -99,8 +99,8 @@ def prepare_sources() -> Path:
|
||||
for junk in list(src.glob("src/*/obj")) + list(src.glob("src/*/bin")):
|
||||
shutil.rmtree(junk)
|
||||
patches = sorted((XLATE / "patches").glob("*.patch"))
|
||||
if len(patches) != 11:
|
||||
sys.exit(f"expected 11 patches, found {len(patches)}")
|
||||
if len(patches) != 15:
|
||||
sys.exit(f"expected 15 patches, found {len(patches)}")
|
||||
for patch in patches:
|
||||
# git apply works outside a repository when given a directory; --check first so a failure names the patch.
|
||||
for extra in (["--check"], []):
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
"""Writes runtime/dlaa_mask_spv.h: the SPIR-V of DLSS's reactive mask pass (vk_dlaa.c, mask_pass), assembled here like
|
||||
tools/make_mvobj_fs.py (no GLSL compiler on the machine this was written on).
|
||||
|
||||
The shader, as GLSL:
|
||||
|
||||
#version 450
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
layout(binding = 0) uniform sampler2D before; // the light target when the transparents start
|
||||
layout(binding = 1) uniform sampler2D after; // the light target when DLSS runs
|
||||
layout(binding = 2, r8) uniform writeonly image2D mask;
|
||||
layout(push_constant) uniform P { vec4 p; }; // render size xy, threshold z, gain w
|
||||
void main() {
|
||||
vec2 g = vec2(gl_GlobalInvocationID.xy);
|
||||
if (g.x < p.x && g.y < p.y) {
|
||||
ivec2 c = ivec2(gl_GlobalInvocationID.xy);
|
||||
vec3 b = texelFetch(before, c, 0).rgb, a = texelFetch(after, c, 0).rgb;
|
||||
vec3 d = abs(a - b);
|
||||
float dm = max(max(d.r, d.g), d.b);
|
||||
float m = max(max(max(a.r, a.g), a.b), max(max(b.r, b.g), b.b));
|
||||
float r = dm / max(m, 1e-4);
|
||||
imageStore(mask, c, vec4(clamp((r - p.z) * p.w, 0.0, 1.0)));
|
||||
}
|
||||
}
|
||||
|
||||
The mask is the share of a pixel's colour the transparents changed (particles, smoke, sparks: what has no motion
|
||||
vectors of its own), 0 where nothing was drawn over the solid scene.
|
||||
|
||||
python tools/make_dlaa_mask.py
|
||||
"""
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
OUT = ROOT / 'runtime' / 'dlaa_mask_spv.h'
|
||||
|
||||
SECTIONS = ('cap', 'ext', 'mem', 'entry', 'exec', 'dec', 'types', 'func')
|
||||
|
||||
|
||||
class Asm:
|
||||
def __init__(self):
|
||||
self.next = 1
|
||||
self.sections = {k: [] for k in SECTIONS}
|
||||
|
||||
def id(self):
|
||||
self.next += 1
|
||||
return self.next - 1
|
||||
|
||||
def op(self, sec, code, *words):
|
||||
self.sections[sec].append([(len(words) + 1) << 16 | code, *words])
|
||||
|
||||
def words(self):
|
||||
out = [0x07230203, 0x00010000, 0, self.next, 0]
|
||||
for sec in SECTIONS:
|
||||
for ins in self.sections[sec]:
|
||||
out.extend(ins)
|
||||
return out
|
||||
|
||||
|
||||
def string(s):
|
||||
b = s.encode() + b'\0'
|
||||
b += b'\0' * (-len(b) % 4)
|
||||
return list(struct.unpack('<%dI' % (len(b) // 4), b))
|
||||
|
||||
|
||||
def f32(x):
|
||||
return struct.unpack('<I', struct.pack('<f', x))[0]
|
||||
|
||||
|
||||
OpCapability, OpExtInstImport, OpExtInst, OpMemoryModel, OpEntryPoint, OpExecutionMode = 17, 11, 12, 14, 15, 16
|
||||
OpDecorate, OpMemberDecorate = 71, 72
|
||||
OpTypeVoid, OpTypeBool, OpTypeInt, OpTypeFloat, OpTypeVector, OpTypeImage, OpTypeSampledImage = 19, 20, 21, 22, 23, 25, 27
|
||||
OpTypeStruct, OpTypePointer, OpTypeFunction = 30, 32, 33
|
||||
OpConstant, OpConstantComposite, OpVariable, OpFunction, OpFunctionEnd, OpLabel, OpReturn = 43, 44, 59, 54, 56, 248, 253
|
||||
OpLoad, OpAccessChain, OpVectorShuffle, OpCompositeExtract, OpCompositeConstruct = 61, 65, 79, 81, 80
|
||||
OpImage, OpImageFetch, OpImageWrite, OpConvertUToF, OpBitcast = 100, 95, 99, 112, 124
|
||||
OpFSub, OpFMul, OpFDiv, OpFOrdLessThan, OpLogicalAnd = 131, 133, 136, 184, 167
|
||||
OpSelectionMerge, OpBranch, OpBranchConditional = 247, 249, 250
|
||||
FAbs, FMax, FClamp = 4, 40, 43 # GLSL.std.450
|
||||
UniformConstant, Input, PushConstant = 0, 1, 9
|
||||
Binding, DescriptorSet, Block, Offset, BuiltIn, NonReadable = 33, 34, 2, 35, 11, 25
|
||||
GlobalInvocationId = 28
|
||||
|
||||
a = Asm()
|
||||
a.op('cap', OpCapability, 1) # Shader
|
||||
a.op('cap', OpCapability, 49) # StorageImageExtendedFormats (r8)
|
||||
glsl = a.id()
|
||||
a.op('ext', OpExtInstImport, glsl, *string('GLSL.std.450'))
|
||||
a.op('mem', OpMemoryModel, 0, 1) # Logical GLSL450
|
||||
main = a.id()
|
||||
t_void, t_fn, t_bool, t_f, t_i, t_u = a.id(), a.id(), a.id(), a.id(), a.id(), a.id()
|
||||
t_v2, t_v3, t_v4, t_v2i, t_v2u, t_v3u = a.id(), a.id(), a.id(), a.id(), a.id(), a.id()
|
||||
t_img, t_simg, t_wimg = a.id(), a.id(), a.id()
|
||||
t_psimg, t_pwimg, t_pgid, t_pc, t_ppc, t_ppv4 = a.id(), a.id(), a.id(), a.id(), a.id(), a.id()
|
||||
v_before, v_after, v_mask, v_gid, v_p = a.id(), a.id(), a.id(), a.id(), a.id()
|
||||
c_i0, c_zero, c_one, c_eps = a.id(), a.id(), a.id(), a.id()
|
||||
|
||||
a.op('entry', OpEntryPoint, 5, main, *string('main'), v_gid) # GLCompute
|
||||
a.op('exec', OpExecutionMode, main, 17, 8, 8, 1) # LocalSize
|
||||
a.op('dec', OpDecorate, v_gid, BuiltIn, GlobalInvocationId)
|
||||
for var, binding in ((v_before, 0), (v_after, 1), (v_mask, 2)):
|
||||
a.op('dec', OpDecorate, var, DescriptorSet, 0)
|
||||
a.op('dec', OpDecorate, var, Binding, binding)
|
||||
a.op('dec', OpDecorate, v_mask, NonReadable)
|
||||
a.op('dec', OpDecorate, t_pc, Block)
|
||||
a.op('dec', OpMemberDecorate, t_pc, 0, Offset, 0)
|
||||
|
||||
T = 'types'
|
||||
a.op(T, OpTypeVoid, t_void)
|
||||
a.op(T, OpTypeFunction, t_fn, t_void)
|
||||
a.op(T, OpTypeBool, t_bool)
|
||||
a.op(T, OpTypeFloat, t_f, 32)
|
||||
a.op(T, OpTypeInt, t_i, 32, 1)
|
||||
a.op(T, OpTypeInt, t_u, 32, 0)
|
||||
a.op(T, OpTypeVector, t_v2, t_f, 2)
|
||||
a.op(T, OpTypeVector, t_v3, t_f, 3)
|
||||
a.op(T, OpTypeVector, t_v4, t_f, 4)
|
||||
a.op(T, OpTypeVector, t_v2i, t_i, 2)
|
||||
a.op(T, OpTypeVector, t_v2u, t_u, 2)
|
||||
a.op(T, OpTypeVector, t_v3u, t_u, 3)
|
||||
a.op(T, OpTypeImage, t_img, t_f, 1, 0, 0, 0, 1, 0) # 2D, sampled, format unknown
|
||||
a.op(T, OpTypeSampledImage, t_simg, t_img)
|
||||
a.op(T, OpTypeImage, t_wimg, t_f, 1, 0, 0, 0, 2, 15) # 2D, storage, R8
|
||||
a.op(T, OpTypePointer, t_psimg, UniformConstant, t_simg)
|
||||
a.op(T, OpTypePointer, t_pwimg, UniformConstant, t_wimg)
|
||||
a.op(T, OpTypePointer, t_pgid, Input, t_v3u)
|
||||
a.op(T, OpTypeStruct, t_pc, t_v4)
|
||||
a.op(T, OpTypePointer, t_ppc, PushConstant, t_pc)
|
||||
a.op(T, OpTypePointer, t_ppv4, PushConstant, t_v4)
|
||||
a.op(T, OpConstant, t_i, c_i0, 0)
|
||||
a.op(T, OpConstant, t_f, c_zero, f32(0.0))
|
||||
a.op(T, OpConstant, t_f, c_one, f32(1.0))
|
||||
a.op(T, OpConstant, t_f, c_eps, f32(1e-4))
|
||||
a.op(T, OpVariable, t_psimg, v_before, UniformConstant)
|
||||
a.op(T, OpVariable, t_psimg, v_after, UniformConstant)
|
||||
a.op(T, OpVariable, t_pwimg, v_mask, UniformConstant)
|
||||
a.op(T, OpVariable, t_pgid, v_gid, Input)
|
||||
a.op(T, OpVariable, t_ppc, v_p, PushConstant)
|
||||
|
||||
F = 'func'
|
||||
|
||||
|
||||
def ins(code, rtype, *args):
|
||||
r = a.id()
|
||||
a.op(F, code, rtype, r, *args)
|
||||
return r
|
||||
|
||||
|
||||
def ext(rtype, inst, *args):
|
||||
return ins(OpExtInst, rtype, glsl, inst, *args)
|
||||
|
||||
|
||||
l_entry, l_body, l_merge = a.id(), a.id(), a.id()
|
||||
a.op(F, OpFunction, t_void, main, 0, t_fn)
|
||||
a.op(F, OpLabel, l_entry)
|
||||
pp = ins(OpAccessChain, t_ppv4, v_p, c_i0)
|
||||
p = ins(OpLoad, t_v4, pp)
|
||||
gid = ins(OpLoad, t_v3u, v_gid)
|
||||
gu = ins(OpVectorShuffle, t_v2u, gid, gid, 0, 1)
|
||||
gx = ins(OpConvertUToF, t_f, ins(OpCompositeExtract, t_u, gid, 0))
|
||||
gy = ins(OpConvertUToF, t_f, ins(OpCompositeExtract, t_u, gid, 1))
|
||||
px = ins(OpCompositeExtract, t_f, p, 0)
|
||||
py = ins(OpCompositeExtract, t_f, p, 1)
|
||||
inside = ins(OpLogicalAnd, t_bool, ins(OpFOrdLessThan, t_bool, gx, px), ins(OpFOrdLessThan, t_bool, gy, py))
|
||||
a.op(F, OpSelectionMerge, l_merge, 0)
|
||||
a.op(F, OpBranchConditional, inside, l_body, l_merge)
|
||||
a.op(F, OpLabel, l_body)
|
||||
c = ins(OpBitcast, t_v2i, gu)
|
||||
|
||||
|
||||
def fetch(var):
|
||||
img = ins(OpImage, t_img, ins(OpLoad, t_simg, var))
|
||||
texel = ins(OpImageFetch, t_v4, img, c, 2, c_i0) # Lod 0
|
||||
return ins(OpVectorShuffle, t_v3, texel, texel, 0, 1, 2)
|
||||
|
||||
|
||||
def maxc(v):
|
||||
x, y, z = (ins(OpCompositeExtract, t_f, v, k) for k in range(3))
|
||||
return ext(t_f, FMax, ext(t_f, FMax, x, y), z)
|
||||
|
||||
|
||||
b = fetch(v_before)
|
||||
af = fetch(v_after)
|
||||
d = ext(t_v3, FAbs, ins(OpFSub, t_v3, af, b))
|
||||
dm = maxc(d)
|
||||
m = ext(t_f, FMax, ext(t_f, FMax, maxc(af), maxc(b)), c_eps)
|
||||
r = ins(OpFDiv, t_f, dm, m)
|
||||
thr = ins(OpCompositeExtract, t_f, p, 2)
|
||||
gain = ins(OpCompositeExtract, t_f, p, 3)
|
||||
k = ext(t_f, FClamp, ins(OpFMul, t_f, ins(OpFSub, t_f, r, thr), gain), c_zero, c_one)
|
||||
out = ins(OpCompositeConstruct, t_v4, k, k, k, k)
|
||||
a.op(F, OpImageWrite, ins(OpLoad, t_wimg, v_mask), c, out)
|
||||
a.op(F, OpBranch, l_merge)
|
||||
a.op(F, OpLabel, l_merge)
|
||||
a.op(F, OpReturn)
|
||||
a.op(F, OpFunctionEnd)
|
||||
|
||||
w = a.words()
|
||||
lines = [', '.join(f'0x{x:08x}' for x in w[i:i + 8]) for i in range(0, len(w), 8)]
|
||||
OUT.write_text(
|
||||
'// Generated by tools/make_dlaa_mask.py: DLSS\'s reactive mask pass (vk_dlaa.c mask_pass). Do not edit.\n'
|
||||
'#pragma once\n#include <stdint.h>\n'
|
||||
'static const uint32_t dlaa_mask_spv[] = {\n ' + ',\n '.join(lines) + '\n};\n', newline='\n')
|
||||
print(f'{OUT}: {len(w)} words')
|
||||
@@ -0,0 +1,163 @@
|
||||
"""Writes runtime/vk_mvobj_fs.h: the SPIR-V of the per-object motion fragment shader (vk_mvobj.c), assembled here
|
||||
because it must be patched at run time (the Location of its input follows the vertex shader variant) and is small.
|
||||
|
||||
The shader, as GLSL:
|
||||
|
||||
#version 450
|
||||
layout(location = 0) in vec4 prev; // last frame's clip position (patch 0015); Location patched
|
||||
layout(set = 0, binding = 1, std140) uniform P { vec4 p; }; // render size xy, this frame's jitter zw (pixels)
|
||||
layout(location = 0) out vec4 o;
|
||||
void main() {
|
||||
vec2 win = gl_FragCoord.xy - p.zw; // this frame's window position, without the jitter
|
||||
vec2 pn = prev.xy / prev.w;
|
||||
vec2 pwin = vec2((pn.x * 0.5 + 0.5) * p.x, (0.5 - pn.y * 0.5) * p.y);
|
||||
o = prev.w > 1e-6 ? vec4(pwin - win, 0.0, 0.0) : vec4(0.0);
|
||||
}
|
||||
|
||||
The vector is last frame's window position minus this frame's, in render pixels, as runtime/shaders/dlaa_mv.comp
|
||||
writes it. Its descriptor set and binding are renumbered by vk_pipeline.c like a translated shader's (the .res.json
|
||||
text in vk_mvobj.c names binding 1 as slot 3).
|
||||
|
||||
python tools/make_mvobj_fs.py
|
||||
"""
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
OUT = ROOT / 'runtime' / 'vk_mvobj_fs.h'
|
||||
|
||||
|
||||
class Asm:
|
||||
def __init__(self):
|
||||
self.next = 1
|
||||
self.sections = {k: [] for k in ('cap', 'mem', 'entry', 'exec', 'dec', 'types', 'func')}
|
||||
self.loc_word = None # index of the patched Location literal in the final words
|
||||
|
||||
def id(self):
|
||||
self.next += 1
|
||||
return self.next - 1
|
||||
|
||||
def op(self, sec, code, *words):
|
||||
self.sections[sec].append([(len(words) + 1) << 16 | code, *words])
|
||||
return self.sections[sec][-1]
|
||||
|
||||
def words(self):
|
||||
out = [0x07230203, 0x00010000, 0, self.next, 0]
|
||||
for sec in ('cap', 'mem', 'entry', 'exec', 'dec', 'types', 'func'):
|
||||
for ins in self.sections[sec]:
|
||||
if ins is self.loc_ins:
|
||||
self.loc_word = len(out) + 3
|
||||
out.extend(ins)
|
||||
return out
|
||||
|
||||
|
||||
def string(s):
|
||||
b = s.encode() + b'\0'
|
||||
b += b'\0' * (-len(b) % 4)
|
||||
return list(struct.unpack('<%dI' % (len(b) // 4), b))
|
||||
|
||||
|
||||
def f32(x):
|
||||
return struct.unpack('<I', struct.pack('<f', x))[0]
|
||||
|
||||
|
||||
a = Asm()
|
||||
OpCapability, OpMemoryModel, OpEntryPoint, OpExecutionMode, OpDecorate, OpMemberDecorate = 17, 14, 15, 16, 71, 72
|
||||
OpTypeVoid, OpTypeFunction, OpTypeFloat, OpTypeVector, OpTypePointer, OpTypeStruct, OpTypeInt, OpTypeBool = 19, 33, 22, 23, 32, 30, 21, 20
|
||||
OpConstant, OpConstantComposite, OpVariable, OpFunction, OpLabel, OpLoad, OpStore, OpReturn, OpFunctionEnd = 43, 44, 59, 54, 248, 61, 62, 253, 56
|
||||
OpAccessChain, OpVectorShuffle, OpCompositeExtract, OpCompositeConstruct, OpFSub, OpFMul, OpFAdd, OpFDiv = 65, 79, 81, 80, 131, 133, 129, 136
|
||||
OpFOrdGreaterThan, OpSelect = 186, 169
|
||||
Input, Output, Uniform = 1, 3, 2
|
||||
Location, Binding, DescriptorSet, Block, Offset, BuiltIn = 30, 33, 34, 2, 35, 11
|
||||
FragCoord = 15
|
||||
|
||||
a.op('cap', OpCapability, 1) # Shader
|
||||
a.op('mem', OpMemoryModel, 0, 1) # Logical GLSL450
|
||||
main = a.id()
|
||||
t_void, t_fn, t_f, t_v2, t_v4, t_u = a.id(), a.id(), a.id(), a.id(), a.id(), a.id()
|
||||
t_bool = a.id()
|
||||
t_pin, t_pout, t_struct, t_pu, t_pf_u = a.id(), a.id(), a.id(), a.id(), a.id()
|
||||
v_prev, v_frag, v_out, v_p = a.id(), a.id(), a.id(), a.id()
|
||||
c_half, c_zero, c_eps, c_u0, c_vzero = a.id(), a.id(), a.id(), a.id(), a.id()
|
||||
t_pv4u = a.id()
|
||||
a.op('entry', OpEntryPoint, 4, main, *string('main'), v_prev, v_frag, v_out) # Fragment
|
||||
a.op('exec', OpExecutionMode, main, 7) # OriginUpperLeft
|
||||
a.loc_ins = a.op('dec', OpDecorate, v_prev, Location, 0)
|
||||
a.op('dec', OpDecorate, v_frag, BuiltIn, FragCoord)
|
||||
a.op('dec', OpDecorate, v_out, Location, 0)
|
||||
a.op('dec', OpDecorate, t_struct, Block)
|
||||
a.op('dec', OpMemberDecorate, t_struct, 0, Offset, 0)
|
||||
a.op('dec', OpDecorate, v_p, DescriptorSet, 0)
|
||||
a.op('dec', OpDecorate, v_p, Binding, 1)
|
||||
a.op('types', OpTypeVoid, t_void)
|
||||
a.op('types', OpTypeFunction, t_fn, t_void)
|
||||
a.op('types', OpTypeFloat, t_f, 32)
|
||||
a.op('types', OpTypeVector, t_v2, t_f, 2)
|
||||
a.op('types', OpTypeVector, t_v4, t_f, 4)
|
||||
a.op('types', OpTypeInt, t_u, 32, 0)
|
||||
a.op('types', OpTypeBool, t_bool)
|
||||
a.op('types', OpTypePointer, t_pin, Input, t_v4)
|
||||
a.op('types', OpTypePointer, t_pout, Output, t_v4)
|
||||
a.op('types', OpTypeStruct, t_struct, t_v4)
|
||||
a.op('types', OpTypePointer, t_pu, Uniform, t_struct)
|
||||
a.op('types', OpTypePointer, t_pv4u, Uniform, t_v4)
|
||||
a.op('types', OpConstant, t_f, c_half, f32(0.5))
|
||||
a.op('types', OpConstant, t_f, c_zero, f32(0.0))
|
||||
a.op('types', OpConstant, t_f, c_eps, f32(1e-6))
|
||||
a.op('types', OpConstant, t_u, c_u0, 0)
|
||||
a.op('types', OpConstantComposite, t_v4, c_vzero, c_zero, c_zero, c_zero, c_zero)
|
||||
a.op('types', OpVariable, t_pin, v_prev, Input)
|
||||
a.op('types', OpVariable, t_pin, v_frag, Input)
|
||||
a.op('types', OpVariable, t_pout, v_out, Output)
|
||||
a.op('types', OpVariable, t_pu, v_p, Uniform)
|
||||
|
||||
F = 'func'
|
||||
a.op(F, OpFunction, t_void, main, 0, t_fn)
|
||||
a.op(F, OpLabel, a.id())
|
||||
pp = a.id(); a.op(F, OpAccessChain, t_pv4u, pp, v_p, c_u0)
|
||||
p = a.id(); a.op(F, OpLoad, t_v4, p, pp)
|
||||
prev = a.id(); a.op(F, OpLoad, t_v4, prev, v_prev)
|
||||
frag = a.id(); a.op(F, OpLoad, t_v4, frag, v_frag)
|
||||
fxy = a.id(); a.op(F, OpVectorShuffle, t_v2, fxy, frag, frag, 0, 1)
|
||||
jit = a.id(); a.op(F, OpVectorShuffle, t_v2, jit, p, p, 2, 3)
|
||||
win = a.id(); a.op(F, OpFSub, t_v2, win, fxy, jit)
|
||||
px, py, pw = a.id(), a.id(), a.id()
|
||||
a.op(F, OpCompositeExtract, t_f, px, prev, 0)
|
||||
a.op(F, OpCompositeExtract, t_f, py, prev, 1)
|
||||
a.op(F, OpCompositeExtract, t_f, pw, prev, 3)
|
||||
nx, ny = a.id(), a.id()
|
||||
a.op(F, OpFDiv, t_f, nx, px, pw)
|
||||
a.op(F, OpFDiv, t_f, ny, py, pw)
|
||||
sx, sy = a.id(), a.id()
|
||||
a.op(F, OpCompositeExtract, t_f, sx, p, 0)
|
||||
a.op(F, OpCompositeExtract, t_f, sy, p, 1)
|
||||
# x: (nx * 0.5 + 0.5) * sx
|
||||
x1, x2, x3 = a.id(), a.id(), a.id()
|
||||
a.op(F, OpFMul, t_f, x1, nx, c_half)
|
||||
a.op(F, OpFAdd, t_f, x2, x1, c_half)
|
||||
a.op(F, OpFMul, t_f, x3, x2, sx)
|
||||
# y: (0.5 - ny * 0.5) * sy
|
||||
y1, y2, y3 = a.id(), a.id(), a.id()
|
||||
a.op(F, OpFMul, t_f, y1, ny, c_half)
|
||||
a.op(F, OpFSub, t_f, y2, c_half, y1)
|
||||
a.op(F, OpFMul, t_f, y3, y2, sy)
|
||||
wx, wy = a.id(), a.id()
|
||||
a.op(F, OpCompositeExtract, t_f, wx, win, 0)
|
||||
a.op(F, OpCompositeExtract, t_f, wy, win, 1)
|
||||
mx, my = a.id(), a.id()
|
||||
a.op(F, OpFSub, t_f, mx, x3, wx)
|
||||
a.op(F, OpFSub, t_f, my, y3, wy)
|
||||
mv = a.id(); a.op(F, OpCompositeConstruct, t_v4, mv, mx, my, c_zero, c_zero)
|
||||
ok = a.id(); a.op(F, OpFOrdGreaterThan, t_bool, ok, pw, c_eps)
|
||||
res = a.id(); a.op(F, OpSelect, t_v4, res, ok, mv, c_vzero)
|
||||
a.op(F, OpStore, v_out, res)
|
||||
a.op(F, OpReturn)
|
||||
a.op(F, OpFunctionEnd)
|
||||
|
||||
w = a.words()
|
||||
lines = [', '.join(f'0x{x:08x}' for x in w[i:i + 8]) for i in range(0, len(w), 8)]
|
||||
OUT.write_text(
|
||||
'// Generated by tools/make_mvobj_fs.py: the per-object motion fragment shader (vk_mvobj.c). Do not edit.\n'
|
||||
f'#define MVOBJ_FS_LOCATION_WORD {a.loc_word} // the input Location literal, patched to the variant\'s\n'
|
||||
'static const uint32_t mvobj_fs_spv[] = {\n ' + ',\n '.join(lines) + '\n};\n', newline='\n')
|
||||
print(f'{OUT}: {len(w)} words, Location at word {a.loc_word}')
|
||||
@@ -105,9 +105,10 @@ LLVM_SKIP_DIRS = {'aarch64-w64-mingw32', 'armv7-w64-mingw32', 'i686-w64-mingw32'
|
||||
LLVM_SKIP_SUFFIXES = {'.idl', '.tlb'}
|
||||
|
||||
# tools/ files the installer steps run (and what they import), copied from the repo.
|
||||
TOOLS_FILES = ['build_game.py', 'build_shaders.py', 'button_icons.py', 'check_content.py', 'check_isa.py', 'extract.py', 'hostmem.py',
|
||||
'make_romfs_patches.py', 'pack_shader_cache.py', 'prepare_shaders.py', 'romfs_index.py', 'shader_census.py',
|
||||
'shader_list.py', 'ultrawide_layout.py', 'cheats.py', 'exefs_patches.py']
|
||||
TOOLS_FILES = ['build_game.py', 'build_shaders.py', 'button_icons.py', 'check_content.py', 'check_isa.py', 'extract.py',
|
||||
'hostmem.py', 'make_romfs_patches.py', 'ncz.py', 'pack_shader_cache.py', 'prepare_shaders.py',
|
||||
'romfs_index.py', 'shader_census.py', 'shader_list.py', 'ultrawide_layout.py', 'cheats.py',
|
||||
'exefs_patches.py']
|
||||
TOOLS_DIRS = ['vendor/macnativegames', 'data'] # data/: expected hashes
|
||||
RUNTIME_SUFFIXES = {'.h', '.inc', '.ico'} # .ico: the installer window icon
|
||||
|
||||
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
"""Unpack an NSZ or XCZ (a game file compressed by the nsz tool) into a plain NSP.
|
||||
|
||||
An NSZ is an NSP whose NCAs are stored as NCZ files: the first 0x4000 bytes of the NCA (its encrypted header) as they
|
||||
are, then an NCZSECTN table of the NCA's sections with the AES-CTR key and counter of each, then the rest of the NCA
|
||||
decrypted and compressed with zstd, either as one stream or as an NCZBLOCK table of independently compressed blocks.
|
||||
An XCZ is an XCI whose secure partition holds NCZ files. Unpacking encrypts the sections again, so every NCA comes back
|
||||
byte for byte; its file name is the first half of its SHA-256, which is checked. The output is an NSP of the secure
|
||||
partition (an XCZ) or of the NSZ's files, with .ncz renamed to .nca. Format: nicoboss/nsz, nsz/NszDecompressor.py
|
||||
(__decompressNcz) and nsz/BlockDecompressorReader.py; this is our own code.
|
||||
|
||||
usage: ncz.py <file.nsz|file.xcz> <out.nsp>. Runs under .venv-py314 (compression.zstd).
|
||||
"""
|
||||
import hashlib
|
||||
import io
|
||||
import struct
|
||||
import sys
|
||||
from compression import zstd
|
||||
from pathlib import Path
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent / 'vendor' / 'macnativegames'))
|
||||
from extract_exefs import container_entries, read_at, require # noqa: E402
|
||||
|
||||
HEADER = 0x4000 # the NCA header, kept encrypted and uncompressed
|
||||
CHUNK = 1 << 20
|
||||
CRYPTO_CTR = (3, 4) # AES-CTR and BKTR sections; the other types are stored as they are
|
||||
|
||||
|
||||
class DamagedFile(ValueError):
|
||||
"""An NCZ whose unpacked NCA does not match its name's hash, or whose data ends early."""
|
||||
|
||||
def __init__(self, path, detail):
|
||||
self.path, self.detail = Path(path), detail
|
||||
super().__init__(f'{self.path} is damaged or incomplete ({detail})')
|
||||
|
||||
|
||||
def compressed(path):
|
||||
"""('nsz' or 'xcz', entries) for a game file holding NCZ files, else None. Raises ValueError for a file that is
|
||||
neither an NSP nor an XCI."""
|
||||
with Path(path).open('rb') as f:
|
||||
kind, entries = container_entries(f, Path(path).stat().st_size)
|
||||
if not any(name.lower().endswith('.ncz') for name, _, _ in entries):
|
||||
return None
|
||||
return ('nsz' if kind == 'nsp' else 'xcz'), entries
|
||||
|
||||
|
||||
def sections(f, start):
|
||||
"""[(offset, size, crypto type, key, counter)] of the NCZ at start, and the offset of its data after the table."""
|
||||
head = read_at(f, start + HEADER, 16)
|
||||
require(head[:8] == b'NCZSECTN', 'NCZ without its NCZSECTN table')
|
||||
count = struct.unpack_from('<Q', head, 8)[0]
|
||||
require(0 < count <= 0x10000, 'Invalid NCZ section count')
|
||||
raw = read_at(f, start + HEADER + 16, count * 0x40)
|
||||
out = []
|
||||
for i in range(count):
|
||||
offset, size, crypto = struct.unpack_from('<QQQ', raw, i * 0x40)
|
||||
out.append((offset, size, crypto, raw[i * 0x40 + 0x20:i * 0x40 + 0x30], raw[i * 0x40 + 0x30:i * 0x40 + 0x40]))
|
||||
out.sort()
|
||||
require(out[0][0] >= HEADER, 'NCZ section inside the NCA header')
|
||||
for (a, size, *_), (b, *_) in zip(out, out[1:]):
|
||||
require(a + size <= b, 'Overlapping NCZ sections')
|
||||
return out, start + HEADER + 16 + count * 0x40
|
||||
|
||||
|
||||
def nca_size(f, start):
|
||||
table, _ = sections(f, start)
|
||||
return table[-1][0] + table[-1][1]
|
||||
|
||||
|
||||
def layout(source):
|
||||
"""(entries, NSP header, NSP size) of the NSP that unpack writes for the NSZ or XCZ source: its files in order,
|
||||
.ncz renamed to .nca, the PFS0 table padded to 0x20 bytes."""
|
||||
_, entries = compressed(source)
|
||||
with Path(source).open('rb') as f:
|
||||
sizes = [nca_size(f, start) if name.lower().endswith('.ncz') else length for name, start, length in entries]
|
||||
names = [name[:-4] + '.nca' if name.lower().endswith('.ncz') else name for name, _, _ in entries]
|
||||
strings = b''.join(n.encode('utf-8') + b'\0' for n in names)
|
||||
strings += bytes(-(16 + 0x18 * len(names) + len(strings)) % 0x20)
|
||||
head = b'PFS0' + struct.pack('<III', len(names), len(strings), 0)
|
||||
offset, name_at = 0, 0
|
||||
for name, size in zip(names, sizes):
|
||||
head += struct.pack('<QQII', offset, size, name_at, 0)
|
||||
offset += size
|
||||
name_at += len(name.encode('utf-8')) + 1
|
||||
head += strings
|
||||
return entries, head, len(head) + offset
|
||||
|
||||
|
||||
def unpacked_size(source):
|
||||
"""Bytes of the NSP that unpack writes for the NSZ or XCZ source."""
|
||||
return layout(source)[2]
|
||||
|
||||
|
||||
class Slice(io.RawIOBase):
|
||||
"""Bytes start..end of a file, for zstd.ZstdFile: a stream may hold several frames and must stop at the entry's
|
||||
end."""
|
||||
|
||||
def __init__(self, f, start, end):
|
||||
self.f, self.pos, self.end = f, start, end
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
def readinto(self, buffer):
|
||||
n = min(len(buffer), self.end - self.pos)
|
||||
if n <= 0:
|
||||
return 0
|
||||
self.f.seek(self.pos)
|
||||
data = self.f.read(n)
|
||||
buffer[:len(data)] = data
|
||||
self.pos += len(data)
|
||||
return len(data)
|
||||
|
||||
|
||||
def blocks(f, pos, end, total):
|
||||
"""The NCZBLOCK data at pos, decompressed, in pieces. A block whose stored size is not smaller than its
|
||||
decompressed size is stored as it is."""
|
||||
head = read_at(f, pos, 24)
|
||||
exponent, count = head[11], struct.unpack_from('<I', head, 12)[0]
|
||||
size = struct.unpack_from('<Q', head, 16)[0]
|
||||
require(head[8] == 2 and head[9] == 1 and 14 <= exponent <= 32, 'Unsupported NCZBLOCK header')
|
||||
require(size == total and count == -(-size // (1 << exponent)), 'NCZBLOCK sizes do not match the sections')
|
||||
stored = struct.unpack_from(f'<{count}I', read_at(f, pos + 24, 4 * count))
|
||||
pos += 24 + 4 * count
|
||||
for i, length in enumerate(stored):
|
||||
plain = min(1 << exponent, size - (i << exponent))
|
||||
require(pos + length <= end, 'Truncated NCZ block')
|
||||
data = read_at(f, pos, length)
|
||||
pos += length
|
||||
yield zstd.decompress(data) if length < plain else data
|
||||
|
||||
|
||||
def stream(f, pos, end, total):
|
||||
"""The decompressed NCA body after the header, in pieces of up to CHUNK bytes."""
|
||||
if read_at(f, pos, 8) == b'NCZBLOCK':
|
||||
yield from blocks(f, pos, end, total)
|
||||
return
|
||||
with zstd.ZstdFile(io.BufferedReader(Slice(f, pos, end), CHUNK)) as z:
|
||||
while data := z.read(CHUNK):
|
||||
yield data
|
||||
|
||||
|
||||
def unpack_ncz(f, start, end, out, name, source, progress):
|
||||
"""Write the NCA of the NCZ at start..end to out; check it against name's hash. progress(bytes) after each piece."""
|
||||
table, pos = sections(f, start)
|
||||
total = table[-1][0] + table[-1][1]
|
||||
digest = hashlib.sha256()
|
||||
header = read_at(f, start, HEADER)
|
||||
out.write(header)
|
||||
digest.update(header)
|
||||
progress(HEADER)
|
||||
at = HEADER
|
||||
pending = b''
|
||||
try:
|
||||
pieces = stream(f, pos, end, total - HEADER)
|
||||
for piece in pieces:
|
||||
pending += piece
|
||||
while pending and at < total:
|
||||
section = next((s for s in table if s[0] <= at < s[0] + s[1]), None)
|
||||
stop = section[0] + section[1] if section else next((s[0] for s in table if s[0] > at), total)
|
||||
n = min(len(pending), stop - at)
|
||||
crypto = section and section[2] in CRYPTO_CTR
|
||||
if crypto and at + n < stop:
|
||||
n -= n % 16 # the counter of the next piece starts at a whole AES block
|
||||
if not n:
|
||||
break
|
||||
data, pending = pending[:n], pending[n:]
|
||||
if crypto:
|
||||
require(at % 16 == 0, 'Unaligned NCZ section')
|
||||
counter = section[4][:8] + struct.pack('>Q', at >> 4)
|
||||
data = Cipher(algorithms.AES(section[3]), modes.CTR(counter)).encryptor().update(data)
|
||||
out.write(data)
|
||||
digest.update(data)
|
||||
at += n
|
||||
progress(n)
|
||||
if at == total:
|
||||
break
|
||||
except (zstd.ZstdError, EOFError) as e: # EOFError: the stream ends before its end-of-frame marker
|
||||
raise DamagedFile(source, f'{name}: {e}') from None
|
||||
if at != total or pending:
|
||||
raise DamagedFile(source, f'{name}: {at - HEADER:,} of {total - HEADER:,} bytes')
|
||||
stem = name[:-4]
|
||||
if len(stem) == 32 and digest.hexdigest()[:32] != stem.lower():
|
||||
raise DamagedFile(source, f'{name}: hash mismatch')
|
||||
|
||||
|
||||
def unpack(source, target, progress=lambda done, total: None):
|
||||
"""Write the NSP of the NSZ or XCZ source to target (through target.part). progress(done, total) in bytes."""
|
||||
source, target = Path(source), Path(target)
|
||||
kind = compressed(source)[0]
|
||||
entries, head, total = layout(source)
|
||||
part = target.with_name(target.name + '.part')
|
||||
done = 0
|
||||
|
||||
def step(n):
|
||||
nonlocal done
|
||||
done += n
|
||||
progress(done, total)
|
||||
|
||||
with source.open('rb') as f, part.open('wb') as out:
|
||||
out.write(head)
|
||||
step(len(head))
|
||||
for name, start, length in entries:
|
||||
if name.lower().endswith('.ncz'):
|
||||
unpack_ncz(f, start, start + length, out, name, source, step)
|
||||
continue
|
||||
for at in range(start, start + length, CHUNK):
|
||||
data = read_at(f, at, min(CHUNK, start + length - at))
|
||||
out.write(data)
|
||||
step(len(data))
|
||||
part.replace(target)
|
||||
return kind
|
||||
|
||||
|
||||
def main():
|
||||
source, target = sys.argv[1:3]
|
||||
shown = [-1]
|
||||
|
||||
def progress(done, total):
|
||||
percent = done * 100 // total
|
||||
if percent != shown[0]:
|
||||
shown[0] = percent
|
||||
print(f'\r{percent}%', end='', flush=True)
|
||||
|
||||
kind = unpack(source, target, progress)
|
||||
print(f'\n{kind}: wrote {target}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -32,9 +32,15 @@ MARKER_ROOT = {
|
||||
'AttentionBalloon_00': ('C_Capture_00', 'N_InOut_00'), # N_PosRoot_00 moves; N_InOut_00 animates y and alpha
|
||||
'AttentionLockOn_00': ('N_Offset_00',), # N_PosRoot_00 moves; N_Offset_00 animates y
|
||||
'EnemyInfo_00': ('Pa_EnemyLife_00', 'Pa_EnemyNotice_00'), # the parts move; N_EnemyLifeArea_* stay screen-sized
|
||||
'BalloonMessage_00': ('Pa_BalloonMessage_00',),
|
||||
'BalloonMessage_00': (), # the speech balloon part is pooled: its scale goes inside, see BALLOON_PART
|
||||
}
|
||||
|
||||
# Speech balloons are pooled: the game clones Pa_BalloonMessage_00 into Pa_BalloonMessage_%02d at run time, and the clones
|
||||
# don't keep a scale set on the template part pane, so they drew at full canvas width. The scale goes on the part
|
||||
# layout's top panes instead (their offset scaled with them, so they sit where a scaled root would put them); its
|
||||
# animations only key alpha, visibility and N_Loop_00's y. (pane, x, y) as shipped.
|
||||
BALLOON_PART = (('C_CaptureBase_00', 226.0, 20.0), ('N_InOut_00', 30.0, -10.0))
|
||||
|
||||
def edits(k, hud_corner):
|
||||
"""(layout, pane, field, value) for the aspect: k = 16/9 over the aspect (the root's scale), w = how far a 1920-wide
|
||||
layout's edge moves out in its own units (960 * (1 / k - 1)). OFF moves a pane out of sight."""
|
||||
@@ -67,6 +73,8 @@ def edits(k, hud_corner):
|
||||
('PaRecipeCardList_00', 'P_BG_00', 'scale_x', wide),
|
||||
('SaveLoadIcon_00', 'RootPane', 'x', -960 * (1 - k)),
|
||||
('ShopMenu_00', 'N_BG_00', 'scale_x', wide), ('StaffRoll_00', 'N_BG_00', 'scale_x', wide),
|
||||
*[e for pane, x, _ in BALLOON_PART for e in (('PaBalloonMessage_00', pane, 'scale_x', k),
|
||||
('PaBalloonMessage_00', pane, 'x', x * k))],
|
||||
]
|
||||
# Menus framed by two side decorations, a header and a background: the frame stays at the screen's edges.
|
||||
for layout, deco in (('PauseMenuOverlay_00', 'P_Deco{}_00'), ('SystemLoadList_00', 'P_Deco{}_00'),
|
||||
@@ -117,7 +125,9 @@ def tall_edits(k):
|
||||
*[('CameraPointer_00', p, 'scale_y', tall) for p in ('N_InOutScope_00', 'Pa_CameraFocus_00', 'S_Scissor_00',
|
||||
'W_Shadow_01')],
|
||||
('CameraSystemWindow_00', 'N_InOut_00', 'scale_y', tall), ('MessageLog_01', 'P_Capture_00', 'scale_y', tall),
|
||||
*[(layout, 'N_SystemBG_00', 'scale_y', tall) for layout in ('SystemActionGuide_00',) + SYSTEM_MENUS]]
|
||||
*[(layout, 'N_SystemBG_00', 'scale_y', tall) for layout in ('SystemActionGuide_00',) + SYSTEM_MENUS],
|
||||
*[e for pane, _, y in BALLOON_PART for e in (('PaBalloonMessage_00', pane, 'scale_y', k),
|
||||
('PaBalloonMessage_00', pane, 'y', y * k))]]
|
||||
# The HUD at the screen's top and bottom edges instead of the middle 16:9
|
||||
out += [(name, 'RootPane', 'y', 540 * (1 - k)) for name in (
|
||||
'AppAlbum_00', 'AppPictureBook_00', 'ChallengeLog_00', 'Challenge_00', 'InstantTipsWithPict_00', 'InstantTips_00',
|
||||
|
||||
@@ -117,6 +117,12 @@ static class SwshXlate
|
||||
public int QueryHostStorageBufferOffsetAlignment() => _job["sbuf_align"] is JsonNode n ? (int)n : 16;
|
||||
// "subgroup": n: lanes per subgroup when the host cannot pin 32 (AMD GCN/Vega run 64): ballots and votes span n lanes.
|
||||
public int QueryHostSubgroupSize() => _job["subgroup"] is JsonNode n ? (int)n : 32;
|
||||
// "mvprev": true: the vertex shader also writes last frame's position, computed from slots bank + 15 in place of
|
||||
// the user banks c3-c16 (patch 0015, runtime/vk_mvprev.c); res.json's "mvprev" names the output location (-1:
|
||||
// none) and "mvprev_banks" the banks the position depends on (bit n: cn).
|
||||
public bool QueryMotionPrevious() => _job["mvprev"] is JsonValue v && v.TryGetValue<bool>(out bool b) && b;
|
||||
public int MotionPreviousLocation = -1, MotionPreviousBanks;
|
||||
public void ReportMotionPrevious(int location, int banks) => (MotionPreviousLocation, MotionPreviousBanks) = (location, banks);
|
||||
}
|
||||
|
||||
|
||||
@@ -140,6 +146,7 @@ static class SwshXlate
|
||||
["uses_frag_coord"] = p.Info.UsesFragCoord, ["frag_out_map"] = p.Info.FragmentOutputMap,
|
||||
["tex_queried"] = new JsonArray(acc.TexQueried.Select(x => (JsonNode)x).ToArray()), ["img_queried"] = new JsonArray(acc.ImgQueried.Select(x => (JsonNode)x).ToArray()),
|
||||
};
|
||||
if (acc.QueryMotionPrevious()) (res["mvprev"], res["mvprev_banks"]) = (acc.MotionPreviousLocation, acc.MotionPreviousBanks);
|
||||
foreach (var b in p.Info.CBuffers) res["cbufs"].AsArray().Add((JsonNode)new JsonObject { ["binding"] = b.Binding, ["slot"] = b.Slot });
|
||||
foreach (var b in p.Info.SBuffers) res["sbufs"].AsArray().Add((JsonNode)new JsonObject { ["binding"] = b.Binding, ["slot"] = b.Slot, ["cb_slot"] = b.SbCbSlot, ["cb_offset"] = b.SbCbOffset, ["flags"] = (int)b.Flags });
|
||||
foreach (var (list, name) in new[] { (p.Info.Textures, "textures"), (p.Info.Images, "images") })
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitBitfield.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitBitfield.cs
|
||||
@@ -142,13 +142,46 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
|
||||
Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8));
|
||||
|
||||
+ // TOTK: Maxwell's BFE (as PTX bfe) cuts a field that runs past bit 31 there, and a field that starts past it
|
||||
+ // gives 0, or bit 31 repeated when signed and the size is not 0. SPIR-V leaves an offset or offset + count over
|
||||
+ // 32 undefined, and AMD's bitfield instructions take offset and width modulo 32. TOTK's Depths terrain vertex
|
||||
+ // shaders take position and size from a register (BFE.R), so they are clamped into the defined range here;
|
||||
+ // constant fields inside the word are emitted as before.
|
||||
+ bool constantInRange = srcB.Type == OperandType.Constant && (srcB.Value & 0xff) + ((srcB.Value >> 8) & 0xff) <= 32;
|
||||
+ Operand positionIn = position;
|
||||
+ Operand sizeIn = size;
|
||||
+
|
||||
+ if (!constantInRange)
|
||||
+ {
|
||||
+ ClampBitfield(context, ref position, ref size);
|
||||
+ }
|
||||
+
|
||||
Operand res = isSigned
|
||||
? context.BitfieldExtractS32(srcA, position, size)
|
||||
: context.BitfieldExtractU32(srcA, position, size);
|
||||
|
||||
+ if (!constantInRange && isSigned)
|
||||
+ {
|
||||
+ // The clamped extract gave 0 for a field that starts past bit 31; a signed one is bit 31 repeated.
|
||||
+ Operand signBits = context.ConditionalSelect(context.ICompareEqual(sizeIn, Const(0)), Const(0), context.ShiftRightS32(srcA, Const(31)));
|
||||
+
|
||||
+ res = context.ConditionalSelect(context.ICompareGreaterOrEqualUnsigned(positionIn, Const(32)), signBits, res);
|
||||
+ }
|
||||
+
|
||||
context.Copy(GetDest(rd), res);
|
||||
|
||||
- // TODO: CC, X, corner cases.
|
||||
+ // TODO: CC, X.
|
||||
+ }
|
||||
+
|
||||
+ // TOTK: position and size of a bitfield cut to the 32-bit word: the size shrinks to what is left of the word
|
||||
+ // after the position (0 for a position of 32 or more) and the position stays at most 31, so offset and
|
||||
+ // offset + count stay inside the range SPIR-V defines.
|
||||
+ private static void ClampBitfield(EmitterContext context, ref Operand position, ref Operand size)
|
||||
+ {
|
||||
+ Operand left = context.ISubtract(Const(32), context.IMinimumU32(position, Const(32)));
|
||||
+
|
||||
+ size = context.IMinimumU32(size, left);
|
||||
+ position = context.IMinimumU32(position, Const(31));
|
||||
}
|
||||
|
||||
private static void EmitBfi(EmitterContext context, Operand srcA, Operand srcB, Operand srcC, int rd)
|
||||
@@ -157,6 +190,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
|
||||
Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8));
|
||||
|
||||
+ // TOTK: as in EmitBfe, a field past bit 31 is cut there; one that starts past it inserts nothing (size 0
|
||||
+ // after the clamp), so C comes back unchanged, as on Maxwell.
|
||||
+ if (!(srcB.Type == OperandType.Constant && (srcB.Value & 0xff) + ((srcB.Value >> 8) & 0xff) <= 32))
|
||||
+ {
|
||||
+ ClampBitfield(context, ref position, ref size);
|
||||
+ }
|
||||
+
|
||||
Operand res = context.BitfieldInsert(srcC, srcA, position, size);
|
||||
|
||||
context.Copy(GetDest(rd), res);
|
||||
@@ -0,0 +1,32 @@
|
||||
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitShift.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitShift.cs
|
||||
@@ -119,6 +119,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
bool signedShift = maxShift == MaxShift.S64;
|
||||
int maxShiftConst = isLongShift ? 64 : 32;
|
||||
|
||||
+ // TOTK: the shift amount as a constant, when it is one (taken before the mask makes it an operation).
|
||||
+ bool constantShift = srcB.Type == OperandType.Constant;
|
||||
+ int shiftValue = constantShift ? (mask ? srcB.Value & (maxShiftConst - 1) : srcB.Value) : 0;
|
||||
+
|
||||
if (mask)
|
||||
{
|
||||
srcB = context.BitwiseAnd(srcB, Const(maxShiftConst - 1));
|
||||
@@ -159,6 +163,18 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
+ // TOTK: a shift by 0 shifts the other word by 32, which SPIR-V leaves undefined; AMD's shift instructions take
|
||||
+ // the amount's low 5 bits and give A | C. Maxwell gives the unshifted word: C for SHF.L, A for SHF.R, 32- and
|
||||
+ // 64-bit alike. TOTK's Depths terrain vertex shaders shift by registers (SHF.L.R, SHF.R.R).
|
||||
+ if (!constantShift)
|
||||
+ {
|
||||
+ res = context.ConditionalSelect(context.ICompareEqual(srcB, Const(0)), left ? srcC : srcA, res);
|
||||
+ }
|
||||
+ else if (shiftValue == 0)
|
||||
+ {
|
||||
+ res = left ? srcC : srcA;
|
||||
+ }
|
||||
+
|
||||
if (!mask)
|
||||
{
|
||||
// Clamped shift value.
|
||||
@@ -0,0 +1,18 @@
|
||||
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
|
||||
@@ -33,8 +33,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
foreach (AstOperand local in function.Locals)
|
||||
{
|
||||
- SpvInstruction localPointerType = context.TypePointer(StorageClass.Function, context.GetType(local.VarType));
|
||||
- SpvInstruction spvLocal = context.Variable(localPointerType, StorageClass.Function);
|
||||
+ SpvInstruction localType = context.GetType(local.VarType);
|
||||
+ SpvInstruction localPointerType = context.TypePointer(StorageClass.Function, localType);
|
||||
+
|
||||
+ // TOTK: a register read on a path that did not write it reads 0. Without an initializer the load is
|
||||
+ // undefined, and a compiler that folds undefined values (Mesa's, on the Steam Deck) may compute anything
|
||||
+ // from it.
|
||||
+ SpvInstruction spvLocal = context.Variable(localPointerType, StorageClass.Function, context.ConstantNull(localType));
|
||||
|
||||
context.AddLocalVariable(spvLocal);
|
||||
context.DeclareLocal(local, spvLocal);
|
||||
@@ -0,0 +1,376 @@
|
||||
--- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
|
||||
@@ -184,6 +184,14 @@ namespace Ryujinx.Graphics.Shader
|
||||
return false;
|
||||
}
|
||||
|
||||
+ // TOTK: true when a vertex shader should also write last frame's position (Transforms/MotionPrevious.cs),
|
||||
+ // the user output location it went to (-1: none) and the banks the position depends on (bit n: cn).
|
||||
+ bool QueryMotionPrevious() => false;
|
||||
+
|
||||
+ void ReportMotionPrevious(int location, int banks)
|
||||
+ {
|
||||
+ }
|
||||
+
|
||||
/// <summary>
|
||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||
/// </summary>
|
||||
--- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
|
||||
@@ -65,7 +65,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
_stage = stage;
|
||||
_stagePrefix = GetShaderStagePrefix(stage);
|
||||
|
||||
- _cbSlotToBindingMap = new SetBindingPair[18];
|
||||
+ _cbSlotToBindingMap = new SetBindingPair[32]; // TOTK: slots 18-31 for Transforms/MotionPrevious.cs
|
||||
_sbSlotToBindingMap = new SetBindingPair[16];
|
||||
_cbSlotToBindingMap.AsSpan().Fill(new(-1, -1));
|
||||
_sbSlotToBindingMap.AsSpan().Fill(new(-1, -1));
|
||||
--- /dev/null
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/MotionPrevious.cs
|
||||
@@ -0,0 +1,330 @@
|
||||
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
+using System.Collections.Generic;
|
||||
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
+
|
||||
+namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||
+{
|
||||
+ // TOTK: last frame's position for per-object motion vectors (runtime/vk_exec_draw.c, vk_mvprev.c). A vertex shader's
|
||||
+ // position depends on uniform banks of this frame: the camera (c4), the object's place (c7), its bones (c5), the
|
||||
+ // clock that moves wind and water (c14), and so on. Every operation the position depends on that reads one of the
|
||||
+ // user banks c3..c16, directly or through other values, is computed a second time beside the original, reading slot
|
||||
+ // bank + 15 (18..31) instead, where the runtime binds that bank's block of last frame; the position this gives is
|
||||
+ // written to a free user output. Control flow stays the original's. Operations that cannot be repeated (stores,
|
||||
+ // texture operations) keep their original result. The runtime gets the output location and the banks the position
|
||||
+ // depends on (ReportMotionPrevious): an object with none of them changed since last frame held still.
|
||||
+ static class MotionPrevious
|
||||
+ {
|
||||
+ public const int SlotOffset = 15;
|
||||
+
|
||||
+ private static int Remap(int slot) => slot >= 3 && slot <= 16 ? slot + SlotOffset : -1;
|
||||
+
|
||||
+ // The output location written (-1: none free, or no position store), and in banks the mask of the banks (bit
|
||||
+ // n: cn) the position depends on.
|
||||
+ public static int Run(BasicBlock[] blocks, ResourceManager resourceManager, AttributeUsage attributeUsage, out int banks)
|
||||
+ {
|
||||
+ banks = 0;
|
||||
+ int location = -1;
|
||||
+
|
||||
+ for (int l = Constants.MaxAttributes - 1; l >= 0; l--)
|
||||
+ {
|
||||
+ if ((attributeUsage.UsedOutputAttributes & (1 << l)) == 0)
|
||||
+ {
|
||||
+ location = l;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (location < 0)
|
||||
+ {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ // 1. What the position depends on: back from the values the position stores write, through every
|
||||
+ // operation and phi that defines them.
|
||||
+ HashSet<INode> slice = new();
|
||||
+ Stack<Operand> work = new();
|
||||
+ bool stores = false;
|
||||
+
|
||||
+ foreach (BasicBlock block in blocks)
|
||||
+ {
|
||||
+ for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
|
||||
+ {
|
||||
+ if (IsPositionStore(node.Value))
|
||||
+ {
|
||||
+ stores = true;
|
||||
+ work.Push(((Operation)node.Value).GetSource(2));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (!stores)
|
||||
+ {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ while (work.Count != 0)
|
||||
+ {
|
||||
+ Operand v = work.Pop();
|
||||
+
|
||||
+ if (v == null || v.Type != OperandType.LocalVariable || v.AsgOp is not INode def || !slice.Add(def))
|
||||
+ {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ for (int s = 0; s < def.SourcesCount; s++)
|
||||
+ {
|
||||
+ work.Push(def.GetSource(s));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // 2. The banks the slice reads, and the bindings of those it loads with a computed offset to their
|
||||
+ // last-frame slot's (only these get one: a binding is declared in the SPIR-V whether used or not, and the
|
||||
+ // host's layout comes from the used ones). Direct operands (c[slot][offset]) get theirs when the
|
||||
+ // structured program is made.
|
||||
+ Dictionary<int, int> bindings = new();
|
||||
+
|
||||
+ foreach (INode n in slice)
|
||||
+ {
|
||||
+ if (n is Operation op && IsBankLoad(op, resourceManager, out int slot))
|
||||
+ {
|
||||
+ banks |= 1 << slot;
|
||||
+
|
||||
+ if (!bindings.ContainsKey(op.GetSource(0).Value))
|
||||
+ {
|
||||
+ bindings[op.GetSource(0).Value] = resourceManager.GetConstantBufferBinding(Remap(slot));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (int s = 0; s < n.SourcesCount; s++)
|
||||
+ {
|
||||
+ Operand src = n.GetSource(s);
|
||||
+
|
||||
+ if (src != null && src.Type == OperandType.ConstantBuffer && Remap(src.GetCbufSlot()) >= 0)
|
||||
+ {
|
||||
+ banks |= 1 << src.GetCbufSlot();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (banks == 0)
|
||||
+ {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ // 3. Which of the slice's values depend on those banks, to a fixed point (phis carry it around loops).
|
||||
+ HashSet<Operand> tainted = new();
|
||||
+ bool changed = true;
|
||||
+
|
||||
+ while (changed)
|
||||
+ {
|
||||
+ changed = false;
|
||||
+
|
||||
+ foreach (BasicBlock block in blocks)
|
||||
+ {
|
||||
+ for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
|
||||
+ {
|
||||
+ INode n = node.Value;
|
||||
+
|
||||
+ if (!slice.Contains(n) || n.DestsCount == 0 || !Reads(n, tainted, bindings))
|
||||
+ {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ for (int d = 0; d < n.DestsCount; d++)
|
||||
+ {
|
||||
+ Operand dest = n.GetDest(d);
|
||||
+
|
||||
+ if (dest != null && dest.Type == OperandType.LocalVariable && tainted.Add(dest))
|
||||
+ {
|
||||
+ changed = true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // 4. A copy of each such operation beside it, with new destinations.
|
||||
+ Dictionary<Operand, Operand> prev = new();
|
||||
+ List<(INode Original, INode Copy)> copies = new();
|
||||
+
|
||||
+ foreach (BasicBlock block in blocks)
|
||||
+ {
|
||||
+ for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
|
||||
+ {
|
||||
+ INode n = node.Value;
|
||||
+
|
||||
+ if (!slice.Contains(n) || n.DestsCount == 0 || !IsTainted(n, tainted))
|
||||
+ {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ INode copy = null;
|
||||
+
|
||||
+ if (n is PhiNode phi)
|
||||
+ {
|
||||
+ Operand dest = Local();
|
||||
+ prev[phi.Dest] = dest;
|
||||
+ copy = new PhiNode(dest);
|
||||
+ }
|
||||
+ else if (n is Operation op && op.GetType() == typeof(Operation) && op.Inst != Instruction.Store)
|
||||
+ {
|
||||
+ Operand[] dests = new Operand[op.DestsCount];
|
||||
+
|
||||
+ for (int d = 0; d < dests.Length; d++)
|
||||
+ {
|
||||
+ Operand dest = op.GetDest(d);
|
||||
+ dests[d] = dest != null && dest.Type == OperandType.LocalVariable ? Local() : dest;
|
||||
+
|
||||
+ if (dest != null && dest.Type == OperandType.LocalVariable)
|
||||
+ {
|
||||
+ prev[dest] = dests[d];
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ Operand[] sources = new Operand[op.SourcesCount];
|
||||
+
|
||||
+ for (int s = 0; s < sources.Length; s++)
|
||||
+ {
|
||||
+ sources[s] = op.GetSource(s);
|
||||
+ }
|
||||
+
|
||||
+ Operation c = op.StorageKind != StorageKind.None && dests.Length == 1
|
||||
+ ? new Operation(op.Inst, op.StorageKind, dests[0], sources)
|
||||
+ : new Operation(op.Inst, op.Index, dests, sources);
|
||||
+ c.ForcePrecise = op.ForcePrecise;
|
||||
+ copy = c;
|
||||
+ }
|
||||
+
|
||||
+ if (copy != null)
|
||||
+ {
|
||||
+ node = block.Operations.AddAfter(node, copy);
|
||||
+ copies.Add((n, copy));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // 5. The copies' sources: last frame's values, the remapped banks.
|
||||
+ foreach ((INode original, INode copy) in copies)
|
||||
+ {
|
||||
+ if (copy is PhiNode phiCopy)
|
||||
+ {
|
||||
+ PhiNode phi = (PhiNode)original;
|
||||
+
|
||||
+ for (int s = 0; s < phi.SourcesCount; s++)
|
||||
+ {
|
||||
+ phiCopy.AddSource(phi.GetBlock(s), Prev(phi.GetSource(s), prev));
|
||||
+ }
|
||||
+
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ Operation op = (Operation)original, c = (Operation)copy;
|
||||
+
|
||||
+ for (int s = 0; s < op.SourcesCount; s++)
|
||||
+ {
|
||||
+ Operand src = op.GetSource(s);
|
||||
+
|
||||
+ if (s == 0 && op.Inst == Instruction.Load && op.StorageKind == StorageKind.ConstantBuffer &&
|
||||
+ src.Type == OperandType.Constant && bindings.TryGetValue(src.Value, out int binding))
|
||||
+ {
|
||||
+ c.SetSource(s, Const(binding));
|
||||
+ }
|
||||
+ else if (src.Type == OperandType.ConstantBuffer && Remap(src.GetCbufSlot()) >= 0)
|
||||
+ {
|
||||
+ c.SetSource(s, Cbuf(Remap(src.GetCbufSlot()), src.GetCbufOffset()));
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ c.SetSource(s, Prev(src, prev));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // 6. Each position store also writes last frame's position to the output.
|
||||
+ foreach (BasicBlock block in blocks)
|
||||
+ {
|
||||
+ for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
|
||||
+ {
|
||||
+ if (!IsPositionStore(node.Value))
|
||||
+ {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ Operation op = (Operation)node.Value;
|
||||
+ Operation store = new(Instruction.Store, StorageKind.Output, null,
|
||||
+ Const((int)IoVariable.UserDefined), Const(location), op.GetSource(1), Prev(op.GetSource(2), prev));
|
||||
+ node = block.Operations.AddAfter(node, store);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ attributeUsage.SetOutputUserAttribute(location);
|
||||
+
|
||||
+ return location;
|
||||
+ }
|
||||
+
|
||||
+ private static bool IsPositionStore(INode n)
|
||||
+ {
|
||||
+ return n is Operation op && op.Inst == Instruction.Store && op.StorageKind == StorageKind.Output &&
|
||||
+ op.SourcesCount == 3 && op.GetSource(0).Type == OperandType.Constant &&
|
||||
+ (IoVariable)op.GetSource(0).Value == IoVariable.Position;
|
||||
+ }
|
||||
+
|
||||
+ private static bool IsBankLoad(Operation op, ResourceManager resourceManager, out int slot)
|
||||
+ {
|
||||
+ slot = -1;
|
||||
+
|
||||
+ return op.Inst == Instruction.Load && op.StorageKind == StorageKind.ConstantBuffer &&
|
||||
+ op.GetSource(0).Type == OperandType.Constant &&
|
||||
+ resourceManager.TryGetConstantBufferSlot(op.GetSource(0).Value, out slot) && Remap(slot) >= 0;
|
||||
+ }
|
||||
+
|
||||
+ private static Operand Prev(Operand src, Dictionary<Operand, Operand> prev)
|
||||
+ {
|
||||
+ return src != null && prev.TryGetValue(src, out Operand p) ? p : src;
|
||||
+ }
|
||||
+
|
||||
+ private static bool IsTainted(INode n, HashSet<Operand> tainted)
|
||||
+ {
|
||||
+ for (int d = 0; d < n.DestsCount; d++)
|
||||
+ {
|
||||
+ if (n.GetDest(d) is Operand dest && tainted.Contains(dest))
|
||||
+ {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ private static bool Reads(INode n, HashSet<Operand> tainted, Dictionary<int, int> bindings)
|
||||
+ {
|
||||
+ if (n is Operation op && op.Inst == Instruction.Load && op.StorageKind == StorageKind.ConstantBuffer &&
|
||||
+ op.GetSource(0).Type == OperandType.Constant && bindings.ContainsKey(op.GetSource(0).Value))
|
||||
+ {
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ for (int s = 0; s < n.SourcesCount; s++)
|
||||
+ {
|
||||
+ Operand src = n.GetSource(s);
|
||||
+
|
||||
+ if (src == null)
|
||||
+ {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (src.Type == OperandType.ConstantBuffer && Remap(src.GetCbufSlot()) >= 0)
|
||||
+ {
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (tainted.Contains(src))
|
||||
+ {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
--- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
|
||||
+++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
|
||||
@@ -301,6 +301,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
Optimizer.RunPass(context);
|
||||
TransformPasses.RunPass(context);
|
||||
+
|
||||
+ if (i == 0 && Definitions.Stage == ShaderStage.Vertex && GpuAccessor.QueryMotionPrevious())
|
||||
+ {
|
||||
+ int location = MotionPrevious.Run(cfg.Blocks, resourceManager, AttributeUsage, out int banks);
|
||||
+ GpuAccessor.ReportMotionPrevious(location, banks);
|
||||
+ }
|
||||
}
|
||||
|
||||
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
|
||||
+50
-11
@@ -32,8 +32,9 @@
|
||||
# A thread waiting for a virtual core interrupts the owner when it runs game code: 1 = on, 2 = priority inheritance
|
||||
# without the interrupt, 0 = off (the old scheduling).
|
||||
# async_preempt = 1
|
||||
# Replay NVN submissions on their own thread, at most one frame behind the game (1 = on). Adds up to a frame of latency.
|
||||
# replay_async = 0
|
||||
# Replay NVN submissions on their own thread, at most one frame behind the game, 0 or 1. Default: on. Adds up to a
|
||||
# frame of latency; 0 replays them on the game thread.
|
||||
# replay_async = 1
|
||||
# EXPERIMENTAL, THE GAME CRASHES WITH 1. 1 lifts the virtual core gate: guest threads run without the console's limit
|
||||
# of one thread per core, and the game keeps per-core data without locks. With 1, 1.2.1 and 1.4.3 crash or hang,
|
||||
# usually at start (#212). Keep 0 to play.
|
||||
@@ -55,13 +56,16 @@
|
||||
# pipe_prebuild = 1
|
||||
# Threads that translate known shaders at start (1 to 64; default: half the CPU threads, at most 16).
|
||||
# prewarm_xlate = 8
|
||||
# Vulkan graphics pipeline library, 0 or 1. Default: on, except on AMD's driver for RDNA 1 and 2 (RX 5000 and 6000).
|
||||
# Vulkan graphics pipeline library, 0 or 1. Default: on.
|
||||
# gpl = 1
|
||||
|
||||
# Textures and memory.
|
||||
# ASTC texture decoding: native (the GPU's own ASTC support), gpu (a compute shader), cpu, or auto (native when the GPU
|
||||
# has it, gpu otherwise).
|
||||
# astc = auto
|
||||
# With astc = gpu: re-encode the decoded ASTC textures to BC3, a quarter of the video memory, textures slightly blockier,
|
||||
# 0 or 1. Default: on when the GPU has less than 5 GB of video memory, off otherwise.
|
||||
# astc_bc = 1
|
||||
# Game memory shared with the GPU: auto (mirror on a discrete GPU, import on an integrated one), import (host memory
|
||||
# imported into Vulkan), mirror (pools used as buffers get a copy in video memory) or verify (mirror plus a check of
|
||||
# every copy; slow). On a discrete GPU import can be far slower than mirror, not slightly slower: the GPU reads the
|
||||
@@ -136,8 +140,9 @@
|
||||
# Game resolution: the size the game draws its picture and interface in (1280x720 to 7680x4320). Above 1920x1080 the
|
||||
# game's texture heap and memory pool grow with it. Another shape than 16:9, such as 3440x1440 on a 21:9 monitor or
|
||||
# the Steam Deck's 1280x800 (16:10), gives the camera's view that shape and lays the HUD out for it. The installer
|
||||
# prepares the UI layout for ten sizes: 2560x1080, 3440x1440, 3840x1600, 5120x2160 (21:9), 3840x1080, 5120x1440 (32:9),
|
||||
# 1280x800, 1680x1050, 1920x1200 and 2560x1600 (16:10). Any other size
|
||||
# prepares the UI layout for fifteen sizes: 2560x1080, 3440x1440, 3840x1600, 5120x2160 (21:9), 3840x1080, 5120x1440
|
||||
# (32:9), 1280x800, 1680x1050, 1920x1200, 2560x1600 (16:10), 1920x1280, 2160x1440, 2256x1504, 2880x1920 and 3000x2000
|
||||
# (3:2). Any other size
|
||||
# gets the camera's view but a 16:9 HUD, stretched over the picture, unless you build its layout with
|
||||
# tools/make_romfs_patches.py --aspect WxH. Read at start.
|
||||
# output_res = 1920x1080
|
||||
@@ -191,14 +196,30 @@
|
||||
# patch 0010), so the shader cache is rebuilt once after an update. Read at start.
|
||||
# pcss = 0
|
||||
|
||||
# NVIDIA DLSS / DLAA for the 3D scene, on NVIDIA RTX GPUs with nvngx_dlss.dll (from NVIDIA's DLSS SDK) next to
|
||||
# totk.exe; elsewhere the setting does nothing. The HUD is drawn after it and is not touched. Read at start.
|
||||
# NVIDIA DLSS / DLAA for the 3D scene, on NVIDIA RTX GPUs with the DLSS model from NVIDIA's DLSS SDK next to the
|
||||
# executable (Windows: nvngx_dlss.dll next to totk.exe; Linux: libnvidia-ngx-dlss.so.<version> next to totk, with the
|
||||
# NVIDIA driver's libnvidia-ngx.so.1 installed); elsewhere the setting does nothing. The HUD is drawn after it and is not touched.
|
||||
# Read at start.
|
||||
# dlaa = 0
|
||||
# What DLSS works from: dlaa (the 3D scene at full size), quality, balanced or performance (the scene at 67, 58 or
|
||||
# 50 % of the output, scaled up by DLSS; these replace render_scale and sharpen texture filtering to match). They apply
|
||||
# only with dlaa = 1 and nvngx_dlss.dll next to totk.exe, on Windows; otherwise the scene follows render_scale. Read at
|
||||
# start.
|
||||
# What DLSS works from: dlaa (the 3D scene at full size), quality, balanced, performance or ultra_performance (the
|
||||
# scene at 67, 58, 50 or 33 % of the output, scaled up by DLSS; these replace render_scale and sharpen texture filtering
|
||||
# to match). They apply only with dlaa = 1 and the DLSS model next to the executable; otherwise the scene follows
|
||||
# render_scale. Changes while the game runs take effect at once (1.4.3; 1.2.1 at the next start).
|
||||
# dlss = dlaa
|
||||
# The DLSS model: default (NVIDIA's advice per mode: K for dlaa, quality and balanced, M for performance, L for
|
||||
# ultra_performance), k (K in every mode) or j (the other transformer model: slightly less ghosting behind moving
|
||||
# characters, a little more flicker). Any other model by its letter, a to m: on slower RTX cards the older model e is
|
||||
# cheaper. Changes while the game runs take effect at once (DLSS starts a new history).
|
||||
# dlss_preset = default
|
||||
# Per-object motion vectors for DLSS: characters, animals and moving objects get their own motion instead of the
|
||||
# camera's, so DLSS keeps their history instead of smearing or rejecting it. Costs some CPU time each frame (about 1-2 ms
|
||||
# on a laptop CPU); 0 leaves camera-only motion. Read at start.
|
||||
# dlaa_objmv = 1
|
||||
# Characters' trails: where a character, animal or held item moves without motion vectors of its own (all of them with
|
||||
# dlaa_objmv = 0), DLSS can drag a faint copy of it. The G-buffer marks those objects; on them and where they were the
|
||||
# frame before, DLSS is told to favour the current frame by this percentage (0, 35, 65 or 100; 0 off). Higher removes
|
||||
# more of the trails and leaves those edges a little less smooth. Changes while the game runs take effect at once.
|
||||
# dlss_char_mask = 65
|
||||
# The game's own FXAA after the post effects; with DLAA on it is not needed. Changes while the game runs take effect at
|
||||
# once.
|
||||
# fxaa = 1
|
||||
@@ -211,6 +232,9 @@
|
||||
# hdr_peak_nits = 1000
|
||||
# hdr_game_nits = 203
|
||||
# hdr_ui_nits = 203
|
||||
# Highlight hue correction in percent (0 to 100): bright colours such as fire turn towards white as in the game's SDR
|
||||
# picture, matching the light they cast; 0 keeps them fully saturated. Changes take effect at once.
|
||||
# hdr_hue = 80
|
||||
|
||||
[language]
|
||||
# Language: a Windows locale such as de-DE, or a Switch code (en-US, ja, zh-Hant, ...). Default: the Windows locale.
|
||||
@@ -222,6 +246,18 @@
|
||||
# it is none of them.
|
||||
# menu_lang = en
|
||||
|
||||
[discord]
|
||||
# Rich Presence: "Playing TotK Recomp" with the play time in your Discord status while the game runs, through the
|
||||
# local IPC of a running Discord client. 1 = on; the Menu tab of the settings menu (F1) switches it too, and the
|
||||
# change reaches the presence thread at once.
|
||||
# discord_rpc = 0
|
||||
# The application whose name and artwork the status shows. The default is the project's own application,
|
||||
# registered as "TotK Recomp" with the port's green ring icon as its art. Register your own at
|
||||
# discord.com/developers (name and art are yours to choose) and put its id here.
|
||||
# discord_app_id = 1567232599846293504
|
||||
# The key of the application's uploaded art asset shown as the status's large image.
|
||||
# discord_image = totk_512x512
|
||||
|
||||
[audio]
|
||||
# none mixes without opening an audio device.
|
||||
# audio = default
|
||||
@@ -229,6 +265,9 @@
|
||||
# audio_prefill_ms = 20
|
||||
# Volume in percent (0 to 100), on top of the game's own. Changes while the game runs take effect at once.
|
||||
# volume = 100
|
||||
# Speakers: stereo, 5.1 or auto (5.1 when the Windows speaker setup has six or more channels, stereo otherwise). The
|
||||
# game is told the same channel count and mixes for it. Takes effect at the next start.
|
||||
# audio_channels = stereo
|
||||
|
||||
[input]
|
||||
# Gamepad motion controls (0 = off) and vibration (0 = off).
|
||||
|
||||
Reference in new issue
Block a user