diff --git a/examples/src/examples/graphics/texture-array.example.mjs b/examples/src/examples/graphics/texture-array.example.mjs index ed7dfe77feb..5859e555ba1 100644 --- a/examples/src/examples/graphics/texture-array.example.mjs +++ b/examples/src/examples/graphics/texture-array.example.mjs @@ -105,9 +105,10 @@ assetListLoader.load(() => { const textureArrayOptions = { name: 'textureArrayImages', format: pc.PIXELFORMAT_SRGBA8, + dimension: pc.TEXTUREDIMENSION_2D_ARRAY, width: 1024, height: 1024, - arrayLength: 4, // array texture with 4 textures + layers: 4, // array texture with 4 textures magFilter: pc.FILTER_NEAREST, minFilter: pc.FILTER_NEAREST_MIPMAP_NEAREST, mipmaps: true, @@ -120,17 +121,17 @@ assetListLoader.load(() => { assets.aerialRocks.resource.getSource(), assets.coastSand.resource.getSource() ] - ] + ], + immediate: true }; const textureArray = new pc.Texture(app.graphicsDevice, textureArrayOptions); - textureArray.upload(); // generate mipmaps for visualization 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.layers; i++) { textures.push(data); } return textures; diff --git a/src/framework/handlers/texture.js b/src/framework/handlers/texture.js index e82a381fd68..85d5a65c447 100644 --- a/src/framework/handlers/texture.js +++ b/src/framework/handlers/texture.js @@ -61,11 +61,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; } @@ -99,10 +99,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 layer = 0; layer < texture.layers; ++layer) { + mips.push(downsample(width, height, texture._levels[level - 1][layer])); } texture._levels.push(mips); } else { @@ -110,7 +110,7 @@ const _completePartialMipmapChain = function (texture) { } } - texture._levelsUpdated = texture._cubemap ? [[true, true, true, true, true, true]] : [true]; + texture._levelsUpdated = (texture.cubemap || texture.array) ? [Array(texture.layers).fill(true)] : [true]; }; /** diff --git a/src/framework/lightmapper/lightmapper.js b/src/framework/lightmapper/lightmapper.js index 4b3147b3d44..fe1bf19f8fc 100644 --- a/src/framework/lightmapper/lightmapper.js +++ b/src/framework/lightmapper/lightmapper.js @@ -1107,6 +1107,8 @@ class Lightmapper { } else { // use the old path for WebGL till the render pass way above is fixed + device._uploadDirtyTextures(); + // ping-ponging output this.renderer.setCamera(this.camera, tempRT, true); diff --git a/src/framework/xr/xr-view.js b/src/framework/xr/xr-view.js index 55978fc8f23..e48ae1fb960 100644 --- a/src/framework/xr/xr-view.js +++ b/src/framework/xr/xr-view.js @@ -172,7 +172,8 @@ class XrView extends EventHandler { this._textureDepth = new Texture(device, { format: this._manager.views.depthPixelFormat, - arrayLength: (viewsCount === 1) ? 0 : viewsCount, + array: viewsCount > 1, + layers: viewsCount, mipmaps: false, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE, diff --git a/src/platform/graphics/null/null-texture.js b/src/platform/graphics/null/null-texture.js index aa74db6f606..131a03b8271 100644 --- a/src/platform/graphics/null/null-texture.js +++ b/src/platform/graphics/null/null-texture.js @@ -10,6 +10,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 1407f944f9f..d3603a3dcbc 100644 --- a/src/platform/graphics/texture-utils.js +++ b/src/platform/graphics/texture-utils.js @@ -26,7 +26,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 layers. Defaults to 1. * @returns {number} The number of mip levels required for the texture. */ static calcMipLevelsCount(width, height, depth = 1) { @@ -38,7 +38,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 layers. * @param {number} format - Texture's pixel format PIXELFORMAT_***. * @returns {number} The number of bytes of GPU memory required for the texture. */ @@ -70,14 +70,15 @@ class TextureUtils { * * @param {number} width - Texture's width. * @param {number} height - Texture's height. - * @param {number} depth - Texture's depth. + * @param {number} layers - Texture's layers. * @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. - * @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. */ - static calcGpuSize(width, height, depth, format, mipmaps, cubemap) { + static calcGpuSize(width, height, layers, format, isVolume, mipmaps) { let result = 0; + let depth = isVolume ? layers : 1; while (1) { result += TextureUtils.calcLevelGpuSize(width, height, depth, format); @@ -91,7 +92,7 @@ class TextureUtils { depth = Math.max(depth >> 1, 1); } - return result * (cubemap ? 6 : 1); + return result * (isVolume ? 1 : layers); } } diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index 987b1f32e1f..8d85fa84f82 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -16,7 +16,8 @@ import { TEXPROPERTY_MIN_FILTER, TEXPROPERTY_MAG_FILTER, TEXPROPERTY_ADDRESS_U, TEXPROPERTY_ADDRESS_V, TEXPROPERTY_ADDRESS_W, TEXPROPERTY_COMPARE_ON_READ, TEXPROPERTY_COMPARE_FUNC, TEXPROPERTY_ANISOTROPY, TEXPROPERTY_ALL, requiresManualGamma, - pixelFormatInfo + pixelFormatInfo, TEXTUREDIMENSION_2D, TEXTUREDIMENSION_3D, + TEXTUREDIMENSION_2D_ARRAY, TEXTUREDIMENSION_CUBE } from './constants.js'; import { TextureUtils } from './texture-utils.js'; @@ -72,9 +73,6 @@ class Texture { /** @protected */ _invalid = false; - /** @protected */ - _lockedLevel = -1; - /** @protected */ _lockedMode = TEXTURELOCK_NONE; @@ -104,7 +102,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.layers] - The number of depth layers 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} @@ -161,9 +167,8 @@ class Texture { * the mipmaps property is ignored. * @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: @@ -198,9 +203,10 @@ class Texture { * Defaults to {@link FUNC_LESS}. * @param {Uint8Array[]|Uint16Array[]|Uint32Array[]|Float32Array[]|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. * @example * // Create a 8x8x24-bit texture * const texture = new pc.Texture(graphicsDevice, { @@ -226,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.layers || Number.isInteger(options.layers), 'Texture layers 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._layers = Math.floor(options.layers ?? (this._dimension === TEXTUREDIMENSION_CUBE ? 6 : 1)); + + Debug.assert((this._dimension === TEXTUREDIMENSION_CUBE ? this._layers === 6 : true), 'Texture cube map must have 6 layers'); + this._format = options.format ?? PIXELFORMAT_RGBA8; this._compressed = isCompressedPixelFormat(this._format); this._integerFormat = isIntegerPixelFormat(this._format); @@ -241,12 +256,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; @@ -272,7 +282,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; @@ -288,9 +298,9 @@ class Texture { this._levels = options.levels; if (this._levels) { - this.upload(); + 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._layers).fill(null)] : [null]; } // track the texture @@ -338,10 +348,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} [layers] - The new number of layers for the texture. Defaults to 1. * @ignore */ - resize(width, height, depth = 1) { + resize(width, height, layers = 1) { // destroy texture impl const device = this.device; @@ -350,7 +360,7 @@ class Texture { this._width = Math.floor(width); this._height = Math.floor(height); - this._depth = Math.floor(depth); + this._layers = Math.floor(layers); this._updateNumLevel(); // re-create the implementation @@ -541,7 +551,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; } @@ -699,12 +709,21 @@ class Texture { } /** - * The number of depth slices in a 3D texture. + * The number of depth layers in a 3D texture. * * @type {number} */ get depth() { - return this._depth; + return this._dimension === TEXTUREDIMENSION_3D ? this._layers : 1; + } + + /** + * The number of textures in a texture array or the number of faces for a cubemap. + * + * @type {number} + */ + get layers() { + return this._layers; } /** @@ -746,12 +765,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._layers, this._format, this.volume, mips); } /** @@ -760,16 +779,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; } /** @@ -778,7 +788,7 @@ class Texture { * @type {boolean} */ get volume() { - return this._volume; + return this._dimension === TEXTUREDIMENSION_3D; } /** @@ -841,7 +851,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._layers).fill(true)] : [true]; this._needsUpload = true; this._needsMipmapsUpload = this._mipmaps; @@ -858,6 +868,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.layer] - If the texture is a texture array, this is the index of the + * layer to lock. * @param {number} [options.mode] - The lock mode. Can be: * - {@link TEXTURELOCK_READ} * - {@link TEXTURELOCK_WRITE} @@ -869,6 +881,7 @@ class Texture { // Initialize options to some sensible defaults options.level ??= 0; options.face ??= 0; + options.layer ??= 0; options.mode ??= TEXTURELOCK_WRITE; Debug.assert( @@ -883,19 +896,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.layer] : 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._depth >> 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.layer] = true; + } else { + this._levelsUpdated[0] = true; + } + } + return levels[options.level]; } @@ -905,21 +937,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) { + 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._layers; i++) { const face = source[i]; // cubemap becomes invalid if any condition is not satisfied if (!face || // face is missing @@ -937,9 +969,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._layers; i++) { + if (this._levels[0][i] !== source[i]) { + this._levelsUpdated[0][i] = true; } } } @@ -951,8 +983,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; } if (source instanceof HTMLVideoElement) { @@ -973,23 +1005,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._layers; 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 @@ -997,7 +1028,7 @@ class Texture { this._invalid = invalid; // reupload - this.upload(); + this.upload(immediate); } } @@ -1005,43 +1036,41 @@ 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]; } /** * Unlocks the currently locked mip level and uploads it to VRAM. + * @param {boolean} [immediate] - When set to true it forces an immediate upload upon unlocking. Defaults to false. */ - unlock() { + unlock(immediate = false) { if (this._lockedMode === TEXTURELOCK_NONE) { Debug.warn('pc.Texture#unlock: Attempting to unlock a texture that is not locked.', this); } // Upload the new pixel data if locked in write mode (default) if (this._lockedMode === TEXTURELOCK_WRITE) { - this.upload(); + this.upload(immediate); } - this._lockedLevel = -1; this._lockedMode = TEXTURELOCK_NONE; } /** * Forces a reupload of the textures pixel data to graphics memory. Ordinarily, this function - * is called by internally by {@link Texture#setSource} and {@link Texture#unlock}. However, it + * is called internally by {@link Texture#setSource} and {@link Texture#unlock}. However, it * still needs to be called explicitly in the case where an HTMLVideoElement is set as the * source of the texture. Normally, this is done once every frame before video textured * geometry is rendered. + * @param {boolean} [immediate] - When set to true it forces an immediate upload. Defaults to false. */ - upload() { + upload(immediate = false) { this._needsUpload = true; this._needsMipmapsUpload = this._mipmaps; - this.impl.uploadImmediate?.(this.device, this); + this.impl.uploadImmediate(this.device, this, immediate); } /** diff --git a/src/platform/graphics/webgl/webgl-graphics-device.js b/src/platform/graphics/webgl/webgl-graphics-device.js index fb997b67924..068ab247573 100644 --- a/src/platform/graphics/webgl/webgl-graphics-device.js +++ b/src/platform/graphics/webgl/webgl-graphics-device.js @@ -1145,6 +1145,22 @@ class WebglGraphicsDevice extends GraphicsDevice { this.gpuProfiler.request(); } + _uploadDirtyTextures() { + + this.textures.forEach((texture) => { + if (texture._needsUpload || texture._needsMipmapsUpload) { + if (!texture.impl._glTexture) { + texture.impl.initialize(this, texture); + } + + this.bindTexture(texture); + texture.impl.upload(this, texture); + texture._needsUpload = false; + texture._needsMipmapsUpload = false; + } + }); + } + /** * Start a render pass. * @@ -1153,6 +1169,8 @@ class WebglGraphicsDevice extends GraphicsDevice { */ startRenderPass(renderPass) { + this._uploadDirtyTextures(); + // set up render target const rt = renderPass.renderTarget ?? this.backBuffer; this.renderTarget = rt; @@ -1526,7 +1544,7 @@ class WebglGraphicsDevice extends GraphicsDevice { impl.initialize(this, texture); } - if (impl.dirtyParameterFlags > 0 || texture._needsUpload || texture._needsMipmapsUpload) { + if (impl.dirtyParameterFlags > 0) { // Ensure the specified texture unit is active this.activeTexture(textureUnit); @@ -1538,12 +1556,6 @@ class WebglGraphicsDevice extends GraphicsDevice { this.setTextureParameters(texture); impl.dirtyParameterFlags = 0; } - - if (texture._needsUpload || texture._needsMipmapsUpload) { - impl.upload(this, texture); - texture._needsUpload = false; - texture._needsMipmapsUpload = false; - } } else { // Ensure the texture is currently bound to the correct target on the specified texture unit. // If the texture is already bound to the correct target on the specified unit, there's no need diff --git a/src/platform/graphics/webgl/webgl-render-target.js b/src/platform/graphics/webgl/webgl-render-target.js index f9fb0f20b14..d4296a0e356 100644 --- a/src/platform/graphics/webgl/webgl-render-target.js +++ b/src/platform/graphics/webgl/webgl-render-target.js @@ -165,12 +165,13 @@ class WebglRenderTarget { colorBuffer._width = Math.min(colorBuffer.width, device.maxRenderBufferSize); colorBuffer._height = Math.min(colorBuffer.height, device.maxRenderBufferSize); device.setTexture(colorBuffer, 0); + colorBuffer.upload(true); } // Attach the color buffer 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, target.mipLevel ); @@ -193,11 +194,12 @@ class WebglRenderTarget { depthBuffer._width = Math.min(depthBuffer.width, device.maxRenderBufferSize); depthBuffer._height = Math.min(depthBuffer.height, device.maxRenderBufferSize); device.setTexture(depthBuffer, 0); + depthBuffer.upload(true); } // Attach gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, - 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, target.mipLevel); } else { @@ -348,7 +350,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 c40c5b43418..a2b73045833 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -110,8 +110,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) { @@ -453,6 +453,19 @@ class WebglTexture { this._glCreated = false; } + uploadImmediate(device, texture, immediate) { + if (immediate) { + if (!this._glTexture) { + this.initialize(device, texture); + } + + device.bindTexture(texture); + this.upload(device, texture); + texture._needsUpload = false; + texture._needsMipmapsUpload = false; + } + } + /** * @param {WebglGraphicsDevice} device - The device. * @param {Texture} texture - The texture to update. @@ -472,14 +485,14 @@ class WebglTexture { const requiredMipLevels = texture.numLevels; - if (texture.array) { + if (texture.array && !this._glCreated) { // for texture arrays we reserve the space in advance gl.texStorage3D(gl.TEXTURE_2D_ARRAY, requiredMipLevels, this._glInternalFormat, texture._width, texture._height, - texture._arrayLength); + texture._layers); } // Upload all existing mip levels. Initialize 0 mip anyway. @@ -503,18 +516,18 @@ 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++) { - if (!texture._levelsUpdated[0][face]) { + for (face = 0; face < texture.layers; 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) { @@ -552,12 +565,12 @@ class WebglTexture { } else { // Upload the byte array resMult = 1 / Math.pow(2, mipLevel); - for (face = 0; face < 6; face++) { + for (face = 0; face < texture.layers; face++) { + const texData = mipObject[face]; if (!texture._levelsUpdated[0][face]) { continue; } - const texData = mipObject[face]; if (texture._compressed) { if (this._glCreated && texData) { gl.compressedTexSubImage2D( @@ -609,7 +622,7 @@ class WebglTexture { } } } - } else if (texture._volume) { + } else if (texture.volume) { // ----- 3D ----- // Image/canvas/video not supported (yet?) // Upload the byte array @@ -619,7 +632,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._layers * resMult, 1), 0, mipObject); } else { @@ -630,45 +643,49 @@ 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._layers * resMult, 1), 0, this._glFormat, this._glPixelType, mipObject); } } else if (texture.array && typeof mipObject === 'object') { - if (texture._arrayLength === mipObject.length) { - if (texture._compressed) { - for (let index = 0; index < texture._arrayLength; 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] - ); + if (texture._compressed) { + for (let index = 0; index < texture._layers; index++) { + if (!texture._levelsUpdated[0][index] || !mipObject[index]) { + continue; } - } else { - for (let index = 0; index < texture._arrayLength; 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] - ); + 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.layers; 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 { @@ -783,8 +800,8 @@ class WebglTexture { } if (texture._needsUpload) { - if (texture._cubemap) { - for (let i = 0; i < 6; i++) { + if (texture.cubemap || texture.array) { + for (let i = 0; i < texture.layers; i++) { texture._levelsUpdated[0][i] = false; } } else { diff --git a/src/platform/graphics/webgpu/webgpu-graphics-device.js b/src/platform/graphics/webgpu/webgpu-graphics-device.js index 449b0b09037..33229bcf46f 100644 --- a/src/platform/graphics/webgpu/webgpu-graphics-device.js +++ b/src/platform/graphics/webgpu/webgpu-graphics-device.js @@ -658,7 +658,7 @@ class WebgpuGraphicsDevice extends GraphicsDevice { _uploadDirtyTextures() { this.textures.forEach((texture) => { - if (texture._needsUpload || texture._needsMipmaps) { + if (texture._needsUpload || texture._needsMipmapsUpload) { texture.upload(); } }); diff --git a/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js b/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js index 67cd14e1c00..3c9f036b5d7 100644 --- a/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js +++ b/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js @@ -110,7 +110,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.layers; 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 c0c336ce265..67234baaaa6 100644 --- a/src/platform/graphics/webgpu/webgpu-texture.js +++ b/src/platform/graphics/webgpu/webgpu-texture.js @@ -100,7 +100,7 @@ class WebgpuTexture { size: { width: texture.width, height: texture.height, - depthOrArrayLayers: texture.cubemap ? 6 : (texture.array ? texture.arrayLength : 1) + depthOrArrayLayers: texture.layers }, format: this.format, mipLevelCount: numLevels, @@ -331,15 +331,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.layers === mipObject.length) { - for (let index = 0; index < texture._arrayLength; index++) { + for (let index = 0; index < texture.layers; index++) { const arraySource = mipObject[index]; if (this.isExternalImage(arraySource)) {