Skip to content

Commit 6e2f1da

Browse files
feat: Cleanup and bring texture recycling to webgl2, and add unit test
1 parent d221e97 commit 6e2f1da

19 files changed

+494
-229
lines changed

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ NOTE: documentation is slightly out of date for the upcoming release of v2. We
9393
* [Loops](#loops)
9494
* [Pipelining](#pipelining)
9595
* [Cloning Textures](#cloning-textures-new-in-v2)
96+
* [Cleanup pipeline texture memory](#cleanup-pipeline-texture-memory-new-in-v2)
9697
* [Offscreen Canvas](#offscreen-canvas)
9798
* [Cleanup](#cleanup)
9899
* [Flattened typed array support](#flattened-typed-array-support)
@@ -236,7 +237,7 @@ Settings are an object used to create a `kernel` or `kernelMap`. Example: `gpu.
236237
* VERY IMPORTANT! - Use this to add special native functions to your environment when you need specific functionality is needed.
237238
* `injectedNative` or `kernel.setInjectedNative(string)` **New in V2!**: string, defined as: `{ functionName: functionSource }`. This is for injecting native code before translated kernel functions.
238239
* `subKernels` or `kernel.setSubKernels(array)`: array, generally inherited from `GPU` instance.
239-
* `immutable` or `kernel.setImmutable(boolean)`: boolean, default = `false`
240+
* ~~`immutable` or `kernel.setImmutable(boolean)`: boolean, default = `false`~~ Deprecated
240241
* `strictIntegers` or `kernel.setStrictIntegers(boolean)`: boolean, default = `false` - allows undefined argumentTypes and function return values to use strict integer declarations.
241242
* `useLegacyEncoder` or `kernel.setUseLegacyEncoder(boolean)`: boolean, default `false` - more info [here](https://github.com/gpujs/gpu.js/wiki/Encoder-details).
242243
* `warnVarUsage` or `kernel.setWarnVarUsage(boolean)`: turn off var usage warnings, they can be irritating, and in transpiled environments, there is nothing we can do about it.
@@ -794,6 +795,12 @@ const result2 = kernel2(result1);
794795
// Result: Float32Array[0, 1, 2, 3, ... 99]
795796
```
796797

798+
### Cleanup pipeline texture memory **New in V2.4!**
799+
Handling minimal amounts of GPU memory is handled internally, but a good practice is to clean up memory you no longer need.
800+
Cleanup kernel outputs by using `texture.delete()` to keep GPU memory as small as possible.
801+
802+
NOTE: Internally textures will only release from memory if there are no references to them,
803+
797804
## Offscreen Canvas
798805
GPU.js supports offscreen canvas where available. Here is an example of how to use it with two files, `gpu-worker.js`, and `index.js`:
799806

@@ -831,6 +838,7 @@ worker.onmessage = function(e) {
831838
## Cleanup
832839
* for instances of `GPU` use the `destroy` method. Example: `gpu.destroy()`
833840
* for instances of `Kernel` use the `destroy` method. Example: `kernel.destroy()`
841+
* for instances of `Texture` use the `delete` method. Example: `texture.delete()`
834842

835843
## Flattened typed array support
836844
To use the useful `x`, `y`, `z` `thread` lookup api inside of GPU.js, and yet use flattened arrays, there is the `Input` type.
@@ -984,8 +992,8 @@ To assist with mostly unit tests, but perhaps in scenarios outside of GPU.js, th
984992
## Typescript Typings
985993
Typescript is supported! Typings can be found [here](src/index.d.ts)!
986994
For strongly typed kernels:
987-
```ts
988-
import { GPU, IKernelFunctionThis } from './src';
995+
```typescript
996+
import { GPU, IKernelFunctionThis } from 'gpu.js';
989997
const gpu = new GPU();
990998

991999
function kernelFunction(this: IKernelFunctionThis): number {
@@ -1001,8 +1009,8 @@ console.log(result as number[][][]);
10011009
```
10021010

10031011
For strongly typed mapped kernels:
1004-
```ts
1005-
import { GPU, Texture, IKernelFunctionThis } from './src';
1012+
```typescript
1013+
import { GPU, Texture, IKernelFunctionThis } from 'gpu.js';
10061014
const gpu = new GPU();
10071015

10081016
function kernelFunction(this: IKernelFunctionThis): [number, number] {
@@ -1025,8 +1033,8 @@ console.log((result.test as Texture).toArray() as [number, number][]);
10251033
```
10261034

10271035
For extending constants:
1028-
```ts
1029-
import { GPU, IKernelFunctionThis } from './src';
1036+
```typescript
1037+
import { GPU, IKernelFunctionThis } from 'gpu.js';
10301038
const gpu = new GPU();
10311039

10321040
interface IConstants {

dist/gpu-browser-core.js

Lines changed: 100 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
*
55
* GPU Accelerated JavaScript
66
*
7-
* @version 2.3.1
8-
* @date Fri Dec 20 2019 12:27:34 GMT-0500 (Eastern Standard Time)
7+
* @version 2.4.0
8+
* @date Sun Dec 22 2019 17:36:21 GMT-0500 (Eastern Standard Time)
99
*
1010
* @license MIT
1111
* The MIT License
@@ -5144,10 +5144,16 @@ class GLKernel extends Kernel {
51445144
gl.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]);
51455145
this.canvas.width = this.maxTexSize[0];
51465146
this.canvas.height = this.maxTexSize[1];
5147-
this._setupOutputTexture();
5148-
if (this.subKernels && this.subKernels.length > 0) {
5149-
this._setupSubOutputTextures();
5147+
if (this.texture) {
5148+
this.texture.delete();
51505149
}
5150+
this.texture = null;
5151+
if (this.mappedTextures) {
5152+
for (let i = 0; i < this.mappedTextures.length; i++) {
5153+
this.mappedTextures[i].delete();
5154+
}
5155+
}
5156+
this.mappedTextures = null;
51515157
} else {
51525158
this.output = newOutput;
51535159
}
@@ -5195,6 +5201,39 @@ class GLKernel extends Kernel {
51955201
throw new Error(`Unknown tactic "${tactic}" use "speed", "balanced", "precision", or empty for auto`);
51965202
}
51975203
}
5204+
5205+
updateTextureArgumentRefs(arg) {
5206+
if (this.texture.texture === arg.texture) {
5207+
const { prevInput } = this;
5208+
if (prevInput) {
5209+
if (prevInput.texture.refs === 1) {
5210+
this.texture.delete();
5211+
this.texture = prevInput.clone();
5212+
}
5213+
prevInput.delete();
5214+
}
5215+
this.prevInput = arg.clone();
5216+
} else if (this.mappedTextures && this.mappedTextures.length > 0) {
5217+
const { mappedTextures, prevMappedInputs } = this;
5218+
for (let i = 0; i < mappedTextures.length; i++) {
5219+
const mappedTexture = mappedTextures[i];
5220+
if (mappedTexture.texture === arg.texture) {
5221+
const prevMappedInput = prevMappedInputs[i];
5222+
if (prevMappedInput) {
5223+
if (prevMappedInput.texture.refs === 1) {
5224+
if (mappedTexture) {
5225+
mappedTexture.delete();
5226+
mappedTextures[i] = prevMappedInput.clone();
5227+
}
5228+
}
5229+
prevMappedInput.delete();
5230+
}
5231+
prevMappedInputs[i] = arg.clone();
5232+
break;
5233+
}
5234+
}
5235+
}
5236+
}
51985237
}
51995238

52005239
const renderStrategy = Object.freeze({
@@ -5534,7 +5573,7 @@ class GLTexture extends Texture {
55345573
}
55355574

55365575
function selectTexture(gl, texture) {
5537-
gl.activeTexture(gl.TEXTURE0);
5576+
gl.activeTexture(gl.TEXTURE31);
55385577
gl.bindTexture(gl.TEXTURE_2D, texture);
55395578
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
55405579
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
@@ -6187,7 +6226,7 @@ class Kernel {
61876226
}
61886227

61896228
setImmutable(flag) {
6190-
this.immutable = flag;
6229+
utils.warnDeprecated('method', 'setImmutable');
61916230
return this;
61926231
}
61936232

@@ -8400,8 +8439,8 @@ class WebGLKernelValueDynamicMemoryOptimizedNumberTexture extends WebGLKernelVal
84008439
}
84018440

84028441
updateValue(inputTexture) {
8403-
this.checkSize(inputTexture.size[0], inputTexture.size[1]);
84048442
this.dimensions = inputTexture.dimensions;
8443+
this.checkSize(inputTexture.size[0], inputTexture.size[1]);
84058444
this.textureSize = inputTexture.size;
84068445
this.kernel.setUniform3iv(this.dimensionsId, this.dimensions);
84078446
this.kernel.setUniform2iv(this.sizeId, this.textureSize);
@@ -8906,12 +8945,12 @@ class WebGLKernelValueMemoryOptimizedNumberTexture extends WebGLKernelValue {
89068945
if (this.checkContext && inputTexture.context !== this.context) {
89078946
throw new Error(`Value ${this.name} (${this.type }) must be from same context`);
89088947
}
8909-
const { context: gl } = this;
8910-
if (inputTexture.texture === this.kernel.outputTexture) {
8911-
inputTexture = inputTexture.clone();
8912-
gl.useProgram(this.kernel.program);
8913-
this.kernel.textureGarbage.push(inputTexture);
8948+
8949+
const { context: gl, kernel } = this;
8950+
if (kernel.pipeline) {
8951+
kernel.updateTextureArgumentRefs(inputTexture);
89148952
}
8953+
89158954
gl.activeTexture(this.contextHandle);
89168955
gl.bindTexture(gl.TEXTURE_2D, this.uploadValue = inputTexture.texture);
89178956
this.kernel.setUniform1i(this.id, this.index);
@@ -8952,7 +8991,6 @@ class WebGLKernelValueNumberTexture extends WebGLKernelValue {
89528991
}
89538992

89548993
updateValue(inputTexture) {
8955-
const { kernel, context: gl } = this;
89568994
if (inputTexture.constructor !== this.initialValueConstructor) {
89578995
this.onUpdateValueMismatch(inputTexture.constructor);
89588996
return;
@@ -8961,40 +8999,9 @@ class WebGLKernelValueNumberTexture extends WebGLKernelValue {
89618999
throw new Error(`Value ${this.name} (${this.type}) must be from same context`);
89629000
}
89639001

9002+
const { kernel, context: gl } = this;
89649003
if (kernel.pipeline) {
8965-
if (kernel.texture.texture === inputTexture.texture) {
8966-
const { prevInput } = kernel;
8967-
if (prevInput) {
8968-
if (prevInput.texture.refs === 1) {
8969-
if (kernel.texture) {
8970-
kernel.texture.delete();
8971-
kernel.texture = prevInput.clone();
8972-
}
8973-
}
8974-
prevInput.delete();
8975-
}
8976-
kernel.prevInput = inputTexture.clone();
8977-
} else if (kernel.mappedTextures && kernel.mappedTextures.length > 0) {
8978-
const { mappedTextures, prevMappedInputs } = kernel;
8979-
for (let i = 0; i < mappedTextures.length; i++) {
8980-
const mappedTexture = mappedTextures[i];
8981-
if (mappedTexture.texture === inputTexture.texture) {
8982-
const prevMappedInput = prevMappedInputs[i];
8983-
if (prevMappedInput) {
8984-
if (prevMappedInput.texture.refs === 1) {
8985-
if (mappedTexture) {
8986-
mappedTexture.delete();
8987-
mappedTextures[i] = prevMappedInput.clone();
8988-
}
8989-
}
8990-
prevMappedInput.delete();
8991-
}
8992-
debugger;
8993-
prevMappedInputs[i] = inputTexture.clone();
8994-
break;
8995-
}
8996-
}
8997-
}
9004+
kernel.updateTextureArgumentRefs(inputTexture);
89989005
}
89999006

90009007
gl.activeTexture(this.contextHandle);
@@ -9605,7 +9612,7 @@ class WebGLKernel extends GLKernel {
96059612
this.framebuffer = null;
96069613
this.buffer = null;
96079614
this.texture = null;
9608-
this.mappedTextures = [];
9615+
this.mappedTextures = null;
96099616
this.textureCache = [];
96109617
this.programUniformLocationCache = {};
96119618
this.uniform1fCache = {};
@@ -9878,7 +9885,7 @@ class WebGLKernel extends GLKernel {
98789885
kernel: this,
98799886
strictIntegers: this.strictIntegers,
98809887
onRequestTexture: () => {
9881-
this.createTexture();
9888+
return this.createTexture();
98829889
},
98839890
onRequestIndex: () => {
98849891
return textureIndexes++;
@@ -10083,9 +10090,6 @@ class WebGLKernel extends GLKernel {
1008310090
}
1008410091

1008510092
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
10086-
if (gl.getError() > 0) {
10087-
debugger;
10088-
}
1008910093
}
1009010094

1009110095
drawBuffers() {
@@ -10140,7 +10144,7 @@ class WebGLKernel extends GLKernel {
1014010144

1014110145
_setupSubOutputTextures() {
1014210146
const { context: gl } = this;
10143-
if (this.mappedTextures.length > 0) {
10147+
if (this.mappedTextures && this.mappedTextures.length > 0) {
1014410148
for (let i = 0; i < this.mappedTextures.length; i++) {
1014510149
const mappedTexture = this.mappedTextures[i];
1014610150
mappedTexture.beforeMutate();
@@ -10800,7 +10804,7 @@ class WebGLKernel extends GLKernel {
1080010804
if (textureCacheIndex > -1) {
1080110805
this.textureCache.splice(textureCacheIndex, 1);
1080210806
}
10803-
delete this.texture;
10807+
this.texture = null;
1080410808
}
1080510809
if (this.mappedTextures && this.mappedTextures.length) {
1080610810
for (let i = 0; i < this.mappedTextures.length; i++) {
@@ -10811,7 +10815,7 @@ class WebGLKernel extends GLKernel {
1081110815
this.textureCache.splice(textureCacheIndex, 1);
1081210816
}
1081310817
}
10814-
delete this.mappedTextures;
10818+
this.mappedTextures = null;
1081510819
}
1081610820
while (this.textureCache.length > 0) {
1081710821
const texture = this.textureCache.pop();
@@ -12377,9 +12381,14 @@ class WebGL2Kernel extends WebGLKernel {
1237712381
}
1237812382

1237912383
_setupOutputTexture() {
12380-
const { texSize } = this;
12381-
const gl = this.context;
12384+
const { context: gl } = this;
12385+
if (this.texture) {
12386+
this.texture.beforeMutate();
12387+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture.texture, 0);
12388+
return;
12389+
}
1238212390
const texture = this.outputTexture = gl.createTexture();
12391+
const { texSize } = this;
1238312392
gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount);
1238412393
gl.bindTexture(gl.TEXTURE_2D, texture);
1238512394
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
@@ -12393,12 +12402,32 @@ class WebGL2Kernel extends WebGLKernel {
1239312402
gl.texImage2D(gl.TEXTURE_2D, 0, format, texSize[0], texSize[1], 0, format, gl.UNSIGNED_BYTE, null);
1239412403
}
1239512404
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
12405+
this.texture = new this.TextureConstructor({
12406+
texture,
12407+
size: texSize,
12408+
dimensions: this.threadDim,
12409+
output: this.output,
12410+
context: this.context,
12411+
internalFormat: this.getInternalFormat(),
12412+
textureFormat: this.getTextureFormat(),
12413+
kernel: this,
12414+
});
1239612415
}
1239712416

1239812417
_setupSubOutputTextures() {
12418+
const { context: gl } = this;
12419+
if (this.mappedTextures && this.mappedTextures.length > 0) {
12420+
for (let i = 0; i < this.mappedTextures.length; i++) {
12421+
const mappedTexture = this.mappedTextures[i];
12422+
mappedTexture.beforeMutate();
12423+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i + 1, gl.TEXTURE_2D, mappedTexture.texture, 0);
12424+
}
12425+
return;
12426+
}
1239912427
const { texSize } = this;
12400-
const gl = this.context;
1240112428
this.drawBuffersMap = [gl.COLOR_ATTACHMENT0];
12429+
this.mappedTextures = [];
12430+
this.prevMappedInputs = {};
1240212431
for (let i = 0; i < this.subKernels.length; i++) {
1240312432
const texture = this.createTexture();
1240412433
this.drawBuffersMap.push(gl.COLOR_ATTACHMENT0 + i + 1);
@@ -12408,12 +12437,24 @@ class WebGL2Kernel extends WebGLKernel {
1240812437
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1240912438
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
1241012439
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
12440+
const format = this.getInternalFormat();
1241112441
if (this.precision === 'single') {
12412-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null);
12442+
gl.texStorage2D(gl.TEXTURE_2D, 1, format, texSize[0], texSize[1]);
1241312443
} else {
1241412444
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
1241512445
}
1241612446
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i + 1, gl.TEXTURE_2D, texture, 0);
12447+
12448+
this.mappedTextures.push(new this.TextureConstructor({
12449+
texture,
12450+
size: texSize,
12451+
dimensions: this.threadDim,
12452+
output: this.output,
12453+
context: this.context,
12454+
internalFormat: this.getInternalFormat(),
12455+
textureFormat: this.getTextureFormat(),
12456+
kernel: this,
12457+
}));
1241712458
}
1241812459
}
1241912460

dist/gpu-browser-core.min.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)