From 4eb6eb8454b2d5f9825cc0a5af3cdf5d2f3292df Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Fri, 13 Feb 2026 11:53:31 +0000 Subject: [PATCH] Refactor data texture creation using internal Texture helper Add Texture.createDataTexture2D as an internal API and migrate repeated nearest/clamp/no-mipmap 2D texture creation to this helper, including optional levels upload support. Co-authored-by: Cursor --- .../render-passes/render-pass-prepass.js | 14 +------- src/extras/render-passes/render-pass-ssao.js | 14 ++------ .../components/camera/post-effect-queue.js | 14 ++------ src/framework/graphics/picker.js | 14 ++------ src/platform/graphics/texture.js | 30 +++++++++++++++- src/scene/graphics/render-pass-depth-grab.js | 20 +++++------ src/scene/gsplat-unified/gsplat-info.js | 27 ++------------- src/scene/gsplat-unified/gsplat-octree.js | 15 ++------ .../gsplat-unified/gsplat-work-buffer.js | 34 +++---------------- src/scene/gsplat/gsplat-streams.js | 19 +---------- src/scene/lighting/light-texture-atlas.js | 15 ++------ 11 files changed, 57 insertions(+), 159 deletions(-) diff --git a/src/extras/render-passes/render-pass-prepass.js b/src/extras/render-passes/render-pass-prepass.js index f4878273064..ad928a21a8d 100644 --- a/src/extras/render-passes/render-pass-prepass.js +++ b/src/extras/render-passes/render-pass-prepass.js @@ -1,6 +1,4 @@ import { - FILTER_NEAREST, - ADDRESS_CLAMP_TO_EDGE, PIXELFORMAT_R32F, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js'; @@ -70,17 +68,7 @@ class RenderPassPrepass extends RenderPass { const { device } = this; this.linearDepthFormat = device.textureFloatRenderable ? PIXELFORMAT_R32F : PIXELFORMAT_RGBA8; - this.linearDepthTexture = new Texture(device, { - name: 'SceneLinearDepthTexture', - width: 1, - height: 1, - format: this.linearDepthFormat, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }); + this.linearDepthTexture = Texture.createDataTexture2D(device, 'SceneLinearDepthTexture', 1, 1, this.linearDepthFormat); const renderTarget = new RenderTarget({ name: 'PrepassRT', diff --git a/src/extras/render-passes/render-pass-ssao.js b/src/extras/render-passes/render-pass-ssao.js index f47f5f9b16a..c2f416e9301 100644 --- a/src/extras/render-passes/render-pass-ssao.js +++ b/src/extras/render-passes/render-pass-ssao.js @@ -2,7 +2,7 @@ import { BlueNoise } from '../../core/math/blue-noise.js'; import { Color } from '../../core/math/color.js'; import { math } from '../../core/math/math.js'; import { - ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_R8, SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, + PIXELFORMAT_R8, SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../../platform/graphics/constants.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; @@ -173,17 +173,7 @@ class RenderPassSsao extends RenderPassShaderQuad { createRenderTarget(name) { return new RenderTarget({ depth: false, - colorBuffer: new Texture(this.device, { - name: name, - width: 1, - height: 1, - format: PIXELFORMAT_R8, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }) + colorBuffer: Texture.createDataTexture2D(this.device, name, 1, 1, PIXELFORMAT_R8) }); } diff --git a/src/framework/components/camera/post-effect-queue.js b/src/framework/components/camera/post-effect-queue.js index 61ff90b8130..0de680af502 100644 --- a/src/framework/components/camera/post-effect-queue.js +++ b/src/framework/components/camera/post-effect-queue.js @@ -1,4 +1,4 @@ -import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA8, PIXELFORMAT_SRGBA8 } from '../../../platform/graphics/constants.js'; +import { PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA8, PIXELFORMAT_SRGBA8 } from '../../../platform/graphics/constants.js'; import { DebugGraphics } from '../../../platform/graphics/debug-graphics.js'; import { RenderTarget } from '../../../platform/graphics/render-target.js'; import { Texture } from '../../../platform/graphics/texture.js'; @@ -83,17 +83,7 @@ class PostEffectQueue { const width = Math.floor(rect.z * (renderTarget?.width ?? device.width)); const height = Math.floor(rect.w * (renderTarget?.height ?? device.height)); - const colorBuffer = new Texture(device, { - name: name, - format: format, - width: width, - height: height, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }); + const colorBuffer = Texture.createDataTexture2D(device, name, width, height, format); return colorBuffer; } diff --git a/src/framework/graphics/picker.js b/src/framework/graphics/picker.js index 1b594aaf125..e0d11e80eb9 100644 --- a/src/framework/graphics/picker.js +++ b/src/framework/graphics/picker.js @@ -1,5 +1,5 @@ import { Color } from '../../core/math/color.js'; -import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js'; +import { PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; import { Texture } from '../../platform/graphics/texture.js'; import { Layer } from '../../scene/layer.js'; @@ -398,17 +398,7 @@ class Picker { } createTexture(name) { - return new Texture(this.device, { - format: PIXELFORMAT_RGBA8, - width: this.width, - height: this.height, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE, - name: name - }); + return Texture.createDataTexture2D(this.device, name, this.width, this.height, PIXELFORMAT_RGBA8); } allocateRenderTarget() { diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js index 34c8efa8ceb..7eade76e225 100644 --- a/src/platform/graphics/texture.js +++ b/src/platform/graphics/texture.js @@ -4,7 +4,7 @@ import { math } from '../../core/math/math.js'; import { isCompressedPixelFormat, getPixelFormatArrayType, - ADDRESS_REPEAT, + ADDRESS_REPEAT, ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, FUNC_LESS, PIXELFORMAT_RGBA8, @@ -66,6 +66,34 @@ let id = 0; * @category Graphics */ class Texture { + /** + * Creates a 2D data texture with nearest filtering, clamp-to-edge addressing and no mipmaps. + * + * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this texture. + * @param {string} name - The name of the texture. + * @param {number} width - The width of the texture in pixels. + * @param {number} height - The height of the texture in pixels. + * @param {number} format - The pixel format of the texture. + * @param {Uint8Array[]|Uint16Array[]|Uint32Array[]|Float32Array[]|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]|Uint8Array[][]} [levels] + * - Optional initial mip level data. + * @returns {Texture} The created texture. + * @ignore + */ + static createDataTexture2D(graphicsDevice, name, width, height, format, levels) { + return new Texture(graphicsDevice, { + name, + width, + height, + format, + mipmaps: false, + minFilter: FILTER_NEAREST, + magFilter: FILTER_NEAREST, + addressU: ADDRESS_CLAMP_TO_EDGE, + addressV: ADDRESS_CLAMP_TO_EDGE, + levels + }); + } + /** * The name of the texture. * diff --git a/src/scene/graphics/render-pass-depth-grab.js b/src/scene/graphics/render-pass-depth-grab.js index dc46d94a117..dcd258f2b65 100644 --- a/src/scene/graphics/render-pass-depth-grab.js +++ b/src/scene/graphics/render-pass-depth-grab.js @@ -1,4 +1,4 @@ -import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_DEPTH, PIXELFORMAT_DEPTHSTENCIL, PIXELFORMAT_R32F } from '../../platform/graphics/constants.js'; +import { PIXELFORMAT_DEPTH, PIXELFORMAT_DEPTHSTENCIL, PIXELFORMAT_R32F } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; @@ -38,17 +38,13 @@ class RenderPassDepthGrab extends RenderPass { allocateRenderTarget(renderTarget, sourceRenderTarget, device, format, isDepth) { // allocate texture buffer - const texture = new Texture(device, { - name: _depthUniformName, - format, - width: sourceRenderTarget ? sourceRenderTarget.colorBuffer.width : device.width, - height: sourceRenderTarget ? sourceRenderTarget.colorBuffer.height : device.height, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }); + const texture = Texture.createDataTexture2D( + device, + _depthUniformName, + sourceRenderTarget ? sourceRenderTarget.colorBuffer.width : device.width, + sourceRenderTarget ? sourceRenderTarget.colorBuffer.height : device.height, + format + ); if (renderTarget) { diff --git a/src/scene/gsplat-unified/gsplat-info.js b/src/scene/gsplat-unified/gsplat-info.js index 0078c82193a..9094fa050fe 100644 --- a/src/scene/gsplat-unified/gsplat-info.js +++ b/src/scene/gsplat-unified/gsplat-info.js @@ -3,7 +3,7 @@ import { Mat4 } from '../../core/math/mat4.js'; import { Vec2 } from '../../core/math/vec2.js'; import { Vec4 } from '../../core/math/vec4.js'; import { BoundingBox } from '../../core/shape/bounding-box.js'; -import { PIXELFORMAT_R32U, PIXELFORMAT_RGBA32U, FILTER_NEAREST, ADDRESS_CLAMP_TO_EDGE } from '../../platform/graphics/constants.js'; +import { PIXELFORMAT_R32U, PIXELFORMAT_RGBA32U } from '../../platform/graphics/constants.js'; import { Texture } from '../../platform/graphics/texture.js'; import { TextureUtils } from '../../platform/graphics/texture-utils.js'; @@ -355,17 +355,7 @@ class GSplatInfo { const { x: texWidth, y: texHeight } = TextureUtils.calcTextureSize(subDrawCount, tmpSize); // Create the sub-draw data texture - this.subDrawTexture = new Texture(this.device, { - name: 'subDrawData', - width: texWidth, - height: texHeight, - format: PIXELFORMAT_RGBA32U, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }); + this.subDrawTexture = Texture.createDataTexture2D(this.device, 'subDrawData', texWidth, texHeight, PIXELFORMAT_RGBA32U); // Upload sub-draw data const texData = this.subDrawTexture.lock(); @@ -416,18 +406,7 @@ class GSplatInfo { this.numBoundsEntries = localIdx; // Create the texture with initial data - this.nodeToLocalBoundsTexture = new Texture(this.device, { - name: 'nodeToLocalBoundsTexture', - width: width, - height: height, - format: PIXELFORMAT_R32U, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE, - levels: [data] - }); + this.nodeToLocalBoundsTexture = Texture.createDataTexture2D(this.device, 'nodeToLocalBoundsTexture', width, height, PIXELFORMAT_R32U, [data]); } /** diff --git a/src/scene/gsplat-unified/gsplat-octree.js b/src/scene/gsplat-unified/gsplat-octree.js index b7c54b06816..3b3dece79b8 100644 --- a/src/scene/gsplat-unified/gsplat-octree.js +++ b/src/scene/gsplat-unified/gsplat-octree.js @@ -3,7 +3,7 @@ import { path } from '../../core/path.js'; import { Debug } from '../../core/debug.js'; import { Tracing } from '../../core/tracing.js'; import { TRACEID_OCTREE_RESOURCES } from '../../core/constants.js'; -import { PIXELFORMAT_R8U, PIXELFORMAT_R16U, PIXELFORMAT_R32U, FILTER_NEAREST, ADDRESS_CLAMP_TO_EDGE } from '../../platform/graphics/constants.js'; +import { PIXELFORMAT_R8U, PIXELFORMAT_R16U, PIXELFORMAT_R32U } from '../../platform/graphics/constants.js'; import { Texture } from '../../platform/graphics/texture.js'; // Temporary array reused to avoid allocations during cooldown ticking @@ -263,18 +263,7 @@ class GSplatOctree { } } - return new Texture(resource.device, { - name: `nodeMappingTexture-${fileIndex}`, - width: dim.x, - height: dim.y, - format: format, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE, - levels: [data] - }); + return Texture.createDataTexture2D(resource.device, `nodeMappingTexture-${fileIndex}`, dim.x, dim.y, format, [data]); } getFileResource(fileIndex) { diff --git a/src/scene/gsplat-unified/gsplat-work-buffer.js b/src/scene/gsplat-unified/gsplat-work-buffer.js index 155b2f25ca5..3ded0abd864 100644 --- a/src/scene/gsplat-unified/gsplat-work-buffer.js +++ b/src/scene/gsplat-unified/gsplat-work-buffer.js @@ -3,7 +3,7 @@ import { Frustum } from '../../core/shape/frustum.js'; import { Mat4 } from '../../core/math/mat4.js'; import { Vec2 } from '../../core/math/vec2.js'; import { - ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_R32U, PIXELFORMAT_RGBA16U, PIXELFORMAT_RGBA32F, + ADDRESS_CLAMP_TO_EDGE, PIXELFORMAT_R32U, PIXELFORMAT_RGBA16U, PIXELFORMAT_RGBA32F, BUFFERUSAGE_COPY_DST, SEMANTIC_POSITION, getGlslShaderType } from '../../platform/graphics/constants.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; @@ -329,30 +329,6 @@ class GSplatWorkBuffer { } } - /** - * Creates a nearest-filtered, clamp-to-edge texture with no mipmaps. - * - * @param {string} name - Debug name for the texture. - * @param {number} width - Texture width. - * @param {number} height - Texture height. - * @param {number} format - Pixel format constant. - * @returns {Texture} The created texture. - * @private - */ - _createTexture(name, width, height, format) { - return new Texture(this.device, { - name: name, - width: width, - height: height, - format: format, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }); - } - /** * Gets a texture by name. * @@ -469,14 +445,14 @@ class GSplatWorkBuffer { // Create/resize bounds sphere texture (RGBA32F: center.xyz, radius) if (!this.boundsSphereTexture) { - this.boundsSphereTexture = this._createTexture('boundsSphereTexture', width, height, PIXELFORMAT_RGBA32F); + this.boundsSphereTexture = Texture.createDataTexture2D(this.device, 'boundsSphereTexture', width, height, PIXELFORMAT_RGBA32F); } else { this.boundsSphereTexture.resize(width, height); } // Create/resize transform index texture (R32U: GSplatInfo index per bounds entry) if (!this.boundsTransformIndexTexture) { - this.boundsTransformIndexTexture = this._createTexture('boundsTransformIndexTexture', width, height, PIXELFORMAT_R32U); + this.boundsTransformIndexTexture = Texture.createDataTexture2D(this.device, 'boundsTransformIndexTexture', width, height, PIXELFORMAT_R32U); } else { this.boundsTransformIndexTexture.resize(width, height); } @@ -518,7 +494,7 @@ class GSplatWorkBuffer { const { x: width, y: height } = TextureUtils.calcTextureSize(totalTexels, tmpSize, 3); if (!this.transformsTexture) { - this.transformsTexture = this._createTexture('transformsTexture', width, height, PIXELFORMAT_RGBA32F); + this.transformsTexture = Texture.createDataTexture2D(this.device, 'transformsTexture', width, height, PIXELFORMAT_RGBA32F); } else { this.transformsTexture.resize(width, height); } @@ -579,7 +555,7 @@ class GSplatWorkBuffer { // Create/resize visibility texture (R32U: bit-packed, 32 spheres per texel) if (!this.nodeVisibilityTexture) { - this.nodeVisibilityTexture = this._createTexture('nodeVisibilityTexture', width, height, PIXELFORMAT_R32U); + this.nodeVisibilityTexture = Texture.createDataTexture2D(this.device, 'nodeVisibilityTexture', width, height, PIXELFORMAT_R32U); this.cullingRenderTarget = new RenderTarget({ name: 'NodeCullingRT', diff --git a/src/scene/gsplat/gsplat-streams.js b/src/scene/gsplat/gsplat-streams.js index 1c0e0aa0b94..2fb80863ce9 100644 --- a/src/scene/gsplat/gsplat-streams.js +++ b/src/scene/gsplat/gsplat-streams.js @@ -1,5 +1,4 @@ import { Vec2 } from '../../core/math/vec2.js'; -import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST } from '../../platform/graphics/constants.js'; import { Texture } from '../../platform/graphics/texture.js'; import { TextureUtils } from '../../platform/graphics/texture-utils.js'; @@ -198,23 +197,7 @@ class GSplatStreams { * @returns {Texture} The created texture instance. */ createTexture(name, format, size, data) { - /** @type {object} */ - const options = { - name: name, - width: size.x, - height: size.y, - format: format, - cubemap: false, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }; - if (data) { - options.levels = [data]; - } - return new Texture(this.device, /** @type {any} */ (options)); + return Texture.createDataTexture2D(this.device, name, size.x, size.y, format, data ? [data] : undefined); } } diff --git a/src/scene/lighting/light-texture-atlas.js b/src/scene/lighting/light-texture-atlas.js index 389999336f0..13e5de96d98 100644 --- a/src/scene/lighting/light-texture-atlas.js +++ b/src/scene/lighting/light-texture-atlas.js @@ -1,7 +1,7 @@ import { Vec2 } from '../../core/math/vec2.js'; import { Vec4 } from '../../core/math/vec4.js'; -import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_SRGBA8 } from '../../platform/graphics/constants.js'; +import { PIXELFORMAT_SRGBA8 } from '../../platform/graphics/constants.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; import { Texture } from '../../platform/graphics/texture.js'; @@ -37,18 +37,7 @@ class LightTextureAtlas { this.shadowEdgePixels = 3; this.cookieAtlasResolution = 4; - this.cookieAtlas = new Texture(this.device, { - name: 'CookieAtlas', - width: this.cookieAtlasResolution, - height: this.cookieAtlasResolution, - format: PIXELFORMAT_SRGBA8, - cubemap: false, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }); + this.cookieAtlas = Texture.createDataTexture2D(this.device, 'CookieAtlas', this.cookieAtlasResolution, this.cookieAtlasResolution, PIXELFORMAT_SRGBA8); this.cookieRenderTarget = new RenderTarget({ colorBuffer: this.cookieAtlas,