diff --git a/examples/src/examples/graphics/texture-array.example.mjs b/examples/src/examples/graphics/texture-array.example.mjs index fff6f760c96..5559ef76bbf 100644 --- a/examples/src/examples/graphics/texture-array.example.mjs +++ b/examples/src/examples/graphics/texture-array.example.mjs @@ -131,10 +131,11 @@ assetListLoader.load(() => { const textureArrayOptions = { name: 'textureArrayImages', + dimension: pc.TEXTUREDIMENSION_2D_ARRAY, format: pc.PIXELFORMAT_R8_G8_B8_A8, width: 1024, height: 1024, - arrayLength: 4, // array texture with 4 textures + slices: 4, // array texture with 4 textures magFilter: pc.FILTER_NEAREST, minFilter: pc.FILTER_NEAREST_MIPMAP_NEAREST, mipmaps: true, @@ -157,7 +158,7 @@ assetListLoader.load(() => { const mipmaps = generateMipmaps(textureArrayOptions.width, textureArrayOptions.height); const levels = mipmaps.map((data) => { const textures = []; - for (let i = 0; i < textureArrayOptions.arrayLength; i++) { + for (let i = 0; i < textureArrayOptions.slices; i++) { textures.push(data); } return textures; diff --git a/src/framework/handlers/texture.js b/src/framework/handlers/texture.js index 4d3c6ac2d66..329f4fe2b00 100644 --- a/src/framework/handlers/texture.js +++ b/src/framework/handlers/texture.js @@ -60,11 +60,11 @@ const _completePartialMipmapChain = function (texture) { if (!(texture._format === PIXELFORMAT_RGBA8 || texture._format === PIXELFORMAT_RGBA32F) || - texture._volume || + texture.volume || texture._compressed || texture._levels.length === 1 || texture._levels.length === requiredMipLevels || - isHtmlElement(texture._cubemap ? texture._levels[0][0] : texture._levels[0])) { + isHtmlElement(texture.cubemap ? texture._levels[0][0] : texture._levels[0])) { return; } @@ -98,7 +98,7 @@ const _completePartialMipmapChain = function (texture) { for (let level = texture._levels.length; level < requiredMipLevels; ++level) { const width = Math.max(1, texture._width >> (level - 1)); const height = Math.max(1, texture._height >> (level - 1)); - if (texture._cubemap) { + if (texture.cubemap) { const mips = []; for (let face = 0; face < 6; ++face) { mips.push(downsample(width, height, texture._levels[level - 1][face])); @@ -109,7 +109,7 @@ const _completePartialMipmapChain = function (texture) { } } - texture._levelsUpdated = texture._cubemap ? [[true, true, true, true, true, true]] : [true]; + texture._levelsUpdated = texture.cubemap ? [[true, true, true, true, true, true]] : [true]; }; /** diff --git a/src/framework/xr/xr-view.js b/src/framework/xr/xr-view.js index 94d4cd6e564..8d457cf2a5d 100644 --- a/src/framework/xr/xr-view.js +++ b/src/framework/xr/xr-view.js @@ -168,7 +168,8 @@ class XrView extends EventHandler { if (this._manager.views.supportedDepth && this._manager.views.availableDepth) { this._textureDepth = new Texture(device, { format: this._manager.views.depthPixelFormat, - arrayLength: (viewsCount === 1) ? 0 : viewsCount, + array: viewsCount > 1, + slices: viewsCount, mipmaps: false, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE, diff --git a/src/platform/graphics/texture-utils.js b/src/platform/graphics/texture-utils.js index b1aed8592bd..c0fee74f848 100644 --- a/src/platform/graphics/texture-utils.js +++ b/src/platform/graphics/texture-utils.js @@ -1,7 +1,8 @@ import { Debug } from '../../core/debug.js'; import { pixelFormatInfo, - PIXELFORMAT_PVRTC_2BPP_RGB_1, PIXELFORMAT_PVRTC_2BPP_RGBA_1 + PIXELFORMAT_PVRTC_2BPP_RGB_1, PIXELFORMAT_PVRTC_2BPP_RGBA_1, + TEXTUREDIMENSION_3D } from './constants.js'; /** @@ -26,7 +27,7 @@ class TextureUtils { * * @param {number} width - Texture's width. * @param {number} height - Texture's height. - * @param {number} [depth] - Texture's depth. Defaults to 1. + * @param {number} [depth] - Texture's depth slices. Defaults to 1. * @returns {number} The number of mip levels required for the texture. */ static calcMipLevelsCount(width, height, depth = 1) { @@ -38,7 +39,7 @@ class TextureUtils { * * @param {number} width - Texture's width. * @param {number} height - Texture's height. - * @param {number} depth - Texture's depth. + * @param {number} depth - Texture's depth slices. * @param {number} format - Texture's pixel format PIXELFORMAT_***. * @returns {number} The number of bytes of GPU memory required for the texture. * @ignore @@ -69,17 +70,18 @@ class TextureUtils { /** * Calculate the GPU memory required for a texture. * + * @param {string} dimension - Texture's dimension * @param {number} width - Texture's width. * @param {number} height - Texture's height. - * @param {number} depth - Texture's depth. + * @param {number} slices - Texture's slices. * @param {number} format - Texture's pixel format PIXELFORMAT_***. * @param {boolean} mipmaps - True if the texture includes mipmaps, false otherwise. - * @param {boolean} cubemap - True is the texture is a cubemap, false otherwise. * @returns {number} The number of bytes of GPU memory required for the texture. * @ignore */ - static calcGpuSize(width, height, depth, format, mipmaps, cubemap) { + static calcGpuSize(width, height, slices, format, isVolume, mipmaps) { let result = 0; + let depth = isVolume ? slices : 1; while (1) { result += TextureUtils.calcLevelGpuSize(width, height, depth, format); @@ -93,7 +95,7 @@ class TextureUtils { depth = Math.max(depth >> 1, 1); } - return result * (cubemap ? 6 : 1); + return result * (isVolume ? 1 : slices); } } diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index eb7814cd07e..1e5c5b57a68 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -15,7 +15,11 @@ import { TEXTURELOCK_WRITE, TEXTUREPROJECTION_NONE, TEXTUREPROJECTION_CUBE, TEXTURETYPE_DEFAULT, TEXTURETYPE_RGBM, TEXTURETYPE_RGBE, TEXTURETYPE_RGBP, - isIntegerPixelFormat, FILTER_NEAREST, TEXTURELOCK_NONE, TEXTURELOCK_READ + isIntegerPixelFormat, FILTER_NEAREST, TEXTURELOCK_NONE, TEXTURELOCK_READ, + TEXTUREDIMENSION_2D, + TEXTUREDIMENSION_3D, + TEXTUREDIMENSION_2D_ARRAY, + TEXTUREDIMENSION_CUBE } from './constants.js'; let id = 0; @@ -101,7 +105,15 @@ class Texture { * @param {string} [options.name] - The name of the texture. Defaults to null. * @param {number} [options.width] - The width of the texture in pixels. Defaults to 4. * @param {number} [options.height] - The height of the texture in pixels. Defaults to 4. - * @param {number} [options.depth] - The number of depth slices in a 3D texture. + * @param {number} [options.slices] - The number of depth slices in a 3D texture, the number of textures + * in a texture array or the number of faces for a cubemap. + * @param {string} [options.dimension] - The texture dimension type. Can be: + * - {@link TEXTUREDIMENSION_2D} + * - {@link TEXTUREDIMENSION_2D_ARRAY} + * - {@link TEXTUREDIMENSION_3D} + * - {@link TEXTUREDIMENSION_CUBE} + * Defaults to {@link TEXTUREDIMENSION_2D}. Alternatively, you can specify the dimension using + * the options.cubemap, options.volume or options.array properties. * @param {number} [options.format] - The pixel format of the texture. Can be: * * - {@link PIXELFORMAT_R8} @@ -155,9 +167,8 @@ class Texture { * texture. Default is true. * @param {boolean} [options.cubemap] - Specifies whether the texture is to be a cubemap. * Defaults to false. - * @param {number} [options.arrayLength] - Specifies whether the texture is to be a 2D texture array. - * When passed in as undefined or < 1, this is not an array texture. If >= 1, this is an array texture. - * Defaults to undefined. + * @param {boolean} [options.array] - Specifies whether the texture is to be a 2D texture array. + * Defaults to false. * @param {boolean} [options.volume] - Specifies whether the texture is to be a 3D volume. * Defaults to false. * @param {string} [options.type] - Specifies the texture type. Can be: @@ -192,7 +203,7 @@ class Texture { * Defaults to {@link FUNC_LESS}. * @param {Uint8Array[]|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]|Uint8Array[][]} [options.levels] * - Array of Uint8Array or other supported browser interface; or a two-dimensional array - * of Uint8Array if options.arrayLength is defined and greater than zero. + * of Uint8Array if options.dimension is {@link TEXTUREDIMENSION_2D_ARRAY}. * @param {boolean} [options.storage] - Defines if texture can be used as a storage texture by * a compute shader. Defaults to false. * @param {boolean} [options.immediate] - If set and true, the texture will be uploaded to the GPU immediately. @@ -221,13 +232,22 @@ class Texture { Debug.assert(this.device, "Texture constructor requires a graphicsDevice to be valid"); Debug.assert(!options.width || Number.isInteger(options.width), "Texture width must be an integer number, got", options); Debug.assert(!options.height || Number.isInteger(options.height), "Texture height must be an integer number, got", options); - Debug.assert(!options.depth || Number.isInteger(options.depth), "Texture depth must be an integer number, got", options); + Debug.assert(!options.slices || Number.isInteger(options.slices), "Texture slices must be an integer number, got", options); this.name = options.name ?? ''; + this._dimension = options.dimension ?? TEXTUREDIMENSION_2D; + this._dimension = options.array ? TEXTUREDIMENSION_2D_ARRAY : this._dimension; + this._dimension = options.cubemap ? TEXTUREDIMENSION_CUBE : this._dimension; + this._dimension = options.volume ? TEXTUREDIMENSION_3D : this._dimension; + this._width = Math.floor(options.width ?? 4); this._height = Math.floor(options.height ?? 4); + this._slices = Math.floor(options.slices ?? (this._dimension === TEXTUREDIMENSION_CUBE ? 6 : 1)); + + Debug.assert((this._dimension === TEXTUREDIMENSION_CUBE ? this._slices === 6 : true), "Texture cube map must have 6 slices"); + this._format = options.format ?? PIXELFORMAT_RGBA8; this._compressed = isCompressedPixelFormat(this._format); this._integerFormat = isIntegerPixelFormat(this._format); @@ -237,12 +257,7 @@ class Texture { options.magFilter = FILTER_NEAREST; } - this._volume = options.volume ?? false; - this._depth = Math.floor(options.depth ?? 1); - this._arrayLength = Math.floor(options.arrayLength ?? 0); - this._storage = options.storage ?? false; - this._cubemap = options.cubemap ?? false; this._flipY = options.flipY ?? false; this._premultiplyAlpha = options.premultiplyAlpha ?? false; @@ -262,7 +277,7 @@ class Texture { Debug.assert(!options.hasOwnProperty('swizzleGGGR'), 'Use options.type.'); this.projection = TEXTUREPROJECTION_NONE; - if (this._cubemap) { + if (this.cubemap) { this.projection = TEXTUREPROJECTION_CUBE; } else if (options.projection && options.projection !== TEXTUREPROJECTION_CUBE) { this.projection = options.projection; @@ -280,7 +295,7 @@ class Texture { if (this._levels) { this.upload(options.immediate ?? false); } else { - this._levels = this._cubemap ? [[null, null, null, null, null, null]] : [null]; + this._levels = this.cubemap ? [[null, null, null, null, null, null]] : [null]; } // track the texture @@ -328,10 +343,10 @@ class Texture { * * @param {number} width - The new width of the texture. * @param {number} height - The new height of the texture. - * @param {number} [depth] - The new depth of the texture. Defaults to 1. + * @param {number} [slices] - The new number of slices for the texture. Defaults to 1. * @ignore */ - resize(width, height, depth = 1) { + resize(width, height, slices = 1) { // destroy texture impl const device = this.device; @@ -340,7 +355,7 @@ class Texture { this._width = Math.floor(width); this._height = Math.floor(height); - this._depth = Math.floor(depth); + this._slices = Math.floor(slices); // re-create the implementation this.impl = device.createTextureImpl(this); @@ -508,7 +523,7 @@ class Texture { * @type {number} */ set addressW(addressW) { - if (!this._volume) { + if (!this.volume) { Debug.warn("pc.Texture#addressW: Can't set W addressing mode for a non-3D texture."); return; } @@ -637,7 +652,16 @@ class Texture { * @type {number} */ get depth() { - return this._depth; + return this._dimension === TEXTUREDIMENSION_3D ? this._slices : 1; + } + + /** + * The number of textures in a texture array or the number of faces for a cubemap. + * + * @type {number} + */ + get slices() { + return this._slices; } /** @@ -679,12 +703,12 @@ class Texture { * @type {boolean} */ get cubemap() { - return this._cubemap; + return this._dimension === TEXTUREDIMENSION_CUBE; } get gpuSize() { const mips = this.pot && this._mipmaps && !(this._compressed && this._levels.length === 1); - return TextureUtils.calcGpuSize(this._width, this._height, this._depth, this._format, mips, this._cubemap); + return TextureUtils.calcGpuSize(this._width, this._height, this._slices, this._format, this.volume, mips); } /** @@ -693,16 +717,7 @@ class Texture { * @type {boolean} */ get array() { - return this._arrayLength > 0; - } - - /** - * Returns the number of textures inside this texture if this is a 2D array texture or 0 otherwise. - * - * @type {number} - */ - get arrayLength() { - return this._arrayLength; + return this._dimension === TEXTUREDIMENSION_2D_ARRAY; } /** @@ -711,7 +726,7 @@ class Texture { * @type {boolean} */ get volume() { - return this._volume; + return this._dimension === TEXTUREDIMENSION_3D; } /** @@ -772,7 +787,7 @@ class Texture { // Force a full resubmission of the texture to the GPU (used on a context restore event) dirtyAll() { - this._levelsUpdated = this._cubemap ? [[true, true, true, true, true, true]] : [true]; + this._levelsUpdated = this.cubemap ? [[true, true, true, true, true, true]] : [true]; this._needsUpload = true; this._needsMipmapsUpload = this._mipmaps; @@ -822,7 +837,7 @@ class Texture { // allocate storage for this mip level const width = Math.max(1, this._width >> options.level); const height = Math.max(1, this._height >> options.level); - const depth = Math.max(1, this._depth >> options.level); + const depth = Math.max(1, (this._dimension === TEXTUREDIMENSION_3D ? this._slices : 1) >> options.level); const data = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, depth, this._format)); levels[options.level] = new (getPixelFormatArrayType(this._format))(data); } @@ -845,7 +860,7 @@ class Texture { let invalid = false; let width, height; - if (this._cubemap) { + if (this.cubemap) { if (source[0]) { // rely on first face sizes width = source[0].width || 0; @@ -897,7 +912,7 @@ class Texture { this._height = 4; // remove levels - if (this._cubemap) { + if (this.cubemap) { for (let i = 0; i < 6; i++) { this._levels[mipLevel][i] = null; this._levelsUpdated[mipLevel][i] = true; diff --git a/src/platform/graphics/webgl/webgl-render-target.js b/src/platform/graphics/webgl/webgl-render-target.js index 23d3b527e63..2b7e703f92e 100644 --- a/src/platform/graphics/webgl/webgl-render-target.js +++ b/src/platform/graphics/webgl/webgl-render-target.js @@ -139,7 +139,7 @@ class WebglRenderTarget { gl.framebufferTexture2D( gl.FRAMEBUFFER, attachmentBaseConstant + i, - colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, + colorBuffer.cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, colorBuffer.impl._glTexture, 0 ); @@ -162,11 +162,11 @@ class WebglRenderTarget { // Attach if (target._stencil) { gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, - depthBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, + depthBuffer.cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, target._depthBuffer.impl._glTexture, 0); } else { gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, - depthBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, + depthBuffer.cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, target._depthBuffer.impl._glTexture, 0); } } else if (target._depth) { @@ -291,7 +291,7 @@ class WebglRenderTarget { const dstFramebuffer = gl.createFramebuffer(); device.setFramebuffer(dstFramebuffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, - colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, + colorBuffer.cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, colorBuffer.impl._glTexture, 0 ); diff --git a/src/platform/graphics/webgl/webgl-texture.js b/src/platform/graphics/webgl/webgl-texture.js index bff4f18af34..07c34655645 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -104,8 +104,8 @@ class WebglTexture { this._glTexture = gl.createTexture(); - this._glTarget = texture._cubemap ? gl.TEXTURE_CUBE_MAP : - (texture._volume ? gl.TEXTURE_3D : + this._glTarget = texture.cubemap ? gl.TEXTURE_CUBE_MAP : + (texture.volume ? gl.TEXTURE_3D : (texture.array ? gl.TEXTURE_2D_ARRAY : gl.TEXTURE_2D)); switch (texture._format) { @@ -378,7 +378,7 @@ class WebglTexture { uploadImmediate(device, texture, immediate) { if (immediate) { - if (!texture._glTexture) { + if (!this._glTexture) { this.initialize(device, texture); } @@ -414,7 +414,7 @@ class WebglTexture { this._glInternalFormat, texture._width, texture._height, - texture._arrayLength); + texture._slices); } // Upload all existing mip levels. Initialize 0 mip anyway. @@ -438,13 +438,13 @@ class WebglTexture { texture._mipmapsUploaded = true; } - if (texture._cubemap) { + if (texture.cubemap) { // ----- CUBEMAP ----- let face; if (device._isBrowserInterface(mipObject[0])) { // Upload the image, canvas or video - for (face = 0; face < 6; face++) { + for (face = 0; face < texture.slices; face++) { if (!texture._levelsUpdated[0][face]) continue; @@ -486,7 +486,7 @@ class WebglTexture { } else { // Upload the byte array resMult = 1 / Math.pow(2, mipLevel); - for (face = 0; face < 6; face++) { + for (face = 0; face < texture.slices; face++) { if (!texture._levelsUpdated[0][face]) continue; @@ -542,7 +542,7 @@ class WebglTexture { } } } - } else if (texture._volume) { + } else if (texture.volume) { // ----- 3D ----- // Image/canvas/video not supported (yet?) // Upload the byte array @@ -552,7 +552,7 @@ class WebglTexture { this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), - Math.max(texture._depth * resMult, 1), + Math.max(texture._slices * resMult, 1), 0, mipObject); } else { @@ -563,16 +563,16 @@ class WebglTexture { this._glInternalFormat, Math.max(texture._width * resMult, 1), Math.max(texture._height * resMult, 1), - Math.max(texture._depth * resMult, 1), + Math.max(texture._slices * resMult, 1), 0, this._glFormat, this._glPixelType, mipObject); } } else if (texture.array && typeof mipObject === "object") { - if (texture._arrayLength === mipObject.length) { + if (texture._slices === mipObject.length) { if (texture._compressed) { - for (let index = 0; index < texture._arrayLength; index++) { + for (let index = 0; index < texture._slices; index++) { gl.compressedTexSubImage3D( gl.TEXTURE_2D_ARRAY, mipLevel, @@ -587,7 +587,7 @@ class WebglTexture { ); } } else { - for (let index = 0; index < texture._arrayLength; index++) { + for (let index = 0; index < texture.slices; index++) { gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, mipLevel, @@ -716,8 +716,8 @@ class WebglTexture { } if (texture._needsUpload) { - if (texture._cubemap) { - for (let i = 0; i < 6; i++) + if (texture.cubemap) { + for (let i = 0; i < texture.slices; i++) texture._levelsUpdated[0][i] = false; } else { texture._levelsUpdated[0] = false; diff --git a/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js b/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js index 7e89ba7e9d7..3f132277d2e 100644 --- a/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js +++ b/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js @@ -106,7 +106,7 @@ class WebgpuMipmapRenderer { DebugHelper.setLabel(pipeline, 'RenderPipeline-MipmapRenderer'); const texture = webgpuTexture.texture; - const numFaces = texture.cubemap ? 6 : (texture.array ? texture.arrayLength : 1); + const numFaces = texture.slices; const srcViews = []; for (let face = 0; face < numFaces; face++) { diff --git a/src/platform/graphics/webgpu/webgpu-texture.js b/src/platform/graphics/webgpu/webgpu-texture.js index d2a05be0bfa..0f527456f7f 100644 --- a/src/platform/graphics/webgpu/webgpu-texture.js +++ b/src/platform/graphics/webgpu/webgpu-texture.js @@ -98,7 +98,7 @@ class WebgpuTexture { size: { width: texture.width, height: texture.height, - depthOrArrayLayers: texture.cubemap ? 6 : (texture.array ? texture.arrayLength : 1) + depthOrArrayLayers: texture.slices }, format: this.format, mipLevelCount: mipLevelCount, @@ -330,15 +330,15 @@ class WebgpuTexture { } } - } else if (texture._volume) { + } else if (texture.volume) { Debug.warn('Volume texture data upload is not supported yet', this.texture); } else if (texture.array) { // texture array - if (texture.arrayLength === mipObject.length) { + if (texture.slices === mipObject.length) { - for (let index = 0; index < texture._arrayLength; index++) { + for (let index = 0; index < texture.slices; index++) { const arraySource = mipObject[index]; if (this.isExternalImage(arraySource)) {