Add CF_DRAW_FILTER_PIXELART: seam-free pixel art filter ported from SDL - #574
Add CF_DRAW_FILTER_PIXELART: seam-free pixel art filter ported from SDL#574pusewicz wants to merge 4 commits into
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
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_PIXELARTin 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
tilefilterssample 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.5threshold can fail when commands are replayed from retained draw lists (the replay rescalesshape.z). Use> 0.0to 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.
| 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); | ||
| } |
There was a problem hiding this comment.
Addressed in cfd243c — cf_string_append reassigns the string in place so nothing actually leaked, but the single-variable form makes the ownership obvious.
| // 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) { |
…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
cfd243c to
db1c605
Compare
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_PIXELARTportsSDL_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-unusedaacommand lane).Verified with the new
tilefilterssample (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
NEAREST
PIXELART (NEW)
https://claude.ai/code/session_019FfKRRtZpt4vzQohJn257A