diff --git a/cocos/core/pipeline/custom/compiler.ts b/cocos/core/pipeline/custom/compiler.ts index 57c2a8b161f..a0a2e164478 100644 --- a/cocos/core/pipeline/custom/compiler.ts +++ b/cocos/core/pipeline/custom/compiler.ts @@ -26,7 +26,7 @@ import { Buffer, Framebuffer, Texture } from '../../gfx'; import { assert } from '../../platform/debug'; import { LayoutGraphData } from './layout-graph'; import { Pipeline } from './pipeline'; -import { AccessType, Blit, ComputePass, CopyPass, Dispatch, ManagedResource, MovePass, +import { AccessType, Blit, ComputePass, ComputeView, CopyPass, Dispatch, ManagedResource, MovePass, PresentPass, RasterPass, RasterView, RaytracePass, RenderGraph, RenderGraphValue, RenderGraphVisitor, RenderQueue, RenderSwapchain, ResourceGraph, ResourceGraphVisitor, SceneData } from './render-graph'; import { ResourceResidency } from './types'; @@ -82,7 +82,7 @@ class PassVisitor implements RenderGraphVisitor { rg.visitVertex(this, sceneID); } } - scene (value: SceneData) { + private _fetchValidPass () { if (this._currPass!.isValid) { return; } @@ -90,23 +90,36 @@ class PassVisitor implements RenderGraphVisitor { const outputName = this._context.resourceGraph.vertexName(outputId); const readViews: Map = new Map(); const pass = this._currPass!; - for (const [name, raster] of pass.rasterViews) { + + for (const [readName, raster] of pass.rasterViews) { // find the pass - if (name === outputName + if (readName === outputName && raster.accessType !== AccessType.READ) { assert(!pass.isValid, 'The same pass cannot output multiple resources with the same name at the same time'); pass.isValid = true; continue; } if (raster.accessType !== AccessType.WRITE) { - readViews.set(name, raster); + readViews.set(readName, raster); } } if (pass.isValid) { - for (const [name, raster] of readViews) { - const resVisitor = new ResourceVisitor(this._context); - const resourceGraph = this._context.resourceGraph; - const vertID = resourceGraph.vertex(name); + let resVisitor; + let resourceGraph; + let vertID; + for (const [rasterName, raster] of readViews) { + resVisitor = new ResourceVisitor(this._context); + resourceGraph = this._context.resourceGraph; + vertID = resourceGraph.vertex(rasterName); + if (vertID) { + resVisitor.resID = vertID; + resourceGraph.visitVertex(resVisitor, vertID); + } + } + for (const [computeName, cViews] of pass.computeViews) { + resVisitor = new ResourceVisitor(this._context); + resourceGraph = this._context.resourceGraph; + vertID = resourceGraph.vertex(computeName); if (vertID) { resVisitor.resID = vertID; resourceGraph.visitVertex(resVisitor, vertID); @@ -114,7 +127,11 @@ class PassVisitor implements RenderGraphVisitor { } } } + scene (value: SceneData) { + this._fetchValidPass(); + } blit (value: Blit) { + this._fetchValidPass(); } dispatch (value: Dispatch) { } diff --git a/cocos/core/pipeline/custom/effect.ts b/cocos/core/pipeline/custom/effect.ts index 411f2611bf6..56d70f6c81d 100644 --- a/cocos/core/pipeline/custom/effect.ts +++ b/cocos/core/pipeline/custom/effect.ts @@ -215,6 +215,7 @@ export function buildForwardLayout (ppl: Pipeline) { enum DeferredStage { GEOMETRY, LIGHTING, + POST } export class VectorGraphColorMap implements MutableVertexPropertyMap { @@ -234,9 +235,11 @@ export function buildDeferredLayout (ppl: Pipeline) { const lg = new WebDescriptorHierarchy(); const geometryPassID = lg.addRenderStage('Geometry', DeferredStage.GEOMETRY); const lightingPassID = lg.addRenderStage('Lighting', DeferredStage.LIGHTING); + const postPassID = lg.addRenderStage('Postprocess', DeferredStage.POST); const geometryQueueID = lg.addRenderPhase('Queue', geometryPassID); const lightingQueueID = lg.addRenderPhase('Queue', lightingPassID); + const postQueueID = lg.addRenderPhase('Queue', postPassID); const lightingDescriptors = lg.layoutGraph.getDescriptors(lightingQueueID); @@ -255,6 +258,20 @@ export function buildDeferredLayout (ppl: Pipeline) { const colorMap = new VectorGraphColorMap(lg.layoutGraph.numVertices()); depthFirstSearch(lg.layoutGraph, visitor, colorMap); + lg.mergeDescriptors(lightingPassID); + // Postprocess + const postDescriptors = lg.layoutGraph.getDescriptors(postPassID); + + const postPassBlock = lg.getLayoutBlock(UpdateFrequency.PER_PASS, + ParameterType.TABLE, + DescriptorTypeOrder.SAMPLER_TEXTURE, + ShaderStageFlagBit.FRAGMENT, + postDescriptors); + + lg.setDescriptor(postPassBlock, 'outputResultMap', Type.FLOAT4); + lg.merge(postDescriptors); + + lg.mergeDescriptors(postPassID); if (visitor.error) { console.log(visitor.error); } diff --git a/cocos/core/pipeline/custom/executor.ts b/cocos/core/pipeline/custom/executor.ts index 65843e1d4d0..d0d9c063bf6 100644 --- a/cocos/core/pipeline/custom/executor.ts +++ b/cocos/core/pipeline/custom/executor.ts @@ -30,13 +30,17 @@ */ /* eslint-disable max-len */ import { getPhaseID, InstancedBuffer, PipelineStateManager } from '..'; +import { assert } from '../..'; import { AABB } from '../../geometry/aabb'; import intersect from '../../geometry/intersect'; -import { AccessFlagBit, Buffer, ClearFlagBit, Color, ColorAttachment, CommandBuffer, DepthStencilAttachment, DescriptorSet, Device, deviceManager, Format, Framebuffer, - FramebufferInfo, GeneralBarrierInfo, LoadOp, PipelineState, Rect, RenderPass, RenderPassInfo, Shader, StoreOp, Swapchain, Texture, TextureInfo, +import { Sphere } from '../../geometry/sphere'; +import { AccessFlagBit, Attribute, Buffer, BufferInfo, BufferUsageBit, BufferViewInfo, ClearFlagBit, Color, ColorAttachment, CommandBuffer, DepthStencilAttachment, DescriptorSet, DescriptorSetInfo, Device, deviceManager, Format, Framebuffer, + FramebufferInfo, GeneralBarrierInfo, InputAssemblerInfo, LoadOp, MemoryUsageBit, PipelineState, Rect, RenderPass, RenderPassInfo, SamplerInfo, Shader, StoreOp, SurfaceTransform, Swapchain, Texture, TextureInfo, TextureType, TextureUsageBit, Viewport } from '../../gfx'; import { legacyCC } from '../../global-exports'; import { Mat4 } from '../../math/mat4'; +import { Vec3 } from '../../math/vec3'; +import { Vec4 } from '../../math/vec4'; import { BatchingSchemes, Pass } from '../../renderer'; import { DirectionalLight, SpotLight } from '../../renderer/scene'; import { Camera, SKYBOX_FLAG } from '../../renderer/scene/camera'; @@ -46,8 +50,9 @@ import { CSMLevel, CSMOptimizationMode, ShadowType } from '../../renderer/scene/ import { SubModel } from '../../renderer/scene/submodel'; import { Root } from '../../root'; import { BatchedBuffer } from '../batched-buffer'; -import { RenderPassStage, SetIndex } from '../define'; +import { RenderPassStage, SetIndex, UBODeferredLight, UBOForwardLight, UBOLocal } from '../define'; import { PipelineSceneData } from '../pipeline-scene-data'; +import { PipelineInputAssemblerData } from '../render-pipeline'; import { ShadowLayerVolume } from '../shadow/csm-layers'; import { LayoutGraph, LayoutGraphData, LayoutGraphDataVisitor, LayoutGraphVisitor, PipelineLayoutData, RenderPhase, RenderPhaseData, RenderStageData } from './layout-graph'; import { Pipeline, SceneVisitor } from './pipeline'; @@ -58,6 +63,7 @@ import { QueueHint, ResourceDimension, ResourceFlags, SceneFlags, UpdateFrequenc import { PipelineUBO } from './ubos'; import { RenderInfo, RenderObject, WebSceneTask, WebSceneTransversal } from './web-scene'; import { WebSceneVisitor } from './web-scene-visitor'; +import { stringify, parse } from './utils'; class DeviceResource { protected _context: ExecutorContext; @@ -129,6 +135,17 @@ class DeviceTexture extends DeviceResource { info.height, )); } + + release () { + if (this.framebuffer) { + this.framebuffer.destroy(); + this._framebuffer = null; + } + if (this.texture) { + this.texture.destroy(); + this._texture = null; + } + } } class DeviceBuffer extends DeviceResource { @@ -136,6 +153,230 @@ class DeviceBuffer extends DeviceResource { super(name, context); } } + +class BlitDesc { + private _blit: Blit; + private _screenQuad: PipelineInputAssemblerData | null = null; + private _queue: DeviceRenderQueue | null = null; + private _stageDesc: DescriptorSet | null = null; + // If VOLUMETRIC_LIGHTING is turned on, it needs to be assigned + private _lightVolumeBuffer: Buffer | null = null; + private _lightMeterScale = 10000.0; + private _lightBufferData!: Float32Array; + get screenQuad () { return this._screenQuad; } + get blit () { return this._blit; } + set blit (blit: Blit) { this._blit = blit; } + get stageDesc () { return this._stageDesc; } + constructor (blit: Blit, queue: DeviceRenderQueue) { + this._blit = blit; + this._queue = queue; + } + /** + * @zh + * 创建四边形输入汇集器。 + */ + protected _createQuadInputAssembler (): PipelineInputAssemblerData { + // create vertex buffer + const inputAssemblerData = new PipelineInputAssemblerData(); + + const vbStride = Float32Array.BYTES_PER_ELEMENT * 4; + const vbSize = vbStride * 4; + const device = this._queue!.devicePass.context.device; + const quadVB = device.createBuffer(new BufferInfo( + BufferUsageBit.VERTEX | BufferUsageBit.TRANSFER_DST, + MemoryUsageBit.DEVICE | MemoryUsageBit.HOST, + vbSize, + vbStride, + )); + + if (!quadVB) { + return inputAssemblerData; + } + + // create index buffer + const ibStride = Uint8Array.BYTES_PER_ELEMENT; + const ibSize = ibStride * 6; + + const quadIB = device.createBuffer(new BufferInfo( + BufferUsageBit.INDEX | BufferUsageBit.TRANSFER_DST, + MemoryUsageBit.DEVICE, + ibSize, + ibStride, + )); + + if (!quadIB) { + return inputAssemblerData; + } + + const indices = new Uint8Array(6); + indices[0] = 0; indices[1] = 1; indices[2] = 2; + indices[3] = 1; indices[4] = 3; indices[5] = 2; + + quadIB.update(indices); + + // create input assembler + + const attributes = new Array(2); + attributes[0] = new Attribute('a_position', Format.RG32F); + attributes[1] = new Attribute('a_texCoord', Format.RG32F); + + const quadIA = device.createInputAssembler(new InputAssemblerInfo( + attributes, + [quadVB], + quadIB, + )); + + inputAssemblerData.quadIB = quadIB; + inputAssemblerData.quadVB = quadVB; + inputAssemblerData.quadIA = quadIA; + return inputAssemblerData; + } + createSreenQuad () { + if (!this._screenQuad) { + this._screenQuad = this._createQuadInputAssembler(); + } + } + private _updateScreenVB () { + const devicePass = this._queue!.devicePass; + const width = devicePass.context.width; + const height = devicePass.context.height; + const vb = devicePass.genQuadVertexData(SurfaceTransform.IDENTITY, new Rect(0, 0, width, height)); + this._screenQuad!.quadVB!.update(vb); + } + private _gatherVolumeLights (camera: Camera) { + if (!camera.scene) { return; } + const context = this._queue!.devicePass.context; + const pipeline = context.pipeline; + const cmdBuff = context.commandBuffer; + + const sphereLights = camera.scene.sphereLights; + const spotLights = camera.scene.spotLights; + const _sphere = Sphere.create(0, 0, 0, 1); + const _vec4Array = new Float32Array(4); + const exposure = camera.exposure; + + let idx = 0; + const maxLights = UBODeferredLight.LIGHTS_PER_PASS; + const elementLen = Vec4.length; // sizeof(vec4) / sizeof(float32) + const fieldLen = elementLen * maxLights; + + for (let i = 0; i < sphereLights.length && idx < maxLights; i++, ++idx) { + const light = sphereLights[i]; + Sphere.set(_sphere, light.position.x, light.position.y, light.position.z, light.range); + if (intersect.sphereFrustum(_sphere, camera.frustum)) { + // cc_lightPos + Vec3.toArray(_vec4Array, light.position); + _vec4Array[3] = 0; + this._lightBufferData.set(_vec4Array, idx * elementLen); + + // cc_lightColor + Vec3.toArray(_vec4Array, light.color); + if (light.useColorTemperature) { + const tempRGB = light.colorTemperatureRGB; + _vec4Array[0] *= tempRGB.x; + _vec4Array[1] *= tempRGB.y; + _vec4Array[2] *= tempRGB.z; + } + + if (pipeline.pipelineSceneData.isHDR) { + _vec4Array[3] = light.luminance * exposure * this._lightMeterScale; + } else { + _vec4Array[3] = light.luminance; + } + + this._lightBufferData.set(_vec4Array, idx * elementLen + fieldLen * 1); + + // cc_lightSizeRangeAngle + _vec4Array[0] = light.size; + _vec4Array[1] = light.range; + _vec4Array[2] = 0.0; + this._lightBufferData.set(_vec4Array, idx * elementLen + fieldLen * 2); + } + } + + for (let i = 0; i < spotLights.length && idx < maxLights; i++, ++idx) { + const light = spotLights[i]; + Sphere.set(_sphere, light.position.x, light.position.y, light.position.z, light.range); + if (intersect.sphereFrustum(_sphere, camera.frustum)) { + // cc_lightPos + Vec3.toArray(_vec4Array, light.position); + _vec4Array[3] = 1; + this._lightBufferData.set(_vec4Array, idx * elementLen + fieldLen * 0); + + // cc_lightColor + Vec3.toArray(_vec4Array, light.color); + if (light.useColorTemperature) { + const tempRGB = light.colorTemperatureRGB; + _vec4Array[0] *= tempRGB.x; + _vec4Array[1] *= tempRGB.y; + _vec4Array[2] *= tempRGB.z; + } + if (pipeline.pipelineSceneData.isHDR) { + _vec4Array[3] = light.luminance * exposure * this._lightMeterScale; + } else { + _vec4Array[3] = light.luminance; + } + this._lightBufferData.set(_vec4Array, idx * elementLen + fieldLen * 1); + + // cc_lightSizeRangeAngle + _vec4Array[0] = light.size; + _vec4Array[1] = light.range; + _vec4Array[2] = light.spotAngle; + this._lightBufferData.set(_vec4Array, idx * elementLen + fieldLen * 2); + + // cc_lightDir + Vec3.toArray(_vec4Array, light.direction); + this._lightBufferData.set(_vec4Array, idx * elementLen + fieldLen * 3); + } + } + + // the count of lights is set to cc_lightDir[0].w + const offset = fieldLen * 3 + 3; + this._lightBufferData.set([idx], offset); + + cmdBuff.updateBuffer(this._lightVolumeBuffer!, this._lightBufferData); + } + update () { + this._updateScreenVB(); + if (this.blit.sceneFlags & SceneFlags.VOLUMETRIC_LIGHTING + && this.blit.camera) { + this._gatherVolumeLights(this.blit.camera); + } + this._stageDesc!.update(); + } + + createStageDescriptor () { + if (this._stageDesc) { + return; + } + const pass = this.blit.material!.passes[0]; + const device = this._queue!.devicePass.context.device; + this._stageDesc = device.createDescriptorSet(new DescriptorSetInfo(pass.localSetLayout)); + if (this.blit.sceneFlags & SceneFlags.VOLUMETRIC_LIGHTING) { + let totalSize = Float32Array.BYTES_PER_ELEMENT * 4 * 4 * UBODeferredLight.LIGHTS_PER_PASS; + totalSize = Math.ceil(totalSize / device.capabilities.uboOffsetAlignment) * device.capabilities.uboOffsetAlignment; + + this._lightVolumeBuffer = device.createBuffer(new BufferInfo( + BufferUsageBit.UNIFORM | BufferUsageBit.TRANSFER_DST, + MemoryUsageBit.HOST | MemoryUsageBit.DEVICE, + totalSize, + device.capabilities.uboOffsetAlignment, + )); + + const deferredLitsBufView = device.createBuffer(new BufferViewInfo(this._lightVolumeBuffer, 0, totalSize)); + this._lightBufferData = new Float32Array(totalSize / Float32Array.BYTES_PER_ELEMENT); + this._stageDesc.bindBuffer(UBOForwardLight.BINDING, deferredLitsBufView); + } + const _localUBO = device.createBuffer(new BufferInfo( + BufferUsageBit.UNIFORM | BufferUsageBit.TRANSFER_DST, + MemoryUsageBit.DEVICE, + UBOLocal.SIZE, + UBOLocal.SIZE, + )); + this._stageDesc.bindBuffer(UBOLocal.BINDING, _localUBO); + } +} + class DeviceRenderQueue { private _preSceneTasks: DevicePreSceneTask[] = []; private _sceneTasks: DeviceSceneTask[] = []; @@ -149,12 +390,21 @@ class DeviceRenderQueue { get renderPhase (): RenderPhaseData | null { return this._renderPhase; } set renderPhase (val) { this._renderPhase = val; } private _sceneVisitor: WebSceneVisitor; + private _blitDesc: BlitDesc | null = null; constructor (devicePass: DeviceRenderPass) { this._devicePass = devicePass; this._sceneVisitor = new WebSceneVisitor(this._devicePass.context.commandBuffer, this._devicePass.context.pipeline.pipelineSceneData); } - addSceneTask (scene: SceneData) { + createBlitDesc (blit: Blit) { + if (this._blitDesc) { + return; + } + this._blitDesc = new BlitDesc(blit, this); + this._blitDesc.createSreenQuad(); + this._blitDesc.createStageDescriptor(); + } + addSceneTask (scene: GraphScene): void { if (!this._transversal) { this._transversal = new DeviceSceneTransversal(this, this.devicePass.context.pipelineSceneData, scene); } @@ -164,6 +414,7 @@ class DeviceRenderQueue { clearTasks () { this._sceneTasks.length = 0; } + get blitDesc () { return this._blitDesc; } get sceneTasks () { return this._sceneTasks; } set queueHint (value: QueueHint) { this._hint = value; } get queueHint () { return this._hint; } @@ -201,13 +452,13 @@ class SubmitInfo { public shadowMap: ShadowMap | null = null; } -class RenderPassStageInfo { +class RenderPassLayoutInfo { protected _layoutID = 0; protected _stage: RenderStageData | null = null; protected _layout: PipelineLayoutData; protected _context: ExecutorContext; protected _inputName: string; - protected _desc: DescriptorSet | null = null; + protected _descriptorSet: DescriptorSet | null = null; constructor (layoutId, input: [string, ComputeView[]], context: ExecutorContext) { this._inputName = input[0]; this._layoutID = layoutId; @@ -218,8 +469,11 @@ class RenderPassStageInfo { const layoutData = this._layout.descriptorSets.get(UpdateFrequency.PER_PASS); if (layoutData) { // find resource - const resID = context.resourceGraph.find(this._inputName); - const res = context.resourceGraph.getPersistentTexture(resID); // TODO: handle other resource types + const deviceTex = context.deviceTextures.get(this._inputName); + const gfxTex = deviceTex?.texture; + if (!gfxTex) { + throw Error(`Could not find texture with resource name ${this._inputName}`); + } // bind descriptors for (const descriptor of input[1]) { const descriptorName = descriptor.name; @@ -228,14 +482,16 @@ class RenderPassStageInfo { for (const block of layoutData.descriptorSetLayoutData.descriptorBlocks) { for (let i = 0; i !== block.descriptors.length; ++i) { if (descriptorID === block.descriptors[i].descriptorID) { - layoutData.descriptorSet!.bindTexture(block.offset + i, res); + layoutData.descriptorSet!.bindTexture(block.offset + i, gfxTex); + layoutData.descriptorSet!.bindSampler(block.offset + i, context.device.getSampler(new SamplerInfo())); + if (!this._descriptorSet) this._descriptorSet = layoutData.descriptorSet; } } } } } } - get descriptorSet () { return this._desc; } + get descriptorSet () { return this._descriptorSet; } get layoutID () { return this._layoutID; } get stage () { return this._stage; } get layout () { return this._layout; } @@ -250,6 +506,10 @@ class RasterPassInfo { } get id () { return this._id; } get pass () { return this._pass; } + applyInfo (id: number, pass: RasterPass) { + this._id = id; + this._pass = pass; + } } class DeviceRenderPass { @@ -261,7 +521,7 @@ class DeviceRenderPass { protected _clearStencil = 0; protected _context: ExecutorContext; private _rasterInfo: RasterPassInfo; - private _stage: RenderPassStageInfo | null = null; + private _layout: RenderPassLayoutInfo | null = null; public submitMap: Map = new Map(); constructor (context: ExecutorContext, passInfo: RasterPassInfo) { this._context = context; @@ -269,8 +529,10 @@ class DeviceRenderPass { const device = context.device; const depthStencilAttachment = new DepthStencilAttachment(); depthStencilAttachment.format = Format.DEPTH_STENCIL; + depthStencilAttachment.depthLoadOp = LoadOp.DISCARD; + depthStencilAttachment.stencilLoadOp = LoadOp.DISCARD; depthStencilAttachment.stencilStoreOp = StoreOp.DISCARD; - depthStencilAttachment.depthStoreOp = StoreOp.STORE; + depthStencilAttachment.depthStoreOp = StoreOp.DISCARD; depthStencilAttachment.barrier = device.getGeneralBarrier(new GeneralBarrierInfo( AccessFlagBit.DEPTH_STENCIL_ATTACHMENT_WRITE, AccessFlagBit.DEPTH_STENCIL_ATTACHMENT_WRITE, @@ -281,18 +543,21 @@ class DeviceRenderPass { let swapchain: Swapchain | null = null; let framebuffer: Framebuffer | null = null; for (const cv of passInfo.pass.computeViews) { - this._applyRenderStage(cv); + this._applyRenderLayout(cv); + } + // update the layout descriptorSet + if (this.renderLayout && this.renderLayout.descriptorSet) { + this.renderLayout.descriptorSet.update(); } for (const [resName, rasterV] of passInfo.pass.rasterViews) { let resTex = context.deviceTextures.get(resName); - if (resTex) { - continue; + if (!resTex) { + const resourceGraph = context.resourceGraph; + const vertId = resourceGraph.vertex(resName); + const resourceVisitor = new ResourceVisitor(resName, context); + resourceGraph.visitVertex(resourceVisitor, vertId); + resTex = context.deviceTextures.get(resName)!; } - const resourceGraph = context.resourceGraph; - const vertId = resourceGraph.vertex(resName); - const resourceVisitor = new ResourceVisitor(resName, context); - resourceGraph.visitVertex(resourceVisitor, vertId); - resTex = context.deviceTextures.get(resName)!; if (!swapchain) swapchain = resTex.swapchain; if (!framebuffer) framebuffer = resTex.framebuffer; const clearFlag = rasterV.clearFlags & 0xffffffff; @@ -303,31 +568,24 @@ class DeviceRenderPass { const colorAttachment = new ColorAttachment(); colorAttachment.format = resTex.description!.format; colorAttachment.sampleCount = resTex.description!.sampleCount; - if (!(clearFlag & ClearFlagBit.COLOR)) { - rasterV.clearColor.x = 0; - rasterV.clearColor.y = 0; - rasterV.clearColor.z = 0; - rasterV.clearColor.w = 1; - if (clearFlag & SKYBOX_FLAG) { - colorAttachment.loadOp = LoadOp.DISCARD; - } else { - colorAttachment.loadOp = LoadOp.LOAD; - colorAttachment.barrier = device.getGeneralBarrier(new GeneralBarrierInfo( - AccessFlagBit.COLOR_ATTACHMENT_WRITE, - AccessFlagBit.COLOR_ATTACHMENT_WRITE, - )); - } + colorAttachment.loadOp = rasterV.loadOp; + colorAttachment.storeOp = rasterV.storeOp; + if (rasterV.loadOp === LoadOp.LOAD) { + colorAttachment.barrier = device.getGeneralBarrier(new GeneralBarrierInfo( + AccessFlagBit.COLOR_ATTACHMENT_WRITE, + AccessFlagBit.COLOR_ATTACHMENT_WRITE, + )); } this._clearColor.push(rasterV.clearColor); colors.push(colorAttachment); } break; case AttachmentType.DEPTH_STENCIL: + depthStencilAttachment.depthStoreOp = rasterV.storeOp; + depthStencilAttachment.stencilStoreOp = rasterV.storeOp; + depthStencilAttachment.depthLoadOp = rasterV.loadOp; + depthStencilAttachment.stencilLoadOp = rasterV.loadOp; if (!resTex.swapchain && !resTex.framebuffer) depthTex = resTex.texture!; - if ((clearFlag & ClearFlagBit.DEPTH_STENCIL) !== ClearFlagBit.DEPTH_STENCIL) { - if (!(clearFlag & ClearFlagBit.DEPTH)) depthStencilAttachment.depthLoadOp = LoadOp.LOAD; - if (!(clearFlag & ClearFlagBit.STENCIL)) depthStencilAttachment.stencilLoadOp = LoadOp.LOAD; - } this._clearDepth = rasterV.clearColor.x; this._clearStencil = rasterV.clearColor.y; break; @@ -342,21 +600,21 @@ class DeviceRenderPass { const currTex = device.createTexture(new TextureInfo()); colorTexs.push(currTex); } - if (!depthTex && !swapchain && !framebuffer) { - depthTex = device.createTexture(new TextureInfo( - TextureType.TEX2D, - TextureUsageBit.DEPTH_STENCIL_ATTACHMENT | TextureUsageBit.SAMPLED, - Format.DEPTH_STENCIL, - colorTexs[0].width, - colorTexs[0].height, - )); - } + // if (!depthTex && !swapchain && !framebuffer) { + // depthTex = device.createTexture(new TextureInfo( + // TextureType.TEX2D, + // TextureUsageBit.DEPTH_STENCIL_ATTACHMENT | TextureUsageBit.SAMPLED, + // Format.DEPTH_STENCIL, + // colorTexs[0].width, + // colorTexs[0].height, + // )); + // } this._renderPass = device.createRenderPass(new RenderPassInfo(colors, depthStencilAttachment)); this._framebuffer = framebuffer || device.createFramebuffer(new FramebufferInfo(this._renderPass, swapchain ? [swapchain.colorTexture] : colorTexs, swapchain ? swapchain.depthStencilTexture : depthTex)); } - get renderStage () { return this._stage; } + get renderLayout () { return this._layout; } get context () { return this._context; } get renderPass () { return this._renderPass; } get framebuffer () { return this._framebuffer; } @@ -365,22 +623,72 @@ class DeviceRenderPass { get clearStencil () { return this._clearStencil; } get deviceQueues () { return this._deviceQueues; } get rasterPassInfo () { return this._rasterInfo; } + addQueue (queue: DeviceRenderQueue) { this._deviceQueues.push(queue); } prePass () { for (const queue of this._deviceQueues) { queue.preRecord(); } } - protected _applyRenderStage (input: [string, ComputeView[]]) { + protected _applyRenderLayout (input: [string, ComputeView[]]) { const stageName = this._context.renderGraph.getLayout(this.rasterPassInfo.id); if (stageName) { const layoutGraph = this._context.layoutGraph; const stageId = layoutGraph.locateChild(layoutGraph.nullVertex(), stageName); if (stageId !== 0xFFFFFFFF) { - this._stage = new RenderPassStageInfo(stageId, input, this._context); + this._layout = new RenderPassLayoutInfo(stageId, input, this._context); } } } + genQuadVertexData (surfaceTransform: SurfaceTransform, renderArea: Rect) : Float32Array { + const vbData = new Float32Array(4 * 4); + + const minX = renderArea.x / this._context.width; + const maxX = (renderArea.x + renderArea.width) / this._context.width; + let minY = renderArea.y / this._context.height; + let maxY = (renderArea.y + renderArea.height) / this._context.height; + if (this._context.root.device.capabilities.screenSpaceSignY > 0) { + const temp = maxY; + maxY = minY; + minY = temp; + } + let n = 0; + switch (surfaceTransform) { + case (SurfaceTransform.IDENTITY): + n = 0; + vbData[n++] = -1.0; vbData[n++] = -1.0; vbData[n++] = minX; vbData[n++] = maxY; + vbData[n++] = 1.0; vbData[n++] = -1.0; vbData[n++] = maxX; vbData[n++] = maxY; + vbData[n++] = -1.0; vbData[n++] = 1.0; vbData[n++] = minX; vbData[n++] = minY; + vbData[n++] = 1.0; vbData[n++] = 1.0; vbData[n++] = maxX; vbData[n++] = minY; + break; + case (SurfaceTransform.ROTATE_90): + n = 0; + vbData[n++] = -1.0; vbData[n++] = -1.0; vbData[n++] = maxX; vbData[n++] = maxY; + vbData[n++] = 1.0; vbData[n++] = -1.0; vbData[n++] = maxX; vbData[n++] = minY; + vbData[n++] = -1.0; vbData[n++] = 1.0; vbData[n++] = minX; vbData[n++] = maxY; + vbData[n++] = 1.0; vbData[n++] = 1.0; vbData[n++] = minX; vbData[n++] = minY; + break; + case (SurfaceTransform.ROTATE_180): + n = 0; + vbData[n++] = -1.0; vbData[n++] = -1.0; vbData[n++] = minX; vbData[n++] = minY; + vbData[n++] = 1.0; vbData[n++] = -1.0; vbData[n++] = maxX; vbData[n++] = minY; + vbData[n++] = -1.0; vbData[n++] = 1.0; vbData[n++] = minX; vbData[n++] = maxY; + vbData[n++] = 1.0; vbData[n++] = 1.0; vbData[n++] = maxX; vbData[n++] = maxY; + break; + case (SurfaceTransform.ROTATE_270): + n = 0; + vbData[n++] = -1.0; vbData[n++] = -1.0; vbData[n++] = minX; vbData[n++] = minY; + vbData[n++] = 1.0; vbData[n++] = -1.0; vbData[n++] = minX; vbData[n++] = maxY; + vbData[n++] = -1.0; vbData[n++] = 1.0; vbData[n++] = maxX; vbData[n++] = minY; + vbData[n++] = 1.0; vbData[n++] = 1.0; vbData[n++] = maxX; vbData[n++] = maxY; + break; + default: + break; + } + + return vbData; + } + // record common buffer record () { const cmdBuff = this._context.commandBuffer; @@ -400,13 +708,18 @@ class DeviceRenderPass { queue.postRecord(); } } + resetQueues (id: number, pass: RasterPass) { + this._rasterInfo.applyInfo(id, pass); + this._deviceQueues.length = 0; + } } class DeviceSceneTransversal extends WebSceneTransversal { protected _currentQueue: DeviceRenderQueue; - protected _graphScene: SceneData; - constructor (quque: DeviceRenderQueue, sceneData: PipelineSceneData, graphSceneData: SceneData) { - super(graphSceneData.camera!, sceneData, quque.devicePass.context.ubo); + protected _graphScene: GraphScene; + constructor (quque: DeviceRenderQueue, sceneData: PipelineSceneData, graphSceneData: GraphScene) { + const camera = graphSceneData.scene ? graphSceneData.scene.camera : null; + super(camera, sceneData, quque.devicePass.context.ubo); this._currentQueue = quque; this._graphScene = graphSceneData; } @@ -421,22 +734,35 @@ class DeviceSceneTransversal extends WebSceneTransversal { return new DevicePostSceneTask(this._sceneData, this._currentQueue.devicePass.context.ubo, this._camera, visitor); } } - +class GraphScene { + scene: SceneData | null = null; + blit: Blit | null = null; + dispatch: Dispatch | null = null; +} class DevicePreSceneTask extends WebSceneTask { protected _currentQueue: DeviceRenderQueue; protected _renderPass: RenderPass; protected _submitInfo: SubmitInfo | null = null; - protected _graphScene: SceneData; + protected _graphScene: GraphScene; private _cmdBuff: CommandBuffer; - constructor (queue: DeviceRenderQueue, graphScene: SceneData, visitor: SceneVisitor) { - super(queue.devicePass.context.pipelineSceneData, queue.devicePass.context.ubo, graphScene.camera!, visitor); + constructor (queue: DeviceRenderQueue, graphScene: GraphScene, visitor: SceneVisitor) { + super(queue.devicePass.context.pipelineSceneData, queue.devicePass.context.ubo, + graphScene.scene && graphScene.scene.camera ? graphScene.scene.camera : null, visitor); this._currentQueue = queue; this._graphScene = graphScene; this._renderPass = this._currentQueue.devicePass.renderPass; this._cmdBuff = queue.devicePass.context.commandBuffer; } get graphScene () { return this._graphScene; } + public start () { + if (this.graphScene.blit) { + this._currentQueue.createBlitDesc(this.graphScene.blit); + return; + } + if (!this.camera) { + return; + } const submitMap = this._currentQueue.devicePass.submitMap; if (submitMap.has(this.camera)) { this._submitInfo = submitMap.get(this.camera)!; @@ -449,7 +775,7 @@ class DevicePreSceneTask extends WebSceneTask { // shadowmap if (this._isShadowMap() && !this._submitInfo.shadowMap) { this._submitInfo.shadowMap = new ShadowMap(this._currentQueue.devicePass.context); - this._submitInfo.shadowMap.gatherLightPasses(this.camera, this.graphScene.light!, this._cmdBuff, ShadowMap.level); + this._submitInfo.shadowMap.gatherLightPasses(this.camera, this.graphScene.scene!.light!, this._cmdBuff, ShadowMap.level); return; } @@ -457,7 +783,7 @@ class DevicePreSceneTask extends WebSceneTask { const subModels = ro.model.subModels; for (const submodel of subModels) { const passes = submodel.passes; - const sceneFlag = this._graphScene.flags; + const sceneFlag = this._graphScene.scene!.flags; for (const p of passes) { if (p.phase !== this._currentQueue.phaseID) continue; const batchingScheme = p.batchingScheme; @@ -551,18 +877,22 @@ class DevicePreSceneTask extends WebSceneTask { private _isShadowMap () { return this.sceneData.shadows.enabled && this.sceneData.shadows.type === ShadowType.ShadowMap - && this.graphScene.flags & SceneFlags.SHADOW_CASTER; + && this.graphScene.scene!.flags & SceneFlags.SHADOW_CASTER; } public submit () { + if (this.graphScene.blit) { + this._currentQueue.blitDesc!.update(); + return; + } const ubo = this._currentQueue.devicePass.context.ubo; if (this._isShadowMap()) { - ubo.updateShadowUBOLight(this.graphScene.light!, ShadowMap.level); + ubo.updateShadowUBOLight(this.graphScene.scene!.light!, ShadowMap.level); return; } - ubo.updateGlobalUBO(this.camera.window); - ubo.updateCameraUBO(this.camera); - ubo.updateShadowUBO(this.camera); + ubo.updateGlobalUBO(this.camera!.window); + ubo.updateCameraUBO(this.camera!); + ubo.updateShadowUBO(this.camera!); this._uploadInstanceBuffers(); this._uploadBatchedBuffers(); @@ -572,9 +902,10 @@ class DevicePreSceneTask extends WebSceneTask { class DeviceSceneTask extends WebSceneTask { protected _currentQueue: DeviceRenderQueue; protected _renderPass: RenderPass; - protected _graphScene: SceneData; - constructor (queue: DeviceRenderQueue, graphScene: SceneData, visitor: SceneVisitor) { - super(queue.devicePass.context.pipelineSceneData, queue.devicePass.context.ubo, graphScene.camera!, visitor); + protected _graphScene: GraphScene; + constructor (queue: DeviceRenderQueue, graphScene: GraphScene, visitor: SceneVisitor) { + super(queue.devicePass.context.pipelineSceneData, queue.devicePass.context.ubo, + graphScene.scene && graphScene.scene.camera ? graphScene.scene.camera : null, visitor); this._currentQueue = queue; this._renderPass = this._currentQueue.devicePass.renderPass; this._graphScene = graphScene; @@ -583,7 +914,7 @@ class DeviceSceneTask extends WebSceneTask { public start () {} protected _recordRenderList (isTransparent: boolean) { const submitMap = this._currentQueue.devicePass.submitMap; - const renderList = isTransparent ? submitMap.get(this.camera)!.transparentList : submitMap.get(this.camera)!.opaqueList; + const renderList = isTransparent ? submitMap.get(this.camera!)!.transparentList : submitMap.get(this.camera!)!.opaqueList; for (let i = 0; i < renderList.length; ++i) { const { subModel, passIdx } = renderList[i]; const { inputAssembler } = subModel; @@ -603,7 +934,7 @@ class DeviceSceneTask extends WebSceneTask { } protected _recordInstences () { const submitMap = this._currentQueue.devicePass.submitMap; - const it = submitMap.get(this.camera)!.instances.values(); let res = it.next(); + const it = submitMap.get(this.camera!)!.instances.values(); let res = it.next(); while (!res.done) { const { instances, pass, hasPendingModels } = res.value; if (hasPendingModels) { @@ -630,7 +961,7 @@ class DeviceSceneTask extends WebSceneTask { } protected _recordBatches () { const submitMap = this._currentQueue.devicePass.submitMap; - const it = submitMap.get(this.camera)!.batches.values(); let res = it.next(); + const it = submitMap.get(this.camera!)!.batches.values(); let res = it.next(); while (!res.done) { let boundPSO = false; for (let b = 0; b < res.value.batches.length; ++b) { @@ -653,11 +984,11 @@ class DeviceSceneTask extends WebSceneTask { } } protected _recordUI () { - const batches = this.camera.scene!.batches; + const batches = this.camera!.scene!.batches; for (let i = 0; i < batches.length; i++) { const batch = batches[i]; let visible = false; - if (this.camera.visibility & batch.visFlags) { + if (this.camera!.visibility & batch.visFlags) { visible = true; } @@ -685,12 +1016,12 @@ class DeviceSceneTask extends WebSceneTask { protected _recordShadowMap () { const context = this._currentQueue.devicePass.context; const submitMap = this._currentQueue.devicePass.submitMap; - submitMap.get(this.camera)?.shadowMap?.recordCommandBuffer(context.device, + submitMap.get(this.camera!)?.shadowMap?.recordCommandBuffer(context.device, this.visitor, this._renderPass); } protected _generateRenderArea (): Rect { const out = new Rect(); - const vp = this.camera.viewport; + const vp = this.camera ? this.camera.viewport : new Rect(0, 0, 1, 1); const texture = this._currentQueue.devicePass.framebuffer.colorTextures[0]!; const w = texture.width; const h = texture.height; @@ -698,8 +1029,8 @@ class DeviceSceneTask extends WebSceneTask { out.y = vp.y * h; out.width = vp.width * w; out.height = vp.height * h; - if (this._isShadowMap() && this.graphScene.light) { - const light = this.graphScene.light; + if (this._isShadowMap() && this.graphScene.scene!.light) { + const light = this.graphScene.scene!.light; switch (light.type) { case LightType.DIRECTIONAL: { const mainLight = light as DirectionalLight; @@ -731,12 +1062,39 @@ class DeviceSceneTask extends WebSceneTask { private _isShadowMap () { return this.sceneData.shadows.enabled && this.sceneData.shadows.type === ShadowType.ShadowMap - && this.graphScene.flags & SceneFlags.SHADOW_CASTER; + && this.graphScene.scene!.flags & SceneFlags.SHADOW_CASTER; + } + private _recordBlit () { + if (!this.graphScene.blit) { return; } + const currMat = this.graphScene.blit.material; + const pass = currMat!.passes[0]; + const shader = pass.getShaderVariant(); + const devicePass = this._currentQueue.devicePass; + const screenIa: any = this._currentQueue.blitDesc!.screenQuad!.quadIA; + let pso; + if (pass !== null && shader !== null && screenIa !== null) { + pso = PipelineStateManager.getOrCreatePipelineState(devicePass.context.device, pass, shader, + devicePass.renderPass, screenIa); + } + if (pso) { + this.visitor.bindPipelineState(pso); + const layoutStage = devicePass.renderLayout; + // TODO: It will be changed to global later + this.visitor.bindDescriptorSet(SetIndex.MATERIAL, layoutStage!.descriptorSet!); + this.visitor.bindDescriptorSet(SetIndex.LOCAL, this._currentQueue.blitDesc!.stageDesc!); + this.visitor.bindInputAssembler(screenIa); + this.visitor.draw(screenIa); + } } public submit () { const area = this._generateRenderArea(); this.visitor.setViewport(new Viewport(area.x, area.y, area.width, area.height)); this.visitor.setScissor(area); + // Currently processing blit and camera first + if (this.graphScene.blit) { + this._recordBlit(); + return; + } if (this._isShadowMap()) { this._recordShadowMap(); return; @@ -745,7 +1103,9 @@ class DeviceSceneTask extends WebSceneTask { this._recordInstences(); this._recordBatches(); this._recordTransparentList(); - this._recordUI(); + if (this.graphScene.scene!.flags & SceneFlags.UI) { + this._recordUI(); + } } } @@ -757,7 +1117,9 @@ class ExecutorContext { device: Device, resourceGraph: ResourceGraph, renderGraph: RenderGraph, - layoutGraph: LayoutGraphData) { + layoutGraph: LayoutGraphData, + width: number, + height: number) { this.pipeline = pipeline; this.device = device; this.commandBuffer = device.commandBuffer; @@ -767,18 +1129,22 @@ class ExecutorContext { this.root = legacyCC.director.root; this.ubo = ubo; this.layoutGraph = layoutGraph; + this.width = width; + this.height = height; } readonly device: Device; readonly pipeline: Pipeline; readonly commandBuffer: CommandBuffer; readonly pipelineSceneData: PipelineSceneData; readonly resourceGraph: ResourceGraph; - readonly devicePasses: Map = new Map(); + readonly devicePasses: Map = new Map(); readonly deviceTextures: Map = new Map(); - readonly renderGraph: RenderGraph; readonly layoutGraph: LayoutGraphData; readonly root: Root; readonly ubo: PipelineUBO; + readonly width: number; + readonly height: number; + renderGraph: RenderGraph; } class ShadowMap { @@ -1094,15 +1460,18 @@ class PassVisitor implements RenderGraphVisitor { const rg = this._context.renderGraph; const devicePasses = this._context.devicePasses; const layout = this._context.layoutGraph; - this._currPass = devicePasses.get(this.passID); + const passHash = stringify(pass); + this._currPass = devicePasses.get(passHash); if (!this._currPass) { this._currPass = new DeviceRenderPass(this._context, new RasterPassInfo(this.passID, pass)); - devicePasses.set(this.passID, this._currPass); - for (const q of rg.children(this.passID)) { - const queueID = q.target as number; - this._queueID = queueID; - rg.visitVertex(this, queueID); - } + devicePasses.set(passHash, this._currPass); + } else { + this._currPass.resetQueues(this.passID, pass); + } + for (const q of rg.children(this.passID)) { + const queueID = q.target as number; + this._queueID = queueID; + rg.visitVertex(this, queueID); } this._currPass.prePass(); this._currPass.record(); @@ -1132,8 +1501,8 @@ class PassVisitor implements RenderGraphVisitor { const layoutName = this._context.renderGraph.getLayout(this._queueID); if (layoutName) { const layoutGraph = this._context.layoutGraph; - if (this._currPass!.renderStage) { - const layoutId = layoutGraph.locateChild(this._currPass!.renderStage.layoutID, layoutName); + if (this._currPass!.renderLayout) { + const layoutId = layoutGraph.locateChild(this._currPass!.renderLayout.layoutID, layoutName); deviceQueue.renderPhase = layoutGraph.tryGetRenderPhase(layoutId); } } @@ -1144,9 +1513,14 @@ class PassVisitor implements RenderGraphVisitor { } } scene (value: SceneData) { - this._currQueue!.addSceneTask(value); + const graphScene = new GraphScene(); + graphScene.scene = value; + this._currQueue!.addSceneTask(graphScene); } blit (value: Blit) { + const graphScene = new GraphScene(); + graphScene.blit = value; + this._currQueue!.addSceneTask(graphScene); } dispatch (value: Dispatch) { } @@ -1157,27 +1531,25 @@ export class Executor { ubo: PipelineUBO, device: Device, resourceGraph: ResourceGraph, - layoutGraph: LayoutGraphData) { - this._pipeline = pipeline; - this._device = device; - this._ubo = ubo; - this._commandBuffer = device.commandBuffer; - this._resourceGraph = resourceGraph; - this._layoutGraph = layoutGraph; + layoutGraph: LayoutGraphData, + width: number, height: number) { + this._context = new ExecutorContext( + pipeline, + ubo, + device, + resourceGraph, + new RenderGraph(), + layoutGraph, + width, + height, + ); } execute (rg: RenderGraph) { - const context = new ExecutorContext( - this._pipeline, - this._ubo, - this._device, - this._resourceGraph, - rg, - this._layoutGraph, - ); - const cmdBuff = context.commandBuffer; + this._context.renderGraph = rg; + const cmdBuff = this._context.commandBuffer; cmdBuff.begin(); - const passVisitor = new PassVisitor(context); + const passVisitor = new PassVisitor(this._context); for (const vertID of rg.vertices()) { if (rg.numParents(vertID) === 0) { // vertex has no parents, must be pass @@ -1186,12 +1558,15 @@ export class Executor { } } cmdBuff.end(); - context.device.queue.submit([cmdBuff]); - } - private readonly _pipeline: Pipeline; - private readonly _device: Device; - private readonly _commandBuffer: CommandBuffer; - private readonly _resourceGraph: ResourceGraph; - private readonly _layoutGraph: LayoutGraphData; - private readonly _ubo: PipelineUBO; + this._context.device.queue.submit([cmdBuff]); + } + + release () { + this._context.devicePasses.clear(); + for (const [k, v] of this._context.deviceTextures) { + v.release(); + } + this._context.deviceTextures.clear(); + } + private readonly _context: ExecutorContext; } diff --git a/cocos/core/pipeline/custom/utils.ts b/cocos/core/pipeline/custom/utils.ts new file mode 100644 index 00000000000..ee00bad60a6 --- /dev/null +++ b/cocos/core/pipeline/custom/utils.ts @@ -0,0 +1,39 @@ +// https://stackoverflow.com/questions/56714318/how-to-disable-multiple-rules-for-eslint-nextline?msclkid=5d4c2298ba7911eca34d0ab30591752e + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function replacer (key: unknown, value: unknown) { + if (value instanceof Map) { + return { + meta_t: 'Map', + value: Array.from(value.entries()).sort((a, b) => String(a[0]).localeCompare(b[0])), + }; + } else if (value instanceof Set) { + return { + meta_t: 'Set', + value: Array.from(value).sort(), + }; + } + return value; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars +export function reviver (key: unknown, value: any) { + if (typeof value === 'object' && value !== null) { + if (value.meta_t === 'Map') { + return new Map(value.value); + } else if (value.meta_t === 'Set') { + return new Set(value.value); + } + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return value; +} + +export function stringify (data: unknown, space?: string | number | undefined) { + return JSON.stringify(data, replacer, space); +} + +export function parse (text: string) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return JSON.parse(text, reviver); +} diff --git a/cocos/core/pipeline/custom/web-pipeline.ts b/cocos/core/pipeline/custom/web-pipeline.ts index 4b061ecf88c..946254b4e09 100644 --- a/cocos/core/pipeline/custom/web-pipeline.ts +++ b/cocos/core/pipeline/custom/web-pipeline.ts @@ -28,7 +28,7 @@ import { systemInfo } from 'pal/system-info'; import { Color, Buffer, DescriptorSetLayout, Device, Feature, Format, FormatFeatureBit, Sampler, Swapchain, Texture, StoreOp, LoadOp, ClearFlagBit, DescriptorSet, deviceManager } from '../../gfx/index'; import { Mat4, Quat, Vec2, Vec4 } from '../../math'; import { LightingMode, QueueHint, ResourceDimension, ResourceFlags, ResourceResidency, SceneFlags, UpdateFrequency } from './types'; -import { AccessType, AttachmentType, Blit, ComputePass, ComputeView, CopyPair, CopyPass, Dispatch, ManagedResource, MovePair, MovePass, PresentPass, RasterPass, RasterView, RenderData, RenderGraph, RenderGraphValue, RenderQueue, RenderSwapchain, ResourceDesc, ResourceGraph, ResourceGraphValue, ResourceStates, ResourceTraits, SceneData } from './render-graph'; +import { AccessType, AttachmentType, Blit, ComputePass, ComputeView, CopyPair, CopyPass, Dispatch, ManagedResource, MovePair, MovePass, PresentPass, RasterPass, RasterView, RenderData, RenderGraph, RenderGraphComponent, RenderGraphValue, RenderQueue, RenderSwapchain, ResourceDesc, ResourceGraph, ResourceGraphValue, ResourceStates, ResourceTraits, SceneData } from './render-graph'; import { ComputePassBuilder, ComputeQueueBuilder, CopyPassBuilder, LayoutGraphBuilder, MovePassBuilder, Pipeline, RasterPassBuilder, RasterQueueBuilder, SceneTask, SceneTransversal, SceneVisitor, Setter } from './pipeline'; import { PipelineSceneData } from '../pipeline-scene-data'; import { Model, Camera, SKYBOX_FLAG, Light, LightType, ShadowType, DirectionalLight, Shadows } from '../../renderer/scene'; @@ -50,7 +50,13 @@ import { Texture2D } from '../../assets/texture-2d'; import { WebLayoutGraphBuilder } from './web-layout-graph'; import { GeometryRenderer } from '../geometry-renderer'; import { Material } from '../../assets'; +import { SRGBToLinear } from '../pipeline-funcs'; +// Anti-aliasing type, other types will be gradually added in the future +export enum AntiAliasing { + NONE, + FXAA, +} export class WebSetter { constructor (data: RenderData) { this._data = data; @@ -126,7 +132,7 @@ function setCameraValues (setter: Setter, } function getFirstChildLayoutName (lg: LayoutGraphData, parentID: number): string { - if (lg.numVertices() && lg.numChildren(parentID)) { + if (lg.numVertices() && parentID !== 0xFFFFFFFF && lg.numChildren(parentID)) { const childNodes = lg.children(parentID); if (childNodes.next().value && childNodes.next().value.target !== lg.nullVertex()) { const ququeLayoutID = childNodes.next().value.target; @@ -186,6 +192,11 @@ export class WebRasterPassBuilder extends WebSetter implements RasterPassBuilder this._vertID = vertID; this._pass = pass; this._pipeline = pipeline; + + const layoutName = this._renderGraph.component( + RenderGraphComponent.Layout, this._vertID, + ); + this._layoutID = layoutGraph.locateChild(layoutGraph.nullVertex(), layoutName); } addRasterView (name: string, view: RasterView) { this._pass.rasterViews.set(name, view); @@ -200,7 +211,7 @@ export class WebRasterPassBuilder extends WebSetter implements RasterPassBuilder addQueue (hint: QueueHint = QueueHint.RENDER_OPAQUE, layoutName = '', name = 'Queue') { if (!layoutName) { - layoutName = getFirstChildLayoutName(this._layoutGraph, this._vertID); + layoutName = getFirstChildLayoutName(this._layoutGraph, this._layoutID); } const queue = new RenderQueue(hint); const data = new RenderData(); @@ -236,6 +247,7 @@ export class WebRasterPassBuilder extends WebSetter implements RasterPassBuilder } private readonly _renderGraph: RenderGraph; private readonly _vertID: number; + private readonly _layoutID: number; private readonly _pass: RasterPass; private readonly _pipeline: PipelineSceneData; private readonly _layoutGraph: LayoutGraphData; @@ -275,6 +287,11 @@ export class WebComputePassBuilder extends WebSetter implements ComputePassBuild this._vertID = vertID; this._pass = pass; this._pipeline = pipeline; + + const layoutName = this._renderGraph.component( + RenderGraphComponent.Layout, this._vertID, + ); + this._layoutID = layoutGraph.locateChild(layoutGraph.nullVertex(), layoutName); } addComputeView (name: string, view: ComputeView) { if (this._pass.computeViews.has(name)) { @@ -285,7 +302,7 @@ export class WebComputePassBuilder extends WebSetter implements ComputePassBuild } addQueue (layoutName = '', name = 'Queue') { if (!layoutName) { - layoutName = getFirstChildLayoutName(this._layoutGraph, this._vertID); + layoutName = getFirstChildLayoutName(this._layoutGraph, this._layoutID); } const queue = new RenderQueue(); const data = new RenderData(); @@ -301,7 +318,7 @@ export class WebComputePassBuilder extends WebSetter implements ComputePassBuild layoutName = '', name = 'Dispatch') { if (!layoutName) { - layoutName = getFirstChildLayoutName(this._layoutGraph, this._vertID); + layoutName = getFirstChildLayoutName(this._layoutGraph, this._layoutID); } this._renderGraph.addVertex( RenderGraphValue.Dispatch, @@ -312,6 +329,7 @@ export class WebComputePassBuilder extends WebSetter implements ComputePassBuild private readonly _renderGraph: RenderGraph; private readonly _layoutGraph: LayoutGraphData; private readonly _vertID: number; + private readonly _layoutID: number; private readonly _pass: ComputePass; private readonly _pipeline: PipelineSceneData; } @@ -366,6 +384,13 @@ export class WebPipeline extends Pipeline { public presentAll (): void { throw new Error('Method not implemented.'); } + public get lightingMode (): LightingMode { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return this._lightingMode; + } + public set lightingMode (mode: LightingMode) { + this._lightingMode = mode; + } public createSceneTransversal (camera: Camera, scene: RenderScene): SceneTransversal { throw new Error('Method not implemented.'); } @@ -385,6 +410,31 @@ export class WebPipeline extends Pipeline { this._constantMacros = str; } + private _buildMaterial () { + this._deferredLightingMaterial = new Material(); + this._deferredLightingMaterial.name = 'builtin-deferred-material'; + this._deferredLightingMaterial.initialize({ effectName: 'pipeline/deferred-lighting' }); + for (let i = 0; i < this._deferredLightingMaterial.passes.length; ++i) { + this._deferredLightingMaterial.passes[i].tryCompile(); + } + + this._deferredPostMaterial = new Material(); + this._deferredPostMaterial.name = 'builtin-post-process-material'; + if (macro.ENABLE_ANTIALIAS_FXAA) { + this._antiAliasing = AntiAliasing.FXAA; + } + this._deferredPostMaterial.initialize({ + effectName: 'pipeline/post-process', + defines: { + // Anti-aliasing type, currently only fxaa, so 1 means fxaa + ANTIALIAS_TYPE: this._antiAliasing, + }, + }); + for (let i = 0; i < this._deferredPostMaterial.passes.length; ++i) { + this._deferredPostMaterial.passes[i].tryCompile(); + } + } + public activate (swapchain: Swapchain): boolean { this._device = deviceManager.gfxDevice; this._globalDSManager = new GlobalDSManager(this._device); @@ -393,9 +443,10 @@ export class WebPipeline extends Pipeline { this._pipelineSceneData.activate(this._device); this._pipelineUBO.activate(this._device, this); const root = legacyCC.director.root; + // enable the deferred pipeline if (root.useDeferredPipeline) { - // enable the deferred pipeline this.setMacroInt('CC_PIPELINE_TYPE', 1); + this._buildMaterial(); } const shadowMapSampler = this._globalDSManager.pointSampler; this.descriptorSet.bindSampler(UNIFORM_SHADOWMAP_BINDING, shadowMapSampler); @@ -403,9 +454,7 @@ export class WebPipeline extends Pipeline { this.descriptorSet.bindSampler(UNIFORM_SPOT_SHADOW_MAP_TEXTURE_BINDING, shadowMapSampler); this.descriptorSet.bindTexture(UNIFORM_SPOT_SHADOW_MAP_TEXTURE_BINDING, builtinResMgr.get('default-texture').getGFXTexture()!); this.descriptorSet.update(); - this.layoutGraphBuilder.compile(); - return true; } public destroy (): boolean { @@ -520,7 +569,7 @@ export class WebPipeline extends Pipeline { desc.depthOrArraySize = 1; desc.mipLevels = 1; desc.format = format; - desc.flags = ResourceFlags.COLOR_ATTACHMENT; + desc.flags = ResourceFlags.COLOR_ATTACHMENT | ResourceFlags.SAMPLED; assert(isManaged(residency)); return this._resourceGraph.addVertex( @@ -539,7 +588,7 @@ export class WebPipeline extends Pipeline { desc.depthOrArraySize = 1; desc.mipLevels = 1; desc.format = format; - desc.flags = ResourceFlags.DEPTH_STENCIL_ATTACHMENT; + desc.flags = ResourceFlags.DEPTH_STENCIL_ATTACHMENT | ResourceFlags.SAMPLED; return this._resourceGraph.addVertex( ResourceGraphValue.Managed, new ManagedResource(), @@ -570,7 +619,8 @@ export class WebPipeline extends Pipeline { throw new Error('Cannot run without creating rendergraph'); } if (!this._executor) { - this._executor = new Executor(this, this._pipelineUBO, this._device, this._resourceGraph, this.layoutGraph); + this._executor = new Executor(this, this._pipelineUBO, this._device, + this._resourceGraph, this.layoutGraph, this.width, this.height); } this._executor.execute(this._renderGraph); } @@ -646,6 +696,24 @@ export class WebPipeline extends Pipeline { this._spotLightShadowName = []; } + public getLoadOpOfClearFlag (clearFlag: ClearFlagBit, attachment: AttachmentType): LoadOp { + let loadOp = LoadOp.CLEAR; + if (!(clearFlag & ClearFlagBit.COLOR) + && attachment === AttachmentType.RENDER_TARGET) { + if (clearFlag & SKYBOX_FLAG) { + loadOp = LoadOp.DISCARD; + } else { + loadOp = LoadOp.LOAD; + } + } + if ((clearFlag & ClearFlagBit.DEPTH_STENCIL) !== ClearFlagBit.DEPTH_STENCIL + && attachment === AttachmentType.DEPTH_STENCIL) { + if (!(clearFlag & ClearFlagBit.DEPTH)) loadOp = LoadOp.LOAD; + if (!(clearFlag & ClearFlagBit.STENCIL)) loadOp = LoadOp.LOAD; + } + return loadOp; + } + private _forward (camera: Camera, idx: number): boolean { const window = camera.window; const width = Math.floor(window.width); @@ -659,7 +727,7 @@ export class WebPipeline extends Pipeline { this.addRenderTexture(forwardPassRTName, Format.RGBA8, width, height, camera.window); this.addDepthStencil(forwardPassDSName, Format.DEPTH_STENCIL, width, height, ResourceResidency.MANAGED); } - const forwardPass = this.addRasterPass(width, height, 'DEFAULT', `CameraForwardPass${idx}`); + const forwardPass = this.addRasterPass(width, height, 'Default', `CameraForwardPass${idx}`); if (this._mainLightShadowName && this.resourceGraph.contains(this._mainLightShadowName)) { const computeView = new ComputeView(); forwardPass.addComputeView(this._mainLightShadowName, computeView); @@ -687,7 +755,7 @@ export class WebPipeline extends Pipeline { .addSceneOfCamera(camera, null, SceneFlags.OPAQUE_OBJECT | SceneFlags.CUTOUT_OBJECT); forwardPass .addQueue(QueueHint.RENDER_TRANSPARENT) - .addSceneOfCamera(camera, null, SceneFlags.TRANSPARENT_OBJECT); + .addSceneOfCamera(camera, null, SceneFlags.TRANSPARENT_OBJECT | SceneFlags.UI); } return !isDeferred; } @@ -699,17 +767,16 @@ export class WebPipeline extends Pipeline { const root = legacyCC.director.root; const isDeferred: boolean = root.useDeferredPipeline; if (isDeferred) { - const deferredGbufferPassRTName = `dsDeferredPassColorCamera${idx}`; - const deferredGbufferPassNormal = `deferredGbufferPassNormal${idx}`; - const deferredGbufferPassEmissive = `deferredGbufferPassEmissive${idx}`; - const deferredGbufferPassDSName = `dsDeferredPassDSCamera${idx}`; - const isSurpportR16 = supportsR16HalfFloatTexture(this.device); + const deferredGbufferPassRTName = `dsDeferredPassColorCamera`; + const deferredGbufferPassNormal = `deferredGbufferPassNormal`; + const deferredGbufferPassEmissive = `deferredGbufferPassEmissive`; + const deferredGbufferPassDSName = `dsDeferredPassDSCamera`; if (!this.resourceGraph.contains(deferredGbufferPassRTName)) { - const colFormat = isSurpportR16 ? Format.R16F : Format.RGBA8; - this.addRenderTarget(deferredGbufferPassRTName, colFormat, width, height, ResourceResidency.PERSISTENT); - this.addRenderTarget(deferredGbufferPassNormal, colFormat, width, height, ResourceResidency.PERSISTENT); - this.addRenderTarget(deferredGbufferPassEmissive, colFormat, width, height, ResourceResidency.PERSISTENT); - this.addDepthStencil(deferredGbufferPassDSName, Format.DEPTH_STENCIL, width, height, ResourceResidency.PERSISTENT); + const colFormat = Format.RGBA16F; + this.addRenderTarget(deferredGbufferPassRTName, colFormat, width, height, ResourceResidency.MANAGED); + this.addRenderTarget(deferredGbufferPassNormal, colFormat, width, height, ResourceResidency.MANAGED); + this.addRenderTarget(deferredGbufferPassEmissive, colFormat, width, height, ResourceResidency.MANAGED); + this.addDepthStencil(deferredGbufferPassDSName, Format.DEPTH_STENCIL, width, height, ResourceResidency.MANAGED); } // gbuffer pass const gbufferPass = this.addRasterPass(width, height, 'Geometry', `CameraGbufferPass${idx}`); @@ -717,27 +784,48 @@ export class WebPipeline extends Pipeline { const computeView = new ComputeView(); gbufferPass.addComputeView(this._mainLightShadowName, computeView); } - const passView = new RasterView('_', + const rtColor = new Color(0, 0, 0, 0); + if (camera.clearFlag & ClearFlagBit.COLOR) { + if (this.pipelineSceneData.isHDR) { + SRGBToLinear(rtColor, camera.clearColor); + } else { + rtColor.x = camera.clearColor.x; + rtColor.y = camera.clearColor.y; + rtColor.z = camera.clearColor.z; + } + } + rtColor.w = camera.clearColor.w; + const passColorView = new RasterView('_', AccessType.WRITE, AttachmentType.RENDER_TARGET, LoadOp.CLEAR, StoreOp.STORE, camera.clearFlag, - new Color(camera.clearColor.x, camera.clearColor.y, camera.clearColor.z, camera.clearColor.w)); + rtColor); + const passNormalView = new RasterView('_', + AccessType.WRITE, AttachmentType.RENDER_TARGET, + LoadOp.CLEAR, StoreOp.STORE, + camera.clearFlag, + new Color(0, 0, 0, 0)); + const passEmissiveView = new RasterView('_', + AccessType.WRITE, AttachmentType.RENDER_TARGET, + LoadOp.CLEAR, StoreOp.STORE, + camera.clearFlag, + new Color(0, 0, 0, 0)); const passDSView = new RasterView('_', AccessType.WRITE, AttachmentType.DEPTH_STENCIL, LoadOp.CLEAR, StoreOp.STORE, camera.clearFlag, new Color(1, 0, 0, 0)); - gbufferPass.addRasterView(deferredGbufferPassRTName, passView); - gbufferPass.addRasterView(deferredGbufferPassNormal, passView); - gbufferPass.addRasterView(deferredGbufferPassEmissive, passView); + gbufferPass.addRasterView(deferredGbufferPassRTName, passColorView); + gbufferPass.addRasterView(deferredGbufferPassNormal, passNormalView); + gbufferPass.addRasterView(deferredGbufferPassEmissive, passEmissiveView); gbufferPass.addRasterView(deferredGbufferPassDSName, passDSView); gbufferPass .addQueue(QueueHint.RENDER_OPAQUE) .addSceneOfCamera(camera, null, SceneFlags.OPAQUE_OBJECT | SceneFlags.CUTOUT_OBJECT); - const deferredLightingPassRTName = `deferredLightingPassRTName${idx}`; - const deferredLightingPassDS = `deferredLightingPassDS${idx}`; + const deferredLightingPassRTName = `deferredLightingPassRTName`; + const deferredLightingPassDS = `deferredLightingPassDS`; if (!this.resourceGraph.contains(deferredLightingPassRTName)) { - this.addRenderTarget(deferredLightingPassRTName, Format.RGBA8, width, height, ResourceResidency.PERSISTENT); + this.addRenderTarget(deferredLightingPassRTName, Format.RGBA16F, width, height, ResourceResidency.MANAGED); this.addDepthStencil(deferredLightingPassDS, Format.DEPTH_STENCIL, width, height, ResourceResidency.MANAGED); } // lighting pass @@ -759,25 +847,27 @@ export class WebPipeline extends Pipeline { computeDepthView.name = 'depth_stencil'; lightingPass.addComputeView(deferredGbufferPassDSName, computeDepthView); } + const lightingClearColor = new Color(0, 0, 0, 0); + if (camera.clearFlag & ClearFlagBit.COLOR) { + lightingClearColor.x = camera.clearColor.x; + lightingClearColor.y = camera.clearColor.y; + lightingClearColor.z = camera.clearColor.z; + } + lightingClearColor.w = 0; const lightingPassView = new RasterView('_', AccessType.WRITE, AttachmentType.RENDER_TARGET, LoadOp.CLEAR, StoreOp.STORE, camera.clearFlag, - new Color(camera.clearColor.x, camera.clearColor.y, camera.clearColor.z, camera.clearColor.w)); - const lightingPassDSView = new RasterView('_', - AccessType.WRITE, AttachmentType.DEPTH_STENCIL, - LoadOp.CLEAR, StoreOp.STORE, - camera.clearFlag, - new Color(1, 0, 0, 0)); + lightingClearColor); lightingPass.addRasterView(deferredLightingPassRTName, lightingPassView); - lightingPass.addRasterView(deferredLightingPassDS, lightingPassDSView); - lightingPass.addQueue(QueueHint.RENDER_TRANSPARENT).addFullscreenQuad(new Material(), SceneFlags.NONE); + lightingPass.addQueue(QueueHint.RENDER_TRANSPARENT).addCameraQuad(camera, this._deferredLightingMaterial, + SceneFlags.VOLUMETRIC_LIGHTING); lightingPass.addQueue(QueueHint.RENDER_TRANSPARENT).addSceneOfCamera(camera, null, SceneFlags.TRANSPARENT_OBJECT); // Postprocess const postprocessPassRTName = `postprocessPassRTName${idx}`; const postprocessPassDS = `postprocessPassDS${idx}`; if (!this.resourceGraph.contains(postprocessPassRTName)) { - this.addRenderTarget(postprocessPassRTName, Format.RGBA8, width, height, ResourceResidency.PERSISTENT); + this.addRenderTexture(postprocessPassRTName, Format.RGBA8, width, height, camera.window); this.addDepthStencil(postprocessPassDS, Format.DEPTH_STENCIL, width, height, ResourceResidency.MANAGED); } const postprocessPass = this.addRasterPass(width, height, 'Postprocess', `CameraPostprocessPass${idx}`); @@ -786,32 +876,51 @@ export class WebPipeline extends Pipeline { computeView.name = 'outputResultMap'; postprocessPass.addComputeView(deferredLightingPassRTName, computeView); } + const postClearColor = new Color(0, 0, 0, camera.clearColor.w); const postprocessPassView = new RasterView('_', AccessType.WRITE, AttachmentType.RENDER_TARGET, - LoadOp.CLEAR, StoreOp.STORE, + this.getLoadOpOfClearFlag(camera.clearFlag, + AttachmentType.RENDER_TARGET), StoreOp.STORE, camera.clearFlag, - new Color(camera.clearColor.x, camera.clearColor.y, camera.clearColor.z, camera.clearColor.w)); + postClearColor); const postprocessPassDSView = new RasterView('_', AccessType.WRITE, AttachmentType.DEPTH_STENCIL, - LoadOp.CLEAR, StoreOp.STORE, + this.getLoadOpOfClearFlag(camera.clearFlag, + AttachmentType.DEPTH_STENCIL), StoreOp.STORE, camera.clearFlag, new Color(1, 0, 0, 0)); postprocessPass.addRasterView(postprocessPassRTName, postprocessPassView); postprocessPass.addRasterView(postprocessPassDS, postprocessPassDSView); - postprocessPass.addQueue(QueueHint.NONE).addFullscreenQuad(new Material(), SceneFlags.NONE); + postprocessPass.addQueue(QueueHint.NONE).addFullscreenQuad(this._deferredPostMaterial, SceneFlags.NONE); + postprocessPass.addQueue(QueueHint.RENDER_TRANSPARENT).addSceneOfCamera(camera, null, SceneFlags.UI); } return isDeferred; } - - render (cameras: Camera[]) { - if (cameras.length === 0) { - return; - } + protected _applySize (cameras: Camera[]) { + let newWidth = this._width; + let newHeight = this._height; cameras.forEach((camera) => { + const window = camera.window; + newWidth = Math.max(window.width, newWidth); + newHeight = Math.max(window.height, newHeight); if (!this._cameras.includes(camera)) { this._cameras.push(camera); } }); + if (newWidth !== this._width || newHeight !== this._height) { + this._width = newWidth; + this._height = newHeight; + } + } + private _width = 0; + private _height = 0; + get width () { return this._width; } + get height () { return this._height; } + render (cameras: Camera[]) { + if (cameras.length === 0) { + return; + } + this._applySize(cameras); // build graph this.beginFrame(); this.clear(); @@ -823,10 +932,10 @@ export class WebPipeline extends Pipeline { this._pipelineSceneData, `Camera${idx}`); - if (this._deferred(camera, i)) { + if (this._deferred(camera, idx)) { continue; } - this._forward(camera, i); + this._forward(camera, idx); } } this.compile(); @@ -883,9 +992,13 @@ export class WebPipeline extends Pipeline { private _pipelineUBO: PipelineUBO = new PipelineUBO(); private _cameras: Camera[] = []; - private readonly _layoutGraph: LayoutGraphData = new LayoutGraphData(); + private _layoutGraph: LayoutGraphData = new LayoutGraphData(); private readonly _resourceGraph: ResourceGraph = new ResourceGraph(); private _renderGraph: RenderGraph | null = null; private _compiler: Compiler | null = null; private _executor: Executor | null = null; + // deferred material + protected declare _deferredLightingMaterial: Material; + protected declare _deferredPostMaterial: Material; + protected _antiAliasing: AntiAliasing = AntiAliasing.NONE; } diff --git a/cocos/core/pipeline/custom/web-scene.ts b/cocos/core/pipeline/custom/web-scene.ts index b86810dc397..694584f1e02 100644 --- a/cocos/core/pipeline/custom/web-scene.ts +++ b/cocos/core/pipeline/custom/web-scene.ts @@ -50,11 +50,14 @@ export class RenderInfo implements IRenderPass { public subModel; public passIdx = -1; } + export class WebSceneTask extends SceneTask { - constructor (scneData: PipelineSceneData, ubo: PipelineUBO, camera: Camera, visitor: SceneVisitor) { + constructor (scneData: PipelineSceneData, ubo: PipelineUBO, camera: Camera | null, visitor: SceneVisitor) { super(); - this._scene = camera.scene!; - this._camera = camera; + if (camera) { + this._scene = camera.scene!; + this._camera = camera; + } this._visitor = visitor; this._sceneData = scneData; this._ubo = ubo; @@ -116,9 +119,10 @@ export class WebSceneTask extends SceneTask { } protected _sceneCulling () { - const scene = this._scene; - const camera = this._camera; - const mainLight = scene.mainLight; + if (!this.camera) { return; } + const scene = this.renderScene; + const camera = this.camera; + const mainLight = scene!.mainLight; const sceneData = this._sceneData; const shadows = sceneData.shadows; const skybox = sceneData.skybox; @@ -151,7 +155,7 @@ export class WebSceneTask extends SceneTask { renderObjects.push(this._getRenderObject(skybox.model, camera)); } - const models = scene.models; + const models = scene!.models; const visibility = camera.visibility; for (let i = 0; i < models.length; i++) { @@ -187,14 +191,14 @@ export class WebSceneTask extends SceneTask { } get camera () { return this._camera; } - get scene (): RenderScene { + get renderScene () { return this._scene; } get visitor () { return this._visitor; } get dirLightFrustum () { return this._dirLightFrustum; } get sceneData (): PipelineSceneData { return this._sceneData; } - private _scene: RenderScene; - private _camera: Camera; + private _scene: RenderScene | null = null; + private _camera: Camera | null = null; private _visitor: SceneVisitor; private _sceneData: PipelineSceneData; private _dirLightFrustum = new Frustum(); @@ -208,18 +212,18 @@ export class WebSceneTransversal extends SceneTransversal { public postRenderPass (visitor: SceneVisitor): SceneTask { return new WebSceneTask(this._sceneData, this._ubo, this._camera, visitor); } - constructor (camera: Camera, sceneData: PipelineSceneData, ubo: PipelineUBO) { + constructor (camera: Camera | null, sceneData: PipelineSceneData, ubo: PipelineUBO) { super(); this._camera = camera; - this._scene = camera.scene!; + if (camera) this._scene = camera.scene!; this._sceneData = sceneData; this._ubo = ubo; } public transverse (visitor: SceneVisitor): SceneTask { return new WebSceneTask(this._sceneData, this._ubo, this._camera, visitor); } - protected _scene: RenderScene; - protected _camera: Camera; + protected _scene: RenderScene | null = null; + protected _camera: Camera | null = null; protected _ubo: PipelineUBO; protected _sceneData: PipelineSceneData; }