import { applyToneMappingPreset, bloomPixels, chromaticAberrationPixels, colorGradePixels, depthOfFieldPixels, filmGrainPixels, fxaaPixels, motionBlurPixels, outlinePixels, ssaoPixels, ssrPixels, taaPixels, toneMapPixels, writePostProcessPixels } from "../PostProcessPass.js";
import { RenderDeviceError } from "../RenderDevice.js";
import {} from "../Texture.js";
export class PostProcessComposer {
    device;
    label;
    width;
    height;
    passes = [];
    pingPongTargets;
    disposed = false;
    lastDiagnostics;
    constructor(options) {
        this.device = options.device;
        this.label = options.label ?? "postprocess-composer";
        validateComposerSize(options.width, options.height);
        this.width = options.width;
        this.height = options.height;
        this.pingPongTargets = this.createPingPongTargets(options.format ?? "rgba8");
        this.lastDiagnostics = this.createDiagnostics([], false, null);
    }
    addPass(pass) {
        this.assertAlive();
        this.passes.push(pass);
        return this;
    }
    setPasses(passes) {
        this.assertAlive();
        this.passes = [...passes];
        return this;
    }
    clearPasses() {
        this.assertAlive();
        this.passes = [];
        return this;
    }
    resize(width, height) {
        this.assertAlive();
        validateComposerSize(width, height);
        if (width === this.width && height === this.height)
            return;
        for (const target of this.pingPongTargets)
            target.dispose();
        this.width = width;
        this.height = height;
        this.pingPongTargets = this.createPingPongTargets("rgba8");
        this.lastDiagnostics = this.createDiagnostics([], false, null);
    }
    render(options) {
        this.assertAlive();
        this.validateSource(options.source);
        if (options.target)
            this.validateTarget(options.target);
        const passes = options.passes ? [...options.passes] : [...this.passes];
        let current = options.source;
        if (passes.length === 0) {
            this.device.setRenderTarget(current);
            writePostProcessPixels(this.device, current, options.target, this.device.readPixels(0, 0, current.width, current.height));
            this.lastDiagnostics = this.createDiagnostics([], !options.target, options.target?.label ?? null);
            return this.lastDiagnostics;
        }
        for (let index = 0; index < passes.length; index += 1) {
            const pass = passes[index];
            const target = index === passes.length - 1 ? options.target : this.pingPongTargets[index % 2];
            this.device.setRenderTarget(current);
            const pixels = this.device.readPixels(0, 0, current.width, current.height);
            const output = executeComposerPass(pass, pixels, current.width, current.height);
            writePostProcessPixels(this.device, current, target, output);
            if (target)
                current = target;
        }
        this.lastDiagnostics = this.createDiagnostics(passes.map((pass) => pass.name), !options.target, options.target?.label ?? null);
        return this.lastDiagnostics;
    }
    getDiagnostics() {
        return this.lastDiagnostics;
    }
    dispose() {
        if (this.disposed)
            return;
        for (const target of this.pingPongTargets)
            target.dispose();
        this.disposed = true;
    }
    createPingPongTargets(format) {
        return [
            this.device.createRenderTarget({ width: this.width, height: this.height, label: `${this.label}-ping`, format, depth: false }),
            this.device.createRenderTarget({ width: this.width, height: this.height, label: `${this.label}-pong`, format, depth: false })
        ];
    }
    validateSource(source) {
        if (source.width !== this.width || source.height !== this.height) {
            throw new RenderDeviceError("PostProcessComposer source dimensions must match composer dimensions.", "POSTPROCESS_COMPOSER_SIZE_MISMATCH", {
                source: source.label,
                sourceWidth: source.width,
                sourceHeight: source.height,
                width: this.width,
                height: this.height
            });
        }
    }
    validateTarget(target) {
        if (target.width !== this.width || target.height !== this.height) {
            throw new RenderDeviceError("PostProcessComposer target dimensions must match composer dimensions.", "POSTPROCESS_COMPOSER_SIZE_MISMATCH", {
                target: target.label,
                targetWidth: target.width,
                targetHeight: target.height,
                width: this.width,
                height: this.height
            });
        }
    }
    createDiagnostics(lastPasses, presentedToBackbuffer, outputTargetLabel) {
        return {
            width: this.width,
            height: this.height,
            passCount: lastPasses.length,
            pingPongTargets: this.disposed ? 0 : 2,
            textureCount: this.disposed ? 0 : 2,
            lastPasses,
            presentedToBackbuffer,
            outputTargetLabel
        };
    }
    assertAlive() {
        if (this.disposed) {
            throw new RenderDeviceError("PostProcessComposer is disposed.", "DISPOSED_RESOURCE", { label: this.label });
        }
    }
}
export function createPostProcessCapabilityReport(device) {
    const capabilities = new Set(device.info.capabilities ?? []);
    const supportsRenderTargets = capabilities.has("render-targets");
    const supportsPresentation = capabilities.has("postprocess-presentation");
    const supportsPixelReadback = capabilities.has("pixel-readback");
    const supportsFloatReadback = capabilities.has("float-readback");
    const supportsHdrTargets = capabilities.has("hdr-render-targets");
    const supportsRendererOwnedDepthEffects = capabilities.has("depth-textures");
    const unsupportedEffects = [];
    if (!supportsRenderTargets) {
        unsupportedEffects.push({
            name: "composer-render-target-chain",
            reason: "Postprocess composer requires backend render-target support.",
            requiredCapability: "render-targets"
        });
    }
    if (!supportsPresentation) {
        unsupportedEffects.push({
            name: "backbuffer-presentation",
            reason: "Final postprocess presentation requires a backend presentation path.",
            requiredCapability: "postprocess-presentation"
        });
    }
    if (!supportsPixelReadback) {
        unsupportedEffects.push({
            name: "cpu-pixel-postprocess",
            reason: "Current public composer kernels require byte pixel readback.",
            requiredCapability: "pixel-readback"
        });
    }
    if (!supportsRendererOwnedDepthEffects) {
        unsupportedEffects.push({
            name: "renderer-owned-depth-postprocess",
            reason: "Renderer-owned depth of field, SSAO, contact shadow, and SSR require sampleable depth render targets.",
            requiredCapability: "depth-textures"
        });
    }
    if (!supportsHdrTargets) {
        unsupportedEffects.push({
            name: "hdr-render-target-postprocess",
            reason: "HDR postprocess targets require backend HDR render-target support.",
            requiredCapability: "hdr-render-targets"
        });
    }
    if (!supportsFloatReadback) {
        unsupportedEffects.push({
            name: "hdr-float-readback-postprocess",
            reason: "HDR bloom and tone-map readback require float render-target readback.",
            requiredCapability: "float-readback"
        });
    }
    unsupportedEffects.push({
        name: "smaa",
        reason: "SMAA is not implemented in the public A3D postprocess pass catalog; use FXAA or TAA."
    });
    return {
        backend: device.info.backend,
        supportedEffects: [
            "bloom",
            "tone-mapping",
            "tone-mapping-preset",
            "color-grade",
            "chromatic-aberration",
            "film-grain",
            "depth-of-field-with-depth-binding",
            "motion-blur",
            "ssao-with-depth-binding",
            "ssr-with-depth-binding",
            "taa",
            "outline",
            "fxaa",
            "stereo-anaglyph",
            "stereo-parallax-barrier"
        ],
        unsupportedEffects,
        supportsRenderTargets,
        supportsPresentation,
        supportsPixelReadback,
        supportsFloatReadback,
        supportsHdrTargets,
        supportsRendererOwnedDepthEffects
    };
}
function executeComposerPass(pass, pixels, width, height) {
    switch (pass.name) {
        case "bloom":
            return bloomPixels(pixels, width, height, pass.options).pixels;
        case "tone-mapping":
            return toneMapPixels(pixels, width, height, pass.options).pixels;
        case "tone-mapping-preset":
            return applyToneMappingPreset(pixels, width, height, pass.preset, pass.options).pixels;
        case "color-grade":
            return colorGradePixels(pixels, width, height, pass.options).pixels;
        case "chromatic-aberration":
            return chromaticAberrationPixels(pixels, width, height, pass.options).pixels;
        case "film-grain":
            return filmGrainPixels(pixels, width, height, pass.options).pixels;
        case "depth-of-field":
            return depthOfFieldPixels(pixels, width, height, pass.options).pixels;
        case "motion-blur":
            return motionBlurPixels(pixels, width, height, pass.options).pixels;
        case "ssao":
            return ssaoPixels(pixels, width, height, pass.options).pixels;
        case "ssr":
            return ssrPixels(pixels, width, height, pass.options).pixels;
        case "taa":
            return taaPixels(pixels, width, height, pass.options).pixels;
        case "outline":
            return outlinePixels(pixels, width, height, pass.options).pixels;
        case "fxaa":
            return fxaaPixels(pixels, width, height, pass.options).pixels;
    }
}
function validateComposerSize(width, height) {
    if (!Number.isInteger(width) || width <= 0 || !Number.isInteger(height) || height <= 0) {
        throw new RenderDeviceError("PostProcessComposer dimensions must be positive integers.", "INVALID_RENDER_TARGET_SIZE", { width, height });
    }
}
//# sourceMappingURL=EffectComposer.js.map