Skip to content

Commit

Permalink
feat: Cleanup and bring texture recycling to webgl2, and add unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
robertleeplummerjr committed Dec 22, 2019
1 parent d221e97 commit 6e2f1da
Show file tree
Hide file tree
Showing 19 changed files with 494 additions and 229 deletions.
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ NOTE: documentation is slightly out of date for the upcoming release of v2. We
* [Loops](#loops)
* [Pipelining](#pipelining)
* [Cloning Textures](#cloning-textures-new-in-v2)
* [Cleanup pipeline texture memory](#cleanup-pipeline-texture-memory-new-in-v2)
* [Offscreen Canvas](#offscreen-canvas)
* [Cleanup](#cleanup)
* [Flattened typed array support](#flattened-typed-array-support)
Expand Down Expand Up @@ -236,7 +237,7 @@ Settings are an object used to create a `kernel` or `kernelMap`. Example: `gpu.
* VERY IMPORTANT! - Use this to add special native functions to your environment when you need specific functionality is needed.
* `injectedNative` or `kernel.setInjectedNative(string)` **New in V2!**: string, defined as: `{ functionName: functionSource }`. This is for injecting native code before translated kernel functions.
* `subKernels` or `kernel.setSubKernels(array)`: array, generally inherited from `GPU` instance.
* `immutable` or `kernel.setImmutable(boolean)`: boolean, default = `false`
* ~~`immutable` or `kernel.setImmutable(boolean)`: boolean, default = `false`~~ Deprecated
* `strictIntegers` or `kernel.setStrictIntegers(boolean)`: boolean, default = `false` - allows undefined argumentTypes and function return values to use strict integer declarations.
* `useLegacyEncoder` or `kernel.setUseLegacyEncoder(boolean)`: boolean, default `false` - more info [here](https://github.com/gpujs/gpu.js/wiki/Encoder-details).
* `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.
Expand Down Expand Up @@ -794,6 +795,12 @@ const result2 = kernel2(result1);
// Result: Float32Array[0, 1, 2, 3, ... 99]
```

### Cleanup pipeline texture memory **New in V2.4!**
Handling minimal amounts of GPU memory is handled internally, but a good practice is to clean up memory you no longer need.
Cleanup kernel outputs by using `texture.delete()` to keep GPU memory as small as possible.

NOTE: Internally textures will only release from memory if there are no references to them,

## Offscreen Canvas
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`:

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

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

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

For strongly typed mapped kernels:
```ts
import { GPU, Texture, IKernelFunctionThis } from './src';
```typescript
import { GPU, Texture, IKernelFunctionThis } from 'gpu.js';
const gpu = new GPU();

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

For extending constants:
```ts
import { GPU, IKernelFunctionThis } from './src';
```typescript
import { GPU, IKernelFunctionThis } from 'gpu.js';
const gpu = new GPU();

interface IConstants {
Expand Down
159 changes: 100 additions & 59 deletions dist/gpu-browser-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
*
* GPU Accelerated JavaScript
*
* @version 2.3.1
* @date Fri Dec 20 2019 12:27:34 GMT-0500 (Eastern Standard Time)
* @version 2.4.0
* @date Sun Dec 22 2019 17:36:21 GMT-0500 (Eastern Standard Time)
*
* @license MIT
* The MIT License
Expand Down Expand Up @@ -5144,10 +5144,16 @@ class GLKernel extends Kernel {
gl.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]);
this.canvas.width = this.maxTexSize[0];
this.canvas.height = this.maxTexSize[1];
this._setupOutputTexture();
if (this.subKernels && this.subKernels.length > 0) {
this._setupSubOutputTextures();
if (this.texture) {
this.texture.delete();
}
this.texture = null;
if (this.mappedTextures) {
for (let i = 0; i < this.mappedTextures.length; i++) {
this.mappedTextures[i].delete();
}
}
this.mappedTextures = null;
} else {
this.output = newOutput;
}
Expand Down Expand Up @@ -5195,6 +5201,39 @@ class GLKernel extends Kernel {
throw new Error(`Unknown tactic "${tactic}" use "speed", "balanced", "precision", or empty for auto`);
}
}

updateTextureArgumentRefs(arg) {
if (this.texture.texture === arg.texture) {
const { prevInput } = this;
if (prevInput) {
if (prevInput.texture.refs === 1) {
this.texture.delete();
this.texture = prevInput.clone();
}
prevInput.delete();
}
this.prevInput = arg.clone();
} else if (this.mappedTextures && this.mappedTextures.length > 0) {
const { mappedTextures, prevMappedInputs } = this;
for (let i = 0; i < mappedTextures.length; i++) {
const mappedTexture = mappedTextures[i];
if (mappedTexture.texture === arg.texture) {
const prevMappedInput = prevMappedInputs[i];
if (prevMappedInput) {
if (prevMappedInput.texture.refs === 1) {
if (mappedTexture) {
mappedTexture.delete();
mappedTextures[i] = prevMappedInput.clone();
}
}
prevMappedInput.delete();
}
prevMappedInputs[i] = arg.clone();
break;
}
}
}
}
}

const renderStrategy = Object.freeze({
Expand Down Expand Up @@ -5534,7 +5573,7 @@ class GLTexture extends Texture {
}

function selectTexture(gl, texture) {
gl.activeTexture(gl.TEXTURE0);
gl.activeTexture(gl.TEXTURE31);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
Expand Down Expand Up @@ -6187,7 +6226,7 @@ class Kernel {
}

setImmutable(flag) {
this.immutable = flag;
utils.warnDeprecated('method', 'setImmutable');
return this;
}

Expand Down Expand Up @@ -8400,8 +8439,8 @@ class WebGLKernelValueDynamicMemoryOptimizedNumberTexture extends WebGLKernelVal
}

updateValue(inputTexture) {
this.checkSize(inputTexture.size[0], inputTexture.size[1]);
this.dimensions = inputTexture.dimensions;
this.checkSize(inputTexture.size[0], inputTexture.size[1]);
this.textureSize = inputTexture.size;
this.kernel.setUniform3iv(this.dimensionsId, this.dimensions);
this.kernel.setUniform2iv(this.sizeId, this.textureSize);
Expand Down Expand Up @@ -8906,12 +8945,12 @@ class WebGLKernelValueMemoryOptimizedNumberTexture extends WebGLKernelValue {
if (this.checkContext && inputTexture.context !== this.context) {
throw new Error(`Value ${this.name} (${this.type }) must be from same context`);
}
const { context: gl } = this;
if (inputTexture.texture === this.kernel.outputTexture) {
inputTexture = inputTexture.clone();
gl.useProgram(this.kernel.program);
this.kernel.textureGarbage.push(inputTexture);

const { context: gl, kernel } = this;
if (kernel.pipeline) {
kernel.updateTextureArgumentRefs(inputTexture);
}

gl.activeTexture(this.contextHandle);
gl.bindTexture(gl.TEXTURE_2D, this.uploadValue = inputTexture.texture);
this.kernel.setUniform1i(this.id, this.index);
Expand Down Expand Up @@ -8952,7 +8991,6 @@ class WebGLKernelValueNumberTexture extends WebGLKernelValue {
}

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

const { kernel, context: gl } = this;
if (kernel.pipeline) {
if (kernel.texture.texture === inputTexture.texture) {
const { prevInput } = kernel;
if (prevInput) {
if (prevInput.texture.refs === 1) {
if (kernel.texture) {
kernel.texture.delete();
kernel.texture = prevInput.clone();
}
}
prevInput.delete();
}
kernel.prevInput = inputTexture.clone();
} else if (kernel.mappedTextures && kernel.mappedTextures.length > 0) {
const { mappedTextures, prevMappedInputs } = kernel;
for (let i = 0; i < mappedTextures.length; i++) {
const mappedTexture = mappedTextures[i];
if (mappedTexture.texture === inputTexture.texture) {
const prevMappedInput = prevMappedInputs[i];
if (prevMappedInput) {
if (prevMappedInput.texture.refs === 1) {
if (mappedTexture) {
mappedTexture.delete();
mappedTextures[i] = prevMappedInput.clone();
}
}
prevMappedInput.delete();
}
debugger;
prevMappedInputs[i] = inputTexture.clone();
break;
}
}
}
kernel.updateTextureArgumentRefs(inputTexture);
}

gl.activeTexture(this.contextHandle);
Expand Down Expand Up @@ -9605,7 +9612,7 @@ class WebGLKernel extends GLKernel {
this.framebuffer = null;
this.buffer = null;
this.texture = null;
this.mappedTextures = [];
this.mappedTextures = null;
this.textureCache = [];
this.programUniformLocationCache = {};
this.uniform1fCache = {};
Expand Down Expand Up @@ -9878,7 +9885,7 @@ class WebGLKernel extends GLKernel {
kernel: this,
strictIntegers: this.strictIntegers,
onRequestTexture: () => {
this.createTexture();
return this.createTexture();
},
onRequestIndex: () => {
return textureIndexes++;
Expand Down Expand Up @@ -10083,9 +10090,6 @@ class WebGLKernel extends GLKernel {
}

gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
if (gl.getError() > 0) {
debugger;
}
}

drawBuffers() {
Expand Down Expand Up @@ -10140,7 +10144,7 @@ class WebGLKernel extends GLKernel {

_setupSubOutputTextures() {
const { context: gl } = this;
if (this.mappedTextures.length > 0) {
if (this.mappedTextures && this.mappedTextures.length > 0) {
for (let i = 0; i < this.mappedTextures.length; i++) {
const mappedTexture = this.mappedTextures[i];
mappedTexture.beforeMutate();
Expand Down Expand Up @@ -10800,7 +10804,7 @@ class WebGLKernel extends GLKernel {
if (textureCacheIndex > -1) {
this.textureCache.splice(textureCacheIndex, 1);
}
delete this.texture;
this.texture = null;
}
if (this.mappedTextures && this.mappedTextures.length) {
for (let i = 0; i < this.mappedTextures.length; i++) {
Expand All @@ -10811,7 +10815,7 @@ class WebGLKernel extends GLKernel {
this.textureCache.splice(textureCacheIndex, 1);
}
}
delete this.mappedTextures;
this.mappedTextures = null;
}
while (this.textureCache.length > 0) {
const texture = this.textureCache.pop();
Expand Down Expand Up @@ -12377,9 +12381,14 @@ class WebGL2Kernel extends WebGLKernel {
}

_setupOutputTexture() {
const { texSize } = this;
const gl = this.context;
const { context: gl } = this;
if (this.texture) {
this.texture.beforeMutate();
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture.texture, 0);
return;
}
const texture = this.outputTexture = gl.createTexture();
const { texSize } = this;
gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
Expand All @@ -12393,12 +12402,32 @@ class WebGL2Kernel extends WebGLKernel {
gl.texImage2D(gl.TEXTURE_2D, 0, format, texSize[0], texSize[1], 0, format, gl.UNSIGNED_BYTE, null);
}
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
this.texture = new this.TextureConstructor({
texture,
size: texSize,
dimensions: this.threadDim,
output: this.output,
context: this.context,
internalFormat: this.getInternalFormat(),
textureFormat: this.getTextureFormat(),
kernel: this,
});
}

_setupSubOutputTextures() {
const { context: gl } = this;
if (this.mappedTextures && this.mappedTextures.length > 0) {
for (let i = 0; i < this.mappedTextures.length; i++) {
const mappedTexture = this.mappedTextures[i];
mappedTexture.beforeMutate();
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i + 1, gl.TEXTURE_2D, mappedTexture.texture, 0);
}
return;
}
const { texSize } = this;
const gl = this.context;
this.drawBuffersMap = [gl.COLOR_ATTACHMENT0];
this.mappedTextures = [];
this.prevMappedInputs = {};
for (let i = 0; i < this.subKernels.length; i++) {
const texture = this.createTexture();
this.drawBuffersMap.push(gl.COLOR_ATTACHMENT0 + i + 1);
Expand All @@ -12408,12 +12437,24 @@ class WebGL2Kernel extends WebGLKernel {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
const format = this.getInternalFormat();
if (this.precision === 'single') {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null);
gl.texStorage2D(gl.TEXTURE_2D, 1, format, texSize[0], texSize[1]);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
}
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i + 1, gl.TEXTURE_2D, texture, 0);

this.mappedTextures.push(new this.TextureConstructor({
texture,
size: texSize,
dimensions: this.threadDim,
output: this.output,
context: this.context,
internalFormat: this.getInternalFormat(),
textureFormat: this.getTextureFormat(),
kernel: this,
}));
}
}

Expand Down
6 changes: 3 additions & 3 deletions dist/gpu-browser-core.min.js

Large diffs are not rendered by default.

Loading

0 comments on commit 6e2f1da

Please sign in to comment.