Repository navigation
Thanks to @lkmavi for the Windows CI work on this release: the coverage tests for the validation and error paths, the MSL clamp test, the DXIL shader model 6.5 cap after the validator rejected naga's 6.6+ output, and the CI runs on WARP that surfaced three DX12 defects fixed here. Thanks also to @ahmedtd for the pass usage-scope design this release builds on (#369), and to @darkliquid (#356) and @TimLai666 (#370) for their reports.
Added
- Timestamp queries on Vulkan and DX12. Both backends implemented timestamp queries but never offered
FeatureTimestampQuery, so every timestamp query set was refused. Vulkan now offers the feature when the queue family reports at least 36timestampValidBits, and DX12 always offers it, as Rust wgpu-hal does. ComputePassDescriptor.TimestampWrites(ComputePassTimestampWrites, WebGPUGPUComputePassTimestampWrites) andQueue.GetTimestampPeriod(nanoseconds per tick, RustQueue::get_timestamp_period). Both are in the default build.- Vulkan:
FeatureShaderF16. Offered when the device supportsshaderFloat16,storageBuffer16BitAccessanduniformAndStorageBuffer16BitAccess, as in Rust wgpu-hal, and enabled only when requested. WGSLf16shaders now run on Vulkan. - Shader capability features, offered where a readback test proves them (gputypes v0.8.1):
-
Vulkan, from the device feature each one comes from, as in Rust wgpu-hal:
PrimitiveIndex(geometryShader);ClipDistances(shaderClipDistance);ShaderInt64,ShaderInt64AtomicMinMaxandShaderInt64AtomicAllOps(64-bit buffer and workgroup atomics);ShaderFloat32Atomic(VK_EXT_shader_atomic_float);ShaderBarycentricsandShaderPerVertex(VK_KHR_fragment_shader_barycentric);ShaderDrawIndex(shaderDrawParameters);ShaderEarlyDepthTest;SubgroupOperations,SubgroupVertexandSubgroupBarrier(VkPhysicalDeviceSubgroupProperties with Rust's stage and operation conditions;VK_EXT_subgroup_size_controlis enabled and stages may vary their subgroup size when subgroups are requested). Proven by Rust's ownsubgroup_operationsGPU test, ported verbatim, plus value, fragment and vertex readbacks. Requested subgroup features used to be accepted on every backend and then ignored.
Tested on Intel and NVIDIA under the validation layer.
-
GLES:
PrimitiveIndexandShaderEarlyDepthTestwith Rust wgpu-hal's conditions, andShaderDrawIndex(desktop GL 4.6, orGL_ARB_shader_draw_parameters; shaders are then compiled with naga'sDrawParameters).MultiDrawIndirecton GL 4.3 or the multi-draw-indirect extensions goes through realglMultiDraw*Indirectcalls, sodraw_indexcounts the records. Rust offers neitherShaderDrawIndexnor multi-draw indirect on GLES. -
DX12 on the HLSL/FXC path:
PrimitiveIndexandClipDistances.ClipDistancesis a deviation from Rust wgpu-hal, which does not offer it on DX12; Dawn enables it on every D3D12 device (SV_ClipDistance is shader model 4+).
-
Fixed
-
Software backend:
discardwrote zeros, and fragment shaders without@locationinputs ignored the fragment position and the bound resources — the SPIR-V interpreter treatedOpKillas a normal return of the current function. The fragment's unwritten color output (zero) was then written, and adiscardinside a helper function did not even end the invocation. A fragment shader without@locationinputs ran once per draw, with@builtin(position)left at zero and no bind groups, and that one color was used for every pixel. A shader that discards by position therefore cleared the whole covered area to transparent black. NowOpKillandOpTerminateInvocationend the invocation, and a discarded fragment writes no color, depth or stencil; a shader that may discard runs before the depth and stencil tests. A fragment shader that reads any input, a@locationor a builtin, runs per pixel.@builtin(position)is the pixel center in framebuffer coordinates, the depth, and 1/w. The one-invocation path, for shaders with no input, reads the bound resources. Covered by a new cross-backend readback test ofdiscard(constant and uniform conditions on@builtin(position)). The test passes on Vulkan, DX12 FXC, GLES and Software; DX12 DXIL needs naga'sdx.op.discard(naga v0.21.0). -
hal/vulkan/vk:FlagBitsparameters were passed as 64-bit handles — vk-gen classified single-bit enum types such asVkExternalMemoryHandleTypeFlagBitsas handles, soGetMemoryHostPointerPropertiesEXT,GetMemoryMetalHandlePropertiesEXT,GetMemoryFdPropertiesKHRandSubmitDebugUtilsMessageEXTread 8 bytes from a 4-byte argument. They are 32-bit now, andGetSwapchainCounterEXT, which the generator skipped for the same reason, is generated. -
Vulkan: descriptor writes could point into a replaced array (#356, reported by @darkliquid).
updateDescriptorSetgrew the buffer and image info slices withappend, so with several entries an earlierVkWriteDescriptorSetpointed into an older backing array. The GC kept that array alive and the values were the same, so no wrong binding was observed. The slices are now allocated for every entry up front, so every pointer refers to the final array, and they stay reachable untilvkUpdateDescriptorSetsreturns. A unit test checks each write against the final arrays. -
Vulkan render passes ignored
TimestampWrites. Begin and end timestamps are now written around the pass, as in Rust wgpu-hal. -
Vulkan query sets broke two validation rules.
CreateQuerySetreset the pool from the CPU without thehostQueryResetfeature; queries are now reset in the command buffer. AQueryResolvebuffer lackedTRANSFER_DSTusage forvkCmdCopyQueryPoolResults. -
ResolveQuerySetwas not ordered with later uses of its destination. The buffer is now tracked as a copy destination, so a following copy or map waits for the resolve. -
Metal wrote shaders for MSL 2.1 and compiled them at Metal's default version. The MSL version now comes from the OS version, as Rust wgpu-hal picks it (macOS 15 → 3.2, macOS 26 → 4.0, ...). It is used both for naga's output and for
MTLCompileOptions.languageVersion, andpreserveInvarianceis set from macOS 11 / iOS 13. Without this, naga v0.21.0 refused every Metal shader that needs a newer MSL, such as ray queries. Not yet run on a Mac. -
Metal offered
FeatureTimestampQuerybut could not create timestamp query sets. It is no longer offered until Metal query sets are implemented. -
GLES (Linux): the EGL context stayed current on the thread that created it. Instance and surface creation made the context current and never released it, so a later
Lockfrom another OS thread could fail withEGL_BAD_ACCESS. Creation now pins the thread, loads GL and releases the context, as Rust wgpu-hal pairs every make-current with an unbind. A test reproduces the cross-thread failure on Mesa. -
GLES (Linux): headless adapters reported no capabilities. The adapter of the X11, headless and surfaceless EGL context skipped the capability probe. It reported no features, no downlevel flags (so no compute, even on GL 4.5 drivers such as Mesa llvmpipe), default limits and an unset GLSL version. It now probes like the surface path and Windows do.
-
Vulkan: storage textures were bound as sampled images. The bind group wrote every texture view as
SAMPLED_IMAGEinSHADER_READ_ONLY_OPTIMAL, while the layout declaredSTORAGE_IMAGE, which the validation layer reports (VUID-VkWriteDescriptorSet-descriptorType-00319). The descriptor type now comes from the layout entry, as Rust wgpu-hal does: a storage texture is aSTORAGE_IMAGEinGENERAL, the layout its barriers use. -
Vertex buffers with
arrayStride: 0(broadcast) (#364): every vertex or instance now reads element 0, as WebGPU specifies, instead of being rejected at pipeline creation.- Vulkan and DX12 take the zero stride natively.
- Metal uses the constant step function.
- GLES uses an instance divisor of
0xFFFFFFFF, as Dawn does. - The software backend reads element 0.
CreateRenderPipelineErrorVertexStrideZerois deprecated and no longer returned. A GPU readback test covers every backend. -
DX12, GLES: vertex buffers bound before the pipeline drew nothing. WebGPU allows
SetVertexBufferbeforeSetPipeline, but DX12 took a zero stride and GLES set up no attributes. Both backends now keep the vertex buffer bindings in the render pass and apply them before each draw with the current pipeline's layout, as Rust wgpu-hal does. A readback test covers both call orders on every backend. -
Out-of-bounds storage buffer access is contained (#370, reported by @TimLai666).
- Vulkan enables
VK_EXT_robustness2(robustBufferAccess2,robustImageAccess2) andVK_EXT_image_robustnesswherever supported, next to the corerobustBufferAccess, as Rust wgpu-hal does. Shaders skip naga's buffer and image-load checks only where the driver performs them: an out-of-bounds write is then discarded and a read returns zero. Elsewhere naga's Restrict policy clamps the access to the last element. Index checks stay on.VK_EXT_robustness2stays off on Intel integrated GPUs with a Windows driver older than 31.0.101.2115, as in Rust. - GLES on EGL now requests a robust-buffer-access context (
EGL_CONTEXT_OPENGL_ROBUST_ACCESSon EGL 1.5+, theEGL_EXT_create_context_robustnessattribute otherwise, with a fallback when the driver refuses), as Rust wgpu-hal does. - GLES on WGL requests it too, through
WGL_ARB_create_context_robustness, with a fallback to a plain context. This is a deliberate deviation from Rust, whose WGL path does not ask: containment must not depend on the driver. - A GPU readback test checks that a read or write past a storage binding never reaches memory outside it, on every backend and adapter, and the exact Vulkan outcome for each mode.
- Vulkan enables
-
DX12: a shader module shared by pipelines with different layouts read and wrote the wrong resources in every pipeline after the first. The module was compiled once with the first pipeline's layout, while DX12 numbers registers across the whole layout. Every stage is now compiled with its own pipeline's layout, as Rust wgpu-hal does, and the DXIL shader cache key holds the binding options.
-
Command buffers of abandoned encoders leaked at device release. An encoder left without
Finish(for example after an error), or a command buffer that was never submitted or released, kept its Vulkan command pool or DX12 command allocator, and the Vulkan validation layer reported VUID-vkDestroyDevice-device-05137.Device.Releasenow frees them. -
Vulkan:
ComputePipelineDescriptor.ZeroInitializeWorkgroupMemoryset to false had no effect. A compute stage that turns zeroing off now leaves workgroup memory as it is, as in Rust. -
Direct
DrawandDrawIndexedrefused a nonzerofirstInstancewithoutFeatureIndirectFirstInstance. That feature governs only thefirstInstancefield of indirect draw arguments (WebGPU spec, Rust wgpu-core), so direct draws now accept any value. A readback test of first instance, base vertex and index buffer offset covers every backend. -
Buffer copies and clears reached the backend unvalidated.
CopyBufferToBuffernow checks, as Rust wgpu-core and the WebGPU spec do: the same buffer on both sides,CopySrcandCopyDstusage, 4-byte size and offsets, and both ranges (overflow-safe). Failures come fromFinishas*core.TransferError.ClearBufferchecksCopyDstusage, 4-byte offset and size and the range, with failures as the new*core.ClearError; a nil buffer is an encoder error and a released oneErrReleased(a nil HAL buffer used to reach the backend), and a zero size is a no-op.ClearBufferwas also untracked: it is now aCopyDstuse, ordered after earlier uses of the buffer and keeping it alive until the submission completes. -
Texture copies were missing WebGPU checks.
CopyTextureToBuffer,CopyBufferToTextureandCopyTextureToTexturenow reject: missingCopySrc/CopyDstusage on the texture or the buffer; a mip level or aspect the texture does not have; copies fromDepth24Plus, or toDepth24PlusorDepth32Float(and to the depth aspect of combined formats); a buffer-texture copy that does not name a single aspect; a texture-to-texture copy that does not cover all aspects; different sample counts; overlapping subresources of one texture. As in Rust wgpu-core, they also check the copy range (inside the physical mip level; depth/stencil and multisampled textures copied whole in x and y; origin and size on the texel block of compressed formats), the buffer layout (bytesPerRowholds a row,rowsPerImagean image, both given when the copy spans several, no overflow, the copy inside the buffer), copy-compatible formats for texture-to-texture copies (equal except for the sRGB suffix:RGBA8UnormtoBGRA8Unormused to reach the backend), and theDEPTH_TEXTURE_AND_BUFFER_COPIESdownlevel flag for depth/stencil buffer copies. Failures are*core.TransferError.CopyTextureToTexturewith a region that has no texture records an error instead of crashing. -
Queue.WriteTexturereached the backend unchecked. It now validates as Rust wgpu-core'swrite_texturedoes:CopyDstusage, the mip level and aspect, the region inside the mip level, a single-sampled texture, a format that may be copied, and a data layout (bytesPerRow,rowsPerImage, offset) that fits the data. Failures are*core.TransferError. -
Texture view formats were not validated.
CreateTexturechecksViewFormatsas Rust wgpu-core does: each must equal the texture format except for the sRGB suffix (InvalidViewFormat), and a list that reinterprets the format needs theVIEW_FORMATSdownlevel flag.CreateTextureViewrefuses a format that is neither the texture's nor in its view formats. Views of a single aspect are not format-checked yet. -
Zero-size copies reached the backend, and Vulkan refuses a zero extent. After validation they are dropped, as in Rust wgpu-core, for the four copy commands and
Queue.WriteTexture. -
CopyBufferToTexturedropped the region's texture aspect, so an upload to the stencil or depth aspect of a depth/stencil texture was recorded for the color aspect. -
Vulkan: buffer-texture copies of compressed formats and stencil used a wrong row length.
bufferRowLengthandbufferImageHeightare now in texels as Rust wgpu-hal computes them, from the block size of the copied aspect: BC, ETC2 and ASTC copies were a block width short per row, and stencil copies assumed 4-byte texels. -
SurfaceTexture.AsTexture()reportedTextureFormatUndefined; textures derived from a surface texture now report the surface format. -
ComputePass.DispatchIndirectfailed on DX12 and miscounted elsewhere. Core now creates everyINDIRECTbuffer with internal storage usage, because the indirect validation shader reads it as a storage buffer, as Rust wgpu-core'screate_bufferdoes (Buffer.Usagestill reports what was asked for). Before, DX12 failed the submit withE_INVALIDARGand ran nothing, and Vulkan reported VUID-VkWriteDescriptorSet-descriptorType-00331. The validation pass also wrote the argument offset into one shared buffer at record time, so everyDispatchIndirectrecorded before a submit used the last offset (two dispatches gave 8 workgroups instead of 5 on DX12 and GLES), and it destroyed a bind group the recorded commands still used. Each validation dispatch now binds groups built ahead of recording: the arguments buffer at the aligned offset, cached per buffer and released with it, plus an entry of a constant index table for the remainder, giving the values of Rust'sindirect_validation/dispatch.rs. The table is created on a device's firstDispatchIndirect. A readback test covers offsets, several dispatches per pass and several encoders per submit on every backend and adapter. -
CreateBufferacceptedINDIRECTusage on adapters that cannot execute indirect commands. It now fails withCreateBufferErrorkindCreateBufferErrorMissingDownlevelFlags, as Rust wgpu-core does. -
Software: indirect draws and dispatches did nothing.
DrawIndirect,DrawIndexedIndirectandDispatchIndirectread their records when they run, in recording order, and the software adapter reportsDownlevelFlagsIndirectExecution. Atomic operations on a storage buffer now write the buffer (they read 0 and changed nothing), and a draw with an instance count of 0 draws nothing (it drew one instance). -
GLES: dynamic offsets were applied in bind group entry order, not binding order as WebGPU specifies, so a layout listing its entries in another order bound buffers at each other's offsets.
-
Bind groups set before
SetPipeline, or kept across a pipeline change, were lost or crashed. Render and compute passes now keep the bind groups and their dynamic offsets and bind them at the next draw or dispatch, from the first group whose layout changed, as Rust wgpu-core's binder does. A group set before the pipeline crashed the Vulkan render pass (nil pipeline), was never bound on Vulkan compute (VUID-vkCmdDispatch-None-08600) and was lost on GLES.SetBindGroupno longer rejects an index the current pipeline does not use (WebGPU allows it for a later pipeline); the index is checked againstmaxBindGroups.ExecuteBundlesnow clears the pass's pipeline, bind groups, vertex and index buffers, as WebGPU requires. -
Resources recorded by an unsubmitted encoder or a queue write could be destroyed too early (use after free). Textures and texture views had no reference count: their destruction depended only on the last submission, so an encoder, command buffer or bind group that recorded a texture did not keep it alive. Queue writes kept only the backend handle of their destination buffer. Releasing a resource after recording or writing it, then submitting something else, destroyed it under an open recording. On DX12 the debug layer reported "An ID3D12Resource ... was deleted prior to closing the command list", and an abandoned encoder crashed with an access violation on NVIDIA. As in Rust wgpu, textures and texture views are now reference counted, and encoders, command buffers, bind groups and queue writes keep what they use alive until the GPU is done. An encoder that fails
Finishor is discarded closes its recording before it drops its references, and DX12 no longer leaks the command list of a discarded encoding. A texture view does not by itself keep its texture alive (a deliberate difference from Rust: Go has no drop, so a forgotten view would pin the texture's memory); everything that uses a view holds the texture too. -
Vulkan: a render target that is also sampled reported layout validation errors. A texture with
RenderAttachment | TextureBindingwas left inGENERALafter a render pass (an old workaround) while the barriers assumedCOLOR_ATTACHMENT_OPTIMAL, so a copy or a sampling draw afterwards reported VUID-VkImageMemoryBarrier-oldLayout-01197 or VUID-vkCmdDraw-None-09600. Every color attachment and resolve target now begins and ends its pass inCOLOR_ATTACHMENT_OPTIMAL, as Rust wgpu-hal does. -
Render bundles worked on no backend. Vulkan crashed in
vkBeginCommandBuffer(a secondary command buffer begun without a render pass), and DX12, GLES, Metal and software returned "render bundles not supported". Core now records a bundle as a command list, validates it inRenderBundleEncoder.Finishand replays it into the render pass inExecuteBundles, as Rust wgpu-core does (command/bundle.rs), so bundles work on every backend. A bundle may set bind groups before its pipeline.Finishchecks each pipeline against the bundle's attachment formats, sample count and read-only depth/stencil.ExecuteBundleschecks the bundle against the pass, keeps its resources alive until the submission completes, and merges its buffer and texture usages into the pass's usage scope, so a texture a bundle samples that is also an attachment of the pass is a usage conflict now. Invalid, released or foreign-device bundles are validation errors instead of being skipped. -
SetBindGroupdynamic offsets were not fully validated. The offset count must match the bind group's dynamic bindings, each offset must be a multiple ofminUniformBufferOffsetAlignmentorminStorageBufferOffsetAlignmentby binding type (it was a fixed 256), and it must keep the binding inside its buffer, as WebGPU and Rust wgpu-core require. -
GLES: samplers did not follow the pipeline. A bind group kept across a pipeline change now reaches the new shader's combined samplers, a sampler shared by two textures reaches both, and compute pipelines pair samplers with their textures too.
-
Device.Releaseleft what unfinished encoders referenced alive. An encoder left in its error state, or a command buffer never submitted, kept its pipelines and buffers past the device (Vulkan VUID-vkDestroyDevice-device-05137). -
Vulkan:
SetBindGroupon a render pass without a pipeline panicked for callers of the HAL directly. -
DX12: bind group dynamic offsets were ignored. Every buffer with
HasDynamicOffsetread its binding at offset 0. Dynamic uniform buffers are now root CBVs, and dynamic storage buffers read their offset (and binding size, soarrayLength()counts the binding) from root constants, as in Rust wgpu-hal, on the FXC and DXIL paths. Bind groups are bound at the next draw or dispatch, so a group set before the pipeline, or kept across a pipeline change, works; setting one before the pipeline used to crash. -
DX12: the debug layer's clear-value warnings (#820, #821) are dropped, as Rust wgpu-hal does. WebGPU has no optimized clear values, so they fired on every clear and buried real messages.
-
GLES: indirect draws recorded nothing.
DrawIndirectandDrawIndexedIndirectnow draw throughglDrawArraysIndirect/glDrawElementsIndirect. Direct draws honour the first instance (base-instance draws on desktop GL 4.2 withGL_ARB_shader_draw_parameters, otherwise naga's first-instance uniform and instance-stepped buffer offsets, as Rust wgpu-hal does),DrawIndexedhonours the base vertex, and theSetIndexBufferoffset is applied.IndirectFirstInstanceis offered only where the first instance of an indirect record reaches GL. -
GLES: the index buffer offset was lost in indexed indirect draws.
glDrawElementsIndirecthas no index offset parameter (Rust wgpu-hal has the same gap). The draw now binds a GPU copy of the index buffer from that offset and restores the binding afterwards. -
GLES: storage textures were bound as sampled textures, never with
glBindImageTexture, sotextureStorewrote nothing. They now bind to their image unit with the view's mip level and layer and the layout's access and format, as Rust wgpu-hal does. Sampled textures bind to their own target instead of alwaysGL_TEXTURE_2D. -
GLES (Linux): depth clear and depth range on desktop GL contexts.
glClearDepthandglDepthRangetake doubles and were called with floats, so the depth buffer was cleared to about 0 and every depth-tested draw failed on desktop GL under EGL (seen on Mesa's d3d12 driver in WSL). The float entry points are used where they exist, as in Rust wgpu-hal, and the double ones are called with doubles otherwise. -
Software: storage texture writes from compute shaders had no effect. Compute dispatches bound no textures, the interpreter skipped
OpImageWrite/OpImageRead, and shuffles of integer vectors such asglobal_invocation_id.xyread zeros. All three are fixed, with format-aware texel encoding. -
Software: depth did not persist across draws and passes (fixes the v0.34.6 known issue). A render pass now loads or clears the depth attachment once, shares it across its draws and stores it at the end.
-
Software: SPIR-V interpreter gaps.
var<private>globals and function variable initializers are set up,matCxR * vecCworks for every size, struct zero values are composites, andOpArrayLength, the image size, level and sample queries, the bit field instructions,OpBitCount,OpBitReverseand the logical and unordered comparisons are executed instead of skipped. -
DX12:
@builtin(vertex_index)and@builtin(instance_index)ignoredfirstVertex,baseVertexandfirstInstance, and@builtin(num_workgroups)was wrong. D3D12'sSV_VertexIDandSV_InstanceIDdo not include the draw's offsets. Every pipeline layout now carries naga's special constants as three root constants, written before each draw and dispatch, as Rust wgpu-hal does. FXC and DXIL paths, tested on Intel and NVIDIA. -
Bind group resources were outside the pass usage scopes (fixes the v0.34.6 known issue that a texture used through a bind group got no barrier from its previous use). Bind group buffers and textures now take part in the usage scope of each render pass and each dispatch, as in Rust wgpu-core. Their barriers go in a command buffer before a render pass, and before each dispatch in a compute pass. A resource used as an attachment and bound in the same render pass is a usage conflict. Indirect draw buffers are tracked as
INDIRECT. Queue writes no longer end with an eagerCOPY_DST→ read barrier: the next submission moves a written resource into the usage its first pass gives it. Based on the design in @ahmedtd's d9e2c1b (#369). -
WGSL was parsed and validated twice. Core now parses and validates a WGSL module once, with the capabilities of the device's features, and hands the IR and module info to the backend (
hal.ShaderModuleDescriptor.IRandInfo). The backends compile that IR, and lower the source themselves only when used without core. Parse and validation errors are typed:core.CreateShaderModuleErrorwith kindsParsingandValidation. -
Late buffer-binding size validation used the shader's sizes from every entry point. The minimum binding sizes are now read per entry point, as Rust wgpu-core's
check_stagedoes. -
RequestDeviceaccepted features the adapter does not expose and limits better than the adapter's. It now fails withcore.RequestDeviceError(UnsupportedFeature,LimitsExceeded), as the WebGPU spec and Rust wgpu-core require. Nil or zero limits still mean the adapter's limits. -
Compute pipelines were not checked against the workgroup limits after pipeline constants.
CreateComputePipelinechecks workgroup storage againstmaxComputeWorkgroupStorageSize, and the workgroup size after the pipeline constants are applied. NewCreateComputePipelineErrorkindsWorkgroupStorageExceededandPipelineConstants. -
Vertex layouts were missing WebGPU checks (#364): the number of vertex buffers, attribute offset alignment, the 2^28 bound, the
shaderLocationrange and duplicate locations are now checked at pipeline creation. -
A depth-only render pipeline with a fragment stage and no color targets was refused. WebGPU requires a color target or a depth-stencil state, not both, so it is now accepted.
-
Buffer-texture copies were not validated against the buffer layout.
CopyTextureToBufferandCopyBufferToTexturenow requirebytesPerRowto be a multiple of 256, an offset aligned to the texel block size and a single-sampled texture. Failures arecore.TransferError. -
Metal: resources were bound at the wrong slots in some layouts. Metal slots came from the bindings a shader declares, and the encoder bound bind group entries, and applied dynamic offsets, in the order the caller listed them. Dynamic offsets were then lost (compute wrote nothing, render drew nothing), and a shader module shared by pipelines with different layouts read the wrong resources. Slots now come from the pipeline layout in binding order, per stage, as in Rust wgpu-hal. Confirmed on the macOS CI runner.
-
DX12: D3D12 structs that hold a C union had the wrong alignment or size. Eleven structs were checked against
d3d12.h, and eight were wrong. Unions modelled as byte arrays left the Go struct 4-byte aligned where C needs 8, so building a resource barrier could cast a misaligned pointer.D3D12_RAYTRACING_GEOMETRY_DESCwas 72 bytes instead of 56, so in a bottom-level acceleration structure with more than one geometry, every geometry after the first was misread. A layout test now pins each struct's size, alignment and union offset. -
DX12, GLES: binding handles carried Go pointers through
uintptr. The DX12Buffer,TextureViewandSamplerand the GLESTextureViewreturned a Go heap pointer as theirNativeHandle, and bind group creation turned it back into a pointer. That is invalid by Go'sunsafe.Pointerrules, and Go's checkptr (part ofgo test -race) rejects it.NativeHandleis now an opaque per-device id resolved through a table. An unknown or destroyed handle is an error instead of a crash. -
DX12: destroying a sampler twice freed its descriptor heap slots twice, so two later samplers could share a slot.
Destroyis now idempotent for every DX12 resource. -
DX12: render targets in 3D textures used an invalid view. The view-time RTV spanned the whole depth, which is invalid at a mip level above 0. As in Rust wgpu-hal, a 3D color attachment now gets a one-slice render target view per render pass, through the new
hal.RenderPassColorAttachment.DepthSlice. The publicRenderPassColorAttachment.DepthSlice(WebGPUdepthSlice) follows in v0.34.8; until then a 3D attachment renders to slice 0. -
Software backend: arrays of
vec3and matrices withvec3columns in buffers were laid out wrong. The SPIR-V interpreter stepped through buffer arrays and matrix columns by the element size (12 bytes for avec3) instead of theArrayStrideandMatrixStridedecorations (16). Element 1 of anarray<vec3<T>>was read and written at byte 12, andarrayLengthof a 28-bytearray<vec3<u32>>binding was 2 instead of WGSL's 1. -
Software backend: integers written to a buffer were stored as float bits, and a
vec4<u32>uniform member read back as the bits of the float (41 became0x42240000). Integer components now stay integer,bitcastreinterprets bits by its operand and result types, and theOpConvert*instructions convert vectors component by component (they returned 0 for a vector). -
Software backend: textured draws without a vertex buffer skipped the pipeline's shaders. Any such draw was turned into a copy of the texture over the whole target, ignoring the vertex and fragment shaders, the quad position, the UV mapping, the alpha and the blend state. These draws now run the pipeline's shaders. They are correct, and much slower than the old shortcut until the software backend runs compiled shaders (P1-SW-023). Normal gg and ui frames are not affected: they present their pixels to the software surface directly.
-
Software backend: texture-to-texture copies ignored origins and layers, and dropped a surface texture. A copy went from byte 0 of the source to byte 0 of the destination, whatever the origins, the layers and the two textures' widths; a copy into the surface texture did nothing. Copies now follow WebGPU, row by row at each texture's own width, and
CopyTextureToBufferandCopyBufferToTexturetoo. The software backend stores only mip level 0, so a transfer to or from a level above 0 now writes nothing (before, it overwrote level 0; see Known issues). -
Software backend: compute shaders that share workgroup memory across a barrier computed wrong results. The interpreter gives each invocation a private copy of workgroup memory and runs the invocations one after another, so
workgroupBarriersynchronises nothing, and reductions, prefix sums and tiled kernels gave wrong values without an error.CreateComputePipelinenow refuses such a shader on the software backend withwgpu.ErrWorkgroupBarrierUnsupported, so the caller can fall back. Workgroups of a single invocation share nothing and still run. The full fix is planned for v0.34.8 (P1-SW-025). -
DX12: storage textures were bound as sampled textures. A storage texture got an SRV descriptor in a UAV slot, so a compute shader's
textureStorereached nothing and reads returned zeros, and a texture withStorageBindingbut noTextureBindingusage failedCreateBindGroupon every adapter. Storage textures now get an unordered-access view of the view's mip and layers, as Rust wgpu-hal creates. -
DX12: buffers were created and tracked in
COPY_DEST. D3D12 creates every buffer in theCOMMONstate whatever initial state is asked for, so the state tracker started from a state the buffer was not in. Buffers are now created and tracked inCOMMON, as in Rust wgpu-hal. -
DX12: mappable buffers always used write-combined pages. The CPU page property of upload and readback heaps now comes from the adapter (
GetCustomHeapProperties), so integrated GPUs and WARP get the write-back pages they need.
These three were found by running the DX12 tests on WARP, the D3D12 software rasterizer and the only DX12 adapter on GitHub's Windows runners, after @lkmavi's work on the Windows CI job; the earlier WARP skip is removed. -
Software backend: pixels on an edge shared by two triangles were drawn twice. The top-left fill rule was applied the wrong way round: a pixel centre exactly on a shared edge was given to both triangles, so a translucent quad drawn as two triangles blended its diagonal twice. Coverage is now computed on vertices snapped to 8 bits of sub-pixel precision, as D3D's rasterization rules specify, and a pixel centre on an edge belongs only to the triangle for which that edge is a top or left edge, for both windings. Reported by the gogpu project.
-
Software backend: float colour writes to unorm and snorm targets truncated instead of rounding. They now follow D3D11 §3.2.3.6 and §3.2.3.4: NaN is 0, the value is clamped, scaled by 2^n−1 (or 2^(n−1)−1 for snorm), and rounded to nearest with halves away from zero. The readback tests now give the same values as the hardware backends.
Changed
- Vulkan: workgroup memory is zeroed by the driver where the device supports
shaderZeroInitializeWorkgroupMemory(VK_KHR_zero_initialize_workgroup_memory), instead of zero stores in every compute shader, as Rust wgpu-hal does. Other devices keep naga's zero stores. - New core validation API for the copy checks above:
core.ValidateBufferToBufferCopy,core.ValidateBufferClear,core.ValidateBufferTextureCopyTexture,core.ValidateTextureToTextureCopy,core.ValidateTextureCopyRange,core.ValidateLinearTextureData, the typescore.BufferCopyEndpoint,core.TextureCopyView,core.TexelCopyBufferLayout,core.TextureErrorDimension,core.CopySideandcore.ClearError, the constantcore.CopyBufferAlignment, and newcore.TransferErrorKindvalues. - gputypes v0.8.2 (from v0.8.1): texture format helpers from Rust wgpu-types (block dimensions, aspect-aware copy size, sRGB suffix), used by the copy validation.
- Render and compute pass
TimestampWritesare validated as in Rust wgpu-core. The feature must be enabled, the query set must be a live timestamp set of the same device, the indices must be in range and different, and at least one must be given. A missing or released query set is now an error instead of being dropped silently. - Vulkan enables the shader capability features only when requested.
geometryShader,shaderClipDistance,shaderInt64andshaderInt16used to be enabled whenever supported. A shader that uses primitive_index, clip distances or 64-bit integers must request the matching feature (core requires that for naga v0.21.0 anyway). Requesting one the adapter does not offer fails device creation. - Metal tests run in CI.
TestMainno longer exits early under CI. Tests that need a device skip one by one, and fail whenWGPU_REQUIRElistsmetal. - DX12: the DXIL path targets shader model 6.5 at most. The DXIL validator on GitHub's Windows runners rejects naga's DXIL for shader model 6.6 and above: naga still writes
CreateHandlethere, where 6.6 needsCreateHandleFromBindingandAnnotateHandle. The cap rises when naga writes the 6.6 handles (naga P1-DXIL-090). The default HLSL path through FXC is not affected. Found by @lkmavi. - CI runs the tests with Go's standard race detector (
go test -race, cgo at test time only; the library stays cgo-free). It also turns on checkptr, which found the DX12 and GLES pointer defects above. A failure on one OS no longer cancels the other OSes' test jobs. Thehal/allbackendsreadback tests were made smaller so each OS finishes in minutes. - Software backend: shader resources are set up once per draw, not once per vertex or fragment invocation, and type lookups in the interpreter no longer scan the whole module. This roughly halves the cost of a shaded draw.
- New tests: out-of-bounds containment on Metal checks the clamp exactly, a
vec3case checksarrayLengthagainst the WGSL definition and containment on every backend, andvec3andmat3x3buffer layout is read back on every backend. Three software "known defect" skips are removed, with their defects fixed. - Shader capability features (gputypes v0.8.1).
CreateShaderModulegrants the naga capabilities of 12 more device features, by Rust'sfeatures_to_naga_capabilities:FeaturePrimitiveIndex,FeatureClipDistances,FeatureShaderEarlyDepthTest,FeatureShaderInt64,FeatureShaderInt64AtomicMinMax,FeatureShaderInt64AtomicAllOps,FeatureShaderFloat32Atomic,FeatureShaderI16,FeatureShaderBarycentrics,FeatureShaderPerVertex,FeatureSubgroupVertex,FeatureShaderDrawIndex.FeatureShaderInt64AtomicAllOpsalso grants the min/max capability, andFeatureSubgroupVertexalso grants subgroups, as in Rust.- Shaders that use these capabilities compiled unchecked with naga v0.20, and naga v0.21.0 refuses them without the feature. Request the feature where the adapter offers it.
- naga v0.21.0 has no WGSL
i16/u16type names yet, soFeatureShaderI16reaches only SPIR-V input.
hal/software/raster:FragmentShaderFuncandMRTFragmentShaderFuncnow receive the whole*Fragmentand return a discard flag.DrawTrianglesWithFragmentShaderandDrawTrianglesWithMRTShadertake amayDiscardargument, andFragmenthas a newInvWfield.hal/software/shaderhas a newErrDiscardedsentinel andModule.MayDiscard.- naga v0.21.2 (from v0.20.5). v0.21.2 clamps every index into a runtime-sized array on Metal, bounded by the stride as the WGSL spec defines the array length, and makes MSL
arrayLengthfollow the spec forvec3arrays. It also fixes two silent DXIL miscompiles (a constant-initialized local array read by index lost its initializer; a loop-carried local was folded to its pre-loop value) and emits asubgroupBarrierthat Vulkan validation accepts. WGSL is now checked by the WGSL spec and Rust naga, so shaders that compiled before may be refused:- An optional feature needs its
enabledirective:enable f16;,enable subgroups;(subgroup and quad built-ins),enable clip_distances;,enable primitive_index;,enable dual_source_blending;,enable wgpu_ray_query;and the other Rust extensions. An unknown directive is an error.subgroupBarrierneeds no directive. - Operations a stage cannot run are refused, also through called functions:
discardoutside fragment shaders, barriers andworkgroupUniformLoadoutside compute shaders, implicit-LOD sampling and derivatives outside fragment shaders. - Operand types are checked: negating a matrix or a
u32,~on floats, binary operators on mismatched types (f32 & f32,u32 + i32, ...), aselect()whose condition is notbool.var<immediate>follows Rust's rules. - DX12 FXC: a
returninside aswitchinside a loop of a helper function compiled as the case'sbreakand gave wrong results without an error. Fixed. - A backend refuses a shader its target version cannot express, with naga's
backend.VersionError(wrappingbackend.ErrUnsupported) naming the version needed (naga's MSL output used to raise the version silently). DX12 DXIL compiles for the device's shader model, and Metal for the MSL version of the OS (see Fixed).
- An optional feature needs its
- naga v0.21.0 options: every backend sets them explicitly, as each Rust wgpu-hal backend does, since naga's zero values now mean Restrict on every bounds check and SPIR-V 1.0. Vulkan compiles for the highest SPIR-V version the device accepts at the instance's Vulkan 1.2 (1.0, 1.3 or 1.5), writes
PointSize, and checks indices (Restrict; binding arrays unchecked), and buffers and image loads where the driver's robust access does not (see Fixed). GLES checks image loads only on desktop GL 4.3+ (GLSL ES has no level and sample queries). DX12 DXIL compiles for the adapter's highest shader model, and HLSL for FXC's 5.1. Metal checks indices, buffers and image loads. Software keeps SPIR-V 1.3 and leaves image loads unchecked until the interpreter has the image query instructions. - Shader capabilities come from the device features, as in Rust (naga v0.21.0 gates every optional shader feature).
CreateShaderModulegrants naga capabilities by Rust'sfeatures_to_naga_capabilities:ShaderF16→ f16,ShaderFloat64→ f64,SubgroupOperations/SubgroupBarrier→ subgroups,RayQuery→ ray queries,ASBindingArray→ acceleration structure binding arrays. A shader that needs one of them without the feature fails with aFeatureErrornaming it.ShaderFloat64no longer allows 64-bit integers: as in Rust, they needFeatureShaderInt64(above). Shader features that Rust gates on a feature wgpu does not have yet (dual-source blending, binding arrays, texture atomics, ...) are refused, where naga v0.20 compiled them unchecked. The backends no longer gate capabilities themselves, so Vulkan and Software stop rejecting f16 and f64 shaders on devices that have the features. The subgroup features are checked by naga too, replacing a scan of the source text that refused any shader with "subgroup" in a comment or an identifier.
Upgrade notes
This release fixes validation that was missing, so code that ran before may now get an error. Each case is a bug in the calling code by the WebGPU spec and Rust wgpu:
- Shader validation is stricter (naga v0.21.0). Missing
enabledirectives, stage-only operations used in the wrong stage, and mismatched operand types are refused (see Changed). Fix the shader; the error names the problem. - Optional shader features must be requested. A shader that uses f16, f64, 64-bit integers, subgroups, primitive index, clip distances, ray queries and the other capabilities above needs the matching feature in
DeviceDescriptor.RequiredFeatures. Vulkan no longer enablesgeometryShader,shaderClipDistance,shaderInt64andshaderInt16unless they are requested. RequestDevicerefuses what the adapter cannot do. Request only features the adapter lists, and limits no better than the adapter's. Nil or zero limits keep working and mean the adapter's limits.- New usage conflicts inside a pass. A texture or buffer used as a render attachment and bound through a bind group in the same pass is now a usage conflict, as in WebGPU.
TimestampWritesare validated. The timestamp feature must be enabled, and a missing or released query set is an error instead of being dropped.- Copies and clears are validated. Buffer-texture copies need a
bytesPerRowthat is a multiple of 256 and a buffer offset aligned to the texel block size. A buffer-texture copy of a combined depth/stencil format (Depth24PlusStencil8,Depth32FloatStencil8) must nameDepthOnlyorStencilOnly, andDepth24Pluscannot be copied at all, as WebGPU specifies.CopyBufferToBufferandClearBufferneed 4-byte aligned offsets and sizes and the matchingCopySrc/CopyDstusage. Copies that run past the end of the buffer or the texture, abytesPerRowshorter than a row, a missingbytesPerRow/rowsPerImagefor a multi-row copy, partial copies of depth/stencil or multisampled textures, and texture-to-texture copies between formats that differ by more than the sRGB suffix are errors now. Check copy code paths at run time: the errors surface atFinish.Queue.WriteTextureapplies the same layout rules, andTextureDescriptor.ViewFormatsmust list only the texture format's sRGB or non-sRGB pair. - Software backend: compute pipelines that share workgroup memory across a barrier now fail with
wgpu.ErrWorkgroupBarrierUnsupportedinstead of computing wrong values. Check for it witherrors.Isand fall back where your code can run on the software adapter. NativeHandleof DX12 buffers, texture views and samplers and of GLES texture views is now an opaque id, not a pointer. It was never a D3D12 or GL object, so no code outside wgpu could use it. DX12 textures and the other backends' handles are unchanged.hal/vulkan/vk: about 50 generated constants had wrong values and are corrected, and the external-memoryFdfields areint32(the Cint).hal/software/raster: the fragment shader callback signatures changed (see Changed), and the edge functions are now fixed point:IncrementalEdge.A,.B,Value()andIncrementalTriangle.EdgeValues()areint64,TileTriangleTesttakes theTriangle, andNewIncrementalEdge,EdgeFunction.IsTopLeft,IncrementalEdge.CandIncrementalTriangle.InvAreaare gone (useNewIncrementalEdgeFromPointsandIncrementalEdge.IsTopLeft).core.IndirectValidation:CreateSrcBindGroup,DstBindGroupandParamsBufferare gone with the new per-dispatch binding scheme.hal/dx12:ShaderModule.IsDeferredis gone (every stage now compiles per pipeline layout). These are low-level packages that no known consumer imports; the publicwgpuAPI has compatible additions only (checked with apidiff against v0.34.6).
Known issues
- Not offered yet:
FeatureShaderF16on Metal (not yet run on a Mac),FeatureShaderI16on every backend (naga's WGSL has noi16type yet), the subgroup features on DX12 (naga's DXIL wave operations fail the DXIL validator in three cases) and Metal,FeatureClipDistanceson GLES (needs a naga GLSL change), the new shader capability features on the DX12 DXIL path and on Metal, and on the software backend. A feature that is not offered is refused byRequestDevice; nothing runs unchecked. -tags rust(wgpu-native through go-webgpu/webgpu v0.5.5) still has the ABI mismatches listed for v0.34.6. The default Pure Go build is not affected.- Metal: the changes in this release run in CI on GitHub's macOS runner (the binding fixes and the readback tests pass there), but not on Apple GPU hardware beyond it.
- No
RenderPassColorAttachment.DepthSliceyet (WebGPUdepthSlice): rendering into a 3D texture reaches slice 0 only. Planned for v0.34.8 (P1-API-019) with core validation and every backend. - DX12:
@builtin(vertex_index),@builtin(instance_index)and@builtin(num_workgroups)miss the first vertex, first instance and workgroup count in indirect draws and dispatches. Direct calls are correct. The fix needs wgpu-core's rewriting of indirect arguments (P1-CORE-014), planned for v0.35.0. - Software backend: no sRGB conversion. Sampling an
*Srgbtexture does not decode it, and rendering to one does not encode, and texture views do not carry their own format. The software adapter therefore does not reportDownlevelFlagsViewFormats, so a texture whoseViewFormatsreinterpret the sRGB pair is refused there. Planned with the software fixes of v0.35.0 (P1-SW-021). - Software backend: the SPIR-V interpreter holds integer values as floats in places, so integer values above 2^24 can lose exactness, and
all()/any()are skipped. Unknown opcodes are skipped rather than reported. Derivatives return zero (no 2x2 quad execution). The proper fix is a typed value model for the interpreter, planned for v0.35.0 (P1-SW-019). The hardware backends are not affected. - Software backend: compute shaders that share workgroup memory across a barrier are refused (
wgpu.ErrWorkgroupBarrierUnsupported, see Fixed) until the interpreter runs a workgroup's invocations together. Planned for v0.34.8 (P1-SW-025). - Software backend: textures have only mip level 0, and texture views select no subresource. A view of an array layer or a mip level renders to and samples layer 0 and level 0, array, cube and 3D views sample as a 2D texture, and sampling ignores the level of detail. Transfers to or from a level above 0 write nothing. Planned for a later patch (P1-SW-026).
- Software backend: no clipping in clip space. Vertices are divided by w without clipping first, so a triangle crossing the w = 0 plane (part of it behind the camera) is drawn wrong, and one with a vertex beyond about ±2^21 pixels is not drawn. Planned for a later patch (P1-SW-027).
- Software backend: shaded draws are slow. The interpreter runs every vertex and fragment invocation, about 1.8 s for a full-screen 1280x720 textured draw. The fix is compiled shaders (P1-SW-023).