"use strict";
/*---------------------------------------------------------------------------------------------
* 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
 */
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OffScreenTarget = exports.OnScreenTarget = exports.Target = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const ViewRect_1 = require("../../common/ViewRect");
const ImageUtil_1 = require("../../common/ImageUtil");
const Pixel_1 = require("../Pixel");
const RenderPlan_1 = require("../RenderPlan");
const RenderTarget_1 = require("../RenderTarget");
const BranchState_1 = require("./BranchState");
const CachedGeometry_1 = require("./CachedGeometry");
const ColorInfo_1 = require("./ColorInfo");
const DrawCommand_1 = require("./DrawCommand");
const FrameBuffer_1 = require("./FrameBuffer");
const GL_1 = require("./GL");
const Graphic_1 = require("./Graphic");
const IModelFrameLifecycle_1 = require("./IModelFrameLifecycle");
const PlanarClassifier_1 = require("./PlanarClassifier");
const Primitive_1 = require("./Primitive");
const RenderState_1 = require("./RenderState");
const SceneCompositor_1 = require("./SceneCompositor");
const ScratchDrawParams_1 = require("./ScratchDrawParams");
const ShaderProgram_1 = require("./ShaderProgram");
const Sync_1 = require("./Sync");
const System_1 = require("./System");
const TargetUniforms_1 = require("./TargetUniforms");
const Texture_1 = require("./Texture");
const TargetGraphics_1 = require("./TargetGraphics");
const VisibleTileFeatures_1 = require("./VisibleTileFeatures");
const FrameStats_1 = require("../FrameStats");
const AnimationNodeId_1 = require("../../common/internal/render/AnimationNodeId");
const Symbols_1 = require("../../common/internal/Symbols");
function swapImageByte(image, i0, i1) {
    const tmp = image.data[i0];
    image.data[i0] = image.data[i1];
    image.data[i1] = tmp;
}
class EmptyHiliteSet {
    constructor() {
        this.isEmpty = true;
        this.modelSubCategoryMode = "union";
        this.elements = this.subcategories = this.models = new core_bentley_1.Id64.Uint32Set();
    }
}
/** @internal */
class Target extends RenderTarget_1.RenderTarget {
    get shadowFrustum() {
        const map = this.solarShadowMap;
        return map.isEnabled && map.isReady ? map.frustum : undefined;
    }
    get debugControl() { return this; }
    get viewRect() {
        return this.renderRect;
    }
    constructor(rect) {
        super();
        this[_a] = undefined;
        this.graphics = new TargetGraphics_1.TargetGraphics();
        this._currPickExclusions = new core_bentley_1.Id64.Uint32Set();
        this._swapPickExclusions = new core_bentley_1.Id64.Uint32Set();
        this.pickExclusionsSyncTarget = { syncKey: Number.MIN_SAFE_INTEGER };
        this._hilites = new EmptyHiliteSet();
        this._hiliteSyncTarget = { syncKey: Number.MIN_SAFE_INTEGER };
        this._flashed = { lower: 0, upper: 0 };
        this._flashedId = core_bentley_1.Id64.invalid;
        this._flashIntensity = 0;
        this._dcAssigned = false;
        this.decorationsState = BranchState_1.BranchState.createForDecorations(); // Used when rendering view background and view/world overlays.
        this.uniforms = new TargetUniforms_1.TargetUniforms(this);
        this.renderRect = new ViewRect_1.ViewRect();
        this.ambientOcclusionSettings = core_common_1.AmbientOcclusion.Settings.defaults;
        this._wantAmbientOcclusion = false;
        this._batches = [];
        this.plan = (0, RenderPlan_1.createEmptyRenderPlan)();
        this._isReadPixelsInProgress = false;
        this._readPixelsSelector = Pixel_1.Pixel.Selector.None;
        this._drawNonLocatable = true;
        this._analysisFraction = 0;
        this._antialiasSamples = 1;
        this._screenSpaceEffects = [];
        this.isFadeOutActive = false;
        // RenderTargetDebugControl
        this.vcSupportIntersectingVolumes = false;
        this.drawForReadPixels = false;
        this.drawingBackgroundForReadPixels = false;
        this.primitiveVisibility = RenderTarget_1.PrimitiveVisibility.All;
        this.displayDrapeFrustum = false;
        this.displayMaskFrustum = false;
        this.displayRealityTilePreload = false;
        this.displayRealityTileRanges = false;
        this.logRealityTiles = false;
        this.displayNormalMaps = true;
        this.freezeRealityTiles = false;
        this._isDisposed = false;
        this._scratchRange = new core_geometry_1.Range3d();
        this._frameStatsCollector = new FrameStats_1.FrameStatsCollector();
        this._scratchTmpFrustum = new core_common_1.Frustum();
        this._scratchRectFrustum = new core_common_1.Frustum();
        this._renderCommands = this.uniforms.branch.createRenderCommands(this.uniforms.batch.state);
        this._overlayRenderState = new RenderState_1.RenderState();
        this._overlayRenderState.flags.depthMask = false;
        this._overlayRenderState.flags.blend = true;
        this._overlayRenderState.blend.setBlendFunc(GL_1.GL.BlendFactor.One, GL_1.GL.BlendFactor.OneMinusSrcAlpha);
        this._compositor = SceneCompositor_1.SceneCompositor.create(this); // compositor is created but not yet initialized... we are still undisposed
        this.renderRect = rect ? rect : new ViewRect_1.ViewRect(); // if the rect is undefined, expect that it will be updated dynamically in an OnScreenTarget
        if (undefined !== System_1.System.instance.antialiasSamples)
            this._antialiasSamples = System_1.System.instance.antialiasSamples;
        else
            this._antialiasSamples = (undefined !== System_1.System.instance.options.antialiasSamples ? System_1.System.instance.options.antialiasSamples : 1);
    }
    get compositor() { return this._compositor; }
    get isReadPixelsInProgress() { return this._isReadPixelsInProgress; }
    get readPixelsSelector() { return this._readPixelsSelector; }
    get drawNonLocatable() { return this._drawNonLocatable; }
    get techniques() { return this.renderSystem.techniques; }
    get hilites() { return this._hilites; }
    get hiliteSyncTarget() { return this._hiliteSyncTarget; }
    get pickExclusions() { return this._currPickExclusions; }
    get flashed() { return core_bentley_1.Id64.isValid(this._flashedId) ? this._flashed : undefined; }
    get flashedId() { return this._flashedId; }
    get flashIntensity() { return this._flashIntensity; }
    get analysisFraction() { return this._analysisFraction; }
    set analysisFraction(fraction) { this._analysisFraction = fraction; }
    get animationBranches() {
        return this._animationBranches;
    }
    set animationBranches(branches) {
        this.disposeAnimationBranches();
        this._animationBranches = branches;
    }
    disposeAnimationBranches() {
        this._animationBranches = undefined;
    }
    get antialiasSamples() { return this._antialiasSamples; }
    set antialiasSamples(numSamples) { this._antialiasSamples = numSamples; }
    get solarShadowMap() { return this.compositor.solarShadowMap; }
    get isDrawingShadowMap() { return this.solarShadowMap.isEnabled && this.solarShadowMap.isDrawing; }
    getPlanarClassifier(id) {
        return undefined !== this._planarClassifiers ? this._planarClassifiers.get(id) : undefined;
    }
    createPlanarClassifier(properties) {
        return PlanarClassifier_1.PlanarClassifier.create(properties, this);
    }
    getTextureDrape(id) {
        return undefined !== this._textureDrapes ? this._textureDrapes.get(id) : undefined;
    }
    getWorldDecorations(decs) {
        if (undefined === this._worldDecorations) {
            // Don't allow flags like monochrome etc to affect world decorations. Allow lighting in 3d only.
            const vf = new core_common_1.ViewFlags({
                renderMode: core_common_1.RenderMode.SmoothShade,
                clipVolume: false,
                whiteOnWhiteReversal: false,
                lighting: !this.is2d,
                shadows: false,
            });
            this._worldDecorations = new Graphic_1.WorldDecorations(vf);
        }
        this._worldDecorations.init(decs);
        return this._worldDecorations;
    }
    get currentBranch() { return this.uniforms.branch.top; }
    get currentViewFlags() { return this.currentBranch.viewFlags; }
    get currentTransform() { return this.currentBranch.transform; }
    get currentTransparencyThreshold() { return this.currentEdgeSettings.transparencyThreshold; }
    get currentEdgeSettings() { return this.currentBranch.edgeSettings; }
    get currentFeatureSymbologyOverrides() { return this.currentBranch.symbologyOverrides; }
    get currentPlanarClassifier() { return this.currentBranch.planarClassifier; }
    get currentlyDrawingClassifier() { return this._currentlyDrawingClassifier; }
    get currentTextureDrape() {
        const drape = this.currentBranch.textureDrape;
        return undefined !== drape && drape.isReady ? drape : undefined;
    }
    get currentPlanarClassifierOrDrape() {
        const drape = this.currentTextureDrape;
        return undefined === drape ? this.currentPlanarClassifier : drape;
    }
    modelToView(modelPt, result) {
        return this.uniforms.branch.modelViewMatrix.multiplyPoint3dQuietNormalize(modelPt, result);
    }
    get is2d() { return this.uniforms.frustum.is2d; }
    get is3d() { return !this.is2d; }
    get isDisposed() {
        return this.graphics.isDisposed
            && undefined === this._fbo
            && undefined === this._worldDecorations
            && undefined === this._planarClassifiers
            && undefined === this._textureDrapes
            && this._renderCommands.isEmpty
            && 0 === this._batches.length
            && this.uniforms.thematic.isDisposed
            && this._isDisposed;
    }
    allocateFbo() {
        if (this._fbo)
            return this._fbo;
        const rect = this.viewRect;
        const color = Texture_1.TextureHandle.createForAttachment(rect.width, rect.height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
        if (undefined === color)
            return undefined;
        const depth = System_1.System.instance.createDepthBuffer(rect.width, rect.height, 1);
        if (undefined === depth) {
            color.dispose();
            return undefined;
        }
        this._fbo = FrameBuffer_1.FrameBuffer.create([color], depth);
        if (undefined === this._fbo) {
            color.dispose();
            depth.dispose();
            return undefined;
        }
        return this._fbo;
    }
    disposeFbo() {
        if (!this._fbo)
            return;
        const tx = this._fbo.getColor(0);
        const db = this._fbo.depthBuffer;
        this._fbo = (0, core_bentley_1.dispose)(this._fbo);
        this._dcAssigned = false;
        // We allocated our framebuffer's color attachment, so must dispose of it too.
        (0, core_bentley_1.assert)(undefined !== tx);
        (0, core_bentley_1.dispose)(tx);
        // We allocated our framebuffer's depth attachment, so must dispose of it too.
        (0, core_bentley_1.assert)(undefined !== db);
        (0, core_bentley_1.dispose)(db);
    }
    dispose() {
        this.reset();
        this.disposeFbo();
        (0, core_bentley_1.dispose)(this._compositor);
        this._viewport = undefined;
        this._isDisposed = true;
    }
    pushBranch(branch) {
        this.uniforms.branch.pushBranch(branch);
    }
    pushState(state) {
        this.uniforms.branch.pushState(state);
    }
    popBranch() {
        this.uniforms.branch.pop();
    }
    pushViewClip() {
        this.uniforms.branch.pushViewClip();
    }
    popViewClip() {
        this.uniforms.branch.popViewClip();
    }
    /** @internal */
    isRangeOutsideActiveVolume(range) {
        return this.uniforms.branch.clipStack.isRangeClipped(range, this.currentTransform);
    }
    /** @internal */
    isGeometryOutsideActiveVolume(geom) {
        if (!this.uniforms.branch.clipStack.hasClip || this.uniforms.branch.clipStack.hasOutsideColor)
            return false;
        const range = geom.computeRange(this._scratchRange);
        return this.isRangeOutsideActiveVolume(range);
    }
    pushBatch(batch) {
        this.uniforms.batch.setCurrentBatch(batch);
    }
    popBatch() {
        this.uniforms.batch.setCurrentBatch(undefined);
    }
    addBatch(batch) {
        (0, core_bentley_1.assert)(this._batches.indexOf(batch) < 0);
        this._batches.push(batch);
    }
    onBatchDisposed(batch) {
        const index = this._batches.indexOf(batch);
        (0, core_bentley_1.assert)(index > -1);
        this._batches.splice(index, 1);
    }
    get wantAmbientOcclusion() {
        return this._wantAmbientOcclusion;
    }
    get wantThematicDisplay() {
        return this.currentViewFlags.thematicDisplay && this.is3d && undefined !== this.uniforms.thematic.thematicDisplay;
    }
    get wantAtmosphere() {
        return undefined !== this.plan.atmosphere;
    }
    get wantThematicSensors() {
        const thematic = this.plan.thematic;
        return this.wantThematicDisplay && undefined !== thematic && core_common_1.ThematicDisplayMode.InverseDistanceWeightedSensors === thematic.displayMode && thematic.sensorSettings.sensors.length > 0;
    }
    updateSolarShadows(context) {
        this.compositor.updateSolarShadows(context);
    }
    // ---- Implementation of RenderTarget interface ---- //
    get renderSystem() { return System_1.System.instance; }
    get planFraction() { return this.uniforms.frustum.planFraction; }
    get planFrustum() { return this.uniforms.frustum.planFrustum; }
    changeDecorations(decs) {
        this.graphics.decorations = decs;
    }
    changeScene(scene) {
        this.graphics.changeScene(scene);
        this.changeTextureDrapes(scene.textureDrapes);
        this.changePlanarClassifiers(scene.planarClassifiers);
        this.changeDrapesOrClassifiers(this._planarClassifiers, scene.planarClassifiers);
        this._planarClassifiers = scene.planarClassifiers;
        this.activeVolumeClassifierProps = scene.volumeClassifier?.classifier;
        this.activeVolumeClassifierModelId = scene.volumeClassifier?.modelId;
    }
    onBeforeRender(viewport, setSceneNeedRedraw) {
        this._viewport = viewport;
        IModelFrameLifecycle_1.IModelFrameLifecycle.onBeforeRender.raiseEvent({
            renderSystem: this.renderSystem,
            viewport,
            setSceneNeedRedraw,
        });
    }
    changeDrapesOrClassifiers(oldMap, newMap) {
        if (undefined === newMap) {
            if (undefined !== oldMap)
                for (const value of oldMap.values())
                    value.dispose();
            return;
        }
        if (undefined !== oldMap) {
            for (const entry of oldMap)
                if (newMap.get(entry[0]) !== entry[1])
                    entry[1].dispose();
        }
    }
    changeTextureDrapes(textureDrapes) {
        this.changeDrapesOrClassifiers(this._textureDrapes, textureDrapes);
        this._textureDrapes = textureDrapes;
    }
    changePlanarClassifiers(planarClassifiers) {
        this.changeDrapesOrClassifiers(this._planarClassifiers, planarClassifiers);
        this._planarClassifiers = planarClassifiers;
    }
    changeDynamics(dynamics) {
        this.graphics.dynamics = dynamics;
    }
    overrideFeatureSymbology(ovr) {
        this.uniforms.branch.overrideFeatureSymbology(ovr);
    }
    setHiliteSet(hilite) {
        this._hilites = hilite;
        (0, Sync_1.desync)(this._hiliteSyncTarget);
    }
    setFlashed(id, intensity) {
        if (id !== this._flashedId) {
            this._flashedId = id;
            this._flashed = core_bentley_1.Id64.getUint32Pair(id);
        }
        this._flashIntensity = intensity;
    }
    changeFrustum(newFrustum, newFraction, is3d) {
        this.uniforms.frustum.changeFrustum(newFrustum, newFraction, is3d);
    }
    changeRenderPlan(plan) {
        this.plan = plan;
        if (this._dcAssigned && plan.is3d !== this.is3d) {
            // changed the dimensionality of the Target. World decorations no longer valid.
            // (lighting is enabled or disabled based on 2d vs 3d).
            this._worldDecorations = (0, core_bentley_1.dispose)(this._worldDecorations);
            // Turn off shadows if switching from 3d to 2d
            if (!plan.is3d)
                this.updateSolarShadows(undefined);
        }
        if (plan.is3d !== this.decorationsState.is3d)
            this.decorationsState.changeRenderPlan(this.decorationsState.viewFlags, plan.is3d, undefined);
        if (!this.assignDC())
            return;
        this.isFadeOutActive = plan.isFadeOutActive;
        this.analysisStyle = plan.analysisStyle;
        this.analysisTexture = plan.analysisTexture;
        this.uniforms.branch.updateViewClip(plan.clip, plan.clipStyle);
        let vf = plan.viewFlags;
        if (!plan.is3d)
            vf = vf.withRenderMode(core_common_1.RenderMode.Wireframe);
        if (core_common_1.RenderMode.SmoothShade === vf.renderMode && plan.is3d && undefined !== plan.ao && vf.ambientOcclusion) {
            this._wantAmbientOcclusion = true;
            this.ambientOcclusionSettings = plan.ao;
        }
        else {
            this._wantAmbientOcclusion = false;
            vf = vf.with("ambientOcclusion", false);
        }
        this.uniforms.branch.changeRenderPlan(vf, plan.is3d, plan.hline);
        this.changeFrustum(plan.frustum, plan.fraction, plan.is3d);
        this.uniforms.thematic.update(this);
        this.uniforms.contours.update(this);
        this.uniforms.atmosphere.update(this);
        // NB: This must be done after changeFrustum() as some of the uniforms depend on the frustum.
        this.uniforms.updateRenderPlan(plan);
    }
    drawFrame(sceneMilSecElapsed) {
        (0, core_bentley_1.assert)(this.renderSystem.frameBufferStack.isEmpty);
        if (!this.assignDC())
            return;
        this.paintScene(sceneMilSecElapsed);
        this.drawOverlayDecorations();
        (0, core_bentley_1.assert)(this.renderSystem.frameBufferStack.isEmpty);
    }
    drawOverlayDecorations() { }
    /**
     * Invoked via Viewport.changeView() when the owning Viewport is changed to look at a different view.
     * Invoked via dispose() when the target is being destroyed.
     * The primary difference is that in the former case we retain the SceneCompositor.
     */
    reset() {
        this.graphics.dispose();
        this._worldDecorations = (0, core_bentley_1.dispose)(this._worldDecorations);
        (0, core_bentley_1.dispose)(this.uniforms.thematic);
        this.changePlanarClassifiers(undefined);
        this.changeTextureDrapes(undefined);
        this._renderCommands.clear();
        // Clear FeatureOverrides for this Target.
        // This may not be strictly necessary as the Target may still be viewing some of these batches, but better to clean up and recreate
        // than to leave unused in memory.
        for (const batch of this._batches)
            batch.onTargetDisposed(this);
        this._batches = [];
        this.disposeAnimationBranches();
        (0, ScratchDrawParams_1.freeDrawParams)();
        ShaderProgram_1.ShaderProgramExecutor.freeParams();
        Primitive_1.Primitive.freeParams();
    }
    get wantInvertBlackBackground() { return false; }
    computeEdgeWeight(pass, baseWeight) {
        return this.currentEdgeSettings.getWeight(pass, this.currentViewFlags) ?? baseWeight;
    }
    computeEdgeLineCode(pass, baseCode) {
        return this.currentEdgeSettings.getLineCode(pass, this.currentViewFlags) ?? baseCode;
    }
    computeEdgeColor(baseColor) {
        const color = this.currentEdgeSettings.getColor(this.currentViewFlags);
        return undefined !== color ? ColorInfo_1.ColorInfo.createUniform(color) : baseColor;
    }
    beginPerfMetricFrame(sceneMilSecElapsed, readPixels = false) {
        if (!readPixels || (this.performanceMetrics && !this.performanceMetrics.gatherCurPerformanceMetrics)) { // only capture readPixel data if in disp-perf-test-app
            if (this.renderSystem.isGLTimerSupported)
                this.renderSystem.glTimer.beginFrame();
            if (this.performanceMetrics)
                this.performanceMetrics.beginFrame(sceneMilSecElapsed);
        }
    }
    endPerfMetricFrame(readPixels = false) {
        if (!readPixels || (this.performanceMetrics && !this.performanceMetrics.gatherCurPerformanceMetrics)) { // only capture readPixel data if in disp-perf-test-app
            if (this.renderSystem.isGLTimerSupported)
                this.renderSystem.glTimer.endFrame();
            if (undefined === this.performanceMetrics)
                return;
            this.performanceMetrics.endOperation(); // End the 'CPU Total Time' operation
            this.performanceMetrics.completeFrameTimings(this._fbo);
        }
    }
    beginPerfMetricRecord(operation, readPixels = false) {
        if (!readPixels || (this.performanceMetrics && !this.performanceMetrics.gatherCurPerformanceMetrics)) { // only capture readPixel data if in disp-perf-test-app
            if (this.renderSystem.isGLTimerSupported)
                this.renderSystem.glTimer.beginOperation(operation);
            if (this.performanceMetrics)
                this.performanceMetrics.beginOperation(operation);
        }
    }
    endPerfMetricRecord(readPixels = false) {
        if (!readPixels || (this.performanceMetrics && !this.performanceMetrics.gatherCurPerformanceMetrics)) { // only capture readPixel data if in disp-perf-test-app
            if (this.renderSystem.isGLTimerSupported)
                this.renderSystem.glTimer.endOperation();
            if (this.performanceMetrics)
                this.performanceMetrics.endOperation();
        }
    }
    get frameStatsCollector() { return this._frameStatsCollector; }
    assignFrameStatsCollector(collector) { this._frameStatsCollector = collector; }
    paintScene(sceneMilSecElapsed) {
        if (!this._dcAssigned)
            return;
        this._frameStatsCollector.beginTime("totalFrameTime");
        this.beginPerfMetricFrame(sceneMilSecElapsed, this.drawForReadPixels);
        this.beginPerfMetricRecord("Begin Paint", this.drawForReadPixels);
        (0, core_bentley_1.assert)(undefined !== this._fbo);
        this._beginPaint(this._fbo);
        this.endPerfMetricRecord(this.drawForReadPixels);
        const gl = this.renderSystem.context;
        const rect = this.viewRect;
        gl.viewport(0, 0, rect.width, rect.height);
        // Set this to true to visualize the output of readPixels()...useful for debugging pick.
        if (this.drawForReadPixels) {
            this.beginReadPixels(Pixel_1.Pixel.Selector.Feature);
            this.compositor.drawForReadPixels(this._renderCommands, this.graphics.overlays, this.graphics.decorations?.worldOverlay, this.graphics.decorations?.viewOverlay);
            this.endReadPixels();
        }
        else {
            // After the Target is first created or any time its dimensions change, SceneCompositor.preDraw() must update
            // the compositor's textures, framebuffers, etc. This *must* occur before any drawing occurs.
            // SceneCompositor.draw() checks this, but solar shadow maps, planar classifiers, and texture drapes try to draw
            // before then. So do it now.
            this.compositor.preDraw();
            this._frameStatsCollector.beginTime("classifiersTime");
            this.beginPerfMetricRecord("Planar Classifiers");
            this.drawPlanarClassifiers();
            this.endPerfMetricRecord();
            this._frameStatsCollector.endTime("classifiersTime");
            this._frameStatsCollector.beginTime("shadowsTime");
            this.beginPerfMetricRecord("Shadow Maps");
            this.drawSolarShadowMap();
            this.endPerfMetricRecord();
            this._frameStatsCollector.endTime("shadowsTime");
            this.beginPerfMetricRecord("Texture Drapes");
            this.drawTextureDrapes();
            this.endPerfMetricRecord();
            this.beginPerfMetricRecord("Init Commands");
            this._renderCommands.initForRender(this.graphics);
            this.endPerfMetricRecord();
            this.compositor.draw(this._renderCommands); // scene compositor gets disposed and then re-initialized... target remains undisposed
            this._frameStatsCollector.beginTime("overlaysTime");
            this.beginPerfMetricRecord("Overlay Draws");
            this.beginPerfMetricRecord("World Overlays");
            this.drawPass(12 /* RenderPass.WorldOverlay */);
            this.endPerfMetricRecord();
            this.beginPerfMetricRecord("View Overlays");
            this.drawPass(13 /* RenderPass.ViewOverlay */);
            this.endPerfMetricRecord();
            this.endPerfMetricRecord(); // End "Overlay Draws"
            this._frameStatsCollector.endTime("overlaysTime");
        }
        // Apply screen-space effects. Note we do not reset this._isReadPixelsInProgress until *after* doing so, as screen-space effects only apply
        // during readPixels() if the effect shifts pixels from their original locations.
        this._frameStatsCollector.beginTime("screenspaceEffectsTime");
        this.beginPerfMetricRecord("Screenspace Effects", this.drawForReadPixels);
        this.renderSystem.screenSpaceEffects.apply(this);
        this.endPerfMetricRecord(this.drawForReadPixels);
        this._frameStatsCollector.endTime("screenspaceEffectsTime");
        // Reset the batch IDs in all batches drawn for this call.
        this.uniforms.batch.resetBatchState();
        this.beginPerfMetricRecord("End Paint", this.drawForReadPixels);
        this._endPaint();
        this.endPerfMetricRecord(this.drawForReadPixels);
        this.endPerfMetricFrame(this.drawForReadPixels);
        this._frameStatsCollector.endTime("totalFrameTime");
    }
    drawPass(pass) {
        this.renderSystem.applyRenderState(this.getRenderState(pass));
        this.techniques.execute(this, this._renderCommands.getCommands(pass), pass);
    }
    getRenderState(pass) {
        // the other passes are handled by SceneCompositor
        (0, core_bentley_1.assert)(13 /* RenderPass.ViewOverlay */ === pass || 12 /* RenderPass.WorldOverlay */ === pass);
        return this._overlayRenderState;
    }
    assignDC() {
        if (this._dcAssigned)
            return true;
        if (!this._assignDC())
            return false;
        const rect = this.viewRect;
        if (rect.width < 1 || rect.height < 1)
            return false;
        this.uniforms.viewRect.update(rect.width, rect.height);
        this._dcAssigned = true;
        return true;
    }
    readPixels(rect, selector, receiver, excludeNonLocatable, excludedElements) {
        if (!this.assignDC())
            return;
        // if (this.performanceMetrics && !this.performanceMetrics.gatherCurPerformanceMetrics)
        this.beginPerfMetricFrame(undefined, true);
        rect = this.cssViewRectToDeviceViewRect(rect);
        const gl = this.renderSystem.context;
        const viewRect = this.viewRect;
        gl.viewport(0, 0, viewRect.width, viewRect.height);
        // We can't reuse the previous frame's data for a variety of reasons, chief among them that some types of geometry (surfaces, translucent stuff) don't write
        // to the pick buffers and others we don't want - such as non-pickable decorations - do.
        // Render to an offscreen buffer so that we don't destroy the current color buffer.
        const resources = this.createOrReuseReadPixelResources(rect);
        if (resources === undefined) {
            receiver(undefined);
            return;
        }
        let result;
        this.renderSystem.frameBufferStack.execute(resources.fbo, true, false, () => {
            let updatedExclusions = false;
            if (excludedElements) {
                const swap = this._swapPickExclusions;
                swap.clear();
                for (const exclusion of excludedElements) {
                    swap.addId(exclusion);
                }
                if (!this._currPickExclusions.equals(swap)) {
                    this._swapPickExclusions = this._currPickExclusions;
                    this._currPickExclusions = swap;
                    updatedExclusions = true;
                    (0, Sync_1.desync)(this.pickExclusionsSyncTarget);
                }
            }
            this._drawNonLocatable = !excludeNonLocatable;
            result = this.readPixelsFromFbo(rect, selector);
            this._drawNonLocatable = true;
            if (updatedExclusions) {
                this._currPickExclusions.clear();
                (0, Sync_1.desync)(this.pickExclusionsSyncTarget);
            }
        });
        this.disposeOrReuseReadPixelResources(resources);
        receiver(result);
        // Reset the batch IDs in all batches drawn for this call.
        this.uniforms.batch.resetBatchState();
    }
    createOrReuseReadPixelResources(rect) {
        if (this._readPixelReusableResources !== undefined) {
            // To reuse a texture, we need it to be the same size or bigger than what we need
            if (this._readPixelReusableResources.texture.width >= rect.width && this._readPixelReusableResources.texture.height >= rect.height) {
                const resources = this._readPixelReusableResources;
                this._readPixelReusableResources = undefined;
                return resources;
            }
        }
        // Create a new texture/fbo
        const texture = Texture_1.TextureHandle.createForAttachment(rect.width, rect.height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
        if (texture === undefined)
            return undefined;
        const fbo = FrameBuffer_1.FrameBuffer.create([texture]);
        if (fbo === undefined) {
            (0, core_bentley_1.dispose)(texture);
            return undefined;
        }
        return { texture, fbo };
    }
    disposeOrReuseReadPixelResources({ texture, fbo }) {
        const maxReusableTextureSize = 256;
        const isTooBigToReuse = texture.width > maxReusableTextureSize || texture.height > maxReusableTextureSize;
        let reuseResources = !isTooBigToReuse;
        if (reuseResources && this._readPixelReusableResources !== undefined) {
            // Keep the biggest texture
            if (this._readPixelReusableResources.texture.width > texture.width && this._readPixelReusableResources.texture.height > texture.height) {
                reuseResources = false; // The current resources being reused are better
            }
            else {
                // Free memory of the current reusable resources before replacing them
                (0, core_bentley_1.dispose)(this._readPixelReusableResources.fbo);
                (0, core_bentley_1.dispose)(this._readPixelReusableResources.texture);
            }
        }
        if (reuseResources) {
            this._readPixelReusableResources = { texture, fbo };
        }
        else {
            (0, core_bentley_1.dispose)(fbo);
            (0, core_bentley_1.dispose)(texture);
        }
    }
    beginReadPixels(selector, cullingFrustum) {
        this.beginPerfMetricRecord("Init Commands", true);
        this._isReadPixelsInProgress = true;
        this._readPixelsSelector = selector;
        // Temporarily turn off lighting to speed things up.
        // ###TODO: Disable textures *unless* they contain transparency. If we turn them off unconditionally then readPixels() will locate fully-transparent pixels, which we don't want.
        const vf = this.currentViewFlags.copy({
            transparency: false,
            lighting: false,
            shadows: false,
            acsTriad: false,
            grid: false,
            monochrome: false,
            materials: false,
            ambientOcclusion: false,
            thematicDisplay: this.currentViewFlags.thematicDisplay && this.uniforms.thematic.wantIsoLines,
        });
        const top = this.currentBranch;
        const state = new BranchState_1.BranchState({
            viewFlags: vf,
            symbologyOverrides: top.symbologyOverrides,
            is3d: top.is3d,
            edgeSettings: top.edgeSettings,
            transform: core_geometry_1.Transform.createIdentity(),
            clipVolume: top.clipVolume,
        });
        this.pushState(state);
        // Repopulate the command list, omitting non-pickable decorations and putting transparent stuff into the opaque passes.
        if (cullingFrustum)
            this._renderCommands.setCheckRange(cullingFrustum);
        this._renderCommands.initForReadPixels(this.graphics);
        this._renderCommands.clearCheckRange();
        this.endPerfMetricRecord(true);
    }
    endReadPixels(preserveBatchState = false) {
        // Pop the BranchState pushed by beginReadPixels.
        this.uniforms.branch.pop();
        if (!preserveBatchState)
            this.uniforms.batch.resetBatchState();
        this._isReadPixelsInProgress = false;
    }
    readPixelsFromFbo(rect, selector) {
        // Create a culling frustum based on the input rect. We can't do this if a screen-space effect is going to move pixels around.
        let rectFrust;
        if (!this.renderSystem.screenSpaceEffects.shouldApply(this)) {
            const viewRect = this.viewRect;
            const leftScale = (rect.left - viewRect.left) / (viewRect.right - viewRect.left);
            const rightScale = (viewRect.right - rect.right) / (viewRect.right - viewRect.left);
            const topScale = (rect.top - viewRect.top) / (viewRect.bottom - viewRect.top);
            const bottomScale = (viewRect.bottom - rect.bottom) / (viewRect.bottom - viewRect.top);
            const tmpFrust = this._scratchTmpFrustum;
            const planFrust = this.planFrustum;
            interpolateFrustumPoint(tmpFrust, planFrust, core_common_1.Npc._000, leftScale, core_common_1.Npc._100);
            interpolateFrustumPoint(tmpFrust, planFrust, core_common_1.Npc._100, rightScale, core_common_1.Npc._000);
            interpolateFrustumPoint(tmpFrust, planFrust, core_common_1.Npc._010, leftScale, core_common_1.Npc._110);
            interpolateFrustumPoint(tmpFrust, planFrust, core_common_1.Npc._110, rightScale, core_common_1.Npc._010);
            interpolateFrustumPoint(tmpFrust, planFrust, core_common_1.Npc._001, leftScale, core_common_1.Npc._101);
            interpolateFrustumPoint(tmpFrust, planFrust, core_common_1.Npc._101, rightScale, core_common_1.Npc._001);
            interpolateFrustumPoint(tmpFrust, planFrust, core_common_1.Npc._011, leftScale, core_common_1.Npc._111);
            interpolateFrustumPoint(tmpFrust, planFrust, core_common_1.Npc._111, rightScale, core_common_1.Npc._011);
            rectFrust = this._scratchRectFrustum;
            interpolateFrustumPoint(rectFrust, tmpFrust, core_common_1.Npc._000, bottomScale, core_common_1.Npc._010);
            interpolateFrustumPoint(rectFrust, tmpFrust, core_common_1.Npc._100, bottomScale, core_common_1.Npc._110);
            interpolateFrustumPoint(rectFrust, tmpFrust, core_common_1.Npc._010, topScale, core_common_1.Npc._000);
            interpolateFrustumPoint(rectFrust, tmpFrust, core_common_1.Npc._110, topScale, core_common_1.Npc._100);
            interpolateFrustumPoint(rectFrust, tmpFrust, core_common_1.Npc._001, bottomScale, core_common_1.Npc._011);
            interpolateFrustumPoint(rectFrust, tmpFrust, core_common_1.Npc._101, bottomScale, core_common_1.Npc._111);
            interpolateFrustumPoint(rectFrust, tmpFrust, core_common_1.Npc._011, topScale, core_common_1.Npc._001);
            interpolateFrustumPoint(rectFrust, tmpFrust, core_common_1.Npc._111, topScale, core_common_1.Npc._101);
        }
        this.beginReadPixels(selector, rectFrust);
        // Draw the scene
        this.compositor.drawForReadPixels(this._renderCommands, this.graphics.overlays, this.graphics.decorations?.worldOverlay, this.graphics.decorations?.viewOverlay);
        if (this.performanceMetrics && !this.performanceMetrics.gatherCurPerformanceMetrics) { // Only collect readPixels data if in disp-perf-test-app
            this.performanceMetrics.endOperation(); // End the 'CPU Total Time' operation
            if (this.performanceMetrics.gatherGlFinish && !this.renderSystem.isGLTimerSupported) {
                // Ensure all previously queued webgl commands are finished by reading back one pixel since gl.Finish didn't work
                this.performanceMetrics.beginOperation("Finish GPU Queue");
                const gl = this.renderSystem.context;
                const bytes = new Uint8Array(4);
                this.renderSystem.frameBufferStack.execute(this._fbo, true, false, () => {
                    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, bytes);
                });
                this.performanceMetrics.endOperation();
            }
        }
        // Apply any screen-space effects that shift pixels from their original locations.
        this.beginPerfMetricRecord("Screenspace Effects", true);
        this.renderSystem.screenSpaceEffects.apply(this);
        this.endPerfMetricRecord(true); // End "Screenspace Effects"
        this.endReadPixels(true);
        this.beginPerfMetricRecord("Read Pixels", true);
        const result = this.compositor.readPixels(rect, selector);
        this.endPerfMetricRecord(true);
        if (this.performanceMetrics && !this.performanceMetrics.gatherCurPerformanceMetrics) { // Only collect readPixels data if in disp-perf-test-app
            if (this.renderSystem.isGLTimerSupported)
                this.renderSystem.glTimer.endFrame();
            if (this.performanceMetrics)
                this.performanceMetrics.endFrame();
        }
        return result;
    }
    queryVisibleTileFeatures(options, iModel, callback) {
        this.beginReadPixels(Pixel_1.Pixel.Selector.Feature);
        callback(new VisibleTileFeatures_1.VisibleTileFeatures(this._renderCommands, options, this, iModel));
        this.endReadPixels();
    }
    readImagePixels(out, x, y, w, h) {
        (0, core_bentley_1.assert)(this._fbo !== undefined);
        if (this._fbo === undefined)
            return false;
        const context = this.renderSystem.context;
        let didSucceed = true;
        this.renderSystem.frameBufferStack.execute(this._fbo, true, false, () => {
            try {
                context.readPixels(x, y, w, h, context.RGBA, context.UNSIGNED_BYTE, out);
            }
            catch {
                didSucceed = false;
            }
        });
        return didSucceed;
    }
    /** Returns a new size scaled up to a maximum size while maintaining proper aspect ratio.  The new size will be
     * curSize adjusted so that it fits fully within maxSize in one dimension, maintaining its original aspect ratio.
     */
    static _applyAspectRatioCorrection(curSize, maxSize) {
        const widthRatio = maxSize.x / curSize.x;
        const heightRatio = maxSize.y / curSize.y;
        const bestRatio = Math.min(widthRatio, heightRatio);
        return new core_geometry_1.Point2d(curSize.x * bestRatio, curSize.y * bestRatio);
    }
    /** wantRectIn is in CSS pixels. Output ImageBuffer will be in device pixels.
     * If wantRect is null, that means "read the entire image".
     */
    readImage(wantRectIn, targetSizeIn, flipVertically) {
        if (!this.assignDC())
            return undefined;
        // Determine capture rect and validate
        const actualViewRect = this.renderRect; // already has device pixel ratio applied
        const wantRect = wantRectIn.isNull ? actualViewRect : this.cssViewRectToDeviceViewRect(wantRectIn);
        const lowerRight = core_geometry_1.Point2d.create(wantRect.right - 1, wantRect.bottom - 1);
        if (!actualViewRect.containsPoint(core_geometry_1.Point2d.create(wantRect.left, wantRect.top)) || !actualViewRect.containsPoint(lowerRight))
            return undefined;
        // Read pixels. Note ViewRect thinks (0,0) = top-left. gl.readPixels expects (0,0) = bottom-left.
        const bytesPerPixel = 4;
        const imageData = new Uint8Array(bytesPerPixel * wantRect.width * wantRect.height);
        const isValidImageData = this.readImagePixels(imageData, wantRect.left, wantRect.top, wantRect.width, wantRect.height);
        if (!isValidImageData)
            return undefined;
        let image = core_common_1.ImageBuffer.create(imageData, core_common_1.ImageBufferFormat.Rgba, wantRect.width);
        if (!image)
            return undefined;
        const targetSize = targetSizeIn.clone();
        if (targetSize.x === 0 || targetSize.y === 0) { // Indicates image should have same dimensions as rect (no scaling)
            targetSize.x = wantRect.width;
            targetSize.y = wantRect.height;
        }
        if (targetSize.x === wantRect.width && targetSize.y === wantRect.height) {
            // No need to scale image.
            // Some callers want background pixels to be treated as fully-transparent
            // They indicate this by supplying a background color with full transparency
            // Any other pixels are treated as fully-opaque as alpha has already been blended
            // ###TODO: This introduces a defect in that we are not preserving alpha of translucent pixels, and therefore the returned image cannot be blended
            const preserveBGAlpha = 0.0 === this.uniforms.style.backgroundAlpha;
            // Optimization for view attachments: if image consists entirely of background pixels, return an undefined
            let isEmptyImage = true;
            for (let i = 3; i < image.data.length; i += 4) {
                const a = image.data[i];
                if (!preserveBGAlpha || 0 < a) {
                    image.data[i] = 0xff;
                    isEmptyImage = false;
                }
            }
            if (isEmptyImage)
                return undefined;
        }
        else {
            // Need to scale image.
            const canvas = (0, ImageUtil_1.imageBufferToCanvas)(image, false); // retrieve a canvas of the image we read, throwing away alpha channel.
            if (undefined === canvas)
                return undefined;
            const adjustedTargetSize = Target._applyAspectRatioCorrection(new core_geometry_1.Point2d(wantRect.width, wantRect.height), targetSize);
            const resizedCanvas = (0, ImageUtil_1.canvasToResizedCanvasWithBars)(canvas, adjustedTargetSize, new core_geometry_1.Point2d(targetSize.x - adjustedTargetSize.x, targetSize.y - adjustedTargetSize.y), this.uniforms.style.backgroundHexString);
            const resizedImage = (0, ImageUtil_1.canvasToImageBuffer)(resizedCanvas);
            if (undefined !== resizedImage)
                image = resizedImage;
        }
        if (flipVertically) {
            const halfHeight = Math.floor(image.height / 2);
            const numBytesPerRow = image.width * 4;
            for (let loY = 0; loY < halfHeight; loY++) {
                for (let x = 0; x < image.width; x++) {
                    const hiY = (image.height - 1) - loY;
                    const loIdx = loY * numBytesPerRow + x * 4;
                    const hiIdx = hiY * numBytesPerRow + x * 4;
                    swapImageByte(image, loIdx, hiIdx);
                    swapImageByte(image, loIdx + 1, hiIdx + 1);
                    swapImageByte(image, loIdx + 2, hiIdx + 2);
                    swapImageByte(image, loIdx + 3, hiIdx + 3);
                }
            }
        }
        return image;
    }
    readImageBuffer(args) {
        if (!this.assignDC())
            return undefined;
        // Determine and validate capture rect.
        const viewRect = this.renderRect; // already has device pixel ratio applied
        const captureRect = args?.rect ? this.cssViewRectToDeviceViewRect(args.rect) : viewRect;
        if (captureRect.isNull)
            return undefined;
        const topLeft = core_geometry_1.Point3d.create(captureRect.left, captureRect.top);
        const bottomRight = core_geometry_1.Point2d.create(captureRect.right - 1, captureRect.bottom - 1);
        if (!viewRect.containsPoint(topLeft) || !viewRect.containsPoint(bottomRight))
            return undefined;
        // ViewRect origin is at top-left. GL origin is at bottom-left.
        const bottom = viewRect.height - captureRect.bottom;
        const imageData = new Uint8Array(4 * captureRect.width * captureRect.height);
        if (!this.readImagePixels(imageData, captureRect.left, bottom, captureRect.width, captureRect.height))
            return undefined;
        // Alpha has already been blended. Make all pixels opaque, *except* for background pixels if background color is fully transparent.
        const preserveBGAlpha = 0 === this.uniforms.style.backgroundAlpha;
        let isEmptyImage = true;
        for (let i = 3; i < imageData.length; i += 4) {
            const a = imageData[i];
            if (!preserveBGAlpha || a > 0) {
                imageData[i] = 0xff;
                isEmptyImage = false;
            }
        }
        // Optimization for view attachments: if image consists entirely of transparent background pixels, don't bother producing an image.
        let image = !isEmptyImage ? core_common_1.ImageBuffer.create(imageData, core_common_1.ImageBufferFormat.Rgba, captureRect.width) : undefined;
        if (!image)
            return undefined;
        // Scale image.
        if (args?.size && (args.size.x !== captureRect.width || args.size.y !== captureRect.height)) {
            if (args.size.x <= 0 || args.size.y <= 0)
                return undefined;
            let canvas = (0, ImageUtil_1.imageBufferToCanvas)(image, true);
            if (!canvas)
                return undefined;
            const adjustedSize = Target._applyAspectRatioCorrection({ x: captureRect.width, y: captureRect.height }, args.size);
            canvas = (0, ImageUtil_1.canvasToResizedCanvasWithBars)(canvas, adjustedSize, new core_geometry_1.Point2d(args.size.x - adjustedSize.x, args.size.y - adjustedSize.y), this.uniforms.style.backgroundHexString);
            image = (0, ImageUtil_1.canvasToImageBuffer)(canvas);
            if (!image)
                return undefined;
        }
        // Our image is upside-down by default. Flip it unless otherwise specified.
        if (!args?.upsideDown) {
            const halfHeight = Math.floor(image.height / 2);
            const numBytesPerRow = image.width * 4;
            for (let loY = 0; loY < halfHeight; loY++) {
                for (let x = 0; x < image.width; x++) {
                    const hiY = (image.height - 1) - loY;
                    const loIdx = loY * numBytesPerRow + x * 4;
                    const hiIdx = hiY * numBytesPerRow + x * 4;
                    swapImageByte(image, loIdx, hiIdx);
                    swapImageByte(image, loIdx + 1, hiIdx + 1);
                    swapImageByte(image, loIdx + 2, hiIdx + 2);
                    swapImageByte(image, loIdx + 3, hiIdx + 3);
                }
            }
        }
        return image;
    }
    copyImageToCanvas() {
        const image = this.readImageBuffer();
        const canvas = undefined !== image ? (0, ImageUtil_1.imageBufferToCanvas)(image, false) : undefined;
        const retCanvas = undefined !== canvas ? canvas : document.createElement("canvas");
        const pixelRatio = this.devicePixelRatio;
        retCanvas.getContext("2d").scale(pixelRatio, pixelRatio);
        return retCanvas;
    }
    drawPlanarClassifiers() {
        if (this._planarClassifiers) {
            this._planarClassifiers.forEach((classifier) => {
                this._currentlyDrawingClassifier = classifier;
                this._currentlyDrawingClassifier.draw(this);
                this._currentlyDrawingClassifier = undefined;
            });
        }
    }
    drawSolarShadowMap() {
        if (this.solarShadowMap.isEnabled)
            this.solarShadowMap.draw(this);
    }
    drawTextureDrapes() {
        if (this._textureDrapes)
            this._textureDrapes.forEach((drape) => drape.draw(this));
    }
    get screenSpaceEffects() {
        return this._screenSpaceEffects;
    }
    set screenSpaceEffects(effects) {
        this._screenSpaceEffects = [...effects];
    }
    get screenSpaceEffectContext() {
        (0, core_bentley_1.assert)(undefined !== this._viewport);
        return { viewport: this._viewport };
    }
    get currentAnimationTransformNodeId() {
        return this._currentAnimationTransformNodeId;
    }
    set currentAnimationTransformNodeId(id) {
        (0, core_bentley_1.assert)(undefined === this._currentAnimationTransformNodeId || undefined === id);
        this._currentAnimationTransformNodeId = id;
    }
    /** Given GraphicBranch.animationId identifying *any* node in the scene's schedule script, return the transform node Id
     * that should be used to filter the branch's graphics for display, or undefined if no filtering should be applied.
     */
    getAnimationTransformNodeId(animationNodeId) {
        if (undefined === this.animationBranches || undefined === this.currentAnimationTransformNodeId || undefined === animationNodeId)
            return undefined;
        return this.animationBranches.transformNodeIds.has(animationNodeId) ? animationNodeId : AnimationNodeId_1.AnimationNodeId.Untransformed;
    }
    collectStatistics(stats) {
        this._compositor.collectStatistics(stats);
        const thematicBytes = this.uniforms.thematic.bytesUsed;
        if (0 < thematicBytes)
            stats.addThematicTexture(thematicBytes);
        const clipBytes = this.uniforms.branch.clipStack.bytesUsed;
        if (clipBytes)
            stats.addClipVolume(clipBytes);
    }
    cssViewRectToDeviceViewRect(rect) {
        // NB: ViewRect constructor *floors* inputs.
        const ratio = this.devicePixelRatio;
        return new ViewRect_1.ViewRect(Math.floor(rect.left * ratio), Math.floor(rect.top * ratio), Math.floor(rect.right * ratio), Math.floor(rect.bottom * ratio));
    }
    getRenderCommands() {
        return this._renderCommands.dump();
    }
}
exports.Target = Target;
_a = Symbols_1._implementationProhibited;
class CanvasState {
    constructor(canvas) {
        this.needsClear = false;
        this.canvas = canvas;
        this._isWebGLCanvas = this.canvas === System_1.System.instance.canvas;
    }
    // Returns true if the rect actually changed.
    updateDimensions(pixelRatio) {
        const w = Math.floor(this.canvas.clientWidth * pixelRatio);
        const h = Math.floor(this.canvas.clientHeight * pixelRatio);
        // Do not update the dimensions if not needed, or if new width or height is 0, which is invalid.
        // NB: the 0-dimension check indirectly resolves an issue when a viewport is dropped and immediately re-added
        // to the view manager. See ViewManager.test.ts for more details.  0 is also the case when vpDiv.removeChild
        // is done on webGLCanvas, due to client sizes being 0 afterward.
        if (w === this.canvas.width && h === this.canvas.height || (0 === w || 0 === h))
            return false;
        // Must ensure internal bitmap grid dimensions of on-screen canvas match its own on-screen appearance.
        this.canvas.width = w;
        this.canvas.height = h;
        if (!this._isWebGLCanvas) {
            const ctx = this.canvas.getContext("2d");
            ctx.scale(pixelRatio, pixelRatio); // apply the pixelRatio as a scale on the 2d context for drawing of decorations, etc.
            ctx.save();
        }
        return true;
    }
    get width() { return this.canvas.width; }
    get height() { return this.canvas.height; }
}
/** A Target that renders to a canvas on the screen
 * @internal
 */
class OnScreenTarget extends Target {
    get _curCanvas() { return this._usingWebGLCanvas ? this._webglCanvas : this._2dCanvas; }
    constructor(canvas) {
        super();
        this._usingWebGLCanvas = false;
        this._2dCanvas = new CanvasState(canvas);
        this._webglCanvas = new CanvasState(this.renderSystem.canvas);
    }
    get isDisposed() {
        return undefined === this._blitGeom
            && undefined === this._scratchProgParams
            && undefined === this._scratchDrawParams
            && super.isDisposed;
    }
    dispose() {
        this._blitGeom = (0, core_bentley_1.dispose)(this._blitGeom);
        this._scratchProgParams = undefined;
        this._scratchDrawParams = undefined;
        super.dispose();
    }
    collectStatistics(stats) {
        super.collectStatistics(stats);
        if (undefined !== this._blitGeom)
            this._blitGeom.collectStatistics(stats);
    }
    get devicePixelRatioOverride() { return this._devicePixelRatioOverride; }
    set devicePixelRatioOverride(ovr) { this._devicePixelRatioOverride = ovr; }
    get devicePixelRatio() {
        if (undefined !== this.devicePixelRatioOverride)
            return this.devicePixelRatioOverride;
        if (false === this.renderSystem.options.dpiAwareViewports)
            return 1.0;
        if (undefined !== this.renderSystem.options.devicePixelRatioOverride)
            return this.renderSystem.options.devicePixelRatioOverride;
        return window.devicePixelRatio || 1.0;
    }
    setViewRect(_rect, _temporary) {
        (0, core_bentley_1.assert)(false);
    }
    /** Internal-only function for testing. Returns true if the FBO dimensions match the canvas dimensions */
    checkFboDimensions() {
        if (undefined !== this._fbo) {
            const tx = this._fbo.getColor(0);
            if (tx.width !== this._curCanvas.width || tx.height !== this._curCanvas.height)
                return false;
        }
        return true;
    }
    _assignDC() {
        this.disposeFbo();
        const fbo = this.allocateFbo();
        if (!fbo)
            return false;
        const tx = fbo.getColor(0);
        (0, core_bentley_1.assert)(undefined !== tx.getHandle());
        this._blitGeom = CachedGeometry_1.SingleTexturedViewportQuadGeometry.createGeometry(tx.getHandle(), 19 /* TechniqueId.CopyColorNoAlpha */);
        if (undefined === this._blitGeom)
            this.disposeFbo();
        return undefined !== this._blitGeom;
    }
    updateViewRect() {
        const pixelRatio = this.devicePixelRatio;
        const changed2d = this._2dCanvas.updateDimensions(pixelRatio);
        const changedWebGL = this._webglCanvas.updateDimensions(pixelRatio);
        this.renderRect.init(0, 0, this._curCanvas.width, this._curCanvas.height);
        return this._usingWebGLCanvas ? changedWebGL : changed2d;
    }
    _beginPaint(fbo) {
        // Render to our framebuffer
        const system = this.renderSystem;
        system.frameBufferStack.push(fbo, true, false);
        const viewRect = this.viewRect;
        // Ensure off-screen canvas is sufficiently large for on-screen canvas.
        // Using a portion of a larger canvas lets us avoid thrashing canvas resizes with multiple viewports.
        if (system.canvas.width < viewRect.width)
            system.canvas.width = viewRect.width;
        if (system.canvas.height < viewRect.height)
            system.canvas.height = viewRect.height;
        (0, core_bentley_1.assert)(system.context.drawingBufferWidth >= viewRect.width, "offscreen context dimensions don't match onscreen");
        (0, core_bentley_1.assert)(system.context.drawingBufferHeight >= viewRect.height, "offscreen context dimensions don't match onscreen");
    }
    getDrawParams(target, geom) {
        if (undefined === this._scratchProgParams) {
            this._scratchProgParams = new DrawCommand_1.ShaderProgramParams();
            this._scratchDrawParams = new DrawCommand_1.DrawParams();
        }
        this._scratchProgParams.init(target);
        this._scratchDrawParams.init(this._scratchProgParams, geom);
        return this._scratchDrawParams;
    }
    _endPaint() {
        if (undefined === this._blitGeom)
            return;
        // Blit the final image to the canvas.
        const drawParams = this.getDrawParams(this, this._blitGeom);
        const system = this.renderSystem;
        system.frameBufferStack.pop();
        system.applyRenderState(RenderState_1.RenderState.defaults);
        system.techniques.draw(drawParams);
        if (this._usingWebGLCanvas)
            return; // We already drew (using WebGL) the framebuffer contents directly to the on-screen WebGL canvas.
        // Copy off-screen canvas contents to on-screen canvas
        const onscreenContext = this._2dCanvas.canvas.getContext("2d", { alpha: true });
        (0, core_bentley_1.assert)(null !== onscreenContext);
        if (null !== onscreenContext) {
            const w = this.viewRect.width, h = this.viewRect.height;
            const yOffset = system.canvas.height - h; // drawImage has top as Y=0, GL has bottom as Y=0
            onscreenContext.save();
            if (this.uniforms.style.backgroundAlpha < 1) {
                // If background is transparent, we aren't guaranteed that every pixel will be overwritten - clear it.
                onscreenContext.clearRect(0, 0, w, h);
            }
            onscreenContext.setTransform(1, 0, 0, 1, 0, 0); // revert any previous devicePixelRatio scale for drawImage() call below.
            onscreenContext.drawImage(system.canvas, 0, yOffset, w, h, 0, 0, w, h);
            onscreenContext.restore();
        }
    }
    drawOverlayDecorations() {
        const ctx = this._2dCanvas.canvas.getContext("2d", { alpha: true });
        if (this._usingWebGLCanvas && this._2dCanvas.needsClear) {
            ctx.save();
            ctx.setTransform(1, 0, 0, 1, 0, 0); // revert any previous devicePixelRatio scale for clearRect() call below.
            ctx.clearRect(0, 0, this._2dCanvas.width, this._2dCanvas.height);
            ctx.restore();
            this._2dCanvas.needsClear = false;
        }
        const canvasDecs = this.graphics.canvasDecorations;
        if (canvasDecs) {
            for (const overlay of canvasDecs) {
                ctx.save();
                if (overlay.position)
                    ctx.translate(overlay.position.x, overlay.position.y);
                overlay.drawDecoration(ctx);
                this._2dCanvas.needsClear = true;
                ctx.restore();
            }
        }
    }
    pickOverlayDecoration(pt) {
        const overlays = this.graphics.canvasDecorations;
        if (undefined === overlays)
            return undefined;
        // loop over array backwards, because later entries are drawn on top.
        for (let i = overlays.length - 1; i >= 0; --i) {
            const overlay = overlays[i];
            if (undefined !== overlay.pick && overlay.pick(pt))
                return overlay;
        }
        return undefined;
    }
    onResized() {
        this.disposeFbo();
    }
    setRenderToScreen(toScreen) {
        if (toScreen === this._usingWebGLCanvas)
            return;
        // NB: need to force a buffer change in this case because nothing is changing sizes except the webglCanvas
        if (toScreen)
            this.compositor.forceBufferChange();
        this._usingWebGLCanvas = toScreen;
        return toScreen ? this._webglCanvas.canvas : undefined;
    }
    readImageToCanvas() {
        return this._usingWebGLCanvas ? this.copyImageToCanvas() : this._2dCanvas.canvas;
    }
}
exports.OnScreenTarget = OnScreenTarget;
/** @internal */
class OffScreenTarget extends Target {
    constructor(rect) {
        super(rect);
    }
    onResized() {
        (0, core_bentley_1.assert)(false); // offscreen viewport's dimensions are set once, in constructor.
    }
    updateViewRect() { return false; } // offscreen target does not dynamically resize the view rect
    setViewRect(rect, temporary) {
        if (this.renderRect.equals(rect))
            return;
        this.renderRect.setFrom(rect);
        if (temporary) {
            // Temporarily adjust view rect to create scene for a view attachment.
            // Will be reset before attachment is rendered - so don't blow away our framebuffers + textures
            return;
        }
        this.disposeFbo();
        (0, core_bentley_1.dispose)(this._compositor);
    }
    _assignDC() {
        return undefined !== this.allocateFbo();
    }
    _beginPaint(fbo) {
        this.renderSystem.frameBufferStack.push(fbo, true, false);
    }
    _endPaint() {
        this.renderSystem.frameBufferStack.pop();
    }
    readImageToCanvas() {
        return this.copyImageToCanvas();
    }
}
exports.OffScreenTarget = OffScreenTarget;
function interpolatePoint(p0, fraction, p1, out) {
    let x;
    let y;
    let z;
    if (fraction <= 0.5) {
        x = p0.x + fraction * (p1.x - p0.x);
        y = p0.y + fraction * (p1.y - p0.y);
        z = p0.z + fraction * (p1.z - p0.z);
    }
    else {
        const t = fraction - 1.0;
        x = p1.x + t * (p1.x - p0.x);
        y = p1.y + t * (p1.y - p0.y);
        z = p1.z + t * (p1.z - p0.z);
    }
    return core_geometry_1.Point3d.create(x, y, z, out);
}
function interpolateFrustumPoint(destFrust, srcFrust, destPoint, scale, srcPoint) {
    interpolatePoint(srcFrust.getCorner(destPoint), scale, srcFrust.getCorner(srcPoint), destFrust.points[destPoint]);
}
//# sourceMappingURL=Target.js.map