Experiment: use colordx for pixel renderer hot path#211
Experiment: use colordx for pixel renderer hot path#211ai merged 3 commits intoevilmartians:mainfrom
Conversation
|
Very interesting. I will look tomorrow and will give feedback about the API. I think we can migrate if we will be able to remove culori. |
| let color = getColor(x, y) | ||
| let proxyColor = getProxyColor(color) | ||
| let colorP3 = p3(proxyColor) | ||
| let [lr, lg, lb] = oklchToLinear(color.l, color.c, color.h ?? 0) |
There was a problem hiding this comment.
There is very crazy optimization trick of re-using the same array:
const RETURN_ARRAY = [0, 0, 0]
export const oklabToLinear = (l: number, a: number, b: number): [number, number, number] => {
…
RETURN_ARRAY[0] = 4.0767416613 * lv - 3.3077115904 * mv + 0.2309699287 * sv
RETURN_ARRAY[1] = 4.0767416613 * lv - 3.3077115904 * mv + 0.2309699287 * sv
RETURN_ARRAY[2] = 4.0767416613 * lv - 3.3077115904 * mv + 0.2309699287 * sv
return RETURN_ARRAY;
}It will be interesting to check how this trick could affect performance
There was a problem hiding this comment.
I've update colordx lib to support this. The performance gain is huge in memory. Time is also improved
There was a problem hiding this comment.
I will need numbers from b integration benchmark (custom one is very different)
| let colorSRGB = rgb(proxyColor) | ||
| let [lr, lg, lb] = oklchToLinear(color.l, color.c, color.h ?? 0) | ||
| let [sr, sg, sb] = oklchToRgbChannels(color.l, color.c, color.h ?? 0) | ||
| let [pr, pg, pb] = linearToP3Channels(lr, lg, lb) |
There was a problem hiding this comment.
We can speed up the code by calling linearToP3Channels only before if (inGamutEps(pr, pg, pb)) { check
| let proxyColor = getProxyColor(color) | ||
| let colorP3 = p3(proxyColor) | ||
| let [lr, lg, lb] = oklchToLinear(color.l, color.c, color.h ?? 0) | ||
| let [pr, pg, pb] = linearToP3Channels(lr, lg, lb) |
There was a problem hiding this comment.
The same here, we need [pr, pg, pb] only on false inGamutEps(lr, lg, lb), let’s calc it there
There was a problem hiding this comment.
Not sure here. It is using next line
| @@ -0,0 +1,102 @@ | |||
| import './set-globals.ts' | |||
There was a problem hiding this comment.
Removed. Just wanted to have some numbers for debug/validation
There was a problem hiding this comment.
What are numbers in our built-in benchmark? The best way to test is to open some specific colors (remember it).
Then open old version and press b.
Then open new version with the same color and press b again.
There was a problem hiding this comment.
The built-in overlay isn't great for this comparison:
- Timings use
Date.now()(1 ms resolution) — too coarse for sub-ms work Worker sumis CPU time across parallel cores, not wall-clockPaintis bounded by worker messaging + main-thread drawing (Freeze), so once color math drops below those,Paintstops reflecting it- Each press of
bis a single-frame sample — no median, no p95
Workers parallelize across cores, so user-visible repaint ≈ max(L, C, H) instead of sum. That hides most of the speedup at typical canvas sizes.
Instead I added a batch bench: 100 sequential color changes on main thread, each rendering all 3 charts synchronously. This isolates the color-math path and gives stable median / p95:
There was a problem hiding this comment.
Each press of b is a single-frame sample — no median, no p95
BTW, if you want you can change it in separated PR, I will accept it.
But without synchronous run. The whole idea of real benchmark is to see the difference for real end-user.
Paint is bounded by worker messaging + main-thread drawing (Freeze), so once color math drops below those, Paint stops reflecting it
But we can use worker * metrics, right? worker max should be equal to Paint without messaging and freeze.
before: / after:
Why Freeze is twice bigger? Is it just one-run issue or the result is repeating?
There was a problem hiding this comment.
Nice! Give me a few days to review benchmark PR.
75a108f to
a78ff5c
Compare
|
Can we replace all |
I would prefer to split work in few PR's if possible |
|
Something like this:
|
|
Nice plan |







An experimental PR to explore replacing culori with @colordx/core in the
generateGetPixelhot path.What changed
generateGetPixelnow uses colordx's low-level channel functions (oklchToLinear,oklchToRgbChannels,linearToP3Channels,linearToRec2020Channels) instead of culori's higher-level color objects avoiding object allocation on every pixel.Performance
Benchmarked on a 500×500 grid (250k pixels):
The biggest win is when the display supports P3 the most common modern case