From 465d7bedd71fd5fd40f440b2355c02359a21cdf7 Mon Sep 17 00:00:00 2001 From: Catalin Moldovan Date: Tue, 21 May 2024 10:42:27 +0300 Subject: [PATCH 1/8] Add immediate upload option to textures --- .../graphics/texture-array.example.mjs | 4 ++-- src/platform/graphics/texture.js | 20 +++++++++++-------- src/platform/graphics/webgl/webgl-texture.js | 13 ++++++++++++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/examples/src/examples/graphics/texture-array.example.mjs b/examples/src/examples/graphics/texture-array.example.mjs index ed7dfe77feb..68067f3270f 100644 --- a/examples/src/examples/graphics/texture-array.example.mjs +++ b/examples/src/examples/graphics/texture-array.example.mjs @@ -120,11 +120,11 @@ 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); diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index 987b1f32e1f..f5195b143a4 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -201,6 +201,7 @@ class Texture { * of Uint8Array if options.arrayLength is defined and greater than zero. * @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, { @@ -288,7 +289,7 @@ 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]; } @@ -908,8 +909,9 @@ class Texture { * @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, mipLevel = 0, immediate = false) { let invalid = false; let width, height; @@ -997,7 +999,7 @@ class Texture { this._invalid = invalid; // reupload - this.upload(); + this.upload(immediate); } } @@ -1017,15 +1019,16 @@ class Texture { /** * 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; @@ -1033,15 +1036,16 @@ class Texture { /** * 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-texture.js b/src/platform/graphics/webgl/webgl-texture.js index c40c5b43418..91e2d70f5e6 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -453,6 +453,19 @@ class WebglTexture { this._glCreated = false; } + uploadImmediate(device, texture, immediate) { + if (immediate) { + if (!texture._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. From 1e60f9ad2a62673817d53bd2d74fd6792ff6a094 Mon Sep 17 00:00:00 2001 From: Catalin Moldovan Date: Tue, 4 Jun 2024 17:14:32 +0300 Subject: [PATCH 2/8] calcGPUSize --- .../graphics/texture-array.example.mjs | 5 +- src/framework/handlers/texture.js | 8 +- src/framework/xr/xr-view.js | 3 +- src/platform/graphics/texture-utils.js | 16 ++-- src/platform/graphics/texture.js | 90 +++++++++++-------- .../graphics/webgl/webgl-render-target.js | 6 +- src/platform/graphics/webgl/webgl-texture.js | 72 +++++++-------- .../graphics/webgpu/webgpu-mipmap-renderer.js | 2 +- .../graphics/webgpu/webgpu-texture.js | 8 +- 9 files changed, 113 insertions(+), 97 deletions(-) diff --git a/examples/src/examples/graphics/texture-array.example.mjs b/examples/src/examples/graphics/texture-array.example.mjs index 68067f3270f..4749c5591be 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 + slices: 4, // array texture with 4 textures magFilter: pc.FILTER_NEAREST, minFilter: pc.FILTER_NEAREST_MIPMAP_NEAREST, mipmaps: true, @@ -130,7 +131,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 e82a381fd68..4e2e763cc12 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,7 +99,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])); @@ -110,7 +110,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 55978fc8f23..a8618835cf6 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, + 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 1407f944f9f..33e55b8186f 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. */ @@ -68,16 +69,17 @@ 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. */ - 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); @@ -91,7 +93,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 f5195b143a4..f357b978256 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'; @@ -104,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} @@ -161,9 +170,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,7 +206,7 @@ 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. @@ -224,16 +232,25 @@ class Texture { */ constructor(graphicsDevice, options = {}) { this.device = graphicsDevice; - 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(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.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); @@ -242,12 +259,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; @@ -273,7 +285,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; @@ -291,7 +303,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 @@ -339,10 +351,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; @@ -351,7 +363,7 @@ class Texture { this._width = Math.floor(width); this._height = Math.floor(height); - this._depth = Math.floor(depth); + this._slices = Math.floor(slices); this._updateNumLevel(); // re-create the implementation @@ -542,7 +554,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; } @@ -705,7 +717,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; } /** @@ -747,12 +768,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); } /** @@ -761,16 +782,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; } /** @@ -779,7 +791,7 @@ class Texture { * @type {boolean} */ get volume() { - return this._volume; + return this._dimension === TEXTUREDIMENSION_3D; } /** @@ -842,7 +854,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; @@ -892,7 +904,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); } @@ -915,7 +927,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; @@ -975,7 +987,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 f9fb0f20b14..a18d273675f 100644 --- a/src/platform/graphics/webgl/webgl-render-target.js +++ b/src/platform/graphics/webgl/webgl-render-target.js @@ -170,7 +170,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, target.mipLevel ); @@ -197,7 +197,7 @@ class WebglRenderTarget { // 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 +348,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 91e2d70f5e6..dccdca01c7f 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) { @@ -455,7 +455,7 @@ class WebglTexture { uploadImmediate(device, texture, immediate) { if (immediate) { - if (!texture._glTexture) { + if (!this._glTexture) { this.initialize(device, texture); } @@ -488,11 +488,11 @@ class WebglTexture { if (texture.array) { // for texture arrays we reserve the space in advance gl.texStorage3D(gl.TEXTURE_2D_ARRAY, - requiredMipLevels, - this._glInternalFormat, - texture._width, - texture._height, - texture._arrayLength); + requiredMipLevels, + this._glInternalFormat, + texture._width, + texture._height, + texture._slices); } // Upload all existing mip levels. Initialize 0 mip anyway. @@ -516,14 +516,14 @@ 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.slices; face++) { + if (!texture._levelsUpdated[0][face]) continue; } @@ -565,8 +565,8 @@ class WebglTexture { } else { // Upload the byte array resMult = 1 / Math.pow(2, mipLevel); - for (face = 0; face < 6; face++) { - if (!texture._levelsUpdated[0][face]) { + for (face = 0; face < texture.slices; face++) { + if (!texture._levelsUpdated[0][face]) continue; } @@ -622,37 +622,37 @@ class WebglTexture { } } } - } else if (texture._volume) { + } else if (texture.volume) { // ----- 3D ----- // Image/canvas/video not supported (yet?) // Upload the byte array if (texture._compressed) { gl.compressedTexImage3D(gl.TEXTURE_3D, - mipLevel, - this._glInternalFormat, - Math.max(texture._width * resMult, 1), - Math.max(texture._height * resMult, 1), - Math.max(texture._depth * resMult, 1), - 0, - mipObject); + mipLevel, + this._glInternalFormat, + Math.max(texture._width * resMult, 1), + Math.max(texture._height * resMult, 1), + Math.max(texture._slices * resMult, 1), + 0, + mipObject); } else { device.setUnpackFlipY(false); device.setUnpackPremultiplyAlpha(texture._premultiplyAlpha); gl.texImage3D(gl.TEXTURE_3D, - mipLevel, - this._glInternalFormat, - Math.max(texture._width * resMult, 1), - Math.max(texture._height * resMult, 1), - Math.max(texture._depth * resMult, 1), - 0, - this._glFormat, - this._glPixelType, - mipObject); + mipLevel, + this._glInternalFormat, + Math.max(texture._width * resMult, 1), + Math.max(texture._height * 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) { + } else if (texture.array && typeof mipObject === "object") { + 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, @@ -667,7 +667,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, @@ -796,8 +796,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 { diff --git a/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js b/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js index 67cd14e1c00..11c0d12700d 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.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 c0c336ce265..677c89031b9 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.slices }, 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.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)) { From 8e4e1e16b1b2208385011969a96f32166c1fea39 Mon Sep 17 00:00:00 2001 From: Catalin Moldovan Date: Tue, 11 Jun 2024 22:12:57 +0300 Subject: [PATCH 3/8] Texture array updates WIP --- src/framework/handlers/texture.js | 8 +- src/platform/graphics/null/null-texture.js | 3 + src/platform/graphics/texture-utils.js | 5 +- src/platform/graphics/texture.js | 83 +++++++++++--------- src/platform/graphics/webgl/webgl-texture.js | 78 +++++++++--------- 5 files changed, 96 insertions(+), 81 deletions(-) diff --git a/src/framework/handlers/texture.js b/src/framework/handlers/texture.js index 4e2e763cc12..2e7b1ab5a5a 100644 --- a/src/framework/handlers/texture.js +++ b/src/framework/handlers/texture.js @@ -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 slice = 0; slice < texture.slices; ++slice) { + mips.push(downsample(width, height, texture._levels[level - 1][slice])); } 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.slices).fill(true)] : [true]; }; /** 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 33e55b8186f..6be5af0f9c4 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'; /** @@ -69,11 +68,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. */ diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index f357b978256..71a7c532e34 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -73,9 +73,6 @@ class Texture { /** @protected */ _invalid = false; - /** @protected */ - _lockedLevel = -1; - /** @protected */ _lockedMode = TEXTURELOCK_NONE; @@ -303,7 +300,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 @@ -854,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._slices).fill(true)] : [true]; this._needsUpload = true; this._needsMipmapsUpload = this._mipmaps; @@ -871,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.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} @@ -882,6 +881,7 @@ class Texture { // Initialize options to some sensible defaults options.level ??= 0; options.face ??= 0; + options.slice ??= 0; options.mode ??= TEXTURELOCK_WRITE; Debug.assert( @@ -896,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.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]; } @@ -918,22 +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, 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 @@ -951,10 +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._slices; i++) { + if (this._levels[0][i] !== source[i]) + this._levelsUpdated[0][i] = true; } } } else { @@ -965,9 +982,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) { width = source.videoWidth; @@ -987,23 +1003,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 @@ -1019,14 +1034,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]; } /** @@ -1042,7 +1054,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 dccdca01c7f..b4098387ed8 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -485,7 +485,7 @@ 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, @@ -523,11 +523,11 @@ 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) { @@ -566,11 +566,11 @@ 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( @@ -650,38 +650,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 { @@ -796,7 +798,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; } From d9f301d8120d7d3633f427b0f225349cead8b6e3 Mon Sep 17 00:00:00 2001 From: Catalin Moldovan Date: Sat, 15 Jun 2024 14:18:35 +0300 Subject: [PATCH 4/8] WebGL _uploadDirtyTextures --- .../graphics/webgl/webgl-graphics-device.js | 26 ++++++++++++++----- .../graphics/webgl/webgl-render-target.js | 2 ++ .../graphics/webgpu/webgpu-graphics-device.js | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) 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 a18d273675f..d4296a0e356 100644 --- a/src/platform/graphics/webgl/webgl-render-target.js +++ b/src/platform/graphics/webgl/webgl-render-target.js @@ -165,6 +165,7 @@ 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( @@ -193,6 +194,7 @@ 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 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(); } }); From 74dc2a172592b362c8af107f1810296ed2450437 Mon Sep 17 00:00:00 2001 From: Catalin Moldovan Date: Sun, 16 Jun 2024 13:03:44 +0300 Subject: [PATCH 5/8] Fix back creation for empty cubemaps --- src/platform/graphics/webgl/webgl-texture.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/graphics/webgl/webgl-texture.js b/src/platform/graphics/webgl/webgl-texture.js index b4098387ed8..87de0390eab 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -567,7 +567,7 @@ class WebglTexture { resMult = 1 / Math.pow(2, mipLevel); for (face = 0; face < texture.slices; face++) { const texData = mipObject[face]; - if (!texture._levelsUpdated[0][face] || !texData) + if (!texture._levelsUpdated[0][face]) continue; } From 9b7e1f6b4eb5135b491ad5d640f19a69c4f5619b Mon Sep 17 00:00:00 2001 From: Catalin Moldovan Date: Mon, 17 Jun 2024 18:05:20 +0300 Subject: [PATCH 6/8] Rename slices to layers to better match WebGPU naming but also gl.framebufferTextureLayer --- .../graphics/texture-array.example.mjs | 4 +- src/framework/handlers/texture.js | 6 +- src/framework/xr/xr-view.js | 2 +- src/platform/graphics/texture-utils.js | 12 ++-- src/platform/graphics/texture.js | 44 +++++++-------- src/platform/graphics/webgl/webgl-texture.js | 56 +++++++++---------- .../graphics/webgpu/webgpu-mipmap-renderer.js | 2 +- .../graphics/webgpu/webgpu-texture.js | 6 +- 8 files changed, 66 insertions(+), 66 deletions(-) diff --git a/examples/src/examples/graphics/texture-array.example.mjs b/examples/src/examples/graphics/texture-array.example.mjs index 4749c5591be..5859e555ba1 100644 --- a/examples/src/examples/graphics/texture-array.example.mjs +++ b/examples/src/examples/graphics/texture-array.example.mjs @@ -108,7 +108,7 @@ assetListLoader.load(() => { dimension: pc.TEXTUREDIMENSION_2D_ARRAY, width: 1024, height: 1024, - slices: 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, @@ -131,7 +131,7 @@ assetListLoader.load(() => { const mipmaps = generateMipmaps(textureArrayOptions.width, textureArrayOptions.height); const levels = mipmaps.map((data) => { const textures = []; - for (let i = 0; i < textureArrayOptions.slices; 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 2e7b1ab5a5a..85d5a65c447 100644 --- a/src/framework/handlers/texture.js +++ b/src/framework/handlers/texture.js @@ -101,8 +101,8 @@ const _completePartialMipmapChain = function (texture) { const height = Math.max(1, texture._height >> (level - 1)); if (texture.cubemap || texture.array) { const mips = []; - for (let slice = 0; slice < texture.slices; ++slice) { - mips.push(downsample(width, height, texture._levels[level - 1][slice])); + 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 || texture.array) ? [Array(texture.slices).fill(true)] : [true]; + texture._levelsUpdated = (texture.cubemap || texture.array) ? [Array(texture.layers).fill(true)] : [true]; }; /** diff --git a/src/framework/xr/xr-view.js b/src/framework/xr/xr-view.js index a8618835cf6..e48ae1fb960 100644 --- a/src/framework/xr/xr-view.js +++ b/src/framework/xr/xr-view.js @@ -173,7 +173,7 @@ class XrView extends EventHandler { this._textureDepth = new Texture(device, { format: this._manager.views.depthPixelFormat, array: viewsCount > 1, - slices: viewsCount, + layers: 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 6be5af0f9c4..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 slices. 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 slices. + * @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,15 +70,15 @@ class TextureUtils { * * @param {number} width - Texture's width. * @param {number} height - Texture's height. - * @param {number} slices - Texture's slices. + * @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. * @returns {number} The number of bytes of GPU memory required for the texture. */ - static calcGpuSize(width, height, slices, format, isVolume, mipmaps) { + static calcGpuSize(width, height, layers, format, isVolume, mipmaps) { let result = 0; - let depth = isVolume ? slices : 1; + let depth = isVolume ? layers : 1; while (1) { result += TextureUtils.calcLevelGpuSize(width, height, depth, format); @@ -92,7 +92,7 @@ class TextureUtils { depth = Math.max(depth >> 1, 1); } - return result * (isVolume ? 1 : slices); + return result * (isVolume ? 1 : layers); } } diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index 71a7c532e34..8a7228775b9 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -102,7 +102,7 @@ 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.slices] - The number of depth slices in a 3D texture, the number of textures + * @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} @@ -232,7 +232,7 @@ 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.slices || Number.isInteger(options.slices), "Texture slices 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 ?? ''; @@ -244,9 +244,9 @@ class Texture { 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)); + this._layers = Math.floor(options.layers ?? (this._dimension === TEXTUREDIMENSION_CUBE ? 6 : 1)); - Debug.assert((this._dimension === TEXTUREDIMENSION_CUBE ? this._slices === 6 : true), "Texture cube map must have 6 slices"); + 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); @@ -300,7 +300,7 @@ class Texture { if (this._levels) { this.upload(options.immediate ?? false); } else { - this._levels = (this.cubemap || this.array) ? [Array(this._slices).fill(null)] : [null]; + this._levels = (this.cubemap || this.array) ? [Array(this._layers).fill(null)] : [null]; } // track the texture @@ -348,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} [slices] - The new number of slices for the texture. Defaults to 1. + * @param {number} [layers] - The new number of layers for the texture. Defaults to 1. * @ignore */ - resize(width, height, slices = 1) { + resize(width, height, layers = 1) { // destroy texture impl const device = this.device; @@ -360,7 +360,7 @@ class Texture { this._width = Math.floor(width); this._height = Math.floor(height); - this._slices = Math.floor(slices); + this._layers = Math.floor(layers); this._updateNumLevel(); // re-create the implementation @@ -709,12 +709,12 @@ 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._dimension === TEXTUREDIMENSION_3D ? this._slices : 1; + return this._dimension === TEXTUREDIMENSION_3D ? this._layers : 1; } /** @@ -722,8 +722,8 @@ class Texture { * * @type {number} */ - get slices() { - return this._slices; + get layers() { + return this._layers; } /** @@ -770,7 +770,7 @@ class Texture { get gpuSize() { const mips = this.pot && this._mipmaps && !(this._compressed && this._levels.length === 1); - return TextureUtils.calcGpuSize(this._width, this._height, this._slices, this._format, this.volume, mips); + return TextureUtils.calcGpuSize(this._width, this._height, this._layers, this._format, this.volume, mips); } /** @@ -851,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 || this.array) ? [Array(this._slices).fill(true)] : [true]; + this._levelsUpdated = (this.cubemap || this.array) ? [Array(this._layers).fill(true)] : [true]; this._needsUpload = true; this._needsMipmapsUpload = this._mipmaps; @@ -868,8 +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.slice] - If the texture is a texture array, this is the index of the - * slice 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} @@ -881,7 +881,7 @@ class Texture { // Initialize options to some sensible defaults options.level ??= 0; options.face ??= 0; - options.slice ??= 0; + options.layer ??= 0; options.mode ??= TEXTURELOCK_WRITE; Debug.assert( @@ -910,7 +910,7 @@ class Texture { this._lockedMode = options.mode; - const levels = this.cubemap ? this._levels[options.face] : this.array ? this._levels[options.slice] : 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); @@ -922,7 +922,7 @@ class Texture { if (this._lockedMode === TEXTURELOCK_WRITE) { if (this.cubemap || this.array) { - this._levelsUpdated[0][options.face ?? options.slice] = true; + this._levelsUpdated[0][options.face ?? options.layer] = true; } else { this._levelsUpdated[0] = true; } @@ -951,7 +951,7 @@ class Texture { width = source[0].width || 0; height = source[0].height || 0; - for (let i = 0; i < this._slices; 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 @@ -969,7 +969,7 @@ class Texture { if (!invalid) { // mark levels as updated - for (let i = 0; i < this._slices; i++) { + for (let i = 0; i < this._layers; i++) { if (this._levels[0][i] !== source[i]) this._levelsUpdated[0][i] = true; } @@ -1004,7 +1004,7 @@ class Texture { // remove levels if (this.cubemap || this.array) { - for (let i = 0; i < this._slices; i++) { + for (let i = 0; i < this._layers; i++) { this._levels[0][i] = null; this._levelsUpdated[0][i] = true; } diff --git a/src/platform/graphics/webgl/webgl-texture.js b/src/platform/graphics/webgl/webgl-texture.js index 87de0390eab..294e8036850 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -488,11 +488,11 @@ class WebglTexture { 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._slices); + requiredMipLevels, + this._glInternalFormat, + texture._width, + texture._height, + texture._layers); } // Upload all existing mip levels. Initialize 0 mip anyway. @@ -522,9 +522,9 @@ class WebglTexture { if (device._isBrowserInterface(mipObject[0])) { // Upload the image, canvas or video - for (face = 0; face < texture.slices; face++) { + for (face = 0; face < texture.layers; face++) { let src = mipObject[face]; - if (!texture._levelsUpdated[0][face] || !src) + if (!texture._levelsUpdated[0][face] || !src) { continue; } @@ -565,9 +565,9 @@ class WebglTexture { } else { // Upload the byte array resMult = 1 / Math.pow(2, mipLevel); - for (face = 0; face < texture.slices; face++) { + for (face = 0; face < texture.layers; face++) { const texData = mipObject[face]; - if (!texture._levelsUpdated[0][face]) + if (!texture._levelsUpdated[0][face]) { continue; } @@ -628,30 +628,30 @@ class WebglTexture { // Upload the byte array if (texture._compressed) { gl.compressedTexImage3D(gl.TEXTURE_3D, - mipLevel, - this._glInternalFormat, - Math.max(texture._width * resMult, 1), - Math.max(texture._height * resMult, 1), - Math.max(texture._slices * resMult, 1), - 0, - mipObject); + mipLevel, + this._glInternalFormat, + Math.max(texture._width * resMult, 1), + Math.max(texture._height * resMult, 1), + Math.max(texture._layers * resMult, 1), + 0, + mipObject); } else { device.setUnpackFlipY(false); device.setUnpackPremultiplyAlpha(texture._premultiplyAlpha); gl.texImage3D(gl.TEXTURE_3D, - mipLevel, - this._glInternalFormat, - Math.max(texture._width * resMult, 1), - Math.max(texture._height * resMult, 1), - Math.max(texture._slices * resMult, 1), - 0, - this._glFormat, - this._glPixelType, - mipObject); + mipLevel, + this._glInternalFormat, + Math.max(texture._width * resMult, 1), + Math.max(texture._height * resMult, 1), + Math.max(texture._layers * resMult, 1), + 0, + this._glFormat, + this._glPixelType, + mipObject); } } else if (texture.array && typeof mipObject === "object") { if (texture._compressed) { - for (let index = 0; index < texture._slices; index++) { + for (let index = 0; index < texture._layers; index++) { if (!texture._levelsUpdated[0][index] || !mipObject[index]) continue; gl.compressedTexSubImage3D( @@ -668,7 +668,7 @@ class WebglTexture { ); } } else { - for (let index = 0; index < texture.slices; index++) { + for (let index = 0; index < texture.layers; index++) { if (!texture._levelsUpdated[0][index] || !mipObject[index]) continue; gl.texSubImage3D( @@ -799,7 +799,7 @@ class WebglTexture { if (texture._needsUpload) { if (texture.cubemap || texture.array) { - for (let i = 0; i < texture.slices; i++) + for (let i = 0; i < texture.layers; i++) { texture._levelsUpdated[0][i] = false; } } else { diff --git a/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js b/src/platform/graphics/webgpu/webgpu-mipmap-renderer.js index 11c0d12700d..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.slices; + 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 677c89031b9..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.slices + depthOrArrayLayers: texture.layers }, format: this.format, mipLevelCount: numLevels, @@ -337,9 +337,9 @@ class WebgpuTexture { } else if (texture.array) { // texture array - if (texture.slices === mipObject.length) { + if (texture.layers === mipObject.length) { - for (let index = 0; index < texture.slices; index++) { + for (let index = 0; index < texture.layers; index++) { const arraySource = mipObject[index]; if (this.isExternalImage(arraySource)) { From b6e59151e1725278f008dd1786d7269b167ddf1c Mon Sep 17 00:00:00 2001 From: Catalin Moldovan Date: Fri, 23 Aug 2024 12:58:51 +0300 Subject: [PATCH 7/8] Fix linting errors --- src/platform/graphics/texture.js | 16 +++++++++------- src/platform/graphics/webgl/webgl-texture.js | 8 +++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index 8a7228775b9..8d85fa84f82 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -229,10 +229,10 @@ class Texture { */ constructor(graphicsDevice, options = {}) { this.device = graphicsDevice; - 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.layers || Number.isInteger(options.layers), "Texture layers must be an integer number, got", options); + 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.layers || Number.isInteger(options.layers), 'Texture layers must be an integer number, got', options); this.name = options.name ?? ''; @@ -246,7 +246,7 @@ class Texture { 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"); + 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); @@ -970,8 +970,9 @@ class Texture { if (!invalid) { // mark levels as updated for (let i = 0; i < this._layers; i++) { - if (this._levels[0][i] !== source[i]) + if (this._levels[0][i] !== source[i]) { this._levelsUpdated[0][i] = true; + } } } } else { @@ -982,8 +983,9 @@ class Texture { if (!invalid) { // mark level as updated - if (source !== this._levels[0]) + if (source !== this._levels[0]) { this._levelsUpdated[0] = true; + } if (source instanceof HTMLVideoElement) { width = source.videoWidth; diff --git a/src/platform/graphics/webgl/webgl-texture.js b/src/platform/graphics/webgl/webgl-texture.js index 294e8036850..a2b73045833 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -649,11 +649,12 @@ class WebglTexture { this._glPixelType, mipObject); } - } else if (texture.array && typeof mipObject === "object") { + } else if (texture.array && typeof mipObject === 'object') { if (texture._compressed) { for (let index = 0; index < texture._layers; index++) { - if (!texture._levelsUpdated[0][index] || !mipObject[index]) + if (!texture._levelsUpdated[0][index] || !mipObject[index]) { continue; + } gl.compressedTexSubImage3D( gl.TEXTURE_2D_ARRAY, mipLevel, @@ -669,8 +670,9 @@ class WebglTexture { } } else { for (let index = 0; index < texture.layers; index++) { - if (!texture._levelsUpdated[0][index] || !mipObject[index]) + if (!texture._levelsUpdated[0][index] || !mipObject[index]) { continue; + } gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, mipLevel, From 5b2b715dabe84a129323417f66f491075c1acdd3 Mon Sep 17 00:00:00 2001 From: Catalin Moldovan Date: Tue, 3 Sep 2024 13:09:07 +0300 Subject: [PATCH 8/8] Fix lightmapper baking for WebGL --- src/framework/lightmapper/lightmapper.js | 2 ++ 1 file changed, 2 insertions(+) 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);