Skip to content

release: v0.34.11 - #388

Merged
kolkov merged 16 commits into
mainfrom
release/v0.34.11
Nov 8, 2026
Merged

kolkov merged 16 commits into
mainfrom
release/v0.34.11

Conversation

@kolkov

@kolkov kolkov commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

A lifetime and validation patch: resources released after recording stay alive through the work that uses them, pass input is validated as the spec requires, GLES works on real ES contexts, and the module builds on 32-bit. naga v0.21.7 brings the DX12 position.w fix and the DXIL WARP pair.

Commits

  • cba00f7 docs: v0.34.11 state, and the CI and GLES facts the release changed
  • 95fd262 chore(deps): naga v0.21.7, the DXIL WARP pair, and its readback case
  • ad143fc test(hal): naga's flow/short-circuit-or-rt readback case
  • 7def6d9 chore(deps): naga v0.21.6, and the DX12 position.w readback asserts 1/w
  • d5b21db test(hal): pin stencil increment-wrap, decrement-wrap and the write mask
  • d1e27ec fix(gles): detect BC textures from core GL versions and ARB names
  • b38aab1 fix(gles): MSAA, buffer mapping and storage textures on OpenGL ES
  • bc2e22b test(vulkan): type-check the vulkan tests for 32-bit targets
  • 6b28f8b ci: build windows/386 in the cross-compile job
  • 6d1a79e ci: lint windows and darwin targets, pin every action (P2-INFRA-007)
  • 2c2c73f fix(core): validate SetViewport and vertex attribute formats
  • 026ddf0 fix: a released bind group layout cannot back a new bind group or pipeline layout
  • 28e7af1 fix(rust): an unmapped error filter is an error, not a silent Validation scope
  • 12358be fix: build on GOARCH=386 — int constants no longer overflow
  • 111fbbd fix: pipelines keep their pipeline layout alive
  • 87a1061 fix: a query set outlives its Release until the work that recorded it completes

[0.34.11] - 2026-11-08

A lifetime and validation patch: resources released after recording stay alive through the work that uses them,
pass input is validated as the spec requires, and the module builds on 32-bit.

Fixed

  • A query set released after recording stays alive until that work completes (P1-CORE-021, closes the v0.34.10 known issue). QuerySet.Release destroyed the HAL query pool at the previous submission index, so a set released after timestamp writes or a ResolveQuerySet were recorded went away under the submitted command buffer: VUID-vkDestroyQueryPool-queryPool-00793 on Vulkan, zero timestamps on DX12. Query sets are reference counted as samplers are; the command encoder holds the set's reference until the submission completes, and Submit refuses only a set whose HAL resource is gone (ErrSubmitQuerySetDestroyed).
  • A render or compute pipeline keeps its pipeline layout alive (P2-CORE-022, closes the second v0.34.10 known issue). A PipelineLayout released after the pipeline was created was destroyed while later SetBindGroup calls still named it — an invalid VkPipelineLayout at vkCmdBindDescriptorSets. Pipelines now hold their layout's reference and drop it when their own resource is destroyed.
  • A released bind group layout cannot back a new bind group or pipeline layout (P2-CORE-023). CreateBindGroup and CreatePipelineLayout now return ErrReleased, as CreateBindGroup already did for a released sampler. Before, the released layout's HAL handle reached vkAllocateDescriptorSets and crashed inside the driver.
  • SetViewport is validated as the spec requires (P2-CORE-019). Non-finite or negative sizes, an origin outside the viewport range, or a depth range outside [0, 1] is a RenderPassValidationError on the encoder instead of invalid values reaching the driver. Where the spec and Rust wgpu differ, the spec decides.
  • A vertex format gputypes does not define is a pipeline-creation error (P2-CORE-019, CreateRenderPipelineErrorInvalidVertexFormat). Each backend used to substitute a format of its own (float32x4 on Vulkan and GLES, an invalid Metal format, an unknown DXGI format).
  • The module builds on GOARCH=386 (P1-INFRA-008). Four comparisons against TrackerIndex and math.MaxUint32 were written as int constants and overflowed a 32-bit int, so every 386 build of wgpu failed to compile. Comparison is done in uint64 now; behaviour on 64-bit targets is unchanged. Build only — there is no GPU on 386, and ecosystem CI needs goffi's own 386 support before the other repositories can run the leg. Two test files that did not type-check for 32-bit are fixed with it.
  • GLES: MSAA, buffer mapping and storage textures work on an OpenGL ES context (P1-GLES-025, items 1 and 3). Multisampled render targets were created with glTexImage2DMultisample, which ES does not have, so every MSAA texture failed with GL_INVALID_OPERATION; they are renderbuffers now (glRenderbufferStorageMultisample), as in Rust wgpu-hal. MapBuffer used glMapBuffer, absent from ES, so every buffer readback of GPU-written data returned zeros while rendering was correct; it maps with glMapBufferRange. And glBindImageTexture rejects a texture with mutable storage on ES, so compute storage textures wrote nothing; textures are allocated with immutable storage (glTexStorage2D/glTexStorage3D) where the context offers it.
  • GLES: BC textures are detected from core GL versions and ARB names (P2-GLES-024). Detection required only the EXT_texture_compression_* names, so a driver that lists GL_ARB_texture_compression_rgtc (Intel on Windows) advertised no BC formats at all. RGTC from desktop GL 3.0, BPTC from 4.2 and the ARB names are accepted now; the compressed-format readback compares all 24 formats against the software backend on that adapter.

