Skip to content

Sample magnified nearest planes through cached columns and rows - #35

Merged
timzifer merged 1 commit into
mainfrom
claude/stilus-issue-ozkint
Oct 6, 2026
Merged

timzifer merged 1 commit into
mainfrom
claude/stilus-issue-ozkint

Conversation

@timzifer

@timzifer timzifer commented Oct 6, 2026

Copy link
Copy Markdown
Owner

What and why

Fixes #34.

Nearest, axis-aligned sampling where u does not depend on y (m[1] = m[2] = 0) now:

  • Columns once per image: finds the texel column of every device column once and caches it. The cache and the per-pixel path compute it with the same expressions through shared helpers (origin, nearestCol), so a pixel gets the same texel either way.
  • Rows once per texel row: when rows repeat (|m[3]| < 1), expands each texel row once and copies it for every device row on it. No float work per pixel.
  • Direct compositing: ImageShader.srcRow returns the expanded row, so coverage spans composite straight from it. Opaque runs at alpha 255 copy it into the destination through the existing opaqueSpan path, without spanOver.
  • Unit-scale one-bit planes: bytes that are all one colour (0x00/0xff) are filled 8 pixels at a time.

Cache buffers survive ImageShader.Reset, so there are no allocations after warm-up. Sampler is now documented as not safe for concurrent use.

Not included:

  • No exported OpaqueShader interface, since that would change the API. Once cera gets this version it can drop its own bitShader and use ImageShader again.
  • Stencils: the mask is now read through the same cache, but the 256-entry stencil LUT is still looked up per pixel.

Benchmarks

BenchmarkMagnifiedBits uses the geometries of cera's scan pages; -count 6, compared with benchstat:

case before after
2.1× 2587 µs 926 µs −64 %
8.3× 517 µs 88 µs −83 %
7.9× 420 µs 78 µs −81 %
1× 1803 µs 1043 µs −42 %

Tests

bitplane_test.go:

  • Compares cached sampling with the old per-pixel float mapping for RGBA, index and bit planes. Matrices include mirrored, sheared and rotated ones. Spans come in random order and length.
  • Tests the unit-scale bit path at every offset.
  • Compares direct and opaque compositing against the same shader hidden behind an interface: antialiased edges, clip, alpha 255 and 140.
  • Checks that drawing does not allocate.
  • Two deliberately injected bugs both made the new tests fail.

cera's test suite passes with this stilus through a local replace.

Checklist

  • go test ./... passes
  • gofmt and go vet are clean (also built for js/wasm, arm64, 386)
  • Tests added or updated for behaviour changes
  • Benchmarks attached for performance-relevant changes (benchstat before/after)
  • No new allocations on the drawing path after warm-up
  • No new dependencies in the core module

🤖 Generated with Claude Code

https://claude.ai/code/session_01JLyJY68DWZFiofJb5BDadh


Generated by Claude Code

Nearest, axis-aligned sampling with u independent of y now finds the
texel column of every device column once per image (same float
expressions as the per-pixel path, via the shared origin/nearestCol
helpers) and, when rows repeat (|m[3]| < 1), expands each texel row once
for all device rows on it. ImageShader.srcRow hands out the expanded row,
so coverage spans composite straight from it, and opaque runs at full
alpha copy it into the destination through opaqueSpan without spanOver.
Unit-scale one-bit planes fill whole bytes of one colour at once.

Caches keep their buffers across ImageShader.Reset: no allocations after
warm-up. Sampler is documented as not safe for concurrent use.

BenchmarkMagnifiedBits (cera's scan-page geometries), -count 6:
  2.1x  2587µs -> 926µs  (-64%)
  8.3x   517µs ->  88µs  (-83%)
  7.9x   420µs ->  78µs  (-81%)
  1x    1803µs -> 1043µs (-42%)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLyJY68DWZFiofJb5BDadh
@timzifer
timzifer merged commit f9dc45c into main Oct 6, 2026
13 checks passed
@timzifer
timzifer deleted the claude/stilus-issue-ozkint branch October 6, 2026 16:37
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.

ImageShader: fast paths for magnified one-bit planes, and compositing opaque spans without spanOver

2 participants