import { backbufferColorTextureHandle, backbufferDepthStencilTextureHandle } from "../../frameGraphTypes.js";
import { FrameGraphTask } from "../../frameGraphTask.js";
import { ObjectRenderer } from "../../../Rendering/objectRenderer.js";
import { FrameGraphCascadedShadowGeneratorTask } from "./csmShadowGeneratorTask.js";

/**
 * Task used to render objects to a texture.
 */
export class FrameGraphObjectRendererTask extends FrameGraphTask {
    /**
     * Gets or sets the camera used to render the objects.
     */
    get camera() {
        return this._camera;
    }
    set camera(camera) {
        this._camera = camera;
        this._renderer.activeCamera = this.camera;
    }
    /**
     * If image processing should be disabled (default is false).
     * false means that the default image processing configuration will be applied (the one from the scene)
     */
    get disableImageProcessing() {
        return this._disableImageProcessing;
    }
    set disableImageProcessing(value) {
        if (value === this._disableImageProcessing) {
            return;
        }
        this._disableImageProcessing = value;
        this._renderer.disableImageProcessing = value;
    }
    /**
     * Defines if meshes should be rendered (default is true).
     */
    get renderMeshes() {
        return this._renderMeshes;
    }
    set renderMeshes(value) {
        if (value === this._renderMeshes) {
            return;
        }
        this._renderMeshes = value;
        this._renderer.renderMeshes = value;
    }
    /**
     * Defines if particles should be rendered (default is true).
     */
    get renderParticles() {
        return this._renderParticles;
    }
    set renderParticles(value) {
        if (value === this._renderParticles) {
            return;
        }
        this._renderParticles = value;
        this._renderer.renderParticles = value;
    }
    /**
     * Defines if sprites should be rendered (default is true).
     */
    get renderSprites() {
        return this._renderSprites;
    }
    set renderSprites(value) {
        if (value === this._renderSprites) {
            return;
        }
        this._renderSprites = value;
        this._renderer.renderSprites = value;
    }
    /**
     * Forces checking the layerMask property even if a custom list of meshes is provided (ie. if renderList is not undefined). Default is true.
     */
    get forceLayerMaskCheck() {
        return this._forceLayerMaskCheck;
    }
    set forceLayerMaskCheck(value) {
        if (value === this._forceLayerMaskCheck) {
            return;
        }
        this._forceLayerMaskCheck = value;
        this._renderer.forceLayerMaskCheck = value;
    }
    /**
     * Enables the rendering of bounding boxes for meshes (still subject to Mesh.showBoundingBox or scene.forceShowBoundingBoxes). Default is true.
     */
    get enableBoundingBoxRendering() {
        return this._enableBoundingBoxRendering;
    }
    set enableBoundingBoxRendering(value) {
        if (value === this._enableBoundingBoxRendering) {
            return;
        }
        this._enableBoundingBoxRendering = value;
        this._renderer.enableBoundingBoxRendering = value;
    }
    /**
     * Enables the rendering of outlines/overlays for meshes (still subject to Mesh.renderOutline/Mesh.renderOverlay). Default is true.
     */
    get enableOutlineRendering() {
        return this._enableOutlineRendering;
    }
    set enableOutlineRendering(value) {
        if (value === this._enableOutlineRendering) {
            return;
        }
        this._enableOutlineRendering = value;
        this._renderer.enableOutlineRendering = value;
    }
    /**
     * The object renderer used to render the objects.
     */
    get objectRenderer() {
        return this._renderer;
    }
    get name() {
        return this._name;
    }
    set name(value) {
        this._name = value;
        if (this._renderer) {
            this._renderer.name = value;
        }
    }
    /**
     * Constructs a new object renderer task.
     * @param name The name of the task.
     * @param frameGraph The frame graph the task belongs to.
     * @param scene The scene the frame graph is associated with.
     * @param options The options of the object renderer.
     * @param existingObjectRenderer An existing object renderer to use (optional). If provided, the options parameter will be ignored.
     */
    constructor(name, frameGraph, scene, options, existingObjectRenderer) {
        super(name, frameGraph);
        /**
         * The shadow generators used to render the objects (optional).
         */
        this.shadowGenerators = [];
        /**
         * If depth testing should be enabled (default is true).
         */
        this.depthTest = true;
        /**
         * If depth writing should be enabled (default is true).
         */
        this.depthWrite = true;
        /**
         * If shadows should be disabled (default is false).
         */
        this.disableShadows = false;
        this._disableImageProcessing = false;
        /**
         * Sets this property to true if this task is the main object renderer of the frame graph.
         * It will help to locate the main object renderer in the frame graph when multiple object renderers are used.
         * This is useful for the inspector to know which object renderer to use for additional rendering features like wireframe rendering or frustum light debugging.
         * It is also used to determine the main camera used by the frame graph: this is the camera used by the main object renderer.
         */
        this.isMainObjectRenderer = false;
        this._renderMeshes = true;
        this._renderParticles = true;
        this._renderSprites = true;
        this._forceLayerMaskCheck = true;
        this._enableBoundingBoxRendering = true;
        this._enableOutlineRendering = true;
        /**
         * If true, targetTexture will be resolved at the end of the render pass, if this/these texture(s) is/are MSAA (default: true)
         */
        this.resolveMSAAColors = true;
        /**
         * If true, depthTexture will be resolved at the end of the render pass, if this texture is provided and is MSAA (default: false).
         */
        this.resolveMSAADepth = false;
        this._onBeforeRenderObservable = null;
        this._onAfterRenderObservable = null;
        this._externalObjectRenderer = false;
        this._scene = scene;
        this._engine = scene.getEngine();
        this._externalObjectRenderer = !!existingObjectRenderer;
        this._renderer = existingObjectRenderer ?? new ObjectRenderer(name, scene, options);
        this.name = name;
        this._renderer.disableImageProcessing = this._disableImageProcessing;
        this._renderer.renderParticles = this._renderParticles;
        this._renderer.renderSprites = this._renderSprites;
        this._renderer.enableBoundingBoxRendering = this._enableBoundingBoxRendering;
        this._renderer.forceLayerMaskCheck = this._forceLayerMaskCheck;
        if (!this._externalObjectRenderer) {
            this._renderer.onBeforeRenderingManagerRenderObservable.add(() => {
                if (!this._renderer.options.doNotChangeAspectRatio) {
                    scene.updateTransformMatrix(true);
                }
            });
        }
        this.outputTexture = this._frameGraph.textureManager.createDanglingHandle();
        this.outputDepthTexture = this._frameGraph.textureManager.createDanglingHandle();
    }
    isReady() {
        return this._renderer.isReadyForRendering(this._textureWidth, this._textureHeight);
    }
    getClassName() {
        return "FrameGraphObjectRendererTask";
    }
    record(skipCreationOfDisabledPasses = false, additionalExecute) {
        this._checkParameters();
        // Make sure the renderList / particleSystemList are set when FrameGraphObjectRendererTask.isReady() is called!
        this._renderer.renderList = this.objectList.meshes;
        this._renderer.particleSystemList = this.objectList.particleSystems;
        const targetTextures = this._getTargetHandles();
        const depthEnabled = this._checkTextureCompatibility(targetTextures);
        this._resolveDanglingHandles(targetTextures);
        this._setLightsForShadow();
        const pass = this._frameGraph.addRenderPass(this.name);
        pass.setRenderTarget(targetTextures);
        pass.setRenderTargetDepth(this.depthTexture);
        pass.setExecuteFunc((context) => {
            this._renderer.renderList = this.objectList.meshes;
            this._renderer.particleSystemList = this.objectList.particleSystems;
            const renderTargetWrapper = pass.frameGraphRenderTarget.renderTargetWrapper;
            if (renderTargetWrapper) {
                renderTargetWrapper.resolveMSAAColors = this.resolveMSAAColors;
                renderTargetWrapper.resolveMSAADepth = this.resolveMSAADepth;
            }
            // The cast to "any" is to avoid an error in ES6 in case you don't import boundingBoxRenderer
            const boundingBoxRenderer = this.getBoundingBoxRenderer?.();
            const currentBoundingBoxMeshList = boundingBoxRenderer && boundingBoxRenderer.renderList.length > 0 ? boundingBoxRenderer.renderList.data.slice() : [];
            if (boundingBoxRenderer) {
                currentBoundingBoxMeshList.length = boundingBoxRenderer.renderList.length;
            }
            this._prepareRendering(context, depthEnabled);
            const camera = this._renderer.activeCamera;
            if (camera && camera.cameraRigMode !== 0 && !camera._renderingMultiview) {
                for (let index = 0; index < camera._rigCameras.length; index++) {
                    const rigCamera = camera._rigCameras[index];
                    rigCamera.rigParent = undefined; // for some reasons, ObjectRenderer uses the rigParent viewport if rigParent is defined (we want to use rigCamera.viewport instead)
                    this._renderer.activeCamera = rigCamera;
                    context.render(this._renderer, this._textureWidth, this._textureHeight);
                    rigCamera.rigParent = camera;
                }
                this._renderer.activeCamera = camera;
            }
            else {
                context.render(this._renderer, this._textureWidth, this._textureHeight);
            }
            additionalExecute?.(context);
            if (boundingBoxRenderer) {
                boundingBoxRenderer.renderList.data = currentBoundingBoxMeshList;
                boundingBoxRenderer.renderList.length = currentBoundingBoxMeshList.length;
            }
        });
        if (!skipCreationOfDisabledPasses) {
            const passDisabled = this._frameGraph.addRenderPass(this.name + "_disabled", true);
            passDisabled.setRenderTarget(targetTextures);
            passDisabled.setRenderTargetDepth(this.depthTexture);
            passDisabled.setExecuteFunc((_context) => { });
        }
        return pass;
    }
    dispose() {
        this._renderer.onBeforeRenderObservable.remove(this._onBeforeRenderObservable);
        this._renderer.onAfterRenderObservable.remove(this._onAfterRenderObservable);
        if (!this._externalObjectRenderer) {
            this._renderer.dispose();
        }
        super.dispose();
    }
    _resolveDanglingHandles(targetTextures) {
        if (targetTextures.length > 0) {
            this._frameGraph.textureManager.resolveDanglingHandle(this.outputTexture, targetTextures[0]);
        }
        if (this.depthTexture !== undefined) {
            this._frameGraph.textureManager.resolveDanglingHandle(this.outputDepthTexture, this.depthTexture);
        }
    }
    _checkParameters() {
        if (this.targetTexture === undefined || this.objectList === undefined || this.camera === undefined) {
            throw new Error(`FrameGraphObjectRendererTask ${this.name}: targetTexture, objectList, and camera are required`);
        }
    }
    _checkTextureCompatibility(targetTextures) {
        const className = this.getClassName();
        let outputTextureDescription = targetTextures.length > 0 ? this._frameGraph.textureManager.getTextureDescription(targetTextures[0]) : null;
        let depthEnabled = false;
        if (this.depthTexture !== undefined) {
            if (outputTextureDescription && this.depthTexture !== backbufferDepthStencilTextureHandle && targetTextures[0] === backbufferColorTextureHandle) {
                throw new Error(`${className} ${this.name}: the back buffer depth/stencil texture is the only depth texture allowed when the target is the back buffer color`);
            }
            const depthTextureDescription = this._frameGraph.textureManager.getTextureDescription(this.depthTexture);
            if (!outputTextureDescription) {
                outputTextureDescription = depthTextureDescription;
            }
            if (depthTextureDescription.options.samples !== outputTextureDescription.options.samples) {
                throw new Error(`${className} ${this.name}: the depth texture "${depthTextureDescription.options.labels?.[0] ?? "noname"}" (${depthTextureDescription.options.samples} samples) and the output texture "${outputTextureDescription.options.labels?.[0] ?? "noname"}" (${outputTextureDescription.options.samples} samples) must have the same number of samples`);
            }
            if (depthTextureDescription.size.width !== outputTextureDescription.size.width || depthTextureDescription.size.height !== outputTextureDescription.size.height) {
                throw new Error(`${className} ${this.name}: the depth texture (size: ${depthTextureDescription.size.width}x${depthTextureDescription.size.height}) and the target texture (size: ${outputTextureDescription.size.width}x${outputTextureDescription.size.height}) must have the same dimensions.`);
            }
            depthEnabled = true;
        }
        this._textureWidth = outputTextureDescription?.size.width ?? 1;
        this._textureHeight = outputTextureDescription?.size.height ?? 1;
        return depthEnabled;
    }
    _getTargetHandles() {
        return Array.isArray(this.targetTexture) ? this.targetTexture : [this.targetTexture];
    }
    _prepareRendering(context, depthEnabled) {
        context.setDepthStates(this.depthTest && depthEnabled, this.depthWrite && depthEnabled);
    }
    _setLightsForShadow() {
        const lightsForShadow = new Set();
        const shadowEnabled = new Map();
        if (this.shadowGenerators) {
            for (const shadowGeneratorTask of this.shadowGenerators) {
                const shadowGenerator = shadowGeneratorTask.shadowGenerator;
                const light = shadowGenerator.getLight();
                if (light.isEnabled() && light.shadowEnabled) {
                    lightsForShadow.add(light);
                    if (FrameGraphCascadedShadowGeneratorTask.IsCascadedShadowGenerator(shadowGeneratorTask)) {
                        light._shadowGenerators.set(shadowGeneratorTask.camera, shadowGenerator);
                    }
                    else {
                        light._shadowGenerators.set(null, shadowGenerator);
                    }
                }
            }
        }
        this._renderer.onBeforeRenderObservable.remove(this._onBeforeRenderObservable);
        this._onBeforeRenderObservable = this._renderer.onBeforeRenderObservable.add(() => {
            for (let i = 0; i < this._scene.lights.length; i++) {
                const light = this._scene.lights[i];
                if (!light.setShadowProjectionMatrix) {
                    continue; // Ignore lights that cannot cast shadows
                }
                shadowEnabled.set(light, light.shadowEnabled);
                light.shadowEnabled = !this.disableShadows && lightsForShadow.has(light);
            }
        });
        this._renderer.onAfterRenderObservable.remove(this._onAfterRenderObservable);
        this._onAfterRenderObservable = this._renderer.onAfterRenderObservable.add(() => {
            for (let i = 0; i < this._scene.lights.length; i++) {
                const light = this._scene.lights[i];
                if (!light.setShadowProjectionMatrix) {
                    continue; // Ignore lights that cannot cast shadows
                }
                light.shadowEnabled = shadowEnabled.get(light);
            }
        });
    }
}
//# sourceMappingURL=objectRendererTask.js.map