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..94af165cb7f 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -89,6 +89,12 @@ class Texture { /** @protected */ _storage = false; + /** @protected */ + _numLevels = 0; + + /** @protected */ + _numLevelsRequested; + /** * 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.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. * @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._numLevelsRequested = options.numLevels; + if (options.numLevels !== undefined) { + this._numLevels = options.numLevels; + } + this._updateNumLevel(); + 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.numLevels}]`, this); } /** @@ -336,6 +350,7 @@ class Texture { this._width = Math.floor(width); this._height = Math.floor(height); this._depth = Math.floor(depth); + this._updateNumLevel(); // 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; + _updateNumLevel() { + + const maxLevels = this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1; + const requestedLevels = this._numLevelsRequested; + if (requestedLevels !== undefined && requestedLevels > maxLevels) { + Debug.warn('Texture#numLevels: requested mip level count is greater than the maximum possible, will be clamped to', maxLevels, this); + } + + this._numLevels = Math.min(requestedLevels ?? maxLevels, maxLevels); + this._mipmaps = this._numLevels > 1; } /** @@ -644,6 +661,15 @@ class Texture { return this._mipmaps; } + /** + * Gets the number of mip levels. + * + * @type {number} + */ + get numLevels() { + return this._numLevels; + } + /** * 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..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.requiredMipLevels; + 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 d6063ad6ae9..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.requiredMipLevels; + 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.requiredMipLevels; + const requiredMipLevels = texture.numLevels; 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); }