From 6439f791c4aac76e6b8535eba76a94fd41f642e9 Mon Sep 17 00:00:00 2001 From: Micah Date: Tue, 18 Apr 2023 15:10:26 -0500 Subject: [PATCH 1/3] functions for setting values at specific indices --- src/GPULayer.ts | 37 +++++++++++++++++++++++++++++++++++++ src/GPULayerHelpers.ts | 19 ++++++++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/GPULayer.ts b/src/GPULayer.ts index bc3c8ce..b9e8596 100644 --- a/src/GPULayer.ts +++ b/src/GPULayer.ts @@ -672,7 +672,43 @@ export class GPULayer { // Unbind texture. gl.bindTexture(gl.TEXTURE_2D, null); } + + // set a single value at a given 2D location in the layer. + setAtIndex2D(x: number, y: number, components: GPULayerArray | number[]) { + const { _composer, width, height, _currentTexture } = this; + const { gl } = _composer; + + // Validate x and y. + if (x < 0 || x >= width || y < 0 || y >= height) { + throw new Error(`Invalid coordinates [${x}, ${y}] for GPULayer "${this.name}" with dimensions [${width}, ${height}].`); + } + // Validate components. + const validatedValues = GPULayer.validateGPULayerArray(components, this, 1); + + // Set the value at the index. + gl.bindTexture(gl.TEXTURE_2D, _currentTexture); + gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, 1, 1, this._glFormat, this._glType, validatedValues); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + // set a single value at a given 1D location in the layer. + setAtIndex1D(index: number, components: GPULayerArray | number[]) { + const { _glInternalFormat, _glFormat, _glType, width, height, _currentTexture } = this; + + // Validate index. + if (index < 0 || index >= width * height) { + throw new Error(`Invalid index ${index} for GPULayer "${this.name}" with dimensions [${width}, ${height}].`); + } + + // Get the x and y coordinates of the index. + const x = index % width; + const y = Math.floor(index / width); + + // Set the value at the index. + this.setAtIndex2D(x, y, components); + } + // setFromImage(image: HTMLImageElement) { // const { name, _composer, width, height, _currentTexture, _glInternalFormat, _glFormat, _glType, numComponents, type } = this; // const { gl } = _composer; @@ -1190,5 +1226,6 @@ export class GPULayer { static validateGPULayerArray( array: GPULayerArray | number[], layer: GPULayer, + validateSubarrayLength?: number ): GPULayerArray; } \ No newline at end of file diff --git a/src/GPULayerHelpers.ts b/src/GPULayerHelpers.ts index a0de800..67976e3 100644 --- a/src/GPULayerHelpers.ts +++ b/src/GPULayerHelpers.ts @@ -889,14 +889,21 @@ export function minMaxValuesForType(type: GPULayerType) { * Recasts typed array to match GPULayer.internalType. * @private */ -GPULayer.validateGPULayerArray = (array: GPULayerArray | number[], layer: GPULayer) => { +GPULayer.validateGPULayerArray = (array: GPULayerArray | number[], layer: GPULayer, validateSubarrayLength : number | null = null) => { const { numComponents, width, height, name } = layer; const glNumChannels = layer._glNumChannels; const internalType = layer._internalType; const length = layer.is1D() ? layer.length : null; // Check that data is correct length (user error). - if (array.length !== width * height * numComponents) { // Either the correct length for WebGLTexture size + + // Are we validating a subarray? + if (validateSubarrayLength) { + if (array.length !== validateSubarrayLength * numComponents) { + throw new Error(`Invalid data length: ${array.length} for GPULayer "${name}" of numComponents: ${numComponents}.`); + } + // Otherwise our data + } else if (array.length !== width * height * numComponents) { // Either the correct length for WebGLTexture size if (!length || (length && array.length !== length * numComponents)) { // Of the correct length for 1D array. throw new Error(`Invalid data length: ${array.length} for GPULayer "${name}" of ${length ? `length ${length} and ` : ''}dimensions: [${width}, ${height}] and numComponents: ${numComponents}.`); } @@ -948,7 +955,13 @@ GPULayer.validateGPULayerArray = (array: GPULayerArray | number[], layer: GPULay // Then check if array needs to be lengthened. // This could be because glNumChannels !== numComponents or because length !== width * height. - const arrayLength = width * height * glNumChannels; + + const arrayLength = + // If we are validating a subarray, we need to use the subarray length. + validateSubarrayLength ? validateSubarrayLength * glNumChannels : + // otherwise we can use the layer length. + width * height * glNumChannels; + const shouldResize = array.length !== arrayLength; let validatedArray = array as GPULayerArray; From 00efc6b774a8e9e5d009e0c6973d4cde2d38d605 Mon Sep 17 00:00:00 2001 From: Micah Date: Tue, 2 May 2023 20:20:30 -0500 Subject: [PATCH 2/3] update docs --- docs/classes/GPULayer.md | 41 ++++++++++++++++++++++++++++++++++++++++ src/GPULayer.ts | 15 ++++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/docs/classes/GPULayer.md b/docs/classes/GPULayer.md index b65dca3..f79d81c 100644 --- a/docs/classes/GPULayer.md +++ b/docs/classes/GPULayer.md @@ -23,6 +23,8 @@ - [decrementBufferIndex](GPULayer.md#decrementbufferindex) - [getStateAtIndex](GPULayer.md#getstateatindex) - [setFromArray](GPULayer.md#setfromarray) +- [setAtIndex2D](GPULayer.md#setatindex2d) +- [setAtIndex1D](GPULayer.md#setatindex1d) - [resize](GPULayer.md#resize) - [clear](GPULayer.md#clear) - [getValues](GPULayer.md#getvalues) @@ -216,6 +218,45 @@ ___ ___ +### setAtIndex2D + +▸ **setAtIndex2D**(`x`, `y`, `components`): `void` + +Set a single value at a given 2D location in the layer. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `x` | `number` | +| `y` | `number` | +| `components` | `number`[] \| [`GPULayerArray`](../README.md#gpulayerarray) | + +#### Returns + +`void` + +___ + +### setAtIndex1D + +▸ **setAtIndex1D**(`index`, `components`): `void` + +Set a single value at a given 1D location in the layer. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `index` | `number` | +| `components` | `number`[] \| [`GPULayerArray`](../README.md#gpulayerarray) | + +#### Returns + +`void` + +___ + ### resize ▸ **resize**(`dimensions`, `arrayOrImage?`): `void` diff --git a/src/GPULayer.ts b/src/GPULayer.ts index b9e8596..3d1b49c 100644 --- a/src/GPULayer.ts +++ b/src/GPULayer.ts @@ -654,7 +654,7 @@ export class GPULayer { this._textureOverrides[this.bufferIndex] = undefined; } } - + setFromArray(array: GPULayerArray | number[]) { const { _composer, @@ -673,7 +673,12 @@ export class GPULayer { gl.bindTexture(gl.TEXTURE_2D, null); } - // set a single value at a given 2D location in the layer. + /** + * Set a single value at a given 2D location in the layer. + * @param x + * @param y + * @param components + */ setAtIndex2D(x: number, y: number, components: GPULayerArray | number[]) { const { _composer, width, height, _currentTexture } = this; const { gl } = _composer; @@ -692,7 +697,11 @@ export class GPULayer { gl.bindTexture(gl.TEXTURE_2D, null); } - // set a single value at a given 1D location in the layer. + /** + * Set a single value at a given 1D location in the layer. + * @param index + * @param components + */ setAtIndex1D(index: number, components: GPULayerArray | number[]) { const { _glInternalFormat, _glFormat, _glType, width, height, _currentTexture } = this; From 580fed7da0e1956d730e96e2f2c0cd47ac00b2ac Mon Sep 17 00:00:00 2001 From: Micah Date: Wed, 3 May 2023 01:11:08 -0500 Subject: [PATCH 3/3] set subranges / subregions of layers from array --- docs/README.md | 30 ++++++++ docs/classes/GPULayer.md | 3 +- src/GPULayer.ts | 151 +++++++++++++++++++++++++++------------ src/constants.ts | 11 +++ 4 files changed, 149 insertions(+), 46 deletions(-) diff --git a/docs/README.md b/docs/README.md index 74d8b3f..d542765 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,6 +51,8 @@ gpu-io - [GPULayerFilter](README.md#gpulayerfilter) - [GPULayerWrap](README.md#gpulayerwrap) - [GPULayerState](README.md#gpulayerstate) +- [GPULayerRange](README.md#gpulayerrange) +- [GPULayerRegion](README.md#gpulayerregion) - [ImageFormat](README.md#imageformat) - [ImageType](README.md#imagetype) - [GLSLVersion](README.md#glslversion) @@ -366,6 +368,34 @@ This data structure also includes a reference back to the GPULayer that it origi ___ +### GPULayerRange + +Ƭ **GPULayerRange**: `Object` + +#### Type declaration + +| Name | Type | +| :------ | :------ | +| `start` | `number` | +| `end` | `number` | + +___ + +### GPULayerRegion + +Ƭ **GPULayerRegion**: `Object` + +#### Type declaration + +| Name | Type | +| :------ | :------ | +| `x` | `number` | +| `y` | `number` | +| `width` | `number` | +| `height` | `number` | + +___ + ### ImageFormat Ƭ **ImageFormat**: typeof [`RGB`](README.md#rgb) \| typeof [`RGBA`](README.md#rgba) diff --git a/docs/classes/GPULayer.md b/docs/classes/GPULayer.md index f79d81c..679080c 100644 --- a/docs/classes/GPULayer.md +++ b/docs/classes/GPULayer.md @@ -204,13 +204,14 @@ ___ ### setFromArray -▸ **setFromArray**(`array`): `void` +▸ **setFromArray**(`array`, `range?`): `void` #### Parameters | Name | Type | | :------ | :------ | | `array` | `number`[] \| [`GPULayerArray`](../README.md#gpulayerarray) | +| `range?` | [`GPULayerRange`](../README.md#gpulayerrange) \| [`GPULayerRegion`](../README.md#gpulayerregion) | #### Returns diff --git a/src/GPULayer.ts b/src/GPULayer.ts index 3d1b49c..a3bca9d 100644 --- a/src/GPULayer.ts +++ b/src/GPULayer.ts @@ -46,6 +46,8 @@ import { ImageType, validImageFormats, validImageTypes, + GPULayerRange, + GPULayerRegion, } from './constants'; import { readPixelsAsync, @@ -654,24 +656,109 @@ export class GPULayer { this._textureOverrides[this.bufferIndex] = undefined; } } - - setFromArray(array: GPULayerArray | number[]) { - const { - _composer, - _glInternalFormat, - _glFormat, - _glType, - width, - height, - _currentTexture, - } = this; - const { gl } = _composer; - const validatedArray = GPULayer.validateGPULayerArray(array, this); - gl.bindTexture(gl.TEXTURE_2D, _currentTexture); - gl.texImage2D(gl.TEXTURE_2D, 0, _glInternalFormat, width, height, 0, _glFormat, _glType, validatedArray); - // Unbind texture. - gl.bindTexture(gl.TEXTURE_2D, null); + + /** + * Get texImage2D regions for layer range or region + * @private + */ + _getTexImage2DRegions(range?: GPULayerRange | GPULayerRegion): { + x: number; + y: number; + width: number; + height: number; + sliceStart: number; + sliceEnd: number; + }[] { + const { width, height } = this; + if (!range) { + // no range + return [{x: 0, y: 0, width, height, sliceStart: 0, sliceEnd: width*height}]; + } else if ((range as GPULayerRegion).width !== undefined) { + // this is a 2D region + const { x, y, width, height } = range as GPULayerRegion; + return [{x, y, width, height, sliceStart: 0, sliceEnd: width*height}]; + } + + if ((range as GPULayerRange).start !== undefined) { + // this is a 1D range + const { width } = this; + const { start, end } = range as GPULayerRange; + const length = end - start; + + let regions = [] + const numRows = Math.ceil(length / width); + if (numRows > 0) { + // first region, top cap + regions.push({ + x: start % width, + y: Math.floor(start / width), + width: Math.min(width - start % width, length), + height: 1, + sliceStart: 0, + sliceEnd: Math.min(width - start % width, length), + }); + } + if (numRows > 1) { + // end region, bottom cap + regions.push({ + x: 0, + y: Math.floor(end / width), + width: end % width, + height: 1, + sliceStart: length - end % width, + sliceEnd: length, + }); + } + if (numRows > 2) { + // middle region + regions.push({ + x: 0, + y: Math.floor(start / width) + 1, + width: width, + height: numRows - 2, + sliceStart: width - start % width + 1, + sliceEnd: width - start % width + 1 + (numRows - 2) * width, + }); + } + + return regions; + } + return []; } + + setFromArray(array: GPULayerArray | number[], range?: GPULayerRange | GPULayerRegion) { + const { + _composer, + _glFormat, + _glType, + _currentTexture, + numComponents + } = this; + const { gl } = _composer; + const regions = this._getTexImage2DRegions(range); + gl.bindTexture(gl.TEXTURE_2D, _currentTexture); + for (const { x, y, width, height, sliceStart, sliceEnd } of regions) { + const validatedArray = GPULayer.validateGPULayerArray( + array.slice(sliceStart*numComponents, sliceEnd*numComponents), + this, + // if no range was passed, we're setting the whole layer and we can validate the whole array + !range ? undefined : Math.min(sliceEnd - sliceStart, array.length / numComponents) + ); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + x, + y, + width, + height, + _glFormat, + _glType, + validatedArray + ); + } + // Unbind texture. + gl.bindTexture(gl.TEXTURE_2D, null); + } /** * Set a single value at a given 2D location in the layer. @@ -680,21 +767,7 @@ export class GPULayer { * @param components */ setAtIndex2D(x: number, y: number, components: GPULayerArray | number[]) { - const { _composer, width, height, _currentTexture } = this; - const { gl } = _composer; - - // Validate x and y. - if (x < 0 || x >= width || y < 0 || y >= height) { - throw new Error(`Invalid coordinates [${x}, ${y}] for GPULayer "${this.name}" with dimensions [${width}, ${height}].`); - } - - // Validate components. - const validatedValues = GPULayer.validateGPULayerArray(components, this, 1); - - // Set the value at the index. - gl.bindTexture(gl.TEXTURE_2D, _currentTexture); - gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, 1, 1, this._glFormat, this._glType, validatedValues); - gl.bindTexture(gl.TEXTURE_2D, null); + this.setFromArray(components, {x, y, width: 1, height: 1}); } /** @@ -703,19 +776,7 @@ export class GPULayer { * @param components */ setAtIndex1D(index: number, components: GPULayerArray | number[]) { - const { _glInternalFormat, _glFormat, _glType, width, height, _currentTexture } = this; - - // Validate index. - if (index < 0 || index >= width * height) { - throw new Error(`Invalid index ${index} for GPULayer "${this.name}" with dimensions [${width}, ${height}].`); - } - - // Get the x and y coordinates of the index. - const x = index % width; - const y = Math.floor(index / width); - - // Set the value at the index. - this.setAtIndex2D(x, y, components); + this.setFromArray(components, {start: index, end: index+1}); } // setFromImage(image: HTMLImageElement) { diff --git a/src/constants.ts b/src/constants.ts index ea31e1f..8c91ae5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -112,6 +112,17 @@ export type GPULayerState = { texture: WebGLTexture, layer: GPULayer, } +export type GPULayerRange = { + start: number, + end: number, +} + +export type GPULayerRegion = { + x: number, + y: number, + width: number, + height: number, +} // For image urls that are passed in and inited as textures. /**