import { now } from '../../core/time.js';
import { Vec3 } from '../../core/math/vec3.js';
import { Color } from '../../core/math/color.js';
import { LIGHTSHAPE_PUNCTUAL, LIGHTTYPE_OMNI, LIGHTTYPE_SPOT, LIGHTTYPE_DIRECTIONAL, FOG_NONE, FOG_LINEAR, LAYERID_DEPTH } from '../constants.js';
import { Renderer } from './renderer.js';
import { LightCamera } from './light-camera.js';
import { RenderPassForward } from './render-pass-forward.js';
import { RenderPassPostprocessing } from './render-pass-postprocessing.js';
import { BINDGROUP_VIEW } from '../../platform/graphics/constants.js';

const _noLights = [
		[],
		[],
		[]
];
const tmpColor = new Color();
const _drawCallList = {
		drawCalls: [],
		shaderInstances: [],
		isNewMaterial: [],
		lightMaskChanged: [],
		clear: function() {
				this.drawCalls.length = 0;
				this.shaderInstances.length = 0;
				this.isNewMaterial.length = 0;
				this.lightMaskChanged.length = 0;
		}
};
function vogelDiskPrecalculationSamples(numSamples) {
		const samples = [];
		for(let i = 0; i < numSamples; ++i){
				const r = Math.sqrt(i + 0.5) / Math.sqrt(numSamples);
				samples.push(r);
		}
		return samples;
}
function vogelSpherePrecalculationSamples(numSamples) {
		const samples = [];
		for(let i = 0; i < numSamples; i++){
				const weight = i / numSamples;
				const radius = Math.sqrt(weight * weight);
				samples.push(radius);
		}
		return samples;
}
class ForwardRenderer extends Renderer {
		constructor(graphicsDevice, scene){
				super(graphicsDevice, scene);
				const device = this.device;
				this._forwardDrawCalls = 0;
				this._materialSwitches = 0;
				this._depthMapTime = 0;
				this._forwardTime = 0;
				this._sortTime = 0;
				const scope = device.scope;
				this.fogColorId = scope.resolve('fog_color');
				this.fogStartId = scope.resolve('fog_start');
				this.fogEndId = scope.resolve('fog_end');
				this.fogDensityId = scope.resolve('fog_density');
				this.ambientId = scope.resolve('light_globalAmbient');
				this.skyboxIntensityId = scope.resolve('skyboxIntensity');
				this.cubeMapRotationMatrixId = scope.resolve('cubeMapRotationMatrix');
				this.pcssDiskSamplesId = scope.resolve('pcssDiskSamples[0]');
				this.pcssSphereSamplesId = scope.resolve('pcssSphereSamples[0]');
				this.lightColorId = [];
				this.lightDir = [];
				this.lightDirId = [];
				this.lightShadowMapId = [];
				this.lightShadowMatrixId = [];
				this.lightShadowParamsId = [];
				this.lightShadowIntensity = [];
				this.lightRadiusId = [];
				this.lightPos = [];
				this.lightPosId = [];
				this.lightWidth = [];
				this.lightWidthId = [];
				this.lightHeight = [];
				this.lightHeightId = [];
				this.lightInAngleId = [];
				this.lightOutAngleId = [];
				this.lightCookieId = [];
				this.lightCookieIntId = [];
				this.lightCookieMatrixId = [];
				this.lightCookieOffsetId = [];
				this.lightShadowSearchAreaId = [];
				this.lightCameraParamsId = [];
				this.lightSoftShadowParamsId = [];
				this.shadowMatrixPaletteId = [];
				this.shadowCascadeDistancesId = [];
				this.shadowCascadeCountId = [];
				this.shadowCascadeBlendId = [];
				this.screenSizeId = scope.resolve('uScreenSize');
				this._screenSize = new Float32Array(4);
				this.fogColor = new Float32Array(3);
				this.ambientColor = new Float32Array(3);
				this.pcssDiskSamples = vogelDiskPrecalculationSamples(16);
				this.pcssSphereSamples = vogelSpherePrecalculationSamples(16);
		}
		destroy() {
				super.destroy();
		}
		static{
				this.skipRenderCamera = null;
		}
		static{
				this._skipRenderCounter = 0;
		}
		static{
				this.skipRenderAfter = 0;
		}
		dispatchGlobalLights(scene) {
				const ambientUniform = this.ambientColor;
				tmpColor.linear(scene.ambientLight);
				ambientUniform[0] = tmpColor.r;
				ambientUniform[1] = tmpColor.g;
				ambientUniform[2] = tmpColor.b;
				if (scene.physicalUnits) {
						for(let i = 0; i < 3; i++){
								ambientUniform[i] *= scene.ambientLuminance;
						}
				}
				this.ambientId.setValue(ambientUniform);
				this.skyboxIntensityId.setValue(scene.physicalUnits ? scene.skyboxLuminance : scene.skyboxIntensity);
				this.cubeMapRotationMatrixId.setValue(scene._skyboxRotationMat3.data);
		}
		_resolveLight(scope, i) {
				const light = `light${i}`;
				this.lightColorId[i] = scope.resolve(`${light}_color`);
				this.lightDir[i] = new Float32Array(3);
				this.lightDirId[i] = scope.resolve(`${light}_direction`);
				this.lightShadowMapId[i] = scope.resolve(`${light}_shadowMap`);
				this.lightShadowMatrixId[i] = scope.resolve(`${light}_shadowMatrix`);
				this.lightShadowParamsId[i] = scope.resolve(`${light}_shadowParams`);
				this.lightShadowIntensity[i] = scope.resolve(`${light}_shadowIntensity`);
				this.lightShadowSearchAreaId[i] = scope.resolve(`${light}_shadowSearchArea`);
				this.lightRadiusId[i] = scope.resolve(`${light}_radius`);
				this.lightPos[i] = new Float32Array(3);
				this.lightPosId[i] = scope.resolve(`${light}_position`);
				this.lightWidth[i] = new Float32Array(3);
				this.lightWidthId[i] = scope.resolve(`${light}_halfWidth`);
				this.lightHeight[i] = new Float32Array(3);
				this.lightHeightId[i] = scope.resolve(`${light}_halfHeight`);
				this.lightInAngleId[i] = scope.resolve(`${light}_innerConeAngle`);
				this.lightOutAngleId[i] = scope.resolve(`${light}_outerConeAngle`);
				this.lightCookieId[i] = scope.resolve(`${light}_cookie`);
				this.lightCookieIntId[i] = scope.resolve(`${light}_cookieIntensity`);
				this.lightCookieMatrixId[i] = scope.resolve(`${light}_cookieMatrix`);
				this.lightCookieOffsetId[i] = scope.resolve(`${light}_cookieOffset`);
				this.lightCameraParamsId[i] = scope.resolve(`${light}_cameraParams`);
				this.lightSoftShadowParamsId[i] = scope.resolve(`${light}_softShadowParams`);
				this.shadowMatrixPaletteId[i] = scope.resolve(`${light}_shadowMatrixPalette[0]`);
				this.shadowCascadeDistancesId[i] = scope.resolve(`${light}_shadowCascadeDistances`);
				this.shadowCascadeCountId[i] = scope.resolve(`${light}_shadowCascadeCount`);
				this.shadowCascadeBlendId[i] = scope.resolve(`${light}_shadowCascadeBlend`);
		}
		setLTCDirectionalLight(wtm, cnt, dir, campos, far) {
				this.lightPos[cnt][0] = campos.x - dir.x * far;
				this.lightPos[cnt][1] = campos.y - dir.y * far;
				this.lightPos[cnt][2] = campos.z - dir.z * far;
				this.lightPosId[cnt].setValue(this.lightPos[cnt]);
				const hWidth = wtm.transformVector(new Vec3(-0.5, 0, 0));
				this.lightWidth[cnt][0] = hWidth.x * far;
				this.lightWidth[cnt][1] = hWidth.y * far;
				this.lightWidth[cnt][2] = hWidth.z * far;
				this.lightWidthId[cnt].setValue(this.lightWidth[cnt]);
				const hHeight = wtm.transformVector(new Vec3(0, 0, 0.5));
				this.lightHeight[cnt][0] = hHeight.x * far;
				this.lightHeight[cnt][1] = hHeight.y * far;
				this.lightHeight[cnt][2] = hHeight.z * far;
				this.lightHeightId[cnt].setValue(this.lightHeight[cnt]);
		}
		dispatchDirectLights(dirs, mask, camera) {
				let cnt = 0;
				const scope = this.device.scope;
				for(let i = 0; i < dirs.length; i++){
						if (!(dirs[i].mask & mask)) continue;
						const directional = dirs[i];
						const wtm = directional._node.getWorldTransform();
						if (!this.lightColorId[cnt]) {
								this._resolveLight(scope, cnt);
						}
						this.lightColorId[cnt].setValue(directional._colorLinear);
						wtm.getY(directional._direction).mulScalar(-1);
						directional._direction.normalize();
						this.lightDir[cnt][0] = directional._direction.x;
						this.lightDir[cnt][1] = directional._direction.y;
						this.lightDir[cnt][2] = directional._direction.z;
						this.lightDirId[cnt].setValue(this.lightDir[cnt]);
						if (directional.shape !== LIGHTSHAPE_PUNCTUAL) {
								this.setLTCDirectionalLight(wtm, cnt, directional._direction, camera._node.getPosition(), camera.farClip);
						}
						if (directional.castShadows) {
								const lightRenderData = directional.getRenderData(camera, 0);
								const biases = directional._getUniformBiasValues(lightRenderData);
								this.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);
								this.lightShadowMatrixId[cnt].setValue(lightRenderData.shadowMatrix.data);
								this.shadowMatrixPaletteId[cnt].setValue(directional._shadowMatrixPalette);
								this.shadowCascadeDistancesId[cnt].setValue(directional._shadowCascadeDistances);
								this.shadowCascadeCountId[cnt].setValue(directional.numCascades);
								this.shadowCascadeBlendId[cnt].setValue(1 - directional.cascadeBlend);
								this.lightShadowIntensity[cnt].setValue(directional.shadowIntensity);
								this.lightSoftShadowParamsId[cnt].setValue(directional._softShadowParams);
								const shadowRT = lightRenderData.shadowCamera.renderTarget;
								if (shadowRT) {
										this.lightShadowSearchAreaId[cnt].setValue(directional.penumbraSize / lightRenderData.shadowCamera.renderTarget.width * lightRenderData.projectionCompensation);
								}
								const cameraParams = directional._shadowCameraParams;
								cameraParams.length = 4;
								cameraParams[0] = 0;
								cameraParams[1] = lightRenderData.shadowCamera._farClip;
								cameraParams[2] = lightRenderData.shadowCamera._nearClip;
								cameraParams[3] = 1;
								this.lightCameraParamsId[cnt].setValue(cameraParams);
								const params = directional._shadowRenderParams;
								params.length = 4;
								params[0] = directional._shadowResolution;
								params[1] = biases.normalBias;
								params[2] = biases.bias;
								params[3] = 0;
								this.lightShadowParamsId[cnt].setValue(params);
						}
						cnt++;
				}
				return cnt;
		}
		setLTCPositionalLight(wtm, cnt) {
				const hWidth = wtm.transformVector(new Vec3(-0.5, 0, 0));
				this.lightWidth[cnt][0] = hWidth.x;
				this.lightWidth[cnt][1] = hWidth.y;
				this.lightWidth[cnt][2] = hWidth.z;
				this.lightWidthId[cnt].setValue(this.lightWidth[cnt]);
				const hHeight = wtm.transformVector(new Vec3(0, 0, 0.5));
				this.lightHeight[cnt][0] = hHeight.x;
				this.lightHeight[cnt][1] = hHeight.y;
				this.lightHeight[cnt][2] = hHeight.z;
				this.lightHeightId[cnt].setValue(this.lightHeight[cnt]);
		}
		dispatchOmniLight(scope, omni, cnt) {
				const wtm = omni._node.getWorldTransform();
				if (!this.lightColorId[cnt]) {
						this._resolveLight(scope, cnt);
				}
				this.lightRadiusId[cnt].setValue(omni.attenuationEnd);
				this.lightColorId[cnt].setValue(omni._colorLinear);
				wtm.getTranslation(omni._position);
				this.lightPos[cnt][0] = omni._position.x;
				this.lightPos[cnt][1] = omni._position.y;
				this.lightPos[cnt][2] = omni._position.z;
				this.lightPosId[cnt].setValue(this.lightPos[cnt]);
				if (omni.shape !== LIGHTSHAPE_PUNCTUAL) {
						this.setLTCPositionalLight(wtm, cnt);
				}
				if (omni.castShadows) {
						const lightRenderData = omni.getRenderData(null, 0);
						this.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);
						const biases = omni._getUniformBiasValues(lightRenderData);
						const params = omni._shadowRenderParams;
						params.length = 4;
						params[0] = omni._shadowResolution;
						params[1] = biases.normalBias;
						params[2] = biases.bias;
						params[3] = 1.0 / omni.attenuationEnd;
						this.lightShadowParamsId[cnt].setValue(params);
						this.lightShadowIntensity[cnt].setValue(omni.shadowIntensity);
						const pixelsPerMeter = omni.penumbraSize / lightRenderData.shadowCamera.renderTarget.width;
						this.lightShadowSearchAreaId[cnt].setValue(pixelsPerMeter);
						const cameraParams = omni._shadowCameraParams;
						cameraParams.length = 4;
						cameraParams[0] = 0;
						cameraParams[1] = lightRenderData.shadowCamera._farClip;
						cameraParams[2] = lightRenderData.shadowCamera._nearClip;
						cameraParams[3] = 0;
						this.lightCameraParamsId[cnt].setValue(cameraParams);
				}
				if (omni._cookie) {
						this.lightCookieId[cnt].setValue(omni._cookie);
						this.lightShadowMatrixId[cnt].setValue(wtm.data);
						this.lightCookieIntId[cnt].setValue(omni.cookieIntensity);
				}
		}
		dispatchSpotLight(scope, spot, cnt) {
				const wtm = spot._node.getWorldTransform();
				if (!this.lightColorId[cnt]) {
						this._resolveLight(scope, cnt);
				}
				this.lightInAngleId[cnt].setValue(spot._innerConeAngleCos);
				this.lightOutAngleId[cnt].setValue(spot._outerConeAngleCos);
				this.lightRadiusId[cnt].setValue(spot.attenuationEnd);
				this.lightColorId[cnt].setValue(spot._colorLinear);
				wtm.getTranslation(spot._position);
				this.lightPos[cnt][0] = spot._position.x;
				this.lightPos[cnt][1] = spot._position.y;
				this.lightPos[cnt][2] = spot._position.z;
				this.lightPosId[cnt].setValue(this.lightPos[cnt]);
				if (spot.shape !== LIGHTSHAPE_PUNCTUAL) {
						this.setLTCPositionalLight(wtm, cnt);
				}
				wtm.getY(spot._direction).mulScalar(-1);
				spot._direction.normalize();
				this.lightDir[cnt][0] = spot._direction.x;
				this.lightDir[cnt][1] = spot._direction.y;
				this.lightDir[cnt][2] = spot._direction.z;
				this.lightDirId[cnt].setValue(this.lightDir[cnt]);
				if (spot.castShadows) {
						const lightRenderData = spot.getRenderData(null, 0);
						this.lightShadowMapId[cnt].setValue(lightRenderData.shadowBuffer);
						this.lightShadowMatrixId[cnt].setValue(lightRenderData.shadowMatrix.data);
						const biases = spot._getUniformBiasValues(lightRenderData);
						const params = spot._shadowRenderParams;
						params.length = 4;
						params[0] = spot._shadowResolution;
						params[1] = biases.normalBias;
						params[2] = biases.bias;
						params[3] = 1.0 / spot.attenuationEnd;
						this.lightShadowParamsId[cnt].setValue(params);
						this.lightShadowIntensity[cnt].setValue(spot.shadowIntensity);
						const pixelsPerMeter = spot.penumbraSize / lightRenderData.shadowCamera.renderTarget.width;
						const fov = lightRenderData.shadowCamera._fov * Math.PI / 180.0;
						const fovRatio = 1.0 / Math.tan(fov / 2.0);
						this.lightShadowSearchAreaId[cnt].setValue(pixelsPerMeter * fovRatio);
						const cameraParams = spot._shadowCameraParams;
						cameraParams.length = 4;
						cameraParams[0] = 0;
						cameraParams[1] = lightRenderData.shadowCamera._farClip;
						cameraParams[2] = lightRenderData.shadowCamera._nearClip;
						cameraParams[3] = 0;
						this.lightCameraParamsId[cnt].setValue(cameraParams);
				}
				if (spot._cookie) {
						if (!spot.castShadows) {
								const cookieMatrix = LightCamera.evalSpotCookieMatrix(spot);
								this.lightShadowMatrixId[cnt].setValue(cookieMatrix.data);
						}
						this.lightCookieId[cnt].setValue(spot._cookie);
						this.lightCookieIntId[cnt].setValue(spot.cookieIntensity);
						if (spot._cookieTransform) {
								spot._cookieTransformUniform[0] = spot._cookieTransform.x;
								spot._cookieTransformUniform[1] = spot._cookieTransform.y;
								spot._cookieTransformUniform[2] = spot._cookieTransform.z;
								spot._cookieTransformUniform[3] = spot._cookieTransform.w;
								this.lightCookieMatrixId[cnt].setValue(spot._cookieTransformUniform);
								spot._cookieOffsetUniform[0] = spot._cookieOffset.x;
								spot._cookieOffsetUniform[1] = spot._cookieOffset.y;
								this.lightCookieOffsetId[cnt].setValue(spot._cookieOffsetUniform);
						}
				}
		}
		dispatchLocalLights(sortedLights, mask, usedDirLights) {
				let cnt = usedDirLights;
				const scope = this.device.scope;
				const omnis = sortedLights[LIGHTTYPE_OMNI];
				const numOmnis = omnis.length;
				for(let i = 0; i < numOmnis; i++){
						const omni = omnis[i];
						if (!(omni.mask & mask)) continue;
						this.dispatchOmniLight(scope, omni, cnt);
						cnt++;
				}
				const spts = sortedLights[LIGHTTYPE_SPOT];
				const numSpts = spts.length;
				for(let i = 0; i < numSpts; i++){
						const spot = spts[i];
						if (!(spot.mask & mask)) continue;
						this.dispatchSpotLight(scope, spot, cnt);
						cnt++;
				}
		}
		renderForwardPrepareMaterials(camera, renderTarget, drawCalls, sortedLights, layer, pass) {
				const fogParams = camera.fogParams ?? this.scene.fog;
				const shaderParams = camera.shaderParams;
				shaderParams.fog = fogParams.type;
				shaderParams.srgbRenderTarget = renderTarget?.isColorBufferSrgb(0) ?? false;
				const addCall = (drawCall, shaderInstance, isNewMaterial, lightMaskChanged)=>{
						_drawCallList.drawCalls.push(drawCall);
						_drawCallList.shaderInstances.push(shaderInstance);
						_drawCallList.isNewMaterial.push(isNewMaterial);
						_drawCallList.lightMaskChanged.push(lightMaskChanged);
				};
				_drawCallList.clear();
				const device = this.device;
				const scene = this.scene;
				const clusteredLightingEnabled = scene.clusteredLightingEnabled;
				const lightHash = layer?.getLightHash(clusteredLightingEnabled) ?? 0;
				let prevMaterial = null, prevObjDefs, prevLightMask;
				const drawCallsCount = drawCalls.length;
				for(let i = 0; i < drawCallsCount; i++){
						const drawCall = drawCalls[i];
						if (camera === ForwardRenderer.skipRenderCamera) {
								if (ForwardRenderer._skipRenderCounter >= ForwardRenderer.skipRenderAfter) {
										continue;
								}
								ForwardRenderer._skipRenderCounter++;
						}
						if (layer) {
								if (layer._skipRenderCounter >= layer.skipRenderAfter) {
										continue;
								}
								layer._skipRenderCounter++;
						}
						const instancingData = drawCall.instancingData;
						if (instancingData && instancingData.count <= 0) {
								continue;
						}
						drawCall.ensureMaterial(device);
						const material = drawCall.material;
						const objDefs = drawCall._shaderDefs;
						const lightMask = drawCall.mask;
						if (material && material === prevMaterial && objDefs !== prevObjDefs) {
								prevMaterial = null;
						}
						if (material !== prevMaterial) {
								this._materialSwitches++;
								material._scene = scene;
								if (material.dirty) {
										material.updateUniforms(device, scene);
										material.dirty = false;
								}
						}
						const shaderInstance = drawCall.getShaderInstance(pass, lightHash, scene, shaderParams, this.viewUniformFormat, this.viewBindGroupFormat, sortedLights);
						addCall(drawCall, shaderInstance, material !== prevMaterial, !prevMaterial || lightMask !== prevLightMask);
						prevMaterial = material;
						prevObjDefs = objDefs;
						prevLightMask = lightMask;
				}
				return _drawCallList;
		}
		renderForwardInternal(camera, preparedCalls, sortedLights, pass, drawCallback, flipFaces, viewBindGroups) {
				const device = this.device;
				const scene = this.scene;
				const passFlag = 1 << pass;
				const flipFactor = flipFaces ? -1 : 1;
				const clusteredLightingEnabled = scene.clusteredLightingEnabled;
				const viewList = camera.xr?.session && camera.xr.views.list.length ? camera.xr.views.list : null;
				const preparedCallsCount = preparedCalls.drawCalls.length;
				for(let i = 0; i < preparedCallsCount; i++){
						const drawCall = preparedCalls.drawCalls[i];
						const newMaterial = preparedCalls.isNewMaterial[i];
						const lightMaskChanged = preparedCalls.lightMaskChanged[i];
						const shaderInstance = preparedCalls.shaderInstances[i];
						const material = drawCall.material;
						const lightMask = drawCall.mask;
						if (shaderInstance.shader.failed) continue;
						if (newMaterial) {
								const asyncCompile = false;
								device.setShader(shaderInstance.shader, asyncCompile);
								material.setParameters(device);
								if (lightMaskChanged) {
										const usedDirLights = this.dispatchDirectLights(sortedLights[LIGHTTYPE_DIRECTIONAL], lightMask, camera);
										if (!clusteredLightingEnabled) {
												this.dispatchLocalLights(sortedLights, lightMask, usedDirLights);
										}
								}
								this.alphaTestId.setValue(material.alphaTest);
								device.setBlendState(material.blendState);
								device.setDepthState(material.depthState);
								device.setAlphaToCoverage(material.alphaToCoverage);
						}
						this.setupCullMode(camera._cullFaces, flipFactor, drawCall);
						const stencilFront = drawCall.stencilFront ?? material.stencilFront;
						const stencilBack = drawCall.stencilBack ?? material.stencilBack;
						device.setStencilState(stencilFront, stencilBack);
						drawCall.setParameters(device, passFlag);
						device.scope.resolve('meshInstanceId').setValue(drawCall.id);
						const mesh = drawCall.mesh;
						this.setVertexBuffers(device, mesh);
						this.setMorphing(device, drawCall.morphInstance);
						this.setSkinning(device, drawCall);
						const instancingData = drawCall.instancingData;
						if (instancingData) {
								device.setVertexBuffer(instancingData.vertexBuffer);
						}
						this.setMeshInstanceMatrices(drawCall, true);
						this.setupMeshUniformBuffers(shaderInstance);
						const style = drawCall.renderStyle;
						const indexBuffer = mesh.indexBuffer[style];
						drawCallback?.(drawCall, i);
						const indirectSlot = drawCall.indirectData?.get(camera);
						if (viewList) {
								for(let v = 0; v < viewList.length; v++){
										const view = viewList[v];
										device.setViewport(view.viewport.x, view.viewport.y, view.viewport.z, view.viewport.w);
										if (device.supportsUniformBuffers) {
												const viewBindGroup = viewBindGroups[v];
												device.setBindGroup(BINDGROUP_VIEW, viewBindGroup);
										} else {
												this.setupViewUniforms(view, v);
										}
										const first = v === 0;
										const last = v === viewList.length - 1;
										device.draw(mesh.primitive[style], indexBuffer, instancingData?.count, indirectSlot, first, last);
										this._forwardDrawCalls++;
										if (drawCall.instancingData) {
												this._instancedDrawCalls++;
										}
								}
						} else {
								device.draw(mesh.primitive[style], indexBuffer, instancingData?.count, indirectSlot);
								this._forwardDrawCalls++;
								if (drawCall.instancingData) {
										this._instancedDrawCalls++;
								}
						}
						if (i < preparedCallsCount - 1 && !preparedCalls.isNewMaterial[i + 1]) {
								material.setParameters(device, drawCall.parameters);
						}
				}
		}
		renderForward(camera, renderTarget, allDrawCalls, sortedLights, pass, drawCallback, layer, flipFaces, viewBindGroups) {
				const forwardStartTime = now();
				const preparedCalls = this.renderForwardPrepareMaterials(camera, renderTarget, allDrawCalls, sortedLights, layer, pass);
				this.renderForwardInternal(camera, preparedCalls, sortedLights, pass, drawCallback, flipFaces, viewBindGroups);
				_drawCallList.clear();
				this._forwardTime += now() - forwardStartTime;
		}
		renderForwardLayer(camera, renderTarget, layer, transparent, shaderPass, viewBindGroups, options = {}) {
				const { scene, device } = this;
				const clusteredLightingEnabled = scene.clusteredLightingEnabled;
				this.setupViewport(camera, renderTarget);
				let visible, splitLights;
				if (layer) {
						const sortTime = now();
						layer.sortVisible(camera, transparent);
						this._sortTime += now() - sortTime;
						const culledInstances = layer.getCulledInstances(camera);
						visible = transparent ? culledInstances.transparent : culledInstances.opaque;
						scene.immediate.onPreRenderLayer(layer, visible, transparent);
						if (layer.requiresLightCube) {
								this.lightCube.update(scene.ambientLight, layer._lights);
								this.constantLightCube.setValue(this.lightCube.colors);
						}
						splitLights = layer.splitLights;
				} else {
						visible = options.meshInstances;
						splitLights = options.splitLights ?? _noLights;
				}
				if (clusteredLightingEnabled) {
						const lightClusters = options.lightClusters ?? this.worldClustersAllocator.empty;
						lightClusters.activate();
						if (layer) {
								if (!this.clustersDebugRendered && scene.lighting.debugLayer === layer.id) {
										this.clustersDebugRendered = true;
								}
						}
				}
				scene._activeCamera = camera;
				const fogParams = camera.fogParams ?? this.scene.fog;
				this.setFogConstants(fogParams);
				const viewList = this.setCameraUniforms(camera, renderTarget);
				if (device.supportsUniformBuffers) {
						this.setupViewUniformBuffers(viewBindGroups, this.viewUniformFormat, this.viewBindGroupFormat, viewList);
				}
				const clearColor = options.clearColor ?? false;
				const clearDepth = options.clearDepth ?? false;
				const clearStencil = options.clearStencil ?? false;
				if (clearColor || clearDepth || clearStencil) {
						this.clear(camera, clearColor, clearDepth, clearStencil);
				}
				const flipFaces = !!(camera._flipFaces ^ renderTarget?.flipY);
				const forwardDrawCalls = this._forwardDrawCalls;
				this.renderForward(camera, renderTarget, visible, splitLights, shaderPass, null, layer, flipFaces, viewBindGroups);
				if (layer) {
						layer._forwardDrawCalls += this._forwardDrawCalls - forwardDrawCalls;
				}
		}
		setFogConstants(fogParams) {
				if (fogParams.type !== FOG_NONE) {
						tmpColor.linear(fogParams.color);
						const fogUniform = this.fogColor;
						fogUniform[0] = tmpColor.r;
						fogUniform[1] = tmpColor.g;
						fogUniform[2] = tmpColor.b;
						this.fogColorId.setValue(fogUniform);
						if (fogParams.type === FOG_LINEAR) {
								this.fogStartId.setValue(fogParams.start);
								this.fogEndId.setValue(fogParams.end);
						} else {
								this.fogDensityId.setValue(fogParams.density);
						}
				}
		}
		setSceneConstants() {
				const scene = this.scene;
				this.dispatchGlobalLights(scene);
				const device = this.device;
				this._screenSize[0] = device.width;
				this._screenSize[1] = device.height;
				this._screenSize[2] = 1 / device.width;
				this._screenSize[3] = 1 / device.height;
				this.screenSizeId.setValue(this._screenSize);
				this.pcssDiskSamplesId.setValue(this.pcssDiskSamples);
				this.pcssSphereSamplesId.setValue(this.pcssSphereSamples);
		}
		buildFrameGraph(frameGraph, layerComposition) {
				const scene = this.scene;
				frameGraph.reset();
				if (scene.clusteredLightingEnabled) {
						const { shadowsEnabled, cookiesEnabled } = scene.lighting;
						this._renderPassUpdateClustered.update(frameGraph, shadowsEnabled, cookiesEnabled, this.lights, this.localLights);
						frameGraph.addRenderPass(this._renderPassUpdateClustered);
				} else {
						this._shadowRendererLocal.buildNonClusteredRenderPasses(frameGraph, this.localLights);
				}
				let startIndex = 0;
				let newStart = true;
				let renderTarget = null;
				const renderActions = layerComposition._renderActions;
				for(let i = startIndex; i < renderActions.length; i++){
						const renderAction = renderActions[i];
						const { layer, camera } = renderAction;
						if (renderAction.useCameraPasses) {
								camera.camera.renderPasses.forEach((renderPass)=>{
										frameGraph.addRenderPass(renderPass);
								});
						} else {
								const isDepthLayer = layer.id === LAYERID_DEPTH;
								const isGrabPass = isDepthLayer && (camera.renderSceneColorMap || camera.renderSceneDepthMap);
								if (newStart) {
										newStart = false;
										startIndex = i;
										renderTarget = renderAction.renderTarget;
								}
								const nextRenderAction = renderActions[i + 1];
								const isNextLayerDepth = nextRenderAction ? !nextRenderAction.useCameraPasses && nextRenderAction.layer.id === LAYERID_DEPTH : false;
								const isNextLayerGrabPass = isNextLayerDepth && (camera.renderSceneColorMap || camera.renderSceneDepthMap);
								const nextNeedDirShadows = nextRenderAction ? nextRenderAction.firstCameraUse && this.cameraDirShadowLights.has(nextRenderAction.camera.camera) : false;
								if (!nextRenderAction || nextRenderAction.renderTarget !== renderTarget || nextNeedDirShadows || isNextLayerGrabPass || isGrabPass) {
										const isDepthOnly = isDepthLayer && startIndex === i;
										if (!isDepthOnly) {
												this.addMainRenderPass(frameGraph, layerComposition, renderTarget, startIndex, i);
										}
										if (isDepthLayer) {
												if (camera.renderSceneColorMap) {
														const colorGrabPass = camera.camera.renderPassColorGrab;
														colorGrabPass.source = camera.renderTarget;
														frameGraph.addRenderPass(colorGrabPass);
												}
												if (camera.renderSceneDepthMap) {
														frameGraph.addRenderPass(camera.camera.renderPassDepthGrab);
												}
										}
										if (renderAction.triggerPostprocess && camera?.onPostprocessing) {
												const renderPass = new RenderPassPostprocessing(this.device, this, renderAction);
												frameGraph.addRenderPass(renderPass);
										}
										newStart = true;
								}
						}
				}
		}
		addMainRenderPass(frameGraph, layerComposition, renderTarget, startIndex, endIndex) {
				const renderPass = new RenderPassForward(this.device, layerComposition, this.scene, this);
				renderPass.init(renderTarget);
				const renderActions = layerComposition._renderActions;
				for(let i = startIndex; i <= endIndex; i++){
						renderPass.addRenderAction(renderActions[i]);
				}
				frameGraph.addRenderPass(renderPass);
		}
		update(comp) {
				this.frameUpdate();
				this.shadowRenderer.frameUpdate();
				this.scene._updateSkyMesh();
				this.updateLayerComposition(comp);
				this.collectLights(comp);
				this.beginFrame(comp);
				this.setSceneConstants();
				this.gsplatDirector?.update(comp);
				this.cullComposition(comp);
				this.gpuUpdate(this.processingMeshInstances);
		}
}

export { ForwardRenderer };
