Skip to content

Commit caca297

Browse files
committed
Fix corrupted textures when rendering too many WebGL characters
1 parent 55ed78e commit caca297

File tree

5 files changed

+60
-32
lines changed

5 files changed

+60
-32
lines changed

src/type/p5.Font.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ const invalidFontError = 'Sorry, only TTF, OTF and WOFF files are supported.'; /
5151
const fontFaceVariations = ['weight', 'stretch', 'style'];
5252

5353

54-
let nextId = 0;
5554
export class Font {
5655
constructor(p, fontFace, name, path, data) {
5756
if (!(fontFace instanceof FontFace)) {
@@ -62,7 +61,6 @@ export class Font {
6261
this.path = path;
6362
this.data = data;
6463
this.face = fontFace;
65-
this.id = nextId++;
6664
}
6765

6866
/**

src/webgl/text.js

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,9 @@ import { Font, arrayCommandsToObjects } from "../type/p5.Font";
77
function text(p5, fn) {
88
RendererGL.prototype.maxCachedGlyphs = function() {
99
// TODO: use more than vibes to find a good value for this
10-
return 200
10+
return 200;
1111
};
1212

13-
RendererGL.prototype.freeGlyphInfo = function(gi) {
14-
const datas = [
15-
gi.strokeImageInfo.imageData,
16-
gi.rowInfo.cellImageInfo.imageData,
17-
gi.rowInfo.dimImageInfo.imageData,
18-
gi.colInfo.cellImageInfo.imageData,
19-
gi.colInfo.dimImageInfo.imageData,
20-
];
21-
for (const data of datas) {
22-
const tex = this.textures.get(data);
23-
if (tex) {
24-
tex.remove();
25-
this.textures.delete(data);
26-
}
27-
}
28-
}
29-
3013
Font.prototype._getFontInfo = function(axs) {
3114
// For WebGL, a cache of font data to use on the GPU.
3215
this._fontInfos = this._fontInfos || {};
@@ -792,7 +775,7 @@ function text(p5, fn) {
792775
sh.setUniform("uMaterialColor", curFillColor);
793776
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
794777

795-
this.fontCache = this.fontCache || new Map();
778+
this.glyphDataCache = this.glyphDataCache || new Set();
796779

797780
try {
798781
// fetch the glyphs in the line of text
@@ -801,20 +784,35 @@ function text(p5, fn) {
801784
for (const glyph of glyphs) {
802785
const gi = fontInfo.getGlyphInfo(glyph);
803786
if (gi.uGlyphRect) {
787+
const rowInfo = gi.rowInfo;
788+
const colInfo = gi.colInfo;
804789

805-
const cacheKey = JSON.stringify({ font: font.id, axs, glyph: glyph.shape.g });
806-
// Bump this font to the end of the cache list by deleting and re-adding it
807-
this.fontCache.delete(cacheKey);
808-
this.fontCache.set(cacheKey, gi);
809-
if (this.fontCache.size > this.maxCachedGlyphs()) {
810-
const keyToRemove = this.fontCache.keys().next().value;
811-
const val = this.fontCache.get(keyToRemove);
812-
this.fontCache.delete(keyToRemove);
813-
this.freeGlyphInfo(val);
790+
// Bump the resources for this glyph to the end of the cache list by deleting and re-adding
791+
const glyphResources = [
792+
gi.strokeImageInfo.imageData,
793+
rowInfo.cellImageInfo.imageData,
794+
rowInfo.dimImageInfo.imageData,
795+
colInfo.cellImageInfo.imageData,
796+
colInfo.dimImageInfo.imageData
797+
];
798+
for (const resource of glyphResources) {
799+
this.glyphDataCache.delete(resource);
800+
this.glyphDataCache.add(resource);
801+
}
802+
803+
// If we have too many glyph textures, remove the least recently used
804+
// ones from GPU memory. The data still exists on the CPU and will be
805+
// re-uploaded if it gets actively used again.
806+
while (this.glyphDataCache.size > this.maxCachedGlyphs()) {
807+
const data = this.glyphDataCache.values().next().value;
808+
this.glyphDataCache.delete(data);
809+
const tex = this.textures.get(data);
810+
if (tex) {
811+
tex.remove();
812+
this.textures.delete(data);
813+
}
814814
}
815815

816-
const rowInfo = gi.rowInfo;
817-
const colInfo = gi.colInfo;
818816
sh.setUniform("uSamplerStrokes", gi.strokeImageInfo.imageData);
819817
sh.setUniform("uSamplerRowStrokes", rowInfo.cellImageInfo.imageData);
820818
sh.setUniform("uSamplerRows", rowInfo.dimImageInfo.imageData);

test/unit/visual/cases/webgl.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { vi, beforeEach, afterEach } from 'vitest';
12
import { visualSuite, visualTest } from '../visualTest';
3+
import { RendererGL } from '../../../../src/webgl/p5.RendererGL';
24

35
visualSuite('WebGL', function() {
46
visualSuite('Camera', function() {
@@ -631,4 +633,31 @@ visualSuite('WebGL', function() {
631633
screenshot();
632634
});
633635
});
636+
637+
visualSuite('font data', () => {
638+
afterEach(() => {
639+
vi.restoreAllMocks();
640+
});
641+
642+
visualTest('glyph resource allocation does not corrupt textures', async (p5, screenshot) => {
643+
p5.createCanvas(100, 100, p5.WEBGL);
644+
vi.spyOn(p5._renderer, 'maxCachedGlyphs').mockReturnValue(6);
645+
646+
const font = await p5.loadFont(
647+
'/unit/assets/Inconsolata-Bold.ttf'
648+
);
649+
650+
p5.textFont(font);
651+
p5.clear();
652+
p5.textSize(10);
653+
p5.textAlign(p5.LEFT, p5.TOP);
654+
for (let i = 0; i < 100; i++) {
655+
const x = -p5.width/2 + (i % 10) * 10;
656+
const y = -p5.height/2 + p5.floor(i / 10) * 10;
657+
p5.text(String.fromCharCode(33 + i), x, y);
658+
}
659+
660+
screenshot();
661+
});
662+
});
634663
});
1.84 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"numScreenshots": 1
3+
}

0 commit comments

Comments
 (0)