/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Rendering
 */
import { base64StringToUint8Array } from "@itwin/core-bentley";
import { ColorIndex, FeatureIndex, FeatureIndexType, FillFlags, Gradient, ImageBuffer, ImageSource, ImageSourceFormat, isValidImageSourceFormat, QParams3d, QPoint3dList, RenderTexture, TextureTransparency, } from "@itwin/core-common";
import { Point2d, Point3d, Range3d, Transform } from "@itwin/core-geometry";
import { IModelApp } from "../IModelApp";
import { createGraphicFromDescription, createGraphicTemplateFromDescription } from "../tile/internal";
import { ToolAdmin } from "../tools/ToolAdmin";
import { imageElementFromImageSource, tryImageElementFromUrl } from "../common/ImageUtil";
import { createPointStringParams } from "../common/internal/render/PointStringParams";
import { createPolylineParams } from "../common/internal/render/PolylineParams";
import { GraphicBranch } from "./GraphicBranch";
import { RenderGraphicOwner } from "./RenderGraphic";
import { createMeshParams } from "../common/internal/render/VertexTableBuilder";
import { _featureTable, _implementationProhibited, _textures, _transformCenter, _transforms } from "../common/internal/Symbols";
// cSpell:ignore deserializing subcat uninstanced wiremesh qorigin trimesh
/** Default implementation of RenderGraphicOwner. */
class GraphicOwner extends RenderGraphicOwner {
    _graphic;
    constructor(_graphic) {
        super();
        this._graphic = _graphic;
    }
    get graphic() { return this._graphic; }
}
/** Transparency settings for planar grid display.
 * @alpha
 */
export class PlanarGridTransparency {
    /** Transparency for the grid plane.   This should generally be fairly high to avoid obscuring other geometry */
    planeTransparency = .9;
    /** Transparency of the grid lines.  This should be higher than the plane, but less than reference line transparency */
    lineTransparency = .75;
    /** Transparency of the reference lines.   This should be less than plane or line transparency so that reference lines are more prominent */
    refTransparency = .5;
}
/** A RenderSystem provides access to resources used by the internal WebGL-based rendering system.
 * An application rarely interacts directly with the RenderSystem; instead it interacts with types like [[Viewport]] which
 * coordinate with the RenderSystem on the application's behalf.
 * @see [Display system overview]($docs/learning/display/index.md)
 * @see [[IModelApp.renderSystem]].
 * @public
 * @extensions
 */
