Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): Update AttributeTransitionUtils for Luma v9 #8392

Merged
merged 11 commits into from
Jan 11, 2024
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;
felixpalmer marked this conversation as resolved.
Show resolved Hide resolved
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
Loading
Loading