Skip to content

PoC pixel-art mode - #2

Draft
skhoroshavin wants to merge 4 commits into
Antriel:mainfrom
skhoroshavin:pixel-art
Draft

PoC pixel-art mode#2
skhoroshavin wants to merge 4 commits into
Antriel:mainfrom
skhoroshavin:pixel-art

Conversation

@skhoroshavin

Copy link
Copy Markdown

Proof of concept: when the Phaser game's pixelArt config flag is set, MSDF text switches to plain bitmap rendering - atlases upload with NEAREST filtering and the shader reads coverage straight from the texel alpha instead of the distance field. Everything in one draw call, as before.

What should work:

  • Rich text end to end: per-run colors/alpha, per-run fonts (incl. merged atlases), fontScale, kerning, wrap
  • Underline / strikethrough / highlight pills / dashed rules unchanged (they are shader-drawn rects, not texture data)
  • Hard drop shadows (offset silhouette); glyph substitution, transforms, deform, all callbacks

What is disabled (one-time console warning if someone still tries to enable them):

  • Weight, outline in all forms, soft/spread/rounded shadows, two-tone ramps - all distance-field based

What is left:

  • Update to the actual demo
  • Actual testing - there might still be edge cases, especially related to rounding or absence of it

@Antriel

Antriel commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Talked with my robot about this a bit, I might end up trying something for pixelart when I will need it, meanwhile here are robot's notes:

Why not that PR

  • NEAREST filtering is wrong for MSDF — linear interpolation of the field is the mechanism.
  • texel.a is not a mask on any atlas msdf-atlas-gen emits: msdf → 255 everywhere, mtsdf → the true SDF, hardmask/softmask → greyscale PNG, so the mask is in RGB.
  • Disables weight, outline, spread, rounded and two-tone — the features a pixel-art game most wants, and the ones BitmapText can't do.
  • What remains is BitmapText: a baked mask, point-sampled, integer scales only. The MSDF does no work.
  • Global game.config.pixelArt makes hasPixelArtChanged dead code — every binding gets the same value, the flush gate can never fire.
  • Same flag forbids a pixel-art game from owning one smooth font.
  • No demo, so it's unclear what atlas it was ever tested against.

Better direction

  • Keep the field, harden the edge: one uSharpness uniform scaling the 1.0 AA floor in px, clamped to 4096 for mediump. That's the whole shader change.
  • Per font, not per game or per object — rides the existing configureFont flush gate, and a smooth logo is just a different font.
  • Declared at load (pixelGrid: 8 = design pixels per em), since no atlas JSON says "my outline is on an 8px grid".
  • Weight, outline, spread, rounded, two-tone, hard shadow and animated scale all survive a threshold. Outline and animated scale are the two things BitmapText flatly cannot do.
  • Exactness comes from three conditions: grid-aligned outline (pixel-font TTF), fontSize a multiple of the grid unit, integer pen. Meet them and edges land halfway between fragment centres — the most robust configuration, not a knife edge.
  • The real work is layout, not shader: size quantization (incl. fitInside and fontScale), pen snapping in rebuildText only (after measurement, so the three-pass advance invariant holds), rounded alignment insets, snapped decoration rects.
  • Generate atlases at or above the largest render size — minification is what produces the misplaced pixels you were worried about.
  • PR PoC pixel-art mode #2's other feature — plain bitmap/hardmask atlas support — is worth having separately, and needs no shader branch at all: with zeroed params the existing lane already outputs a binary mask exactly.

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.

2 participants