diff --git a/Apps/Sandcastle/gallery/VolumeCloud.html b/Apps/Sandcastle/gallery/VolumeCloud.html index f7f3b632d3cc..68c71d5cbfa2 100644 --- a/Apps/Sandcastle/gallery/VolumeCloud.html +++ b/Apps/Sandcastle/gallery/VolumeCloud.html @@ -327,9 +327,6 @@
MAX_COMBINED_TEXTURE_IMAGE_UNITS.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_COMBINED_TEXTURE_IMAGE_UNITS.
*/
maximumCombinedTextureImageUnits: {
get: function () {
@@ -45,10 +46,10 @@ Object.defineProperties(ContextLimits, {
/**
* The approximate maximum cube map width and height supported by this WebGL implementation.
- * The minimum is 16, but most desktop and laptop implementations will support much larger sizes like 8,192.
+ * The minimum is 2048, but most desktop and laptop implementations will support much larger sizes like 8,192.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_CUBE_MAP_TEXTURE_SIZE.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_CUBE_MAP_TEXTURE_SIZE.
*/
maximumCubeMapSize: {
get: function () {
@@ -58,10 +59,10 @@ Object.defineProperties(ContextLimits, {
/**
* The maximum number of vec4, ivec4, and bvec4
- * uniforms that can be used by a fragment shader with this WebGL implementation. The minimum is 16.
+ * uniforms that can be used by a fragment shader with this WebGL implementation. The minimum is 224.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_FRAGMENT_UNIFORM_VECTORS.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_FRAGMENT_UNIFORM_VECTORS.
*/
maximumFragmentUniformVectors: {
get: function () {
@@ -70,10 +71,10 @@ Object.defineProperties(ContextLimits, {
},
/**
- * The maximum number of texture units that can be used from the fragment shader with this WebGL implementation. The minimum is eight.
+ * The maximum number of texture units that can be used from the fragment shader with this WebGL implementation. The minimum is 16.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_TEXTURE_IMAGE_UNITS.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_TEXTURE_IMAGE_UNITS.
*/
maximumTextureImageUnits: {
get: function () {
@@ -83,10 +84,10 @@ Object.defineProperties(ContextLimits, {
/**
* The maximum renderbuffer width and height supported by this WebGL implementation.
- * The minimum is 16, but most desktop and laptop implementations will support much larger sizes like 8,192.
+ * The minimum is 2048, but most desktop and laptop implementations will support much larger sizes like 8,192.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_RENDERBUFFER_SIZE.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_RENDERBUFFER_SIZE.
*/
maximumRenderbufferSize: {
get: function () {
@@ -96,10 +97,10 @@ Object.defineProperties(ContextLimits, {
/**
* The approximate maximum texture width and height supported by this WebGL implementation.
- * The minimum is 64, but most desktop and laptop implementations will support much larger sizes like 8,192.
+ * The minimum is 2048, but most desktop and laptop implementations will support much larger sizes like 8,192.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_TEXTURE_SIZE.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_TEXTURE_SIZE.
*/
maximumTextureSize: {
get: function () {
@@ -107,12 +108,25 @@ Object.defineProperties(ContextLimits, {
},
},
+ /**
+ * The approximate maximum texture width, height, and depth supported by this WebGL implementation.
+ * The minimum is 256, but most desktop and laptop implementations will support much larger sizes like 2048.
+ * @memberof ContextLimits
+ * @type {number}
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_3D_TEXTURE_SIZE.
+ */
+ maximum3DTextureSize: {
+ get: function () {
+ return ContextLimits._maximum3DTextureSize;
+ },
+ },
+
/**
* The maximum number of vec4 varying variables supported by this WebGL implementation.
- * The minimum is eight. Matrices and arrays count as multiple vec4s.
+ * The minimum is 15. Matrices and arrays count as multiple vec4s.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VARYING_VECTORS.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_VARYING_VECTORS.
*/
maximumVaryingVectors: {
get: function () {
@@ -121,10 +135,10 @@ Object.defineProperties(ContextLimits, {
},
/**
- * The maximum number of vec4 vertex attributes supported by this WebGL implementation. The minimum is eight.
+ * The maximum number of vec4 vertex attributes supported by this WebGL implementation. The minimum is 16.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VERTEX_ATTRIBS.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_VERTEX_ATTRIBS.
*/
maximumVertexAttributes: {
get: function () {
@@ -134,10 +148,10 @@ Object.defineProperties(ContextLimits, {
/**
* The maximum number of texture units that can be used from the vertex shader with this WebGL implementation.
- * The minimum is zero, which means the GL does not support vertex texture fetch.
+ * The minimum is 16.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VERTEX_TEXTURE_IMAGE_UNITS.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_VERTEX_TEXTURE_IMAGE_UNITS.
*/
maximumVertexTextureImageUnits: {
get: function () {
@@ -147,10 +161,10 @@ Object.defineProperties(ContextLimits, {
/**
* The maximum number of vec4, ivec4, and bvec4
- * uniforms that can be used by a vertex shader with this WebGL implementation. The minimum is 16.
+ * uniforms that can be used by a vertex shader with this WebGL implementation. The minimum is 256.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VERTEX_UNIFORM_VECTORS.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_VERTEX_UNIFORM_VECTORS.
*/
maximumVertexUniformVectors: {
get: function () {
@@ -162,7 +176,7 @@ Object.defineProperties(ContextLimits, {
* The minimum aliased line width, in pixels, supported by this WebGL implementation. It will be at most one.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_LINE_WIDTH_RANGE.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with ALIASED_LINE_WIDTH_RANGE.
*/
minimumAliasedLineWidth: {
get: function () {
@@ -174,7 +188,7 @@ Object.defineProperties(ContextLimits, {
* The maximum aliased line width, in pixels, supported by this WebGL implementation. It will be at least one.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_LINE_WIDTH_RANGE.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with ALIASED_LINE_WIDTH_RANGE.
*/
maximumAliasedLineWidth: {
get: function () {
@@ -186,7 +200,7 @@ Object.defineProperties(ContextLimits, {
* The minimum aliased point size, in pixels, supported by this WebGL implementation. It will be at most one.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_POINT_SIZE_RANGE.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with ALIASED_POINT_SIZE_RANGE.
*/
minimumAliasedPointSize: {
get: function () {
@@ -198,7 +212,7 @@ Object.defineProperties(ContextLimits, {
* The maximum aliased point size, in pixels, supported by this WebGL implementation. It will be at least one.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_POINT_SIZE_RANGE.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with ALIASED_POINT_SIZE_RANGE.
*/
maximumAliasedPointSize: {
get: function () {
@@ -210,7 +224,7 @@ Object.defineProperties(ContextLimits, {
* The maximum supported width of the viewport. It will be at least as large as the visible width of the associated canvas.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VIEWPORT_DIMS.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_VIEWPORT_DIMS.
*/
maximumViewportWidth: {
get: function () {
@@ -222,7 +236,7 @@ Object.defineProperties(ContextLimits, {
* The maximum supported height of the viewport. It will be at least as large as the visible height of the associated canvas.
* @memberof ContextLimits
* @type {number}
- * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VIEWPORT_DIMS.
+ * @see {@link https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml|glGet} with MAX_VIEWPORT_DIMS.
*/
maximumViewportHeight: {
get: function () {
diff --git a/packages/engine/Source/Renderer/ShaderSource.js b/packages/engine/Source/Renderer/ShaderSource.js
index 16d405fae149..413326414894 100644
--- a/packages/engine/Source/Renderer/ShaderSource.js
+++ b/packages/engine/Source/Renderer/ShaderSource.js
@@ -153,14 +153,11 @@ function getBuiltinsAndAutomaticUniforms(shaderSource) {
}
function combineShader(shaderSource, isFragmentShader, context) {
- let i;
- let length;
-
// Combine shader sources, generally for pseudo-polymorphism, e.g., czm_getMaterial.
let combinedSources = "";
const sources = shaderSource.sources;
if (defined(sources)) {
- for (i = 0, length = sources.length; i < length; ++i) {
+ for (let i = 0; i < sources.length; ++i) {
// #line needs to be on its own line.
combinedSources += `\n#line 0\n${sources[i]}`;
}
@@ -225,30 +222,34 @@ function combineShader(shaderSource, isFragmentShader, context) {
let result = "";
const extensionsLength = extensions.length;
- for (i = 0; i < extensionsLength; i++) {
+ for (let i = 0; i < extensionsLength; i++) {
result += extensions[i];
}
if (isFragmentShader) {
- // If high precision isn't support replace occurrences of highp with mediump
- // The highp keyword is not always available on older mobile devices
+ // If high precision isn't supported, replace occurrences of highp with mediump.
+ // The highp keyword is not always available on older mobile devices.
// See https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#In_WebGL_1_highp_float_support_is_optional_in_fragment_shaders
- result +=
- "\
-#ifdef GL_FRAGMENT_PRECISION_HIGH\n\
- precision highp float;\n\
- precision highp int;\n\
-#else\n\
- precision mediump float;\n\
- precision mediump int;\n\
- #define highp mediump\n\
-#endif\n\n";
+ result += `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+ precision highp float;
+ precision highp int;
+#else
+ precision mediump float;
+ precision mediump int;
+ #define highp mediump
+#endif
+`;
+ }
+
+ if (context.webgl2) {
+ result += `precision highp sampler3D;\n\n`;
}
// Prepend #defines for uber-shaders
const defines = shaderSource.defines;
if (defined(defines)) {
- for (i = 0, length = defines.length; i < length; ++i) {
+ for (let i = 0, length = defines.length; i < length; ++i) {
const define = defines[i];
if (define.length !== 0) {
result += `#define ${define}\n`;
diff --git a/packages/engine/Source/Renderer/Texture.js b/packages/engine/Source/Renderer/Texture.js
index 314b4432d29c..4b521a720e60 100644
--- a/packages/engine/Source/Renderer/Texture.js
+++ b/packages/engine/Source/Renderer/Texture.js
@@ -398,7 +398,7 @@ function loadBufferSource(texture, source) {
* @param {number} xOffset The texel x coordinate of the lower left corner of the subregion of the texture to be updated.
* @param {number} yOffset The texel y coordinate of the lower left corner of the subregion of the texture to be updated.
* @param {number} width The width of the source data, in pixels.
- * @param {number} width The height of the source data, in pixels.
+ * @param {number} height The height of the source data, in pixels.
*
* @private
*/
diff --git a/packages/engine/Source/Renderer/Texture3D.js b/packages/engine/Source/Renderer/Texture3D.js
index 9e6813fa1f85..ec7d0c3b37b1 100644
--- a/packages/engine/Source/Renderer/Texture3D.js
+++ b/packages/engine/Source/Renderer/Texture3D.js
@@ -68,7 +68,7 @@ function Texture3D(options) {
sampler = new Sampler(),
} = options;
- if (!context.webgl2) {
+ if (!context.webgl2 && !defined(context.options.getWebGLStub)) {
throw new DeveloperError(
"WebGL1 does not support texture3D. Please use a WebGL2 context.",
);
@@ -113,25 +113,25 @@ function Texture3D(options) {
Check.typeOf.number.greaterThan("width", width, 0);
- if (width > ContextLimits.maximumTextureSize) {
+ if (width > ContextLimits.maximum3DTextureSize) {
throw new DeveloperError(
- `Width must be less than or equal to the maximum texture3D size (${ContextLimits.maximumTextureSize}). Check maximumTextureSize.`,
+ `Width must be less than or equal to the maximum texture3D size (${ContextLimits.maximum3DTextureSize}). Check maximum3DTextureSize.`,
);
}
Check.typeOf.number.greaterThan("height", height, 0);
- if (height > ContextLimits.maximumTextureSize) {
+ if (height > ContextLimits.maximum3DTextureSize) {
throw new DeveloperError(
- `Height must be less than or equal to the maximum texture3D size (${ContextLimits.maximumTextureSize}). Check maximumTextureSize.`,
+ `Height must be less than or equal to the maximum texture3D size (${ContextLimits.maximum3DTextureSize}). Check maximum3DTextureSize.`,
);
}
Check.typeOf.number.greaterThan("depth", depth, 0);
- if (depth > ContextLimits.maximumTextureSize) {
+ if (depth > ContextLimits.maximum3DTextureSize) {
throw new DeveloperError(
- `Depth must be less than or equal to the maximum texture3D size (${ContextLimits.maximumTextureSize}). Check maximumTextureSize.`,
+ `Depth must be less than or equal to the maximum texture3D size (${ContextLimits.maximum3DTextureSize}). Check maximum3DTextureSize.`,
);
}
@@ -333,6 +333,164 @@ function loadBufferSource(texture3D, source) {
}
}
+/**
+ * Copy new image data into this texture, from a source object with width, height, depth, and arrayBufferView properties.
+ * @param {object} options Object with the following properties:
+ * @param {object} options.source The source object with width, height, depth, and arrayBufferView properties.
+ * @param {number} [options.xOffset=0] The offset in the x direction within the texture to copy into.
+ * @param {number} [options.yOffset=0] The offset in the y direction within the texture to copy into.
+ * @param {number} [options.zOffset=0] The offset in the z direction within the texture to copy into.
+ * @param {boolean} [options.skipColorSpaceConversion=false] If true, any custom gamma or color profiles in the texture will be ignored.
+ *
+ * @exception {DeveloperError} Cannot call copyFrom with a compressed texture pixel format.
+ * @exception {DeveloperError} xOffset must be greater than or equal to zero.
+ * @exception {DeveloperError} yOffset must be greater than or equal to zero.
+ * @exception {DeveloperError} zOffset must be greater than or equal to zero.
+ * @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
+ * @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
+ * @exception {DeveloperError} zOffset + source.depth must be less than or equal to depth.
+ * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
+ * @private
+ * @example
+ * texture.copyFrom({
+ * source: {
+ * width : 1,
+ * height : 1,
+ * depth : 1,
+ * arrayBufferView : new Uint8Array([255, 0, 0, 255])
+ * }
+ * });
+ */
+Texture3D.prototype.copyFrom = function (options) {
+ options = options ?? Frozen.EMPTY_OBJECT;
+
+ const { source, xOffset = 0, yOffset = 0, zOffset = 0 } = options;
+
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("options.source", source);
+ Check.defined("options.source.arrayBufferView", source.arrayBufferView);
+ if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
+ throw new DeveloperError(
+ "Cannot call copyFrom with a compressed texture pixel format.",
+ );
+ }
+ Check.typeOf.number.greaterThanOrEquals("xOffset", xOffset, 0);
+ Check.typeOf.number.greaterThanOrEquals("yOffset", yOffset, 0);
+ Check.typeOf.number.greaterThanOrEquals("zOffset", zOffset, 0);
+ Check.typeOf.number.lessThanOrEquals(
+ "xOffset + options.source.width",
+ xOffset + source.width,
+ this._width,
+ );
+ Check.typeOf.number.lessThanOrEquals(
+ "yOffset + options.source.height",
+ yOffset + source.height,
+ this._height,
+ );
+ Check.typeOf.number.lessThanOrEquals(
+ "zOffset + options.source.depth",
+ zOffset + source.depth,
+ this._depth,
+ );
+ //>>includeEnd('debug');
+
+ const context = this._context;
+ const gl = context._gl;
+ const target = this._textureTarget;
+
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(target, this._texture);
+
+ const { width, height, depth } = source;
+ let uploaded = false;
+ if (!this._initialized) {
+ if (
+ xOffset === 0 &&
+ yOffset === 0 &&
+ zOffset === 0 &&
+ width === this._width &&
+ height === this._height &&
+ depth === this._depth
+ ) {
+ loadBufferSource(this, source);
+ uploaded = true;
+ } else {
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
+ loadNull(this);
+ }
+ this._initialized = true;
+ }
+
+ if (!uploaded) {
+ loadPartialBufferSource(
+ this,
+ source.arrayBufferView,
+ xOffset,
+ yOffset,
+ zOffset,
+ width,
+ height,
+ depth,
+ );
+ }
+
+ gl.bindTexture(target, null);
+};
+
+/**
+ * Load texel data from a buffer into part of a 3D texture
+ *
+ * @param {Texture3D} texture3D The texture3D to which texel values will be loaded.
+ * @param {TypedArray} arrayBufferView The texel values to be loaded into the texture3D.
+ * @param {number} xOffset The texel x coordinate of the lower left corner of the subregion of the texture to be updated.
+ * @param {number} yOffset The texel y coordinate of the lower left corner of the subregion of the texture to be updated.
+ * @param {number} zOffset The texel z coordinate of the lower left corner of the subregion of the texture to be updated.
+ * @param {number} width The width of the source data, in pixels.
+ * @param {number} height The height of the source data, in pixels.
+ * @param {number} depth The depth of the source data, in pixels.
+ *
+ * @private
+ */
+function loadPartialBufferSource(
+ texture3D,
+ arrayBufferView,
+ xOffset,
+ yOffset,
+ zOffset,
+ width,
+ height,
+ depth,
+) {
+ const context = texture3D._context;
+ const gl = context._gl;
+
+ const { pixelFormat, pixelDatatype } = texture3D;
+
+ const unpackAlignment = PixelFormat.alignmentInBytes(
+ pixelFormat,
+ pixelDatatype,
+ width,
+ );
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
+
+ gl.texSubImage3D(
+ texture3D._textureTarget,
+ 0,
+ xOffset,
+ yOffset,
+ zOffset,
+ width,
+ height,
+ depth,
+ pixelFormat,
+ PixelDatatype.toWebGLConstant(pixelDatatype, context),
+ arrayBufferView,
+ );
+}
+
/**
* Compute a dimension of the image for the next mip level.
*
diff --git a/packages/engine/Source/Scene/Megatexture.js b/packages/engine/Source/Scene/Megatexture.js
index 9976f7c0e3ab..3686f7b48160 100644
--- a/packages/engine/Source/Scene/Megatexture.js
+++ b/packages/engine/Source/Scene/Megatexture.js
@@ -1,17 +1,15 @@
-import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
-import ComponentDatatype from "../Core/ComponentDatatype.js";
+import Check from "../Core/Check.js";
import ContextLimits from "../Renderer/ContextLimits.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 Texture3D from "../Renderer/Texture3D.js";
import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js";
import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
import TextureWrap from "../Renderer/TextureWrap.js";
@@ -33,52 +31,33 @@ function Megatexture(
dimensions,
channelCount,
componentType,
- availableTextureMemoryBytes,
+ availableTextureMemoryBytes = 134217728,
+ tileCount,
) {
- const maximumTextureMemoryByteLength = 512 * 1024 * 1024;
- availableTextureMemoryBytes = Math.min(
- availableTextureMemoryBytes ?? 128 * 1024 * 1024,
- maximumTextureMemoryByteLength,
- );
-
// 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;
- }
-
- if (
- componentType === MetadataComponentType.FLOAT32 &&
- !context.floatingPointTexture
- ) {
- throw new RuntimeError("Floating point texture not supported");
- }
-
const pixelDataType = getPixelDataType(componentType);
- const pixelFormat = getPixelFormat(channelCount, context.webgl2);
- const componentTypeByteLength =
- MetadataComponentType.getSizeInBytes(componentType);
- const textureDimension = getTextureDimension(
+ const pixelFormat = getPixelFormat(channelCount);
+
+ const bytesPerSample =
+ channelCount * MetadataComponentType.getSizeInBytes(componentType);
+ const textureDimension = Megatexture.get3DTextureDimension(
+ dimensions,
+ bytesPerSample,
availableTextureMemoryBytes,
- channelCount,
- componentTypeByteLength,
+ tileCount,
);
+ if (Cartesian3.equals(textureDimension, Cartesian3.ZERO)) {
+ throw new RuntimeError(
+ "Not enough texture memory available to create a megatexture with the given tile dimensions.",
+ );
+ }
- 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,
+ const tileCounts = Cartesian3.divideComponents(
+ textureDimension,
+ dimensions,
+ new Cartesian3(),
);
- if (regionCountPerMegatextureX === 0 || regionCountPerMegatextureY === 0) {
- throw new RuntimeError("Tileset is too large to fit into megatexture");
- }
-
/**
* @type {number}
* @readonly
@@ -96,87 +75,43 @@ function Megatexture(
* @readonly
*/
this.textureMemoryByteLength =
- componentTypeByteLength * channelCount * textureDimension ** 2;
+ bytesPerSample *
+ textureDimension.x *
+ textureDimension.y *
+ textureDimension.z;
/**
* @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,
- );
+ this.tileCounts = Cartesian3.clone(tileCounts, new Cartesian3());
/**
- * @type {Cartesian2}
+ * @type {Cartesian3}
* @readonly
*/
- this.sliceSizeUv = new Cartesian2(
- dimensions.x / textureDimension,
- dimensions.y / textureDimension,
- );
+ this.voxelCountPerTile = Cartesian3.clone(dimensions, new Cartesian3());
/**
- * @type {Cartesian2}
+ * @type {number}
* @readonly
*/
- this.regionSizeUv = new Cartesian2(
- voxelCountPerRegionX / textureDimension,
- voxelCountPerRegionY / textureDimension,
- );
+ this.maximumTileCount = tileCounts.x * tileCounts.y * tileCounts.z;
/**
- * @type {Texture}
+ * @type {Texture3D}
* @readonly
*/
- this.texture = new Texture({
+ this.texture = new Texture3D({
context: context,
pixelFormat: pixelFormat,
pixelDatatype: pixelDataType,
flipY: false,
- width: textureDimension,
- height: textureDimension,
+ width: textureDimension.x,
+ height: textureDimension.y,
+ depth: textureDimension.z,
sampler: new Sampler({
+ wrapR: TextureWrap.CLAMP_TO_EDGE,
wrapS: TextureWrap.CLAMP_TO_EDGE,
wrapT: TextureWrap.CLAMP_TO_EDGE,
minificationFilter: TextureMinificationFilter.LINEAR,
@@ -184,17 +119,6 @@ function Megatexture(
}),
});
- const componentDatatype =
- MetadataComponentType.toComponentDatatype(componentType);
-
- /**
- * @type {Array}
- */
- this.tileVoxelDataTemp = ComponentDatatype.createTypedArray(
- componentDatatype,
- voxelCountPerRegionX * voxelCountPerRegionY * channelCount,
- );
-
/**
* @type {MegatextureNode[]}
* @readonly
@@ -229,8 +153,42 @@ function Megatexture(
* @readonly
*/
this.occupiedCount = 0;
+
+ this._nearestSampling = false;
}
+Object.defineProperties(Megatexture.prototype, {
+ /**
+ * Gets or sets the nearest sampling flag.
+ * @type {boolean}
+ */
+ nearestSampling: {
+ get: function () {
+ return this._nearestSampling;
+ },
+ set: function (nearestSampling) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("nearestSampling", nearestSampling);
+ //>>includeEnd('debug');
+ if (this._nearestSampling === nearestSampling) {
+ return;
+ }
+ if (nearestSampling) {
+ this.texture.sampler = Sampler.NEAREST;
+ } else {
+ this.texture.sampler = new Sampler({
+ wrapR: TextureWrap.CLAMP_TO_EDGE,
+ wrapS: TextureWrap.CLAMP_TO_EDGE,
+ wrapT: TextureWrap.CLAMP_TO_EDGE,
+ minificationFilter: TextureMinificationFilter.LINEAR,
+ magnificationFilter: TextureMagnificationFilter.LINEAR,
+ });
+ }
+ this._nearestSampling = nearestSampling;
+ },
+ },
+});
+
/**
* Get the pixel data type to use in a megatexture.
* TODO support more
@@ -255,49 +213,23 @@ function getPixelDataType(componentType) {
* Get the pixel format to use for a megatexture.
*
* @param {number} channelCount The number of channels in the metadata. Must be 1 to 4.
- * @param {boolean} webgl2 true if the context is using webgl2
* @returns {PixelFormat} The pixel format to use for a megatexture.
*
* @private
*/
-function getPixelFormat(channelCount, webgl2) {
- if (channelCount === 1) {
- return webgl2 ? PixelFormat.RED : PixelFormat.LUMINANCE;
- } else if (channelCount === 2) {
- return webgl2 ? PixelFormat.RG : PixelFormat.LUMINANCE_ALPHA;
- } else if (channelCount === 3) {
- return PixelFormat.RGB;
- } else if (channelCount === 4) {
- return PixelFormat.RGBA;
+function getPixelFormat(channelCount) {
+ switch (channelCount) {
+ case 1:
+ return PixelFormat.RED;
+ case 2:
+ return PixelFormat.RG;
+ case 3:
+ return PixelFormat.RGB;
+ case 4:
+ return PixelFormat.RGBA;
}
}
-/**
- * Compute the largest size of a square texture that will fit in the available memory.
- *
- * @param {number} availableTextureMemoryBytes An upper limit on the texture memory size.
- * @param {number} channelCount The number of metadata channels per texel.
- * @param {number} componentByteLength The byte length of each component of the metadata.
- * @returns {number} The dimension of the square texture to use for the megatexture.
- *
- * @private
- */
-function getTextureDimension(
- availableTextureMemoryBytes,
- channelCount,
- componentByteLength,
-) {
- // Compute how many texels will fit in the available memory
- const texelCount = Math.floor(
- availableTextureMemoryBytes / (channelCount * componentByteLength),
- );
- // Return the largest power of two texture size that will fit in memory
- return Math.min(
- ContextLimits.maximumTextureSize,
- CesiumMath.previousPowerOfTwo(Math.floor(Math.sqrt(texelCount))),
- );
-}
-
/**
* @alias MegatextureNode
* @constructor
@@ -389,130 +321,173 @@ Megatexture.prototype.isFull = function () {
};
/**
- * @param {number} tileCount The total number of tiles in the tileset.
- * @param {Cartesian3} dimensions The number of voxels in each dimension of the tile.
- * @param {number} channelCount The number of channels in the metadata.
- * @param {MetadataComponentType} componentType The type of one channel of the metadata.
- * @returns {number}
+ * Compute a 3D texture dimension that contains the given number of tiles, or as many tiles as can fit within the available texture memory.
+ * @param {Cartesian3} tileDimensions The dimensions of one tile in number of voxels.
+ * @param {number} bytesPerSample The number of bytes per voxel sample.
+ * @param {number} availableTextureMemoryBytes An upper limit on the texture memory size in bytes.
+ * @param {number} [tileCount] The total number of tiles in the tileset.
+ * @returns {Cartesian3} The computed 3D texture dimensions.
*/
-Megatexture.getApproximateTextureMemoryByteLength = function (
+Megatexture.get3DTextureDimension = function (
+ tileDimensions,
+ bytesPerSample,
+ availableTextureMemoryBytes,
tileCount,
- dimensions,
- channelCount,
- componentType,
) {
- // TODO there's a lot of code duplicate with Megatexture constructor
+ const textureDimension = new Cartesian3();
+ const { maximum3DTextureSize } = ContextLimits;
+
+ // Find the number of tiles we can fit.
+ const tileSizeInBytes =
+ bytesPerSample * tileDimensions.x * tileDimensions.y * tileDimensions.z;
+ const maxTileCount = Math.floor(
+ availableTextureMemoryBytes / tileSizeInBytes,
+ );
+ if (maxTileCount < 1) {
+ return textureDimension;
+ }
- // Unsigned short textures not allowed in webgl 1, so treat as float
- if (componentType === MetadataComponentType.UNSIGNED_SHORT) {
- componentType = MetadataComponentType.FLOAT32;
+ if (defined(tileCount)) {
+ tileCount = Math.min(tileCount, maxTileCount);
+ } else {
+ tileCount = maxTileCount;
}
- const datatypeSizeInBytes =
- MetadataComponentType.getSizeInBytes(componentType);
- const voxelCountTotal =
- tileCount * dimensions.x * dimensions.y * dimensions.z;
+ // Sort the tile dimensions from smallest to largest.
+ const sortedDimensions = Object.entries(tileDimensions).sort(
+ (a, b) => a[1] - b[1],
+ );
- 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;
+ // Try arranging all tiles in a single column along the axis of the smallest tile dimension.
+ const singleColumnLength = sortedDimensions[0][1] * tileCount;
+ if (singleColumnLength <= maximum3DTextureSize) {
+ textureDimension[sortedDimensions[0][0]] = singleColumnLength;
+ textureDimension[sortedDimensions[1][0]] = sortedDimensions[1][1];
+ textureDimension[sortedDimensions[2][0]] = sortedDimensions[2][1];
+ return textureDimension;
+ }
- // 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;
+ // Find a nearby number with no prime factor larger than 7.
+ const factors = findFactorsOfNearbyComposite(tileCount, maxTileCount);
+
+ // Split these factors into the three dimensions, keeping the total texture size
+ // smaller than the maximum in each dimension.
+ for (let i = 0; i < 3; i++) {
+ const [axis, length] = sortedDimensions[i];
+ const maxTileCountAlongAxis = Math.floor(maximum3DTextureSize / length);
+ const axisFactors = getDimensionFromFactors(factors, maxTileCountAlongAxis);
+ textureDimension[axis] = getProductOfFactors(axisFactors) * length;
+ // Remove used factors.
+ for (let j = 0; j < factors.length; j++) {
+ factors[j] -= axisFactors[j];
}
}
- const textureMemoryByteLength =
- textureDimension * textureDimension * channelCount * datatypeSizeInBytes;
- return textureMemoryByteLength;
+ return textureDimension;
};
/**
- * Write an array of tile metadata to the megatexture.
- * @param {number} index The index of the tile's location in the megatexture.
- * @param {Float32Array|Uint16Array|Uint8Array} data The data to be written.
+ * Approximate an integer by a product of small prime factors (2, 3, 5, and 7).
+ * @private
+ * @param {number} n The integer to be approximated
+ * @param {number} maxN A maximum integer which the approximation should not exceed
+ * @returns {number[]} The exponents of the prime factors 2, 3, 5, and 7 whose product is the approximation of n
*/
-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 {
- tileVoxelDataTemp,
- voxelCountPerTile,
- sliceCountPerRegion,
- voxelCountPerRegion,
- channelCount,
- regionCountPerMegatexture,
- } = this;
-
- for (let z = 0; z < voxelCountPerTile.z; z++) {
- const sliceVoxelOffsetX = (z % sliceCountPerRegion.x) * voxelCountPerTile.x;
- const sliceVoxelOffsetY =
- Math.floor(z / sliceCountPerRegion.x) * voxelCountPerTile.y;
- for (let y = 0; y < voxelCountPerTile.y; y++) {
- const readOffset = getReadOffset(voxelCountPerTile, y, z);
- const writeOffset =
- (sliceVoxelOffsetY + y) * voxelCountPerRegion.x + sliceVoxelOffsetX;
- for (let x = 0; x < voxelCountPerTile.x; x++) {
- const readIndex = readOffset + x;
- const writeIndex = writeOffset + x;
- for (let c = 0; c < channelCount; c++) {
- tileVoxelDataTemp[writeIndex * channelCount + c] =
- tileData[readIndex * channelCount + c];
- }
- }
- }
+function findFactorsOfNearbyComposite(n, maxN) {
+ n = Math.min(n, maxN);
+ if (Math.floor(n) !== n) {
+ throw new DeveloperError("n and maxN must be integers");
+ } else if (n < 1) {
+ throw new DeveloperError("n and maxN must be at least 1");
+ }
+ switch (n) {
+ case 1:
+ return [0, 0, 0, 0];
+ case 2:
+ return [1, 0, 0, 0];
+ case 3:
+ return [0, 1, 0, 0];
+ }
+ const log2n = Math.floor(Math.log2(n));
+ const previousPowerOfTwo = 2 ** log2n;
+ const residual = n - previousPowerOfTwo;
+ const interval = 2 ** (log2n - 2);
+ let intervalIndex = Math.ceil(residual / interval);
+ const nextComposite = previousPowerOfTwo + intervalIndex * interval;
+ if (nextComposite > maxN) {
+ // Use the previous composite instead
+ intervalIndex--;
+ }
+ switch (intervalIndex) {
+ case 0:
+ // previousPowerOfTwo
+ return [log2n, 0, 0, 0];
+ case 1:
+ // previousPowerOfTwo * 5 / 4
+ return [log2n - 2, 0, 1, 0];
+ case 2:
+ // previousPowerOfTwo * 6 / 4
+ return [log2n - 1, 1, 0, 0];
+ case 3:
+ // previousPowerOfTwo * 7 / 4
+ return [log2n - 2, 0, 0, 1];
+ }
+}
+
+function getDimensionFromFactors(factorPowers, maxDimension) {
+ const maxPowerOfTwo = Math.floor(Math.log2(maxDimension));
+ const log2n = Math.min(factorPowers[0], maxPowerOfTwo);
+ const remainingDimension = maxDimension / 2 ** log2n;
+ if (remainingDimension >= 7 / 4 && factorPowers[3] > 0) {
+ return [log2n - 2, 0, 0, 1];
+ } else if (remainingDimension >= 6 / 4 && factorPowers[1] > 0) {
+ return [log2n - 1, 1, 0, 0];
+ } else if (remainingDimension >= 5 / 4 && factorPowers[2] > 0) {
+ return [log2n - 2, 0, 1, 0];
+ }
+ return [log2n, 0, 0, 0];
+}
+
+function getProductOfFactors(factorPowers) {
+ let product = 1;
+ const primeFactors = [2, 3, 5, 7];
+ for (let i = 0; i < primeFactors.length; i++) {
+ product *= primeFactors[i] ** factorPowers[i];
}
+ return product;
+}
- const voxelOffsetX =
- (index % regionCountPerMegatexture.x) * voxelCountPerRegion.x;
- const voxelOffsetY =
- Math.floor(index / regionCountPerMegatexture.x) * voxelCountPerRegion.y;
+/**
+ * Write an array of tile metadata to the megatexture.
+ * @param {number} index The index of the tile's location in the megatexture.
+ * @param {Float32Array|Uint16Array|Uint8Array} tileData The data to be written.
+ */
+Megatexture.prototype.writeDataToTexture = function (index, tileData) {
+ const { tileCounts, voxelCountPerTile } = this;
const source = {
- arrayBufferView: tileVoxelDataTemp,
- width: voxelCountPerRegion.x,
- height: voxelCountPerRegion.y,
+ arrayBufferView: tileData,
+ width: voxelCountPerTile.x,
+ height: voxelCountPerTile.y,
+ depth: voxelCountPerTile.z,
};
+ const tilesPerZ = tileCounts.x * tileCounts.y;
+ const iz = Math.floor(index / tilesPerZ);
+ const remainder = index - iz * tilesPerZ;
+ const iy = Math.floor(remainder / tileCounts.x);
+ const ix = remainder - iy * tileCounts.x;
+
const copyOptions = {
source: source,
- xOffset: voxelOffsetX,
- yOffset: voxelOffsetY,
+ xOffset: ix * voxelCountPerTile.x,
+ yOffset: iy * voxelCountPerTile.y,
+ zOffset: iz * voxelCountPerTile.z,
};
this.texture.copyFrom(copyOptions);
};
-/**
- * Get the offset into the data array for a given row of contiguous voxel data.
- *
- * @param {Cartesian3} dimensions The number of voxels in each dimension of the tile.
- * @param {number} y The y index of the voxel row
- * @param {number} z The z index of the voxel row
- * @returns {number} The offset into the data array
- * @private
- */
-function getReadOffset(dimensions, y, z) {
- const voxelsPerInputSlice = dimensions.y * dimensions.x;
- const sliceIndex = z;
- const rowIndex = y;
- return sliceIndex * voxelsPerInputSlice + rowIndex * dimensions.x;
-}
-
/**
* Returns true if this object was destroyed; otherwise, false.
*