Repository navigation
Releases
1.6.0-dev-fd578d1
1.6.0-dev-fd578d1 (2026-10-21 09:03:35 PM)
Integration-track work since the last release snapshot below. The
upstream-facing payload of this work is split one topical PR per change
(docs/merge-plan-r4.md): diasurgical#8750 (pr/1, SDL3 dependency
builds), diasurgical#8753 (pr/4, test missing-asset guards), diasurgical#8754 (pr/5, GPU scaler,
stacked on pr/1), diasurgical#8755 (pr/6, SDL3 main glue) — all open. The resolution/DPI
fix (diasurgical#8746: SDL_GetWindowDisplayScale() handling, above-native resolution
selection, OS-UI-scale exclusion for physical-pixel modes) intentionally
rides as a separate lightweight PR outside the r4 plan.
The 2026-10-21 wave moves onto upstream libslang-vk M4.07 final: the
dependency pin leaves the pre-release for d176d1b, the scaler gains a
SPIR-V disk cache keyed by the library's pass key, and it learns to recover
from a lost Vulkan device — plus the deep-preset browser scan fix that had
been hiding nested stock presets.
Added
GPU scaler integration
- Shader base resolution: a new
Graphics.shaderBaseResolutionoption plus a
cycle entry inside the shader preset browser — the browser shows the active
base resolution and activating the entry cycles to the next value without
leaving the browser. tools/build-local-win11.ps1: local Windows 11 build script mirroring the
CI gate —staticandnonstatic(DLL) variants, pinned vcpkg
(.vcpkg-local, 40f3c709db), FetchContent SDL 3.5.0 with vcpkg's shadowing
SDL3 payload removed, and an explicitly selected MSVC x64 environment.build_r4_branches.py: branch automation for the merge track — cuts one
branch per payload change from upstream/master with EOL normalization
against upstream and attribution stamps;--recut/--push(lease) /
--prune/--verify-only, and--check-prsfor live upstream PR state
(flags closed/merged plan PRs as retire/prune candidates instead of leaving
the next one to be noticed by hand).
libslang-vk M4.07 — SPIR-V disk cache and device-lost recovery
- SPIR-V disk cache: the scaler wires up
PresetLoadOptions::spirvCache
(the M4.07 cache enabler) — one file per pass under
shaders/cache/spv/<key>.bin, the layout the Packaging
clear_shader_cachescripts already wipe. The key is the library's (a
SHA-256 over the exact GLSL handed to glslang plus the compiler version
and target environment), so an entry can never serve a stale binary the
way the fork's old in-tree GLSL-only hash would have across a glslang
upgrade. Reads are validated (container magic and version, framing,
SPIR-V header, a 64 MB per-stage sanity cap, path-traversal rejection),
so a torn or hand-edited file degrades to a recompile instead of a broken
preset; stores go through a temp file + rename, so a crash mid-store
cannot leave a torn entry. First field run: a 20-pass and a 2-pass preset
each compiled once, then 20/20 and 2/2 passes served from the cache on
every later load. - Device-lost recovery, host side of the M4.06 contract: a
VK_ERROR_DEVICE_LOSTon any of the three surfaces the host touches
(vkAcquireNextImageKHR,Chain::render,vkQueuePresentKHR) recovers
in place instead of ending the shader path. The render case is the
library-driven one — the library has already replaced the device and
rebuilt the chain, and reportsErrc::vk_device_lostso the host
recreates its own device-side resources; acquire and present are
host-driven, callingDevice::recover()directly. Either way the host
forgets its stale objects (game image, swapchain, acquire semaphore,
staging buffer), frees the stagingMappedBufferthrough the old
allocator (it is not in the device's release registry), re-binds the
exe's own volk device table (GPU_SCALER_SHARED builds carry a separate
copy), and lets the chain rebuild itself on the next render through the
generation check. Three recoveries in a row without a completed frame
give up and fall back to the stock renderer.
Fixed
Audio
Source/engine/fbbsanitizer.cpp: theShootFireballBowsound effect's WAV
data in hellfire.mpq is invalid (missing pad byte after the data chunk; the
smpl chunk breaks SDL3 playback) — sanitize it at load time by truncating
after the data chunk and adjusting the RIFF chunk length.
SDL3 display / input
- Scaler mouse-coordinate mapping: when the scaler owns the window there is
no SDL renderer for SDL to convert events with, so
gpu_scaler::MapWindowToGame()maps window and game space through the
scaler's last present rect (display.h/sdl_compat.hhooks). The two
hook call sites are qualified (devilution::gpu_scaler::...) so
translation units that includesdl_compat.hwithoutdisplay.hstill
compile — the Linux CI failure mode cannot recur on the PR branches.
Windows build
- MSVC nonstatic (DLL) build: the shared slangvk target's import library
moved toslangvk-shared-implib/, removing the duplicateslangvk.lib
rule that brokeLink nonstatic. build-local-win11.ps1:-DCMAKE_SUPPRESS_REGENERATION=ONadded to both
variant flag sets — Ninja's RERUN_CMAKE auto-regeneration fired endlessly
inside the build step on some Windows filesystems (each cycle a full cached
re-configure, no compile edge ever ran). The script always configures
explicitly right before building, so self-regeneration is redundant.- Dead
patch.exePATH block removed from the build script (no patch
application remains in the tree since the WAV layer drop).
GPU scaler / libslang-vk
- Deep-preset browser scan:
ScanPresets()scanned three levels deep while
the stock shader corpus nests up to seven (API.md §4.2), so deeper
presets never appeared in the browser — and the stored-preset self-heal
then wiped such a selection at startup as missing. The scan now runs at
depth 8. - First-acquire semaphore: the acquire path could reach its first
vkAcquireNextImageKHRwith aVK_NULL_HANDLEsemaphore and a null
fence — the semaphore used to be created only after a failed render, and
an acquire needs one of the two. The semaphore is now created before the
first acquire and rebuilt after a failed render leaves it signaled
without a wait, so every acquire is well-formed.
Changed
CI (fork-only gate)
gpu-scaler-build.yml: Windows MSVC x64 (vcpkg manifest + preset with an
explicit MSVC dev environment, sodium vendored static) and Linux x64 (ctest
as the regression signal) jobs,--versionbinary smoke tests on both,
ccache, path-keyed build-tree caching with a workspace-path backstop, and
if: always()artifact uploads. The retiredwav-mixer-fixbranch is
removed from the push triggers.
Docs
- README.md: branch-aware phrasing for the master references and the branch
table; the deletedwav-mixer-fixbranch row dropped.
libslang-vk
- Dependency pin: the CMake
FetchContenttag moved from5b72331
("M4.07 Pre-Release") tod176d1b(M4.07 final) — the pre-release
lacked the SPIR-V cache enabler the disk cache above is built on. The
stale M4.05 comment above the pin went with it. - README.md: the shader-cache section now describes the
shaders/cache/spv/
disk cache and drops the stale references to the removed in-tree cache
scripts; the Vulkan render-path section documents the in-place
device-lost recovery.
Removed
WAV fallback / robustness layer
- The generic WAV fallback/decoder-robustness layer is dropped from the tree
(SDL3_mixer zero-length-chunk patch, CMake flags,SoundSamplefallback
logic — 9 files, 228 lines removed): upstream sanitizes the broken audio
data itself now, so the layer is not wanted upstream (PR diasurgical#8751 closed) and
the same layer left the tip.pr/2-wav-audio-robustnessis retired and
pruned from the plan. pr/3-robustness-guards(diasurgical#8752) retired as well: review showed the
menu-time item-cursor crash was fork-side — the scaler's
renderer-recreation path calledInitVirtualGamepadGFX()outside a game
session; the tip gates that path ongbRunGame, and the remaining
capture.cppLogErrorone-liner is left for upstream.
Full Changelog: libslang-vk-v4...libslang-vk-v9
1.6.0-dev-638b973
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog
and this project adheres to Semantic Versioning.
Fork releases are numbered 1.6.0-dev-<commit>, where <commit> is the
short hash of the commit a build or overlay was produced from, and are
dated with an America/Toronto timestamp. Upstream (diasurgical) release
history lives in docs/CHANGELOG_UPSTREAM.md
and is not modified.
The libslang-vk integration branch does not cut numbered releases; its
integration milestones are recorded under Unreleased below, dated by their
commit window.
[1.6.0-dev-638b973] 2026-10-20 5:30:09 PM
Integration-track work since the last release snapshot below. The
upstream-facing payload of this work is split one topical PR per change
(docs/merge-plan-r4.md): diasurgical#8750 (pr/1, SDL3 dependency
builds), diasurgical#8753 (pr/4, test missing-asset guards), diasurgical#8754 (pr/5, GPU scaler,
stacked on pr/1), diasurgical#8755 (pr/6, SDL3 main glue) — all open. The resolution/DPI
fix (diasurgical#8746: SDL_GetWindowDisplayScale() handling, above-native resolution
selection, OS-UI-scale exclusion for physical-pixel modes) intentionally
rides as a separate lightweight PR outside the r4 plan.
Added
GPU scaler integration
- Shader base resolution: a new
Graphics.shaderBaseResolutionoption plus a
cycle entry inside the shader preset browser — the browser shows the active
base resolution and activating the entry cycles to the next value without
leaving the browser. tools/build-local-win11.ps1: local Windows 11 build script mirroring the
CI gate —staticandnonstatic(DLL) variants, pinned vcpkg
(.vcpkg-local, 40f3c709db), FetchContent SDL 3.5.0 with vcpkg's shadowing
SDL3 payload removed, and an explicitly selected MSVC x64 environment.build_r4_branches.py: branch automation for the merge track — cuts one
branch per payload change from upstream/master with EOL normalization
against upstream and attribution stamps;--recut/--push(lease) /
--prune/--verify-only, and--check-prsfor live upstream PR state
(flags closed/merged plan PRs as retire/prune candidates instead of leaving
the next one to be noticed by hand).- libslang-vk M4.05 single-submit frame path: the scaler's per-frame
upload no longer pays the standaloneImage::updateround-trip (its own
staging alloc + command buffer + submit + fence drain, on top of the
render submit). A persistentslv::Device::MappedBuffer— created once
per game-frame size and released/recreated on resolution changes — now
holds the tightly packed copy of the SDL_Surface frame (the pitch is not
promised to equal width * 4, so the packing copy stays); every present
memcpys into the mapped memory and hands the buffer to the chain via
InputFrame::pixelBuffer, and the runner records the buffer→image copy
at the top of its own frame submit. Per frame: one command buffer, one
submit, one fence wait, zero staging churn. The vendored libslang-vk
GIT_TAG rides the tagged M4.05 release (c3567b03→fa65b6d5):
Device::createMappedBuffer/InputFrame::pixelBuffer(1a9911b), the
public-header fix that keepsslangvk/runner.hppoff the VMA include
path (540c9d6), and the M4.05 docs (fa65b6d5).
Fixed
Audio
Source/engine/fbbsanitizer.cpp: theShootFireballBowsound effect's WAV
data in hellfire.mpq is invalid (missing pad byte after the data chunk; the
smpl chunk breaks SDL3 playback) — sanitize it at load time by truncating
after the data chunk and adjusting the RIFF chunk length.
SDL3 display / input
- Scaler mouse-coordinate mapping: when the scaler owns the window there is
no SDL renderer for SDL to convert events with, so
gpu_scaler::MapWindowToGame()maps window and game space through the
scaler's last present rect (display.h/sdl_compat.hhooks). The two
hook call sites are qualified (devilution::gpu_scaler::...) so
translation units that includesdl_compat.hwithoutdisplay.hstill
compile — the Linux CI failure mode cannot recur on the PR branches.
Windows build
- MSVC nonstatic (DLL) build: the shared slangvk target's import library
moved toslangvk-shared-implib/, removing the duplicateslangvk.lib
rule that brokeLink nonstatic. build-local-win11.ps1:-DCMAKE_SUPPRESS_REGENERATION=ONadded to both
variant flag sets — Ninja's RERUN_CMAKE auto-regeneration fired endlessly
inside the build step on some Windows filesystems (each cycle a full cached
re-configure, no compile edge ever ran). The script always configures
explicitly right before building, so self-regeneration is redundant.- Dead
patch.exePATH block removed from the build script (no patch
application remains in the tree since the WAV layer drop).
Changed
GPU scaler
- Heartbeat diagnostics: the 5-second
gpu-scaler.logheartbeat line now
carries EMA-smoothed per-phase timings (upload / render / present, ms;
α = 1/32, measured with SDL_GetPerformanceCounter since the phases are
sub-millisecond), so a field log separates CPU upload cost from chain GPU
cost from acquire/present cost — the data that identified the old
two-submit frame path as sync-bound. The loaded preset's re-stat (the
hot-reload check) is throttled to at most every 500 ms — it is a
filesystem call, measurable at uncapped frame rates.
CI (fork-only gate)
gpu-scaler-build.yml: Windows MSVC x64 (vcpkg manifest + preset with an
explicit MSVC dev environment, sodium vendored static) and Linux x64 (ctest
as the regression signal) jobs,--versionbinary smoke tests on both,
ccache, path-keyed build-tree caching with a workspace-path backstop, and
if: always()artifact uploads. The retiredwav-mixer-fixbranch is
removed from the push triggers.
Docs
- README.md: branch-aware phrasing for the master references and the branch
table; the deletedwav-mixer-fixbranch row dropped.
Removed
WAV fallback / robustness layer
- The generic WAV fallback/decoder-robustness layer is dropped from the tree
(SDL3_mixer zero-length-chunk patch, CMake flags,SoundSamplefallback
logic — 9 files, 228 lines removed): upstream sanitizes the broken audio
data itself now, so the layer is not wanted upstream (PR diasurgical#8751 closed) and
the same layer left the tip.pr/2-wav-audio-robustnessis retired and
pruned from the plan. pr/3-robustness-guards(diasurgical#8752) retired as well: review showed the
menu-time item-cursor crash was fork-side — the scaler's
renderer-recreation path calledInitVirtualGamepadGFX()outside a game
session; the tip gates that path ongbRunGame, and the remaining
capture.cppLogErrorone-liner is left for upstream.
Full Changelog: libslang-vk-v4...libslang-vk-v8
1.6.0-dev-ba8691f
Changelog
[1.6.0-dev-ba8691f]
Shader Base Resolution & console packaging (dea220a...ba8691f)
Added
- Shader Base Resolution — new setting controlling the canvas size handed to the
slang shader chain while a filter is active. The game renders into the configured
base, the window and swapchain keep the selected Resolution, and the chain maps
mouse coordinates through the present rect. A base smaller than the window gives
upscaler presets (ScaleFX, HQx) the input < output headroom they are designed for;
CRT-style overlays (scanlines, masks, grids) keep working at any input size.
Values: Auto (window size), 640x360, 960x540 (default), 1280x720, 1600x900,
1920x1080. Non-16:9 windows letterbox. - In-browser base resolution picker — the Shader Filter browser shows a persistent
"Shader Base Resolution: " entry below "Off" in every browsed folder.
Activating it opens a nested value sub-list (same UI as a normal list option);
leaving it resumes the parent browser in place with focus intact. No auto-cycling,
and the standalone Graphics-menu row is hidden to avoid showing the setting twice. - Nested list browsing infrastructure in the settings menu (
GetNavigationSubList(),
listOptionStack): any list option can now drill into a sub-list and return to the
parent view without closing or flickering the menu. - Hellfire on 3DS / Switch —
hf.mpqis now built and deployed to romfs on both
consoles; MPQ search path addsromfs:/, with assets and mods laid out under
romfs:/assetsandromfs:/mods. BUILD_MOD_MPQSCMake option plusDEVILUTIONX_MODS_MPQ_DIRECTORYto build mod
MPQs independently ofBUILD_ASSETS_MPQ.build_r4_branches.py --recut [--push|--prune]: rebuilds all pr/* branches from the
current libslang-vk tip, keeps unchanged branch tips stable, and sanitizes
sdl_compat.hon the way out so the PR branches stay CI-green.
Changed
- README rewritten: Project Status — October 2026, What this mod adds, Requirements,
Setting Up Shaders, Using Shaders, Contributing. - Merged upstream diasurgical:master into libslang-vk.
Fixed
- Mod manifests (
manifest.ini) for loose (unpacked) mods are now searched under the
same pathsLoadModArchives()registers as OverridePaths (PrefPath/BasePath), so
loose mod requirements resolve consistently with packed archives.
Full Changelog: libslang-vk-v4...libslang-vk-v7
1.6.0-dev-3b94a42
Now linked against libslang-vk M4.08
[M4.08] — 2026-10-24
The first iteration measured by its own benchmark suite: this release
ships a real benchmarking framework (google/benchmark, six executables,
built in-tree behind one flag), and the framework immediately paid for
itself — its first real-GPU run surfaced a double file-read in
TranslateSlang, fixed here and measured at −18% per call on
Windows. Two more audit findings close on the cheap: the preset
parser's edge-case contract is now pinned by tests, and the
thread-safety guarantees are stated in the public headers.
Added
-
The benchmark framework (closes the framework half of TEST-12):
six google/benchmark v1.9.1 executables built with the library behind
-DSLANGVK_BUILD_BENCHMARKS=ON— preset parse, slang translate, slang
compile, chain create+destroy, blit render, FXAA render. The original
suite (M0) had machine-specific build wiring and never produced a
valid measurement outside its author's workstation; the rework links
the in-treeslangvk::slangvktarget (transitive glslang), fetches
google/benchmark pinned via FetchContent, uses repo-relative data
roots, wires in the orphanedbench_vk_render_fxaa.cpp, and makes
every bench count its failures and print first-failure diagnostics
instead of silently timing error paths. Runner scripts for Windows
(VS2022/MSVC) and Linux ship inbenchmarks/scripts/.
benchmarks/README.md documents the
measurement contract and carries the first numbers: the lavapipe
reference (2 vCPU sandbox, GCC 14.2) and the first real-GPU run —
RX 560 / Windows 11 / MSVC 19.44, where an FXAA frame completes
end-to-end in 411 µs (~2,400 FPS ceiling, 13.1× the software
rasterizer) and blit presentation in 225 µs (5.1×). -
Preset-parser edge-case tests (audit ROB-36, test half): 23 checks
pinning the parse contract on adversarial inputs — empty files
(rejected at top level, legal as#referencetargets), lines without
=, unknown-key tolerance, duplicate keys (last wins), CRLF inputs,
comment forms including quoted#literals, missing#reference
targets, self/two-file reference cycles, the 9-vs-10-file
#referencedepth boundary, and the 8 MiB size cap (ROB-20). -
Thread-safety contract in the public headers (audit ARCH-04,
documentation half): the full contract block at the top of
runner.hppand@par Thread safetynotes onDevice,Imageand
Preset— the library is single-threaded per Device by design;
distinct Devices (separate Vulkan devices/queues) are independent and
may be used concurrently; the core layer keeps no shared mutable state
apart from the shader-root global (set it once at startup); glslang's
process initialization is internally mutex-guarded, so concurrent
CompileSlangcalls are supported. The existingChainand
SetShaderRootDirnotes predate this and now have company.
Fixed
TranslateSlangreads the shader file once per call — the first
optimization surfaced by the benchmark suite above (the translate
bench on Windows was paying for two file opens per call). The
include-resolution walk now captures the raw top-level bytes it
already read, and the source fingerprint (sourceSize/sourceHash)
is computed from that capture — previously the file was opened a
second time purely for the fingerprint (ReadWholeFileBytes).
Measured: 1,364 → 1,119 µs per call on the RX 560 box (−18%),
84.4 → 81.6 µs on the lavapipe sandbox (where a read costs ~1 µs).
Byte-for-byte parity of allSlangTranslationfields verified across
LF, CRLF, include-resolved and failure-path cases (the fingerprint
remains a hash of the raw file bytes, CRLF included). The saving
applies per translated shader — and per pass, since chain creation
translates every pass shader.
Full Changelog: libslang-vk-v4...libslang-vk-v10
1.6.0-dev-dea220a
1.6.0-dev-dea220a (2026-10-19 10:13:33 PM)
Window fc9743319..dea220af4 — 64 commits, 163 files changed (+7,501 / −32,700),
2026-10-18 00:20:57 → 2026-10-19 10:13:33 PM America/Toronto.
The window that turned the fork's shader stack inside out. The in-tree slang
translator/preset runner was extracted into the standalone
libslang-vk library — first vendored,
then consumed via CMake FetchContent with the pin climbing M4 → M4.05 — leaving
gpu_scaler.cpp as a pure host adapter (window claim, frame upload,
presentation, OSD, diagnostics, session fallback). Mid-window the tree was
reset to the upstream baseline (clean tree): upstream's CI workflow set and
README were restored, the fork's audit tooling and design docs were purged,
and the changelog was consolidated back to the upstream history. On top of
that reset came a chain of SDL3 display/DPI fixes for real field crashes,
mouse-coordinate mapping for the scaler-owned presentation path, MSVC link
repairs for the static and nonstatic builds, a fork-only CI build gate, a
local Windows 11 build script, and the merge-plan-r4 individual-change PR
staging. Upstream was synced twice (diasurgical/master PRs #2 and #3),
picking up the PCX scanline padding fix. The repository is now
DevilutionX-libslang-vk.
Added
GPU scaler / libslang-vk
- libslang-vk adoption — the slang compile/translate/preset-runner stack
moved out of the fork and into a standalone reusable library
(VTSTech/libslang-vk), consumed by CMakeFetchContentand pinned at
M4.05 (c3567b03) after stepping through M4, the CMake 3.22 floor, the
push-block runner fix, and M4.04 ("2637 load, 21 fail").3rdParty/libslang-vk,
3rdParty/SPIRV-Cross,3rdParty/glslang,Source/engine/render/gpu/preset/,
the prebuilt SPV/MSL headers andgen_spv_header.cmakeare all gone;
Vulkan translation and preset execution now live behindslv::Chain. - Mouse mapping for scaler presentation —
devilution::gpu_scaler:: MapWindowToGame()/MapGameToWindow(): the last presented frame's output
rect is cached at present time and invalidated with the swapchain, and the
SDL3OutputToLogicalpath routes through the mapping so the cursor stays
truthful when the scaler owns the window (window points → swapchain pixels
→ letterboxed game rect, and back). - libdevilutionx_options decoupling — the resolution helpers
(GetNativeDesktopResolution,ClampResolutionForIntegerScaling,
IsResolutionAboveNative) moved fromdisplay.cppintooptions.cppso
the standalone options object library links without the display translation
unit. - Disabled-option UI support —
OptionEntryBase::IsDisabled()virtual;
greyed-out entries render silver-dark, ignore selection, and are skipped in
navigation (ElementDisabled).
Build / CI / tooling
gpu-scaler-build.yml— fork-only build gate (upstream's workflows
don't trigger on fork refs): Linux x64 SDL3 + Windows MSVC x64 with
-DUSE_GPU_SCALER=ON, vendored sodium, explicit MSVC dev env, no Vulkan
SDK needed at build time (volk loads the loader at runtime), build outputs
always uploaded as artifacts, ctest kept as the Linux green signal.- Full upstream CI set restored — 25+ workflows (3ds, Android, iOS,
macOS arm64/x86_64, PS4/PS5, Switch, Vita, Xbox nxdk/One, Windows
MSVC/MinGW x64+x86/XP/9x, Linux x86/x86_64/aarch64/SDL1/SDL3-test,
amiga-m68k, miyoo_mini, opendingux, retrofw, s390x big-endian QEMU,
src_dist, translations, clang-tidy, cache-cleanup); the fork-only
gpu-shaders.ymlretired. tools/build-local-win11.ps1+.cmd— local Windows 11 build
reproducing the CI Windows job 1:1, with-Link static(default,
self-contained exe),-Link nonstatic(libslangvk.dll+ SDL3 trio
collected into a runnable package and smoke-tested like a user machine)
and-Link both.- Packaging helpers —
install_git.cmd(winget Git for Windows + LFS),
install_shaders.cmd(clones libretroslang-shaders+git lfs pull
intoshaders/),clear_gpu-scaler.log.cmd, Linux/Windows
clear_shader_cachescripts;.gitignoregainsgpu-scaler.log. BUILD_SHARED_LIBSpin —set(BUILD_SHARED_LIBS OFF)at root scope in
Dependencies.cmake: SDL_image/SDL_mixer's CMake glue was FORCE-writing
BUILD_SHARED_LIBS=ONinto the global cache, turning libzt's
natpmp_pic/zto_picinto DLLs and breaking the nonstatic MinGW/Linux
links (undefined __imp_socketetc.).
Tests
test/options_test.cpp(+220) — covers the resolution list filtering,
integer-scaling clamping and the new disabled-option semantics, with
malformedpreset_smokefixtures (bad count/index/slang, empty,
missing shader, broken shader) staged at runtime.test/pcx_to_clx_test.cpp(+212) — PCX→CLX conversion regression
tests, including scanline padding cases.- Missing-asset guards —
DevilutionTest::MissingAssets()lets MPQ-less
test runs skip SetUp cleanly instead of failing.
Upstream merge planning
docs/merge-plan-r3.mdanddocs/merge-plan-r4.md+
build_r4_branches.py— the r4 plan implements upstream maintainers'
"make a branch for your individual changes" directive: the builder script
cuts one topical branch per change (pr/1-sdl3-build-fixes,
pr/2-wav-audio-robustness,pr/3-robustness-guards,
pr/4-test-missing-asset-guards,pr/5-gpu-scaler,pr/6-sdl3-main-glue,
…) straight from fork payload blobs, with EOL normalization against each
upstream file's stored convention and attribution stamps applied to the
scaler PR's new files.
Changed
- Clean-tree reset — fork README replaced by the upstream base, fork CI
replaced by the upstream workflow set, and the changelog consolidated:
docs/CHANGELOG.mdnow carries the upstream (diasurgical) history,
docs/CHANGELOG_UPSTREAM.mddeleted, and the fork's1.6.0-devnarrative
entries retired (this log resumes with the present entry). - Repository renamed to
DevilutionX-libslang-vk— workflow URLs, plan
docs, build script and thegszProductNamebranding string
(DevilutionX-libslang-vk <version>, shown in the main-menu footer and
Discord rich presence) all updated. - Audit-era content purged —
docs/audit/,tools/audit/,
docs/GPU_SHADERS.md,docs/SLANG_SPEC.md,docs/deltas.md,
docs/brief.md,docs/diablo-handoff.md,docs/libslang-vk-plan.mdand
the merge-plan r1/r2 drafts removed with the in-tree shader stack they
documented (−32,700 lines across the window). - Options plumbing — the shader-filter preset browser stores
slv::preset::PresetEntrybehind a forward declaration sooptions.h
stops dragging library headers into every target; EOL normalization for
Source/options.cpp,vcpkg.json,3rdParty/SDL3_image/CMakeLists.txt
andCMake/Tests.cmake(the fork had rewritten CRLF files to LF,
inflating true diffs ~10x; re-normalized per upstream convention). - Upstream syncs —
diasurgical/mastermerged twice (fork PRs #2 and
#3), bringing upstream's PCX scanline padding fix (Gleb Mazovetskiy) and
other upstream movement onto the branch.
Fixed
Display / DPI (SDL3)
- SDL_GetWindowDisplayScale() misread as pixel density — Windows, Linux
and Android SDL3 backends report display modes in physical pixels, but the
resolution list was multiplying them by the OS UI scale: at 125% Windows
scaling every mode became a phantom size (1920×1080 listed as 2400×1350
"1350p"), hiding the native mode and offering above-panel resolutions that
crash Integer Scaling. Physical-pixel backends now use a scale factor of
1.0; the Apple point-based backends (UIKit/Cocoa) keep using the window
display scale, as does the SDL2 renderer-output path. - Above-native resolution selection crash — selecting a 1920p-class entry
on a 1080p desktop with Integer Scaling enabled floored the integer factor
to zero and divided by zero (field CTD). Interactive selections are now
caught byClampResolutionForIntegerScaling()with a messagebox and a
fallback to native;CalculatePreferredWindowSize()clamps the factor to
at least 1 (logged) as a backstop for staledevilutionx.inivalues
carried over from other displays. - Integer Scaling can no longer be enabled in the crash state — the
option greys out viaOptionEntryIntegerScaling::IsDisabled()while the
game runs above the native desktop resolution (hand-edited ini or a GPU
driver VSR mode), until a within-native resolution is selected. - Resolution-DPI settings polish — the above fixes ride a settings-menu
rework: disabled entries draw greyed-out and non-interactive, and the
resolution list filters above-native sizes while Integer Scaling is on.
Build (Windows / MSVC)
- LNK2019 unresolved external symbol
WinMain— SDL3'sSDL.hno longer
includesSDL_main.h;Source/main.cppnow includes it so WIN32
GUI-subsystem executables get theSDL_main/WinMainentry points. - "Multiple rules generate slangvk.lib" (MSVC/Ninja, nonstatic build) —
the staticslangvktarget and the sharedslangvk-sharedvariant both
emittedslangvk.libon DLL platforms; from the consumer side the shared
variant is renamed to the documentedlibslangvkartifact, its import
library moves to its own folder, and distinct PDB names stop the
static/D link-PDB race. Linux/macOS untouched (libslangvk.so). - libzt/MinGW nonstatic link failures — fixed by the root-scope
BUILD_SHARED_LIBSpin above.
PCX (upstream)
- PCX scanline padding handling — fixed upstream-style in
load_pcx.cpp/pcx_to_clx.cpp/surface_to_pcx.cpp(Gleb
Mazovetskiy), with ...
1.6.0-libslang-vk-v5
Now using libslang-vk M4.05
[M4.05]
Added
- shader_demo
r/reload— re-reads the current preset from disk and
rebuilds its chain: the edit-slangp → reload → see-result loop without
restarting the demo. The fresh parse goes into a side slot and the cache
entry swaps only after the rebuilt chain replaced the old one, so the
cache-outlives-chain invariant holds at every step and a failed re-parse
leaves the previous preset + chain fully intact. Runtime parameter
overrides are cleared by a reload (the chain is rebuilt from file
values). First entry of the M4.04-post follow-up wave. - Process-global glslang frontend mutex (freeze-review F-1, decision
D-3a):docs/API.md§5.3/§6 claimed "glslang is process-globally
guarded", butGlslangRuntimeguarded lifecycle only —
InitializeProcess()once,FinalizeProcess()at exit, no lock ever
taken (the<mutex>include was dead weight). Two frontend sites ran
unserialized —CompileStageToSpv'sTShader::parse/link/
GlslangToSpvand the translate-time dead-code oracle's
TShader::preprocess— so two concurrentPreset::loads could race
inside glslang, with nothing in CI exercising concurrency. One
process-globalstd::mutex(GlslangFrontendMutex, defined in
slang_compile.cpp, forward-declared inslang_translate.cppper the
EnsureGlslangRuntimepattern) now serializes both entry points;
seconds-scale loads make contention irrelevant and the written threading
claim is true as published. API.md §4.4/§5.3 state the contract.
Changed
Chain::resizedrops its two dead input-size parameters
(freeze-review F-2, decision D-1a; pre-freeze signature break):
resize(inputW, inputH, outputW, outputH)is now
resize(outputWidth, outputHeight). The review proved all three
documented input-size clauses wrong — pass FBOs derive from the
per-frameInputFramesize plus the stored target at render, and pass
targets, the history ring and the mip copy are (re)created lazily on
size drift; the impl only validated the output size and stored it (the
input params were even commented out of the definition). Call sites were
ours (test_runner_m2.cpp,shader_demo.cpp). API.md §5.4 rewritten to
the lazy-rederive semantics; §8 gains the host duty that a swapchain
extent change (window resize /VK_SUBOPTIMAL_KHR) must be followed by
resize()with the new extent before the next render (F-3 — render
rejects a drifted extent withinvalid_argument).docs/API.mdfolds the eight freeze-review findings (Task 29):
§3 names the library's only two stderr writers as documented exceptions
and documentsSLANGVK_FILL_DEBUG(F-8, decision D-4a); §5.4 states
setParameteris write-only by design — no getter in v1, UI-facing
hosts track the merged view fromparameterInfo()+ their own calls
(F-2's getter gap, decision D-2b); §4.2 documentsResolvePresetFilePath
refusing out-of-root hits as the deliberate SEC-01 sandbox behavior
(F-5); §4.3 enumerates the 14UniformTypevalues (F-7); §5.5 carries
the header default ofSwapchainFrame::format(F-6) and the three
undocumented render-time validations — offscreen target needs a concrete
ChainOptions::outputFormat,SwapchainFrame::formatUNDEFINED/
zero-texel rejection, nullInputFrame::imagewhen history/mip is
required (F-4); §9 cross-references the stderr exceptions.
Fixed
- shader_demo
paramsnow shows live values. It printed the preset
default as the current value, so afterset BRIGHTNESS 3.0the display
still read1.250— indistinguishable from "the set didn't take" (the
exact confusion behind the maintainer's live-parameter field report).
Runtime overrides are now tracked per session and reported with a
[runtime override]tag; the lib path itself was never broken —
verified by byte-different PNG dumps before/afterseton llvmpipe.
Help text and both CI artifact USAGE blocks documentreloadand the
file-edits-need-reload rule.
Planned
- M5 (1.0): API freeze, packaging,
docs/API.md. - The 21 ledger-FAIL presets are upstream-broken or malformed (see M4.03) —
no library action planned; they stay pinned as FAIL in the baseline.
Full Changelog: gpu-shaders-v18...libslang-vk-v5
1.6.0-libslang-vk-v4
Now using libslang-vk M4.02
[M4.02] — 2026-10-18
Corpus rescan release: full-corpus verdict went from 826 LOAD / 1832 FAIL
(2026-10-17 baseline) to 2567 LOAD / 91 FAIL — zero regressions at the
pinned revision.
Fixed
#referencemerge rebase stored cwd-relative paths (RebaseReferenceFilePath()
insrc/core/preset_parser.cpp): when the scan root was relative (the
documentedcorpus_sweep slang-shadersinvocation), rebased pass/texture
paths were joined onto the importing preset's directory at load time,
producing garbled paths and failing ~700 otherwise-healthy presets. Rebased
paths are now absolutized inside the SEC-01 shader root. Regression test in
tests/test_preset_parser.cpp(relative root +#referencechdir fixture
asserts an absolute, existing merged path).
Added
corpus_sweep --only-from FILEwhitelist for chunked rescans (keeps
corpus-root semantics; enables full-corpus sweeps in sandboxed/short-lived
environments).- CI: the Windows
shader-demoartifact now bundlescorpus_sweep.exe(Linux
shipscorpus_sweep), with an updated USAGE.txt section showing the
absolute-root invocation. .github/workflows/corpus.ymlworkflow_dispatchcan regenerate the ledger
without any local binary.
Changed
corpus/baseline-ledger.txtrefreshed at the same corpus rev: 2658 presets
→ 2567 LOAD / 91 FAIL, 0 regressions, +1741 improved (bezel +1367,
presets +160, hdr +146, crt +32, ...). Added/gone: 0/0.
[M4.01] — 2026-10-18
Fixed
slang: unknown texture reference 'PhosphorPassFeedback'on presets
whose passes are named only via#pragma name(noaliasN— e.g.
crt-yah.slangp): the translator's texture-name universe only knew LUT
names,aliasNandUser#, while the chain runtime already resolved pragma
names,Feedback-suffix taps and<name>Size. Pass names from
#pragma nameare now collected at load time and seeded into the universe,
so bare-name taps, previous-frame<name>Feedbacktaps and<name>Size
UBO members all bind.
Added
- Public API
slv::preset::CollectSlangPragmaNames(shaderPath, out)
(include/slangvk/core/slang_translate.hpp): soft-fail include-once scan
with the sameResolveIncludesbucketing asTranslateSlang; scan failure
is silent and real errors surface from per-pass translation.
Tests
- Four feedback-tap regressions in
tests/test_slang_translate.cpp: collector
finds the pragma name; seeded universe bindsPhosphorPassFeedbackin set 2;
lowercase spelling resolves; empty universe still hard-fails with the
original diagnostic.
[M4] — 2026-10-17/18
DevilutionX presentation spike (plan §8 mode (a)) plus the shader_demo
example app and its first two real-world fix waves.
Added
examples/shader_demo: hybrid windowed/headless demo — GLFW backend
probing with headless-only fallback, TUI preset browser, animated-pattern +
image inputs, PNG frame writer, and a--headless --no-menu --frames N --out DIRsweep mode used for corpus verification (~2k lines across
shader_demo.cpp,window_target.hpp,input_source.hpp,
preset_browser.hpp,frame_writer.hpp).- Default input: bundled
assets/testcard.png(480×320 pixel-art
Goku/Mario/Luigi, generated bytools/make_testcard.py+tools/sprites/),
with a 5-level portable asset lookup starting at the executable's directory. - Corpus tooling:
corpus/baseline-ledger.txt(per-preset LOAD/FAIL
baseline) +.github/workflows/corpus.yml— nightly pinned-rev sweep with a
no-regression gate. - Runner presentation:
SwapchainFramewith letterbox and rotation;
push-block / size-param runner coverage (test_runner_m4.cpp,
test_runner_pushblock.cpp,sizeparam/pushfraggolden shaders). docs/CHECKLIST.md,tools/scan_codefindings.py, vendored
stb_image_write.h; README CI badge + screenshot.
Fixed
- crt-consumer.slangp black screen: UBO member parsing stripped
smooth
fromfloat smoothness;via a left-token match, leaving memberness
zero-initialized and the corner mask at 0. Added a right-token-boundary
check plus aSLANGVK_FILL_DEBUGblock-fill dump inchain.cpp, with a
byte-exact golden regression (tests/data/runner/smoothqual.*). - analog-service-menu.slangp
include target outside the shaders root:
the SEC-01 ancestor-retry now also fires on that error string (previously
only on cannot-open). - shader_demo defaulted to the animated pattern (characters never visible);
it now defaults to the testcard image and falls back only if the asset is
missing.
1.6.0-libslang-vk-v3
Full Changelog: libslang-vk-v2...libslang-vk-v3
1.6.0-libslang-vk-01ea2bf
Full Changelog: libslang-vk-v1...libslang-vk-v2