UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

283 lines (230 loc) • 7.36 kB
import { ClampToEdgeWrapping, DataTexture, NearestFilter, RedFormat, UnsignedByteType } from "three"; import { BinaryDataType } from "../../../core/binary/type/BinaryDataType.js"; import { assert } from "../../../core/assert.js"; import { DataType2TypedArrayConstructorMapping } from "../../../core/binary/type/DataType2TypedArrayConstructorMapping.js"; import { max2 } from "../../../core/math/max2.js"; import { NumericType } from "../../../core/math/NumericType.js"; import { computeBinaryDataTypeByPrecision } from "../../../core/binary/type/computeBinaryDataTypeByPrecision.js"; import { computeThreeTextureTypeFromDataType } from "./computeThreeTextureTypeFromDataType.js"; import { computeThreeTextureInternalFormatFromDataType } from "./computeThreeTextureInternalFormatFromDataType.js"; import { computeThreeTextureFormat } from "./computeThreeTextureFormat.js"; import { DataTypeByteSizes } from "../../../core/binary/type/DataTypeByteSizes.js"; /** * How wide a data texture is * @type {number} */ const DATA_TEXTURE_WIDTH = 128; const SHRINK_FACTOR = 0.5; const GROW_FACTOR = 1.05; /** * Mainly used for storing non-color data in GPU memory * Useful for things like simulation and running general purpose GPU compute inside fragment shaders */ export class TextureBackedMemoryRegion { constructor() { /** * * @type {number} * @private */ this.__channel_count = 1; this.size = 1; /** * * @type {number} * @private */ this.__precision = 8; /** * * @type {NumericType|number} */ this.__type = NumericType.Uint; /** * * @type {DataTexture} * @private */ this.__texture = new DataTexture( new Uint8Array(0), 0, 0 ); // as of v128 THREE.js will ignore us and set texture width and height to 1, so we need to force it this.__texture.image.width = 0; this.__texture.image.height = 0; this.__texture.type = UnsignedByteType; this.__texture.format = RedFormat; this.__texture.magFilter = NearestFilter; this.__texture.minFilter = NearestFilter; this.__texture.wrapS = ClampToEdgeWrapping; this.__texture.wrapT = ClampToEdgeWrapping; this.__texture.generateMipmaps = false; /** * * @type {boolean} */ this.needsRebuild = false; /** * * @type {number} * @private */ this.__height = 0; /** * * @type {BinaryDataType|string} * @private */ this.__data_type = BinaryDataType.Uint8; } get concrete_type() { return this.__data_type; } /** * * @param {number} v */ set channel_count(v) { if (this.__channel_count === v) { // no change return; } this.__channel_count = v; this.needsRebuild = true; } get channel_count() { return this.__channel_count; } update() { if (this.needsRebuild) { this.build(); } } /** * * @param {NumericType} t */ set type(t) { assert.enum(t, NumericType, 't'); this.__type = t; this.__update_data_type(); } /** * * @return {NumericType|number} */ get type() { return this.__type; } /** * * @return {number} */ get precision() { return this.__precision; } /** * * @param {number} bit_count */ set precision(bit_count) { assert.isNonNegativeInteger(bit_count, 'bit_count'); this.__precision = bit_count; this.__update_data_type(); } dispose() { this.__texture.dispose(); } /** * * @private */ __update_data_type() { const new_data_type = computeBinaryDataTypeByPrecision(this.__type, this.__precision); if (new_data_type !== this.__data_type) { this.__data_type = new_data_type; this.needsRebuild = true; } } /** * @private * @param {boolean} keep_data */ build(keep_data = true) { const texture = this.__texture; const existing_data = texture.image.data; const existing_length = existing_data.length; const target_length = DATA_TEXTURE_WIDTH * this.__height * this.__channel_count; const TypedArray = DataType2TypedArrayConstructorMapping[this.__data_type]; const new_data = new TypedArray(target_length); texture.format = computeThreeTextureFormat(this.__type, this.__channel_count); texture.type = computeThreeTextureTypeFromDataType(this.__data_type); texture.internalFormat = computeThreeTextureInternalFormatFromDataType(this.__data_type, this.channel_count); if (keep_data) { // copy existing data to new storage if (existing_length < target_length) { new_data.set(existing_data); } else { new_data.set(existing_data.slice(0, target_length)); } } // dispose of the texture data on the GPU texture.dispose(); // set new texture dimensions texture.image.height = this.__height; texture.image.width = DATA_TEXTURE_WIDTH; // assign new data texture.image.data = new_data; // mark texture for update texture.needsUpdate = true; // clear build flag this.needsRebuild = false; } /** * * @return {DataTexture} */ getTexture() { return this.__texture; } /** * * @param {number} size * @returns {boolean} whether container was resized or not */ resize(size) { assert.isNonNegativeInteger(size, 'size'); this.size = size; const target_height = max2( Math.ceil(size / DATA_TEXTURE_WIDTH), 1 ); const current_height = this.__texture.image.height; if (current_height < target_height) { // texture too small //Over-provision to prevent thrashing this.__height = Math.ceil(target_height * GROW_FACTOR); this.needsRebuild = true; return true; } else if (current_height * SHRINK_FACTOR > target_height) { // texture too large, lets shrink it down this.__height = target_height; this.needsRebuild = true; return true; } // no actual container change return false; } /** * * @return {number} */ getTextureByteSize() { const unit_byte_size = DataTypeByteSizes[this.__data_type]; const image = this.__texture.image; return unit_byte_size * image.width * image.height * this.__channel_count; } }