/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module WebGL
 */
import { assert, Id64 } from "@itwin/core-bentley";
import { PackedFeature } from "@itwin/core-common";
import { isFeatureHilited } from "./FeatureOverrides";
import { System } from "./System";
import { TechniqueFlags } from "./TechniqueFlags";
/* eslint-disable no-restricted-syntax */
/** @internal */
export class ShaderProgramParams {
    _target;
    _renderPass = 255 /* RenderPass.None */;
    get target() {
        assert(undefined !== this._target);
        return this._target;
    }
    get renderPass() { return this._renderPass; }
    get projectionMatrix() { return this.target.uniforms.getProjectionMatrix32(this.isViewCoords); }
    bindProjectionMatrix(uniform) { this.target.uniforms.bindProjectionMatrix(uniform, this.isViewCoords); }
    get isViewCoords() { return 13 /* RenderPass.ViewOverlay */ === this.renderPass || 0 /* RenderPass.Background */ === this.renderPass; }
    get isOverlayPass() { return 12 /* RenderPass.WorldOverlay */ === this.renderPass || 13 /* RenderPass.ViewOverlay */ === this.renderPass; }
    get context() { return System.instance.context; }
    init(target, pass = 5 /* RenderPass.OpaqueGeneral */) {
        this._renderPass = pass;
        this._target = target;
    }
}
/** @internal */
export class DrawParams {
    _programParams;
    _geometry;
    get geometry() {
        assert(undefined !== this._geometry);
        return this._geometry;
    }
    get programParams() {
        assert(undefined !== this._programParams);
        return this._programParams;
    }
    get target() { return this.programParams.target; }
    get renderPass() { return this.programParams.renderPass; }
    get projectionMatrix() { return this.programParams.projectionMatrix; }
    get isViewCoords() {
        return this.programParams.isViewCoords || this.target.currentBranch.forceViewCoords;
    }
    get isOverlayPass() { return this.programParams.isOverlayPass; }
    get context() { return this.programParams.context; }
    init(programParams, geometry) {
        this._programParams = programParams;
        this._geometry = geometry;
    }
}
/** Represents a command to be executed within a RenderPass. The most common command is
 * to draw a primitive; others involve state changes such as pushing/popping transforms
 * and symbology overrides, which require that commands be executed in order.
 * @internal
 */
export var DrawOpCode;
(function (DrawOpCode) {
    DrawOpCode["Primitive"] = "drawPrimitive";
    DrawOpCode["PushBranch"] = "pushBranch";
    DrawOpCode["PopBranch"] = "popBranch";
    DrawOpCode["PushBatch"] = "pushBatch";
    DrawOpCode["PopBatch"] = "popBatch";
    DrawOpCode["PushState"] = "pushState";
    DrawOpCode["PushClip"] = "pushClip";
    DrawOpCode["PopClip"] = "popClip";
})(DrawOpCode || (DrawOpCode = {}));
/** @internal */
export class PopBatchCommand {
    opcode = "popBatch";
    constructor() { }
    static instance = new PopBatchCommand();
    execute(exec) {
        exec.target.popBatch();
    }
}
/** @internal */
export class PushBatchCommand {
    batch;
    opcode = "pushBatch";
    constructor(batch) {
        this.batch = batch;
    }
    execute(exec) {
        exec.target.pushBatch(this.batch);
    }
}
/** @internal */
export class PushStateCommand {
    state;
    opcode = "pushState";
    constructor(state) {
        this.state = state;
    }
    execute(exec) {
        exec.target.pushState(this.state);
    }
}
/** @internal */
export class PushBranchCommand {
    branch;
    opcode = "pushBranch";
    constructor(branch) {
        this.branch = branch;
    }
    execute(exec) {
        exec.pushBranch(this.branch);
    }
}
/** @internal */
export class PopBranchCommand {
    opcode = "popBranch";
    constructor() { }
    static instance = new PopBranchCommand();
    execute(exec) {
        exec.popBranch();
    }
}
/** @internal */
export class PushClipCommand {
    clip;
    opcode = "pushClip";
    constructor(clip) {
        this.clip = clip;
    }
    execute(exec) {
        exec.target.uniforms.branch.clipStack.push(this.clip);
    }
}
/** @internal */
export class PopClipCommand {
    opcode = "popClip";
    constructor() { }
    static instance = new PopClipCommand();
    execute(exec) {
        exec.target.uniforms.branch.clipStack.pop();
    }
}
/** @internal */
export class PrimitiveCommand {
    primitive;
    opcode = "drawPrimitive";
    constructor(primitive) {
        this.primitive = primitive;
    }
    static _scratchTechniqueFlags = new TechniqueFlags();
    execute(exec) {
        if (exec.target.isGeometryOutsideActiveVolume(this.primitive.cachedGeometry))
            return;
        const techniqueId = this.primitive.techniqueId;
        if (-1 /* TechniqueId.Invalid */ === techniqueId)
            return;
        const target = exec.target;
        const thematic = this.primitive.cachedGeometry.supportsThematicDisplay && target.wantThematicDisplay;
        const shadowable = (techniqueId === 0 /* TechniqueId.Surface */ || techniqueId === 7 /* TechniqueId.RealityMesh */) && target.solarShadowMap.isReady && target.currentViewFlags.shadows && !thematic;
        const isShadowable = shadowable ? 1 /* IsShadowable.Yes */ : 0 /* IsShadowable.No */;
        let isThematic = thematic ? 1 /* IsThematic.Yes */ : 0 /* IsThematic.No */;
        const isClassified = (undefined !== target.currentPlanarClassifierOrDrape || undefined !== target.activeVolumeClassifierTexture) ? 1 /* IsClassified.Yes */ : 0 /* IsClassified.No */;
        const isInstanced = this.primitive.isInstanced ? 1 /* IsInstanced.Yes */ : 0 /* IsInstanced.No */;
        const isAnimated = this.primitive.hasAnimation ? 1 /* IsAnimated.Yes */ : 0 /* IsAnimated.No */;
        // Point clouds do not support hillshade or slope mode for thematic display.
        if (isThematic && (undefined !== this.primitive.cachedGeometry.asPointCloud) && (target.uniforms.thematic.wantSlopeMode || target.uniforms.thematic.wantHillShadeMode))
            isThematic = 0 /* IsThematic.No */;
        const wiremesh = target.currentViewFlags.wiremesh && (techniqueId === 0 /* TechniqueId.Surface */ || techniqueId === 7 /* TechniqueId.RealityMesh */);
        const isWiremesh = wiremesh ? 1 /* IsWiremesh.Yes */ : 0 /* IsWiremesh.No */;
        const flags = PrimitiveCommand._scratchTechniqueFlags;
        const posType = this.primitive.cachedGeometry.usesQuantizedPositions ? "quantized" : "unquantized";
        const enableAtmosphere = target.wantAtmosphere ? 1 /* EnableAtmosphere.Yes */ : 0 /* EnableAtmosphere.No */;
        flags.init(target, exec.renderPass, isInstanced, isAnimated, isClassified, isShadowable, isThematic, isWiremesh, posType, enableAtmosphere);
        const technique = target.techniques.getTechnique(techniqueId);
        const program = technique.getShader(flags);
        if (exec.setProgram(program))
            exec.target.compositor.drawPrimitive(this.primitive, exec, program.outputsToPick);
    }
    get hasFeatures() { return this.primitive.hasFeatures; }
    get renderOrder() { return this.primitive.renderOrder; }
    getPass(target) {
        return this.primitive.getPass(target);
    }
}
/** Extracts the commands for rendering the flashed classifier (if any) from the by-index set of volume classifier commands.
 * NB: Cmds will be sets of some pushes, a primitive, and then some pops (equal to number of pushes).
 * The primitive should be right in the middle of a set.  We need to find the set which matches the flashID.
 * @internal
 */