Changed

  • naga v0.21.7. The DX12 fragment @builtin(position).w is now WGSL's 1/w (naga P1-HLSL-037): the perspective readback asserts it on both DX12 legs instead of excluding them. DXIL mem2reg promotes bool locals as i32 (naga P2-DXIL-107/092): a short-circuit || chain's local used to be a phi i1 or an alloca i1, and WARP, the D3D12 software adapter, ran neither arm's stores on the first shape while D3D12's GPU-based validation dropped them on the second. The new readback case flow/short-circuit-or-rt pins the values on every backend; the discriminating WARP + GPU-based-validation run is kept in the gate harness. Also in the release: the streaming WGSL lexer (behaviour-neutral), a DXIL built-in used as a runtime array index, a DXIL refusal of workgroup storage above the model's limit instead of a bad module, HLSL storage stores nested past 20, and an opt-in SPIR-V demote-to-helper (spirv.Options.DemoteToHelperInvocation, off by default). No IR change.
  • Device.PushErrorScope returns an error for a filter outside the three WebGPU filters (P3-API-025). It used to map an unknown value to Validation silently, pushing a scope that could never capture what the caller asked for. The native build validates in core, -tags rust rejects the value before it reaches wgpu-native, which panics on an unknown filter and whose panic would abort the process across the FFI boundary. Calls written as statements are unaffected.

Infrastructure

  • CI lints windows/amd64 and darwin/arm64 in addition to the host, pins every action by SHA, installs the pinned golangci-lint from the ecosystem action, adds a dependabot configuration, and builds windows/386 (P2-INFRA-007, P1-INFRA-008). The ruleset still requires the Test gate; switching it to Required checks is a separate, coordinated step.

Known issues

  • Scissor rects are not validated yet; they go to the HAL unvalidated, as viewports did before this release (P2-CORE-024, next patch).

kolkov added 16 commits November 8, 2026 17:18
… completes

Release is Rust's drop, not WebGPU's destroy (ADR-056). A query set released
after timestamp writes or a resolve were recorded was destroyed at the previous
submission index, so the HAL query pool went away under the submitted work:
VUID-vkDestroyQueryPool-queryPool-00793 on Vulkan and zero timestamps on DX12.

CreateQuerySet gives the set a core.ResourceRef and Release drops it. The
command encoder clones the reference when a render or compute pass records
timestamp writes or ResolveQuerySet records a resolve (Rust wgpu-core's command
buffer trackers hold Arc<QuerySet>), and Submit rejects only a set whose HAL
query set is gone, as query_set.try_raw does (device/queue.rs:2167-2168).

Test first: hal/allbackends/query_set_release_test.go failed on Vulkan (VVL)
and DX12 before this change. P1-CORE-021.
RenderPipeline and ComputePipeline kept no reference to the PipelineLayout
they were created from. WebGPU has no destroy() for a layout, so the only
way an application lets one go is Release, which is Rust's drop: it deferred
vkDestroyPipelineLayout, and every later SetBindGroup of the pipeline passed
the destroyed handle to vkCmdBindDescriptorSets (VUID-vkCmdBindDescriptorSets-
layout-parameter; on the test machine the Vulkan driver crashed with an access
violation). Rust wgpu-core holds an Arc<PipelineLayout> in both pipelines
(pipeline.rs:496, :1155).

PipelineLayout now has a core.ResourceRef whose onZero defers the HAL destroy
through the destroy queue at the last submission index, as a sampler's does,
and both pipeline kinds clone it. The pipeline drops it in the same onZero
that destroys its own HAL pipeline: pipeline first, then layout, so a
released layout stays valid for the pipelines created with it. A pipeline
created from an already released layout is rejected with ErrReleased instead
of naming a dying HAL handle and reviving its reference count.

