import { drawFullscreenQuad } from './fullscreenquad.js';
import { Disposable, Vector4 } from '@zephyr3d/base';

/**
 * Weighted-blended OIT renderer.
 *
 * @remarks
 * The weighed-blended OIT renderer supports both WebGL, WebGL2 and WebGPU device.
 *
 * @public
 */ class WeightedBlendedOIT extends Disposable {
    /** Type name of WeightedBlendedOIT */ static type = 'wb';
    static _compositeProgram;
    static _compositeBindGroup;
    static _compositeRenderStates;
    constructor(){
        super();
    }
    /**
   * {@inheritDoc OIT.getType}
   */ getType() {
        return WeightedBlendedOIT.type;
    }
    /**
   * {@inheritDoc OIT.supportDevice}
   */ supportDevice(_deviceType) {
        return true;
    }
    /**
   * {@inheritDoc OIT.wantsPremultipliedAlpha}
   */ wantsPremultipliedAlpha() {
        return false;
    }
    /**
   * {@inheritDoc OIT.begin}
   */ begin(_ctx) {
        return 1;
    }
    /**
   * {@inheritDoc OIT.end}
   */ end(_ctx) {
        return;
    }
    /**
   * {@inheritDoc OIT.setupFragmentOutput}
   */ setupFragmentOutput(scope) {
        const pb = scope.$builder;
        scope.$outputs.outColor = pb.vec4();
        scope.$outputs.outAlpha = pb.getDevice().type === 'webgl' ? pb.vec4() : pb.float();
    }
    /**
   * {@inheritDoc OIT.beginPass}
   */ beginPass(ctx, _pass) {
        const device = ctx.device;
        const accumBuffer = this.getAccumFramebuffer(ctx, device);
        device.pushDeviceStates();
        device.setFramebuffer(accumBuffer);
        device.clearFrameBuffer(new Vector4(0, 0, 0, 1), null, null);
        return true;
    }
    /**
   * {@inheritDoc OIT.endPass}
   */ endPass(ctx, _pass) {
        const device = ctx.device;
        const accumBuffer = device.getFramebuffer();
        device.popDeviceStates();
        const accumTargets = accumBuffer.getColorAttachments();
        this.composite(ctx, device, accumTargets[0], accumTargets[1]);
    }
    /**
   * {@inheritDoc OIT.calculateHash}
   */ calculateHash() {
        return this.getType();
    }
    /**
   * {@inheritDoc OIT.applyUniforms}
   */ applyUniforms(_ctx, _bindGroup) {
        return;
    }
    /**
   * {@inheritDoc OIT.outputFragmentColor}
   */ outputFragmentColor(scope, color) {
        const pb = scope.$builder;
        pb.func('Z_WBOIT_depthWeight', [
            pb.float('z'),
            pb.float('a')
        ], function() {
            this.$return(pb.clamp(pb.mul(pb.pow(pb.add(pb.min(1, pb.mul(this.a, 10)), 0.01), 3), 1e8, pb.pow(pb.sub(1, pb.mul(this.z, 0.9)), 2)), 1e-2, 3e3));
        });
        pb.func('Z_WBOIT_output', [
            pb.vec4('color')
        ], function() {
            this.$l.w = this.Z_WBOIT_depthWeight(this.$builtins.fragCoord.z, this.color.a);
            this.$outputs[0] = pb.vec4(pb.mul(this.color.rgb, this.w), this.color.a);
            this.$outputs[1] = pb.getDevice().type === 'webgl' ? pb.vec4(pb.mul(this.color.a, this.w)) : pb.mul(this.color.a, this.w);
        });
        scope.Z_WBOIT_output(color);
        return true;
    }
    /**
   * {@inheritDoc OIT.setRenderStates}
   */ setRenderStates(rs) {
        const blendingState = rs.useBlendingState();
        blendingState.enable(true);
        blendingState.setBlendEquation('add', 'add');
        blendingState.setBlendFuncRGB('one', 'one');
        blendingState.setBlendFuncAlpha('zero', 'inv-src-alpha');
        const depthState = rs.useDepthState();
        depthState.enableWrite(false).enableTest(true);
    }
    /** @internal */ composite(ctx, device, accumColor, accumAlpha) {
        device.setProgram(WeightedBlendedOIT.getCompositeProgram(device));
        WeightedBlendedOIT._compositeBindGroup.setTexture('accumColorTex', accumColor);
        WeightedBlendedOIT._compositeBindGroup.setTexture('accumAlphaTex', accumAlpha);
        device.setBindGroup(0, WeightedBlendedOIT._compositeBindGroup);
        drawFullscreenQuad(WeightedBlendedOIT._compositeRenderStates);
    }
    /** @internal */ getAccumFramebuffer(ctx, device) {
        const width = ctx.depthTexture.width;
        const height = ctx.depthTexture.height;
        const accumColor = device.pool.fetchTemporalTexture2D(false, 'rgba16f', width, height, false);
        const accumAlpha = device.pool.fetchTemporalTexture2D(false, device.type === 'webgl' ? 'rgba16f' : 'r16f', width, height, false);
        const fb = device.pool.createTemporalFramebuffer(true, [
            accumColor,
            accumAlpha
        ], ctx.depthTexture);
        device.pool.releaseTexture(accumColor);
        device.pool.releaseTexture(accumAlpha);
        return fb;
    }
    /** @internal */ static getCompositeProgram(device) {
        if (!this._compositeProgram) {
            this._compositeProgram = device.buildRenderProgram({
                vertex (pb) {
                    this.$inputs.pos = pb.vec2().attrib('position');
                    this.$outputs.uv = pb.vec2();
                    pb.main(function() {
                        this.$builtins.position = pb.vec4(this.$inputs.pos, 1, 1);
                        this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
                        if (device.type === 'webgpu') {
                            this.$builtins.position.y = pb.neg(this.$builtins.position.y);
                        }
                    });
                },
                fragment (pb) {
                    this.$outputs.outColor = pb.vec4();
                    this.accumColorTex = pb.tex2D().uniform(0);
                    this.accumAlphaTex = pb.tex2D().uniform(0);
                    pb.main(function() {
                        this.$l.accumColor = pb.textureSample(this.accumColorTex, this.$inputs.uv);
                        this.$l.accumAlpha = pb.textureSample(this.accumAlphaTex, this.$inputs.uv);
                        this.$l.r = this.accumColor.a;
                        this.accumColor.a = this.accumAlpha.r;
                        this.$outputs.outColor = pb.vec4(pb.div(this.accumColor.rgb, pb.clamp(this.accumColor.a, 0.0001, 50000)), this.r);
                    });
                }
            });
            this._compositeProgram.name = '@WBOIT_Composite';
            this._compositeBindGroup = device.createBindGroup(this._compositeProgram.bindGroupLayouts[0]);
            this._compositeRenderStates = device.createRenderStateSet();
            this._compositeRenderStates.useBlendingState().enable(true).setBlendFuncRGB('inv-src-alpha', 'src-alpha').setBlendFuncAlpha('zero', 'one');
            this._compositeRenderStates.useDepthState().enableTest(false).enableWrite(false);
        }
        return this._compositeProgram;
    }
}

export { WeightedBlendedOIT };
//# sourceMappingURL=weightedblended_oit.js.map
