Skip to content

fix: Emit empty fragment targets instead of [null] - #2920

Open
vende11s wants to merge 1 commit into
mainfrom
fix/empty-fragment-targets
Open

fix: Emit empty fragment targets instead of [null]#2920
vende11s wants to merge 1 commit into
mainfrom
fix/empty-fragment-targets

Conversation

@vende11s

Copy link
Copy Markdown
Collaborator
  • Fragment shaders that return only a builtin e.g (out: d.builtin.fragDepth) now create pipelines with fragment.targets: [] instead of [null].
    Both ways are spec-legal, but Firefox rejects [null] with Missing required 'format' member of GPUColorTargetState, so the pipeline never ran.

  • Due to this bug Point Light Shadow example now clears the color attachment to [0, 0, 0, 1].

@vende11s
vende11s requested review from aleksanderkatan, cieplypolar, iwoplaza and reczkok and a lite review from Copilot and removed request for Copilot and iwoplaza August 25, 2026 14:18
@github-actions

Copy link
Copy Markdown

pkg.pr.new

packages
Ready to be installed by your favorite package manager ⬇️

https://pkg.pr.new/software-mansion/TypeGPU/typegpu@9f0882b43acb55affaa848d3705237ff8c8d9882

benchmark
view benchmark

commit
view commit

@github-actions

Copy link
Copy Markdown

Resolution Time Benchmark

---
config:
  themeVariables:
    xyChart:
      plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
  title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
  x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
  y-axis "time (ms)"
  line [0.95, 1.84, 4.37, 5.92, 7.00, 13.03, 24.67, 26.04]
  line [0.98, 1.89, 4.47, 6.26, 6.96, 11.42, 23.35, 26.11]
  line [0.93, 1.87, 3.98, 6.54, 7.00, 10.65, 22.93, 24.72]
Loading
---
config:
  themeVariables:
    xyChart:
      plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
  title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
  x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
  y-axis "time (ms)"
  line [0.26, 0.53, 0.71, 0.86, 1.13, 1.19, 1.47, 1.53]
  line [0.31, 0.52, 0.69, 0.79, 1.06, 1.17, 1.42, 1.55]
  line [0.27, 0.47, 0.67, 0.82, 1.14, 1.17, 1.37, 1.55]
Loading
---
config:
  themeVariables:
    xyChart:
      plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
  title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
  x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
  y-axis "time (ms)"
  line [0.79, 2.10, 3.27, 6.55, 12.18, 25.45, 54.53, 111.06]
  line [0.78, 2.23, 3.26, 6.76, 11.92, 24.85, 55.30, 110.50]
  line [0.87, 1.98, 3.27, 6.55, 12.67, 25.58, 54.27, 110.52]
Loading

@github-actions

Copy link
Copy Markdown

Bundle size comparison (import * as ... in PR vs import * as ... in target):

🟢 Decreased (max -0.00%) ➖ Unchanged 🔴 Increased ❔ Unknown
21 304 0 0

import { ... } in PR vs import * as ... in PR (is the library tree-Shakeable?):

Test tsdown
tgpu_init.ts 263.09 kB ($${\color{green}-3.5\%}$$)
tgpu_initFromDevice.ts 262.55 kB ($${\color{green}-3.7\%}$$)
tgpu_resolve.ts 161.75 kB ($${\color{green}-40.7\%}$$)
tgpu_resolveWithContext.ts 161.68 kB ($${\color{green}-40.7\%}$$)
tgpu_bindGroupLayout.ts 62.32 kB ($${\color{green}-77.1\%}$$)
tgpu_mutableAccessor.ts 57.04 kB ($${\color{green}-79.1\%}$$)
tgpu_accessor.ts 57.04 kB ($${\color{green}-79.1\%}$$)
tgpu_privateVar.ts 55.73 kB ($${\color{green}-79.6\%}$$)
tgpu_workgroupVar.ts 55.73 kB ($${\color{green}-79.6\%}$$)
tgpu_const.ts 55.15 kB ($${\color{green}-79.8\%}$$)
tgpu_lazy.ts 54.95 kB ($${\color{green}-79.8\%}$$)
tgpu_fragmentFn.ts 39.68 kB ($${\color{green}-85.4\%}$$)
tgpu_fn.ts 39.62 kB ($${\color{green}-85.5\%}$$)
tgpu_vertexFn.ts 39.50 kB ($${\color{green}-85.5\%}$$)
tgpu_computeFn.ts 39.20 kB ($${\color{green}-85.6\%}$$)
tgpu_vertexLayout.ts 28.33 kB ($${\color{green}-89.6\%}$$)
tgpu_comptime.ts 15.93 kB ($${\color{green}-94.2\%}$$)
tgpu_unroll.ts 1.75 kB ($${\color{green}-99.4\%}$$)
tgpu_slot.ts 1.70 kB ($${\color{green}-99.4\%}$$)

If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ The fix is correct and spec-legal — one suggestion inline.

Reviewed changes — Single commit (9f0882b) turning [null] fragment target emission into [] for shaders that write only a builtin or nothing, which Firefox rejects with Missing required 'format' member of GPUColorTargetState.

  • connectTargetsToShader.ts — void/builtin fragment outputs now produce targets: [] instead of [null].
  • renderPipeline.tsconnectedTargets falls back to [] (was [null]) when the fragment output isn't resolvable.
  • point-light-shadow example — clear-color used to make the example work correctly.

The change is narrowly scoped and matches the underlying model: a void or builtin-only fragment (e.g. @builtin(frag_depth)) declares no color outputs, so [] is not merely spec-legal but the semantically accurate descriptor. The struct branch that mixes builtins with color outputs is untouched, so pipelines writing both depth and color keep their real targets.

Technical details
# Suggestion: add a regression test

The two changed pipelines reach the `typeof navigator === 'undefined'`-safe
branches before any `navigator.gpu` access, so `connectTargetsToShader(void|builtin)`
can be unit-tested without a device — it returns before the `getPreferredCanvasFormat`
call. Consider pinning the actual regression: a builtin-only fragment (or void) should
yield `fragment.targets: []`, and a struct mixing color + `@builtin(frag_depth)` should
still yield the color targets. Without a test, this subtle spec-compat behavior can
silently regress back to `[null]`.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏


if (isVoid(fragmentOut) || isBuiltin(fragmentOut)) {
return [null];
return [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: worth a regression test pinning targets: [] here? This branch returns before any navigator.gpu access, so connectTargetsToShader(voidOrBuiltin, ...) is unit-testable without a device. The exactly regression — a builtin-only fragment (e.g. @builtin(frag_depth)) yielding targets: [] instead of [null] — would otherwise be easy to silently revert, since the unmanifested difference only shows up on Firefox (null rejects format).

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