Bind group layouts keep no reference: VkPipelineLayoutCreateInfo says the
implementation must not access the pSetLayouts objects outside the duration of
vkCreatePipelineLayout, so a VkDescriptorSetLayout may be destroyed after a
dependent pipeline layout exists. Rust and Dawn hold their own bind group
layout objects for validation, not to keep that Vulkan handle alive.

Test-first in hal/allbackends: both pipeline kinds are created from an
explicit layout, the layout is released, WaitIdle runs the deferred
destruction, and only then does a new pass bind and draw or dispatch.
Unfixed the Vulkan driver crashes in vkCmdBindDescriptorSets; fixed, every
backend reads back the expected values with the validation layer quiet.

P2-CORE-022
Four comparisons against TrackerIndex bounds were written as int constants:
int(^TrackerIndex(0)-1) is 4294967294 and does not fit a 32-bit int, so
core/track (buffer, texture, encoder tracker) and internal/raytracing
(math.MaxUint32 in safeIntToUint32) failed to compile for GOARCH=386.

Compare in uint64 instead: the tracker guard is now uint64(i) >=
uint64(InvalidTrackerIndex), which rejects the same values on 64-bit and is
vacuous on 32-bit, where an int cannot reach the invalid index; the raytracing
clamp converts v to uint64 after its negative check. Semantics on amd64,
arm64 and 386 are unchanged; 386 was simply never buildable.

Reported by gogpu (BUG-ELSEWHERE, v0.34.10).
…ion scope

errorFilterToRust mapped any value outside the three WebGPU filters to
Validation, so PushErrorScope(ErrorFilter(42)) silently pushed a scope that
could never capture what the caller asked for. Never swallow an error.

