diff --git a/packages/dev/core/src/Engines/Extensions/engine.multiRender.ts b/packages/dev/core/src/Engines/Extensions/engine.multiRender.ts index 0ad6f97e39a..0d7570b99f0 100644 --- a/packages/dev/core/src/Engines/Extensions/engine.multiRender.ts +++ b/packages/dev/core/src/Engines/Extensions/engine.multiRender.ts @@ -140,7 +140,7 @@ ThinEngine.prototype.unBindMultiColorAttachmentFramebuffer = function ( for (let i = 0; i < count; i++) { const texture = rtWrapper.textures![i]; - if (texture.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) { + if (texture?.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) { this._bindTextureDirectly(gl.TEXTURE_2D, texture, true); gl.generateMipmap(gl.TEXTURE_2D); this._bindTextureDirectly(gl.TEXTURE_2D, null); @@ -169,10 +169,15 @@ ThinEngine.prototype.createMultipleRenderTarget = function (size: TextureSize, o const defaultType = Constants.TEXTURETYPE_UNSIGNED_INT; const defaultSamplingMode = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE; const defaultUseSRGBBuffer = false; + const defaultTarget = Constants.TEXTURE_2D; let types = new Array(); let samplingModes = new Array(); let useSRGBBuffers = new Array(); + let targets = new Array(); + let faceIndex = new Array(); + let layerIndex = new Array(); + let layers = new Array(); const rtWrapper = this._createHardwareRenderTargetWrapper(true, false, size) as WebGLRenderTargetWrapper; @@ -192,6 +197,18 @@ ThinEngine.prototype.createMultipleRenderTarget = function (size: TextureSize, o if (options.useSRGBBuffers) { useSRGBBuffers = options.useSRGBBuffers; } + if (options.targetTypes) { + targets = options.targetTypes; + } + if (options.faceIndex) { + faceIndex = options.faceIndex; + } + if (options.layerIndex) { + layerIndex = options.layerIndex; + } + if (options.layerCounts) { + layers = options.layerCounts; + } if ( this.webGLVersion > 1 && (options.depthTextureFormat === Constants.TEXTUREFORMAT_DEPTH24_STENCIL8 || @@ -233,6 +250,9 @@ ThinEngine.prototype.createMultipleRenderTarget = function (size: TextureSize, o let type = types[i] || defaultType; let useSRGBBuffer = useSRGBBuffers[i] || defaultUseSRGBBuffer; + const target = targets[i] || defaultTarget; + const layerCount = layers[i] ?? 1; + if (type === Constants.TEXTURETYPE_FLOAT && !this._caps.textureFloatLinearFiltering) { // if floating point linear (gl.FLOAT) then force to NEAREST_SAMPLINGMODE samplingMode = Constants.TEXTURE_NEAREST_SAMPLINGMODE; @@ -249,31 +269,55 @@ ThinEngine.prototype.createMultipleRenderTarget = function (size: TextureSize, o useSRGBBuffer = useSRGBBuffer && this._caps.supportSRGBBuffers && (this.webGLVersion > 1 || this.isWebGPU); - const texture = new InternalTexture(this, InternalTextureSource.MultiRenderTarget); - const attachment = (gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"]; + const isWebGL2 = this.webGLVersion > 1; + const attachment = (gl)[isWebGL2 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"]; - textures.push(texture); attachments.push(attachment); + if (target === -1) { + continue; + } + + const texture = new InternalTexture(this, InternalTextureSource.MultiRenderTarget); + textures[i] = texture; + gl.activeTexture((gl)["TEXTURE" + i]); - gl.bindTexture(gl.TEXTURE_2D, texture._hardwareTexture!.underlyingResource); + gl.bindTexture(target, texture._hardwareTexture!.underlyingResource); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filters.mag); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filters.min); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, filters.mag); + gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filters.min); + gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); const internalSizedFormat = this._getRGBABufferInternalSizedFormat(type, Constants.TEXTUREFORMAT_RGBA, useSRGBBuffer); - gl.texImage2D(gl.TEXTURE_2D, 0, internalSizedFormat, width, height, 0, gl.RGBA, this._getWebGLTextureType(type), null); + const webGLTextureType = this._getWebGLTextureType(type); - gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture._hardwareTexture!.underlyingResource, 0); + if (isWebGL2 && (target === Constants.TEXTURE_2D_ARRAY || target === Constants.TEXTURE_3D)) { + if (target === Constants.TEXTURE_2D_ARRAY) { + texture.is2DArray = true; + } else { + texture.is3D = true; + } + + texture.baseDepth = texture.depth = layerCount; + + gl.texImage3D(target, 0, internalSizedFormat, width, height, layerCount, 0, gl.RGBA, webGLTextureType, null); + } else if (target === Constants.TEXTURE_CUBE_MAP) { + // We have to generate all faces to complete the framebuffer + for (let i = 0; i < 6; i++) { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalSizedFormat, width, height, 0, gl.RGBA, webGLTextureType, null); + } + texture.isCube = true; + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, internalSizedFormat, width, height, 0, gl.RGBA, webGLTextureType, null); + } if (generateMipMaps) { - this._gl.generateMipmap(this._gl.TEXTURE_2D); + gl.generateMipmap(target); } // Unbind - this._bindTextureDirectly(gl.TEXTURE_2D, null); + this._bindTextureDirectly(target, null); texture.baseWidth = width; texture.baseHeight = height; @@ -346,7 +390,7 @@ ThinEngine.prototype.createMultipleRenderTarget = function (size: TextureSize, o depthTexture.format = depthTextureFormat; depthTexture.type = depthTextureType; - textures.push(depthTexture); + textures[textureCount] = depthTexture; this._internalTexturesCache.push(depthTexture); } rtWrapper.setTextures(textures); @@ -356,6 +400,8 @@ ThinEngine.prototype.createMultipleRenderTarget = function (size: TextureSize, o this._bindUnboundFramebuffer(null); + rtWrapper.setLayerAndFaceIndices(layerIndex, faceIndex); + this.resetTextureCache(); return rtWrapper; @@ -408,28 +454,32 @@ ThinEngine.prototype.updateMultipleRenderTargetTextureSampleCount = function ( const attachments = []; + for (let i = 0; i < count; i++) { + const texture = rtWrapper.textures![i]; + const hardwareTexture = texture._hardwareTexture as WebGLHardwareTexture; + + hardwareTexture.releaseMSAARenderBuffers(); + } + for (let i = 0; i < count; i++) { const texture = rtWrapper.textures![i]; const hardwareTexture = texture._hardwareTexture as WebGLHardwareTexture; const attachment = (gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"]; - const colorRenderbuffer = hardwareTexture._MSAARenderBuffer - ? this._updateRenderBuffer( - hardwareTexture._MSAARenderBuffer, - texture.width, - texture.height, - samples, - -1 /* not used */, - this._getRGBAMultiSampleBufferFormat(texture.type), - attachment - ) - : this._createRenderBuffer(texture.width, texture.height, samples, -1 /* not used */, this._getRGBAMultiSampleBufferFormat(texture.type), attachment); + const colorRenderbuffer = this._createRenderBuffer( + texture.width, + texture.height, + samples, + -1 /* not used */, + this._getRGBAMultiSampleBufferFormat(texture.type), + attachment + ); if (!colorRenderbuffer) { throw new Error("Unable to create multi sampled framebuffer"); } - hardwareTexture._MSAARenderBuffer = colorRenderbuffer; + hardwareTexture.addMSAARenderBuffer(colorRenderbuffer); texture.samples = samples; attachments.push(attachment); diff --git a/packages/dev/core/src/Engines/Extensions/engine.renderTarget.ts b/packages/dev/core/src/Engines/Extensions/engine.renderTarget.ts index 6e1cc7e73b9..af05400aba4 100644 --- a/packages/dev/core/src/Engines/Extensions/engine.renderTarget.ts +++ b/packages/dev/core/src/Engines/Extensions/engine.renderTarget.ts @@ -246,10 +246,7 @@ ThinEngine.prototype.updateRenderTargetTextureSampleCount = function (rtWrapper: } const hardwareTexture = rtWrapper.texture._hardwareTexture as WebGLHardwareTexture; - if (hardwareTexture._MSAARenderBuffer) { - gl.deleteRenderbuffer(hardwareTexture._MSAARenderBuffer); - hardwareTexture._MSAARenderBuffer = null; - } + hardwareTexture.releaseMSAARenderBuffers(); if (samples > 1 && typeof gl.renderbufferStorageMultisample === "function") { const framebuffer = gl.createFramebuffer(); @@ -275,7 +272,7 @@ ThinEngine.prototype.updateRenderTargetTextureSampleCount = function (rtWrapper: throw new Error("Unable to create multi sampled framebuffer"); } - hardwareTexture._MSAARenderBuffer = colorRenderbuffer; + hardwareTexture.addMSAARenderBuffer(colorRenderbuffer); } else { this._bindUnboundFramebuffer(rtWrapper._framebuffer); } diff --git a/packages/dev/core/src/Engines/WebGL/webGLHardwareTexture.ts b/packages/dev/core/src/Engines/WebGL/webGLHardwareTexture.ts index a09c520c7b7..c30e0ef62e3 100644 --- a/packages/dev/core/src/Engines/WebGL/webGLHardwareTexture.ts +++ b/packages/dev/core/src/Engines/WebGL/webGLHardwareTexture.ts @@ -6,8 +6,9 @@ export class WebGLHardwareTexture implements HardwareTextureWrapper { private _webGLTexture: WebGLTexture; private _context: WebGLRenderingContext; - // eslint-disable-next-line @typescript-eslint/naming-convention - public _MSAARenderBuffer: Nullable = null; + // There can be multiple buffers for a single WebGL texture because different layers of a 2DArrayTexture / 3DTexture + // or different faces of a cube texture can be bound to different render targets at the same time. + private _MSAARenderBuffers: Nullable = null; public get underlyingResource(): Nullable { return this._webGLTexture; @@ -32,14 +33,27 @@ export class WebGLHardwareTexture implements HardwareTextureWrapper { public reset() { this._webGLTexture = null as any; - this._MSAARenderBuffer = null; + this._MSAARenderBuffers = null; } - public release() { - if (this._MSAARenderBuffer) { - this._context.deleteRenderbuffer(this._MSAARenderBuffer); - this._MSAARenderBuffer = null; + public addMSAARenderBuffer(buffer: WebGLRenderbuffer) { + if (!this._MSAARenderBuffers) { + this._MSAARenderBuffers = []; + } + this._MSAARenderBuffers.push(buffer); + } + + public releaseMSAARenderBuffers() { + if (this._MSAARenderBuffers) { + for (const buffer of this._MSAARenderBuffers) { + this._context.deleteRenderbuffer(buffer); + } + this._MSAARenderBuffers = null; } + } + + public release() { + this.releaseMSAARenderBuffers(); if (this._webGLTexture) { this._context.deleteTexture(this._webGLTexture); diff --git a/packages/dev/core/src/Engines/WebGL/webGLRenderTargetWrapper.ts b/packages/dev/core/src/Engines/WebGL/webGLRenderTargetWrapper.ts index 608c98afa32..2685e216cbc 100644 --- a/packages/dev/core/src/Engines/WebGL/webGLRenderTargetWrapper.ts +++ b/packages/dev/core/src/Engines/WebGL/webGLRenderTargetWrapper.ts @@ -75,23 +75,44 @@ export class WebGLRenderTargetWrapper extends RenderTargetWrapper { * Binds a texture to this render target on a specific attachment * @param texture The texture to bind to the framebuffer * @param attachmentIndex Index of the attachment - * @param faceIndex The face of the texture to render to in case of cube texture + * @param faceIndexOrLayer The face or layer of the texture to render to in case of cube texture or array texture * @param lodLevel defines the lod level to bind to the frame buffer */ - private _bindTextureRenderTarget(texture: InternalTexture, attachmentIndex: number = 0, faceIndex: number = -1, lodLevel: number = 0) { + private _bindTextureRenderTarget(texture: InternalTexture, attachmentIndex: number = 0, faceIndexOrLayer?: number, lodLevel: number = 0) { if (!texture._hardwareTexture) { return; } - const gl = this._context; const framebuffer = this._framebuffer; const currentFB = this._engine._currentFramebuffer; this._engine._bindUnboundFramebuffer(framebuffer); - const attachment = (gl)[this._engine.webGLVersion > 1 ? "COLOR_ATTACHMENT" + attachmentIndex : "COLOR_ATTACHMENT" + attachmentIndex + "_WEBGL"]; - const target = faceIndex !== -1 ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex : gl.TEXTURE_2D; - gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, target, texture._hardwareTexture.underlyingResource, lodLevel); + if (this._engine.webGLVersion > 1) { + const gl = this._context as WebGL2RenderingContext; + + const attachment = (gl)["COLOR_ATTACHMENT" + attachmentIndex]; + if (texture.is2DArray || texture.is3D) { + faceIndexOrLayer = faceIndexOrLayer ?? this.layerIndices?.[attachmentIndex] ?? 0; + gl.framebufferTextureLayer(gl.FRAMEBUFFER, attachment, texture._hardwareTexture.underlyingResource, lodLevel, faceIndexOrLayer); + } else if (texture.isCube) { + // if face index is not specified, try to query it from faceIndices + // default is face 0 + faceIndexOrLayer = faceIndexOrLayer ?? this.faceIndices?.[attachmentIndex] ?? 0; + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndexOrLayer, texture._hardwareTexture.underlyingResource, lodLevel); + } else { + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture._hardwareTexture.underlyingResource, lodLevel); + } + } else { + // Default behavior (WebGL) + const gl = this._context; + + const attachment = (gl)["COLOR_ATTACHMENT" + attachmentIndex + "_WEBGL"]; + const target = faceIndexOrLayer !== undefined ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndexOrLayer : gl.TEXTURE_2D; + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, target, texture._hardwareTexture.underlyingResource, lodLevel); + } + this._engine._bindUnboundFramebuffer(currentFB); } @@ -106,6 +127,57 @@ export class WebGLRenderTargetWrapper extends RenderTargetWrapper { this._bindTextureRenderTarget(texture, index); } + /** + * Sets the layer and face indices of every render target texture + * @param layers The layer of the texture to be set (make negative to not modify) + * @param faces The face of the texture to be set (make negative to not modify) + */ + public setLayerAndFaceIndices(layers: number[], faces: number[]) { + super.setLayerAndFaceIndices(layers, faces); + + if (!this.textures || !this.layerIndices || !this.faceIndices) { + return; + } + + // the length of this._attachments is the right one as it does not count the depth texture, in case we generated it + const textureCount = this._attachments?.length ?? this.textures.length; + for (let index = 0; index < textureCount; index++) { + const texture = this.textures[index]; + if (!texture) { + // The target type was probably -1 at creation time and setTexture has not been called yet for this index + continue; + } + if (texture.is2DArray || texture.is3D) { + this._bindTextureRenderTarget(texture, index, this.layerIndices[index]); + } else if (texture.isCube) { + this._bindTextureRenderTarget(texture, index, this.faceIndices[index]); + } else { + this._bindTextureRenderTarget(texture, index); + } + } + } + + /** + * Set the face and layer indices of a texture in the textures array + * @param index The index of the texture in the textures array to modify + * @param layer The layer of the texture to be set + * @param face The face of the texture to be set + */ + public setLayerAndFaceIndex(index: number = 0, layer?: number, face?: number): void { + super.setLayerAndFaceIndex(index, layer, face); + + if (!this.textures || !this.layerIndices || !this.faceIndices) { + return; + } + + const texture = this.textures[index]; + if (texture.is2DArray || texture.is3D) { + this._bindTextureRenderTarget(this.textures[index], index, this.layerIndices[index]); + } else if (texture.isCube) { + this._bindTextureRenderTarget(this.textures[index], index, this.faceIndices[index]); + } + } + public dispose(disposeOnlyFramebuffers = false): void { const gl = this._context; diff --git a/packages/dev/core/src/Engines/constants.ts b/packages/dev/core/src/Engines/constants.ts index 1d06b270845..425df87bc2a 100644 --- a/packages/dev/core/src/Engines/constants.ts +++ b/packages/dev/core/src/Engines/constants.ts @@ -257,6 +257,17 @@ export class Constants { /** UNDEFINED */ public static readonly TEXTURETYPE_UNDEFINED = 16; + /** 2D Texture target*/ + public static readonly TEXTURE_2D = 3553; + /** 2D Array Texture target */ + public static readonly TEXTURE_2D_ARRAY = 35866; + /** Cube Map Texture target */ + public static readonly TEXTURE_CUBE_MAP = 34067; + /** Cube Map Array Texture target */ + public static readonly TEXTURE_CUBE_MAP_ARRAY = 0xdeadbeef; + /** 3D Texture target */ + public static readonly TEXTURE_3D = 32879; + /** nearest is mag = nearest and min = nearest and no mip */ public static readonly TEXTURE_NEAREST_SAMPLINGMODE = 1; /** mag = nearest and min = nearest and mip = none */ diff --git a/packages/dev/core/src/Engines/renderTargetWrapper.ts b/packages/dev/core/src/Engines/renderTargetWrapper.ts index 5d16adb60e2..6f09bfba9c8 100644 --- a/packages/dev/core/src/Engines/renderTargetWrapper.ts +++ b/packages/dev/core/src/Engines/renderTargetWrapper.ts @@ -4,6 +4,7 @@ import type { RenderTargetCreationOptions, TextureSize } from "../Materials/Text import type { Nullable } from "../types"; import { Constants } from "./constants"; import type { ThinEngine } from "./thinEngine"; +import type { IMultiRenderTargetOptions } from "../Materials/Textures/multiRenderTarget"; /** * An interface enforcing the renderTarget accessor to used by render target textures. @@ -24,6 +25,8 @@ export class RenderTargetWrapper { private _isCube: boolean; private _isMulti: boolean; private _textures: Nullable = null; + private _faceIndices: Nullable = null; + private _layerIndices: Nullable = null; /** @internal */ public _samples = 1; @@ -96,7 +99,7 @@ export class RenderTargetWrapper { } /** - * Gets the number of layers of the render target wrapper (only used if is2DArray is true) + * Gets the number of layers of the render target wrapper (only used if is2DArray is true and wrapper is not a multi render target) */ public get layers(): number { return (<{ width: number; height: number; layers?: number }>this._size).layers || 0; @@ -116,6 +119,20 @@ export class RenderTargetWrapper { return this._textures; } + /** + * Gets the face indices that correspond to the list of render textures. If we are not in a multi render target, the list will be null + */ + public get faceIndices(): Nullable { + return this._faceIndices; + } + + /** + * Gets the layer indices that correspond to the list of render textures. If we are not in a multi render target, the list will be null + */ + public get layerIndices(): Nullable { + return this._layerIndices; + } + /** * Gets the sample count of the render target */ @@ -173,8 +190,8 @@ export class RenderTargetWrapper { /** * Set a texture in the textures array - * @param texture the texture to set - * @param index the index in the textures array to set + * @param texture The texture to set + * @param index The index in the textures array to set * @param disposePrevious If this function should dispose the previous texture */ public setTexture(texture: InternalTexture, index: number = 0, disposePrevious: boolean = true): void { @@ -188,6 +205,38 @@ export class RenderTargetWrapper { this._textures[index] = texture; } + /** + * Sets the layer and face indices of every render target texture bound to each color attachment + * @param layers The layers of each texture to be set + * @param faces The faces of each texture to be set + */ + public setLayerAndFaceIndices(layers: number[], faces: number[]) { + this._layerIndices = layers; + this._faceIndices = faces; + } + + /** + * Sets the layer and face indices of a texture in the textures array that should be bound to each color attachment + * @param index The index of the texture in the textures array to modify + * @param layer The layer of the texture to be set + * @param face The face of the texture to be set + */ + public setLayerAndFaceIndex(index: number = 0, layer?: number, face?: number): void { + if (!this._layerIndices) { + this._layerIndices = []; + } + if (!this._faceIndices) { + this._faceIndices = []; + } + + if (layer !== undefined && layer >= 0) { + this._layerIndices[index] = layer; + } + if (face !== undefined && face >= 0) { + this._faceIndices[index] = face; + } + } + /** * Creates the depth/stencil texture * @param comparisonFunction Comparison function to use for the texture @@ -267,15 +316,51 @@ export class RenderTargetWrapper { const samplingModes: number[] = []; const types: number[] = []; + const targetTypes: number[] = []; + const faceIndex: number[] = []; + const layerIndex: number[] = []; + const layerCounts: number[] = []; + const internalTexture2Index: { [id: number]: number } = {}; for (let i = 0; i < textureCount; ++i) { const texture = textureArray[i]; samplingModes.push(texture.samplingMode); types.push(texture.type); + + const index = internalTexture2Index[texture.uniqueId]; + if (index !== undefined) { + targetTypes.push(-1); + layerCounts.push(0); + } else { + internalTexture2Index[texture.uniqueId] = i; + if (texture.is2DArray) { + targetTypes.push(Constants.TEXTURE_2D_ARRAY); + layerCounts.push(texture.depth); + } else if (texture.isCube) { + targetTypes.push(Constants.TEXTURE_CUBE_MAP); + layerCounts.push(0); + } /*else if (texture.isCubeArray) { + targetTypes.push(Constants.TEXTURE_CUBE_MAP_ARRAY); + layerCounts.push(texture.depth); + }*/ else if (texture.is3D) { + targetTypes.push(Constants.TEXTURE_3D); + layerCounts.push(texture.depth); + } else { + targetTypes.push(Constants.TEXTURE_2D); + layerCounts.push(0); + } + } + + if (this._faceIndices) { + faceIndex.push(this._faceIndices[i] ?? 0); + } + if (this._layerIndices) { + layerIndex.push(this._layerIndices[i] ?? 0); + } } - const optionsMRT = { + const optionsMRT: IMultiRenderTargetOptions = { samplingModes, generateMipMaps: textureArray[0].generateMipMaps, generateDepthBuffer: this._generateDepthBuffer, @@ -283,6 +368,10 @@ export class RenderTargetWrapper { generateDepthTexture, types, textureCount, + targetTypes, + faceIndex, + layerIndex, + layerCounts, }; const size = { width: this.width, @@ -290,6 +379,14 @@ export class RenderTargetWrapper { }; rtw = this._engine.createMultipleRenderTarget(size, optionsMRT); + + for (let i = 0; i < textureCount; ++i) { + if (targetTypes[i] !== -1) { + continue; + } + const index = internalTexture2Index[textureArray[i].uniqueId]; + rtw.setTexture(rtw.textures![index], i); + } } } else { const options: RenderTargetCreationOptions = {}; diff --git a/packages/dev/core/src/Engines/thinEngine.ts b/packages/dev/core/src/Engines/thinEngine.ts index fc835d27404..20db29a810d 100644 --- a/packages/dev/core/src/Engines/thinEngine.ts +++ b/packages/dev/core/src/Engines/thinEngine.ts @@ -1808,16 +1808,16 @@ export class ThinEngine { /** * Binds the frame buffer to the specified texture. - * @param texture The render target wrapper to render to - * @param faceIndex The face of the texture to render to in case of cube texture + * @param rtWrapper The render target wrapper to render to + * @param faceIndex The face of the texture to render to in case of cube texture and if the render target wrapper is not a multi render target * @param requiredWidth The width of the target to render to * @param requiredHeight The height of the target to render to * @param forceFullscreenViewport Forces the viewport to be the entire texture/screen if true - * @param lodLevel defines the lod level to bind to the frame buffer - * @param layer defines the 2d array index to bind to frame buffer to + * @param lodLevel Defines the lod level to bind to the frame buffer + * @param layer Defines the 2d array index to bind to the frame buffer if the render target wrapper is not a multi render target */ public bindFramebuffer( - texture: RenderTargetWrapper, + rtWrapper: RenderTargetWrapper, faceIndex: number = 0, requiredWidth?: number, requiredHeight?: number, @@ -1825,33 +1825,35 @@ export class ThinEngine { lodLevel = 0, layer = 0 ): void { - const webglRTWrapper = texture as WebGLRenderTargetWrapper; + const webglRTWrapper = rtWrapper as WebGLRenderTargetWrapper; if (this._currentRenderTarget) { this.unBindFramebuffer(this._currentRenderTarget); } - this._currentRenderTarget = texture; + this._currentRenderTarget = rtWrapper; this._bindUnboundFramebuffer(webglRTWrapper._MSAAFramebuffer ? webglRTWrapper._MSAAFramebuffer : webglRTWrapper._framebuffer); const gl = this._gl; - if (texture.is2DArray) { - gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture.texture!._hardwareTexture?.underlyingResource, lodLevel, layer); - } else if (texture.isCube) { - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, - texture.texture!._hardwareTexture?.underlyingResource, - lodLevel - ); + if (!rtWrapper.isMulti) { + if (rtWrapper.is2DArray) { + gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, rtWrapper.texture!._hardwareTexture?.underlyingResource, lodLevel, layer); + } else if (rtWrapper.isCube) { + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, + rtWrapper.texture!._hardwareTexture?.underlyingResource, + lodLevel + ); + } } - const depthStencilTexture = texture._depthStencilTexture; + const depthStencilTexture = rtWrapper._depthStencilTexture; if (depthStencilTexture) { - const attachment = texture._depthStencilTextureWithStencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; - if (texture.is2DArray) { + const attachment = rtWrapper._depthStencilTextureWithStencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + if (rtWrapper.is2DArray) { gl.framebufferTextureLayer(gl.FRAMEBUFFER, attachment, depthStencilTexture._hardwareTexture?.underlyingResource, lodLevel, layer); - } else if (texture.isCube) { + } else if (rtWrapper.isCube) { gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, depthStencilTexture._hardwareTexture?.underlyingResource, lodLevel); } else { gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, depthStencilTexture._hardwareTexture?.underlyingResource, lodLevel); @@ -1862,13 +1864,13 @@ export class ThinEngine { this.setViewport(this._cachedViewport, requiredWidth, requiredHeight); } else { if (!requiredWidth) { - requiredWidth = texture.width; + requiredWidth = rtWrapper.width; if (lodLevel) { requiredWidth = requiredWidth / Math.pow(2, lodLevel); } } if (!requiredHeight) { - requiredHeight = texture.height; + requiredHeight = rtWrapper.height; if (lodLevel) { requiredHeight = requiredHeight / Math.pow(2, lodLevel); } diff --git a/packages/dev/core/src/Materials/Textures/multiRenderTarget.ts b/packages/dev/core/src/Materials/Textures/multiRenderTarget.ts index e013af9dfda..81ff8d60037 100644 --- a/packages/dev/core/src/Materials/Textures/multiRenderTarget.ts +++ b/packages/dev/core/src/Materials/Textures/multiRenderTarget.ts @@ -59,6 +59,27 @@ export interface IMultiRenderTargetOptions { * Define the default type of the buffers we are creating */ drawOnlyOnFirstAttachmentByDefault?: boolean; + /** + * Define the type of texture at each attahment index (of Constants.TEXTURE_2D, .TEXTURE_2D_ARRAY, .TEXTURE_CUBE_MAP, .TEXTURE_CUBE_MAP_ARRAY, .TEXTURE_3D). + * You can also use the -1 value to indicate that no texture should be created but that you will assign a texture to that attachment index later. + * Can be useful when you want to attach several layers of the same 2DArrayTexture / 3DTexture or several faces of the same CubeMapTexture: Use the setInternalTexture + * method for that purpose, after the MultiRenderTarget has been created. + */ + targetTypes?: number[]; + /** + * Define the face index of each texture in the textures array (if applicable, given the corresponding targetType) at creation time (for Constants.TEXTURE_CUBE_MAP and .TEXTURE_CUBE_MAP_ARRAY). + * Can be changed at any time by calling setLayerAndFaceIndices or setLayerAndFaceIndex + */ + faceIndex?: number[]; + /** + * Define the layer index of each texture in the textures array (if applicable, given the corresponding targetType) at creation time (for Constants.TEXTURE_3D, .TEXTURE_2D_ARRAY, and .TEXTURE_CUBE_MAP_ARRAY). + * Can be changed at any time by calling setLayerAndFaceIndices or setLayerAndFaceIndex + */ + layerIndex?: number[]; + /** + * Define the number of layer of each texture in the textures array (if applicable, given the corresponding targetType) (for Constants.TEXTURE_3D, .TEXTURE_2D_ARRAY, and .TEXTURE_CUBE_MAP_ARRAY) + */ + layerCounts?: number[]; } /** @@ -72,6 +93,7 @@ export class MultiRenderTarget extends RenderTargetTexture { private _multiRenderTargetOptions: IMultiRenderTargetOptions; private _count: number; private _drawOnlyOnFirstAttachmentByDefault: boolean; + private _textureNames?: string[]; /** * Get if draw buffers are currently supported by the used hardware and browser. @@ -151,10 +173,16 @@ export class MultiRenderTarget extends RenderTargetTexture { return; } + this._textureNames = textureNames; + const types: number[] = []; const samplingModes: number[] = []; const useSRGBBuffers: boolean[] = []; - this._initTypes(count, types, samplingModes, useSRGBBuffers, options); + const targetTypes: number[] = []; + const faceIndex: number[] = []; + const layerIndex: number[] = []; + const layerCounts: number[] = []; + this._initTypes(count, types, samplingModes, useSRGBBuffers, targetTypes, faceIndex, layerIndex, layerCounts, options); const generateDepthBuffer = !options || options.generateDepthBuffer === undefined ? true : options.generateDepthBuffer; const generateStencilBuffer = !options || options.generateStencilBuffer === undefined ? false : options.generateStencilBuffer; @@ -170,6 +198,10 @@ export class MultiRenderTarget extends RenderTargetTexture { types: types, textureCount: count, useSRGBBuffers: useSRGBBuffers, + targetTypes: targetTypes, + faceIndex: faceIndex, + layerIndex: layerIndex, + layerCounts: layerCounts, }; this._count = count; @@ -181,7 +213,17 @@ export class MultiRenderTarget extends RenderTargetTexture { } } - private _initTypes(count: number, types: number[], samplingModes: number[], useSRGBBuffers: boolean[], options?: IMultiRenderTargetOptions) { + private _initTypes( + count: number, + types: number[], + samplingModes: number[], + useSRGBBuffers: boolean[], + targets: number[], + faceIndex: number[], + layerIndex: number[], + layerCounts: number[], + options?: IMultiRenderTargetOptions + ) { for (let i = 0; i < count; i++) { if (options && options.types && options.types[i] !== undefined) { types.push(options.types[i]); @@ -200,9 +242,58 @@ export class MultiRenderTarget extends RenderTargetTexture { } else { useSRGBBuffers.push(false); } + + if (options && options.targetTypes && options.targetTypes[i] !== undefined) { + targets.push(options.targetTypes[i]); + } else { + targets.push(Constants.TEXTURE_2D); + } + + if (options && options.faceIndex && options.faceIndex[i] !== undefined) { + faceIndex.push(options.faceIndex[i]); + } else { + faceIndex.push(0); + } + + if (options && options.layerIndex && options.layerIndex[i] !== undefined) { + layerIndex.push(options.layerIndex[i]); + } else { + layerIndex.push(0); + } + + if (options && options.layerCounts && options.layerCounts[i] !== undefined) { + layerCounts.push(options.layerCounts[i]); + } else { + layerCounts.push(1); + } } } + private _createInternaTextureIndexMapping() { + const mapMainInternalTexture2Index: { [key: number]: number } = {}; + const mapInternalTexture2MainIndex: number[] = []; + + if (!this._renderTarget) { + return mapInternalTexture2MainIndex; + } + + const internalTextures = this._renderTarget!.textures!; + for (let i = 0; i < internalTextures.length; i++) { + const texture = internalTextures[i]; + if (!texture) { + continue; + } + const mainIndex = mapMainInternalTexture2Index[texture.uniqueId]; + if (mainIndex !== undefined) { + mapInternalTexture2MainIndex[i] = mainIndex; + } else { + mapMainInternalTexture2Index[texture.uniqueId] = i; + } + } + + return mapInternalTexture2MainIndex; + } + /** * @internal */ @@ -211,6 +302,8 @@ export class MultiRenderTarget extends RenderTargetTexture { return; } + const mapInternalTexture2MainIndex = this._createInternaTextureIndexMapping(); + this.releaseInternalTextures(); this._createInternalTextures(); @@ -222,7 +315,14 @@ export class MultiRenderTarget extends RenderTargetTexture { const internalTextures = this._renderTarget!.textures!; for (let i = 0; i < internalTextures.length; i++) { const texture = this._textures[i]; + if (mapInternalTexture2MainIndex[i] !== undefined) { + this._renderTarget!.setTexture(internalTextures[mapInternalTexture2MainIndex[i]], i); + } texture._texture = internalTextures[i]; + if (texture._texture) { + texture._noMipmap = !texture._texture.useMipMaps; + texture._useSRGBBuffer = texture._texture._useSRGBBuffer; + } } if (this.samples !== 1) { @@ -253,6 +353,10 @@ export class MultiRenderTarget extends RenderTargetTexture { texture.name = textureNames[i]; } texture._texture = internalTextures[i]; + if (texture._texture) { + texture._noMipmap = !texture._texture.useMipMaps; + texture._useSRGBBuffer = texture._texture._useSRGBBuffer; + } this._textures.push(texture); } } @@ -276,8 +380,11 @@ export class MultiRenderTarget extends RenderTargetTexture { if (!this.textures[index]) { this.textures[index] = new Texture(null, this.getScene()); + this.textures[index].name = this._textureNames?.[index] ?? this.textures[index].name; } this.textures[index]._texture = texture; + this.textures[index]._noMipmap = !texture.useMipMaps; + this.textures[index]._useSRGBBuffer = texture._useSRGBBuffer; this._count = this.renderTarget.textures ? this.renderTarget.textures.length : 0; @@ -290,6 +397,58 @@ export class MultiRenderTarget extends RenderTargetTexture { if (this._multiRenderTargetOptions.useSRGBBuffers) { this._multiRenderTargetOptions.useSRGBBuffers[index] = texture._useSRGBBuffer; } + if (this._multiRenderTargetOptions.targetTypes && this._multiRenderTargetOptions.targetTypes[index] !== -1) { + let target: number = 0; + if (texture.is2DArray) { + target = Constants.TEXTURE_2D_ARRAY; + } else if (texture.isCube) { + target = Constants.TEXTURE_CUBE_MAP; + } /*else if (texture.isCubeArray) { + target = Constants.TEXTURE_CUBE_MAP_ARRAY; + }*/ else if (texture.is3D) { + target = Constants.TEXTURE_3D; + } else { + target = Constants.TEXTURE_2D; + } + this._multiRenderTargetOptions.targetTypes[index] = target; + } + } + + /** + * Changes an attached texture's face index or layer. + * @param index The index of the texture to modify the attachment of + * @param layerIndex The layer index of the texture to be attached to the framebuffer + * @param faceIndex The face index of the texture to be attached to the framebuffer + */ + public setLayerAndFaceIndex(index: number, layerIndex: number = -1, faceIndex: number = -1) { + if (!this.textures[index] || !this.renderTarget) { + return; + } + + if (this._multiRenderTargetOptions.layerIndex) { + this._multiRenderTargetOptions.layerIndex[index] = layerIndex; + } + if (this._multiRenderTargetOptions.faceIndex) { + this._multiRenderTargetOptions.faceIndex[index] = faceIndex; + } + + this.renderTarget.setLayerAndFaceIndex(index, layerIndex, faceIndex); + } + + /** + * Changes every attached texture's face index or layer. + * @param layerIndices The layer indices of the texture to be attached to the framebuffer + * @param faceIndices The face indices of the texture to be attached to the framebuffer + */ + public setLayerAndFaceIndices(layerIndices: number[], faceIndices: number[]) { + if (!this.renderTarget) { + return; + } + + this._multiRenderTargetOptions.layerIndex = layerIndices; + this._multiRenderTargetOptions.faceIndex = faceIndices; + + this.renderTarget.setLayerAndFaceIndices(layerIndices, faceIndices); } /** @@ -315,7 +474,7 @@ export class MultiRenderTarget extends RenderTargetTexture { */ public resize(size: any) { this._size = size; - this._rebuild(); + this._rebuild(undefined, this._textureNames); } /** @@ -332,11 +491,22 @@ export class MultiRenderTarget extends RenderTargetTexture { const types: number[] = []; const samplingModes: number[] = []; const useSRGBBuffers: boolean[] = []; + const targetTypes: number[] = []; + const faceIndex: number[] = []; + const layerIndex: number[] = []; + const layerCounts: number[] = []; + + this._textureNames = textureNames; - this._initTypes(count, types, samplingModes, useSRGBBuffers, options); + this._initTypes(count, types, samplingModes, useSRGBBuffers, targetTypes, faceIndex, layerIndex, layerCounts, options); this._multiRenderTargetOptions.types = types; this._multiRenderTargetOptions.samplingModes = samplingModes; this._multiRenderTargetOptions.useSRGBBuffers = useSRGBBuffers; + this._multiRenderTargetOptions.targetTypes = targetTypes; + this._multiRenderTargetOptions.faceIndex = faceIndex; + this._multiRenderTargetOptions.layerIndex = layerIndex; + this._multiRenderTargetOptions.layerCounts = layerCounts; + this._rebuild(true, textureNames); } diff --git a/packages/dev/core/src/Materials/Textures/renderTargetTexture.ts b/packages/dev/core/src/Materials/Textures/renderTargetTexture.ts index 3345d75990d..054a3bbfc3b 100644 --- a/packages/dev/core/src/Materials/Textures/renderTargetTexture.ts +++ b/packages/dev/core/src/Materials/Textures/renderTargetTexture.ts @@ -344,6 +344,14 @@ export class RenderTargetTexture extends Texture implements IRenderTargetTexture } private _isCubeData: boolean; + + /** + * Define if the texture has multiple draw buffers or if false a single draw buffer. + */ + public get isMulti(): boolean { + return this._renderTarget?.isMulti ?? false; + } + /** * Gets render target creation options that were used. */ @@ -935,13 +943,13 @@ export class RenderTargetTexture extends Texture implements IRenderTargetTexture let returnValue = checkReadiness; if (!checkReadiness) { - if (this.is2DArray) { + if (this.is2DArray && !this.isMulti) { for (let layer = 0; layer < this.getRenderLayers(); layer++) { this._renderToTarget(0, useCameraPostProcess, dumpForDebug, layer, camera); scene.incrementRenderId(); scene.resetCachedMaterial(); } - } else if (this.isCube) { + } else if (this.isCube && !this.isMulti) { for (let face = 0; face < 6; face++) { this._renderToTarget(face, useCameraPostProcess, dumpForDebug, undefined, camera); scene.incrementRenderId(); diff --git a/packages/dev/core/src/Materials/Textures/texture.ts b/packages/dev/core/src/Materials/Textures/texture.ts index 2e40353d7df..2698b71064b 100644 --- a/packages/dev/core/src/Materials/Textures/texture.ts +++ b/packages/dev/core/src/Materials/Textures/texture.ts @@ -273,7 +273,8 @@ export class Texture extends BaseTexture { */ public inspectableCustomProperties: Nullable = null; - private _noMipmap: boolean = false; + /** @internal */ + public _noMipmap: boolean = false; /** @internal */ public _invertY: boolean = false; private _rowGenerationMatrix: Nullable = null; @@ -312,7 +313,8 @@ export class Texture extends BaseTexture { private _mimeType?: string; private _loaderOptions?: any; private _creationFlags?: number; - private _useSRGBBuffer?: boolean; + /** @internal */ + public _useSRGBBuffer?: boolean; private _forcedExtension?: string; /** Returns the texture mime type if it was defined by a loader (undefined else) */ diff --git a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx index fd0663f7684..d891f450a59 100644 --- a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx +++ b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx @@ -317,6 +317,8 @@ export class TexturePropertyGridComponent extends React.Component + {texture.is2DArray && } + {texture.is3D && } {texture.isRenderTarget && (