UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

196 lines (140 loc) 5.26 kB
import { ClampToEdgeWrapping, DataTexture, NearestFilter, RedIntegerFormat, UnsignedIntType } from "three"; import { assert } from "../../../../core/assert.js"; import { UINT32_MAX } from "../../../../core/binary/UINT32_MAX.js"; import { compose_tile_address } from "./tile/compose_tile_address.js"; import { decompose_finger_print } from "./tile/decompose_finger_print.js"; const EMPTY_TILE = UINT32_MAX; /* ENCODING: 22 bit : slot index 10 bit : tile mip */ /** * Represents entire mip pyramid of tiles, where each tile slot points to an actual resident tile */ export class VirtualTextureMemoryMapping { /** * In tiles * @type {number} */ #resolution = 0; #max_mip_level = 0; get max_mip_level(){ return this.#max_mip_level; } #pyramid = new Uint32Array(0); #texture = new DataTexture( new Uint32Array(1), 1, 1, RedIntegerFormat, UnsignedIntType ) get texture() { return this.#texture; } constructor() { const texture = this.#texture; texture.name = "Virtual Texture / Dereference Mapping" texture.unpackAlignment = 1; texture.format = RedIntegerFormat; texture.type = UnsignedIntType; texture.internalFormat = "R32UI"; texture.generateMipmaps = false; texture.wrapS = ClampToEdgeWrapping; texture.wrapT = ClampToEdgeWrapping; texture.magFilter = NearestFilter; texture.minFilter = NearestFilter; } /** * * @param {number} resolution in tiles */ set resolution(resolution) { assert.isNonNegativeInteger(resolution, 'resolution'); if (this.#resolution === resolution) { return; } this.#resolution = resolution; //max mip const max_mip_level = Math.log2(resolution); const desired_size = compose_tile_address(max_mip_level + 1, 0, 0); const texture_dimension = Math.ceil(Math.sqrt(desired_size)); const size = texture_dimension*texture_dimension; this.#max_mip_level = max_mip_level; const arrayBuffer = new ArrayBuffer(size * 4); this.#pyramid = new Uint32Array(arrayBuffer); const texture = this.#texture; texture.dispose(); texture.image.data = this.#pyramid; texture.image.width = texture_dimension; texture.image.height = texture_dimension; texture.needsUpdate = true; } get resolution() { return this.#resolution; } /** * * @param {VirtualTexturePage} page */ set residency(page) { const tiles = page.resident_tiles; const tile_count = tiles.length; // clear pyramid const pyramid = this.#pyramid; pyramid.fill(EMPTY_TILE); // fill by residence for (let i = 0; i < tile_count; i++) { const tile = tiles[i]; const fingerPrint = tile.finger_print; const { mip, x, y } = decompose_finger_print(fingerPrint); const address = compose_tile_address(mip, x, y); const encoded = (tile.page_slot & 0x3FFFFF) | ((mip & 0x3FF) << 22) //mip ; pyramid[address] = encoded; } // back-fill empty slots for (let mip = 1; mip <= this.#max_mip_level; mip++) { const mip_resolution = 1 << mip; for (let y = 0; y < mip_resolution; y++) { for (let x = 0; x < mip_resolution; x++) { const tile_address = compose_tile_address(mip, x, y); if (pyramid[tile_address] !== EMPTY_TILE) { // page is filled, all is good, move on continue; } const parent_mip = mip - 1; const parent_x = x >>> 1; const parent_y = y >>> 1; const parent_tile_address = compose_tile_address(parent_mip, parent_x, parent_y); const parent_encoded_tile = pyramid[parent_tile_address]; // decode parent tile const tile_index = (parent_encoded_tile) & 0x3FFFFF const tile_mip = (parent_encoded_tile >> 22) & 0x3FF // swap in parent tile const encoded_tile = (tile_index) | (tile_mip << 22) ; pyramid[tile_address] = encoded_tile; } } } // request texture update this.#texture.needsUpdate = true; } getTileMetadata(x,y,mip){ const tile_address = compose_tile_address(mip, x, y); const encoded_tile = this.#pyramid[tile_address]; return { slot: encoded_tile & 0x3FFFFF, mip: ((encoded_tile >> 22) & 0x3FF) }; } dispose() { this.#texture.dispose(); } }