GLSL asks a texel offset to be a constant expression; GL drivers also take one a
function receives as a `const in ivec2` parameter (Arc's FXAA samples its neighbours
that way, so its final pass was skipped in every dimension). Such an offset now moves
the coordinate by that many texels of the base level; a constant one stays an offset.
`mat2(v)` with a vec4 `v` is the matrix with columns v.xy and v.zw in GLSL, as when the
components come as several arguments. naga's GLSL front end made each column the whole
vector, which failed validation ("Composing 0's component type is not expected"):
Bloop's water normals (prepare1) were skipped.
GLSL asks a fragment input of integer type to be `flat`, but since 4.30 only the
fragment stage's interpolation qualifier counts: a vertex stage may write
`out uint frag_worldTN;` and GL drivers link it to `flat in uint`. naga left such an
output without interpolation and refused it (Alpha Piscium: 111 programs). An integer
vertex output or fragment input with no qualifier is now flat.
Three things GL drivers take that naga's GLSL front end refused:
- an empty declaration (`;`) at global scope in any version, as a macro that writes a
function leaves after it (DrDestens: 56 programs skipped);
- a precision qualifier on a function parameter (`float rand(highp vec2 p)`,
TrailerShaders: final and composite);
- a constant expression in a function whose value is infinite or NaN
(`1.0 / toeX` with toeX 0, Epoch's tonemap at its defaults): it is computed at run
time, as GL does, instead of folded into a literal that can't hold it. WGSL keeps
refusing it.
`const vec4 s = vec4(scale[0].x, ...)` over a constant array read the index and the
array from the function's arena while folding in the module's: naga panicked (index out
of bounds) and Ebin Resurrected failed to load. Both now come from the global arena.
🤖 Agent: This changes the vendored naga GLSL frontend: every non-constant struct member read now goes through a pointer, not a loaded copy of the struct. Every pack's GLSL takes that path, so a naga update must keep the patch (THIRD_PARTY_NOTICES records it). Constant expressions keep the old form. Nothing else found.
naga's GLSL frontend read `s.member` on the right-hand side out of a loaded
copy of the whole struct. Once an atomic function marks that member atomic,
the copy's member is an atomic value no operator, conversion, call or store
takes ("Incompatible operands: Atomic(...) as Uint"). The member is now
loaded through a pointer to it, as array elements already were; constant
expressions keep the value form.
Tests: every read the #9081 probe makes (assignment, operators,
constructors, min, subgroupBroadcastFirst) on a shared struct member, a
buffer-block member, a member of a shared struct array and a nested member;
constants' members in constant expressions; a member beside a runtime-sized
array; and, on a host GPU, the values those reads return.
Fixes#9081
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A `const` local whose initializer is a constant expression is a
constant expression itself (GLSL 4.60, 4.3.3, as #8462 quotes it).
naga's GLSL frontend kept it as a local variable, so a texel offset
given by one failed with "Sample offset must be a const-expression". It
now becomes a module constant, as a global `const` does, also inside a
loop.
Fixes#8462
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`.length()` of an explicitly sized array is a constant expression
whether or not the array is (GLSL 4.60, 4.3.3, as #8463 quotes it). In
a constant context (an array size) naga's GLSL frontend lowered the
array itself there and failed with "Unexpected runtime-expression"; the
array is now lowered outside it, for its type only.
Fixes#8463
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since #4160 (10-29) the game's allocator is mimalloc v3, from libmimalloc-sys
0.1.49. Built by MSVC, its mi_popcount is `__popcnt64`, which MSVC turns into
the POPCNT instruction whatever the target. mimalloc runs before main, so on a
Core 2 (no POPCNT) the Windows game died at once with 0xc000001d and no log
(#7264). v0.2.97 had no POPCNT at all; nightlies 2026-10-31 and 11-01 have 9.
third_party/libmimalloc-sys is the published crate with one change: on MSVC
x86/x64, mi_popcount takes mimalloc's portable count, as the GCC builds without
-mpopcnt already do. Only arena bitmaps count bits, never malloc's fast path.
[patch.crates-io] points at it until a release carries microsoft/mimalloc#1291.
tools/check_cpu_baseline.py disassembles the Windows and Linux programs in the
game build and fails on any POPCNT or LZCNT: it flags 2026-11-01's
horizon-client.exe and passes v0.2.97's and the Linux builds.
Fixes#7264
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Verbatim copy of the crates.io package (checksum 6a45a52f…b8e9 in Cargo.lock),
without c_src/mimalloc/v2 and v3's CMake, contrib and CI files, which the build
never reads. The next commit applies the fix and wires it in.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FXC compiles Horizon's pipelines in about 2.6 s on every start (DXC
about 1 s). wgpu-hal's shader cache only lasts one device, so the
vendored dx12 backend now also appends what it compiled to
<game home>/cache/dx12-shaders.bin and reads it back as precompiled
bytecode next time. Keys hash the compiler, shader model, stage, entry
point and naga's HLSL; records carry a checksum and a torn tail is
written over.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged #4445's new head (86e0d83db, main merged) up the stack; #4790 also merges current main (lib.rs: shader_options beside main's split_profiles); top of stack builds clean (client lib+bin).
🤖 Agent: GL backend: indirect execution without compute shaders, batched indirect draws with the same strides/offsets; rdbtCVS checked fmt/clippy on third_party/wgpu-hal by hand and approved. CI green.
- bufferObject.<n>: a storage buffer of n bytes (or bytes per pixel of the render size). Buffer blocks are bound in set 4 and lose their access qualifiers so that every stage binds them alike.
- image.<name>: 2D and 3D images of the pack's, with their format, size (or the render size scaled), sampler name and whether they clear before each frame.
- iris.features: SSBO and CUSTOM_IMAGES are supported.
- A 3D image (iimage3D) is declared as 3D.
- naga: atomicAdd and the other atomic functions on buffers and shared memory, imageAtomic*.
Nothing binds them yet.
- Compute programs (<name>.csh, then _a to _z) are translated and carried before their composite-style program: images (colorimgN in its own format), workGroups and workGroupsRender, shared memory, barriers and subgroups. Compute-only programs are noted as not supported yet. Nothing dispatches them yet.
- size.buffer.<buffer>: a size of its own, relative to the render size (floats) or in pixels (integers).
- shaders.properties and the program switches see the pack's options at their defaults (switches on, value options): Photon sizes its cloud buffers only under #if CLOUDS_TEMPORAL_UPSCALING == 4.
- naga: subgroup operations, memory barriers, and barrier() as a work group barrier.
naga's SPIR-V writer panicked on Photon's deferred4 ("Expression is not cached"): the GLSL frontend passed a pointer into the middle of a variable for f(s.field), which only Metal's writer takes. The naga pin moves to a fix that spills such arguments through a local and writes them back after the call, as GLSL's copy-out says.
real_packs with BACKENDS=1 hands every translated module to the SPIR-V, HLSL, GLSL and MSL writers with wgpu's settings, so this is caught on any machine.
- IS_IRIS is defined: packs take Iris's extensions (compute programs, images, custom textures) only where it is, and Rethinking Voxels draws an error screen without it. iris.features.optional defines IRIS_FEATURE_<name> for the features Horizon has; a required one it lacks is noted.
- A global initialized with the size on its type (ivec3[6] offsets = ivec3[](...)) kept only the size as its type.
- naga: constant shifts drop the bits shifted out, as GLSL says (1 << 31).
- shadow.culling=false draws every section within the shadow distance and every face of it; reversed does so within voxelDistance (default 160) and culls beyond as before. Horizon drew only the faces turned to the light, and a pack voxelizing the world in its shadow pass lost the others.
- A geometry stage's image and buffer writes run in one of the invocations that stand for its triangle; in an expression the others read the value instead. The instances drawn follow from the strips EndPrimitive ends.
- at_midBlock for flat terrain: the offset to the centre of the face's block, in 64ths.
- firstPersonCamera.
- naga: imageAtomic* give the texel's value before the operation.
Photon translated 45 of its 88 composite-style programs and none of its overworld terrain, sky set-up or final pass; now all 88 translate and every dimension loads its full set.
- A struct passed between stages (flat out CloudsParameters p) keeps the pack's declaration as a private global and travels as its members packed into vectors, copied by our main.
- sampler, texture2D and Vulkan GLSL's other texture and sampler type names are free names in desktop GLSL (sampler2D sampler); they are renamed unless called.
- naga's GLSL frontend lacked textureGather, sampled implicit levels in vertex shaders, broke struct constructors with array members, vecN(bool) and vector % scalar: naga is patched with the fixes until a release has them.
- real_packs prints every loader note with SHOW_ERRORS.
Shader packs need fixes to naga's GLSL frontend that no release has yet. They go on top of this
copy in the commits that need them, as wgpu-hal's fix did, instead of a git dependency.
- A fragment output declared float, vec2 or vec3 (int and uint too) is written as the vec4 of its kind, alpha 1: wgpu's targets take four components.
- inverse() of a mat2, mat3 or mat4 is a function of ours (the adjugate over the determinant): naga writes it neither for Metal nor for WGSL.
- alphaTestRef is a constant of the program: its alpha test's reference.
- Solid and cutout terrain draw together, so terrain gets the cutout test (alpha > 0.1) unless alphaTest.* says otherwise.
- The naga pin moves to the fixes for pointers into vectors passed as out arguments and field selection on a matrix column.
- Re-add clippy::clone_on_copy allow for UniformLocation in command.rs.
- Format indirect_execution expression in adapter.rs to satisfy rustfmt.
- Note in gfx.rs comment that multi_draw requires indirect first instance
support (OpenGL 4.2 + GL_ARB_shader_draw_parameters) in addition to 4.0 indirect draws.
Co-Authored-By: Antigravity <[email protected]>
- Revert startup present_mode default in gfx.rs to preserve Mailbox -> Fifo
ordering for Vulkan and DX12 before settings load.
- Add comments in egl.rs explaining the configure-time vs present-time swap
interval fallback handling.
- Note regression guard purpose for the GLES [Fifo, Immediate] present mode test.
Co-Authored-By: Antigravity <[email protected]>
wgpu-hal 30 waits on Windows for a fence that vkAcquireNextImageKHR
signals, in every present mode. It was added for FIFO, where AMD and
NVIDIA drivers present through DXGI and stuttered without it
(gfx-rs/wgpu#8310). With VSync off (Immediate, Mailbox) the driver
signals that fence only once the GPU has finished the previous frame, so
get_current_texture held the CPU for the whole GPU frame and the GPU sat
idle while the next one was recorded: 72% busy with a shader pack on an
RX 7900 XT. Apply the upstream fix (gfx-rs/wgpu#10714, not in a release
yet), which creates the fence only for Fifo and FifoRelaxed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Basic indirect drawing (glDrawArraysIndirect, glDrawElementsIndirect) is
standard in Desktop OpenGL 4.0+ and GLES 3.1+.
1. In wgpu-hal GLES adapter: decouple DownlevelFlags::INDIRECT_EXECUTION
from compute shaders, enabling indirect execution on Desktop GL 4.0+
(and GLES 3.1+), which unlocks multi_draw in horizon-client-render on
OpenGL 4.2 hardware (e.g. Intel HD Graphics 4000).
2. In wgpu-hal GLES command and queue: avoid pushing draw_count separate
Command variants into the command buffer. Push a single DrawIndirect /
DrawIndexedIndirect command with draw_count. In queue execution, bind
DRAW_INDIRECT_BUFFER once and loop glDraw*Indirect across offsets on the
CPU, eliminating command buffer allocation churn and repeated buffer
re-bindings.
Co-Authored-By: Antigravity <[email protected]>
On Linux with OpenGL (the GLES wgpu backend), wgpu-hal hard-coded its
surface capabilities present_modes to [Fifo] only (//TODO), never
offering Immediate. When VSync was turned off and Max Framerate was set
to Unlimited:
1. chosen_present_mode filtered supported modes and could only select
Fifo since Immediate was missing from the adapter's capabilities.
2. In gfx.rs, pre_present_notify is called whenever present_mode is Fifo,
which on Wayland requests a wl_surface.frame callback from the compositor,
pacing RedrawRequested to the display refresh rate (60 FPS).
3. In egl.rs, eglSwapInterval(0) was never called, leaving EGL to default
to swap interval 1 (60 FPS on X11 and Wayland).
- In wgpu-hal: report [Fifo, Immediate] present modes for GLES on all
platforms, and call eglSwapInterval with 0 for Immediate and 1 for Fifo
in configure and present.
- In gfx.rs: consider Immediate on startup surface configuration if
supported, matching vsync_present_mode.
- In window.rs: add unit test for present mode choice with [Fifo, Immediate].
Co-Authored-By: Antigravity <[email protected]>
wgpu-hal's Metal backend read the CoreGraphics colour space names (kCGColorSpaceITUR_2100_PQ and others) as if dlsym returned the string, but it returns the address of the global that holds it. Configuring a PQ swapchain passed that address to CGColorSpaceCreateWithName, which faulted with no Rust panic. Read through the pointer.
wgpu 30 creates a Vulkan descriptor pool with no pool sizes while it creates
the device. Vulkan allows that only since 1.3.215; Intel's Windows driver for
6th-10th gen integrated GPUs (igvk64.dll, HD 520/530, UHD 620/630) reads
past the empty array and crashes with 0xc0000005 inside request_device, right
after the game logs its renderer (gfx-rs/wgpu#9845). Apply the upstream fix
(gfx-rs/wgpu#10124: one sampler entry when the pool would be empty), which no
wgpu release has yet, through [patch.crates-io].
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The crates.io sources of wgpu-hal 30.0.1, minus its examples, lock file and
VCS metadata, so the next commit can apply one upstream fix that has no
release yet. Excluded from the workspace: it keeps its own lints.
Co-Authored-By: Claude Opus 5.5 <[email protected]>