diff --git a/packages/scene/src/SceneMesh.ts b/packages/scene/src/SceneMesh.ts index 7b8bc48ff..1fbc343d4 100644 --- a/packages/scene/src/SceneMesh.ts +++ b/packages/scene/src/SceneMesh.ts @@ -49,6 +49,11 @@ export class SceneMesh { */ object: SceneObject | null; + /** + * TODO + */ + streamLayerIndex: number; + #color: FloatArrayParam; #matrix: FloatArrayParam; #metallic: number; @@ -70,6 +75,7 @@ export class SceneMesh { roughness?: number; metallic?: number; origin?: FloatArrayParam; + streamLayerIndex?: number; }) { this.id = meshParams.id; this.#matrix = meshParams.matrix ? createMat4(meshParams.matrix) : identityMat4(); @@ -81,6 +87,7 @@ export class SceneMesh { this.roughness = (meshParams.roughness !== null && meshParams.roughness !== undefined) ? meshParams.roughness : 1; this.opacity = (meshParams.opacity !== undefined && meshParams.opacity !== null) ? meshParams.opacity : 1.0; this.origin = new Float32Array(meshParams.origin !== undefined ? meshParams.origin : [0, 0, 0]); + this.streamLayerIndex = meshParams.streamLayerIndex !== undefined ? meshParams.streamLayerIndex : 0; } /** @@ -242,6 +249,7 @@ export class SceneMesh { */ getJSON(): SceneMeshParams { const meshParams = { + streamLayerIndex: this.streamLayerIndex || 0, id: this.id, geometryId: this.geometry.id, color: Array.from(this.#color), diff --git a/packages/scene/src/SceneMeshParams.ts b/packages/scene/src/SceneMeshParams.ts index 2d4a04676..d4409fe69 100755 --- a/packages/scene/src/SceneMeshParams.ts +++ b/packages/scene/src/SceneMeshParams.ts @@ -5,6 +5,11 @@ import type {FloatArrayParam} from "@xeokit/math"; */ export interface SceneMeshParams { + /** + * TODO + */ + streamLayerIndex?: number; + /** * ID for the new {@link @xeokit/scene!SceneMesh}, unique within the {@link @xeokit/scene!SceneModel}. */ @@ -21,7 +26,7 @@ export interface SceneMeshParams { geometryId: string; /** - * Optional ID of a {@link Transform} previously created with {@link @xeokit/scene!SceneModel.createTransform | SceneModel.createTransform}. + * Optional ID of a {@link SceneTransform} previously created with {@link @xeokit/scene!SceneModel.createTransform | SceneModel.createTransform}. */ transformId?: string; @@ -97,4 +102,4 @@ export interface SceneMeshParams { * Optional RTC coordinate origin for the new {@link @xeokit/scene!SceneMesh}. */ origin?:FloatArrayParam; -} \ No newline at end of file +} diff --git a/packages/scene/src/SceneModel.ts b/packages/scene/src/SceneModel.ts index d799cb4c6..487a26bb5 100644 --- a/packages/scene/src/SceneModel.ts +++ b/packages/scene/src/SceneModel.ts @@ -22,6 +22,7 @@ import type {SceneModelParams} from "./SceneModelParams"; import type {Scene} from "./Scene"; import type {SceneModelStats} from "./SceneModelStats"; import {composeMat4, eulerToQuat, identityMat4, identityQuat} from "@xeokit/matrix"; +import {SceneModelStreamParams} from "./SceneModelStreamParams"; // DTX texture types @@ -88,6 +89,14 @@ TEXTURE_ENCODING_OPTIONS[OCCLUSION_TEXTURE] = { */ export class SceneModel extends Component { + /** + * Indicates what renderer resources will need to be allocated in a {@link @xeokit/viewer!Viewer | Viewer's} + * {@link @xeokit/viewer!Renderer | Renderer} to support progressive loading for the {@link @xeokit/scene!SceneModel | SceneModel}. + * + * See {@link "@xeokit/scene" | @xeokit/scene} for usage. + */ + public streamParams?: SceneModelStreamParams; + /** * The {@link @xeokit/scene!Scene} that contains this SceneModel. */ @@ -222,6 +231,7 @@ export class SceneModel extends Component { this.#numObjects = 0; this.#meshUsedByObject = {}; + this.streamParams = sceneModelParams.streamParams; this.id = sceneModelParams.id || "default"; this.layerId = sceneModelParams.layerId; this.edgeThreshold = 10; @@ -952,7 +962,9 @@ export class SceneModel extends Component { meshes: [], objects: [] }; - + if (this.streamParams) { + sceneModelParams.streamParams = this.streamParams; + } Object.entries(this.geometries).forEach(([key, value]) => { sceneModelParams.geometriesCompressed.push((value).getJSON()); }); diff --git a/packages/scene/src/SceneModelParams.ts b/packages/scene/src/SceneModelParams.ts index 741628350..56abb3a91 100755 --- a/packages/scene/src/SceneModelParams.ts +++ b/packages/scene/src/SceneModelParams.ts @@ -3,10 +3,11 @@ import type {FloatArrayParam} from "@xeokit/math"; import type {SceneGeometryCompressedParams} from "./SceneGeometryCompressedParams"; import type {SceneTextureParams} from "./SceneTextureParams"; import type {SceneTextureSetParams} from "./SceneTextureSetParams"; -import type {SceneObject} from "./SceneObject"; + import type {SceneMeshParams} from "./SceneMeshParams"; import type {SceneGeometryParams} from "./SceneGeometryParams"; import type {SceneObjectParams} from "./SceneObjectParams"; +import {SceneModelStreamParams} from "./SceneModelStreamParams"; /** @@ -16,6 +17,13 @@ import type {SceneObjectParams} from "./SceneObjectParams"; */ export interface SceneModelParams { + + /** + * Indicates what renderer resources will need to be allocated in a {@link @xeokit/viewer!Viewer | Viewer's} + * {@link @xeokit/viewer!Renderer | Renderer} to support progressive loading for a {@link @xeokit/scene!SceneModel | SceneModel}. + */ + streamParams?: SceneModelStreamParams; + /** * Unique ID for the SceneModel. * diff --git a/packages/scene/src/SceneModelStreamLayerParams.ts b/packages/scene/src/SceneModelStreamLayerParams.ts new file mode 100644 index 000000000..6ebcbe9b0 --- /dev/null +++ b/packages/scene/src/SceneModelStreamLayerParams.ts @@ -0,0 +1,33 @@ + +/** + * Indicates storage to allocate in a renderer layer. + * + * Stored in {@link @scene/SceneModelStreamParams.layers | SceneModelStreamParams.layers}. + */ +export interface SceneModelStreamLayerParams { + + /** + * Number of 32-bit geometry indices to allocate in the renderer layer. + */ + numIndices32Bits: number; + + /** + * Number of 16-bit geometry indices to allocate in the renderer layer. + */ + numIndices16Bits: number; + + /** + * Number of 8-bit geometry indices to allocate in the renderer layer. + */ + numIndices8Bits: number; + + /** + * Number of geometry vertices to allocate in the renderer layer. + */ + numVertices: number; + + /** + * Number of submeshes to allocate in the renderer layer. + */ + numSubMeshes: number; +} diff --git a/packages/scene/src/SceneModelStreamParams.ts b/packages/scene/src/SceneModelStreamParams.ts new file mode 100644 index 000000000..16b30b2b8 --- /dev/null +++ b/packages/scene/src/SceneModelStreamParams.ts @@ -0,0 +1,17 @@ +import {SceneModelStreamLayerParams} from "./SceneModelStreamLayerParams"; + +/** + * Indicates what renderer resources will need to be allocated in a {@link @xeokit/viewer!Viewer | Viewer's} + * {@link @xeokit/viewer!Renderer | Renderer} to support progressive loading for a {@link @xeokit/scene!SceneModel | SceneModel}. + * + * See {@link "@xeokit/scene" | @xeokit/scene} for usage. + */ +export interface SceneModelStreamParams { + + /** + * Indicates what renderer layers will need to be allocated. + */ + streamLayers: SceneModelStreamLayerParams[]; +} + + diff --git a/packages/scene/src/index.ts b/packages/scene/src/index.ts index 505428be5..eae52003d 100644 --- a/packages/scene/src/index.ts +++ b/packages/scene/src/index.ts @@ -379,6 +379,10 @@ * const buffers = thetexture.buffers; // ArrayBuffer[] * ```` * + * ### Preparing a SceneModel for Progressive Loading + * + * TODO: Create in separate tutorial - too complex for here + * * @module @xeokit/scene */ @@ -410,4 +414,7 @@ export * from "./SceneGeometryParams"; export * from "./SceneModelParams"; export * from "./compressGeometryParams"; -export * from "./getSceneObjectGeometry"; \ No newline at end of file +export * from "./getSceneObjectGeometry"; + +export * from "./SceneModelStreamParams"; +export * from "./SceneModelStreamLayerParams"; diff --git a/packages/webglrenderer/src/Layer.ts b/packages/webglrenderer/src/Layer.ts index aeb35885a..ac4acf8ad 100644 --- a/packages/webglrenderer/src/Layer.ts +++ b/packages/webglrenderer/src/Layer.ts @@ -3,7 +3,6 @@ import {MeshCounts} from "./MeshCounts"; import {SceneGeometry, SceneGeometryBucket, SceneMesh} from "@xeokit/scene"; import type {LayerParams} from "./LayerParams"; import {LayerMeshParams} from "./LayerMeshParams"; -import {RenderContext} from "./RenderContext"; import {WebGLRendererModel} from "./WebGLRendererModel"; import {RenderState} from "./RenderState"; import {TickParams, View, Viewer} from "@xeokit/viewer"; @@ -17,7 +16,6 @@ import {SCENE_OBJECT_FLAGS} from "./SCENE_OBJECT_FLAGS"; import {RENDER_PASSES} from "./RENDER_PASSES"; import {SDKError} from "@xeokit/core"; import {RendererSet} from "./RendererSet"; -import {RenderStats} from "./RenderStats"; const tempMat4a = identityMat4(); const tempUint8Array4 = new Uint8Array(4); @@ -71,8 +69,7 @@ export abstract class Layer { #dataTextureBuffer: DataTextureBuffer; #geometryHandles: { [key: string]: any }; - #deferredSetFlagsActive: boolean; - #deferredSetFlagsDirty: boolean; + #built: boolean; #aabb: FloatArrayParam; aabbDirty: boolean; @@ -82,7 +79,15 @@ export abstract class Layer { #subMeshs: any[]; // A SubMesh has a single GeometryBucket #numSubMeshes: number; #layerNumber: number; - #numUpdatesInFrame: number; + + #deferredSetAttributesActive: boolean; + #deferredSetMatricesActive: boolean; + + #needCommitDeferredAttributes: boolean; + #needCommitDeferedMatrices: boolean; + + #countAttributesUpdateInFrame: number; + #countMatricesUpdateInFrame: number; #onViewerTick: () => void; @@ -99,7 +104,10 @@ export abstract class Layer { this.#layerNumber = numLayers++; this.#dataTextureBuffer = new DataTextureBuffer(); this.#built = false; - this.#numUpdatesInFrame = 0; + + this.#countAttributesUpdateInFrame = 0; + this.#countMatricesUpdateInFrame = 0; + this.#meshes = []; this.#subMeshs = []; this.#numSubMeshes = 0; @@ -122,7 +130,8 @@ export abstract class Layer { numVertices: 0 }; - this.#beginDeferredFlags(); // For faster initialization + this.#beginDeferredAttributes(); // For faster initialization; commit happens in build() + this.#beginDeferredMatrices(); // For faster initialization; commit happens in build() } get hash() { @@ -389,12 +398,18 @@ export abstract class Layer { throw new SDKError("Already built"); } this.renderState.dataTextureSet = new DataTextureSet(this.gl, this.#dataTextureBuffer); - this.#deferredSetFlagsDirty = false; + this.#needCommitDeferredAttributes = false; + this.#needCommitDeferedMatrices = false; this.#onViewerTick = this.rendererModel.viewer.onTick.subscribe((viewer: Viewer, tickParams: TickParams) => { - if (this.#deferredSetFlagsDirty) { - this.#uploadDeferredFlags(); + console.log("@xeokit/webglrenderer!Layer.build() onViewerTick"); + if (this.#needCommitDeferredAttributes) { + this.#commitDeferredAttributes(); + } + if (this.#needCommitDeferedMatrices) { + this.#commitDeferredMatrices(); } - this.#numUpdatesInFrame = 0; + this.#countAttributesUpdateInFrame = 0; + this.#countMatricesUpdateInFrame = 0; }); this.#dataTextureBuffer = null; this.#geometryHandles = {}; @@ -406,6 +421,25 @@ export abstract class Layer { return this.meshCounts.numMeshes === 0; } + /** + * This will _start_ a "set-flags transaction". + * + * After invoking this method, calling meshSetFlags/setMeshFlags2 will not update + * the colors+flags texture but only store the new flags/flag2 in the + * colors+flags texture data array. + * + * After invoking this method, and when all desired meshSetFlags/setMeshFlags2 have + * been called on needed portions of the layer, invoke `#commitDeferredAttributes` + * to actually commit the data array to the texture. + * + * In massive "set-flags" scenarios like VFC or LOD mechanisms, the combination of + * `_beginDeferredAttributes` + `#commitDeferredAttributes`brings a speed-up of + * up to 80x when e.g. objects are massively (un)culled 🚀. + */ + #beginDeferredAttributes() { + this.#deferredSetAttributesActive = true; + } + setLayerMeshFlags(meshIndex: number, flags: number, meshTransparent: boolean) { if (flags & SCENE_OBJECT_FLAGS.VISIBLE) { this.meshCounts.numVisible++; @@ -448,9 +482,9 @@ export abstract class Layer { this.#setMeshFlags2(meshIndex, flags, deferred); } - commitLayerMeshFlags() { - this.#commitDeferredFlags(); - this.#commitDeferredFlags2(); + commitRendererState() { + this.#commitDeferredAttributes(); + this.#commitDeferredMatrices(); } setLayerMeshVisible(meshIndex: number, flags: number, transparent: boolean) { @@ -537,65 +571,6 @@ export abstract class Layer { this.#setMeshFlags2(meshIndex, flags); } - /** - * This will _start_ a "set-flags transaction". - * - * After invoking this method, calling meshSetFlags/setMeshFlags2 will not update - * the colors+flags texture but only store the new flags/flag2 in the - * colors+flags texture data array. - * - * After invoking this method, and when all desired meshSetFlags/setMeshFlags2 have - * been called on needed portions of the layer, invoke `#uploadDeferredFlags` - * to actually upload the data array into the texture. - * - * In massive "set-flags" scenarios like VFC or LOD mechanisms, the combination of - * `_beginDeferredFlags` + `#uploadDeferredFlags`brings a speed-up of - * up to 80x when e.g. objects are massively (un)culled 🚀. - */ - #beginDeferredFlags() { - this.#deferredSetFlagsActive = true; - } - - /** - * This will _commit_ a "set-flags transaction". - * - * Invoking this method will update the colors+flags texture data with new - * flags/flags2 set since the previous invocation of `_beginDeferredFlags`. - */ - #uploadDeferredFlags() { - this.#deferredSetFlagsActive = false; - if (!this.#deferredSetFlagsDirty) { - return; - } - this.#deferredSetFlagsDirty = false; - const gl = this.gl; - const dataTextureSet = this.renderState.dataTextureSet; - gl.bindTexture(gl.TEXTURE_2D, dataTextureSet.subMeshAttributesDataTexture.texture); - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, // level - 0, // xoffset - 0, // yoffset - dataTextureSet.subMeshAttributesDataTexture.textureWidth, // width - dataTextureSet.subMeshAttributesDataTexture.textureHeight, // width - gl.RGBA_INTEGER, - gl.UNSIGNED_BYTE, - dataTextureSet.subMeshAttributesDataTexture.textureData - ); - // gl.bindTexture(gl.TEXTURE_2D, dataTextureSet.subMeshInstanceMatricesDataTexture._texture); - // gl.texSubImage2D( - // gl.TEXTURE_2D, - // 0, // level - // 0, // xoffset - // 0, // yoffset - // dataTextureSet.subMeshInstanceMatricesDataTexture._textureWidth, // width - // dataTextureSet.subMeshInstanceMatricesDataTexture._textureHeight, // width - // gl.RGB, - // gl.FLOAT, - // dataTextureSet.subMeshInstanceMatricesDataTexture._textureData - // ); - } - setLayerMeshCulled(meshIndex: number, flags: number, transparent: boolean) { if (!this.#built) { throw new SDKError("Not finalized"); @@ -648,15 +623,14 @@ export abstract class Layer { tempUint8Array4 [2] = color[2]; tempUint8Array4 [3] = color[3]; textureState.subMeshAttributesDataTexture.textureData.set(tempUint8Array4, subMeshIndex * 32); - if (this.#deferredSetFlagsActive) { - // console.info("_setSubMeshColor defer"); - this.#deferredSetFlagsDirty = true; + if (this.#deferredSetAttributesActive) { + this.#needCommitDeferredAttributes = true; return; } - if (++this.#numUpdatesInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { - this.#beginDeferredFlags(); // Subsequent flags updates now deferred + if (++this.#countAttributesUpdateInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { + this.#beginDeferredAttributes(); // Subsequent flags updates now deferred } - // console.info("_setSubMeshColor write through"); + // console.info("_setSubMeshColor write through"); gl.bindTexture(gl.TEXTURE_2D, textureState.subMeshAttributesDataTexture.texture); gl.texSubImage2D( gl.TEXTURE_2D, @@ -763,12 +737,12 @@ export abstract class Layer { tempUint8Array4 [3] = f3; // sceneMesh flags dataTextureSet.subMeshAttributesDataTexture.textureData.set(tempUint8Array4, subMeshIndex * 32 + 8); - if (this.#deferredSetFlagsActive || deferred) { - this.#deferredSetFlagsDirty = true; + if (this.#deferredSetAttributesActive || deferred) { + this.#needCommitDeferredAttributes = true; return; } - if (++this.#numUpdatesInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { - this.#beginDeferredFlags(); // Subsequent flags updates now deferred + if (++this.#countAttributesUpdateInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { + this.#beginDeferredAttributes(); // Subsequent flags updates now deferred } gl.bindTexture(gl.TEXTURE_2D, dataTextureSet.subMeshAttributesDataTexture.texture); gl.texSubImage2D( @@ -785,9 +759,6 @@ export abstract class Layer { // gl.bindTexture (gl.TEXTURE_2D, null); } - #commitDeferredFlags() { - } - #setMeshFlags2(meshIndex: number, flags: number, deferred = false) { const subMeshIndices = this.#meshToSubMeshLookup[meshIndex]; for (let i = 0, len = subMeshIndices.length; i < len; i++) { @@ -808,13 +779,12 @@ export abstract class Layer { tempUint8Array4 [3] = 2; // sceneMesh flags2 textureState.subMeshAttributesDataTexture.textureData.set(tempUint8Array4, subMeshIndex * 32 + 12); - if (this.#deferredSetFlagsActive || deferred) { - // console.log("_setSubMeshFlags2 set flags defer"); - this.#deferredSetFlagsDirty = true; + if (this.#deferredSetAttributesActive || deferred) { + this.#needCommitDeferredAttributes = true; return; } - if (++this.#numUpdatesInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { - this.#beginDeferredFlags(); // Subsequent flags updates now deferred + if (++this.#countAttributesUpdateInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { + this.#beginDeferredAttributes(); // Subsequent flags updates now deferred } gl.bindTexture(gl.TEXTURE_2D, textureState.subMeshAttributesDataTexture.texture); gl.texSubImage2D( @@ -831,7 +801,27 @@ export abstract class Layer { // gl.bindTexture (gl.TEXTURE_2D, null); } - #commitDeferredFlags2() { + #commitDeferredAttributes() { + console.log("@xeokit/webglrenderer!Layer.commitDeferredAttributes()"); + this.#deferredSetAttributesActive = false; + if (!this.#needCommitDeferredAttributes) { + return; + } + this.#needCommitDeferredAttributes = false; + const gl = this.gl; + const subMeshAttributesDataTexture = this.renderState.dataTextureSet.subMeshAttributesDataTexture; + gl.bindTexture(gl.TEXTURE_2D, subMeshAttributesDataTexture.texture); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, // level + 0, // xoffset + 0, // yoffset + subMeshAttributesDataTexture.textureWidth, // width + subMeshAttributesDataTexture.textureHeight, // width + gl.RGBA_INTEGER, + gl.UNSIGNED_BYTE, + subMeshAttributesDataTexture.textureData + ); } setLayerMeshOffset(meshIndex: number, offset: FloatArrayParam) { @@ -840,11 +830,11 @@ export abstract class Layer { } const subMeshIndices = this.#meshToSubMeshLookup[meshIndex]; for (let i = 0, len = subMeshIndices.length; i < len; i++) { - this.#subMeshSetOffset(subMeshIndices[i], offset); + this.#setSubMeshOffset(subMeshIndices[i], offset); } } - #subMeshSetOffset(subMeshIndex: number, offset: FloatArrayParam) { + #setSubMeshOffset(subMeshIndex: number, offset: FloatArrayParam) { // if (!this.#built) { // throw new SDKError("Not finalized"); // } @@ -859,12 +849,12 @@ export abstract class Layer { // tempFloat32Array3 [2] = offset[2]; // // sceneMesh offset // textureState.texturePerObjectOffsets._textureData.set(tempFloat32Array3, subMeshIndex * 3); - // if (this.#deferredSetFlagsActive) { - // this.#deferredSetFlagsDirty = true; + // if (this.#deferredSetAttributesActive) { + // this.#needCommitDeferredAttributes = true; // return; // } - // if (++this.#numUpdatesInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { - // this.#beginDeferredFlags(); // Subsequent flags updates now deferred + // if (++this.#countAttributesUpdateInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { + // this.#beginDeferredAttributes(); // Subsequent flags updates now deferred // } // gl.bindTexture(gl.TEXTURE_2D, textureState.texturePerObjectOffsets._texture); // gl.texSubImage2D( @@ -881,17 +871,26 @@ export abstract class Layer { // // gl.bindTexture (gl.TEXTURE_2D, null); } + + //---------------------------------------------------------------------------------- + // Mesh Matrices + //---------------------------------------------------------------------------------- + + #beginDeferredMatrices() { + this.#deferredSetMatricesActive = true; + } + setLayerMeshMatrix(meshIndex: number, matrix: FloatArrayParam) { if (!this.#built) { throw new SDKError("Not finalized"); } const subMeshIndices = this.#meshToSubMeshLookup[meshIndex]; for (let i = 0, len = subMeshIndices.length; i < len; i++) { - this.#subMeshSetMatrix(subMeshIndices[i], matrix); + this.#setSubMeshMatrix(subMeshIndices[i], matrix); } } - #subMeshSetMatrix(subMeshIndex: number, matrix: FloatArrayParam) { + #setSubMeshMatrix(subMeshIndex: number, matrix: FloatArrayParam) { // if (!this.model.scene.entityMatrixsEnabled) { // this.model.error("Entity#matrix not enabled for this Viewer"); // See Viewer entityMatrixsEnabled // return; @@ -900,12 +899,12 @@ export abstract class Layer { const gl = this.gl; tempMat4a.set(matrix); textureState.subMeshInstanceMatricesDataTexture.textureData.set(tempMat4a, subMeshIndex * 16); - if (this.#deferredSetFlagsActive) { - this.#deferredSetFlagsDirty = true; + if (this.#deferredSetMatricesActive) { + this.#needCommitDeferedMatrices = true; return; } - if (++this.#numUpdatesInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { - this.#beginDeferredFlags(); // Subsequent flags updates now deferred + if (++this.#countMatricesUpdateInFrame >= MAX_MESH_UPDATES_PER_FRAME_WITHOUT_BATCHED_UPDATE) { + this.#beginDeferredMatrices(); // Subsequent flags updates now deferred } gl.bindTexture(gl.TEXTURE_2D, textureState.subMeshInstanceMatricesDataTexture.texture); gl.texSubImage2D( @@ -923,7 +922,28 @@ export abstract class Layer { // gl.bindTexture (gl.TEXTURE_2D, null); } - abstract draw(rendererSet: RendererSet) : void; + #commitDeferredMatrices() { + console.log("@xeokit/webglrenderer!Layer.commitDeferredMatrices()"); + if (this.#needCommitDeferedMatrices) { + const subMeshInstanceMatricesDataTexture = this.renderState.dataTextureSet.subMeshInstanceMatricesDataTexture; + const gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, subMeshInstanceMatricesDataTexture.texture); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, // level + 0, // xoffset + 0, // yoffset + subMeshInstanceMatricesDataTexture.textureWidth, // width + subMeshInstanceMatricesDataTexture.textureHeight, // width + gl.RGBA, + gl.FLOAT, + subMeshInstanceMatricesDataTexture.textureData + ); + this.#needCommitDeferedMatrices = false; + } + } + + abstract draw(rendererSet: RendererSet): void; destroy() { this.rendererModel.viewer.onTick.unsubscribe(this.#onViewerTick); diff --git a/packages/webglrenderer/src/WebGLRenderer.ts b/packages/webglrenderer/src/WebGLRenderer.ts index f917703b6..455da5abe 100755 --- a/packages/webglrenderer/src/WebGLRenderer.ts +++ b/packages/webglrenderer/src/WebGLRenderer.ts @@ -344,8 +344,14 @@ export class WebGLRenderer implements Renderer { } /** - * TODO + * Detaches a {@link @xeokit/scene!SceneModel | SceneModel} from this WebGLRenderer. + * * @internal + * @returns *void* + * * SceneModel successfully detached. + * @returns *{@link @xeokit/core!SDKError}* + * * No View is currently attached to this WebGLRenderer. + * * SceneModel is not attached to this WebGLRenderer. */ detachSceneModel(sceneModel: SceneModel): SDKError | void { if (!this.#viewer) { @@ -355,7 +361,7 @@ export class WebGLRenderer implements Renderer { throw new SDKError("Can't detach SceneModel from WebGLRenderer - no View is attached"); } if (this.#rendererModels[sceneModel.id] == undefined) { - return new SDKError(`Can't detach SceneModel from WebGLRenderer -no SceneModel with this ID ("${sceneModel.id}") has been attached to this WebGLRenderer`); + return new SDKError(`Can't detach SceneModel from WebGLRenderer - no SceneModel with this ID ("${sceneModel.id}") has been attached to this WebGLRenderer`); } this.#detachSceneModel(sceneModel); } diff --git a/packages/webglrenderer/src/WebGLRendererMesh.ts b/packages/webglrenderer/src/WebGLRendererMesh.ts index 7d18740d6..4b8d1cf08 100755 --- a/packages/webglrenderer/src/WebGLRendererMesh.ts +++ b/packages/webglrenderer/src/WebGLRendererMesh.ts @@ -80,7 +80,7 @@ export class WebGLRendererMesh implements RendererMesh, Pickable { } commitRendererState() { - this.layer.commitLayerMeshFlags(); + this.layer.commitRendererState(); } setVisible(flags: any) { diff --git a/packages/webglrenderer/src/createSceneModelStreamParams.ts b/packages/webglrenderer/src/createSceneModelStreamParams.ts new file mode 100644 index 000000000..3287ec6ce --- /dev/null +++ b/packages/webglrenderer/src/createSceneModelStreamParams.ts @@ -0,0 +1,123 @@ +import { SceneModel, SceneModelStreamParams} from "@xeokit/scene"; +import {LinesPrimitive, TrianglesPrimitive} from "@xeokit/constants"; + + +/** + * 12-bits allowed for object ids. + * Limits the per-mesh texture height in the layer. + */ +const MAX_MESHES_IN_LAYER = (1 << 16); + +/** + * 4096 is max data texture height. + * Limits the aggregated geometry texture height in the layer. + */ +const MAX_DATA_TEXTURE_HEIGHT = 1 << 16; + +/** + * Enables a {@link @xeokit/scene!SceneModel} to be progressively loaded + * into {@link @xeokit/viewer!Viewer} that's configured with a {@link @xeokit/webglrenderer!WebGLRenderer}. + * + * @param sceneModel + */ +export function createSceneModelStreamParams(sceneModel: SceneModel) { + + const layersBeingBuilt = {}; + const layerList = []; + let layerIndex = 0; + + Object.entries(sceneModel.objects).forEach(([objectId, sceneObject]) => { + + for (const sceneMesh of sceneObject.meshes) { + + const sceneGeometry = sceneMesh.geometry; + const textureSetId = sceneMesh.textureSet ? sceneMesh.textureSet.id : ""; + const origin = sceneMesh.origin || [0, 0, 0]; + const layerId = `${textureSetId}.${sceneGeometry.primitive}.${Math.round(origin[0])}.${Math.round(origin[1])}.${Math.round(origin[2])}`; + + let layer = layersBeingBuilt[layerId]; + + let canCreate = false; + + while (!canCreate) { + + if (!layer) { + layerIndex = layerList.length; + layer = { + layerIndex, + hasBucket: {}, + numSubMeshes: 0, + numVertices: 0, + numIndices8Bits: 0, + numIndices16Bits: 0, + numIndices32Bits: 0 + }; + layerList.push(layer); + layersBeingBuilt[layerId] = layer; + } + + const indicesPerPrimitive = sceneGeometry.primitive === TrianglesPrimitive ? 3 : (LinesPrimitive ? 2 : 1); // TODO + + const numSubMeshesToCreate = sceneGeometry.geometryBuckets.length; + + canCreate = (layer.numSubMeshes + numSubMeshesToCreate) <= MAX_MESHES_IN_LAYER; + + const bucketIndex = 0; // TODO: Is this a bug? + const bucketId = `${sceneGeometry.id}#${bucketIndex}`; + const layerHasBucket = layer.hasBucket[bucketId]; + + if (!layerHasBucket) { + + const maxExistingIndicesOfAnyBits = Math.max(layer.numIndices8Bits, layer.numIndices16Bits, layer.numIndices32Bits); + + let numVerticesToCreate = 0; + let numIndicesToCreate = 0; + + sceneGeometry.geometryBuckets.forEach(bucket => { + numVerticesToCreate += bucket.positionsCompressed.length / indicesPerPrimitive; + numIndicesToCreate += bucket.indices.length / indicesPerPrimitive; + }); + + canCreate &&= + (layer.numVertices + numVerticesToCreate) <= MAX_DATA_TEXTURE_HEIGHT * 4096 && + (maxExistingIndicesOfAnyBits + numIndicesToCreate) <= MAX_DATA_TEXTURE_HEIGHT * 4096; + + if (canCreate) { + + layer.numSubMeshes++; + layer.numVertices += numVerticesToCreate; + + // TODO + // layer.numIndices8Bits + + layer.hasBucket[bucketId] = true; + } + } + + if (canCreate) { + sceneMesh.streamLayerIndex = layerIndex; + } else { // Layer is full + delete layersBeingBuilt[layerId]; + layer = null; + } + } + } + }); + + const streamParams: SceneModelStreamParams = { + streamLayers: [] + }; + + for (let i = 0, len = layerList.length; i < len; i++) { + const layer = layerList[i]; + streamParams.streamLayers.push({ + numSubMeshes: layer.numSubMeshes, + numVertices: layer.numVertices, + numIndices8Bits: layer.numIndices8Bits, + numIndices16Bits: layer.numIndices16Bits, + numIndices32Bits: layer.numIndices32Bits + }); + } + + sceneModel.streamParams = streamParams; +} diff --git a/packages/webglrenderer/src/index.ts b/packages/webglrenderer/src/index.ts index 67126a3ac..ca0bd416c 100755 --- a/packages/webglrenderer/src/index.ts +++ b/packages/webglrenderer/src/index.ts @@ -1,7 +1,7 @@ /** * [![npm version](https://badge.fury.io/js/%40xeokit%2Fwebgl.svg)](https://badge.fury.io/js/%40xeokit%2Fwebgl) * [![](https://data.jsdelivr.com/v1/package/npm/@xeokit/webglrenderer/badge)](https://www.jsdelivr.com/package/npm/@xeokit/webglrenderer) - * + * * * * # xeokit WebGL2 Renderer @@ -51,4 +51,5 @@ * * @module @xeokit/webglrenderer */ -export {WebGLRenderer} from "./WebGLRenderer"; \ No newline at end of file +export {WebGLRenderer} from "./WebGLRenderer"; +export {createSceneModelStreamParams} from "./createSceneModelStreamParams";