Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0cc9073
feat(engine): Add GL renderers for BufferPrimitiveCollections
donmccurdy Feb 17, 2026
2f2afaa
build before running tsc
donmccurdy Feb 18, 2026
23b65df
clean up attribute types
donmccurdy Feb 25, 2026
1b52d63
avoid tmp array allocation in renderers
donmccurdy Feb 25, 2026
24ec625
feat(engine): Add styles for BufferPointCollection
donmccurdy Feb 20, 2026
c8ca2a2
tidy up point attribute management
donmccurdy Feb 25, 2026
bcc2a9c
fix point outline appearing when outlineWidth=0
donmccurdy Feb 25, 2026
7fa2641
feat(engine): Add meshline styling for BufferPolylineCollection
donmccurdy Feb 25, 2026
dab8c4a
tidy up polygon attribute management
donmccurdy Feb 25, 2026
b1a075c
fix unit test
donmccurdy Mar 2, 2026
c37037e
chore(BufferPointCollection): clean up point renderer
donmccurdy Mar 3, 2026
ab2076d
BufferPrimitiveCollection: Tidy up renderers
donmccurdy Mar 3, 2026
072f8d4
BufferPrimitiveCollection: Tidy up renderers
donmccurdy Mar 3, 2026
bf049df
Merge branch 'main' into donmccurdy/feat/bufferprimitivecollection-re…
donmccurdy Mar 5, 2026
2f9d0fe
BufferPrimitiveCollection: Add .modelMatrix and .boundingVolumeWC, re…
donmccurdy Mar 5, 2026
b443692
BufferPrimitiveCollection: Fix 'build-ts' task
donmccurdy Mar 5, 2026
8e76838
Merge branch 'main' into donmccurdy/feat/bufferprimitivecollection-re…
danielzhong Mar 9, 2026
5d91189
Merge branch 'main' into donmccurdy/feat/bufferprimitivecollection-re…
donmccurdy Mar 9, 2026
3ab955a
Merge branch 'main' into donmccurdy/feat/bufferprimitivecollection-re…
danielzhong Mar 9, 2026
56bb70e
BufferPrimitiveCollection: Implement .destroy(), GPU resource cleanup
donmccurdy Mar 10, 2026
7fce3db
BufferPrimitiveCollection: Update types to make JSDoc happy
donmccurdy Mar 10, 2026
a471873
BufferPointCollection: Add render unit tests
donmccurdy Mar 10, 2026
283e394
BufferPolygonCollection: Choose index data type automatically
donmccurdy Mar 10, 2026
e5d43a9
BufferPolylineCollection: Add render unit tests
donmccurdy Mar 10, 2026
a6ededf
BufferPolygonCollection: Add render unit tests
donmccurdy Mar 10, 2026
ee98e8a
BufferPrimitiveCollection: Use smaller index types if available
donmccurdy Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: dev
on:
on:
push:
branches:
branches:
- main
pull_request:
concurrency:
Expand All @@ -24,6 +24,8 @@ jobs:
run: npm run markdownlint
- name: format code
run: npm run prettier-check
- name: build
run: npm run build
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to build before running npm run tsc so the type checker can see built outputs from *.glsl files.

- name: tsc
run: npm run tsc
coverage:
Expand Down Expand Up @@ -80,4 +82,4 @@ jobs:
run: npm pack &> /dev/null
- name: package workspace modules
run: npm pack --workspaces &> /dev/null
- uses: ./.github/actions/verify-package
- uses: ./.github/actions/verify-package
2 changes: 2 additions & 0 deletions .github/workflows/prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
run: npm run markdownlint
- name: format code
run: npm run prettier-check
- name: build
run: npm run build
- name: tsc
run: npm run tsc

Expand Down
2 changes: 1 addition & 1 deletion packages/engine/Source/Renderer/RenderState.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ RenderState.fromCache = function (renderState) {
};

