import { linearToGamma } from '../shaders/misc.js';
import { PostEffectLayer } from './posteffect.js';
import { MaterialVaryingFlags } from '../values.js';
import { fetchSampler } from '../utility/misc.js';
import { DRef } from '@zephyr3d/base';

/**
 * Post processing compositor
 * @public
 */ class Compositor {
    /** @internal */ _postEffects;
    /** @internal */ _finalFramebuffer;
    /** @internal */ _prevInputTexture;
    /** @internal */ _prevFrameBuffer;
    /** @internal */ _textureFormat;
    /** @internal */ static _blitProgram = null;
    /** @internal */ static _blitBindgroup = null;
    /** @internal */ static _blitRenderStates = null;
    /** @internal */ static _blitVertexLayout = null;
    /**
   * Creates an instance of Compositor
   */ constructor(){
        this._postEffects = [];
        this._postEffects[PostEffectLayer.opaque] = [];
        this._postEffects[PostEffectLayer.transparent] = [];
        this._postEffects[PostEffectLayer.end] = [];
        this._finalFramebuffer = null;
        this._prevInputTexture = null;
        this._prevFrameBuffer = null;
        this._textureFormat = 'rgba16f';
    }
    /** @internal */ requireLinearDepth(ctx) {
        for (const list of this._postEffects){
            for (const postEffect of list){
                if (postEffect.get().requireLinearDepthTexture(ctx)) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
   * Adds a posteffect
   *
   * @param postEffect - The post effect to add
   * @param opaque - true if the post effect should be applied after the opaque pass and before the transparent pass, otherwise the post effect should be applied after the transparent pass
   */ appendPostEffect(postEffect) {
        if (postEffect) {
            for (const list of this._postEffects){
                if (list.findIndex((val)=>val.get() === postEffect) >= 0) {
                    console.error(`Posteffect cannot be added to same compositor multiple times`);
                    return;
                }
            }
            this._postEffects[postEffect.layer].push(new DRef(postEffect));
        }
    }
    /**
   * Removes a posteffect that was previously added
   *
   * @param postEffect - The posteffect to be remove.
   */ removePostEffect(postEffect) {
        for (const list of this._postEffects){
            const index = list.findIndex((val)=>val.get() === postEffect);
            if (index >= 0) {
                list[index].dispose();
                list.splice(index, 1);
                return;
            }
        }
    }
    /**
   * Removes all post effects
   */ clear() {
        for (const list of this._postEffects){
            for (const p of list){
                p.dispose();
            }
            list.splice(0, list.length);
        }
    }
    /** @internal */ begin(ctx) {
        const device = ctx.device;
        this._textureFormat = ctx.camera.HDR && device.getDeviceCaps().textureCaps.supportHalfFloatColorBuffer ? 'rgba16f' : 'rgba8unorm';
        this._finalFramebuffer = device.getFramebuffer();
        const ssr = !!(ctx.materialFlags & MaterialVaryingFlags.SSR_STORE_ROUGHNESS);
        if (this._postEffects.every((list)=>list.length === 0)) {
            return;
        }
        const depth = this._finalFramebuffer?.getDepthAttachment();
        const w = depth ? depth.width : device.getDrawingBufferWidth();
        const h = depth ? depth.height : device.getDrawingBufferHeight();
        //const fmt2: TextureFormat = ssr ? (device.type === 'webgl' ? format : 'rgba8unorm') : format;
        const tmpFramebuffer = device.pool.fetchTemporalFramebuffer(true, w, h, ssr ? [
            this._textureFormat,
            ctx.SSRRoughnessTexture,
            ctx.SSRNormalTexture
        ] : this._textureFormat, depth ?? ctx.depthFormat);
        device.setFramebuffer(tmpFramebuffer);
    }
    /** @internal */ drawPostEffects(ctx, layer, sceneDepthTexture) {
        const postEffects = this._postEffects[layer];
        if (postEffects.length > 0) {
            const device = ctx.device;
            const inputFramebuffer = device.getFramebuffer();
            const inputTexture = inputFramebuffer.getColorAttachments()[0];
            let tmpTexture = null;
            for(let i = 0; i < postEffects.length; i++){
                const postEffect = postEffects[i].get();
                if (!postEffect.enabled) {
                    continue;
                }
                if (this._prevFrameBuffer) {
                    device.pool.releaseFrameBuffer(this._prevFrameBuffer);
                    this._prevFrameBuffer = null;
                }
                const isLast = this.isLastPostEffect(layer, i);
                const finalEffect = isLast && (!postEffect.requireDepthAttachment(ctx) || !!this._finalFramebuffer);
                if (finalEffect) {
                    device.setFramebuffer(this._finalFramebuffer);
                } else {
                    this._prevFrameBuffer = device.pool.fetchTemporalFramebuffer(false, inputFramebuffer.getWidth(), inputFramebuffer.getHeight(), this._textureFormat, inputFramebuffer.getDepthAttachment() ?? null);
                    device.setFramebuffer(this._prevFrameBuffer);
                }
                const inputColorTexture = tmpTexture ?? inputTexture;
                postEffect.apply(ctx, inputColorTexture, sceneDepthTexture, !device.getFramebuffer());
                if (this._prevInputTexture) {
                    device.pool.releaseTexture(this._prevInputTexture);
                    this._prevInputTexture = null;
                }
                if (this._prevFrameBuffer) {
                    tmpTexture = this._prevFrameBuffer.getColorAttachments()[0];
                    device.pool.retainTexture(tmpTexture);
                    this._prevInputTexture = tmpTexture;
                }
            }
        }
    }
    /** @internal */ end(ctx) {
        const device = ctx.device;
        if (device.getFramebuffer() !== this._finalFramebuffer) {
            const srcTex = device.getFramebuffer().getColorAttachments()[0];
            device.setFramebuffer(this._finalFramebuffer);
            device.setViewport(null);
            device.setScissor(null);
            Compositor._blit(device, srcTex, !this._finalFramebuffer);
        }
        if (this._prevInputTexture) {
            device.pool.releaseTexture(this._prevInputTexture);
            this._prevInputTexture = null;
        }
        if (this._prevFrameBuffer) {
            device.pool.releaseFrameBuffer(this._prevFrameBuffer);
            this._prevFrameBuffer = null;
        }
    }
    /** @internal */ isLastPostEffect(layer, index) {
        for(let i = layer; i < this._postEffects.length; i++){
            const start = i === layer ? index + 1 : 0;
            for(let j = start; j < this._postEffects[i].length; j++){
                if (this._postEffects[i][j].get().enabled) {
                    return false;
                }
            }
        }
        return true;
    }
    /** @internal */ static _blit(device, srcTex, srgbOutput) {
        if (!this._blitProgram) {
            this._blitProgram = device.buildRenderProgram({
                vertex (pb) {
                    this.$inputs.pos = pb.vec2().attrib('position');
                    this.$outputs.uv = pb.vec2();
                    this.flip = pb.int().uniform(0);
                    pb.main(function() {
                        this.$builtins.position = pb.vec4(this.$inputs.pos, 0, 1);
                        this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
                        this.$if(pb.notEqual(this.flip, 0), function() {
                            this.$builtins.position.y = pb.neg(this.$builtins.position.y);
                        });
                    });
                },
                fragment (pb) {
                    this.srcTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
                    this.srgbOutput = pb.int().uniform(0);
                    this.$outputs.outColor = pb.vec4();
                    pb.main(function() {
                        this.$outputs.outColor = pb.textureSample(this.srcTex, this.$inputs.uv);
                        this.$if(pb.notEqual(this.srgbOutput, 0), function() {
                            this.$outputs.outColor = pb.vec4(linearToGamma(this, this.$outputs.outColor.rgb), 1);
                        });
                    });
                }
            });
            this._blitProgram.name = '@Compositor_blit';
            this._blitBindgroup = device.createBindGroup(this._blitProgram.bindGroupLayouts[0]);
            this._blitVertexLayout = device.createVertexLayout({
                vertexBuffers: [
                    {
                        buffer: device.createVertexBuffer('position_f32x2', new Float32Array([
                            -1,
                            -1,
                            1,
                            -1,
                            -1,
                            1,
                            1,
                            1
                        ]))
                    }
                ]
            });
            this._blitRenderStates = device.createRenderStateSet();
            this._blitRenderStates.useRasterizerState().setCullMode('none');
            this._blitRenderStates.useDepthState().enableTest(false).enableWrite(false);
        }
        this._blitBindgroup.setTexture('srcTex', srcTex, fetchSampler('clamp_nearest_nomip'));
        this._blitBindgroup.setValue('srgbOutput', srgbOutput ? 1 : 0);
        this._blitBindgroup.setValue('flip', device.type === 'webgpu' && !!device.getFramebuffer() ? 1 : 0);
        device.setRenderStates(this._blitRenderStates);
        device.setProgram(this._blitProgram);
        device.setBindGroup(0, this._blitBindgroup);
        device.setVertexLayout(this._blitVertexLayout);
        device.draw('triangle-strip', 0, 4);
    }
}

export { Compositor };
//# sourceMappingURL=compositor.js.map
