Skip to content

Commit

Permalink
fix(core): Update AttributeTransitionUtils for Luma v9 (#8392)
Browse files Browse the repository at this point in the history

---------

Co-authored-by: felixpalmer <felixpalmer@gmail.com>
  • Loading branch information
donmccurdy and felixpalmer committed Jan 11, 2024
1 parent 3679a92 commit fe65573
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 113 deletions.
16 changes: 7 additions & 9 deletions modules/core/src/lib/attribute/attribute-transition-manager.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// deck.gl, MIT license

import type {Device} from '@luma.gl/core';
import GPUInterpolationTransition from '../../transitions/gpu-interpolation-transition';
import GPUSpringTransition from '../../transitions/gpu-spring-transition';
import log from '../../utils/log';

import type {TransitionSettings} from './attribute-transition-utils';
import type Attribute from './attribute';
import type {Device} from '@luma.gl/core';
import type {Timeline} from '@luma.gl/engine';
import type GPUTransition from '../../transitions/gpu-transition';
import type {ConstructorOf} from '../../types/types';
import type Attribute from './attribute';
import type {TransitionSettings} from './attribute-transition-utils';

const TRANSITION_TYPES: Record<string, ConstructorOf<GPUTransition>> = {
interpolation: GPUInterpolationTransition,
Expand Down Expand Up @@ -148,11 +148,9 @@ export default class AttributeTransitionManager {
// TODO: when switching transition types, make sure to carry over the attribute's
// previous buffers, currentLength, startIndices, etc, to be used as the starting point
// for the next transition
let needsUpdate = !transition || transition.type !== settings.type;
needsUpdate ||=
attribute.buffer.byteLength > transition?.attributeInTransition.buffer.byteLength;
let isNew = !transition || transition.type !== settings.type;

if (needsUpdate) {
if (isNew) {
if (!this.isSupported) {
log.warn(
`WebGL2 not supported by this browser. Transition for ${attributeName} is disabled.`
Expand All @@ -173,11 +171,11 @@ export default class AttributeTransitionManager {
});
} else {
log.error(`unsupported transition type '${settings.type}'`)();
needsUpdate = false;
isNew = false;
}
}

if (needsUpdate || attribute.needsRedraw()) {
if (isNew || attribute.needsRedraw()) {
this.needsRedraw = true;
this.transitions[attributeName].start(settings, this.numInstances);
}
Expand Down
65 changes: 54 additions & 11 deletions modules/core/src/lib/attribute/attribute-transition-utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type {Device} from '@luma.gl/core';
import type {Device, TypedArrayConstructor} from '@luma.gl/core';
import type {Buffer} from '@luma.gl/core';
import {padArray} from '../../utils/array-utils';
import {NumericArray, TypedArray} from '../../types/types';
import Attribute from './attribute';
import type {BufferAccessor} from './data-column';
import {GL} from '@luma.gl/constants';
import {VertexFormat as LumaVertexFormat} from '@luma.gl/core';

export interface TransitionSettings {
type: string;
Expand Down Expand Up @@ -91,6 +93,7 @@ export function getSourceBufferAttribute(
return attribute.value as NumericArray;
}

/** Returns the GLSL attribute type for the given number of float32 components. */
export function getAttributeTypeFromSize(size: number): string {
switch (size) {
case 1:
Expand All @@ -106,6 +109,21 @@ export function getAttributeTypeFromSize(size: number): string {
}
}

/** Returns the {@link VertexFormat} for the given number of float32 components. */
export function getFloat32VertexFormat(size: 1 | 2 | 3 | 4): LumaVertexFormat {
switch (size) {
case 1:
return 'float32';
case 2:
return 'float32x2';
case 3:
return 'float32x3';
case 4:
return 'float32x4';
}
throw new Error('invalid type size');
}

export function cycleBuffers(buffers: Buffer[]): void {
buffers.push(buffers.shift() as Buffer);
}
Expand All @@ -123,6 +141,9 @@ export function getAttributeBufferLength(attribute: Attribute, numInstances: num
// to a buffer layout of [4, 2], it should produce a buffer, using the transition setting's `enter`
// function, that looks like this: [A1, A2, A3, A4 (user `enter` fn), B1, B2, 0]. Note: the final
// 0 in this buffer is because we never shrink buffers, only grow them, for performance reasons.
//
// padBuffer may return either the original buffer, or a new buffer if the size of the original
// was insufficient. Callers are responsible for disposing of the original buffer if needed.
export function padBuffer({
buffer,
numInstances,
Expand All @@ -137,7 +158,7 @@ export function padBuffer({
fromLength: number;
fromStartIndices?: NumericArray | null;
getData?: (toValue: NumericArray, chunk?: NumericArray) => NumericArray;
}): void {
}): Buffer {
// TODO: move the precisionMultiplier logic to the attribute when retrieving
// its `size` and `elementOffset`?
const precisionMultiplier =
Expand All @@ -151,12 +172,12 @@ export function padBuffer({

// check if buffer needs to be padded
if (!hasStartIndices && fromLength >= toLength) {
return;
return buffer;
}

const toData = isConstant
? (attribute.value as TypedArray)
: (attribute.getBuffer() as Buffer).getData();
: getBufferData(attribute.getBuffer()!, Float32Array);
if (attribute.settings.normalized && !isConstant) {
const getter = getData;
getData = (value, chunk) => attribute.normalizeConstant(getter(value, chunk));
Expand All @@ -166,12 +187,11 @@ export function padBuffer({
? (i, chunk) => getData(toData, chunk)
: (i, chunk) => getData(toData.subarray(i + byteOffset, i + byteOffset + size), chunk);

// TODO(donmccurdy): Replace with `.readAsync()` or a helper function.
const sourceData = (buffer as any).getData({length: fromLength});
const source = new Float32Array(
sourceData.buffer,
sourceData.byteOffset,
sourceData.byteLength / Float32Array.BYTES_PER_ELEMENT
const source = getBufferData(
buffer,
Float32Array,
0,
fromLength * Float32Array.BYTES_PER_ELEMENT
);
const target = new Float32Array(toLength);
padArray({
Expand All @@ -184,7 +204,30 @@ export function padBuffer({
});

if (buffer.byteLength < target.byteLength + byteOffset) {
throw new Error(`Buffer size is immutable, ${buffer.byteLength} bytes`);
buffer = buffer.device.createBuffer({byteLength: target.byteLength + byteOffset});
}
buffer.write(target, byteOffset);
return buffer;
}

/** @deprecated TODO(v9.1): Buffer reads should be asynchronous and avoid accessing GL context. */
export function getBufferData(
buffer: Buffer,
TypedArray: TypedArrayConstructor,
byteOffset = 0,
byteLength = buffer.byteLength
): TypedArray {
const _buffer = buffer as any;
_buffer.device.assertWebGL2();

const dstLength = byteLength / TypedArray.BYTES_PER_ELEMENT;
const dstArray = new TypedArray(dstLength);
const dstOffset = 0;

// Use GL.COPY_READ_BUFFER to avoid disturbing other targets and locking type
_buffer.gl.bindBuffer(GL.COPY_READ_BUFFER, _buffer.handle);
_buffer.gl2.getBufferSubData(GL.COPY_READ_BUFFER, byteOffset, dstArray, dstOffset, dstLength);
_buffer.gl.bindBuffer(GL.COPY_READ_BUFFER, null);

return dstArray;
}
3 changes: 1 addition & 2 deletions modules/core/src/lib/attribute/data-column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,10 +588,9 @@ export default class DataColumn<Options, State> {

protected _createBuffer(byteLength: number): Buffer {
if (this._buffer) {
// console.log(`DataColumn#destroyBuffer: id = ${this.id}, byteLength = ${this._buffer.byteLength}`);
this._buffer.destroy();
}
// console.log(`DataColumn#createBuffer: id = ${this.id}, byteLength = ${byteLength}`);

const {isIndexed, type} = this.settings;
this._buffer = this.device.createBuffer({
...this._buffer?.props,
Expand Down
45 changes: 28 additions & 17 deletions modules/core/src/transitions/gpu-interpolation-transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import {Buffer} from '@luma.gl/core';
import {GL} from '@luma.gl/constants';
import Attribute from '../lib/attribute/attribute';
import {
// padBuffer,
getAttributeTypeFromSize,
// getSourceBufferAttribute,
getAttributeBufferLength,
cycleBuffers,
InterpolationTransitionSettings
InterpolationTransitionSettings,
padBuffer,
getFloat32VertexFormat
} from '../lib/attribute/attribute-transition-utils';
import Transition from './transition';

Expand Down Expand Up @@ -87,18 +87,29 @@ export default class GPUInterpolationTransition implements GPUTransition {
// And the other buffer is now the current buffer.
cycleBuffers(buffers);

// const padBufferOpts = {
// numInstances,
// attribute,
// fromLength: this.currentLength,
// fromStartIndices: this.currentStartIndices,
// getData: transitionSettings.enter
// };
const padBufferOpts = {
numInstances,
attribute,
fromLength: this.currentLength,
fromStartIndices: this.currentStartIndices,
getData: transitionSettings.enter
};

for (const [index, buffer] of buffers.entries()) {
const paddedBuffer = padBuffer({buffer, ...padBufferOpts});

if (buffer !== paddedBuffer) {
buffer.destroy();
buffers[index] = paddedBuffer;

// TODO(v9): Requires synchronous reads, unsupported in v9, or a different solution here.
// for (const buffer of buffers) {
// padBuffer({buffer, ...padBufferOpts});
// }
// TODO(v9): While this probably isn't necessary as a user-facing warning, it is helpful
// for debugging buffer allocation during deck.gl v9 development.
console.warn(
`[GPUInterpolationTransition] Replaced buffer ${buffer.id} (${buffer.byteLength} bytes) → ` +
`${paddedBuffer.id} (${paddedBuffer.byteLength} bytes)`
);
}
}

this.currentStartIndices = attribute.startIndices;
this.currentLength = getAttributeBufferLength(attribute, numInstances);
Expand Down Expand Up @@ -161,12 +172,12 @@ void main(void) {

function getTransform(device: Device, attribute: Attribute): BufferTransform {
const attributeType = getAttributeTypeFromSize(attribute.size);
const format = getFloat32VertexFormat(attribute.size as 1 | 2 | 3 | 4);
return new BufferTransform(device, {
vs,
// TODO(v9): Can 'attribute' provide 'format' values?
bufferLayout: [
{name: 'aFrom', format: 'float32'},
{name: 'aTo', format: 'float32'}
{name: 'aFrom', format},
{name: 'aTo', format}
],
defines: {
ATTRIBUTE_TYPE: attributeType
Expand Down

0 comments on commit fe65573

Please sign in to comment.