export function extractFlashedVolumeClassifierCommands(flashedId, cmds, numCmdsPerClassifier) {
    if (!Id64.isValid(flashedId) || 0 === numCmdsPerClassifier)
        return undefined;
    const firstPrim = (numCmdsPerClassifier - 1) / 2;
    for (let i = firstPrim; i < cmds.length; i += numCmdsPerClassifier) {
        assert("drawPrimitive" === cmds[i].opcode, "Command list not configured as expected.");
        const pc = cmds[i];
        const surface = pc.primitive.cachedGeometry.asSurface;
        if (undefined !== surface && undefined !== surface.mesh.uniformFeatureId) {
            let j = i - 1;
            while (j >= 0 && "pushBatch" !== cmds[j].opcode) // Find batch for this primitive
                j--;
            if (j < 0)
                continue;
            const pushBatch = cmds[j];
            const elemId = pushBatch.batch.featureTable.findElementId(surface.mesh.uniformFeatureId);
            if (undefined !== elemId && elemId === flashedId) {
                return cmds.slice(i - firstPrim, i + firstPrim + 1);
            }
        }
    }
    return undefined;
}
const scratchFeature = PackedFeature.create();
/** @internal */
export function extractHilitedVolumeClassifierCommands(hilites, cmds) {
    // TODO: This could really be done at the time the HiliteClassification render pass commands are being generated
    //       by just not putting the ones which are not hilited into the ClassificationHilite command list.
    const result = [];
    let batch;
    for (const cmd of cmds) {
        switch (cmd.opcode) {
            case "popBranch":
                if (result.length > 0 && "pushBranch" === result[result.length - 1].opcode) {
                    result.pop(); // remove empty push/pop pairs
                    continue;
                }
                break;
            case "popBatch":
                batch = undefined;
                if (result.length > 0 && "pushBatch" === result[result.length - 1].opcode) {
                    result.pop(); // remove empty push/pop pairs
                    continue;
                }
                break;
            case "pushBatch":
                batch = cmd.batch;
                break;
            case "drawPrimitive":
                if (undefined !== batch) {
                    // Skip any primitives that are not hilited.
                    const surface = cmd.primitive.cachedGeometry.asSurface;
                    if (undefined === surface || undefined === surface.mesh.uniformFeatureId)
                        continue;
                    const feature = batch.featureTable.getPackedFeature(surface.mesh.uniformFeatureId, scratchFeature);
                    if (undefined === feature || !isFeatureHilited(feature, hilites, hilites.models.hasId(Id64.fromUint32PairObject(feature.modelId))))
                        continue;
                    break;
                }
        }
        result.push(cmd);
    }
    return result;
}
//# sourceMappingURL=DrawCommand.js.map