From a523d71e3c8aeaef5715b026b53602163d350376 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 18 Oct 2017 19:47:44 -0700 Subject: [PATCH] Stop leaking buffer textures --- demo/client/src/buffer-texture.ts | 82 ++++++++++++++++++++----------- demo/client/src/renderer.ts | 32 +++++++----- 2 files changed, 74 insertions(+), 40 deletions(-) diff --git a/demo/client/src/buffer-texture.ts b/demo/client/src/buffer-texture.ts index 28e3cd30..e93ddab8 100644 --- a/demo/client/src/buffer-texture.ts +++ b/demo/client/src/buffer-texture.ts @@ -11,49 +11,67 @@ import * as glmatrix from 'gl-matrix'; import {setTextureParameters, UniformMap} from './gl-utils'; -import {expectNotNull} from './utils'; +import {assert, expectNotNull} from './utils'; export default class PathfinderBufferTexture { readonly texture: WebGLTexture; readonly uniformName: string; private size: glmatrix.vec2; - private capacity: glmatrix.vec2; private glType: number; + private destroyed: boolean; constructor(gl: WebGLRenderingContext, uniformName: string) { this.texture = expectNotNull(gl.createTexture(), "Failed to create buffer texture!"); this.size = glmatrix.vec2.create(); - this.capacity = glmatrix.vec2.create(); this.uniformName = uniformName; this.glType = 0; + this.destroyed = false; } - upload(gl: WebGLRenderingContext, data: Float32Array | Uint8Array) { + destroy(gl: WebGLRenderingContext): void { + assert(!this.destroyed, "Buffer texture destroyed!"); + gl.deleteTexture(this.texture); + this.destroyed = true; + } + + upload(gl: WebGLRenderingContext, data: Float32Array | Uint8Array): void { + assert(!this.destroyed, "Buffer texture destroyed!"); + gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.texture); const glType = data instanceof Float32Array ? gl.FLOAT : gl.UNSIGNED_BYTE; - const area = Math.ceil(data.length / 4); - if (glType !== this.glType || area > this.capacityArea) { - const width = Math.ceil(Math.sqrt(area)); - const height = Math.ceil(area / width); - this.size = glmatrix.vec2.fromValues(width, height); - this.capacity = this.size; + const areaNeeded = Math.ceil(data.length / 4); + if (glType !== this.glType || areaNeeded > this.area) { + const sideLength = nextPowerOfTwo(Math.ceil(Math.sqrt(areaNeeded))); + this.size = glmatrix.vec2.clone([sideLength, sideLength]); this.glType = glType; - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, glType, null); + gl.texImage2D(gl.TEXTURE_2D, + 0, + gl.RGBA, + sideLength, + sideLength, + 0, + gl.RGBA, + glType, + null); setTextureParameters(gl, gl.NEAREST); } - const mainDimensions = glmatrix.vec4.fromValues(0, - 0, - this.capacity[0], - Math.floor(area / this.capacity[0])); - const remainderDimensions = glmatrix.vec4.fromValues(0, - mainDimensions[3], - area % this.capacity[0], - 1); + const mainDimensions = glmatrix.vec4.clone([ + 0, + 0, + this.size[0], + (areaNeeded / this.size[0]) | 0, + ]); + const remainderDimensions = glmatrix.vec4.clone([ + 0, + mainDimensions[3], + areaNeeded % this.size[0], + 1, + ]); const splitIndex = mainDimensions[2] * mainDimensions[3] * 4; if (mainDimensions[2] > 0 && mainDimensions[3] > 0) { @@ -92,20 +110,28 @@ export default class PathfinderBufferTexture { } } - bind(gl: WebGLRenderingContext, uniforms: UniformMap, textureUnit: number) { + bind(gl: WebGLRenderingContext, uniforms: UniformMap, textureUnit: number): void { + assert(!this.destroyed, "Buffer texture destroyed!"); + gl.activeTexture(gl.TEXTURE0 + textureUnit); gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.uniform2i(uniforms[`${this.uniformName}Dimensions`], - this.capacity[0], - this.capacity[1]); + gl.uniform2i(uniforms[`${this.uniformName}Dimensions`], this.size[0], this.size[1]); gl.uniform1i(uniforms[this.uniformName], textureUnit); } - private get area() { + private get area(): number { + assert(!this.destroyed, "Buffer texture destroyed!"); return this.size[0] * this.size[1]; } - - private get capacityArea() { - return this.capacity[0] * this.capacity[1]; - } +} + +/// https://stackoverflow.com/a/466242 +function nextPowerOfTwo(n: number): number { + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + return n + 1; } diff --git a/demo/client/src/renderer.ts b/demo/client/src/renderer.ts index d114db06..f7f11b22 100644 --- a/demo/client/src/renderer.ts +++ b/demo/client/src/renderer.ts @@ -211,29 +211,37 @@ export abstract class Renderer { uploadPathColors(objectCount: number) { const renderContext = this.renderContext; - - this.pathColorsBufferTextures = []; - for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) { - const pathColorsBufferTexture = new PathfinderBufferTexture(renderContext.gl, - 'uPathColors'); const pathColors = this.pathColorsForObject(objectIndex); + + let pathColorsBufferTexture; + if (objectIndex >= this.pathColorsBufferTextures.length) { + pathColorsBufferTexture = new PathfinderBufferTexture(renderContext.gl, + 'uPathColors'); + this.pathColorsBufferTextures[objectIndex] = pathColorsBufferTexture; + } else { + pathColorsBufferTexture = this.pathColorsBufferTextures[objectIndex]; + } + pathColorsBufferTexture.upload(renderContext.gl, pathColors); - this.pathColorsBufferTextures.push(pathColorsBufferTexture); } } uploadPathTransforms(objectCount: number) { const renderContext = this.renderContext; - - this.pathTransformBufferTextures.splice(0); for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) { - const pathTransformBufferTexture = new PathfinderBufferTexture(renderContext.gl, - 'uPathTransform'); - const pathTransforms = this.pathTransformsForObject(objectIndex); + + let pathTransformBufferTexture; + if (objectIndex >= this.pathTransformBufferTextures.length) { + pathTransformBufferTexture = new PathfinderBufferTexture(renderContext.gl, + 'uPathTransform'); + this.pathTransformBufferTextures[objectIndex] = pathTransformBufferTexture; + } else { + pathTransformBufferTexture = this.pathTransformBufferTextures[objectIndex]; + } + pathTransformBufferTexture.upload(renderContext.gl, pathTransforms); - this.pathTransformBufferTextures.push(pathTransformBufferTexture); } }