/*---------------------------------------------------------------------------------------------
* 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, dispose, expectDefined, expectNotNull, Id64 } from "@itwin/core-bentley";
import { Point2d, Point3d, Range3d, Transform } from "@itwin/core-geometry";
import { AmbientOcclusion, Frustum, ImageBuffer, ImageBufferFormat, Npc, RenderMode, ThematicDisplayMode, ViewFlags, } from "@itwin/core-common";
import { ViewRect } from "../../../common/ViewRect";
import { canvasToImageBuffer, canvasToResizedCanvasWithBars, imageBufferToCanvas } from "../../../common/ImageUtil";
import { Pixel } from "../../../render/Pixel";
import { createEmptyRenderPlan } from "../RenderPlan";
import { RenderTarget } from "../../../render/RenderTarget";
import { PrimitiveVisibility } from "../RenderTargetDebugControl";
import { BranchState } from "./BranchState";
import { SingleTexturedViewportQuadGeometry } from "./CachedGeometry";
import { ColorInfo } from "./ColorInfo";
import { DrawParams, ShaderProgramParams } from "./DrawCommand";
import { FrameBuffer } from "./FrameBuffer";
import { GL } from "./GL";
import { WorldDecorations } from "./Graphic";
import { IModelFrameLifecycle } from "./IModelFrameLifecycle";
import { PlanarClassifier } from "./PlanarClassifier";
import { Primitive } from "./Primitive";
import { RenderState } from "./RenderState";
import { SceneCompositor } from "./SceneCompositor";
import { freeDrawParams } from "./ScratchDrawParams";
import { ShaderProgramExecutor } from "./ShaderProgram";
import { desync } from "./Sync";
import { System } from "./System";
import { TargetUniforms } from "./TargetUniforms";
import { TextureHandle } from "./Texture";
import { TargetGraphics } from "./TargetGraphics";
import { VisibleTileFeatures } from "./VisibleTileFeatures";
import { FrameStatsCollector } from "../FrameStatsCollector";
import { AnimationNodeId } from "../../../common/internal/render/AnimationNodeId";
import { _implementationProhibited } from "../../../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 {
    elements;
    subcategories;
    models;
    isEmpty = true;
    modelSubCategoryMode = "union";
    constructor() {
        this.elements = this.subcategories = this.models = new Id64.Uint32Set();
    }
}
/** @internal */
export class Target extends RenderTarget {
    [_implementationProhibited] = undefined;
    graphics = new TargetGraphics();
    _planarClassifiers;
    _textureDrapes;
    _worldDecorations;
    _currPickExclusions = new Id64.Uint32Set();
    _swapPickExclusions = new Id64.Uint32Set();
    pickExclusionsSyncTarget = { syncKey: Number.MIN_SAFE_INTEGER };
    _hilites = new EmptyHiliteSet();
    _hiliteSyncTarget = { syncKey: Number.MIN_SAFE_INTEGER };
    _flashed = { lower: 0, upper: 0 };
    _flashedId = Id64.invalid;
    _flashIntensity = 0;
    _renderCommands;
    _overlayRenderState;
    _compositor;
    _fbo;
    _dcAssigned = false;
    performanceMetrics;
    decorationsState = BranchState.createForDecorations(); // Used when rendering view background and view/world overlays.
    uniforms = new TargetUniforms(this);
    renderRect = new ViewRect();
    analysisStyle;
    analysisTexture;
    ambientOcclusionSettings = AmbientOcclusion.Settings.defaults;
    _wantAmbientOcclusion = false;
    _batches = [];
    plan = createEmptyRenderPlan();
    _animationBranches;
    _isReadPixelsInProgress = false;
    _readPixelsSelector = Pixel.Selector.None;
    _readPixelReusableResources;
    _drawNonLocatable = true;
    _currentlyDrawingClassifier;
    _analysisFraction = 0;
    _antialiasSamples = 1;
    // This exists strictly to be forwarded to ScreenSpaceEffects. Do not use it for anything else.
    _viewport;
    _screenSpaceEffects = [];
    isFadeOutActive = false;
    activeVolumeClassifierTexture;
    activeVolumeClassifierProps;
    activeVolumeClassifierModelId;
    _currentAnimationTransformNodeId;
    // RenderTargetDebugControl
    vcSupportIntersectingVolumes = false;
    drawForReadPixels = false;
    drawingBackgroundForReadPixels = false;
    primitiveVisibility = PrimitiveVisibility.All;
    displayDrapeFrustum = false;
    displayMaskFrustum = false;
    displayRealityTilePreload = false;
    displayRealityTileRanges = false;
    logRealityTiles = false;
    displayNormalMaps = true;
    freezeRealityTiles = false;
    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._renderCommands = this.uniforms.branch.createRenderCommands(this.uniforms.batch.state);
        this._overlayRenderState = new RenderState();
        this._overlayRenderState.flags.depthMask = false;
        this._overlayRenderState.flags.blend = true;
        this._overlayRenderState.blend.setBlendFunc(GL.BlendFactor.One, GL.BlendFactor.OneMinusSrcAlpha);
        this._compositor = SceneCompositor.create(this); // compositor is created but not yet initialized... we are still undisposed
        this.renderRect = rect ? rect : new ViewRect(); // if the rect is undefined, expect that it will be updated dynamically in an OnScreenTarget
        if (undefined !== System.instance.antialiasSamples)
            this._antialiasSamples = System.instance.antialiasSamples;
        else
            this._antialiasSamples = (undefined !== System.instance.options.antialiasSamples ? 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 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.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 ViewFlags({
                renderMode: RenderMode.SmoothShade,
                clipVolume: false,
                whiteOnWhiteReversal: false,
                lighting: !this.is2d,
                shadows: false,
            });
            this._worldDecorations = new 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;
    }
    get currentContours() { return this.currentBranch.contourLine; }
    modelToView(modelPt, result) {
        return this.uniforms.branch.modelViewMatrix.multiplyPoint3dQuietNormalize(modelPt, result);
    }
    get is2d() { return this.uniforms.frustum.is2d; }
    get is3d() { return !this.is2d; }
    _isDisposed = false;
    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 = TextureHandle.createForAttachment(rect.width, rect.height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte);
        if (undefined === color)
            return undefined;
        const depth = System.instance.createDepthBuffer(rect.width, rect.height, 1);
        if (undefined === depth) {
            color[Symbol.dispose]();
            return undefined;
        }
        this._fbo = FrameBuffer.create([color], depth);
        if (undefined === this._fbo) {
            color[Symbol.dispose]();
            depth[Symbol.dispose]();
            return undefined;
        }
        return this._fbo;
    }
    disposeFbo() {
        if (!this._fbo)
            return;
        const tx = this._fbo.getColor(0);
        const db = this._fbo.depthBuffer;
        this._fbo = dispose(this._fbo);
        this._dcAssigned = false;
        // We allocated our framebuffer's color attachment, so must dispose of it too.
        assert(undefined !== tx);
        dispose(tx);
        // We allocated our framebuffer's depth attachment, so must dispose of it too.
        assert(undefined !== db);
        dispose(db);
    }
    [Symbol.dispose]() {
        this.reset();
        this.disposeFbo();
        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);
    }
    _scratchRange = new Range3d();
    /** @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, this.currentBranch);
    }
    popBatch() {
        this.uniforms.batch.clearCurrentBatch();
    }
    addBatch(batch) {
        assert(this._batches.indexOf(batch) < 0);
        this._batches.push(batch);
    }
    onBatchDisposed(batch) {
        const index = this._batches.indexOf(batch);
        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 && ThematicDisplayMode.InverseDistanceWeightedSensors === thematic.displayMode && thematic.sensorSettings.sensors.length > 0;
    }
    updateSolarShadows(context) {
        this.compositor.updateSolarShadows(context);
    }
    // ---- Implementation of RenderTarget interface ---- //
    get renderSystem() { return 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.onBeforeRender.raiseEvent({
            renderSystem: this.renderSystem,
            viewport,
            setSceneNeedRedraw,
        });
    }
    changeDrapesOrClassifiers(oldMap, newMap) {
        if (undefined === newMap) {
            if (undefined !== oldMap)
                for (const value of oldMap.values())
                    value[Symbol.dispose]();
            return;
        }
        if (undefined !== oldMap) {
            for (const entry of oldMap)
                if (newMap.get(entry[0]) !== entry[1])
                    entry[1][Symbol.dispose]();
        }
    }
    changeTextureDrapes(textureDrapes) {
        this.changeDrapesOrClassifiers(this._textureDrapes, textureDrapes);
        this._textureDrapes = textureDrapes;
    }
    changePlanarClassifiers(planarClassifiers) {
        this.changeDrapesOrClassifiers(this._planarClassifiers, planarClassifiers);
        this._planarClassifiers = planarClassifiers;
    }
    changeDynamics(foreground, overlay) {
        this.graphics.changeDynamics(foreground, overlay);
    }
    overrideFeatureSymbology(ovr) {
        this.uniforms.branch.overrideFeatureSymbology(ovr);
    }
    setHiliteSet(hilite) {
        this._hilites = hilite;
        desync(this._hiliteSyncTarget);
    }
    setFlashed(id, intensity) {
        if (id !== this._flashedId) {
            this._flashedId = id;
            this._flashed = 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 = 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(RenderMode.Wireframe);
        if (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, plan.contours);
        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) {
        assert(this.renderSystem.frameBufferStack.isEmpty);
        if (!this.assignDC())
            return;
        this.paintScene(sceneMilSecElapsed);
        this.drawOverlayDecorations();
        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(_realityMapLayerChanged) {
        this.graphics[Symbol.dispose]();
        this._worldDecorations = dispose(this._worldDecorations);
        dispose(this.uniforms.thematic);
        // Ensure that only necessary classifiers are removed. If the reality map layer has not changed,
        // removing all classifiers would result in the loss of draping effects without triggering a refresh.
        if (_realityMapLayerChanged) {
            this.changePlanarClassifiers(undefined);
        }
        else if (this._planarClassifiers) {
            const filteredClassifiers = new Map([...this._planarClassifiers.entries()]
                .filter(([key]) => key.toLowerCase().includes("maplayer")));
            this.changePlanarClassifiers(filteredClassifiers.size > 0 ? filteredClassifiers : 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();
        freeDrawParams();
        ShaderProgramExecutor.freeParams();
        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.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(expectDefined(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();
        }
    }
    _frameStatsCollector = new FrameStatsCollector();
    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);
        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.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
        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;
                    desync(this.pickExclusionsSyncTarget);
                }
            }
            this._drawNonLocatable = !excludeNonLocatable;
            result = this.readPixelsFromFbo(rect, selector);
            this._drawNonLocatable = true;
            if (updatedExclusions) {
                this._currPickExclusions.clear();
                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 = TextureHandle.createForAttachment(rect.width, rect.height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte);
        if (texture === undefined)
            return undefined;
        const fbo = FrameBuffer.create([texture]);
        if (fbo === undefined) {
            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
                dispose(this._readPixelReusableResources.fbo);
                dispose(this._readPixelReusableResources.texture);
            }
        }
        if (reuseResources) {
            this._readPixelReusableResources = { texture, fbo };
        }
        else {
            dispose(fbo);
            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({
            viewFlags: vf,
            symbologyOverrides: top.symbologyOverrides,
            is3d: top.is3d,
            edgeSettings: top.edgeSettings,
            transform: Transform.createIdentity(),
            clipVolume: top.clipVolume,
            contourLine: top.contourLine,
        });
        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;
    }
    _scratchTmpFrustum = new Frustum();
    _scratchRectFrustum = new Frustum();
    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, Npc._000, leftScale, Npc._100);
            interpolateFrustumPoint(tmpFrust, planFrust, Npc._100, rightScale, Npc._000);
            interpolateFrustumPoint(tmpFrust, planFrust, Npc._010, leftScale, Npc._110);
            interpolateFrustumPoint(tmpFrust, planFrust, Npc._110, rightScale, Npc._010);
            interpolateFrustumPoint(tmpFrust, planFrust, Npc._001, leftScale, Npc._101);
            interpolateFrustumPoint(tmpFrust, planFrust, Npc._101, rightScale, Npc._001);
            interpolateFrustumPoint(tmpFrust, planFrust, Npc._011, leftScale, Npc._111);
            interpolateFrustumPoint(tmpFrust, planFrust, Npc._111, rightScale, Npc._011);
            rectFrust = this._scratchRectFrustum;
            interpolateFrustumPoint(rectFrust, tmpFrust, Npc._000, bottomScale, Npc._010);
            interpolateFrustumPoint(rectFrust, tmpFrust, Npc._100, bottomScale, Npc._110);
            interpolateFrustumPoint(rectFrust, tmpFrust, Npc._010, topScale, Npc._000);
            interpolateFrustumPoint(rectFrust, tmpFrust, Npc._110, topScale, Npc._100);
            interpolateFrustumPoint(rectFrust, tmpFrust, Npc._001, bottomScale, Npc._011);
            interpolateFrustumPoint(rectFrust, tmpFrust, Npc._101, bottomScale, Npc._111);
            interpolateFrustumPoint(rectFrust, tmpFrust, Npc._011, topScale, Npc._001);
            interpolateFrustumPoint(rectFrust, tmpFrust, Npc._111, topScale, 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(expectDefined(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.Selector.Feature);
        callback(new VisibleTileFeatures(this._renderCommands, options, this, iModel));
        this.endReadPixels();
    }
    readImagePixels(out, x, y, w, h) {
        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 Point2d(curSize.x * bestRatio, curSize.y * bestRatio);
    }
    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 = Point3d.create(captureRect.left, captureRect.top);
        const bottomRight = 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 ? ImageBuffer.create(imageData, 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 = imageBufferToCanvas(image, true);
            if (!canvas)
                return undefined;
            const adjustedSize = Target._applyAspectRatioCorrection({ x: captureRect.width, y: captureRect.height }, args.size);
            canvas = canvasToResizedCanvasWithBars(canvas, adjustedSize, new Point2d(args.size.x - adjustedSize.x, args.size.y - adjustedSize.y), this.uniforms.style.backgroundHexString);
            image = 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(overlayCanvas) {
        const image = this.readImageBuffer();
        const canvas = undefined !== image ? imageBufferToCanvas(image, false) : undefined;
        const retCanvas = undefined !== canvas ? canvas : document.createElement("canvas");
        if (overlayCanvas) {
            const ctx = expectNotNull(retCanvas.getContext("2d"));
            ctx.drawImage(overlayCanvas, 0, 0);
        }
        const pixelRatio = this.devicePixelRatio;
        expectNotNull(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() {
        assert(undefined !== this._viewport);
        return { viewport: this._viewport };
    }
    get currentAnimationTransformNodeId() {
        return this._currentAnimationTransformNodeId;
    }
    set currentAnimationTransformNodeId(id) {
        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.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(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();
    }
}
class CanvasState {
    canvas;
    needsClear = false;
    _isWebGLCanvas;
    constructor(canvas) {
        this.canvas = canvas;
        this._isWebGLCanvas = this.canvas === 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 = expectNotNull(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
 */
