From 5f609f036d501cb49f9ab9b7bf12d2adba51f257 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Mon, 2 Dec 2024 15:00:59 +0000 Subject: [PATCH 1/3] Texture can be constructed with a specified number of mip levels --- .../graphics/integer-textures.example.mjs | 9 ++-- src/platform/graphics/render-pass.js | 8 ++-- src/platform/graphics/texture.js | 48 ++++++++++++++----- src/platform/graphics/webgl/webgl-texture.js | 2 +- .../graphics/webgpu/webgpu-texture.js | 6 +-- 5 files changed, 52 insertions(+), 21 deletions(-) diff --git a/examples/src/examples/graphics/integer-textures.example.mjs b/examples/src/examples/graphics/integer-textures.example.mjs index bf7ea5e95ae..e7b876be3ed 100644 --- a/examples/src/examples/graphics/integer-textures.example.mjs +++ b/examples/src/examples/graphics/integer-textures.example.mjs @@ -81,13 +81,15 @@ const createPixelColorBuffer = (i) => { name: `PixelBuffer_${i}`, width: TEXTURE_WIDTH, height: TEXTURE_HEIGHT, + mipmaps: false, + addressU: pc.ADDRESS_CLAMP_TO_EDGE, + addressV: pc.ADDRESS_CLAMP_TO_EDGE, + // Note that we are using an unsigned integer format here. // This can be helpful for storing bitfields in each pixel. // In this example, we are storing 3 different properties // in a single Uint8 value. - format: pc.PIXELFORMAT_R8U, - addressU: pc.ADDRESS_CLAMP_TO_EDGE, - addressV: pc.ADDRESS_CLAMP_TO_EDGE + format: pc.PIXELFORMAT_R8U }); }; const createPixelRenderTarget = (i, colorBuffer) => { @@ -114,6 +116,7 @@ const outputTexture = new pc.Texture(device, { name: 'OutputTexture', width: TEXTURE_WIDTH, height: TEXTURE_HEIGHT, + mipmaps: false, format: pc.PIXELFORMAT_RGBA8, minFilter: pc.FILTER_LINEAR_MIPMAP_LINEAR, magFilter: pc.FILTER_LINEAR, diff --git a/src/platform/graphics/render-pass.js b/src/platform/graphics/render-pass.js index 1d0e0de29c2..0f952fb6e29 100644 --- a/src/platform/graphics/render-pass.js +++ b/src/platform/graphics/render-pass.js @@ -2,7 +2,7 @@ import { Debug } from '../../core/debug.js'; import { Tracing } from '../../core/tracing.js'; import { Color } from '../../core/math/color.js'; import { TRACEID_RENDER_PASS, TRACEID_RENDER_PASS_DETAIL } from '../../core/constants.js'; -import { pixelFormatInfo } from './constants.js'; +import { isIntegerPixelFormat, pixelFormatInfo } from './constants.js'; /** * @import { GraphicsDevice } from '../graphics/graphics-device.js' @@ -315,8 +315,10 @@ class RenderPass { } // if render target needs mipmaps - if (this.renderTarget?.mipmaps && this.renderTarget?._colorBuffers?.[i].mipmaps) { - colorOps.genMipmaps = true; + const colorBuffer = this.renderTarget?._colorBuffers?.[i]; + if (this.renderTarget?.mipmaps && colorBuffer?.mipmaps) { + const intFormat = isIntegerPixelFormat(colorBuffer._format); + colorOps.genMipmaps = !intFormat; // no automatic mipmap generation for integer formats } } } diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index 96feba34c2f..96374aef716 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -89,6 +89,12 @@ class Texture { /** @protected */ _storage = false; + /** @protected */ + _mipLevelCount = 0; + + /** @protected */ + _mipLevelCountRequested; + /** * Create a new Texture instance. * @@ -149,6 +155,9 @@ class Texture { * {@link ADDRESS_REPEAT}. * @param {boolean} [options.mipmaps] - When enabled try to generate or use mipmaps for this * texture. Default is true. + * @param {number} [options.mipLevelCount] - Specifies the number of mip levels to generate. If + * not specified, the number is calculated based on the texture size. When this property is set, + * 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. @@ -227,7 +236,6 @@ class Texture { this._compressed = isCompressedPixelFormat(this._format); this._integerFormat = isIntegerPixelFormat(this._format); if (this._integerFormat) { - options.mipmaps = false; options.minFilter = FILTER_NEAREST; options.magFilter = FILTER_NEAREST; } @@ -241,7 +249,13 @@ class Texture { this._flipY = options.flipY ?? false; this._premultiplyAlpha = options.premultiplyAlpha ?? false; - this._mipmaps = options.mipmaps ?? options.autoMipmap ?? true; + this._mipmaps = options.mipmaps ?? true; + this._mipLevelCountRequested = options.mipLevelCount; + if (options.mipLevelCount !== undefined) { + this._mipLevelCount = options.mipLevelCount; + } + this._updateMipLevelCount(); + this._minFilter = options.minFilter ?? FILTER_LINEAR_MIPMAP_LINEAR; this._magFilter = options.magFilter ?? FILTER_LINEAR; this._anisotropy = options.anisotropy ?? 1; @@ -285,7 +299,7 @@ class Texture { `${this.cubemap ? '[Cubemap]' : ''}` + `${this.volume ? '[Volume]' : ''}` + `${this.array ? '[Array]' : ''}` + - `${this.mipmaps ? '[Mipmaps]' : ''}`, this); + `[MipLevels:${this.mipLevelCount}]`, this); } /** @@ -336,6 +350,7 @@ class Texture { this._width = Math.floor(width); this._height = Math.floor(height); this._depth = Math.floor(depth); + this._updateMipLevelCount(); // re-create the implementation this.impl = device.createTextureImpl(this); @@ -379,14 +394,16 @@ class Texture { this.renderVersionDirty = this.device.renderVersion; } - /** - * Returns number of required mip levels for the texture based on its dimensions and parameters. - * - * @ignore - * @type {number} - */ - get requiredMipLevels() { - return this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1; + _updateMipLevelCount() { + + const maxLevels = this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1; + const requestedLevels = this._mipLevelCountRequested; + if (requestedLevels !== undefined && requestedLevels > maxLevels) { + Debug.warn('Texture#mipLevelCount: requested mip level count is greater than the maximum possible, will be clamped to', maxLevels, this); + } + + this._mipLevelCount = Math.min(requestedLevels ?? maxLevels, maxLevels); + this._mipmaps = this._mipLevelCount > 1; } /** @@ -644,6 +661,15 @@ class Texture { return this._mipmaps; } + /** + * Gets the number of mip levels. + * + * @type {number} + */ + get mipLevelCount() { + return this._mipLevelCount; + } + /** * Defines if texture can be used as a storage texture by a compute shader. * diff --git a/src/platform/graphics/webgl/webgl-texture.js b/src/platform/graphics/webgl/webgl-texture.js index 391a69fa3cf..93eac630c44 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -470,7 +470,7 @@ class WebglTexture { let mipObject; let resMult; - const requiredMipLevels = texture.requiredMipLevels; + const requiredMipLevels = texture.mipLevelCount; if (texture.array) { // for texture arrays we reserve the space in advance diff --git a/src/platform/graphics/webgpu/webgpu-texture.js b/src/platform/graphics/webgpu/webgpu-texture.js index d6063ad6ae9..c0e63ae1018 100644 --- a/src/platform/graphics/webgpu/webgpu-texture.js +++ b/src/platform/graphics/webgpu/webgpu-texture.js @@ -92,7 +92,7 @@ class WebgpuTexture { const texture = this.texture; const wgpu = device.wgpu; - const mipLevelCount = texture.requiredMipLevels; + const mipLevelCount = texture.mipLevelCount; Debug.assert(texture.width > 0 && texture.height > 0, `Invalid texture dimensions ${texture.width}x${texture.height} for texture ${texture.name}`, texture); @@ -300,7 +300,7 @@ class WebgpuTexture { // upload texture data if any let anyUploads = false; let anyLevelMissing = false; - const requiredMipLevels = texture.requiredMipLevels; + const requiredMipLevels = texture.mipLevelCount; for (let mipLevel = 0; mipLevel < requiredMipLevels; mipLevel++) { const mipObject = texture._levels[mipLevel]; @@ -383,7 +383,7 @@ class WebgpuTexture { } } - if (anyUploads && anyLevelMissing && texture.mipmaps && !isCompressedPixelFormat(texture.format)) { + if (anyUploads && anyLevelMissing && texture.mipmaps && !isCompressedPixelFormat(texture.format) && !isIntegerPixelFormat(texture.format)) { device.mipmapRenderer.generate(this); } From 83a870e0e20dac8d08a3375e49e0c98a526e73cd Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Mon, 2 Dec 2024 15:53:46 +0000 Subject: [PATCH 2/3] renames --- src/platform/graphics/texture.js | 28 +++++++++---------- src/platform/graphics/webgl/webgl-texture.js | 2 +- .../graphics/webgpu/webgpu-texture.js | 6 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index 96374aef716..45722e3cf3d 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -90,10 +90,10 @@ class Texture { _storage = false; /** @protected */ - _mipLevelCount = 0; + _numLevels = 0; /** @protected */ - _mipLevelCountRequested; + _numLevelsRequested; /** * Create a new Texture instance. @@ -155,8 +155,8 @@ class Texture { * {@link ADDRESS_REPEAT}. * @param {boolean} [options.mipmaps] - When enabled try to generate or use mipmaps for this * texture. Default is true. - * @param {number} [options.mipLevelCount] - Specifies the number of mip levels to generate. If - * not specified, the number is calculated based on the texture size. When this property is set, + * @param {number} [options.numLevels] - Specifies the number of mip levels to generate. If not + * specified, the number is calculated based on the texture size. When this property is set, * the mipmaps property is ignored. * @param {boolean} [options.cubemap] - Specifies whether the texture is to be a cubemap. * Defaults to false. @@ -250,9 +250,9 @@ class Texture { this._premultiplyAlpha = options.premultiplyAlpha ?? false; this._mipmaps = options.mipmaps ?? true; - this._mipLevelCountRequested = options.mipLevelCount; - if (options.mipLevelCount !== undefined) { - this._mipLevelCount = options.mipLevelCount; + this._numLevelsRequested = options.numLevels; + if (options.numLevels !== undefined) { + this._numLevels = options.numLevels; } this._updateMipLevelCount(); @@ -299,7 +299,7 @@ class Texture { `${this.cubemap ? '[Cubemap]' : ''}` + `${this.volume ? '[Volume]' : ''}` + `${this.array ? '[Array]' : ''}` + - `[MipLevels:${this.mipLevelCount}]`, this); + `[MipLevels:${this.numLevels}]`, this); } /** @@ -397,13 +397,13 @@ class Texture { _updateMipLevelCount() { const maxLevels = this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1; - const requestedLevels = this._mipLevelCountRequested; + const requestedLevels = this._numLevelsRequested; if (requestedLevels !== undefined && requestedLevels > maxLevels) { - Debug.warn('Texture#mipLevelCount: requested mip level count is greater than the maximum possible, will be clamped to', maxLevels, this); + Debug.warn('Texture#numLevels: requested mip level count is greater than the maximum possible, will be clamped to', maxLevels, this); } - this._mipLevelCount = Math.min(requestedLevels ?? maxLevels, maxLevels); - this._mipmaps = this._mipLevelCount > 1; + this._numLevels = Math.min(requestedLevels ?? maxLevels, maxLevels); + this._mipmaps = this._numLevels > 1; } /** @@ -666,8 +666,8 @@ class Texture { * * @type {number} */ - get mipLevelCount() { - return this._mipLevelCount; + get numLevels() { + return this._numLevels; } /** diff --git a/src/platform/graphics/webgl/webgl-texture.js b/src/platform/graphics/webgl/webgl-texture.js index 93eac630c44..c40c5b43418 100644 --- a/src/platform/graphics/webgl/webgl-texture.js +++ b/src/platform/graphics/webgl/webgl-texture.js @@ -470,7 +470,7 @@ class WebglTexture { let mipObject; let resMult; - const requiredMipLevels = texture.mipLevelCount; + const requiredMipLevels = texture.numLevels; if (texture.array) { // for texture arrays we reserve the space in advance diff --git a/src/platform/graphics/webgpu/webgpu-texture.js b/src/platform/graphics/webgpu/webgpu-texture.js index c0e63ae1018..c0c336ce265 100644 --- a/src/platform/graphics/webgpu/webgpu-texture.js +++ b/src/platform/graphics/webgpu/webgpu-texture.js @@ -92,7 +92,7 @@ class WebgpuTexture { const texture = this.texture; const wgpu = device.wgpu; - const mipLevelCount = texture.mipLevelCount; + const numLevels = texture.numLevels; Debug.assert(texture.width > 0 && texture.height > 0, `Invalid texture dimensions ${texture.width}x${texture.height} for texture ${texture.name}`, texture); @@ -103,7 +103,7 @@ class WebgpuTexture { depthOrArrayLayers: texture.cubemap ? 6 : (texture.array ? texture.arrayLength : 1) }, format: this.format, - mipLevelCount: mipLevelCount, + mipLevelCount: numLevels, sampleCount: 1, dimension: texture.volume ? '3d' : '2d', @@ -300,7 +300,7 @@ class WebgpuTexture { // upload texture data if any let anyUploads = false; let anyLevelMissing = false; - const requiredMipLevels = texture.mipLevelCount; + const requiredMipLevels = texture.numLevels; for (let mipLevel = 0; mipLevel < requiredMipLevels; mipLevel++) { const mipObject = texture._levels[mipLevel]; From 5c49967f17f68b336fb96df3b3cf6bfe28ad560a Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Mon, 2 Dec 2024 15:55:22 +0000 Subject: [PATCH 3/3] another rename --- src/platform/graphics/texture.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index 45722e3cf3d..94af165cb7f 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -254,7 +254,7 @@ class Texture { if (options.numLevels !== undefined) { this._numLevels = options.numLevels; } - this._updateMipLevelCount(); + this._updateNumLevel(); this._minFilter = options.minFilter ?? FILTER_LINEAR_MIPMAP_LINEAR; this._magFilter = options.magFilter ?? FILTER_LINEAR; @@ -350,7 +350,7 @@ class Texture { this._width = Math.floor(width); this._height = Math.floor(height); this._depth = Math.floor(depth); - this._updateMipLevelCount(); + this._updateNumLevel(); // re-create the implementation this.impl = device.createTextureImpl(this); @@ -394,7 +394,7 @@ class Texture { this.renderVersionDirty = this.device.renderVersion; } - _updateMipLevelCount() { + _updateNumLevel() { const maxLevels = this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1; const requestedLevels = this._numLevelsRequested;