@itwin/core-frontend
Version:
iTwin.js frontend components
847 lines (846 loc) • 128 kB
JavaScript
"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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SceneCompositor = void 0;
exports.collectTextureStatistics = collectTextureStatistics;
exports.collectGeometryStatistics = collectGeometryStatistics;
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 webgl_compatibility_1 = require("@itwin/webgl-compatibility");
const Pixel_1 = require("../../../render/Pixel");
const BranchState_1 = require("./BranchState");
const CachedGeometry_1 = require("./CachedGeometry");
const Diagnostics_1 = require("./Diagnostics");
const DrawCommand_1 = require("./DrawCommand");
const FrameBuffer_1 = require("./FrameBuffer");
const GL_1 = require("./GL");
const IModelFrameLifecycle_1 = require("./IModelFrameLifecycle");
const Matrix_1 = require("./Matrix");
const RenderFlags_1 = require("./RenderFlags");
const RenderState_1 = require("./RenderState");
const ScratchDrawParams_1 = require("./ScratchDrawParams");
const SolarShadowMap_1 = require("./SolarShadowMap");
const System_1 = require("./System");
const Texture_1 = require("./Texture");
const RenderBuffer_1 = require("./RenderBuffer");
const EDL_1 = require("./EDL");
function collectTextureStatistics(texture, stats) {
if (undefined !== texture)
stats.addTextureAttachment(texture.bytesUsed);
}
function collectMsBufferStatistics(msBuff, stats) {
if (undefined !== msBuff)
stats.addTextureAttachment(msBuff.bytesUsed);
}
// Maintains the textures used by a SceneCompositor. The textures are reallocated when the dimensions of the viewport change.
class Textures {
accumulation;
revealage;
color;
featureId;
depthAndOrder;
depthAndOrderHidden; // only used if AO and multisampling
contours;
contoursMsBuff;
hilite;
occlusion;
occlusionBlur;
volClassBlend;
colorMsBuff;
featureIdMsBuff;
featureIdMsBuffHidden;
depthAndOrderMsBuff;
depthAndOrderMsBuffHidden;
hiliteMsBuff;
volClassBlendMsBuff;
get isDisposed() {
return undefined === this.accumulation
&& undefined === this.revealage
&& undefined === this.color
&& undefined === this.featureId
&& undefined === this.depthAndOrder
&& undefined === this.contours
&& undefined === this.contoursMsBuff
&& undefined === this.depthAndOrderHidden
&& undefined === this.hilite
&& undefined === this.occlusion
&& undefined === this.occlusionBlur
&& undefined === this.volClassBlend
&& undefined === this.colorMsBuff
&& undefined === this.featureIdMsBuff
&& undefined === this.featureIdMsBuffHidden
&& undefined === this.depthAndOrderMsBuff
&& undefined === this.depthAndOrderMsBuffHidden
&& undefined === this.hiliteMsBuff
&& undefined === this.volClassBlendMsBuff;
}
[Symbol.dispose]() {
this.accumulation = (0, core_bentley_1.dispose)(this.accumulation);
this.revealage = (0, core_bentley_1.dispose)(this.revealage);
this.color = (0, core_bentley_1.dispose)(this.color);
this.featureId = (0, core_bentley_1.dispose)(this.featureId);
this.depthAndOrder = (0, core_bentley_1.dispose)(this.depthAndOrder);
this.contours = (0, core_bentley_1.dispose)(this.contours);
this.contoursMsBuff = (0, core_bentley_1.dispose)(this.contoursMsBuff);
this.depthAndOrderHidden = (0, core_bentley_1.dispose)(this.depthAndOrderHidden);
this.hilite = (0, core_bentley_1.dispose)(this.hilite);
this.occlusion = (0, core_bentley_1.dispose)(this.occlusion);
this.occlusionBlur = (0, core_bentley_1.dispose)(this.occlusionBlur);
this.colorMsBuff = (0, core_bentley_1.dispose)(this.colorMsBuff);
this.featureIdMsBuff = (0, core_bentley_1.dispose)(this.featureIdMsBuff);
this.featureIdMsBuffHidden = (0, core_bentley_1.dispose)(this.featureIdMsBuffHidden);
this.depthAndOrderMsBuff = (0, core_bentley_1.dispose)(this.depthAndOrderMsBuff);
this.depthAndOrderMsBuffHidden = (0, core_bentley_1.dispose)(this.depthAndOrderMsBuffHidden);
this.hiliteMsBuff = (0, core_bentley_1.dispose)(this.hiliteMsBuff);
this.volClassBlend = (0, core_bentley_1.dispose)(this.volClassBlend);
this.volClassBlendMsBuff = (0, core_bentley_1.dispose)(this.volClassBlendMsBuff);
}
collectStatistics(stats) {
collectTextureStatistics(this.accumulation, stats);
collectTextureStatistics(this.revealage, stats);
collectTextureStatistics(this.color, stats);
collectTextureStatistics(this.featureId, stats);
collectTextureStatistics(this.depthAndOrder, stats);
collectTextureStatistics(this.contours, stats);
collectMsBufferStatistics(this.contoursMsBuff, stats);
collectTextureStatistics(this.depthAndOrderHidden, stats);
collectTextureStatistics(this.hilite, stats);
collectTextureStatistics(this.occlusion, stats);
collectTextureStatistics(this.occlusionBlur, stats);
collectTextureStatistics(this.volClassBlend, stats);
collectMsBufferStatistics(this.colorMsBuff, stats);
collectMsBufferStatistics(this.featureIdMsBuff, stats);
collectMsBufferStatistics(this.featureIdMsBuffHidden, stats);
collectMsBufferStatistics(this.depthAndOrderMsBuff, stats);
collectMsBufferStatistics(this.depthAndOrderMsBuffHidden, stats);
collectMsBufferStatistics(this.hiliteMsBuff, stats);
collectMsBufferStatistics(this.volClassBlendMsBuff, stats);
}
init(width, height, numSamples) {
(0, core_bentley_1.assert)(undefined === this.accumulation);
let pixelDataType = GL_1.GL.Texture.DataType.UnsignedByte;
switch (System_1.System.instance.maxRenderType) {
case webgl_compatibility_1.RenderType.TextureFloat: {
pixelDataType = GL_1.GL.Texture.DataType.Float;
break;
}
case webgl_compatibility_1.RenderType.TextureHalfFloat: {
pixelDataType = System_1.System.instance.context.HALF_FLOAT;
break;
}
/* falls through */
case webgl_compatibility_1.RenderType.TextureUnsignedByte: {
break;
}
}
// NB: Both of these must be of the same type, because they are borrowed by pingpong and bound to the same frame buffer.
this.accumulation = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, pixelDataType);
this.revealage = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, pixelDataType);
// Hilite texture is a simple on-off, but the smallest texture format WebGL allows us to use as output is RGBA with a byte per component.
this.hilite = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
this.color = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
this.featureId = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
this.depthAndOrder = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
this.contours = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
let rVal = undefined !== this.accumulation
&& undefined !== this.revealage
&& undefined !== this.color
&& undefined !== this.featureId
&& undefined !== this.depthAndOrder
&& undefined !== this.contours
&& undefined !== this.hilite;
if (rVal && numSamples > 1) {
rVal = this.enableMultiSampling(width, height, numSamples);
}
return rVal;
}
enableOcclusion(width, height, numSamples) {
(0, core_bentley_1.assert)(undefined === this.occlusion && undefined === this.occlusionBlur);
this.occlusion = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
this.occlusionBlur = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
let rVal = undefined !== this.occlusion && undefined !== this.occlusionBlur;
if (numSamples > 1) {
// If multisampling then we need a texture for storing depth and order for hidden edges.
this.depthAndOrderHidden = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
rVal = rVal && undefined !== this.depthAndOrderHidden;
}
return rVal;
}
disableOcclusion() {
(0, core_bentley_1.assert)(undefined !== this.occlusion && undefined !== this.occlusionBlur);
this.occlusion = (0, core_bentley_1.dispose)(this.occlusion);
this.occlusionBlur = (0, core_bentley_1.dispose)(this.occlusionBlur);
this.depthAndOrderHidden = (0, core_bentley_1.dispose)(this.depthAndOrderHidden);
}
enableVolumeClassifier(width, height, numSamples) {
(0, core_bentley_1.assert)(undefined === this.volClassBlend);
this.volClassBlend = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte);
let rVal = undefined !== this.volClassBlend;
if (rVal && undefined !== numSamples && numSamples > 1) {
this.volClassBlendMsBuff = RenderBuffer_1.RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples);
rVal = undefined !== this.volClassBlendMsBuff;
}
return rVal;
}
disableVolumeClassifier() {
this.volClassBlend = (0, core_bentley_1.dispose)(this.volClassBlend);
this.volClassBlendMsBuff = (0, core_bentley_1.dispose)(this.volClassBlendMsBuff);
}
enableMultiSampling(width, height, numSamples) {
this.colorMsBuff = RenderBuffer_1.RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples);
this.featureIdMsBuff = RenderBuffer_1.RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples);
this.featureIdMsBuffHidden = RenderBuffer_1.RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples);
this.depthAndOrderMsBuff = RenderBuffer_1.RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples);
this.depthAndOrderMsBuffHidden = RenderBuffer_1.RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples);
this.contoursMsBuff = RenderBuffer_1.RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples);
this.hiliteMsBuff = RenderBuffer_1.RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples);
return undefined !== this.colorMsBuff
&& undefined !== this.featureIdMsBuff
&& undefined !== this.featureIdMsBuffHidden
&& undefined !== this.depthAndOrderMsBuff
&& undefined !== this.depthAndOrderMsBuffHidden
&& undefined !== this.contoursMsBuff
&& undefined !== this.hiliteMsBuff;
}
disableMultiSampling() {
this.colorMsBuff = (0, core_bentley_1.dispose)(this.colorMsBuff);
this.featureIdMsBuff = (0, core_bentley_1.dispose)(this.featureIdMsBuff);
this.featureIdMsBuffHidden = (0, core_bentley_1.dispose)(this.featureIdMsBuffHidden);
this.depthAndOrderMsBuff = (0, core_bentley_1.dispose)(this.depthAndOrderMsBuff);
this.depthAndOrderMsBuffHidden = (0, core_bentley_1.dispose)(this.depthAndOrderMsBuffHidden);
this.contoursMsBuff = (0, core_bentley_1.dispose)(this.contoursMsBuff);
this.hiliteMsBuff = (0, core_bentley_1.dispose)(this.hiliteMsBuff);
return true;
}
}
// Maintains the framebuffers used by a SceneCompositor. The color attachments are supplied by a Textures object.
class FrameBuffers {
opaqueColor;
opaqueAndCompositeColor;
depthAndOrder;
contours;
hilite;
hiliteUsingStencil;
stencilSet;
occlusion;
occlusionBlur;
altZOnly;
volClassCreateBlend;
volClassCreateBlendAltZ;
opaqueAll;
opaqueAndCompositeAll;
opaqueAndCompositeAllHidden;
pingPong;
pingPongMS;
translucent;
clearTranslucent;
idsAndZ;
idsAndAltZ;
idsAndZComposite;
idsAndAltZComposite;
edlDrawCol;
init(textures, depth, depthMS) {
if (!this.initPotentialMSFbos(textures, depth, depthMS))
return false;
this.depthAndOrder = FrameBuffer_1.FrameBuffer.create([textures.depthAndOrder], depth);
this.contours = FrameBuffer_1.FrameBuffer.create([textures.contours], depth);
this.hilite = FrameBuffer_1.FrameBuffer.create([textures.hilite], depth);
this.hiliteUsingStencil = FrameBuffer_1.FrameBuffer.create([textures.hilite], depth);
if (!this.depthAndOrder || !this.contours || !this.hilite || !this.hiliteUsingStencil)
return false;
(0, core_bentley_1.assert)(undefined === this.opaqueAll);
if (!this.initPotentialMSMRTFbos(textures, depth, depthMS))
return false;
(0, core_bentley_1.assert)(undefined !== textures.accumulation && undefined !== textures.revealage);
const colors = [textures.accumulation, textures.revealage];
this.translucent = FrameBuffer_1.FrameBuffer.create(colors, depth);
this.clearTranslucent = FrameBuffer_1.FrameBuffer.create(colors);
// We borrow the SceneCompositor's accum and revealage textures for the surface pass.
// First we render edges, writing to our textures.
// Then we copy our textures to borrowed textures.
// Finally we render surfaces, writing to our textures and reading from borrowed textures.
(0, core_bentley_1.assert)(undefined !== textures.accumulation && undefined !== textures.revealage);
const pingPong = [textures.accumulation, textures.revealage];
this.pingPong = FrameBuffer_1.FrameBuffer.create(pingPong);
return undefined !== this.translucent
&& undefined !== this.clearTranslucent
&& undefined !== this.pingPong;
}
initPotentialMSFbos(textures, depth, depthMS) {
const boundColor = System_1.System.instance.frameBufferStack.currentColorBuffer;
(0, core_bentley_1.assert)(undefined !== boundColor && undefined !== textures.color);
if (undefined === depthMS) {
this.opaqueColor = FrameBuffer_1.FrameBuffer.create([boundColor], depth);
this.opaqueAndCompositeColor = FrameBuffer_1.FrameBuffer.create([textures.color], depth);
}
else {
(0, core_bentley_1.assert)(undefined !== textures.colorMsBuff);
this.opaqueColor = FrameBuffer_1.FrameBuffer.create([boundColor], depth, [textures.colorMsBuff], [GL_1.GL.MultiSampling.Filter.Linear], depthMS);
this.opaqueAndCompositeColor = FrameBuffer_1.FrameBuffer.create([textures.color], depth, [textures.colorMsBuff], [GL_1.GL.MultiSampling.Filter.Linear], depthMS);
}
return undefined !== this.opaqueColor
&& undefined !== this.opaqueAndCompositeColor;
}
initPotentialMSMRTFbos(textures, depth, depthMs) {
const boundColor = System_1.System.instance.frameBufferStack.currentColorBuffer;
(0, core_bentley_1.assert)(undefined !== boundColor &&
undefined !== textures.color &&
undefined !== textures.featureId &&
undefined !== textures.depthAndOrder &&
undefined !== textures.contours &&
undefined !== textures.accumulation &&
undefined !== textures.revealage);
const colorAndPick = [boundColor, textures.featureId, textures.depthAndOrder, textures.contours];
if (undefined === depthMs) {
this.opaqueAll = FrameBuffer_1.FrameBuffer.create(colorAndPick, depth);
colorAndPick[0] = textures.color;
this.opaqueAndCompositeAll = FrameBuffer_1.FrameBuffer.create(colorAndPick, depth);
}
else {
(0, core_bentley_1.assert)(undefined !== textures.colorMsBuff &&
undefined !== textures.featureIdMsBuff &&
undefined !== textures.featureIdMsBuffHidden &&
undefined !== textures.contoursMsBuff &&
undefined !== textures.depthAndOrderMsBuff &&
undefined !== textures.depthAndOrderMsBuffHidden);
const colorAndPickMsBuffs = [textures.colorMsBuff, textures.featureIdMsBuff, textures.depthAndOrderMsBuff, textures.contoursMsBuff];
const colorAndPickFilters = [GL_1.GL.MultiSampling.Filter.Linear, GL_1.GL.MultiSampling.Filter.Nearest, GL_1.GL.MultiSampling.Filter.Nearest, GL_1.GL.MultiSampling.Filter.Nearest];
this.opaqueAll = FrameBuffer_1.FrameBuffer.create(colorAndPick, depth, colorAndPickMsBuffs, colorAndPickFilters, depthMs);
colorAndPick[0] = textures.color;
this.opaqueAndCompositeAll = FrameBuffer_1.FrameBuffer.create(colorAndPick, depth, colorAndPickMsBuffs, colorAndPickFilters, depthMs);
}
return undefined !== this.opaqueAll
&& undefined !== this.opaqueAndCompositeAll;
}
enableOcclusion(textures, depth, depthMs) {
(0, core_bentley_1.assert)(undefined !== textures.occlusion && undefined !== textures.occlusionBlur);
this.occlusion = FrameBuffer_1.FrameBuffer.create([textures.occlusion]);
this.occlusionBlur = FrameBuffer_1.FrameBuffer.create([textures.occlusionBlur]);
let rVal = undefined !== this.occlusion && undefined !== this.occlusionBlur;
if (undefined === depthMs) {
// If not using multisampling then we can use the accumulation and revealage textures for the hidden pick buffers,
(0, core_bentley_1.assert)(undefined !== textures.color && undefined !== textures.accumulation && undefined !== textures.revealage);
const colorAndPick = [textures.color, textures.accumulation, textures.revealage];
this.opaqueAndCompositeAllHidden = FrameBuffer_1.FrameBuffer.create(colorAndPick, depth);
rVal = rVal && undefined !== this.opaqueAndCompositeAllHidden;
}
else {
// If multisampling then we cannot use the revealage texture for depthAndOrder for the hidden edges since it is of the wrong type for blitting,
// so instead use a special depthAndOrderHidden texture just for this purpose.
// The featureId texture is not needed for hidden edges, so the accumulation texture can be used for it if we don't blit from the multisample bufffer into it.
(0, core_bentley_1.assert)(undefined !== textures.color && undefined !== textures.accumulation && undefined !== textures.depthAndOrderHidden);
(0, core_bentley_1.assert)(undefined !== textures.colorMsBuff && undefined !== textures.featureIdMsBuffHidden && undefined !== textures.depthAndOrderMsBuffHidden);
const colorAndPick = [textures.color, textures.accumulation, textures.depthAndOrderHidden];
const colorAndPickMsBuffs = [textures.colorMsBuff, textures.featureIdMsBuffHidden, textures.depthAndOrderMsBuffHidden];
const colorAndPickFilters = [GL_1.GL.MultiSampling.Filter.Linear, GL_1.GL.MultiSampling.Filter.Nearest, GL_1.GL.MultiSampling.Filter.Nearest];
this.opaqueAndCompositeAllHidden = FrameBuffer_1.FrameBuffer.create(colorAndPick, depth, colorAndPickMsBuffs, colorAndPickFilters, depthMs);
// We will also need a frame buffer for copying the real pick data buffers into these hidden edge pick data buffers.
const pingPong = [textures.accumulation, textures.depthAndOrderHidden];
const pingPongMSBuffs = [textures.featureIdMsBuffHidden, textures.depthAndOrderMsBuffHidden];
const pingPongFilters = [GL_1.GL.MultiSampling.Filter.Nearest, GL_1.GL.MultiSampling.Filter.Nearest];
this.pingPongMS = FrameBuffer_1.FrameBuffer.create(pingPong, depth, pingPongMSBuffs, pingPongFilters, depthMs);
rVal = rVal && undefined !== this.opaqueAndCompositeAllHidden && (undefined === depthMs || undefined !== this.pingPongMS);
}
return rVal;
}
disableOcclusion() {
if (undefined !== this.occlusion) {
this.occlusion = (0, core_bentley_1.dispose)(this.occlusion);
this.occlusionBlur = (0, core_bentley_1.dispose)(this.occlusionBlur);
}
this.opaqueAndCompositeAllHidden = (0, core_bentley_1.dispose)(this.opaqueAndCompositeAllHidden);
this.pingPongMS = (0, core_bentley_1.dispose)(this.pingPongMS);
}
enableVolumeClassifier(textures, depth, volClassDepth, depthMS, volClassDepthMS) {
const boundColor = System_1.System.instance.frameBufferStack.currentColorBuffer;
if (undefined === boundColor)
return;
if (undefined === this.stencilSet) {
if (undefined !== depthMS) { // if multisampling use the multisampled depth everywhere
this.stencilSet = FrameBuffer_1.FrameBuffer.create([], depth, [], [], depthMS);
this.altZOnly = FrameBuffer_1.FrameBuffer.create([], volClassDepth, [], [], volClassDepthMS);
this.volClassCreateBlend = FrameBuffer_1.FrameBuffer.create([textures.volClassBlend], depth, [textures.volClassBlendMsBuff], [GL_1.GL.MultiSampling.Filter.Nearest], depthMS);
this.volClassCreateBlendAltZ = FrameBuffer_1.FrameBuffer.create([textures.volClassBlend], volClassDepth, [textures.volClassBlendMsBuff], [GL_1.GL.MultiSampling.Filter.Nearest], volClassDepthMS);
}
else if (undefined !== volClassDepth) {
this.stencilSet = FrameBuffer_1.FrameBuffer.create([], depth);
this.altZOnly = FrameBuffer_1.FrameBuffer.create([], volClassDepth);
this.volClassCreateBlend = FrameBuffer_1.FrameBuffer.create([textures.volClassBlend], depth);
this.volClassCreateBlendAltZ = FrameBuffer_1.FrameBuffer.create([textures.volClassBlend], volClassDepth);
}
}
if (undefined !== this.opaqueAll && undefined !== this.opaqueAndCompositeAll) {
if (undefined !== volClassDepth) {
let ids = [this.opaqueAll.getColor(0), this.opaqueAll.getColor(1)];
this.idsAndZ = FrameBuffer_1.FrameBuffer.create(ids, depth);
this.idsAndAltZ = FrameBuffer_1.FrameBuffer.create(ids, volClassDepth);
ids = [this.opaqueAndCompositeAll.getColor(0), this.opaqueAndCompositeAll.getColor(1)];
this.idsAndZComposite = FrameBuffer_1.FrameBuffer.create(ids, depth);
this.idsAndAltZComposite = FrameBuffer_1.FrameBuffer.create(ids, volClassDepth);
}
}
}
disableVolumeClassifier() {
if (undefined !== this.stencilSet) {
this.stencilSet = (0, core_bentley_1.dispose)(this.stencilSet);
this.altZOnly = (0, core_bentley_1.dispose)(this.altZOnly);
this.volClassCreateBlend = (0, core_bentley_1.dispose)(this.volClassCreateBlend);
this.volClassCreateBlendAltZ = (0, core_bentley_1.dispose)(this.volClassCreateBlendAltZ);
}
if (undefined !== this.idsAndZ) {
this.idsAndZ = (0, core_bentley_1.dispose)(this.idsAndZ);
this.idsAndAltZ = (0, core_bentley_1.dispose)(this.idsAndAltZ);
this.idsAndZComposite = (0, core_bentley_1.dispose)(this.idsAndZComposite);
this.idsAndAltZComposite = (0, core_bentley_1.dispose)(this.idsAndAltZComposite);
}
}
enableMultiSampling(textures, depth, depthMS) {
this.opaqueColor = (0, core_bentley_1.dispose)(this.opaqueColor);
this.opaqueAndCompositeColor = (0, core_bentley_1.dispose)(this.opaqueAndCompositeColor);
let rVal = this.initPotentialMSFbos(textures, depth, depthMS);
this.opaqueAll = (0, core_bentley_1.dispose)(this.opaqueAll);
this.opaqueAndCompositeAll = (0, core_bentley_1.dispose)(this.opaqueAndCompositeAll);
rVal = this.initPotentialMSMRTFbos(textures, depth, depthMS);
return rVal;
}
disableMultiSampling(textures, depth) {
this.opaqueAll = (0, core_bentley_1.dispose)(this.opaqueAll);
this.opaqueAndCompositeAll = (0, core_bentley_1.dispose)(this.opaqueAndCompositeAll);
if (!this.initPotentialMSMRTFbos(textures, depth, undefined))
return false;
this.opaqueColor = (0, core_bentley_1.dispose)(this.opaqueColor);
this.opaqueAndCompositeColor = (0, core_bentley_1.dispose)(this.opaqueAndCompositeColor);
return this.initPotentialMSFbos(textures, depth, undefined);
}
get isDisposed() {
return undefined === this.opaqueColor && undefined === this.opaqueAndCompositeColor && undefined === this.depthAndOrder && undefined === this.contours
&& undefined === this.hilite && undefined === this.hiliteUsingStencil && undefined === this.occlusion
&& undefined === this.occlusionBlur && undefined === this.stencilSet && undefined === this.altZOnly
&& undefined === this.volClassCreateBlend && undefined === this.volClassCreateBlendAltZ && undefined === this.opaqueAll
&& undefined === this.opaqueAndCompositeAll && undefined === this.opaqueAndCompositeAllHidden && undefined === this.pingPong
&& undefined === this.pingPongMS && undefined === this.translucent && undefined === this.clearTranslucent
&& undefined === this.idsAndZ && undefined === this.idsAndAltZ && undefined === this.idsAndZComposite
&& undefined === this.idsAndAltZComposite && undefined === this.edlDrawCol;
}
[Symbol.dispose]() {
this.opaqueColor = (0, core_bentley_1.dispose)(this.opaqueColor);
this.opaqueAndCompositeColor = (0, core_bentley_1.dispose)(this.opaqueAndCompositeColor);
this.depthAndOrder = (0, core_bentley_1.dispose)(this.depthAndOrder);
this.contours = (0, core_bentley_1.dispose)(this.contours);
this.hilite = (0, core_bentley_1.dispose)(this.hilite);
this.hiliteUsingStencil = (0, core_bentley_1.dispose)(this.hiliteUsingStencil);
this.occlusion = (0, core_bentley_1.dispose)(this.occlusion);
this.occlusionBlur = (0, core_bentley_1.dispose)(this.occlusionBlur);
this.stencilSet = (0, core_bentley_1.dispose)(this.stencilSet);
this.altZOnly = (0, core_bentley_1.dispose)(this.altZOnly);
this.volClassCreateBlend = (0, core_bentley_1.dispose)(this.volClassCreateBlend);
this.volClassCreateBlendAltZ = (0, core_bentley_1.dispose)(this.volClassCreateBlendAltZ);
this.opaqueAll = (0, core_bentley_1.dispose)(this.opaqueAll);
this.opaqueAndCompositeAll = (0, core_bentley_1.dispose)(this.opaqueAndCompositeAll);
this.opaqueAndCompositeAll = (0, core_bentley_1.dispose)(this.opaqueAndCompositeAllHidden);
this.pingPong = (0, core_bentley_1.dispose)(this.pingPong);
this.pingPongMS = (0, core_bentley_1.dispose)(this.pingPongMS);
this.translucent = (0, core_bentley_1.dispose)(this.translucent);
this.clearTranslucent = (0, core_bentley_1.dispose)(this.clearTranslucent);
this.idsAndZ = (0, core_bentley_1.dispose)(this.idsAndZ);
this.idsAndAltZ = (0, core_bentley_1.dispose)(this.idsAndAltZ);
this.idsAndZComposite = (0, core_bentley_1.dispose)(this.idsAndZComposite);
this.idsAndAltZComposite = (0, core_bentley_1.dispose)(this.idsAndAltZComposite);
this.edlDrawCol = (0, core_bentley_1.dispose)(this.edlDrawCol);
}
}
function collectGeometryStatistics(geom, stats) {
if (undefined !== geom)
geom.collectStatistics(stats);
}
// Maintains the geometry used to execute screenspace operations for a SceneCompositor.
class Geometry {
composite;
volClassColorStencil;
volClassCopyZ;
volClassSetBlend;
volClassBlend;
occlusion;
occlusionXBlur;
occlusionYBlur;
copyPickBuffers;
clearTranslucent;
clearPickAndColor;
collectStatistics(stats) {
collectGeometryStatistics(this.composite, stats);
collectGeometryStatistics(this.volClassColorStencil, stats);
collectGeometryStatistics(this.volClassCopyZ, stats);
collectGeometryStatistics(this.volClassSetBlend, stats);
collectGeometryStatistics(this.volClassBlend, stats);
collectGeometryStatistics(this.occlusion, stats);
collectGeometryStatistics(this.occlusionXBlur, stats);
collectGeometryStatistics(this.occlusionYBlur, stats);
collectGeometryStatistics(this.copyPickBuffers, stats);
collectGeometryStatistics(this.clearTranslucent, stats);
collectGeometryStatistics(this.clearPickAndColor, stats);
}
init(textures) {
(0, core_bentley_1.assert)(undefined === this.composite);
this.composite = CachedGeometry_1.CompositeGeometry.createGeometry(textures.color.getHandle(), textures.accumulation.getHandle(), textures.revealage.getHandle(), textures.hilite.getHandle());
if (undefined === this.composite)
return false;
(0, core_bentley_1.assert)(undefined === this.copyPickBuffers);
this.copyPickBuffers = CachedGeometry_1.CopyPickBufferGeometry.createGeometry(textures.featureId.getHandle(), textures.depthAndOrder.getHandle());
this.clearTranslucent = CachedGeometry_1.ViewportQuadGeometry.create(16 /* TechniqueId.OITClearTranslucent */);
this.clearPickAndColor = CachedGeometry_1.ViewportQuadGeometry.create(21 /* TechniqueId.ClearPickAndColor */);
return undefined !== this.copyPickBuffers && undefined !== this.clearTranslucent && undefined !== this.clearPickAndColor;
}
enableOcclusion(textures, depth) {
(0, core_bentley_1.assert)(undefined !== textures.occlusion && undefined !== textures.occlusionBlur && undefined !== textures.depthAndOrder && undefined !== textures.occlusionBlur);
this.composite.occlusion = textures.occlusion.getHandle();
this.occlusion = CachedGeometry_1.AmbientOcclusionGeometry.createGeometry(textures.depthAndOrder.getHandle(), depth.getHandle());
this.occlusionXBlur = CachedGeometry_1.BlurGeometry.createGeometry(textures.occlusion.getHandle(), textures.depthAndOrder.getHandle(), undefined, new core_geometry_1.Vector2d(1.0, 0.0), CachedGeometry_1.BlurType.NoTest);
const depthAndOrderHidden = (undefined === textures.depthAndOrderHidden ? textures.revealage?.getHandle() : textures.depthAndOrderHidden.getHandle());
this.occlusionYBlur = CachedGeometry_1.BlurGeometry.createGeometry(textures.occlusionBlur.getHandle(), textures.depthAndOrder.getHandle(), depthAndOrderHidden, new core_geometry_1.Vector2d(0.0, 1.0), CachedGeometry_1.BlurType.TestOrder);
}
disableOcclusion() {
this.composite.occlusion = undefined;
this.occlusion = (0, core_bentley_1.dispose)(this.occlusion);
this.occlusionXBlur = (0, core_bentley_1.dispose)(this.occlusionXBlur);
this.occlusionYBlur = (0, core_bentley_1.dispose)(this.occlusionYBlur);
}
enableVolumeClassifier(textures, depth) {
(0, core_bentley_1.assert)(undefined === this.volClassColorStencil && undefined === this.volClassCopyZ && undefined === this.volClassSetBlend && undefined === this.volClassBlend);
this.volClassColorStencil = CachedGeometry_1.ViewportQuadGeometry.create(20 /* TechniqueId.VolClassColorUsingStencil */);
this.volClassCopyZ = CachedGeometry_1.SingleTexturedViewportQuadGeometry.createGeometry(depth.getHandle(), 31 /* TechniqueId.VolClassCopyZ */);
this.volClassSetBlend = CachedGeometry_1.VolumeClassifierGeometry.createVCGeometry(depth.getHandle());
this.volClassBlend = CachedGeometry_1.SingleTexturedViewportQuadGeometry.createGeometry(textures.volClassBlend.getHandle(), 33 /* TechniqueId.VolClassBlend */);
return undefined !== this.volClassColorStencil && undefined !== this.volClassCopyZ && undefined !== this.volClassSetBlend && undefined !== this.volClassBlend;
}
disableVolumeClassifier() {
if (undefined !== this.volClassColorStencil) {
this.volClassColorStencil = (0, core_bentley_1.dispose)(this.volClassColorStencil);
this.volClassCopyZ = (0, core_bentley_1.dispose)(this.volClassCopyZ);
this.volClassSetBlend = (0, core_bentley_1.dispose)(this.volClassSetBlend);
this.volClassBlend = (0, core_bentley_1.dispose)(this.volClassBlend);
}
}
get isDisposed() {
return undefined === this.composite && undefined === this.occlusion && undefined === this.occlusionXBlur
&& undefined === this.occlusionYBlur && undefined === this.volClassColorStencil && undefined === this.volClassCopyZ
&& undefined === this.volClassSetBlend && undefined === this.volClassBlend && undefined === this.copyPickBuffers
&& undefined === this.clearTranslucent && undefined === this.clearPickAndColor;
}
[Symbol.dispose]() {
this.composite = (0, core_bentley_1.dispose)(this.composite);
this.occlusion = (0, core_bentley_1.dispose)(this.occlusion);
this.occlusionXBlur = (0, core_bentley_1.dispose)(this.occlusionXBlur);
this.occlusionYBlur = (0, core_bentley_1.dispose)(this.occlusionYBlur);
this.disableVolumeClassifier();
this.copyPickBuffers = (0, core_bentley_1.dispose)(this.copyPickBuffers);
this.clearTranslucent = (0, core_bentley_1.dispose)(this.clearTranslucent);
this.clearPickAndColor = (0, core_bentley_1.dispose)(this.clearPickAndColor);
}
}
// Represents a view of data read from a region of the frame buffer.
class PixelBuffer {
_rect;
_selector;
_featureId;
_depthAndOrder;
_contours;
_batchState;
_scratchModelFeature = core_common_1.ModelFeature.create();
get _numPixels() { return this._rect.width * this._rect.height; }
getPixelIndex(x, y) {
if (x < this._rect.left || y < this._rect.top)
return this._numPixels;
x -= this._rect.left;
y -= this._rect.top;
if (x >= this._rect.width || y >= this._rect.height)
return this._numPixels;
// NB: View coords have origin at top-left; GL at bottom-left. So our rows are upside-down.
y = this._rect.height - 1 - y;
return y * this._rect.width + x;
}
getPixel32(data, pixelIndex) {
return pixelIndex < data.length ? data[pixelIndex] : undefined;
}
getFeature(pixelIndex, result) {
const featureId = this.getFeatureId(pixelIndex);
return undefined !== featureId ? this._batchState.getFeature(featureId, result) : undefined;
}
getFeatureId(pixelIndex) {
return undefined !== this._featureId ? this.getPixel32(this._featureId, pixelIndex) : undefined;
}
getBatchInfo(pixelIndex) {
const featureId = this.getFeatureId(pixelIndex);
if (undefined !== featureId) {
const batch = this._batchState.find(featureId);
if (undefined !== batch) {
return {
featureTable: batch.featureTable,
iModel: batch.batchIModel,
transformFromIModel: batch.transformFromBatchIModel,
tileId: batch.tileId,
viewAttachmentId: batch.viewAttachmentId,
inSectionDrawingAttachment: batch.inSectionDrawingAttachment,
};
}
}
return undefined;
}
_scratchUint32Array = new Uint32Array(1);
_scratchUint8Array = new Uint8Array(this._scratchUint32Array.buffer);
_scratchVector3d = new core_geometry_1.Vector3d();
_mult = new core_geometry_1.Vector3d(1.0, 1.0 / 255.0, 1.0 / 65025.0);
decodeDepthRgba(depthAndOrder) {
this._scratchUint32Array[0] = depthAndOrder;
const bytes = this._scratchUint8Array;
const fpt = core_geometry_1.Vector3d.create(bytes[1] / 255.0, bytes[2] / 255.0, bytes[3] / 255.0, this._scratchVector3d);
let depth = fpt.dotProduct(this._mult);
(0, core_bentley_1.assert)(0.0 <= depth);
(0, core_bentley_1.assert)(1.01 >= depth); // rounding error...
depth = Math.min(1.0, depth);
depth = Math.max(0.0, depth);
return depth;
}
decodeRenderOrderRgba(depthAndOrder) { return this.decodeUint8(depthAndOrder, 16); }
decodeUint8(rgba32, basis) {
this._scratchUint32Array[0] = rgba32;
const encByte = this._scratchUint8Array[0];
const enc = encByte / 255.0;
const dec = Math.floor(basis * enc + 0.5);
return dec;
}
_invalidPixelData = new Pixel_1.Pixel.Data();
getPixel(x, y) {
const px = this._invalidPixelData;
const index = this.getPixelIndex(x, y);
if (index >= this._numPixels)
return px;
// Initialize to the defaults...
let distanceFraction = px.distanceFraction;
let geometryType = px.type;
let planarity = px.planarity;
const haveFeatureIds = Pixel_1.Pixel.Selector.None !== (this._selector & Pixel_1.Pixel.Selector.Feature);
const feature = haveFeatureIds ? this.getFeature(index, this._scratchModelFeature) : undefined;
const batchInfo = haveFeatureIds ? this.getBatchInfo(index) : undefined;
if (Pixel_1.Pixel.Selector.None !== (this._selector & Pixel_1.Pixel.Selector.GeometryAndDistance) && undefined !== this._depthAndOrder) {
const depthAndOrder = this.getPixel32(this._depthAndOrder, index);
if (undefined !== depthAndOrder) {
distanceFraction = this.decodeDepthRgba(depthAndOrder);
const orderWithPlanarBit = this.decodeRenderOrderRgba(depthAndOrder);
const order = orderWithPlanarBit & ~8 /* RenderOrder.PlanarBit */;
planarity = (orderWithPlanarBit === order) ? Pixel_1.Pixel.Planarity.NonPlanar : Pixel_1.Pixel.Planarity.Planar;
switch (order) {
case 0 /* RenderOrder.None */:
geometryType = Pixel_1.Pixel.GeometryType.None;
planarity = Pixel_1.Pixel.Planarity.None;
break;
case 1 /* RenderOrder.Background */:
case 2 /* RenderOrder.BlankingRegion */:
case 4 /* RenderOrder.LitSurface */:
case 3 /* RenderOrder.UnlitSurface */:
geometryType = Pixel_1.Pixel.GeometryType.Surface;
break;
case 5 /* RenderOrder.Linear */:
geometryType = Pixel_1.Pixel.GeometryType.Linear;
break;
case 6 /* RenderOrder.Edge */:
geometryType = Pixel_1.Pixel.GeometryType.Edge;
break;
case 7 /* RenderOrder.Silhouette */:
geometryType = Pixel_1.Pixel.GeometryType.Silhouette;
break;
default:
// ###TODO: may run into issues with point clouds - they are not written correctly in C++.
(0, core_bentley_1.assert)(false, "Invalid render order");
geometryType = Pixel_1.Pixel.GeometryType.None;
planarity = Pixel_1.Pixel.Planarity.None;
break;
}
}
}
let contour;
if (this._contours) {
const contour32 = this.getPixel32(this._contours.data, index);
if (contour32) { // undefined means out of bounds; zero means not a contour.
const groupIndexAndType = this.decodeUint8(contour32, 32);
const groupIndex = groupIndexAndType & ~(8 | 16);
const group = this._contours.display.groups[groupIndex];
if (group) {
const elevationFraction = this.decodeDepthRgba(contour32);
let elevation = elevationFraction * (this._contours.zHigh - this._contours.zLow) + this._contours.zLow;
// The shader rounds to the nearest contour elevation using single-precision arithmetic.
// Re-round here using double-precision to get closer.
const interval = group.contourDef.minorInterval;
elevation = (elevation >= 0 ? Math.floor((elevation + interval / 2) / interval) : Math.ceil((elevation - interval / 2) / interval)) * interval;
contour = {
group,
elevation,
isMajor: groupIndexAndType > 15,
};
}
}
}
let featureTable, iModel, transformToIModel, tileId, viewAttachmentId, inSectionDrawingAttachment;
if (undefined !== batchInfo) {
featureTable = batchInfo.featureTable;
iModel = batchInfo.iModel;
transformToIModel = batchInfo.transformFromIModel;
tileId = batchInfo.tileId;
viewAttachmentId = batchInfo.viewAttachmentId;
inSectionDrawingAttachment = batchInfo.inSectionDrawingAttachment;
}
return new Pixel_1.Pixel.Data({
feature,
distanceFraction,
type: geometryType,
planarity,
batchType: featureTable?.type,
iModel,
transformFromIModel: transformToIModel,
tileId,
viewAttachmentId,
inSectionDrawingAttachment,
contour,
});
}
constructor(rect, selector, compositor) {
this._rect = rect.clone();
this._selector = selector;
this._batchState = compositor.target.uniforms.batch.state;
if (Pixel_1.Pixel.Selector.None !== (selector & Pixel_1.Pixel.Selector.GeometryAndDistance)) {
const depthAndOrderBytes = compositor.readDepthAndOrder(rect);
if (undefined !== depthAndOrderBytes)
this._depthAndOrder = new Uint32Array(depthAndOrderBytes.buffer);
else
this._selector &= ~Pixel_1.Pixel.Selector.GeometryAndDistance;
}
if (Pixel_1.Pixel.Selector.None !== (selector & Pixel_1.Pixel.Selector.Feature)) {
const features = compositor.readFeatureIds(rect);
if (undefined !== features)
this._featureId = new Uint32Array(features.buffer);
else
this._selector &= ~Pixel_1.Pixel.Selector.Feature;
}
// Note: readContours is a no-op unless contours are actually being drawn.
if (Pixel_1.Pixel.Selector.None !== (selector & Pixel_1.Pixel.Selector.Contours)) {
this._contours = compositor.readContours(rect);
}
}
get isEmpty() { return Pixel_1.Pixel.Selector.None === this._selector; }
static create(rect, selector, compositor) {
const pdb = new PixelBuffer(rect, selector, compositor);
return pdb.isEmpty ? undefined : pdb;
}
}
/** Orchestrates rendering of the scene on behalf of a Target.
* This base class exists only so we don't have to export all the types of the shared Compositor members like Textures, FrameBuffers, etc.
* @internal
*/
class SceneCompositor {
target;
solarShadowMap;
eyeDomeLighting;
_needHiddenEdges;
get needHiddenEdges() { return this._needHiddenEdges; }
constructor(target) {
this.target = target;
this.solarShadowMap = new SolarShadowMap_1.SolarShadowMap(target);
this.eyeDomeLighting = new EDL_1.EyeDomeLighting(target);
this._needHiddenEdges = false;
}
static create(target) {
return new Compositor(target);
}
}
exports.SceneCompositor = SceneCompositor;
// This describes what types of primitives a compositor should draw. See the `drawPrimitive` method of Compositor.
var PrimitiveDrawState;
(function (PrimitiveDrawState) {
PrimitiveDrawState[PrimitiveDrawState["Both"] = 0] = "Both";
PrimitiveDrawState[PrimitiveDrawState["Pickable"] = 1] = "Pickable";
PrimitiveDrawState[PrimitiveDrawState["NonPickable"] = 2] = "NonPickable";
})(PrimitiveDrawState || (PrimitiveDrawState = {}));
// The actual base class. Specializations are provided based on whether or not multiple render targets are supported.
class Compositor extends SceneCompositor {
_width = -1;
_height = -1;
_includeOcclusion = false;
_textures = new Textures();
_depth;
_depthMS; // multisample depth buffer
_fbos;
_geom;
_readPickDataFromPingPong = true;
_opaqueRenderState = new RenderState_1.RenderState();
_layerRenderState = new RenderState_1.RenderState();
_translucentRenderState = new RenderState_1.RenderState();
_hiliteRenderState = new RenderState_1.RenderState();
_noDepthMaskRenderState = new RenderState_1.RenderState();
_backgroundMapRenderState = new RenderState_1.RenderState();
_pointCloudRenderState = new RenderState_1.RenderState();
_debugStencil = 0; // 0 to draw stencil volumes normally, 1 to draw as opaque, 2 to draw blended
_vcBranchState;
_vcSetStencilRenderState;
_vcCopyZRenderState;
_vcColorRenderState;
_vcBlendRenderState;
_vcPickDataRenderState;
_vcDebugRenderState;
_vcAltDepthStencil;
_vcAltDepthStencilMS;
_haveVolumeClassifier = false;
_antialiasSamples = 1;
_viewProjectionMatrix = new Matrix_1.Matrix4();
_primitiveDrawState = PrimitiveDrawState.Both; // used by drawPrimitive to decide whether a primitive needs to be drawn.
forceBufferChange() { this._width = this._height = -1; }
get featureIds() { return this.getSamplerTexture(this._readPickDataFromPingPong ? 0 : 1); }
get depthAndOrder() { return this.getSamplerTexture(this._readPickDataFromPingPong ? 1 : 2); }
get _samplerFbo() { return this._readPickDataFromPingPong ? this._fbos.pingPong : this._fbos.opaqueAll; }
getSamplerTexture(index) { return this._samplerFbo.getColor(index); }
drawPrimitive(primitive, exec, outputsToPick) {
if ((outputsToPick && this._primitiveDrawState !== PrimitiveDrawState.NonPickable) ||
(!outputsToPick && this._primitiveDrawState !== PrimitiveDrawState.Pickable))
primitive.draw(exec);
}
clearOpaque(needComposite) {
const fbo = needComposite ? this._fbos.opaqueAndCompositeAll : this._fbos.opaqueAll;
const system = System_1.System.instance;
system.frameBufferStack.execute(fbo, true, this.useMsBuffers, () => {
// Clear pick data buffers to 0's and color buffer to background color
// (0,0,0,0) in elementID0 and ElementID1 buffers indicates invalid element id
// (0,0,0,0) in DepthAndOrder buffer indicates render order 0 and encoded depth of 0 (= far plane)
system.applyRenderState(this._noDepthMaskRenderState);
const params = (0, ScratchDrawParams_1.getDrawParams)(this.target, this._geom.clearPickAndColor);
this.target.techniques.draw(params);
// Clear depth buffer
system.applyRenderState(RenderState_1.RenderState.defaults); // depthMask == true.
system.context.clearDepth(1.0);
system.context.clear(GL_1.GL.BufferBit.Depth);
});
}
renderLayers(commands, needComposite, pass) {
const fbo = (needComposite ? this._fbos.opaqueAndCompositeAll : this._fbos.opaqueAll);
const useMsBuffers = 1 /* RenderPass.OpaqueLayers */ === pass && fbo.isMultisampled && this.useMsBuffers;
this._readPickDataFromPingPong = !useMsBuffers;
System_1.System.instance.frameBufferStack.execute(fbo, true, useMsBuffers, () => {
this.drawPass(commands, pass, true);
});
this._readPickDataFromPingPong = false;
}
renderOpaque(commands, compositeFlags, renderForReadPixels) {
if (0 /* CompositeFlags.None */ !== (compositeFlags & 4 /* CompositeFlags.AmbientOcclusion */) && !renderForReadPixels) {
this.renderOpaqueAO(commands);
return;
}
const needComposite = 0 /* CompositeFlags.None */ !== compositeFlags;
const fbStack = System_1.System.instance.frameBufferStack;
// Output the first 2 passes to color and pick data buffers. (All 3 in the case of rendering for readPixels() or ambient occlusion).
let fbo = (needComposite ? this._fbos.opaqueAndCompositeAll : this._fbos.opaqueAll);
const useMsBuffers = fbo.isMultisampled && this.useMsBuffers;
this._readPickDataFromPingPong = !useMsBuffers; // if multisampling then can read pick textures directly.