PushErrorScope now returns an error in every build: the native path validates
in core (ValidateErrorFilter, the spec's pushErrorScope TypeError), the rust
path rejects the value before it reaches wgpu-native, which panics on an
unknown filter and the panic aborts the process across the FFI boundary. The
browser stub keeps its panic and only gains the return type.

Callers that call PushErrorScope as a statement are unaffected; none in the
ecosystem used the return value before because there was none. Tests: a table
over every filter constant plus unmapped values in core and under -tags rust.
P3-API-025.
…eline layout

Release is Rust's drop (ADR-056), so a layout the application released may have
its HAL destruction queued. CreateBindGroup passed desc.Layout.hal to
vkAllocateDescriptorSets anyway: on the unfixed code the test crashed with an
access violation inside the Vulkan driver (the layout was already destroyed),
VUID-vkAllocateDescriptorSets-pAllocateInfo's class. CreatePipelineLayout read
the same handles when building the pipeline layout.

Both now refuse a released layout with ErrReleased, as CreateBindGroup already
refuses a released sampler. No reference count is added: the HAL bind group does
not keep the layout — Rust wgpu-hal's Vulkan BindGroup holds only the descriptor
set — and the layout is read at creation only, so a group created while the
layout was alive is unaffected by a later Release. P2-CORE-023.

Test first: hal/allbackends/released_layout_test.go crashed on Vulkan before the
fix and passes on vulkan, dx12 (FXC and DXIL), gles and software after it.
SetViewport recorded whatever it was given: a negative or non-finite size, an
origin outside the spec's viewport range, or a depth range outside [0, 1] went
straight to the HAL (invalid VkViewport values on Vulkan, undefined behaviour on
DX12) and, since the software backend honours MinDepth/MaxDepth, silently
changed the rendered depth.

Validate the viewport as GPURenderPassEncoder.setViewport requires
(spec/index.bs:13695-13708) and as Rust wgpu-core does (command/render.rs:2986):
the first failed rule puts the encoder into its deferred error state, which End
and Finish report, and nothing is recorded into the HAL pass. Rust lets a NaN
origin or size through because its f32 API has no IDL boundary in front of it;
the spec rejects it, so the check is explicit here.

Vertex attribute formats get the same treatment at pipeline creation: an
undefined or unknown gputypes.VertexFormat has size 0, which every existing
vertex check treats as a no-op, and the backends then map it to a wrong format
(GLES and Vulkan Float32x4, Metal MTLVertexFormatInvalid, DX12
DXGI_FORMAT_UNKNOWN, software zeros). Rust's VertexFormat enum cannot express
such a value and Dawn validates it (RenderPipeline.cpp:118); core reports it as
an invalid vertex format instead.

P2-CORE-019.
CI linted one linux target, so code under darwin and windows build tags
(hal/metal, hal/dx12, hal/gles/wgl) was never linted in CI: a PR could carry
darwin or windows findings into main between releases (gogpu#519 hit this).

- lint-targets: golangci-lint for windows/amd64 and darwin/arm64 on a linux
  runner, GOOS/GOARCH in the step env, fail-fast false. The module has no
  arch-specific files and no GOOS+GOARCH build tags, so one GOARCH per GOOS
  covers every file; js/wasm stays a build check in cross-compile.
- golangci-lint 2.15.0 comes from gogpu/.github/actions/setup-golangci-lint
  (sha256-checked, cached, pinned by SHA) and the action runs with
  install-mode none: no more 'latest' drift, no per-run release download.
- every action is pinned by commit SHA with a version comment; dependabot
  tracks github-actions and the /tools module, the root module stays on the
  ecosystem cascade schedule.
- go-mod-outdated is pinned in tools/go.mod, replacing @latest and the
  retry loop.
- test: the plain run is CGO_ENABLED=0 (goffi fakecgo, the path users ship);
  the race run stays CGO_ENABLED=1 with the toolchain detector.
- lint-targets joins the Test gate. Job names stay Lint and Test: the
  ruleset matches that exact name and the switch is a separate decision.
32-bit int is a build class CI did not cover: four constants that fit a 64-bit
int broke the 386 build silently until gogpu reported it (P1-INFRA-008). The
leg builds only — no GPU and no tests there.

windows and not linux: goffi's internal/syscall has no linux/386 files, so the
linux/386 combination cannot build in the ecosystem today; the 32-bit int
checks apply either way.

needs the P1-INFRA-008 fix (1183df5) to pass.
Two test files used values that do not fit 32-bit: a t.Errorf argument of
4294967296 (4 << 30, defaults to int) in memory/types_test.go, and a 64-bit
ANativeWindow handle built with a constant shift in android_surface_policy_test.go.
GOARCH=386 go vet cannot compile either, so the 386 leg added for P1-INFRA-008
would fail on the tests rather than on the code.

The heap size is printed as uint64; the handle's shift count is a variable, so the
constant no longer overflows when the package is type-checked for a 32-bit target,
where the test skips as it already did for the arm64 ABI case.
P1-GLES-025. Three defects found by running the backend on an OpenGL ES 3.2
context (Mesa llvmpipe, the wgpu_gles_force_es seam):

- A multisampled render target was created with glTexImage2DMultisample,
  which OpenGL ES does not have, so every MSAA texture failed with
  GL_INVALID_OPERATION 0x502 and a pass that resolved MSAA could not be
  created at all. It is a renderbuffer now, attached to the framebuffer with
  glFramebufferRenderbuffer, as Rust wgpu-hal creates render targets
  (gles/device.rs create_texture).

- Device.MapBuffer mapped with glMapBuffer, which OpenGL ES does not have
  either (Mesa exposes no glMapBufferOES on ES 3.x): the mapping failed
  silently and the CPU copy stayed zero-filled. Every buffer readback of
  GPU-written data on ES therefore read zeros, which is the whole class of
  "compute and storage readbacks are all zeros" failures. Mapping goes
  through glMapBufferRange, as in Rust wgpu-hal (gles/device.rs map_buffer).

- Textures were allocated with mutable TexImage storage. OpenGL ES rejects
  an image binding (glBindImageTexture) of a texture with mutable storage
  with GL_INVALID_OPERATION, so imageStore and imageLoad wrote nowhere. 2D,
  cube and layered textures use immutable storage (glTexStorage2D/3D) when
  the context has it, which is Rust wgpu-hal's TEXTURE_STORAGE path
  (gles/adapter.rs, device.rs create_texture).

Tests, all forced-ES (integration && linux && wgpu_gles_force_es): an MSAA
clear-and-resolve readback, three compute readbacks over a constant, a
storage buffer and a uniform block, and a storage-texture write. Each fails
on the old code (GL errors and zero readbacks) and passes now; the public API
tests TestMSAAStoreWithResolveIsLoadable, TestSampledRenderTarget,
TestBindGroupRebindCompute, TestWriteBufferThenDispatchReadsIt,
TestStorageTextureWriteThenSample and TestStorageWriteArrayLayer go green on
that context as well.
P2-GLES-024. FeatureTextureCompressionBC was offered only when the EXT
extension names were listed. Intel's Windows GL 4.6 driver (Iris Xe) offers
RGTC as core GL and lists GL_ARB_texture_compression_rgtc, so no BC feature
was advertised there even though every BC format decodes.

RGTC (BC4-5) is core in desktop GL 3.0 and BPTC (BC6-7) in GL 4.2, and the
ARB names count as well; S3TC (BC1-3) stays an extension on both APIs, with
the sRGB S3TC extension required on ES. This is better than Rust wgpu-hal,
which reads the extension strings only and misses the same driver; the
deviation is documented at the detection.

Test: a nine-case table of the detection (desktop 4.6 with ARB/EXT names,
core 4.2, 3.3 and 4.1 without BPTC, a missing S3TC, and ES with and without
the sRGB and BPTC extensions). On Windows the cross-backend
TestCompressedTextureDecode/gles-vs-software now compares 24 of 24 formats
on the Iris Xe GL adapter, BC included.
The stencil tests covered increment-clamp and decrement-wrap, but nothing
pinned the wrap operations' result under a write mask, which gg's EvenOdd
stroke construct builds on: with stencilWriteMask 0x01 an increment of 1
lands in bit 1 and is dropped, and a decrement of 0 wraps to 255, whose
bit 0 survives.

TestStencilWrap draws a front- or back-facing triangle into a
Depth24PlusStencil8 attachment cleared to a known value, with compare
"always", and reads the stencil aspect back through CopyTextureToBuffer.
Each case checks every covered texel against the operation and every
uncovered one against the clear value, so a backend that runs no stencil
operation, or that paints the whole target, fails.

Expectations follow the WebGPU stencil operation definitions, VkStencilOp
(INCREMENT_AND_WRAP, DECREMENT_AND_WRAP) with the write-mask formula
sa = (sa & ~sw) | (sg & sw) of the Vulkan stencil test, and D3D12's
D3D12_STENCIL_OP_INCR and DECR.
naga v0.21.6 fixes the fragment position builtin's w on HLSL/DXIL
(P1-HLSL-037): it passed SV_Position through, whose w is the clip-space w,
while WGSL defines the fragment position's w as 1/w. wgpu excluded both dx12
legs from that check since the readback was written; the exclusion is gone and
the assertion holds on dx12-fxc and dx12-dxil, verified against the pinned
candidate 0b74bc3 and again against the released tag.

Also in v0.21.6: the streaming WGSL lexer (behaviour-neutral, byte-identical
IR over a 308-file differential), DXIL built-in index into a local array,
DXIL refuses workgroup storage above the shader model's limit, HLSL storage
stores nested past 20, and an opt-in SPIR-V demote option (off by default;
wgpu does not set it yet). IR unchanged, one additive public field.

Downstream gate: wgpu GO on the pinned SHA, gg GO on the same candidate.
The case naga added for P2-DXIL-107/092, taken verbatim: a short-circuit ||
chain over runtime values decides an if/else, and the chain's bool local
meets at two if merges. As phi i1 WARP ran neither arm's stores; as alloca i1
D3D12's GPU-based validation dropped them. Four invocations, sentinel fill, so
a wrong arm shows as a value and a missing store shows as the sentinel.

It runs on every backend in the readback corpus. Without the D3D12 debug layer
the case passes on naga v0.21.6 as well (that shape is correct there); the
discriminating run is WARP + GPU-based validation, kept in the gate harness
(P2-INFRA-009) rather than here.
v0.21.7 is v0.21.6 plus the DXIL mem2reg fix (naga P2-DXIL-107/092): bool
locals merge as i32 instead of phi i1 or alloca i1, shapes WARP ran no store
of and D3D12's GPU-based validation dropped. Verified against the tag the same
way as the candidate: the gated SHA 8149de3 and the released module differ only
in CHANGELOG.md and ROADMAP.md.

The flow/short-circuit-or-rt readback joined the corpus in the previous commit;
this one takes the release in. No IR or API change.
ROADMAP: current state and the released v0.34.11 list, with what moves to
v0.34.12 (P2-CORE-026/027, P2-INFRA-009, GLES-023, Metal, ASTC). AGENTS.md
version line: v0.34.11, naga v0.21.7. ARCHITECTURE: the GLES patterns the
release introduced — multisampled renderbuffers, immutable storage, and
glMapBufferRange, BC from core versions and ARB names. CONTRIBUTING: the CI
jobs the lint-targets and 386 legs added.
@kolkov
kolkov merged commit 80d05d3 into main Nov 8, 2026
18 of 19 checks passed
@kolkov
kolkov deleted the release/v0.34.11 branch November 8, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant