From 88e1550a2d4525a0736bc9f0973fb0a7473a00e6 Mon Sep 17 00:00:00 2001 From: Nalin Dalal Date: Fri, 24 Jul 2026 18:22:38 +0530 Subject: [PATCH 1/4] perf: cache _getRGBA and fast-path RGB mode in p5.Color --- src/color/p5.Color.js | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index 29b5996fb3..99c6123165 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -65,6 +65,11 @@ class Color { // where if we `import { Color }` directly, it will be a separate copy of the // Color class from the one imported in the main p5.js bundle. isColor = true; + #rgbaCache = new Map(); + + #invalidateRGBACache() { + this.#rgbaCache.clear(); + } // Used to add additional color modes to p5.js // Uses underlying library's definition @@ -241,6 +246,7 @@ class Color { this._initialize(); this._initialize = undefined; } + this.#invalidateRGBACache(); this._cachedColor = newColor; } @@ -559,6 +565,7 @@ class Color { if(this.mode === RGB || this.mode === RGBHDR){ this._color.coords[0] = newval; + this.#invalidateRGBACache(); }else{ // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; @@ -611,6 +618,7 @@ class Color { if(this.mode === RGB || this.mode === RGBHDR){ this._color.coords[1] = newval; + this.#invalidateRGBACache(); }else{ // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; @@ -663,6 +671,7 @@ class Color { if(this.mode === RGB || this.mode === RGBHDR){ this._color.coords[2] = newval; + this.#invalidateRGBACache(); }else{ // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; @@ -715,17 +724,19 @@ class Color { const newval = map(new_alpha, max[0], max[1], colorjsMax[0], colorjsMax[1]); this._color.alpha = newval; + this.#invalidateRGBACache(); } _getRGBA(maxes=[1, 1, 1, 1]) { + const cacheKey = maxes.join(','); + if (this.#rgbaCache.has(cacheKey)) { + return [...this.#rgbaCache.get(cacheKey)]; + } + // Get colorjs maxes const colorjsMaxes = Color.#colorjsMaxes[RGB]; - // Normalize everything to 0,1 or the provided range (map) - let coords = structuredClone(to(this._color, 'srgb').coords); - coords.push(this._color.alpha); - - const rangeMaxes = maxes.map((v) => { + const rangeMaxes = maxes.map(v => { if(!Array.isArray(v)){ return [0, v]; }else{ @@ -733,7 +744,16 @@ class Color { } }); - coords = coords.map((coord, i) => { + let coords; + if (this.mode === RGB) { + coords = [...this._color.coords, this._color.alpha]; + } else { + // Normalize everything to 0,1 or the provided range (map) + coords = structuredClone(to(this._color, 'srgb').coords); + coords.push(this._color.alpha); + } + + const result = coords.map((coord, i) => { return map( coord, colorjsMaxes[i][0], colorjsMaxes[i][1], @@ -741,7 +761,8 @@ class Color { ); }); - return coords; + this.#rgbaCache.set(cacheKey, result); + return result; } _getMode() { From 7f4b60af6af826c16f652c103c09761f2be10369 Mon Sep 17 00:00:00 2001 From: Nalin Dalal Date: Mon, 27 Jul 2026 11:51:48 +0530 Subject: [PATCH 2/4] fix: cache only srgb-converted coords --- src/color/p5.Color.js | 42 ++++++++------------- test/bench/set_benchmark.html | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 test/bench/set_benchmark.html diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index ae8f8572cc..73eaef16ef 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -65,10 +65,10 @@ class Color { // where if we `import { Color }` directly, it will be a separate copy of the // Color class from the one imported in the main p5.js bundle. isColor = true; - #rgbaCache = new Map(); + #srgbCoords = null; - #invalidateRGBACache() { - this.#rgbaCache.clear(); + #invalidateSrgbCache() { + this.#srgbCoords = null; } // Used to add additional color modes to p5.js @@ -246,7 +246,7 @@ class Color { this._initialize(); this._initialize = undefined; } - this.#invalidateRGBACache(); + this.#invalidateSrgbCache(); this._cachedColor = newColor; } @@ -565,7 +565,7 @@ class Color { if(this.mode === RGB || this.mode === RGBP3){ this._color.coords[0] = newval; - this.#invalidateRGBACache(); + this.#invalidateSrgbCache(); }else{ // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; @@ -618,7 +618,7 @@ class Color { if(this.mode === RGB || this.mode === RGBP3){ this._color.coords[1] = newval; - this.#invalidateRGBACache(); + this.#invalidateSrgbCache(); }else{ // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; @@ -671,7 +671,7 @@ class Color { if(this.mode === RGB || this.mode === RGBP3){ this._color.coords[2] = newval; - this.#invalidateRGBACache(); + this.#invalidateSrgbCache(); }else{ // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; @@ -724,18 +724,20 @@ class Color { const newval = map(new_alpha, max[0], max[1], colorjsMax[0], colorjsMax[1]); this._color.alpha = newval; - this.#invalidateRGBACache(); + this.#invalidateSrgbCache(); } _getRGBA(maxes=[1, 1, 1, 1]) { - const cacheKey = maxes.join(','); - if (this.#rgbaCache.has(cacheKey)) { - return [...this.#rgbaCache.get(cacheKey)]; + // Cache the srgb conversion (the expensive step) + if (!this.#srgbCoords) { + if (this.mode === RGB || this._color.space.id === 'srgb') { + this.#srgbCoords = [...this._color.coords, this._color.alpha]; + } else { + this.#srgbCoords = [...to(this._color, 'srgb').coords, this._color.alpha]; + } } - // Get colorjs maxes const colorjsMaxes = Color.#colorjsMaxes[RGB]; - const rangeMaxes = maxes.map(v => { if(!Array.isArray(v)){ return [0, v]; @@ -744,25 +746,13 @@ class Color { } }); - let coords; - if (this.mode === RGB) { - coords = [...this._color.coords, this._color.alpha]; - } else { - // Normalize everything to 0,1 or the provided range (map) - coords = structuredClone(to(this._color, 'srgb').coords); - coords.push(this._color.alpha); - } - - const result = coords.map((coord, i) => { + return this.#srgbCoords.map((coord, i) => { return map( coord, colorjsMaxes[i][0], colorjsMaxes[i][1], rangeMaxes[i][0], rangeMaxes[i][1] ); }); - - this.#rgbaCache.set(cacheKey, result); - return result; } _getMode() { diff --git a/test/bench/set_benchmark.html b/test/bench/set_benchmark.html new file mode 100644 index 0000000000..4e0a40cb9c --- /dev/null +++ b/test/bench/set_benchmark.html @@ -0,0 +1,71 @@ + + + + + p5.Graphics.set() benchmark + + + + +

p5.Graphics.set() benchmark

+
Starting benchmark...
+ + + + From 2a15b2acd7106ab5ecb17a919d16ae3198cbf2d6 Mon Sep 17 00:00:00 2001 From: Nalin Dalal Date: Thu, 30 Jul 2026 11:12:06 +0530 Subject: [PATCH 3/4] rm test --- test/bench/set_benchmark.html | 71 ----------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 test/bench/set_benchmark.html diff --git a/test/bench/set_benchmark.html b/test/bench/set_benchmark.html deleted file mode 100644 index 4e0a40cb9c..0000000000 --- a/test/bench/set_benchmark.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - p5.Graphics.set() benchmark - - - - -

p5.Graphics.set() benchmark

-
Starting benchmark...
- - - - From c15b88a1d858ff00e172c889dfebf233303e24d4 Mon Sep 17 00:00:00 2001 From: Nalin Dalal Date: Thu, 30 Jul 2026 11:14:23 +0530 Subject: [PATCH 4/4] Implementing the key-based memoization approach using color coordinates; remove all calls to #invalidateSrgbCache --- src/color/p5.Color.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index 73eaef16ef..49dc5c71fe 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -50,6 +50,7 @@ const toHexComponent = (v) => { } const serializationMap = new Map(); +const srgbCoordsCache = new Map(); @@ -65,11 +66,6 @@ class Color { // where if we `import { Color }` directly, it will be a separate copy of the // Color class from the one imported in the main p5.js bundle. isColor = true; - #srgbCoords = null; - - #invalidateSrgbCache() { - this.#srgbCoords = null; - } // Used to add additional color modes to p5.js // Uses underlying library's definition @@ -246,7 +242,6 @@ class Color { this._initialize(); this._initialize = undefined; } - this.#invalidateSrgbCache(); this._cachedColor = newColor; } @@ -565,7 +560,6 @@ class Color { if(this.mode === RGB || this.mode === RGBP3){ this._color.coords[0] = newval; - this.#invalidateSrgbCache(); }else{ // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; @@ -618,7 +612,6 @@ class Color { if(this.mode === RGB || this.mode === RGBP3){ this._color.coords[1] = newval; - this.#invalidateSrgbCache(); }else{ // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; @@ -671,7 +664,6 @@ class Color { if(this.mode === RGB || this.mode === RGBP3){ this._color.coords[2] = newval; - this.#invalidateSrgbCache(); }else{ // Will do an imprecise conversion to 'srgb', not recommended const space = this._color.space.id; @@ -724,17 +716,23 @@ class Color { const newval = map(new_alpha, max[0], max[1], colorjsMax[0], colorjsMax[1]); this._color.alpha = newval; - this.#invalidateSrgbCache(); } _getRGBA(maxes=[1, 1, 1, 1]) { - // Cache the srgb conversion (the expensive step) - if (!this.#srgbCoords) { + const key = `${this._color.space.id}-${this._color.coords.join(',')}-${this._color.alpha}`; + let srgbCoords = srgbCoordsCache.get(key); + + if (srgbCoords === undefined) { if (this.mode === RGB || this._color.space.id === 'srgb') { - this.#srgbCoords = [...this._color.coords, this._color.alpha]; + srgbCoords = [...this._color.coords, this._color.alpha]; } else { - this.#srgbCoords = [...to(this._color, 'srgb').coords, this._color.alpha]; + srgbCoords = [...to(this._color, 'srgb').coords, this._color.alpha]; + } + + if (srgbCoordsCache.size > 1000) { + srgbCoordsCache.delete(srgbCoordsCache.keys().next().value); } + srgbCoordsCache.set(key, srgbCoords); } const colorjsMaxes = Color.#colorjsMaxes[RGB]; @@ -746,7 +744,7 @@ class Color { } }); - return this.#srgbCoords.map((coord, i) => { + return srgbCoords.map((coord, i) => { return map( coord, colorjsMaxes[i][0], colorjsMaxes[i][1],