/**
* @private
* @ignore
*/
RenderState.removeFromCache = function (renderState) {
const states = new RenderState(renderState);
Expand Down
80 changes: 80 additions & 0 deletions packages/engine/Source/Renderer/VertexArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import Buffer from "./Buffer.js";
import BufferUsage from "./BufferUsage.js";
import ContextLimits from "./ContextLimits.js";
import AttributeType from "../Scene/AttributeType.js";
import assert from "../Core/assert.js";

/** @import {TypedArray, TypedArrayConstructor} from "../Core/globalTypes.js"; */

function addAttribute(attributes, attribute, index, context) {
const hasVertexBuffer = defined(attribute.vertexBuffer);
Expand Down Expand Up @@ -801,6 +804,83 @@ function setConstantAttributes(vertexArray, gl) {
}
}

/**
* Copies into a vertex attribute buffer from the given array, at a given
* range specified as offset and count, in number of (VECN) vertices. Array
* and vertex attribute must have the same length, which can be larger
* than the specified range to update.
* @param {number} attributeIndex
* @param {TypedArray} array
* @param {number} vertexOffset
* @param {number} vertexCount
*/
VertexArray.prototype.copyAttributeFromRange = function (
Copy link
Copy Markdown
Member Author

@donmccurdy donmccurdy Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods could be moved somewhere else, but seemed like they might be generally helpful? The use case here is to hold an array of the same size as the buffer in memory, and to re-upload only the dirty range after updates.

Avoids memory allocation (and potential GC stalls) during rendering. This would be harder to guarantee if the array were created dynamically in the render loop, or sized to only include the updated range of elements.

attributeIndex,
array,
vertexOffset,
vertexCount,
) {
const attribute = this.getAttribute(attributeIndex);
const buffer = /** @type {Buffer} */ (attribute.vertexBuffer);
const elementsPerVertex = attribute.componentsPerAttribute;

//>>includeStart('debug', pragmas.debug);
assert(buffer.sizeInBytes === array.byteLength, "Invalid buffer length");
//>>includeEnd('debug');

const ArrayConstructor = /** @type {TypedArrayConstructor} */ (
array.constructor
);

const byteOffset =
vertexOffset * elementsPerVertex * ArrayConstructor.BYTES_PER_ELEMENT;

// Create a zero-copy ArrayView onto the specified range of the source array.
const rangeArrayView = new ArrayConstructor(
/** @type {ArrayBuffer} */ (array.buffer),
array.byteOffset + byteOffset,
vertexCount * elementsPerVertex,
);

buffer.copyFromArrayView(rangeArrayView, byteOffset);
};

/**
* Copies into the index buffer from the given array, at a given range
* specified as offset and count, in number of (uint) indices. Array
* and index buffer must have the same length, which can be larger
* than the specified range to update.
* @param {TypedArray} array
* @param {number} indexOffset
* @param {number} indexCount
*/
VertexArray.prototype.copyIndexFromRange = function (
array,
indexOffset,
indexCount,
) {
const buffer = /** @type {Buffer} */ (this._indexBuffer);

//>>includeStart('debug', pragmas.debug);
assert(buffer.sizeInBytes === array.byteLength, "Invalid buffer length");
//>>includeEnd('debug');

const ArrayConstructor = /** @type {TypedArrayConstructor} */ (
array.constructor
);

const byteOffset = indexOffset * ArrayConstructor.BYTES_PER_ELEMENT;

// Create a zero-copy ArrayView onto the specified range of the source array.
const rangeArrayView = new ArrayConstructor(
/** @type {ArrayBuffer} */ (array.buffer),
array.byteOffset + byteOffset,
indexCount,
);

buffer.copyFromArrayView(rangeArrayView, byteOffset);
};

VertexArray.prototype._bind = function () {
if (defined(this._vao)) {
this._context.glBindVertexArray(this._vao);
Expand Down
9 changes: 8 additions & 1 deletion packages/engine/Source/Scene/BufferPointCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js";
import BufferPoint from "./BufferPoint.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Frozen from "../Core/Frozen.js";
import renderPoints from "./renderBufferPointCollection.js";

/** @import Color from "../Core/Color.js"; */
/** @import FrameState from "./FrameState.js" */
/** @import Matrix4 from "../Core/Matrix4.js"; */
/** @import FrameState from "./FrameState.js"; */

/**
* @typedef {object} BufferPointOptions
* @property {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
* @property {boolean} [show=true]
* @property {Color} [color=Color.WHITE]
* @property {Cartesian3} [position=Cartesian3.ZERO]
Expand Down Expand Up @@ -116,6 +119,10 @@ class BufferPointCollection extends BufferPrimitiveCollection {
*/
update(frameState) {
super.update(frameState);

if (this.show) {
this._renderContext = renderPoints(this, frameState, this._renderContext);
}
}
}

Expand Down
39 changes: 20 additions & 19 deletions packages/engine/Source/Scene/BufferPolygonCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js";
import BufferPolygon from "./BufferPolygon.js";
import Frozen from "../Core/Frozen.js";
import assert from "../Core/assert.js";
import ComponentDatatype from "../Core/ComponentDatatype.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import renderPolygons from "./renderBufferPolygonCollection.js";

/** @import { TypedArray } from "../Core/globalTypes.js"; */
/** @import Color from "../Core/Color.js"; */
/** @import Matrix4 from "../Core/Matrix4.js"; */
/** @import FrameState from "./FrameState.js" */
/** @import ComponentDatatype from "../Core/ComponentDatatype.js"; */

const { ERR_CAPACITY } = BufferPrimitiveCollection.Error;

/**
* @typedef {object} BufferPolygonOptions
* @property {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
* @property {boolean} [show=true]
* @property {Color} [color=Color.WHITE]
* @property {TypedArray} [positions]
Expand Down Expand Up @@ -73,7 +76,6 @@ class BufferPolygonCollection extends BufferPrimitiveCollection {
* @param {number} [options.holeCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
* @param {number} [options.triangleCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
* @param {ComponentDatatype} [options.positionDatatype=ComponentDatatype.DOUBLE]
* @param {IndexDatatype} [options.indexDatatype=IndexDatatype.UNSIGNED_INT]
* @param {boolean} [options.show=true]
* @param {boolean} [options.debugShowBoundingVolume=false]
*/
Expand Down Expand Up @@ -120,15 +122,8 @@ class BufferPolygonCollection extends BufferPrimitiveCollection {
*/
this._triangleIndexView = null;

this._allocateHoleIndexBuffer(
// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
options.indexDatatype ?? IndexDatatype.UNSIGNED_INT,
);

this._allocateTriangleIndexBuffer(
// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
options.indexDatatype ?? IndexDatatype.UNSIGNED_INT,
);
this._allocateHoleIndexBuffer();
this._allocateTriangleIndexBuffer();
}

_getCollectionClass() {
Expand All @@ -143,27 +138,25 @@ class BufferPolygonCollection extends BufferPrimitiveCollection {
// COLLECTION LIFECYCLE

/**
* @param {IndexDatatype} datatype
* @private
* @ignore
*/
_allocateHoleIndexBuffer(datatype) {
_allocateHoleIndexBuffer() {
// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
this._holeIndexView = ComponentDatatype.createTypedArray(
datatype,
this._holeIndexView = IndexDatatype.createTypedArray(
this._positionCountMax,
this._holeCountMax,
);
}

/**
* @param {IndexDatatype} datatype
* @private
* @ignore
*/
_allocateTriangleIndexBuffer(datatype) {
_allocateTriangleIndexBuffer() {
// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
this._triangleIndexView = ComponentDatatype.createTypedArray(
datatype,
this._triangleIndexView = IndexDatatype.createTypedArray(
this._positionCountMax,
this._triangleCountMax * 3,
);
}
Expand Down Expand Up @@ -292,6 +285,14 @@ class BufferPolygonCollection extends BufferPrimitiveCollection {
*/
update(frameState) {
super.update(frameState);

if (this.show) {
this._renderContext = renderPolygons(
this,
frameState,
this._renderContext,
);
}
}

/////////////////////////////////////////////////////////////////////////////
Expand Down
11 changes: 11 additions & 0 deletions packages/engine/Source/Scene/BufferPolylineCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import defined from "../Core/defined.js";
import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js";
import BufferPolyline from "./BufferPolyline.js";
import renderPolylines from "./renderBufferPolylineCollection.js";

/** @import { TypedArray } from "../Core/globalTypes.js"; */
/** @import Color from "../Core/Color.js"; */
/** @import Matrix4 from "../Core/Matrix4.js"; */
/** @import FrameState from "./FrameState.js" */

/**
* @typedef {object} BufferPolylineOptions
* @property {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
* @property {boolean} [show=true]
* @property {Color} [color=Color.WHITE]
* @property {TypedArray} [positions]
Expand Down Expand Up @@ -115,6 +118,14 @@ class BufferPolylineCollection extends BufferPrimitiveCollection {
*/
update(frameState) {
super.update(frameState);

if (this.show) {
this._renderContext = renderPolylines(
this,
frameState,
this._renderContext,
);
}
}
}

Expand Down
37 changes: 34 additions & 3 deletions packages/engine/Source/Scene/BufferPrimitiveCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import Color from "../Core/Color.js";
import Cartesian3 from "../Core/Cartesian3.js";
import DeveloperError from "../Core/DeveloperError.js";
import Frozen from "../Core/Frozen.js";
import Matrix4 from "../Core/Matrix4.js";
import assert from "../Core/assert.js";
import ComponentDatatype from "../Core/ComponentDatatype.js";
import defined from "../Core/defined.js";

/** @import { TypedArray, TypedArrayConstructor } from "../Core/globalTypes.js"; */
/** @import BufferPrimitive from "./BufferPrimitive.js"; */
Expand Down Expand Up @@ -63,13 +65,14 @@ class BufferPrimitiveCollection {
* implementations, so the collection should be ignorant of the renderer's implementation
* and context data. A collection only has one renderer active at a time.
*
* @type {unknown}
* @type {{destroy: Function}|null}
* @ignore
*/
_renderContext = null;

/**
* @param {object} options
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
* @param {number} [options.primitiveCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
* @param {number} [options.vertexCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
* @param {boolean} [options.show=true]
Expand All @@ -85,12 +88,26 @@ class BufferPrimitiveCollection {
this.show = options.show ?? true;

/**
* Bounding volume for all primitives in the collection, including both
* Transforms geometry from model to world coordinates.
* @type {Matrix4}
* @default Matrix4.IDENTITY
*/
this.modelMatrix = Matrix4.clone(options.modelMatrix ?? Matrix4.IDENTITY);

/**
* Local bounding volume for all primitives in the collection, including both
* shown and hidden primitives.
* @type {BoundingSphere}
*/
this.boundingVolume = new BoundingSphere();

/**
* World bounding volume for all primitives in the collection, including both
* shown and hidden primitives.
* @type {BoundingSphere}
*/
this.boundingVolumeWC = new BoundingSphere();

/**
* This property is for debugging only; it is not for production use nor is it optimized.
* <p>
Expand Down Expand Up @@ -234,6 +251,16 @@ class BufferPrimitiveCollection {
return false;
}

/** Destroys collection and its GPU resources. */
destroy() {
if (defined(this._renderContext)) {
this._renderContext.destroy();
this._renderContext = undefined;
this._dirtyOffset = 0;
this._dirtyCount = this.primitiveCount;
}
}

/**
* Sorts primitives of the collection.
*
Expand Down Expand Up @@ -390,7 +417,11 @@ class BufferPrimitiveCollection {
3,
this.boundingVolume,
);

BoundingSphere.transform(
this.boundingVolume,
this.modelMatrix,
this.boundingVolumeWC,
);
this._dirtyBoundingVolume = false;
}

Expand Down
Loading