Skip to content

Add CF_DRAW_FILTER_PIXELART: seam-free pixel art filter ported from SDL - #574

Open
pusewicz wants to merge 4 commits into
RandyGaul:masterfrom
pusewicz:pixelart-filter
Open

Add CF_DRAW_FILTER_PIXELART: seam-free pixel art filter ported from SDL#574
pusewicz wants to merge 4 commits into
RandyGaul:masterfrom
pusewicz:pixelart-filter

Conversation

@pusewicz

@pusewicz pusewicz commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Tilemaps drawn as individual sprites show background bleeding through tile seams whenever the camera sits at a subpixel offset. Root cause: the atlas rings every sprite with transparent border pixels and the quad is expanded to cover them, so tile-edge pixels blend toward transparency — and two abutting antialiased edges only ever composite to ~75% coverage. That makes the seams inherent to SMOOTH's soft edges; no filter tweak can fix them.

CF_DRAW_FILTER_PIXELART ports SDL_SCALEMODE_PIXELART's box filter together with SDL's edge semantics: sampling clamps half a texel inside the sprite's content rect (clamp-to-edge), and edge coverage is binary — a pixel belongs to whichever sprite's content rect contains its center. Texel transitions stay smooth like SMOOTH, edges are hard like NEAREST, and tilemaps stay gap-free at any zoom or subpixel camera position. SMOOTH/LINEAR/NEAREST behavior is unchanged (the bordered-sprite flag rides the sprite-unused aa command lane).

Verified with the new tilefilters sample (SPACE cycles filter modes under a slow subpixel camera drift; it also captures deterministic screenshots): at a half-pixel offset SMOOTH bleeds on every seam line, PIXELART shows zero bleed at every offset and zoom tested. 333/333 tests pass. The FXC (CF_NO_IMPLICIT_GRADIENTS) tile-walk variant is patched identically but I could only test Metal locally.

🤖 Generated with Claude Code

Smooth

tile_filters_SMOOTH

NEAREST

tile_filters_NEAREST

PIXELART (NEW)

tile_filters_PIXELART--SDL-port-

https://claude.ai/code/session_019FfKRRtZpt4vzQohJn257A

Copilot AI lite review requested due to automatic review settings August 8, 2026 08:38
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copilot AI 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.

Pull request overview

Adds a new draw filter mode (CF_DRAW_FILTER_PIXELART) to address tile seam artifacts under subpixel camera motion by porting SDL’s PIXELART-style sampling semantics into Cute Framework’s draw shaders and plumbing it through the draw system. This fits into the existing draw filter stack (NEAREST/LINEAR/SMOOTH) and provides a seam-free option intended for atlas-backed tilemaps.

Changes:

  • Introduces CF_DRAW_FILTER_PIXELART in the public API and documents its intended behavior vs existing filter modes.
  • Extends the built-in sprite/tile shaders to support PIXELART UV adjustment plus seam-safe sampling for bordered atlas sprites.
  • Adds a new tilefilters sample to demonstrate/verify seam behavior and deterministic screenshot capture.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tools/builtin_shaders.h Adds PIXELART UV functions and integrates PIXELART + seam-safe sprite sampling in both mesh and compute/tile-walk paths.
src/cute_draw.cpp Plumbs the new filter mode into shader uniform selection (u_use_smooth_uv = 2) and flags bordered atlas sprites via the aa lane.
include/cute_draw.h Extends CF_DrawFilterMode with CF_DRAW_FILTER_PIXELART and documents usage/semantics.
samples/tile_filters.c New sample demonstrating filter modes on a tilemap under subpixel camera drift, with screenshot automation.
samples/CMakeLists.txt Registers the new tilefilters sample and copies required sample data at build time.
Suppressed comments (1)

tools/builtin_shaders.h:1082

  • Compute/tile-walk path uses the same bordered-sprite flag (cmd.shape.z) for PIXELART seam-safe sampling, but the > 0.5 threshold can fail when commands are replayed from retained draw lists (the replay rescales shape.z). Use > 0.0 to treat any positive value as the flag, matching the mesh path.
			bool seam_safe = type == CMD_TYPE_SPRITE && cmd.shape.z > 0.5 && u_use_smooth_uv == 2;

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread samples/tile_filters.c
Comment on lines +43 to +47
char* path = cf_path_normalize(cf_fs_get_base_directory());
char* mounted = cf_string_append(path, "/import_spritesheet_data");
cf_fs_mount(mounted, dir, false);
cf_string_free(mounted);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in cfd243c — cf_string_append reassigns the string in place so nothing actually leaked, but the single-variable form makes the ownership obvious.

Comment thread tools/builtin_shaders.h Outdated
Comment on lines +792 to +800
// Seam-safe sprite sampling for SMOOTH/PIXELART on bordered atlas sprites (flagged by
// the CPU through the otherwise-unused aa lane): never blend into the transparent 1px
// border ring -- clamp the sample point half a texel inside the content rect (clamp-to-
// edge semantics, like SDL), and rebuild the edge fade geometrically as a one-pixel
// coverage falloff at the content edge. Adjacent tiles stay seamless under subpixel
// motion (a neighbor's opaque interior covers the fade) while silhouettes still fade.
// The branch is quad-uniform (per-command data), so fwidth inside is well-defined.
float sprite_cov = 1.0;
if (is_sprite && v_aa > 0.5 && u_use_smooth_uv == 2) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 6cb43ab.

pusewicz and others added 4 commits August 12, 2026 16:55
…PIXELART

Adjacent tiles drawn as individual sprites bleed background at their
seams whenever the camera sits at a subpixel offset: the atlas rings
every sprite with transparent border pixels and the quad expands to
cover them, so tile-edge pixels blend toward transparency. Two abutting
antialiased edges only ever composite to ~75% coverage, so SMOOTH cannot
be made seam-free without giving up its soft edges.

PIXELART ports SDL's box filter along with its edge semantics: sampling
clamps half a texel inside the sprite's content rect (clamp-to-edge),
and edge coverage is binary -- a pixel belongs to whichever sprite's
content rect contains its center. Texel transitions stay smooth inside,
edges are hard outside, and tilemaps stay gap-free at any zoom or
subpixel camera position. SMOOTH/LINEAR/NEAREST are unchanged.

The new tilefilters sample reproduces the artifact (SPACE cycles filter
modes while the camera drifts by subpixel amounts) and captures
deterministic screenshots for comparison.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FfKRRtZpt4vzQohJn257A
A 5x3 block rotating about its center shows PIXELART keeps interior
pixels stable under subpixel rotation steps (zero interior pops vs
NEAREST's texel crawl) and keeps seams closed even when the shared
tile edges are not axis-aligned -- SMOOTH leaks background through
rotated seams.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FfKRRtZpt4vzQohJn257A
Draw list replays rescale the aa lane (replay_aa_scale), which could
push a sprite's bordered-atlas flag below the previous > 0.5 test and
silently disable PIXELART's seam-safe sampling. Compare against zero
instead, and rewrite the stale comments that still described the
earlier SMOOTH/geometric-fade design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FfKRRtZpt4vzQohJn257A
cf_string_append reassigns its dynamic string argument in place, so the
two-variable form read as if the normalized path leaked. Append into one
variable and free it once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FfKRRtZpt4vzQohJn257A
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