export class OnScreenTarget extends Target {
    _2dCanvas;
    _webglCanvas;
    _usingWebGLCanvas = false;
    _blitGeom;
    _scratchProgParams;
    _scratchDrawParams;
    _devicePixelRatioOverride;
    get _curCanvas() { return this._usingWebGLCanvas ? this._webglCanvas : this._2dCanvas; }
    constructor(canvas) {
        super();
        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;
    }
    [Symbol.dispose]() {
        this._blitGeom = dispose(this._blitGeom);
        this._scratchProgParams = undefined;
        this._scratchDrawParams = undefined;
        super[Symbol.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) {
        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);
        assert(undefined !== tx.getHandle());
        this._blitGeom = SingleTexturedViewportQuadGeometry.createGeometry(expectDefined(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;
        assert(system.context.drawingBufferWidth >= viewRect.width, "offscreen context dimensions don't match onscreen");
        assert(system.context.drawingBufferHeight >= viewRect.height, "offscreen context dimensions don't match onscreen");
    }
    getDrawParams(target, geom) {
        if (undefined === this._scratchProgParams) {
            this._scratchProgParams = new ShaderProgramParams();
            this._scratchDrawParams = new DrawParams();
        }
        this._scratchProgParams.init(target);
        if (undefined === this._scratchDrawParams) {
            throw new Error("OnScreenTarget.getDrawParams: this._scratchDrawParams is undefined");
        }
        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.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 });
        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 = expectNotNull(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(overlayCanvas) {
        return this._usingWebGLCanvas || !overlayCanvas ? this.copyImageToCanvas(overlayCanvas) : this._2dCanvas.canvas;
    }
}
/** @internal */
export class OffScreenTarget extends Target {
    constructor(rect) {
        super(rect);
    }
    onResized() {
        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();
        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();
    }
}
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 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