Skip to content

wgpu/gogpu: a depth-only render pass is never begun and faults on End #115

Description

@dvoyni

Parent

Implement the scene plugin

What happens

A render pass with a depth attachment and no colour attachment kills the
process. Not a validation error, a fault: an access violation inside the driver,
several frames of stack away from anything that names a pass.

github.com/gogpu/wgpu/hal/vulkan/vk.(*Commands).CmdEndRenderPass
github.com/gogpu/wgpu/hal/vulkan.(*RenderPassEncoder).End
github.com/dvoyni/cog/wgpu.(*gfxBackend).EndPass          wgpu/gfxpass.go
github.com/dvoyni/cog/gfx.(*GpuQueue).ReplayPasses        gfx/gpuqueue.go

Why

hal/vulkan.(*CommandEncoder).BeginRenderPass returns a render-pass encoder
without beginning a render pass when the descriptor names no colour
attachments:

if e.active == 0 || len(desc.ColorAttachments) == 0 {
    return rpe
}

RenderPassEncoder.End then calls vkCmdEndRenderPass unconditionally, on a
pass that was never begun. Verified present in the pinned
v0.31.6-0.20260827093430 and still present in v0.34.4, so it is a live
upstream gap rather than a stale pin. A browser's own WebGPU encodes the same
descriptor correctly, so the wasm build of the same demo runs the pass.

cog/wgpu.BeginPass already emits the descriptor correctly — passColour
returns nil and the pass is encoded with an empty ColorAttachments and a depth
attachment only. Everything above the HAL is right.

Two shapes reach it, and only one of them is deliberate

The second is the more serious, because nothing in the tree asked for it:

GpuPassDesc could not tell NoTarget() from a texture target whose view
does not exist yet
— both resolve to a zero TextureViewID. And every
TemporaryTarget is unresolved on its first frame, because its allocation is a
bake the backend replays after the pass descriptors were built. So the first
frame of any app that renders into a temporary target with depth already emitted
a depth-only descriptor, and already faulted. It has not been seen before only
because nothing in the tree used a temporary target as a camera's colour target
until now.

What was done

Containment, in cog, because the fix is upstream:

  • GpuPassDesc.NoColor says a pass declares no colour attachment at all, set
    from head.Target.kind == targetNone, so a zero Target is no longer
    readable as "depth-only".
  • wgpu.BeginPass returns nil when the colour attachment does not resolve, for
    either reason. gfx already skips a pass whose BeginPass returned nil.
  • A pass that asked for it deliberately (NoColor) reports
    ErrDepthOnlyPassUnsupported once per run, through the plugin, which has a
    kernel handle where the backend does not. The unresolved-view case is silent:
    it is a frame-one artefact of allocation order and there is nothing for a
    caller to do about it.
  • The refusal is gated on depthOnlyPassSupported, a per-platform constant:
    false on !js, true on js. The browser runs the pass.

What is still not fixed

A depth-only pass does not execute on the desktop. Its depth texture is left
untouched, so a later pass that loads that depth with LoadPreserve renders
against undefined depth — the whole pass, not one draw. Until the HAL gap
closes, a depth-only pass may be written but its output may not be depended on
in the same frame. cog/scene/spec.md § Passes carries the rule.

This is the constraint shadow maps will have to negotiate, so this ticket stays
the place that records it.

Acceptance criteria

  • GpuPassDesc distinguishes "no colour attachment" from "colour attachment
    not resolved yet"
  • Neither shape reaches BeginRenderPass as a depth-only descriptor on the
    native path
  • The deliberate case is reported once per run and names the pass
  • The browser path still encodes the pass
  • No-GPU tests over the pass-shape predicate and the refusal's text
  • Upstream: hal/vulkan begins a render pass with no colour attachments

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

ready-for-agentSelf-contained ticket an agent can pick up

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions