123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- import Cartesian2 from "../Core/Cartesian2.js";
- import Cartesian3 from "../Core/Cartesian3.js";
- import ComponentDatatype from "../Core/ComponentDatatype.js";
- import ContextLimits from "../Renderer/ContextLimits.js";
- import defaultValue from "../Core/defaultValue.js";
- import defined from "../Core/defined.js";
- import destroyObject from "../Core/destroyObject.js";
- import DeveloperError from "../Core/DeveloperError.js";
- import CesiumMath from "../Core/Math.js";
- import MetadataComponentType from "./MetadataComponentType.js";
- import PixelDatatype from "../Renderer/PixelDatatype.js";
- import PixelFormat from "../Core/PixelFormat.js";
- import RuntimeError from "../Core/RuntimeError.js";
- import Sampler from "../Renderer/Sampler.js";
- import Texture from "../Renderer/Texture.js";
- import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js";
- import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
- import TextureWrap from "../Renderer/TextureWrap.js";
- /**
- * @alias Megatexture
- * @constructor
- *
- * @param {Context} context
- * @param {Cartesian3} dimensions
- * @param {number} channelCount
- * @param {MetadataComponentType} componentType
- * @param {number} [textureMemoryByteLength]
- *
- * @private
- */
- function Megatexture(
- context,
- dimensions,
- channelCount,
- componentType,
- textureMemoryByteLength
- ) {
- // TODO there are a lot of texture packing rules, see https://github.com/CesiumGS/cesium/issues/9572
- // Unsigned short textures not allowed in webgl 1, so treat as float
- if (componentType === MetadataComponentType.UNSIGNED_SHORT) {
- componentType = MetadataComponentType.FLOAT32;
- }
- const supportsFloatingPointTexture = context.floatingPointTexture;
- if (
- componentType === MetadataComponentType.FLOAT32 &&
- !supportsFloatingPointTexture
- ) {
- throw new RuntimeError("Floating point texture not supported");
- }
- // TODO support more
- let pixelType;
- if (
- componentType === MetadataComponentType.FLOAT32 ||
- componentType === MetadataComponentType.FLOAT64
- ) {
- pixelType = PixelDatatype.FLOAT;
- } else if (componentType === MetadataComponentType.UINT8) {
- pixelType = PixelDatatype.UNSIGNED_BYTE;
- }
- let pixelFormat;
- if (channelCount === 1) {
- pixelFormat = context.webgl2 ? PixelFormat.RED : PixelFormat.LUMINANCE;
- } else if (channelCount === 2) {
- pixelFormat = context.webgl2 ? PixelFormat.RG : PixelFormat.LUMINANCE_ALPHA;
- } else if (channelCount === 3) {
- pixelFormat = PixelFormat.RGB;
- } else if (channelCount === 4) {
- pixelFormat = PixelFormat.RGBA;
- }
- const maximumTextureMemoryByteLength = 512 * 1024 * 1024;
- const defaultTextureMemoryByteLength = 128 * 1024 * 1024;
- textureMemoryByteLength = Math.min(
- defaultValue(textureMemoryByteLength, defaultTextureMemoryByteLength),
- maximumTextureMemoryByteLength
- );
- const maximumTextureDimensionContext = ContextLimits.maximumTextureSize;
- const componentTypeByteLength = MetadataComponentType.getSizeInBytes(
- componentType
- );
- const texelCount = Math.floor(
- textureMemoryByteLength / (channelCount * componentTypeByteLength)
- );
- const textureDimension = Math.min(
- maximumTextureDimensionContext,
- CesiumMath.previousPowerOfTwo(Math.floor(Math.sqrt(texelCount)))
- );
- const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.x));
- const sliceCountPerRegionY = Math.ceil(dimensions.z / sliceCountPerRegionX);
- const voxelCountPerRegionX = sliceCountPerRegionX * dimensions.x;
- const voxelCountPerRegionY = sliceCountPerRegionY * dimensions.y;
- const regionCountPerMegatextureX = Math.floor(
- textureDimension / voxelCountPerRegionX
- );
- const regionCountPerMegatextureY = Math.floor(
- textureDimension / voxelCountPerRegionY
- );
- if (regionCountPerMegatextureX === 0 || regionCountPerMegatextureY === 0) {
- throw new RuntimeError("Tileset is too large to fit into megatexture");
- }
- /**
- * @type {number}
- * @readonly
- */
- this.channelCount = channelCount;
- /**
- * @type {MetadataComponentType}
- * @readonly
- */
- this.componentType = componentType;
- /**
- * @type {Cartesian3}
- * @readonly
- */
- this.voxelCountPerTile = Cartesian3.clone(dimensions, new Cartesian3());
- /**
- * @type {number}
- * @readonly
- */
- this.maximumTileCount =
- regionCountPerMegatextureX * regionCountPerMegatextureY;
- /**
- * @type {Cartesian2}
- * @readonly
- */
- this.regionCountPerMegatexture = new Cartesian2(
- regionCountPerMegatextureX,
- regionCountPerMegatextureY
- );
- /**
- * @type {Cartesian2}
- * @readonly
- */
- this.voxelCountPerRegion = new Cartesian2(
- voxelCountPerRegionX,
- voxelCountPerRegionY
- );
- /**
- * @type {Cartesian2}
- * @readonly
- */
- this.sliceCountPerRegion = new Cartesian2(
- sliceCountPerRegionX,
- sliceCountPerRegionY
- );
- /**
- * @type {Cartesian2}
- * @readonly
- */
- this.voxelSizeUv = new Cartesian2(
- 1.0 / textureDimension,
- 1.0 / textureDimension
- );
- /**
- * @type {Cartesian2}
- * @readonly
- */
- this.sliceSizeUv = new Cartesian2(
- dimensions.x / textureDimension,
- dimensions.y / textureDimension
- );
- /**
- * @type {Cartesian2}
- * @readonly
- */
- this.regionSizeUv = new Cartesian2(
- voxelCountPerRegionX / textureDimension,
- voxelCountPerRegionY / textureDimension
- );
- /**
- * @type {Texture}
- * @readonly
- */
- this.texture = new Texture({
- context: context,
- pixelFormat: pixelFormat,
- pixelDatatype: pixelType,
- flipY: false,
- width: textureDimension,
- height: textureDimension,
- sampler: new Sampler({
- wrapS: TextureWrap.CLAMP_TO_EDGE,
- wrapT: TextureWrap.CLAMP_TO_EDGE,
- minificationFilter: TextureMinificationFilter.LINEAR,
- magnificationFilter: TextureMagnificationFilter.LINEAR,
- }),
- });
- const componentDatatype = MetadataComponentType.toComponentDatatype(
- componentType
- );
- /**
- * @type {Array}
- */
- this.tileVoxelDataTemp = ComponentDatatype.createTypedArray(
- componentDatatype,
- voxelCountPerRegionX * voxelCountPerRegionY * channelCount
- );
- /**
- * @type {MegatextureNode[]}
- * @readonly
- */
- this.nodes = new Array(this.maximumTileCount);
- for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {
- this.nodes[tileIndex] = new MegatextureNode(tileIndex);
- }
- for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {
- const node = this.nodes[tileIndex];
- node.previousNode = tileIndex > 0 ? this.nodes[tileIndex - 1] : undefined;
- node.nextNode =
- tileIndex < this.maximumTileCount - 1
- ? this.nodes[tileIndex + 1]
- : undefined;
- }
- /**
- * @type {MegatextureNode}
- * @readonly
- */
- this.occupiedList = undefined;
- /**
- * @type {MegatextureNode}
- * @readonly
- */
- this.emptyList = this.nodes[0];
- /**
- * @type {number}
- * @readonly
- */
- this.occupiedCount = 0;
- }
- /**
- * @alias MegatextureNode
- * @constructor
- *
- * @param {number} index
- *
- * @private
- */
- function MegatextureNode(index) {
- /**
- * @type {number}
- */
- this.index = index;
- /**
- * @type {MegatextureNode}
- */
- this.nextNode = undefined;
- /**
- * @type {MegatextureNode}
- */
- this.previousNode = undefined;
- }
- /**
- * @param {Array} data
- * @returns {number}
- */
- Megatexture.prototype.add = function (data) {
- if (this.isFull()) {
- throw new DeveloperError("Trying to add when there are no empty spots");
- }
- // remove head of empty list
- const node = this.emptyList;
- this.emptyList = this.emptyList.nextNode;
- if (defined(this.emptyList)) {
- this.emptyList.previousNode = undefined;
- }
- // make head of occupied list
- node.nextNode = this.occupiedList;
- if (defined(node.nextNode)) {
- node.nextNode.previousNode = node;
- }
- this.occupiedList = node;
- const index = node.index;
- this.writeDataToTexture(index, data);
- this.occupiedCount++;
- return index;
- };
- /**
- * @param {number} index
- */
- Megatexture.prototype.remove = function (index) {
- if (index < 0 || index >= this.maximumTileCount) {
- throw new DeveloperError("Megatexture index out of bounds");
- }
- // remove from list
- const node = this.nodes[index];
- if (defined(node.previousNode)) {
- node.previousNode.nextNode = node.nextNode;
- }
- if (defined(node.nextNode)) {
- node.nextNode.previousNode = node.previousNode;
- }
- // make head of empty list
- node.nextNode = this.emptyList;
- if (defined(node.nextNode)) {
- node.nextNode.previousNode = node;
- }
- node.previousNode = undefined;
- this.emptyList = node;
- this.occupiedCount--;
- };
- /**
- * @returns {boolean}
- */
- Megatexture.prototype.isFull = function () {
- return this.emptyList === undefined;
- };
- /**
- * @param {number} tileCount
- * @param {Cartesian3} dimensions
- * @param {number} channelCount number of channels in the metadata. Must be 1 to 4.
- * @param {MetadataComponentType} componentType
- * @returns {number}
- */
- Megatexture.getApproximateTextureMemoryByteLength = function (
- tileCount,
- dimensions,
- channelCount,
- componentType
- ) {
- // TODO there's a lot of code duplicate with Megatexture constructor
- // Unsigned short textures not allowed in webgl 1, so treat as float
- if (componentType === MetadataComponentType.UNSIGNED_SHORT) {
- componentType = MetadataComponentType.FLOAT32;
- }
- const datatypeSizeInBytes = MetadataComponentType.getSizeInBytes(
- componentType
- );
- const voxelCountTotal =
- tileCount * dimensions.x * dimensions.y * dimensions.z;
- const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.z));
- const sliceCountPerRegionY = Math.ceil(dimensions.z / sliceCountPerRegionX);
- const voxelCountPerRegionX = sliceCountPerRegionX * dimensions.x;
- const voxelCountPerRegionY = sliceCountPerRegionY * dimensions.y;
- // Find the power of two that can fit all tile data, accounting for slices.
- // There's probably a non-iterative solution for this, but this is good enough for now.
- let textureDimension = CesiumMath.previousPowerOfTwo(
- Math.floor(Math.sqrt(voxelCountTotal))
- );
- for (;;) {
- const regionCountX = Math.floor(textureDimension / voxelCountPerRegionX);
- const regionCountY = Math.floor(textureDimension / voxelCountPerRegionY);
- const regionCount = regionCountX * regionCountY;
- if (regionCount >= tileCount) {
- break;
- } else {
- textureDimension *= 2;
- }
- }
- const textureMemoryByteLength =
- textureDimension * textureDimension * channelCount * datatypeSizeInBytes;
- return textureMemoryByteLength;
- };
- /**
- * @param {number} index
- * @param {Float32Array|Uint16Array|Uint8Array} data
- */
- Megatexture.prototype.writeDataToTexture = function (index, data) {
- // Unsigned short textures not allowed in webgl 1, so treat as float
- const tileData =
- data.constructor === Uint16Array ? new Float32Array(data) : data;
- const voxelDimensionsPerTile = this.voxelCountPerTile;
- const sliceDimensionsPerRegion = this.sliceCountPerRegion;
- const voxelDimensionsPerRegion = this.voxelCountPerRegion;
- const channelCount = this.channelCount;
- const tileVoxelData = this.tileVoxelDataTemp;
- for (let z = 0; z < voxelDimensionsPerTile.z; z++) {
- const sliceVoxelOffsetX =
- (z % sliceDimensionsPerRegion.x) * voxelDimensionsPerTile.x;
- const sliceVoxelOffsetY =
- Math.floor(z / sliceDimensionsPerRegion.x) * voxelDimensionsPerTile.y;
- for (let y = 0; y < voxelDimensionsPerTile.y; y++) {
- for (let x = 0; x < voxelDimensionsPerTile.x; x++) {
- const readIndex =
- z * voxelDimensionsPerTile.y * voxelDimensionsPerTile.x +
- y * voxelDimensionsPerTile.x +
- x;
- const writeIndex =
- (sliceVoxelOffsetY + y) * voxelDimensionsPerRegion.x +
- (sliceVoxelOffsetX + x);
- for (let c = 0; c < channelCount; c++) {
- tileVoxelData[writeIndex * channelCount + c] =
- tileData[readIndex * channelCount + c];
- }
- }
- }
- }
- const regionDimensionsPerMegatexture = this.regionCountPerMegatexture;
- const voxelWidth = voxelDimensionsPerRegion.x;
- const voxelHeight = voxelDimensionsPerRegion.y;
- const voxelOffsetX =
- (index % regionDimensionsPerMegatexture.x) * voxelDimensionsPerRegion.x;
- const voxelOffsetY =
- Math.floor(index / regionDimensionsPerMegatexture.x) *
- voxelDimensionsPerRegion.y;
- const source = {
- arrayBufferView: tileVoxelData,
- width: voxelWidth,
- height: voxelHeight,
- };
- const copyOptions = {
- source: source,
- xOffset: voxelOffsetX,
- yOffset: voxelOffsetY,
- };
- this.texture.copyFrom(copyOptions);
- };
- /**
- * Returns true if this object was destroyed; otherwise, false.
- * <br /><br />
- * If this object was destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
- *
- * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
- *
- * @see Megatexture#destroy
- */
- Megatexture.prototype.isDestroyed = function () {
- return false;
- };
- /**
- * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
- * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
- * <br /><br />
- * Once an object is destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
- * assign the return value (<code>undefined</code>) to the object as done in the example.
- *
- * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
- *
- * @see Megatexture#isDestroyed
- *
- * @example
- * megatexture = megatexture && megatexture.destroy();
- */
- Megatexture.prototype.destroy = function () {
- this.texture = this.texture && this.texture.destroy();
- return destroyObject(this);
- };
- export default Megatexture;
|