Skip to content

shader dsl: add discard() for cut-out fragments - #5

Open
shadowcodex wants to merge 1 commit into
ericdrowell:mainfrom
shadowcodex-forks:shader-discard
Open

shader dsl: add discard() for cut-out fragments#5
shadowcodex wants to merge 1 commit into
ericdrowell:mainfrom
shadowcodex-forks:shader-discard

Conversation

@shadowcodex

@shadowcodex shadowcodex commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Adds discard() to the shader DSL. A fragment stage can throw the current fragment away, and the GPU writes no colour and no depth for it.

fragment({ uAtlas, uCutoff }, { vUv }) {
  const texel = texture(uAtlas, vUv);
  if (texel.w < uCutoff) { discard(); }
  return vec4(texel.xyz, 1);
}

Why

A sprite with a transparent edge has one option today: blend it. A blended program cannot write depth, because a part-transparent fragment has no single depth to record. So the application has to sort sprites back-to-front on the CPU every frame and re-send the instance data.

discard() removes the fragment instead. Every fragment that survives is opaque, so the program writes depth and the GPU orders the sprites per pixel. Nothing sorts, nothing re-uploads.

What changed

Small: one new IR statement, one keyword in the WGSL emitter, one case in the constant folder, and the builtin plus its export.

The compiler permits it in the fragment stage only, and as a statement only. It gives a named error for each way of getting it wrong — in vertex(), inside a helper (which vertex() may call, where discarding is meaningless), used as a value, and called with arguments.

Tests

9 new tests in packages/brometal/tests/discard.test.ts: WGSL output inside a branch and from an else, survival through the optimizer, top-level use before the return, all four rejections, and a varying kept alive when only the discard condition reads it — that last one is the case a dead-varying pass would prune and quietly break.

327 tests pass, npm run typecheck clean.

Provenance, and a note on scope

This is extracted from #2, which bundles discard() with seven other library changes and four demos. That PR is being closed in favour of focused ones, of which this is the first — it carries nothing but discard().

It is ported, not cherry-picked: #2 predates the WebGL2 removal, so its emit-glsl half no longer applies and its GLSL assertion is dropped. The result-shape rename from fragmentSrc/vertexSrc to wgslSrc is reflected in the tests. The implementation and the error messages are otherwise the ones from that branch.


Over to you

Genuine questions, not politeness:

  • Is this applicable at all? If it does not fit where BroMetal is going, say so and close it — no offence taken. We carry it as a local patch over the published package today, so nothing of ours is blocked on it landing.
  • Is there a better approach? We arrived at this from the outside, without the context you have on the design. If you would solve it differently — a different API shape, a different layer, or a reason the current behaviour is deliberate — we would rather write your version than have you merge ours.
  • Anything you want changed — naming, comment style, test placement, scope — tell us and we will do it.

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@shadowcodex is attempting to deploy a commit to the Eric Rowell's projects Team on Vercel.

A member of the Team first needs to authorize it.

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