From ea1f4cc9dc9731590473a2cb862cb815fa4aa876 Mon Sep 17 00:00:00 2001 From: eyworldwide Date: Mon, 28 Jul 2025 17:52:04 +0800 Subject: [PATCH 1/6] refactor: mass manager in GLRenderTarget --- packages/rhi-webgl/src/GLRenderTarget.ts | 298 ++++++++++++++--------- 1 file changed, 178 insertions(+), 120 deletions(-) diff --git a/packages/rhi-webgl/src/GLRenderTarget.ts b/packages/rhi-webgl/src/GLRenderTarget.ts index 0ae8338ecb..4a5e803f7b 100644 --- a/packages/rhi-webgl/src/GLRenderTarget.ts +++ b/packages/rhi-webgl/src/GLRenderTarget.ts @@ -11,22 +11,188 @@ import { import { GLTexture } from "./GLTexture"; import { WebGLGraphicDevice } from "./WebGLGraphicDevice"; +/** + * MSAA manager for WebGL render targets. + * Handles all MSAA-related operations separately from the main render target logic. + */ +class MSAAManager { + private _gl: WebGLRenderingContext & WebGL2RenderingContext; + private _isWebGL2: boolean; + private _target: RenderTarget; + private _frameBuffer: WebGLFramebuffer; + private _colorRenderBuffers: WebGLRenderbuffer[] = []; + private _depthRenderBuffer: WebGLRenderbuffer | null = null; + private _blitDrawBuffers: GLenum[] = []; + private _oriDrawBuffers: GLenum[]; + + constructor( + gl: WebGLRenderingContext & WebGL2RenderingContext, + isWebGL2: boolean, + target: RenderTarget, + oriDrawBuffers: GLenum[] + ) { + this._gl = gl; + this._isWebGL2 = isWebGL2; + this._target = target; + this._oriDrawBuffers = oriDrawBuffers; + this._frameBuffer = this._gl.createFramebuffer(); + + this._bindFBO(); + } + + /** + * Activate MSAA frame buffer for rendering. + */ + activate(): void { + this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, this._frameBuffer); + } + + /** + * Resolve MSAA frame buffer to target frame buffer. + */ + resolveTo(targetFrameBuffer: WebGLFramebuffer): void { + const gl = this._gl; + const mask = gl.COLOR_BUFFER_BIT | (this._target.depthTexture ? gl.DEPTH_BUFFER_BIT : 0); + const { colorTextureCount, width, height } = this._target; + + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this._frameBuffer); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, targetFrameBuffer); + + for (let textureIndex = 0; textureIndex < colorTextureCount; textureIndex++) { + const attachment = gl.COLOR_ATTACHMENT0 + textureIndex; + + this._blitDrawBuffers[textureIndex] = attachment; + + gl.readBuffer(attachment); + gl.drawBuffers(this._blitDrawBuffers); + gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, mask, gl.NEAREST); + + this._blitDrawBuffers[textureIndex] = gl.NONE; + } + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + /** + * Destroy MSAA resources. + */ + destroy(): void { + const gl = this._gl; + + if (this._frameBuffer) { + gl.deleteFramebuffer(this._frameBuffer); + this._frameBuffer = null; + } + + if (this._depthRenderBuffer) { + gl.deleteRenderbuffer(this._depthRenderBuffer); + this._depthRenderBuffer = null; + } + + for (let i = 0; i < this._colorRenderBuffers.length; i++) { + gl.deleteRenderbuffer(this._colorRenderBuffers[i]); + } + this._colorRenderBuffers.length = 0; + } + + private _bindFBO(): void { + const gl = this._gl; + const isWebGL2 = this._isWebGL2; + const depthRenderBuffer = gl.createRenderbuffer(); + + /** @ts-ignore */ + const { _depth, colorTextureCount, antiAliasing, width, height } = this._target; + + this._blitDrawBuffers = new Array(colorTextureCount); + this._depthRenderBuffer = depthRenderBuffer; + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._frameBuffer); + + // prepare MRT+MSAA color RBOs + for (let i = 0; i < colorTextureCount; i++) { + const colorRenderBuffer = gl.createRenderbuffer(); + + this._colorRenderBuffers[i] = colorRenderBuffer; + this._blitDrawBuffers[i] = gl.NONE; + + gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderBuffer); + gl.renderbufferStorageMultisample( + gl.RENDERBUFFER, + antiAliasing, + /** @ts-ignore */ + (this._target.getColorTexture(i)._platformTexture as GLTexture)._formatDetail.internalFormat, + width, + height + ); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, colorRenderBuffer); + } + gl.drawBuffers(this._oriDrawBuffers); + + // prepare MSAA depth RBO + if (_depth !== null) { + const { internalFormat, attachment } = + _depth instanceof Texture + ? /** @ts-ignore */ + (_depth._platformTexture as GLTexture)._formatDetail + : GLTexture._getRenderBufferDepthFormatDetail(_depth, gl, isWebGL2); + + gl.bindRenderbuffer(gl.RENDERBUFFER, depthRenderBuffer); + gl.renderbufferStorageMultisample(gl.RENDERBUFFER, antiAliasing, internalFormat, width, height); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, depthRenderBuffer); + } + + GLRenderTarget.checkFrameBufferStatus(gl); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + } +} + /** * The render target in WebGL platform is used for off-screen rendering. */ export class GLRenderTarget implements IPlatformRenderTarget { + /** + * Check frame buffer status and throw error if invalid. + * Static utility method that can be used by any component that creates FrameBuffers. + */ + static checkFrameBufferStatus(gl: WebGLRenderingContext | WebGL2RenderingContext): void { + const e = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + + switch (e) { + case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + throw new Error( + "The attachment types are mismatched or not all framebuffer attachment points are framebuffer attachment complete" + ); + case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + throw new Error("There is no attachment"); + case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + throw new Error(" Height and width of the attachment are not the same."); + case gl.FRAMEBUFFER_UNSUPPORTED: + // #5.14.3 Event Types in https://registry.khronos.org/webgl/specs/1.0.0/ + if (!gl.isContextLost()) { + throw new Error( + "The format of the attachment is not supported or if depth and stencil attachments are not the same renderbuffer" + ); + } + break; + case (gl as WebGL2RenderingContext).FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: // Only for WebGL2 + throw new Error( + "The values of gl.RENDERBUFFER_SAMPLES are different among attached renderbuffers, or are non-zero if the attached images are a mix of renderbuffers and textures." + ); + } + } + private _gl: WebGLRenderingContext & WebGL2RenderingContext; private _isWebGL2: boolean; private _target: RenderTarget; private _frameBuffer: WebGLFramebuffer; - private _MSAAFrameBuffer: WebGLFramebuffer | null; private _depthRenderBuffer: WebGLRenderbuffer | null; - private _MSAAColorRenderBuffers: WebGLRenderbuffer[] = []; - private _MSAADepthRenderBuffer: WebGLRenderbuffer | null; private _oriDrawBuffers: GLenum[]; - private _blitDrawBuffers: GLenum[] | null; private _curMipLevel: number = 0; private _curFaceIndex: TextureCubeFace = undefined; + + // MSAA manager handles all MSAA-related operations + private _msaaManager: MSAAManager | null = null; /** * Create render target in WebGL platform. @@ -40,11 +206,6 @@ export class GLRenderTarget implements IPlatformRenderTarget { const { _colorTextures, _depth, width, height } = target; const isDepthTexture = _depth instanceof Texture; - /** todo - * MRT + Cube + [,MSAA] - * MRT + MSAA - */ - for (let i = 0, n = _colorTextures.length; i < n; i++) { const { format, isSRGBColorSpace } = _colorTextures[i]; if (!GLTexture._supportRenderBufferColorFormat(format, rhi)) { @@ -91,8 +252,7 @@ export class GLRenderTarget implements IPlatformRenderTarget { // bind MSAA FBO if (target.antiAliasing > 1) { - this._MSAAFrameBuffer = this._gl.createFramebuffer(); - this._bindMSAAFBO(); + this._msaaManager = new MSAAManager(this._gl, this._isWebGL2, target, this._oriDrawBuffers); } } @@ -151,8 +311,8 @@ export class GLRenderTarget implements IPlatformRenderTarget { this._curMipLevel = mipLevel; this._curFaceIndex = faceIndex; - if (this._MSAAFrameBuffer) { - gl.bindFramebuffer(gl.FRAMEBUFFER, this._MSAAFrameBuffer); + if (this._msaaManager) { + this._msaaManager.activate(); } } @@ -160,28 +320,9 @@ export class GLRenderTarget implements IPlatformRenderTarget { * Blit FBO. */ blitRenderTarget(): void { - if (!this._MSAAFrameBuffer) return; - - const gl = this._gl; - const mask = gl.COLOR_BUFFER_BIT | (this._target.depthTexture ? gl.DEPTH_BUFFER_BIT : 0); - const { colorTextureCount, width, height } = this._target; - - gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this._MSAAFrameBuffer); - gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this._frameBuffer); - - for (let textureIndex = 0; textureIndex < colorTextureCount; textureIndex++) { - const attachment = gl.COLOR_ATTACHMENT0 + textureIndex; - - this._blitDrawBuffers[textureIndex] = attachment; - - gl.readBuffer(attachment); - gl.drawBuffers(this._blitDrawBuffers); - gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, mask, gl.NEAREST); - - this._blitDrawBuffers[textureIndex] = gl.NONE; + if (this._msaaManager) { + this._msaaManager.resolveTo(this._frameBuffer); } - - gl.bindFramebuffer(gl.FRAMEBUFFER, null); } /** @@ -192,18 +333,14 @@ export class GLRenderTarget implements IPlatformRenderTarget { this._frameBuffer && gl.deleteFramebuffer(this._frameBuffer); this._depthRenderBuffer && gl.deleteRenderbuffer(this._depthRenderBuffer); - this._MSAAFrameBuffer && gl.deleteFramebuffer(this._MSAAFrameBuffer); - this._MSAADepthRenderBuffer && gl.deleteRenderbuffer(this._MSAADepthRenderBuffer); - for (let i = 0; i < this._MSAAColorRenderBuffers.length; i++) { - gl.deleteRenderbuffer(this._MSAAColorRenderBuffers[i]); + if (this._msaaManager) { + this._msaaManager.destroy(); + this._msaaManager = null; } this._frameBuffer = null; this._depthRenderBuffer = null; - this._MSAAFrameBuffer = null; - this._MSAAColorRenderBuffers.length = 0; - this._MSAADepthRenderBuffer = null; } private _bindMainFBO(): void { @@ -267,83 +404,4 @@ export class GLRenderTarget implements IPlatformRenderTarget { gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindRenderbuffer(gl.RENDERBUFFER, null); } - - private _bindMSAAFBO(): void { - const gl = this._gl; - const isWebGL2 = this._isWebGL2; - const MSAADepthRenderBuffer = gl.createRenderbuffer(); - - /** @ts-ignore */ - const { _depth, colorTextureCount, antiAliasing, width, height } = this._target; - - this._blitDrawBuffers = new Array(colorTextureCount); - this._MSAADepthRenderBuffer = MSAADepthRenderBuffer; - - gl.bindFramebuffer(gl.FRAMEBUFFER, this._MSAAFrameBuffer); - - // prepare MRT+MSAA color RBOs - for (let i = 0; i < colorTextureCount; i++) { - const MSAAColorRenderBuffer = gl.createRenderbuffer(); - - this._MSAAColorRenderBuffers[i] = MSAAColorRenderBuffer; - this._blitDrawBuffers[i] = gl.NONE; - - gl.bindRenderbuffer(gl.RENDERBUFFER, MSAAColorRenderBuffer); - gl.renderbufferStorageMultisample( - gl.RENDERBUFFER, - antiAliasing, - /** @ts-ignore */ - (this._target.getColorTexture(i)._platformTexture as GLTexture)._formatDetail.internalFormat, - width, - height - ); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, MSAAColorRenderBuffer); - } - gl.drawBuffers(this._oriDrawBuffers); - - // prepare MSAA depth RBO - if (_depth !== null) { - const { internalFormat, attachment } = - _depth instanceof Texture - ? /** @ts-ignore */ - (_depth._platformTexture as GLTexture)._formatDetail - : GLTexture._getRenderBufferDepthFormatDetail(_depth, gl, isWebGL2); - - gl.bindRenderbuffer(gl.RENDERBUFFER, MSAADepthRenderBuffer); - gl.renderbufferStorageMultisample(gl.RENDERBUFFER, antiAliasing, internalFormat, width, height); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, MSAADepthRenderBuffer); - } - - this._checkFrameBuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - gl.bindRenderbuffer(gl.RENDERBUFFER, null); - } - - private _checkFrameBuffer(): void { - const gl = this._gl; - const e = gl.checkFramebufferStatus(gl.FRAMEBUFFER); - - switch (e) { - case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - throw new Error( - "The attachment types are mismatched or not all framebuffer attachment points are framebuffer attachment complete" - ); - case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - throw new Error("There is no attachment"); - case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: - throw new Error(" Height and width of the attachment are not the same."); - case gl.FRAMEBUFFER_UNSUPPORTED: - // #5.14.3 Event Types in https://registry.khronos.org/webgl/specs/1.0.0/ - if (!gl.isContextLost()) { - throw new Error( - "The format of the attachment is not supported or if depth and stencil attachments are not the same renderbuffer" - ); - } - break; - case gl.FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: // Only for WebGL2 - throw new Error( - "The values of gl.RENDERBUFFER_SAMPLES are different among attached renderbuffers, or are non-zero if the attached images are a mix of renderbuffers and textures." - ); - } - } } From d0cfe2a836c7e14091a56a5c7a8bac427b11546e Mon Sep 17 00:00:00 2001 From: eyworldwide Date: Mon, 28 Jul 2025 18:17:55 +0800 Subject: [PATCH 2/6] refactor: add test for GLRenderTarget --- tests/src/rhi-webgl/GLRenderTarget.test.ts | 225 +++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 tests/src/rhi-webgl/GLRenderTarget.test.ts diff --git a/tests/src/rhi-webgl/GLRenderTarget.test.ts b/tests/src/rhi-webgl/GLRenderTarget.test.ts new file mode 100644 index 0000000000..76fdf494b5 --- /dev/null +++ b/tests/src/rhi-webgl/GLRenderTarget.test.ts @@ -0,0 +1,225 @@ +import { RenderTarget, Texture2D, TextureFormat } from "@galacean/engine-core"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { GLRenderTarget } from "@galacean/engine-rhi-webgl/src/GLRenderTarget"; +import { describe, beforeAll, beforeEach, expect, it, vi, afterEach } from "vitest"; + +describe("GLRenderTarget", () => { + let engine: WebGLEngine; + let gl: WebGLRenderingContext | WebGL2RenderingContext; + + beforeAll(async () => { + const canvas = document.createElement("canvas"); + engine = await WebGLEngine.create({ canvas }); + // @ts-ignore + gl = engine._hardwareRenderer.gl; + }); + + afterEach(() => { + // 清理所有的 mock + vi.restoreAllMocks(); + }); + + describe("checkFrameBufferStatus static method", () => { + it("should not throw error for complete framebuffer", () => { + // Mock a complete framebuffer + vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_COMPLETE); + + expect(() => { + GLRenderTarget.checkFrameBufferStatus(gl); + }).not.toThrow(); + }); + + it("should throw error for incomplete attachment", () => { + vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT); + + expect(() => { + GLRenderTarget.checkFrameBufferStatus(gl); + }).toThrow("The attachment types are mismatched"); + }); + + it("should throw error for missing attachment", () => { + vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT); + + expect(() => { + GLRenderTarget.checkFrameBufferStatus(gl); + }).toThrow("There is no attachment"); + }); + + it("should throw error for dimension mismatch", () => { + vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS); + + expect(() => { + GLRenderTarget.checkFrameBufferStatus(gl); + }).toThrow("Height and width of the attachment are not the same"); + }); + + it("should throw error for unsupported format when context is not lost", () => { + vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_UNSUPPORTED); + vi.spyOn(gl, 'isContextLost').mockReturnValue(false); + + expect(() => { + GLRenderTarget.checkFrameBufferStatus(gl); + }).toThrow("The format of the attachment is not supported"); + }); + + it("should not throw error for unsupported format when context is lost", () => { + vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_UNSUPPORTED); + vi.spyOn(gl, 'isContextLost').mockReturnValue(true); + + expect(() => { + GLRenderTarget.checkFrameBufferStatus(gl); + }).not.toThrow(); + }); + + it("should handle WebGL2 multisample error if available", () => { + // Only test on WebGL2 + if ('FRAMEBUFFER_INCOMPLETE_MULTISAMPLE' in gl) { + vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue( + (gl as WebGL2RenderingContext).FRAMEBUFFER_INCOMPLETE_MULTISAMPLE + ); + + expect(() => { + GLRenderTarget.checkFrameBufferStatus(gl); + }).toThrow("The values of gl.RENDERBUFFER_SAMPLES are different"); + } + }); + }); + + describe("MSAA functionality integration", () => { + it("should handle MSAA render targets without errors", () => { + // Mock checkFrameBufferStatus to avoid WebGL validation issues + const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, 'checkFrameBufferStatus') + .mockImplementation(() => {}); + + try { + const colorTexture = new Texture2D(engine, 512, 512); + + // Test creating MSAA render target + expect(() => { + const renderTarget = new RenderTarget(engine, 512, 512, colorTexture, TextureFormat.Depth16, 4); + renderTarget.destroy(); + }).not.toThrow(); + + } finally { + mockCheckFrameBuffer.mockRestore(); + } + }); + + it("should handle non-MSAA render targets correctly", () => { + expect(() => { + const colorTexture = new Texture2D(engine, 512, 512); + const renderTarget = new RenderTarget(engine, 512, 512, colorTexture, TextureFormat.Depth16, 1); + renderTarget.destroy(); + }).not.toThrow(); + }); + + it("should support basic render target operations", () => { + const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, 'checkFrameBufferStatus') + .mockImplementation(() => {}); + + try { + const colorTexture = new Texture2D(engine, 512, 512); + const renderTarget = new RenderTarget(engine, 512, 512, colorTexture, TextureFormat.Depth16, 4); + + // @ts-ignore - Access platform render target + const glRenderTarget = renderTarget._platformRenderTarget as GLRenderTarget; + + // Test that we can call activeRenderTarget without errors + expect(() => { + glRenderTarget.activeRenderTarget(0); + }).not.toThrow(); + + // Test that we can call blitRenderTarget without errors + expect(() => { + glRenderTarget.blitRenderTarget(); + }).not.toThrow(); + + renderTarget.destroy(); + } finally { + mockCheckFrameBuffer.mockRestore(); + } + }); + }); + + + + describe("Error handling and validation", () => { + it("should validate texture format support", () => { + const colorTexture = new Texture2D(engine, 512, 512); + + // Test unsupported texture format should throw error (expected behavior) + expect(() => { + new RenderTarget(engine, 512, 512, colorTexture, TextureFormat.R32G32B32A32, 1); + }).toThrow("this TextureFormat is not supported"); + }); + + it("should validate color texture size consistency", () => { + // Mock checkFrameBufferStatus to focus on size validation + const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, 'checkFrameBufferStatus') + .mockImplementation(() => {}); + + try { + const colorTexture1 = new Texture2D(engine, 512, 512); + const colorTexture2 = new Texture2D(engine, 256, 256); // Different size + + expect(() => { + new RenderTarget(engine, 512, 512, [colorTexture1, colorTexture2], TextureFormat.Depth16, 1); + }).toThrow("ColorTexture's size must as same as RenderTarget"); + } finally { + mockCheckFrameBuffer.mockRestore(); + } + }); + + it("should handle MSAA level auto-downgrade", () => { + // Mock maxAntiAliasing capability + // @ts-ignore + const originalMaxAA = engine._hardwareRenderer.capability.maxAntiAliasing; + // @ts-ignore + engine._hardwareRenderer.capability._maxAntiAliasing = 2; + + const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, 'checkFrameBufferStatus') + .mockImplementation(() => {}); + + try { + const colorTexture = new Texture2D(engine, 512, 512); + const renderTarget = new RenderTarget(engine, 512, 512, colorTexture, TextureFormat.Depth16, 8); + + // Should be downgraded to max supported level + expect(renderTarget.antiAliasing).toBe(2); + + renderTarget.destroy(); + } finally { + // Restore original maxAntiAliasing + // @ts-ignore + engine._hardwareRenderer.capability._maxAntiAliasing = originalMaxAA; + mockCheckFrameBuffer.mockRestore(); + } + }); + }); + + describe("Basic render target lifecycle", () => { + it("should create and destroy render targets without errors", () => { + expect(() => { + const colorTexture = new Texture2D(engine, 512, 512); + const renderTarget = new RenderTarget(engine, 512, 512, colorTexture, TextureFormat.Depth16, 1); + renderTarget.destroy(); + }).not.toThrow(); + }); + + it("should handle render target operations without errors", () => { + const colorTexture = new Texture2D(engine, 512, 512); + const renderTarget = new RenderTarget(engine, 512, 512, colorTexture, TextureFormat.Depth16, 1); + + // @ts-ignore + const glRenderTarget = renderTarget._platformRenderTarget as GLRenderTarget; + + // Test that basic operations don't throw errors + expect(() => { + glRenderTarget.activeRenderTarget(0); + glRenderTarget.blitRenderTarget(); + }).not.toThrow(); + + renderTarget.destroy(); + }); + }); +}); \ No newline at end of file From e2ccb4b1cd3dc392a925ed147d4494893a9f2493 Mon Sep 17 00:00:00 2001 From: eyworldwide Date: Mon, 28 Jul 2025 18:29:53 +0800 Subject: [PATCH 3/6] refactor: update test for GLRenderTarget --- tests/src/rhi-webgl/GLRenderTarget.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/src/rhi-webgl/GLRenderTarget.test.ts b/tests/src/rhi-webgl/GLRenderTarget.test.ts index 76fdf494b5..904578da19 100644 --- a/tests/src/rhi-webgl/GLRenderTarget.test.ts +++ b/tests/src/rhi-webgl/GLRenderTarget.test.ts @@ -15,7 +15,6 @@ describe("GLRenderTarget", () => { }); afterEach(() => { - // 清理所有的 mock vi.restoreAllMocks(); }); From 774b640ede01b38294c2e4d23b59b06cfc426efd Mon Sep 17 00:00:00 2001 From: eyworldwide Date: Tue, 29 Jul 2025 16:08:01 +0800 Subject: [PATCH 4/6] refactor: createRenderBuffer in GLRenderTarget --- packages/rhi-webgl/src/GLRenderTarget.ts | 67 +++++++++++++++--------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/packages/rhi-webgl/src/GLRenderTarget.ts b/packages/rhi-webgl/src/GLRenderTarget.ts index 4a5e803f7b..0c8a4a5046 100644 --- a/packages/rhi-webgl/src/GLRenderTarget.ts +++ b/packages/rhi-webgl/src/GLRenderTarget.ts @@ -95,36 +95,27 @@ class MSAAManager { this._colorRenderBuffers.length = 0; } + + private _bindFBO(): void { const gl = this._gl; const isWebGL2 = this._isWebGL2; - const depthRenderBuffer = gl.createRenderbuffer(); /** @ts-ignore */ - const { _depth, colorTextureCount, antiAliasing, width, height } = this._target; + const { _depth, colorTextureCount } = this._target; this._blitDrawBuffers = new Array(colorTextureCount); - this._depthRenderBuffer = depthRenderBuffer; gl.bindFramebuffer(gl.FRAMEBUFFER, this._frameBuffer); // prepare MRT+MSAA color RBOs for (let i = 0; i < colorTextureCount; i++) { - const colorRenderBuffer = gl.createRenderbuffer(); - - this._colorRenderBuffers[i] = colorRenderBuffer; this._blitDrawBuffers[i] = gl.NONE; - gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderBuffer); - gl.renderbufferStorageMultisample( - gl.RENDERBUFFER, - antiAliasing, - /** @ts-ignore */ - (this._target.getColorTexture(i)._platformTexture as GLTexture)._formatDetail.internalFormat, - width, - height - ); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, colorRenderBuffer); + const internalFormat = /** @ts-ignore */ + (this._target.getColorTexture(i)._platformTexture as GLTexture)._formatDetail.internalFormat; + + this._colorRenderBuffers[i] = GLRenderTarget.createRenderBuffer(this._gl, this._target, internalFormat, gl.COLOR_ATTACHMENT0 + i); } gl.drawBuffers(this._oriDrawBuffers); @@ -136,9 +127,7 @@ class MSAAManager { (_depth._platformTexture as GLTexture)._formatDetail : GLTexture._getRenderBufferDepthFormatDetail(_depth, gl, isWebGL2); - gl.bindRenderbuffer(gl.RENDERBUFFER, depthRenderBuffer); - gl.renderbufferStorageMultisample(gl.RENDERBUFFER, antiAliasing, internalFormat, width, height); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, depthRenderBuffer); + this._depthRenderBuffer = GLRenderTarget.createRenderBuffer(this._gl, this._target, internalFormat, attachment); } GLRenderTarget.checkFrameBufferStatus(gl); @@ -151,6 +140,38 @@ class MSAAManager { * The render target in WebGL platform is used for off-screen rendering. */ export class GLRenderTarget implements IPlatformRenderTarget { + /** + * Create and configure render buffer + * @param gl - WebGL context + * @param target - Render target + * @param internalFormat - Internal format for the render buffer + * @param attachment - Framebuffer attachment point + * @returns Created and configured WebGL render buffer + */ + static createRenderBuffer( + gl: WebGLRenderingContext & WebGL2RenderingContext, + target: RenderTarget, + internalFormat: GLenum, + attachment: GLenum + ): WebGLRenderbuffer { + const renderBuffer = gl.createRenderbuffer(); + const { width, height, antiAliasing } = target; + + gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuffer); + + if (antiAliasing > 1) { + // Use MSAA storage + gl.renderbufferStorageMultisample(gl.RENDERBUFFER, antiAliasing, internalFormat, width, height); + } else { + // Use regular storage + gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height); + } + + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, renderBuffer); + + return renderBuffer; + } + /** * Check frame buffer status and throw error if invalid. * Static utility method that can be used by any component that creates FrameBuffers. @@ -391,13 +412,7 @@ export class GLRenderTarget implements IPlatformRenderTarget { ); } else if (this._target.antiAliasing <= 1) { const { internalFormat, attachment } = GLTexture._getRenderBufferDepthFormatDetail(_depth, gl, isWebGL2); - const depthRenderBuffer = gl.createRenderbuffer(); - - this._depthRenderBuffer = depthRenderBuffer; - - gl.bindRenderbuffer(gl.RENDERBUFFER, depthRenderBuffer); - gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, depthRenderBuffer); + this._depthRenderBuffer = GLRenderTarget.createRenderBuffer(gl, this._target, internalFormat, attachment); } } From 62bbf93a7fed395b8f1951987d486cd62e0323ce Mon Sep 17 00:00:00 2001 From: eyworldwide Date: Tue, 29 Jul 2025 16:35:00 +0800 Subject: [PATCH 5/6] refactor: rename GLRenderTarget internal method --- packages/rhi-webgl/src/GLRenderTarget.ts | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/rhi-webgl/src/GLRenderTarget.ts b/packages/rhi-webgl/src/GLRenderTarget.ts index 0c8a4a5046..d43665531a 100644 --- a/packages/rhi-webgl/src/GLRenderTarget.ts +++ b/packages/rhi-webgl/src/GLRenderTarget.ts @@ -115,7 +115,7 @@ class MSAAManager { const internalFormat = /** @ts-ignore */ (this._target.getColorTexture(i)._platformTexture as GLTexture)._formatDetail.internalFormat; - this._colorRenderBuffers[i] = GLRenderTarget.createRenderBuffer(this._gl, this._target, internalFormat, gl.COLOR_ATTACHMENT0 + i); + this._colorRenderBuffers[i] = GLRenderTarget._createRenderBuffer(this._gl, this._target, internalFormat, gl.COLOR_ATTACHMENT0 + i); } gl.drawBuffers(this._oriDrawBuffers); @@ -127,10 +127,10 @@ class MSAAManager { (_depth._platformTexture as GLTexture)._formatDetail : GLTexture._getRenderBufferDepthFormatDetail(_depth, gl, isWebGL2); - this._depthRenderBuffer = GLRenderTarget.createRenderBuffer(this._gl, this._target, internalFormat, attachment); + this._depthRenderBuffer = GLRenderTarget._createRenderBuffer(this._gl, this._target, internalFormat, attachment); } - GLRenderTarget.checkFrameBufferStatus(gl); + GLRenderTarget._checkFrameBufferStatus(gl); gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindRenderbuffer(gl.RENDERBUFFER, null); } @@ -141,14 +141,9 @@ class MSAAManager { */ export class GLRenderTarget implements IPlatformRenderTarget { /** - * Create and configure render buffer - * @param gl - WebGL context - * @param target - Render target - * @param internalFormat - Internal format for the render buffer - * @param attachment - Framebuffer attachment point - * @returns Created and configured WebGL render buffer + * @internal */ - static createRenderBuffer( + static _createRenderBuffer( gl: WebGLRenderingContext & WebGL2RenderingContext, target: RenderTarget, internalFormat: GLenum, @@ -173,10 +168,9 @@ export class GLRenderTarget implements IPlatformRenderTarget { } /** - * Check frame buffer status and throw error if invalid. - * Static utility method that can be used by any component that creates FrameBuffers. + * @internal */ - static checkFrameBufferStatus(gl: WebGLRenderingContext | WebGL2RenderingContext): void { + static _checkFrameBufferStatus(gl: WebGLRenderingContext | WebGL2RenderingContext): void { const e = gl.checkFramebufferStatus(gl.FRAMEBUFFER); switch (e) { @@ -412,7 +406,7 @@ export class GLRenderTarget implements IPlatformRenderTarget { ); } else if (this._target.antiAliasing <= 1) { const { internalFormat, attachment } = GLTexture._getRenderBufferDepthFormatDetail(_depth, gl, isWebGL2); - this._depthRenderBuffer = GLRenderTarget.createRenderBuffer(gl, this._target, internalFormat, attachment); + this._depthRenderBuffer = GLRenderTarget._createRenderBuffer(gl, this._target, internalFormat, attachment); } } From 515451f7413dff015e4061556ec036f82442828b Mon Sep 17 00:00:00 2001 From: eyworldwide Date: Tue, 29 Jul 2025 16:38:23 +0800 Subject: [PATCH 6/6] refactor: rename GLRenderTarget internal method --- tests/src/rhi-webgl/GLRenderTarget.test.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/src/rhi-webgl/GLRenderTarget.test.ts b/tests/src/rhi-webgl/GLRenderTarget.test.ts index 904578da19..beb0d744f8 100644 --- a/tests/src/rhi-webgl/GLRenderTarget.test.ts +++ b/tests/src/rhi-webgl/GLRenderTarget.test.ts @@ -24,7 +24,7 @@ describe("GLRenderTarget", () => { vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_COMPLETE); expect(() => { - GLRenderTarget.checkFrameBufferStatus(gl); + GLRenderTarget._checkFrameBufferStatus(gl); }).not.toThrow(); }); @@ -32,7 +32,7 @@ describe("GLRenderTarget", () => { vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT); expect(() => { - GLRenderTarget.checkFrameBufferStatus(gl); + GLRenderTarget._checkFrameBufferStatus(gl); }).toThrow("The attachment types are mismatched"); }); @@ -40,7 +40,7 @@ describe("GLRenderTarget", () => { vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT); expect(() => { - GLRenderTarget.checkFrameBufferStatus(gl); + GLRenderTarget._checkFrameBufferStatus(gl); }).toThrow("There is no attachment"); }); @@ -48,7 +48,7 @@ describe("GLRenderTarget", () => { vi.spyOn(gl, 'checkFramebufferStatus').mockReturnValue(gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS); expect(() => { - GLRenderTarget.checkFrameBufferStatus(gl); + GLRenderTarget._checkFrameBufferStatus(gl); }).toThrow("Height and width of the attachment are not the same"); }); @@ -57,7 +57,7 @@ describe("GLRenderTarget", () => { vi.spyOn(gl, 'isContextLost').mockReturnValue(false); expect(() => { - GLRenderTarget.checkFrameBufferStatus(gl); + GLRenderTarget._checkFrameBufferStatus(gl); }).toThrow("The format of the attachment is not supported"); }); @@ -66,7 +66,7 @@ describe("GLRenderTarget", () => { vi.spyOn(gl, 'isContextLost').mockReturnValue(true); expect(() => { - GLRenderTarget.checkFrameBufferStatus(gl); + GLRenderTarget._checkFrameBufferStatus(gl); }).not.toThrow(); }); @@ -78,7 +78,7 @@ describe("GLRenderTarget", () => { ); expect(() => { - GLRenderTarget.checkFrameBufferStatus(gl); + GLRenderTarget._checkFrameBufferStatus(gl); }).toThrow("The values of gl.RENDERBUFFER_SAMPLES are different"); } }); @@ -87,7 +87,7 @@ describe("GLRenderTarget", () => { describe("MSAA functionality integration", () => { it("should handle MSAA render targets without errors", () => { // Mock checkFrameBufferStatus to avoid WebGL validation issues - const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, 'checkFrameBufferStatus') + const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, '_checkFrameBufferStatus') .mockImplementation(() => {}); try { @@ -113,7 +113,7 @@ describe("GLRenderTarget", () => { }); it("should support basic render target operations", () => { - const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, 'checkFrameBufferStatus') + const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, '_checkFrameBufferStatus') .mockImplementation(() => {}); try { @@ -154,7 +154,7 @@ describe("GLRenderTarget", () => { it("should validate color texture size consistency", () => { // Mock checkFrameBufferStatus to focus on size validation - const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, 'checkFrameBufferStatus') + const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, '_checkFrameBufferStatus') .mockImplementation(() => {}); try { @@ -176,7 +176,7 @@ describe("GLRenderTarget", () => { // @ts-ignore engine._hardwareRenderer.capability._maxAntiAliasing = 2; - const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, 'checkFrameBufferStatus') + const mockCheckFrameBuffer = vi.spyOn(GLRenderTarget, '_checkFrameBufferStatus') .mockImplementation(() => {}); try {