diff options
| author | user <user@clank> | 2026-08-14 22:05:59 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-14 22:05:59 +0200 |
| commit | f655e7882ef60c5865fae84bd35d4d33ca52d5c2 (patch) | |
| tree | ac8a449d814e269fe919ab44ef82892512e5e7f1 /src | |
| parent | sl0ppty: a README for people, not for the person who wrote it (diff) | |
| download | slosh-f655e7882ef60c5865fae84bd35d4d33ca52d5c2.tar.gz slosh-f655e7882ef60c5865fae84bd35d4d33ca52d5c2.tar.xz slosh-f655e7882ef60c5865fae84bd35d4d33ca52d5c2.zip | |
sl0ppty: walk the row, not screen_at()
shade_apply() reached every cell through screen_at(x, y), which re-derives
the row base and bounds-checks a coordinate the loop already guarantees --
once per cell, per shader, per frame.
Clipping the rect to the screen once, up front, makes the pointer arithmetic
provably in range, so the inner loop can walk a row. Same output, and the
clip is not a behaviour change: the layout never asks for a rect that needs
it, and a caller that did was getting NULL-skipped cells before.
Measured with .scratch/shprof.c on a 200x50 pane and a three-pass chain:
8.0 -> 6.1 ns per cell-pass, 0.24 -> 0.18 ms per frame.
That profile also says where the time is *not*: `vignette`, which computes
squared distances and a divide, is cheaper than `dim`, which computes
nothing. The arithmetic in these shaders is free; what costs is per-cell
overhead and the colour mixing. Anyone arriving here to precompute a
per-cell amount table should read the numbers first -- it buys nothing.
Diffstat (limited to 'src')
| -rw-r--r-- | src/shader.c | 18 |
1 files changed, 15 insertions, 3 deletions
diff --git a/src/shader.c b/src/shader.c index 6085eb4..6a02dbc 100644 --- a/src/shader.c +++ b/src/shader.c @@ -166,6 +166,17 @@ void shade_apply(screen_t *s, const shader_t *shaders, size_t n, uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, const shade_ctx_t *base) { if (!s || !shaders || !n || !w || !h) return; + /* Clip once, here, so the inner loop can walk a row pointer instead of + * bounds-checking every cell through screen_at(). That call was a fifth of + * the pass: it re-derived the row base and re-checked a coordinate the loop + * already guarantees, once per cell per shader. The layout never asks for a + * rect that needs clipping; this is so that a caller that does gets a + * smaller rect rather than an out-of-bounds write. */ + if (x0 >= s->cols || y0 >= s->rows) return; + if ((size_t)x0 + w > s->cols) w = (uint16_t)(s->cols - x0); + if ((size_t)y0 + h > s->rows) h = (uint16_t)(s->rows - y0); + if (!w || !h) return; + shade_ctx_t ctx = base ? *base : (shade_ctx_t){0}; ctx.cols = w; ctx.rows = h; @@ -178,11 +189,13 @@ void shade_apply(screen_t *s, const shader_t *shaders, size_t n, uint16_t x0, if (!sh->kind || !sh->fn) continue; for (uint16_t y = 0; y < h; y++) { + cell_t *row = &s->cur[(size_t)(y0 + y) * s->cols + x0]; + ctx.y = y; for (uint16_t x = 0; x < w; x++) { - cell_t *c = screen_at(s, (uint16_t)(x0 + x), (uint16_t)(y0 + y)); + cell_t *c = &row[x]; /* width 0 is the tail half of a wide cell: never painted, so shading * it would be computing a colour nothing can display. */ - if (!c || !c->width) continue; + if (!c->width) continue; /* Materialised per shader rather than once for the chain, so a shader * added later cannot observe a half-resolved cell. Idempotent: once @@ -191,7 +204,6 @@ void shade_apply(screen_t *s, const shader_t *shaders, size_t n, uint16_t x0, if (!c->bg.set) c->bg = ctx.default_bg; ctx.x = x; - ctx.y = y; sh->fn(sh, &ctx, c); } } |
