@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
283 lines (230 loc) • 7.36 kB
JavaScript
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;
}
}