Skip to content
102 changes: 76 additions & 26 deletions packages/dev/core/src/Engines/Extensions/engine.multiRender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<number>();
let samplingModes = new Array<number>();
let useSRGBBuffers = new Array<boolean>();
let targets = new Array<number>();
let faceIndex = new Array<number>();
let layerIndex = new Array<number>();
let layers = new Array<number>();

const rtWrapper = this._createHardwareRenderTargetWrapper(true, false, size) as WebGLRenderTargetWrapper;

Expand All @@ -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 ||
Expand Down Expand Up @@ -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;
Expand All @@ -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 = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
const isWebGL2 = this.webGLVersion > 1;
const attachment = (<any>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((<any>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;
Expand Down Expand Up @@ -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);
Expand All @@ -356,6 +400,8 @@ ThinEngine.prototype.createMultipleRenderTarget = function (size: TextureSize, o

this._bindUnboundFramebuffer(null);

rtWrapper.setLayerAndFaceIndices(layerIndex, faceIndex);

this.resetTextureCache();

return rtWrapper;
Expand Down Expand Up @@ -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 = (<any>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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
}
Expand Down
28 changes: 21 additions & 7 deletions packages/dev/core/src/Engines/WebGL/webGLHardwareTexture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<WebGLRenderbuffer> = 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<WebGLRenderbuffer[]> = null;

public get underlyingResource(): Nullable<WebGLTexture> {
return this._webGLTexture;
Expand All @@ -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);
Expand Down
84 changes: 78 additions & 6 deletions packages/dev/core/src/Engines/WebGL/webGLRenderTargetWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (<any>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 = (<any>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 = (<any>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);
}

Expand All @@ -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;

Expand Down
11 changes: 11 additions & 0 deletions packages/dev/core/src/Engines/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Loading