diff --git a/e2e/case/.mockForE2E.ts b/e2e/case/.mockForE2E.ts index 1218e394dc..25a0f95547 100644 --- a/e2e/case/.mockForE2E.ts +++ b/e2e/case/.mockForE2E.ts @@ -1,4 +1,4 @@ -import { Camera, Engine, RenderTarget, Texture2D } from "@galacean/engine-core"; +import { Camera, Engine, RenderTarget, Texture2D, TextureFormat } from "@galacean/engine-core"; export const updateForE2E = (engine, deltaTime = 100, loopTime = 10) => { engine._vSyncCount = Infinity; @@ -19,7 +19,7 @@ let flipYCanvas: HTMLCanvasElement = null; export function initScreenshot( engine: Engine, - camera: Camera, + camera: Camera | Camera[], width: number = 1200, height: number = 800, flipY = false, @@ -38,15 +38,25 @@ export function initScreenshot( const isPaused = engine.isPaused; engine.pause(); - const originalTarget = camera.renderTarget; + const cameras = Array.isArray(camera) ? camera : [camera]; + const callbacks = []; const renderColorTexture = new Texture2D(engine, width, height); const renderTargetData = new Uint8Array(width * height * 4); - const renderTarget = new RenderTarget(engine, width, height, renderColorTexture, undefined, 1); + const renderTarget = new RenderTarget(engine, width, height, renderColorTexture, TextureFormat.Depth24Stencil8, 1); - // render to off-screen - camera.renderTarget = renderTarget; - camera.aspectRatio = width / height; - camera.render(); + cameras.forEach((camera) => { + const originalTarget = camera.renderTarget; + + // render to off-screen + camera.renderTarget = renderTarget; + camera.aspectRatio = width / height; + camera.render(); + + callbacks.push(() => { + camera.renderTarget = originalTarget; + camera.resetAspectRatio(); + }); + }); renderColorTexture.getPixelBuffer(0, 0, width, height, 0, renderTargetData); @@ -94,8 +104,7 @@ export function initScreenshot( // window.URL.revokeObjectURL(url); // revert - camera.renderTarget = originalTarget; - camera.resetAspectRatio(); + callbacks.forEach((cb) => cb()); !isPaused && engine.resume(); }, isPNG ? "image/png" : "image/jpeg", diff --git a/e2e/case/multi-camera-no-clear.ts b/e2e/case/multi-camera-no-clear.ts new file mode 100644 index 0000000000..0e0dfc9c9f --- /dev/null +++ b/e2e/case/multi-camera-no-clear.ts @@ -0,0 +1,96 @@ +/** + * @title Multi camera no clear + * @category Advance + */ +import { + BlinnPhongMaterial, + Camera, + CameraClearFlags, + Color, + Engine, + Layer, + Logger, + MeshRenderer, + PrimitiveMesh, + Scene, + WebGLEngine, + WebGLMode +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +Logger.enable(); +WebGLEngine.create({ + canvas: "canvas", + graphicDeviceOptions: { + webGLMode: WebGLMode.WebGL2 + } +}).then((engine) => { + engine.canvas.resizeByClientSize(); + + initFirstScene(engine); + engine.run(); +}); + +function initFirstScene(engine: Engine): Scene { + const scene = engine.sceneManager.scenes[0]; + const rootEntity = scene.createRootEntity(); + + // const renderColorTexture = new Texture2D(engine, 1024, 1024); + // const depthTexture = new Texture2D(engine, 1024, 1024, TextureFormat.Depth24Stencil8, false); + // const renderTarget = new RenderTarget(engine, 1024, 1024, renderColorTexture, TextureFormat.Depth); + // const renderTarget = new RenderTarget(engine, 1024, 1024, renderColorTexture, depthTexture); + + // Create full screen camera + const cameraEntity = rootEntity.createChild("fullscreen-camera"); + const camera = cameraEntity.addComponent(Camera); + // camera.renderTarget = renderTarget; + camera.cullingMask = Layer.Layer0; + cameraEntity.transform.setPosition(0, 0, 20); + + // Create cube + const cubeEntity = rootEntity.createChild("cube"); + cubeEntity.transform.setPosition(-3, 0, 3); + const renderer = cubeEntity.addComponent(MeshRenderer); + renderer.mesh = PrimitiveMesh.createSphere(engine, 2, 24); + const material = new BlinnPhongMaterial(engine); + material.baseColor = new Color(1, 0, 0, 1); + renderer.setMaterial(material); + + { + const cameraEntity = rootEntity.createChild("window-camera"); + const camera2 = cameraEntity.addComponent(Camera); + // camera2.renderTarget = renderTarget; + camera2.cullingMask = Layer.Layer1; + camera2.enablePostProcess = true; + camera2.enableHDR = true; + camera2.clearFlags = CameraClearFlags.None; + camera2.msaaSamples = 4; + + // @ts-ignore + const bloomEffect = scene._postProcessManager._bloomEffect; + // @ts-ignore + const tonemappingEffect = scene._postProcessManager._tonemappingEffect; + + bloomEffect.enabled = true; + tonemappingEffect.enabled = true; + bloomEffect.threshold = 0.1; + bloomEffect.intensity = 2; + cameraEntity.transform.setPosition(0, 0, 20); + + // Create cube + const cubeEntity = rootEntity.createChild("cube"); + cubeEntity.layer = Layer.Layer1; + cubeEntity.transform.setPosition(-2, 0, 3); + const renderer = cubeEntity.addComponent(MeshRenderer); + renderer.mesh = PrimitiveMesh.createSphere(engine, 2, 24); + const material = new BlinnPhongMaterial(engine); + material.baseColor = new Color(1, 0, 0, 1); + material.emissiveColor.set(1, 0, 0, 1); + renderer.setMaterial(material); + + updateForE2E(engine); + initScreenshot(engine, [camera, camera2]); + } + + return scene; +} diff --git a/e2e/case/multi-scene-clear.ts b/e2e/case/multi-scene-clear.ts new file mode 100644 index 0000000000..c54438a949 --- /dev/null +++ b/e2e/case/multi-scene-clear.ts @@ -0,0 +1,85 @@ +/** + * @title Multi scene clear + * @category Advance + */ +import { + BlinnPhongMaterial, + Camera, + CameraClearFlags, + Color, + Engine, + Logger, + MeshRenderer, + PrimitiveMesh, + Scene, + WebGLEngine +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +Logger.enable(); +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const firstCamera = initFirstScene(engine); + const secondCamera = initSecondScene(engine); + + updateForE2E(engine); + initScreenshot(engine, [firstCamera, secondCamera]); + // initScreenshot(engine, [secondCamera, firstCamera]); +}); + +function initFirstScene(engine: Engine): Camera { + const scene = engine.sceneManager.scenes[0]; + const rootEntity = scene.createRootEntity(); + + // Create full screen camera + const cameraEntity = rootEntity.createChild("fullscreen-camera"); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(0, 0, 20); + + // Create cube + const cubeEntity = rootEntity.createChild("cube"); + cubeEntity.transform.setPosition(-2, 0, 3); + const renderer = cubeEntity.addComponent(MeshRenderer); + renderer.mesh = PrimitiveMesh.createCuboid(engine, 2, 2, 2); + const material = new BlinnPhongMaterial(engine); + material.baseColor = new Color(1, 0, 0, 1); + renderer.setMaterial(material); + + return camera; +} + +function initSecondScene(engine: Engine): Camera { + // Init window camera + const scene = new Scene(engine); + engine.sceneManager.addScene(scene); + const rootEntity = scene.createRootEntity(); + + const cameraEntity = rootEntity.createChild("window-camera"); + const camera = cameraEntity.addComponent(Camera); + camera.enablePostProcess = true; + // camera.clearFlags = CameraClearFlags.None; + + // @ts-ignore + const bloomEffect = scene._postProcessManager._bloomEffect; + // @ts-ignore + const tonemappingEffect = scene._postProcessManager._tonemappingEffect; + + bloomEffect.enabled = true; + tonemappingEffect.enabled = true; + bloomEffect.threshold = 0.1; + bloomEffect.intensity = 2; + cameraEntity.transform.setPosition(0, 0, 20); + + // Create cube + const cubeEntity = rootEntity.createChild("cube"); + cubeEntity.transform.setPosition(2, 0, 3); + const renderer = cubeEntity.addComponent(MeshRenderer); + renderer.mesh = PrimitiveMesh.createCuboid(engine, 2, 2, 2); + const material = new BlinnPhongMaterial(engine); + material.baseColor = new Color(1, 0, 0, 1); + material.emissiveColor.set(1, 0, 0, 1); + renderer.setMaterial(material); + + return camera; +} diff --git a/e2e/case/multi-scene-no-clear.ts b/e2e/case/multi-scene-no-clear.ts new file mode 100644 index 0000000000..91985fa839 --- /dev/null +++ b/e2e/case/multi-scene-no-clear.ts @@ -0,0 +1,91 @@ +/** + * @title Multi scene no clear + * @category Advance + */ +import { + BlinnPhongMaterial, + Camera, + CameraClearFlags, + Color, + Engine, + Logger, + MeshRenderer, + PrimitiveMesh, + Scene, + WebGLEngine, + WebGLMode +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +Logger.enable(); +WebGLEngine.create({ + canvas: "canvas", + graphicDeviceOptions: { + webGLMode: WebGLMode.WebGL2 + } +}).then((engine) => { + engine.canvas.resizeByClientSize(); + + const firstCamera = initFirstScene(engine); + const secondCamera = initSecondScene(engine); + + updateForE2E(engine); + initScreenshot(engine, [firstCamera, secondCamera]); + // initScreenshot(engine, [secondCamera, firstCamera]); +}); + +function initFirstScene(engine: Engine): Camera { + const scene = engine.sceneManager.scenes[0]; + const rootEntity = scene.createRootEntity(); + + // Create full screen camera + const cameraEntity = rootEntity.createChild("fullscreen-camera"); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(0, 0, 20); + + // Create cube + const cubeEntity = rootEntity.createChild("cube"); + cubeEntity.transform.setPosition(-2, 0, 3); + const renderer = cubeEntity.addComponent(MeshRenderer); + renderer.mesh = PrimitiveMesh.createCuboid(engine, 2, 2, 2); + const material = new BlinnPhongMaterial(engine); + material.baseColor = new Color(1, 0, 0, 1); + renderer.setMaterial(material); + + return camera; +} + +function initSecondScene(engine: Engine): Camera { + // Init window camera + const scene = new Scene(engine); + engine.sceneManager.addScene(scene); + const rootEntity = scene.createRootEntity(); + + const cameraEntity = rootEntity.createChild("window-camera"); + const camera = cameraEntity.addComponent(Camera); + camera.enablePostProcess = true; + camera.clearFlags = CameraClearFlags.None; + + // @ts-ignore + const bloomEffect = scene._postProcessManager._bloomEffect; + // @ts-ignore + const tonemappingEffect = scene._postProcessManager._tonemappingEffect; + + bloomEffect.enabled = true; + tonemappingEffect.enabled = true; + bloomEffect.threshold = 0.1; + bloomEffect.intensity = 2; + cameraEntity.transform.setPosition(0, 0, 20); + + // Create cube + const cubeEntity = rootEntity.createChild("cube"); + cubeEntity.transform.setPosition(2, 0, 3); + const renderer = cubeEntity.addComponent(MeshRenderer); + renderer.mesh = PrimitiveMesh.createCuboid(engine, 2, 2, 2); + const material = new BlinnPhongMaterial(engine); + material.baseColor = new Color(1, 0, 0, 1); + material.emissiveColor.set(1, 0, 0, 1); + renderer.setMaterial(material); + + return camera; +} diff --git a/e2e/config.ts b/e2e/config.ts index d9c44ae69e..1da73a6a34 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -230,6 +230,21 @@ export const E2E_CONFIG = { category: "Advance", caseFileName: "project-loader", threshold: 0.4 + }, + MultiSceneClear: { + category: "Advance", + caseFileName: "multi-scene-clear", + threshold: 0.2 + }, + MultiSceneNoClear: { + category: "Advance", + caseFileName: "multi-scene-no-clear", + threshold: 0.2 + }, + MultiCameraNoClear: { + category: "Advance", + caseFileName: "multi-camera-no-clear", + threshold: 0.2 } } }; diff --git a/e2e/fixtures/originImage/Advance_multi-camera-no-clear.jpg b/e2e/fixtures/originImage/Advance_multi-camera-no-clear.jpg new file mode 100644 index 0000000000..9c69c14942 --- /dev/null +++ b/e2e/fixtures/originImage/Advance_multi-camera-no-clear.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca1ae66e23d2279fb2c2e7089ada31cb1022f2f3d814d76d4e67e430a2b24131 +size 74464 diff --git a/e2e/fixtures/originImage/Advance_multi-scene-clear.jpg b/e2e/fixtures/originImage/Advance_multi-scene-clear.jpg new file mode 100644 index 0000000000..9f87a54d11 --- /dev/null +++ b/e2e/fixtures/originImage/Advance_multi-scene-clear.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1c23f8dd7c4d9fb5642f69cf3c8bc0cd60ec13cdceea6e14d1199c931b788cf +size 38370 diff --git a/e2e/fixtures/originImage/Advance_multi-scene-no-clear.jpg b/e2e/fixtures/originImage/Advance_multi-scene-no-clear.jpg new file mode 100644 index 0000000000..1c6bd2d70d --- /dev/null +++ b/e2e/fixtures/originImage/Advance_multi-scene-no-clear.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7915c86f36264181445baa0245c051b1732ee94090cc0f81c8e7249a68687ab +size 46193 diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index 75bf0c89df..577952b92e 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -83,7 +83,7 @@ export class Camera extends Component { /** * Multi-sample anti-aliasing samples when use independent canvas mode. * - * @remarks The `independentCanvasEnabled` property should be `true` to take effect, otherwise it will be invalid. + * @remarks It will take effect when `independentCanvasEnabled` property is `true`, otherwise it will be invalid. */ msaaSamples: MSAASamples = MSAASamples.None; @@ -162,15 +162,19 @@ export class Camera extends Component { /** * Whether independent canvas is enabled. - * * @remarks If true, the msaa in viewport can turn or off independently by `msaaSamples` property. */ get independentCanvasEnabled(): boolean { - if (this.enableHDR || (this.enablePostProcess && this.scene._postProcessManager.hasActiveEffect)) { + // Uber pass need internal RT + if (this.enablePostProcess && this.scene._postProcessManager.hasActiveEffect) { return true; } - return this.opaqueTextureEnabled && !this._renderTarget; + if (this.enableHDR || this.opaqueTextureEnabled) { + return this._getInternalColorTextureFormat() !== this.renderTarget?.getColorTexture(0).format; + } + + return false; } /** diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 6d95edbdd2..c5b6d88893 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -1,6 +1,7 @@ import { Vector2 } from "@galacean/engine-math"; import { Background } from "../Background"; import { Camera } from "../Camera"; +import { Logger } from "../base/Logger"; import { BackgroundMode } from "../enums/BackgroundMode"; import { BackgroundTextureFillMode } from "../enums/BackgroundTextureFillMode"; import { CameraClearFlags } from "../enums/CameraClearFlags"; @@ -43,6 +44,9 @@ export class BasicRenderPipeline { private _cascadedShadowCasterPass: CascadedShadowCasterPass; private _depthOnlyPass: DepthOnlyPass; private _opaqueTexturePass: OpaqueTexturePass; + private _grabTexture: Texture2D; + private _canUseBlitFrameBuffer = false; + private _shouldGrabColor = false; /** * Create a basic render pipeline. @@ -77,10 +81,20 @@ export class BasicRenderPipeline { const camera = this._camera; const { scene, engine } = camera; + const rhi = engine._hardwareRenderer; const cullingResults = this._cullingResults; const sunlight = scene._lightManager._sunlight; const depthOnlyPass = this._depthOnlyPass; const depthPassEnabled = camera.depthTextureMode === DepthTextureMode.PrePass && depthOnlyPass._supportDepthTexture; + const finalClearFlags = camera.clearFlags & ~(ignoreClear ?? CameraClearFlags.None); + const independentCanvasEnabled = camera.independentCanvasEnabled; + const msaaSamples = camera.renderTarget ? camera.renderTarget.antiAliasing : camera.msaaSamples; + this._shouldGrabColor = independentCanvasEnabled && !(finalClearFlags & CameraClearFlags.Color); + // 1. Only support blitFramebuffer in webgl2 context + // 2. Can't blit normal FBO to MSAA FBO + // 3. Can't blit screen FBO to normal FBO in some platform when antialias enabled + this._canUseBlitFrameBuffer = + rhi.isWebGL2 && msaaSamples === 1 && (!!camera.renderTarget || !rhi.context.antialias); if (scene.castShadows && sunlight && sunlight.shadowType !== ShadowType.None) { this._cascadedShadowCasterPass.onRender(context); @@ -106,9 +120,20 @@ export class BasicRenderPipeline { camera.shaderData.setTexture(Camera._cameraDepthTextureProperty, engine._basicResources.whiteTexture2D); } - // Check if need to create internal color texture - const independentCanvasEnabled = camera.independentCanvasEnabled; + // Check if need to create internal color texture or grab texture if (independentCanvasEnabled) { + let depthFormat: TextureFormat; + if (camera.renderTarget) { + depthFormat = camera.renderTarget._depthFormat; + } else if (rhi.context.depth && rhi.context.stencil) { + depthFormat = TextureFormat.Depth24Stencil8; + } else if (rhi.context.depth) { + depthFormat = TextureFormat.Depth24; + } else if (rhi.context.stencil) { + depthFormat = TextureFormat.Stencil; + } else { + depthFormat = null; + } const viewport = camera.pixelViewport; const internalColorTarget = PipelineUtils.recreateRenderTargetIfNeeded( engine, @@ -116,32 +141,52 @@ export class BasicRenderPipeline { viewport.width, viewport.height, camera._getInternalColorTextureFormat(), - TextureFormat.Depth24Stencil8, + depthFormat, false, false, - camera.msaaSamples, + msaaSamples, TextureWrapMode.Clamp, TextureFilterMode.Bilinear ); + + if (!this._canUseBlitFrameBuffer && this._shouldGrabColor) { + const grabTexture = PipelineUtils.recreateTextureIfNeeded( + engine, + this._grabTexture, + viewport.width, + viewport.height, + camera.renderTarget?.getColorTexture(0).format ?? TextureFormat.R8G8B8A8, + false, + TextureWrapMode.Clamp, + TextureFilterMode.Bilinear + ); + this._grabTexture = grabTexture; + } + this._internalColorTarget = internalColorTarget; } else { const internalColorTarget = this._internalColorTarget; + const grabTexture = this._grabTexture; if (internalColorTarget) { internalColorTarget.getColorTexture(0)?.destroy(true); internalColorTarget.destroy(true); this._internalColorTarget = null; } + if (grabTexture) { + grabTexture.destroy(true); + this._grabTexture = null; + } } - this._drawRenderPass(context, camera, cubeFace, mipLevel, ignoreClear); + this._drawRenderPass(context, camera, finalClearFlags, cubeFace, mipLevel); } private _drawRenderPass( context: RenderContext, camera: Camera, + finalClearFlags: CameraClearFlags, cubeFace?: TextureCubeFace, - mipLevel?: number, - ignoreClear?: CameraClearFlags + mipLevel?: number ) { const cullingResults = this._cullingResults; const { opaqueQueue, alphaTestQueue, transparentQueue } = cullingResults; @@ -162,15 +207,49 @@ export class BasicRenderPipeline { } rhi.activeRenderTarget(colorTarget, colorViewport, context.flipProjection, mipLevel, cubeFace); - const clearFlags = camera.clearFlags & ~(ignoreClear ?? CameraClearFlags.None); const color = background.solidColor; - if (clearFlags !== CameraClearFlags.None) { - rhi.clearRenderTarget(camera.engine, clearFlags, color); + + if (internalColorTarget && finalClearFlags !== CameraClearFlags.All) { + // Can use `blitFramebuffer` API to copy color/depth/stencil buffer from back buffer to internal RT + if (this._canUseBlitFrameBuffer) { + finalClearFlags !== CameraClearFlags.None && rhi.clearRenderTarget(engine, finalClearFlags, color); + rhi.blitInternalRTByBlitFrameBuffer(camera.renderTarget, internalColorTarget, finalClearFlags, camera.viewport); + } else { + if (!(finalClearFlags & CameraClearFlags.Depth) || !(finalClearFlags & CameraClearFlags.Stencil)) { + Logger.warn( + "We clear all depth/stencil state cause of the internalRT can't copy depth/stencil buffer from back buffer when use copy plan" + ); + } + + if (this._shouldGrabColor) { + rhi.clearRenderTarget(engine, CameraClearFlags.DepthStencil); + // Copy RT's color buffer to grab texture + rhi.copyRenderTargetToSubTexture(camera.renderTarget, this._grabTexture, camera.viewport); + // Then blit grab texture to internal RT's color buffer + PipelineUtils.blitTexture( + engine, + this._grabTexture, + internalColorTarget, + 0, + undefined, + undefined, + undefined, + // Only flip Y axis in webgl context + !camera.renderTarget + ); + } else { + rhi.clearRenderTarget(engine, CameraClearFlags.All, color); + } + } + + rhi.activeRenderTarget(colorTarget, colorViewport, context.flipProjection, mipLevel, cubeFace); + } else if (finalClearFlags !== CameraClearFlags.None) { + rhi.clearRenderTarget(engine, finalClearFlags, color); } opaqueQueue.render(context, PipelineStage.Forward); alphaTestQueue.render(context, PipelineStage.Forward); - if (clearFlags & CameraClearFlags.Color) { + if (finalClearFlags & CameraClearFlags.Color) { if (background.mode === BackgroundMode.Sky) { background.sky._render(context); } else if (background.mode === BackgroundMode.Texture && background.texture) { @@ -201,6 +280,7 @@ export class BasicRenderPipeline { postProcessManager._render(context, internalColorTarget, cameraRenderTarget); } else if (internalColorTarget) { internalColorTarget._blitRenderTarget(); + PipelineUtils.blitTexture( engine, internalColorTarget.getColorTexture(0), diff --git a/packages/core/src/RenderPipeline/PipelineUtils.ts b/packages/core/src/RenderPipeline/PipelineUtils.ts index b8dafd18d8..edc6d60c3e 100644 --- a/packages/core/src/RenderPipeline/PipelineUtils.ts +++ b/packages/core/src/RenderPipeline/PipelineUtils.ts @@ -1,7 +1,7 @@ import { Vector4 } from "@galacean/engine-math"; import { Engine } from "../Engine"; import { Material } from "../material"; -import { ShaderProperty } from "../shader"; +import { ShaderMacro, ShaderProperty } from "../shader"; import { Shader } from "../shader/Shader"; import { ShaderData } from "../shader/ShaderData"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; @@ -15,6 +15,7 @@ export class PipelineUtils { private static _blitTextureProperty = ShaderProperty.getByName("renderer_BlitTexture"); private static _blitMipLevelProperty = ShaderProperty.getByName("renderer_BlitMipLevel"); private static _blitTexelSizeProperty = ShaderProperty.getByName("renderer_texelSize"); // x: 1/width, y: 1/height, z: width, w: height + private static _flipYTextureMacro = ShaderMacro.getByName("renderer_FlipYBlitTexture"); private static _rendererShaderData = new ShaderData(ShaderDataGroup.Renderer); private static _texelSize = new Vector4(); @@ -150,6 +151,7 @@ export class PipelineUtils { * @param viewport - Viewport * @param material - The material to use when blitting * @param passIndex - Pass index to use of the provided material + * @param flipYOfSource - Whether flip Y axis of source texture */ static blitTexture( engine: Engine, @@ -158,7 +160,8 @@ export class PipelineUtils { mipLevel: number = 0, viewport: Vector4 = PipelineUtils.defaultViewport, material: Material = null, - passIndex = 0 + passIndex = 0, + flipYOfSource = false ): void { const basicResources = engine._basicResources; const blitMesh = destination ? basicResources.flipYBlitMesh : basicResources.blitMesh; @@ -177,15 +180,21 @@ export class PipelineUtils { rendererShaderData.setFloat(PipelineUtils._blitMipLevelProperty, mipLevel); PipelineUtils._texelSize.set(1 / source.width, 1 / source.height, source.width, source.height); rendererShaderData.setVector4(PipelineUtils._blitTexelSizeProperty, PipelineUtils._texelSize); + if (flipYOfSource) { + rendererShaderData.enableMacro(PipelineUtils._flipYTextureMacro); + } else { + rendererShaderData.disableMacro(PipelineUtils._flipYTextureMacro); + } const pass = blitMaterial.shader.subShaders[0].passes[passIndex]; const compileMacros = Shader._compileMacros; ShaderMacroCollection.unionCollection( context.camera._globalShaderMacro, - blitMaterial.shaderData._macroCollection, + rendererShaderData._macroCollection, compileMacros ); + ShaderMacroCollection.unionCollection(compileMacros, blitMaterial.shaderData._macroCollection, compileMacros); const program = pass._getShaderProgram(engine, compileMacros); program.bind(); diff --git a/packages/core/src/shaderlib/extra/Blit.fs.glsl b/packages/core/src/shaderlib/extra/Blit.fs.glsl index c5699211ef..78f439796e 100644 --- a/packages/core/src/shaderlib/extra/Blit.fs.glsl +++ b/packages/core/src/shaderlib/extra/Blit.fs.glsl @@ -3,13 +3,19 @@ uniform mediump sampler2D renderer_BlitTexture; uniform float renderer_BlitMipLevel; #endif + varying vec2 v_uv; void main() { + vec2 uv = v_uv; + #ifdef renderer_FlipYBlitTexture + uv.y = 1.0 - uv.y; + #endif + #ifdef HAS_TEX_LOD - gl_FragColor = texture2DLodEXT( renderer_BlitTexture, v_uv, renderer_BlitMipLevel ); + gl_FragColor = texture2DLodEXT( renderer_BlitTexture, uv, renderer_BlitMipLevel ); #else - gl_FragColor = texture2D( renderer_BlitTexture, v_uv ); + gl_FragColor = texture2D( renderer_BlitTexture, uv ); #endif } diff --git a/packages/rhi-webgl/src/WebGLGraphicDevice.ts b/packages/rhi-webgl/src/WebGLGraphicDevice.ts index 2d65c202ca..45e76048d0 100644 --- a/packages/rhi-webgl/src/WebGLGraphicDevice.ts +++ b/packages/rhi-webgl/src/WebGLGraphicDevice.ts @@ -19,7 +19,8 @@ import { Texture2D, Texture2DArray, TextureCube, - TextureCubeFace + TextureCubeFace, + TextureFormat } from "@galacean/engine-core"; import { IHardwareRenderer, IPlatformPrimitive } from "@galacean/engine-design"; import { Color, Vector4 } from "@galacean/engine-math"; @@ -103,6 +104,7 @@ export class WebGLGraphicDevice implements IHardwareRenderer { private _lastScissor: Vector4 = new Vector4(null, null, null, null); private _lastClearColor: Color = new Color(null, null, null, null); private _scissorEnable: boolean = false; + private _contextAttributes: WebGLContextAttributes; private _onDeviceLost: () => void; private _onDeviceRestored: () => void; @@ -135,6 +137,10 @@ export class WebGLGraphicDevice implements IHardwareRenderer { return this.capability.canIUseMoreJoints; } + get context(): WebGLGraphicDeviceOptions { + return this._contextAttributes; + } + constructor(initializeOptions: WebGLGraphicDeviceOptions = {}) { const options = { webGLMode: WebGLMode.Auto, @@ -277,7 +283,7 @@ export class WebGLGraphicDevice implements IHardwareRenderer { this._gl.colorMask(r, g, b, a); } - clearRenderTarget(engine: Engine, clearFlags: CameraClearFlags, clearColor: Color) { + clearRenderTarget(engine: Engine, clearFlags: CameraClearFlags, clearColor?: Color) { const gl = this._gl; const { @@ -287,7 +293,7 @@ export class WebGLGraphicDevice implements IHardwareRenderer { // @ts-ignore } = engine._lastRenderState; let clearFlag = 0; - if (clearFlags & CameraClearFlags.Color) { + if (clearFlags & CameraClearFlags.Color && clearColor) { clearFlag |= gl.COLOR_BUFFER_BIT; const lc = this._lastClearColor; @@ -340,7 +346,7 @@ export class WebGLGraphicDevice implements IHardwareRenderer { renderTarget: RenderTarget, viewport: Vector4, isFlipProjection: boolean, - mipLevel: number, + mipLevel?: number, faceIndex?: TextureCubeFace ) { let bufferWidth: number, bufferHeight: number; @@ -369,6 +375,103 @@ export class WebGLGraphicDevice implements IHardwareRenderer { this.scissor(x, y, width, height); } + blitInternalRTByBlitFrameBuffer( + srcRT: RenderTarget, + destRT: RenderTarget, + clearFlags: CameraClearFlags, + viewport: Vector4 + ) { + if (!this._isWebGL2) { + Logger.warn("WebGL1.0 not support blit frame buffer."); + return; + } + const gl = this._gl; + // @ts-ignore + const srcFrameBuffer = srcRT ? srcRT._platformRenderTarget._frameBuffer : null; + // @ts-ignore + const destFrameBuffer = destRT ? destRT._platformRenderTarget._frameBuffer : null; + const bufferWidth = this.getMainFrameBufferWidth(); + const bufferHeight = this.getMainFrameBufferHeight(); + const srcWidth = srcRT ? srcRT.width : bufferWidth; + const srcHeight = srcRT ? srcRT.height : bufferHeight; + const blitWidth = destRT.width; + const blitHeight = destRT.height; + const needFlipY = !srcRT; + const needBlitColor = (clearFlags & CameraClearFlags.Color) === 0; + const needBlitDepth = (clearFlags & CameraClearFlags.Depth) === 0; + const needBlitStencil = (clearFlags & CameraClearFlags.Stencil) === 0; + + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, srcFrameBuffer); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, destFrameBuffer); + + let blitMask = needBlitColor ? gl.COLOR_BUFFER_BIT : 0; + + if (needBlitDepth || needBlitStencil) { + // @ts-ignore + const depthFormat = destRT._depthFormat; + + if (needBlitDepth) { + if ( + depthFormat === TextureFormat.Depth || + (depthFormat >= TextureFormat.DepthStencil && depthFormat <= TextureFormat.Depth32Stencil8) + ) { + blitMask |= gl.DEPTH_BUFFER_BIT; + } else { + Logger.warn(`Do not clear depth, or set depth format of target which is ${TextureFormat[depthFormat]} now.`); + } + } + + if (needBlitStencil) { + if ( + depthFormat === TextureFormat.Stencil || + depthFormat === TextureFormat.DepthStencil || + depthFormat >= TextureFormat.Depth24Stencil8 || + depthFormat >= TextureFormat.Depth32Stencil8 + ) { + blitMask |= gl.STENCIL_BUFFER_BIT; + } else { + Logger.warn( + `Do not clear stencil, or set stencil format of target which is ${TextureFormat[depthFormat]} now.` + ); + } + } + } + + const xStart = viewport.x * srcWidth; + const xEnd = xStart + blitWidth; + const yStart = needFlipY ? srcHeight - viewport.y * srcHeight : srcHeight - viewport.y * srcHeight - blitHeight; + const yEnd = needFlipY ? yStart - blitHeight : yStart + blitHeight; + + gl.blitFramebuffer(xStart, yStart, xEnd, yEnd, 0, 0, blitWidth, blitHeight, blitMask, gl.NEAREST); + } + + copyRenderTargetToSubTexture(srcRT: RenderTarget, grabTexture: Texture2D, viewport: Vector4) { + const gl = this._gl; + const bufferWidth = this.getMainFrameBufferWidth(); + const bufferHeight = this.getMainFrameBufferHeight(); + const srcWidth = srcRT ? srcRT.width : bufferWidth; + const srcHeight = srcRT ? srcRT.height : bufferHeight; + const copyWidth = grabTexture.width; + const copyHeight = grabTexture.height; + const flipY = !srcRT; + + const xStart = viewport.x * srcWidth; + const yStart = flipY ? srcHeight - viewport.y * srcHeight - copyHeight : viewport.y * srcHeight; + + // @ts-ignore + const frameBuffer = srcRT?._platformRenderTarget._frameBuffer ?? null; + + // @ts-ignore + gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer); + + // @ts-ignore + const glTexture = grabTexture._platformTexture; + + glTexture._bind(); + + gl.copyTexSubImage2D(glTexture._target, 0, 0, 0, xStart, yStart, copyWidth, copyHeight); + } + activeTexture(textureID: number): void { if (this._activeTextureID !== textureID) { this._gl.activeTexture(textureID); @@ -440,6 +543,8 @@ export class WebGLGraphicDevice implements IHardwareRenderer { if (debugRenderInfo != null) { this._renderer = gl.getParameter(debugRenderInfo.UNMASKED_RENDERER_WEBGL); } + + this._contextAttributes = gl.getContextAttributes(); } destroy(): void {