diff --git a/src/framework/handlers/texture.js b/src/framework/handlers/texture.js index 329f4fe2b00..fdcde63c2fa 100644 --- a/src/framework/handlers/texture.js +++ b/src/framework/handlers/texture.js @@ -98,10 +98,10 @@ 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 || texture.array) { const mips = []; - for (let face = 0; face < 6; ++face) { - mips.push(downsample(width, height, texture._levels[level - 1][face])); + for (let slice = 0; slice < texture.slices; ++slice) { + mips.push(downsample(width, height, texture._levels[level - 1][slice])); } texture._levels.push(mips); } else { @@ -109,7 +109,7 @@ const _completePartialMipmapChain = function (texture) { } } - texture._levelsUpdated = texture.cubemap ? [[true, true, true, true, true, true]] : [true]; + texture._levelsUpdated = (texture.cubemap || texture.array) ? [Array(texture.slices).fill(true)] : [true]; }; /** diff --git a/src/platform/graphics/null/null-texture.js b/src/platform/graphics/null/null-texture.js index dad979f9d44..300bb144f33 100644 --- a/src/platform/graphics/null/null-texture.js +++ b/src/platform/graphics/null/null-texture.js @@ -12,6 +12,9 @@ class NullTexture { loseContext() { } + + uploadImmediate(device, texture, immediate) { + } } export { NullTexture }; diff --git a/src/platform/graphics/texture-utils.js b/src/platform/graphics/texture-utils.js index c0fee74f848..367cad798c6 100644 --- a/src/platform/graphics/texture-utils.js +++ b/src/platform/graphics/texture-utils.js @@ -1,8 +1,7 @@ import { Debug } from '../../core/debug.js'; import { pixelFormatInfo, - PIXELFORMAT_PVRTC_2BPP_RGB_1, PIXELFORMAT_PVRTC_2BPP_RGBA_1, - TEXTUREDIMENSION_3D + PIXELFORMAT_PVRTC_2BPP_RGB_1, PIXELFORMAT_PVRTC_2BPP_RGBA_1 } from './constants.js'; /** @@ -70,11 +69,11 @@ 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} slices - Texture's slices. * @param {number} format - Texture's pixel format PIXELFORMAT_***. + * @param {boolean} isVolume - True if the texture is a volume texture, false otherwise. * @param {boolean} mipmaps - True if the texture includes mipmaps, false otherwise. * @returns {number} The number of bytes of GPU memory required for the texture. * @ignore diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index fa93618b26e..d9945fdc255 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -78,9 +78,6 @@ class Texture { /** @protected */ _invalid = false; - /** @protected */ - _lockedLevel = -1; - /** @protected */ _lockedMode = TEXTURELOCK_NONE; @@ -295,7 +292,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 || this.array) ? [Array(this._slices).fill(null)] : [null]; } // track the texture @@ -837,7 +834,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 || this.array) ? [Array(this._slices).fill(true)] : [true]; this._needsUpload = true; this._needsMipmapsUpload = this._mipmaps; @@ -854,6 +851,8 @@ class Texture { * to 0. * @param {number} [options.face] - If the texture is a cubemap, this is the index of the face * to lock. + * @param {number} [options.slice] - If the texture is a texture array, this is the index of the + * slice to lock. * @param {number} [options.mode] - The lock mode. Can be: * - {@link TEXTURELOCK_READ} * - {@link TEXTURELOCK_WRITE} @@ -865,6 +864,7 @@ class Texture { // Initialize options to some sensible defaults options.level ??= 0; options.face ??= 0; + options.slice ??= 0; options.mode ??= TEXTURELOCK_WRITE; Debug.assert( @@ -879,19 +879,38 @@ class Texture { this ); + Debug.assert( + options.level >= 0 && options.level < this._levels.length, + 'Invalid mip level', + this + ); + + Debug.assert( + ((this.cubemap || this.array) && options.mode === TEXTURELOCK_WRITE) ? options.level === 0 : true, + 'Only mip level 0 can be locked for writing for cubemaps and texture arrays', + this + ); + this._lockedMode = options.mode; - this._lockedLevel = options.level; - const levels = this.cubemap ? this._levels[options.face] : this._levels; + const levels = this.cubemap ? this._levels[options.face] : this.array ? this._levels[options.slice] : this._levels; if (levels[options.level] === null) { // 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._dimension === TEXTUREDIMENSION_3D ? this._slices : 1) >> options.level); + const depth = Math.max(1, this.depth >> options.level); const data = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, depth, this._format)); levels[options.level] = new (getPixelFormatArrayType(this._format))(data); } + if (this._lockedMode === TEXTURELOCK_WRITE) { + if (this.cubemap || this.array) { + this._levelsUpdated[0][options.face ?? options.slice] = true; + } else { + this._levelsUpdated[0] = true; + } + } + return levels[options.level]; } @@ -901,22 +920,21 @@ class Texture { * * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]} source - A * canvas, image or video element, or an array of 6 canvas, image or video elements. - * @param {number} [mipLevel] - A non-negative integer specifying the image level of detail. * Defaults to 0, which represents the base image source. A level value of N, that is greater * than 0, represents the image source for the Nth mipmap reduction level. * @param {boolean} [immediate] - When set to true it forces an immediate upload upon assignment. Defaults to false. */ - setSource(source, mipLevel = 0, immediate = false) { + setSource(source, immediate = false) { let invalid = false; let width, height; - if (this.cubemap) { + if (this.cubemap || this.array) { if (source[0]) { // rely on first face sizes width = source[0].width || 0; height = source[0].height || 0; - for (let i = 0; i < 6; i++) { + for (let i = 0; i < this._slices; i++) { const face = source[i]; // cubemap becomes invalid if any condition is not satisfied if (!face || // face is missing @@ -934,9 +952,9 @@ class Texture { if (!invalid) { // mark levels as updated - for (let i = 0; i < 6; i++) { - if (this._levels[mipLevel][i] !== source[i]) - this._levelsUpdated[mipLevel][i] = true; + for (let i = 0; i < this._slices; i++) { + if (this._levels[0][i] !== source[i]) + this._levelsUpdated[0][i] = true; } } } else { @@ -946,8 +964,8 @@ class Texture { if (!invalid) { // mark level as updated - if (source !== this._levels[mipLevel]) - this._levelsUpdated[mipLevel] = true; + if (source !== this._levels[0]) + this._levelsUpdated[0] = true; width = source.width; height = source.height; @@ -962,23 +980,22 @@ class Texture { this._height = 4; // remove levels - if (this.cubemap) { - for (let i = 0; i < 6; i++) { - this._levels[mipLevel][i] = null; - this._levelsUpdated[mipLevel][i] = true; + if (this.cubemap || this.array) { + for (let i = 0; i < this._slices; i++) { + this._levels[0][i] = null; + this._levelsUpdated[0][i] = true; } } else { - this._levels[mipLevel] = null; - this._levelsUpdated[mipLevel] = true; + this._levels[0] = null; + this._levelsUpdated[0] = true; } } else { // valid texture - if (mipLevel === 0) { - this._width = width; - this._height = height; - } - this._levels[mipLevel] = source; + this._width = width; + this._height = height; + + this._levels[0] = source; } // valid or changed state of validity @@ -994,14 +1011,11 @@ class Texture { * Get the pixel data of the texture. If this is a cubemap then an array of 6 images will be * returned otherwise a single image. * - * @param {number} [mipLevel] - A non-negative integer specifying the image level of detail. - * Defaults to 0, which represents the base image source. A level value of N, that is greater - * than 0, represents the image source for the Nth mipmap reduction level. * @returns {HTMLImageElement} The source image of this texture. Can be null if source not * assigned for specific image level. */ - getSource(mipLevel = 0) { - return this._levels[mipLevel]; + getSource() { + return this._levels[0]; } /** @@ -1017,7 +1031,6 @@ class Texture { if (this._lockedMode === TEXTURELOCK_WRITE) { this.upload(immediate); } - this._lockedLevel = -1; this._lockedMode = TEXTURELOCK_NONE; } diff --git a/src/platform/graphics/webgl/webgl-texture.js b/src/platform/graphics/webgl/webgl-texture.js index 07c34655645..b8181eba134 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -407,7 +407,7 @@ class WebglTexture { const requiredMipLevels = texture.requiredMipLevels; - if (texture.array) { + if (texture.array && !this._glCreated) { // for texture arrays we reserve the space in advance gl.texStorage3D(gl.TEXTURE_2D_ARRAY, requiredMipLevels, @@ -445,10 +445,10 @@ class WebglTexture { if (device._isBrowserInterface(mipObject[0])) { // Upload the image, canvas or video for (face = 0; face < texture.slices; face++) { - if (!texture._levelsUpdated[0][face]) + let src = mipObject[face]; + if (!texture._levelsUpdated[0][face] || !src) continue; - let src = mipObject[face]; // Downsize images that are too large to be used as cube maps if (device._isImageBrowserInterface(src)) { if (src.width > device.maxCubeMapSize || src.height > device.maxCubeMapSize) { @@ -487,10 +487,10 @@ class WebglTexture { // Upload the byte array resMult = 1 / Math.pow(2, mipLevel); for (face = 0; face < texture.slices; face++) { - if (!texture._levelsUpdated[0][face]) + const texData = mipObject[face]; + if (!texture._levelsUpdated[0][face] || !texData) continue; - const texData = mipObject[face]; if (texture._compressed) { if (this._glCreated && texData) { gl.compressedTexSubImage2D( @@ -570,38 +570,40 @@ class WebglTexture { mipObject); } } else if (texture.array && typeof mipObject === "object") { - if (texture._slices === mipObject.length) { - if (texture._compressed) { - for (let index = 0; index < texture._slices; index++) { - gl.compressedTexSubImage3D( - gl.TEXTURE_2D_ARRAY, - mipLevel, - 0, - 0, - index, - Math.max(Math.floor(texture._width * resMult), 1), - Math.max(Math.floor(texture._height * resMult), 1), - 1, - this._glFormat, - mipObject[index] - ); - } - } else { - for (let index = 0; index < texture.slices; index++) { - gl.texSubImage3D( - gl.TEXTURE_2D_ARRAY, - mipLevel, - 0, - 0, - index, - Math.max(Math.floor(texture._width * resMult), 1), - Math.max(Math.floor(texture._height * resMult), 1), - 1, - this._glFormat, - this._glPixelType, - mipObject[index] - ); - } + if (texture._compressed) { + for (let index = 0; index < texture._slices; index++) { + if (!texture._levelsUpdated[0][index] || !mipObject[index]) + continue; + gl.compressedTexSubImage3D( + gl.TEXTURE_2D_ARRAY, + mipLevel, + 0, + 0, + index, + Math.max(Math.floor(texture._width * resMult), 1), + Math.max(Math.floor(texture._height * resMult), 1), + 1, + this._glFormat, + mipObject[index] + ); + } + } else { + for (let index = 0; index < texture.slices; index++) { + if (!texture._levelsUpdated[0][index] || !mipObject[index]) + continue; + gl.texSubImage3D( + gl.TEXTURE_2D_ARRAY, + mipLevel, + 0, + 0, + index, + Math.max(Math.floor(texture._width * resMult), 1), + Math.max(Math.floor(texture._height * resMult), 1), + 1, + this._glFormat, + this._glPixelType, + mipObject[index] + ); } } } else { @@ -716,7 +718,7 @@ class WebglTexture { } if (texture._needsUpload) { - if (texture.cubemap) { + if (texture.cubemap || texture.array) { for (let i = 0; i < texture.slices; i++) texture._levelsUpdated[0][i] = false; } else {