export class RenderSystem {
    /** Options used to initialize the RenderSystem. These are primarily used for feature-gating.
     * This object is frozen and cannot be modified after the RenderSystem is created.
     * @internal
     */
    options;
    /** Antialias samples to use on all subsequently created render targets.
     * Default value: undefined (no antialiasing)
     * @beta
     */
    antialiasSamples;
    /** Initialize the RenderSystem with the specified options.
     * @note The RenderSystem takes ownership of the supplied Options and freezes it.
     * @internal
     */
    constructor(options) {
        this.options = undefined !== options ? options : {};
        Object.freeze(this.options);
        if (undefined !== this.options.disabledExtensions)
            Object.freeze(this.options.disabledExtensions);
    }
    [Symbol.dispose]() {
        this.dispose(); // eslint-disable-line @typescript-eslint/no-deprecated
    }
    /** The maximum permitted width or height of a texture supported by this render system. */
    get maxTextureSize() { return 0; }
    /** @internal */
    get supportsCreateImageBitmap() { return false; }
    /** @internal */
    get dpiAwareLOD() { return true === this.options.dpiAwareLOD; }
    /** @internal */
    get isMobile() { return false; }
    /** Find a previously-created [RenderMaterial]($common) by its ID.
     * @param _key The unique ID of the material within the context of the IModelConnection. Typically an element ID.
     * @param _imodel The IModelConnection with which the material is associated.
     * @returns A previously-created material matching the specified ID, or undefined if no such material exists.
     */
    findMaterial(_key, _imodel) { return undefined; }
    /** Create a [RenderMaterial]($common).
     * @see [[CreateRenderMaterialArgs]] for a description of the material parameters.
     */
    createRenderMaterial(_args) {
        return undefined;
    }
    /** Creates a [[GraphicBuilder]] for creating a [[RenderGraphic]].
     * @param placement The local-to-world transform in which the builder's geometry is to be defined.
     * @param type The type of builder to create.
     * @param viewport The viewport in which the resultant [[RenderGraphic]] will be rendered.
     * @param pickableId If the decoration is to be pickable, a unique identifier to associate with the resultant [[RenderGraphic]].
     * @returns A builder for creating a [[RenderGraphic]] of the specified type appropriate for rendering within the specified viewport.
     * @see [[IModelConnection.transientIds]] for obtaining an ID for a pickable decoration.
     * @see [[RenderContext.createGraphicBuilder]].
     * @see [[Decorator]]
     */
    createGraphicBuilder(placement, type, viewport, pickableId) {
        const pickable = undefined !== pickableId ? { id: pickableId } : undefined;
        return this.createGraphic({ type, viewport, placement, pickable });
    }
    /** Obtain an object capable of producing a custom screen-space effect to be applied to the image rendered by a [[Viewport]].
     * @returns undefined if screen-space effects are not supported by this RenderSystem.
     */
    createScreenSpaceEffectBuilder(_params) {
        return undefined;
    }
    /** @internal */
    createTriMesh(args, instances) {
        const params = createMeshParams(args, this.maxTextureSize, IModelApp.tileAdmin.edgeOptions.type !== "non-indexed");
        return this.createMesh(params, instances);
    }
    /** @internal */
    createMeshGraphics(mesh, instances) {
        const meshArgs = mesh.toMeshArgs();
        if (meshArgs) {
            return this.createTriMesh(meshArgs, instances);
        }
        const polylineArgs = mesh.toPolylineArgs();
        return polylineArgs ? this.createIndexedPolylines(polylineArgs, instances) : undefined;
    }
    /** @internal */
    createGeometryFromMesh(mesh, viOrigin, tileData) {
        const meshArgs = mesh.toMeshArgs();
        if (meshArgs) {
            const meshParams = createMeshParams(meshArgs, this.maxTextureSize, IModelApp.tileAdmin.edgeOptions.type !== "non-indexed");
            meshParams.tileData = tileData;
            return this.createMeshGeometry(meshParams, viOrigin);
        }
        const plArgs = mesh.toPolylineArgs();
        if (!plArgs) {
            return undefined;
        }
        if (plArgs.flags.isDisjoint) {
            const psParams = createPointStringParams(plArgs, this.maxTextureSize);
            return psParams ? this.createPointStringGeometry(psParams, viOrigin) : undefined;
        }
        const plParams = createPolylineParams(plArgs, this.maxTextureSize);
        return plParams ? this.createPolylineGeometry(plParams, viOrigin) : undefined;
    }
    /** @internal */
    createIndexedPolylines(args, instances) {
        if (args.flags.isDisjoint) {
            const pointStringParams = createPointStringParams(args, this.maxTextureSize);
            return undefined !== pointStringParams ? this.createPointString(pointStringParams, instances) : undefined;
        }
        else {
            const polylineParams = createPolylineParams(args, this.maxTextureSize);
            return undefined !== polylineParams ? this.createPolyline(polylineParams, instances) : undefined;
        }
    }
    /** @internal */
    createMeshGeometry(_params, _viewIndependentOrigin) { return undefined; }
    /** @internal */
    createPolylineGeometry(_params, _viewIndependentOrigin) { return undefined; }
    /** @internal */
    createPointStringGeometry(_params, _viewIndependentOrigin) { return undefined; }
    /** @internal */
    createPointCloudGeometry(_args) { return undefined; }
    /** @internal */
    createRealityMeshGeometry(_params, _disableTextureDisposal = false) { return undefined; }
    /** @internal */
    createAreaPattern(_params) { return undefined; }
    /** Create a [[RenderInstances]] from a [[RenderInstancesParams]], to be supplied to [[createGraphicFromTemplate]] via [[CreateGraphicFromTempalateArgs.instances]].
     * @beta
     */
    createRenderInstances(_params) { return undefined; }
    createGraphicFromGeometry(createGeometry, instancesOrOrigin) {
        let viOrigin;
        let instances;
        if (instancesOrOrigin instanceof Point3d)
            viOrigin = instancesOrOrigin;
        else
            instances = instancesOrOrigin;
        const geom = createGeometry(viOrigin);
        return geom ? this.createRenderGraphic(geom, instances) : undefined;
    }
    /** @internal */
    createMesh(params, instances) {
        return this.createGraphicFromGeometry((viOrigin) => this.createMeshGeometry(params, viOrigin), instances);
    }
    /** @internal */
    createPolyline(params, instances) {
        return this.createGraphicFromGeometry((origin) => this.createPolylineGeometry(params, origin), instances);
    }
    /** @internal */
    createPointString(params, instances) {
        return this.createGraphicFromGeometry((origin) => this.createPointStringGeometry(params, origin), instances);
    }
    /** @internal */
    createTerrainMesh(_params, _transform, _disableTextureDisposal = false) {
        return undefined;
    }
    /** @internal */
    createRealityMeshGraphic(_params, _disableTextureDisposal = false) { return undefined; }
    /** @internal */
    createRealityMesh(realityMesh, disableTextureDisposal = false) {
        const geom = this.createRealityMeshGeometry(realityMesh, disableTextureDisposal);
        return geom ? this.createRenderGraphic(geom) : undefined;
    }
    /** @internal */
    get maxRealityImageryLayers() { return 0; }
    /** @internal */
    createPointCloud(args, _imodel) {
        const geom = this.createPointCloudGeometry(args);
        return geom ? this.createRenderGraphic(geom) : undefined;
    }
    /** Create a clip volume to clip geometry.
     * @note The clip volume takes ownership of the ClipVector, which must not be subsequently mutated.
     * @param _clipVector Defines how the volume clips geometry.
     * @returns A clip volume, or undefined if, e.g., the clip vector does not clip anything.
     */
    createClipVolume(_clipVector) { return undefined; }
    /** @internal */
    createPlanarGrid(_frustum, _grid) { return undefined; }
    /** @internal */
    createBackgroundMapDrape(_drapedTree, _mapTree) { return undefined; }
    /** @internal */
    createTile(tileTexture, corners, featureIndex) {
        // corners
        // [0] [1]
        // [2] [3]
        // Quantize the points according to their range
        const points = new QPoint3dList(QParams3d.fromRange(Range3d.create(...corners)));
        for (let i = 0; i < 4; i++)
            points.add(corners[i]);
        // Now remove the translation from the quantized points and put it into a transform instead.
        // This prevents graphical artifacts when quantization origin is large relative to quantization scale.
        // ###TODO: Would be better not to create a branch for every tile.
        const qorigin = points.params.origin;
        const transform = Transform.createTranslationXYZ(qorigin.x, qorigin.y, qorigin.z);
        qorigin.setZero();
        const features = new FeatureIndex();
        if (undefined !== featureIndex) {
            features.featureID = featureIndex;
            features.type = FeatureIndexType.Uniform;
        }
        const rasterTile = {
            points,
            vertIndices: [0, 1, 2, 2, 1, 3],
            isPlanar: true,
            features,
            colors: new ColorIndex(),
            fillFlags: FillFlags.None,
            textureMapping: {
                uvParams: [new Point2d(0, 0), new Point2d(1, 0), new Point2d(0, 1), new Point2d(1, 1)],
                texture: tileTexture,
            },
        };
        const trimesh = this.createTriMesh(rasterTile);
        if (undefined === trimesh)
            return undefined;
        const branch = new GraphicBranch(true);
        branch.add(trimesh);
        return this.createBranch(branch, transform);
    }
    /** Create a Graphic for a [[SkyBox]] which encompasses the entire scene, rotating with the camera.
     * @internal
     */
    createSkyBox(_params) { return undefined; }
    /** Create a RenderGraphic consisting of a list of Graphics, with optional transform and symbology overrides applied to the list */
    createBranch(branch, transform, options) {
        return this.createGraphicBranch(branch, transform, options);
    }
    /** Create a node in the scene graph corresponding to a transform node in the scene's schedule script.
     * Nodes under this branch will only be drawn if they belong to the specified transform node.
     * This allows the graphics in a single Tile to be efficiently drawn with different transforms applied by different nodes.
     * The node Id is either the Id of a single transform node in the script, of 0xffffffff to indicate all nodes that have no transform applied to them.
     * @internal
     */
    createAnimationTransformNode(graphic, _nodeId) {
        return graphic;
    }
    /** Return a Promise which when resolved indicates that all pending external textures have finished loading from the backend. */
    async waitForAllExternalTextures() { return Promise.resolve(); }
    /** @internal */
    get hasExternalTextureRequests() { return false; }
    /** Create a graphic that assumes ownership of another graphic.
     * @param ownedGraphic The RenderGraphic to be owned.
     * @returns The owning graphic that exposes a `disposeGraphic` method for explicitly disposing of the owned graphic.
     * @see [[RenderGraphicOwner]] for details regarding ownership semantics.
     * @public
     */
    createGraphicOwner(ownedGraphic) { return new GraphicOwner(ownedGraphic); }
    /** Create a "layer" containing the graphics belonging to it. A layer has a unique identifier and all of its geometry lies in an XY plane.
     * Different layers can be drawn coincident with one another; their draw order can be controlled by a per-layer priority value so that one layer draws
     * on top of another. Layers cannot nest inside other layers. Multiple GraphicLayers can exist with the same ID; they are treated as belonging to the same layer.
     * A GraphicLayer must be contained (perhaps indirectly) inside a GraphicLayerContainer.
     * @see [[createGraphicLayerContainer]]
     * @internal
     */
    createGraphicLayer(graphic, _layerId) { return graphic; }
    /** Create a graphic that can contain [[GraphicLayer]]s.
     * @internal
     */
    createGraphicLayerContainer(graphic, _drawAsOverlay, _transparency, _elevation) { return graphic; }
    /** Find a previously-created [[RenderTexture]] by its key.
     * @param _key The unique key of the texture within the context of the IModelConnection. Typically an element Id.
     * @param _imodel The IModelConnection with which the texture is associated.
     * @returns A previously-created texture matching the specified key, or undefined if no such texture exists.
     */
    findTexture(_key, _imodel) {
        return undefined;
    }
    /** Find or create a [[RenderTexture]] from a persistent texture element.
     * @param id The ID of the texture element.
     * @param iModel The IModel containing the texture element.
     * @returns A Promise resolving to the created RenderTexture or to undefined if the texture could not be created.
     * @note If the texture is successfully created, it will be cached on the IModelConnection such that it can later be retrieved by its ID using [[RenderSystem.findTexture]].
     * @see [[RenderSystem.loadTextureImage]].
     */
    async loadTexture(id, iModel) {
        let texture = this.findTexture(id.toString(), iModel);
        if (undefined === texture) {
            const image = await this.loadTextureImage(id, iModel);
            if (undefined !== image) {
                // This will return a pre-existing RenderTexture if somebody else loaded it while we were awaiting the image.
                texture = this.createTexture({
                    type: RenderTexture.Type.Normal,
                    ownership: { key: id, iModel },
                    image: {
                        source: image.image,
                        transparency: ImageSourceFormat.Png === image.format ? TextureTransparency.Mixed : TextureTransparency.Opaque,
                    },
                });
            }
        }
        return texture;
    }
    /**
     * Load a texture image given the ID of a texture element.
     * @param id The ID of the texture element.
     * @param iModel The IModel containing the texture element.
     * @returns A Promise resolving to a TextureImage created from the texture element's data, or to undefined if the TextureImage could not be created.
     * @see [[RenderSystem.loadTexture]]
     * @internal
     */
    async loadTextureImage(id, iModel) {
        const elemProps = await iModel.elements.getProps(id);
        if (1 !== elemProps.length)
            return undefined;
        const textureProps = elemProps[0];
        if (undefined === textureProps.data || "string" !== typeof (textureProps.data) || undefined === textureProps.format || "number" !== typeof (textureProps.format))
            return undefined;
        const format = textureProps.format;
        if (!isValidImageSourceFormat(format))
            return undefined;
        const imageSource = new ImageSource(base64StringToUint8Array(textureProps.data), format);
        const image = await imageElementFromImageSource(imageSource);
        return { image, format };
    }
    /** Obtain a texture created from a gradient.
     * @param _symb The description of the gradient.
     * @param _imodel The IModelConnection with which the texture is associated.
     * @returns A texture created from the gradient image, or undefined if the texture could not be created.
     * @note If a texture matching the specified gradient is already cached on the iModel, it will be returned.
     * Otherwise, if an iModel is supplied, the newly-created texture will be cached on the iModel such that subsequent calls with an equivalent gradient and the
     * same iModel will return the cached texture instead of creating a new one.
     */
    getGradientTexture(_symb, _imodel) {
        return undefined;
    }
    /** Create a texture from an ImageSource. */
    async createTextureFromSource(args) {
        try {
            // JPEGs don't support transparency.
            const transparency = ImageSourceFormat.Jpeg === args.source.format ? TextureTransparency.Opaque : (args.transparency ?? TextureTransparency.Mixed);
            const image = await imageElementFromImageSource(args.source);
            if (!IModelApp.hasRenderSystem)
                return undefined;
            return this.createTexture({
                type: args.type,
                ownership: args.ownership,
                image: {
                    source: image,
                    transparency,
                },
            });
        }
        catch {
            return undefined;
        }
    }
    /** Create a new texture by its element ID. This texture will be retrieved asynchronously from the backend. A placeholder image will be associated with the texture until the requested image data loads. */
    createTextureFromElement(_id, _imodel, _params, _format) {
        return undefined;
    }
    createTexture(_args) {
        return undefined;
    }
    /** Create a new texture from a cube of HTML images.
     * @internal
     */
    createTextureFromCubeImages(_posX, _negX, _posY, _negY, _posZ, _negZ, _imodel, _params) {
        return undefined;
    }
    /** @internal */
    onInitialized() { }
    /** @internal */
    get supportsLogZBuffer() { return false !== this.options.logarithmicDepthBuffer; }
    /** Obtain an object that can be used to control various debugging features. Returns `undefined` if debugging features are unavailable for this `RenderSystem`.
     * @internal
     */
    get debugControl() { return undefined; }
    /** @internal */
    collectStatistics(_stats) { }
    /** A function that is invoked after the WebGL context is lost. Context loss is almost always caused by excessive consumption of GPU memory.
     * After context loss occurs, the RenderSystem will be unable to interact with WebGL by rendering viewports, creating graphics and textures, etc.
     * By default, this function invokes [[ToolAdmin.exceptionHandler]] with a brief message describing what occurred.
     * An application can override this behavior as follows:
     * ```ts
     * RenderSystem.contextLossHandler = (): Promise<any> => {
     *  // your implementation here.
     * }
     * ```
     * @note Context loss is reported by the browser some short time *after* it has occurred. It is not possible to determine the specific cause.
     * @see [[TileAdmin.gpuMemoryLimit]] to limit the amount of GPU memory consumed thereby reducing the likelihood of context loss.
     * @see [[TileAdmin.totalTileContentBytes]] for the amount of GPU memory allocated for tile graphics.
     */
    static async contextLossHandler() {
        const msg = IModelApp.localization.getLocalizedString("iModelJs:Errors.WebGLContextLost");
        return ToolAdmin.exceptionHandler(msg);
    }
    /** Convert a [[GraphicDescription]] produced by a [[GraphicDescriptionBuilder]] into a [[RenderGraphic]].
     * @beta
     */
    createGraphicFromDescription(args) {
        return createGraphicFromDescription(args.description, args.context, this);
    }
    /** Convert a [[GraphicDescription]] produced by a [[GraphicDescriptionBuilder]] into a [[GraphicTemplate]].
     * @beta
     */
    createTemplateFromDescription(args) {
        return createGraphicTemplateFromDescription(args.description, args.context, this);
    }
    /** Obtain the JSON representation of a [[WorkerGraphicDescriptionContext]] for the specified `iModel` that can be forwarded to a Worker for use with a [[GraphicDescriptionBuilder]].
     * @beta
     */
    createWorkerGraphicDescriptionContextProps(iModel) {
        const props = {
            [_implementationProhibited]: undefined,
            transientIds: iModel.transientIds.fork(),
            constraints: {
                [_implementationProhibited]: undefined,
                maxTextureSize: this.maxTextureSize,
            },
        };
        return props;
    }
    /** Synchronize changes made to a [[WorkerGraphicDescriptionContext]] on a Worker with the state of the `iModel` from which it was created.
     * @beta
     */
    async resolveGraphicDescriptionContext(props, iModel) {
        const impl = props;
        if (typeof impl.transientIds !== "object" || !Array.isArray(impl.textures)) {
            throw new Error("Invalid GraphicDescriptionContextProps");
        }
        if (impl.resolved) {
            throw new Error("resolveGraphicDescriptionContext can only be called once for a given GraphicDescriptionContextProps");
        }
        const textures = new Map();
        await Promise.allSettled(impl.textures.map(async (tex, i) => {
            let texture;
            if (tex.source.gradient) {
                texture = this.getGradientTexture(Gradient.Symb.fromJSON(tex.source.gradient));
            }
            else if (tex.source.imageSource) {
                texture = await this.createTextureFromSource({
                    source: new ImageSource(tex.source.imageSource, tex.source.format),
                    type: tex.type,
                    transparency: tex.transparency,
                });
            }
            else if (tex.source.imageBuffer) {
                texture = this.createTexture({
                    type: tex.type,
                    image: {
                        source: ImageBuffer.create(tex.source.imageBuffer, tex.source.format, tex.source.width),
                        transparency: tex.transparency,
                    },
                });
            }
            else if (tex.source.url) {
                const image = await tryImageElementFromUrl(tex.source.url);
                if (image) {
                    texture = this.createTexture({
                        type: tex.type,
                        image: {
                            source: image,
                            transparency: tex.transparency,
                        },
                    });
                }
            }
            if (texture) {
                textures.set(i.toString(10), texture);
            }
        }));
        const remap = iModel.transientIds.merge(impl.transientIds);
        impl.resolved = true;
        return {
            [_implementationProhibited]: undefined,
            remapTransientLocalId: (source) => remap(source),
            [_textures]: textures,
        };
    }
}
//# sourceMappingURL=RenderSystem.js.map