Skip to content

Commit

Permalink
Merge 24285eb into b4ce2e3
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress committed Nov 18, 2019
2 parents b4ce2e3 + 24285eb commit 5c2b9fc
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 124 deletions.
20 changes: 10 additions & 10 deletions modules/core/src/lib/attribute/attribute-manager.js
Expand Up @@ -232,21 +232,21 @@ export default class AttributeManager {

for (const attributeName in this.attributes) {
const attribute = this.attributes[attributeName];

if (
attribute.setExternalBuffer(
buffers[attributeName] || (data.attributes && data.attributes[attributeName])
)
) {
// Attribute is using external buffer from the props
} else if (attribute.setConstantValue(props[attribute.settings.accessor])) {
// Attribute is using generic value from the props
const accessorName = attribute.settings.accessor;
attribute.startIndices = startIndices;

if (attribute.setVirtualBuffer(buffers[attributeName])) {
// Step 1: try set virtual attribute from external buffers
} else if (attribute.setLogicalBuffer(buffers[accessorName], data.startIndices)) {
// Step 2: try set logical attribute from external buffers
} else if (attribute.setConstantValue(props[accessorName])) {
// Step 3: try set constant value from props
} else if (attribute.needsUpdate()) {
// Step 4: update via updater callback
updated = true;
this._updateAttribute({
attribute,
numInstances,
startIndices,
data,
props,
context
Expand Down
78 changes: 63 additions & 15 deletions modules/core/src/lib/attribute/attribute.js
Expand Up @@ -2,7 +2,7 @@
import {_Accessor as Accessor} from '@luma.gl/core';
import DataColumn from './data-column';
import assert from '../../utils/assert';
import {createIterable} from '../../utils/iterable-utils';
import {createIterable, getAccessorFromBuffer} from '../../utils/iterable-utils';
import {fillArray} from '../../utils/flatten';
import * as range from '../../utils/range';
import {normalizeTransitionSettings} from './attribute-transition-utils';
Expand All @@ -25,14 +25,16 @@ export default class Attribute extends DataColumn {
Object.assign(this.settings, {
transition,
noAlloc,
update: update || (accessor && this._standardAccessor),
update: update || (accessor && this._autoUpdater),
accessor,
elementOffset: offset / Accessor.getBytesPerElement(this.settings),
transform
});

Object.assign(this.state, {
lastExternalBuffer: null,
lastVirtualBuffer: null,
lastLogicalBuffer: null,
logicalAccessor: null,
needsUpdate: true,
needsRedraw: false,
updateRanges: range.FULL,
Expand Down Expand Up @@ -136,7 +138,7 @@ export default class Attribute extends DataColumn {
return false;
}

updateBuffer({numInstances, startIndices, data, props, context}) {
updateBuffer({numInstances, data, props, context}) {
if (!this.needsUpdate()) {
return false;
}
Expand All @@ -150,7 +152,7 @@ export default class Attribute extends DataColumn {
if (update) {
// Custom updater - typically for non-instanced layers
for (const [startRow, endRow] of updateRanges) {
update.call(context, this, {data, startRow, endRow, props, numInstances, startIndices});
update.call(context, this, {data, startRow, endRow, props, numInstances});
}
if (!this.value) {
// no value was assigned during update
Expand Down Expand Up @@ -201,27 +203,73 @@ export default class Attribute extends DataColumn {
// Use external buffer
// Returns true if successful
// eslint-disable-next-line max-statements
setExternalBuffer(buffer) {
setVirtualBuffer(buffer) {
const {state} = this;

if (!buffer) {
state.lastExternalBuffer = null;
state.lastVirtualBuffer = null;
return false;
}

this.clearNeedsUpdate();

if (state.lastExternalBuffer === buffer) {
if (state.lastVirtualBuffer === buffer) {
return true;
}
state.lastExternalBuffer = buffer;
state.lastVirtualBuffer = buffer;
this.setNeedsRedraw();
this.setData(buffer);
return true;
}

setLogicalBuffer(buffer, startIndices) {
const {state, settings} = this;

if (!buffer) {
state.lastLogicalBuffer = null;
state.logicalAccessor = null;
return false;
}

if (settings.noAlloc) {
// Let the layer handle this
return false;
}

if (state.lastLogicalBuffer === buffer) {
this.clearNeedsUpdate();
return true;
}
state.lastLogicalBuffer = buffer;
this.setNeedsRedraw();

const needsUpdate = settings.transform || startIndices !== this.startIndices;

if (needsUpdate) {
if (ArrayBuffer.isView(buffer)) {
buffer = {value: buffer};
}
assert(ArrayBuffer.isView(buffer.value), `invalid ${settings.accessor}`);
const needsNormalize = buffer.size && buffer.size !== this.size;

state.logicalAccessor = getAccessorFromBuffer(buffer.value, {
size: buffer.size || this.size,
stride: buffer.stride,
offset: buffer.offset,
startIndices,
nested: needsNormalize
});
// Fall through to auto updater
return false;
}

this.clearNeedsUpdate();
this.setData(buffer);
return true;
}

getVertexOffset(row, startIndices = this.startIndices) {
getVertexOffset(row) {
const {startIndices} = this;
const vertexIndex = startIndices ? startIndices[row] : row;
return this.settings.elementOffset + vertexIndex * this.size;
}
Expand All @@ -241,15 +289,16 @@ export default class Attribute extends DataColumn {
}

/* eslint-disable max-depth, max-statements */
_standardAccessor(attribute, {data, startRow, endRow, props, numInstances, startIndices}) {
const {settings, value, size} = attribute;
_autoUpdater(attribute, {data, startRow, endRow, props, numInstances}) {
const {settings, state, value, size, startIndices} = attribute;

const {accessor, transform} = settings;
const accessorFunc = typeof accessor === 'function' ? accessor : props[accessor];
const accessorFunc =
state.logicalAccessor || (typeof accessor === 'function' ? accessor : props[accessor]);

assert(typeof accessorFunc === 'function', `accessor "${accessor}" is not a function`);

let i = attribute.getVertexOffset(startRow, startIndices);
let i = attribute.getVertexOffset(startRow);
const {iterable, objectInfo} = createIterable(data, startRow, endRow);
for (const object of iterable) {
objectInfo.index++;
Expand Down Expand Up @@ -288,7 +337,6 @@ export default class Attribute extends DataColumn {
}
}
attribute.constant = false;
attribute.startIndices = startIndices;
}
/* eslint-enable max-depth, max-statements */

Expand Down
2 changes: 1 addition & 1 deletion modules/core/src/lib/layer.js
Expand Up @@ -433,7 +433,7 @@ export default class Layer extends Component {
startIndices,
props,
transitions: props.transitions,
buffers: props,
buffers: props.data.attributes,
context: this,
// Don't worry about non-attribute props
ignoreUnknownAttributes: true
Expand Down
46 changes: 46 additions & 0 deletions modules/core/src/utils/iterable-utils.js
Expand Up @@ -60,3 +60,49 @@ export function createIterable(data, startRow = 0, endRow = Infinity) {
export function isAsyncIterable(data) {
return data && data[Symbol.asyncIterator];
}

/*
* Create an accessor function from a flat buffer that yields the value at each object index
*/
export function getAccessorFromBuffer(typedArray, {size, stride, offset, startIndices, nested}) {
const bytesPerElement = typedArray.BYTES_PER_ELEMENT;
const elementStride = stride ? stride / bytesPerElement : size;
const elementOffset = offset ? offset / bytesPerElement : 0;
const vertexCount = Math.floor((typedArray.length - elementOffset) / elementStride);

return (_, {index, target}) => {
if (!startIndices) {
const sourceIndex = index * elementStride + elementOffset;
for (let j = 0; j < size; j++) {
target[j] = typedArray[sourceIndex + j];
}
return target;
}
const startIndex = startIndices[index];
const endIndex = startIndices[index + 1] || vertexCount;
let result;

if (nested) {
result = new Array(endIndex - startIndex);
for (let i = startIndex; i < endIndex; i++) {
const sourceIndex = i * elementStride + elementOffset;
target = new Array(size);
for (let j = 0; j < size; j++) {
target[j] = typedArray[sourceIndex + j];
}
result[i - startIndex] = target;
}
} else {
result = new typedArray.constructor((endIndex - startIndex) * size);
let targetIndex = 0;
for (let i = startIndex; i < endIndex; i++) {
const sourceIndex = i * elementStride + elementOffset;
for (let j = 0; j < size; j++) {
result[targetIndex++] = typedArray[sourceIndex + j];
}
}
}

return result;
};
}
85 changes: 84 additions & 1 deletion test/modules/core/lib/attribute/attribute-manager.spec.js
Expand Up @@ -144,7 +144,7 @@ test('AttributeManager.update - 0 numInstances', t => {
t.end();
});

test('AttributeManager.update - external buffers', t => {
test('AttributeManager.update - external virtual buffers', t => {
const attributeManager = new AttributeManager(gl);

const dummyUpdate = () => t.fail('updater should not be called when external buffer is present');
Expand Down Expand Up @@ -192,6 +192,89 @@ test('AttributeManager.update - external buffers', t => {
t.end();
});

test('AttributeManager.update - external logical buffers', t => {
const attributeManager = new AttributeManager(gl);

const dummyAccessor = () =>
t.fail('accessor should not be called when external buffer is present');

attributeManager.add({
positions: {size: 2, accessor: 'getPosition'},
colors: {size: 4, type: GL.UNSIGNED_BYTE, accessor: 'getColor', defaultValue: [0, 0, 0, 255]},
types: {size: 1, accessor: 'getType', transform: x => x - 65}
});

// First update, should autoalloc and update the value array
attributeManager.update({
numInstances: 2,
data: {length: 2},
props: {
getPosition: dummyAccessor,
getColor: dummyAccessor,
getType: dummyAccessor
},
buffers: {
getPosition: new Float32Array([1, 1, 2, 2]),
getColor: {value: new Uint8ClampedArray([255, 0, 0, 0, 255, 0]), size: 3},
getType: new Uint8Array([65, 68])
}
});

let attribute = attributeManager.getAttributes()['positions'];
t.deepEqual(attribute.value, [1, 1, 2, 2], 'positions attribute has value');

attribute = attributeManager.getAttributes()['colors'];
t.deepEqual(attribute.value, [255, 0, 0, 0, 255, 0], 'colors attribute has value');
t.is(attribute.getAccessor().size, 3, 'colors attribute has correct size');

attribute = attributeManager.getAttributes()['types'];
t.deepEqual(attribute.value.slice(0, 2), [0, 3], 'types attribute has value');

t.end();
});

test('AttributeManager.update - external logical buffers - variable width', t => {
const attributeManager = new AttributeManager(gl);

const dummyAccessor = () =>
t.fail('accessor should not be called when external buffer is present');

attributeManager.add({
positions: {size: 2, accessor: 'getPosition'},
colors: {size: 4, type: GL.UNSIGNED_BYTE, accessor: 'getColor', defaultValue: [0, 0, 0, 255]}
});

// First update, should autoalloc and update the value array
attributeManager.update({
numInstances: 3,
startIndices: [0, 2],
data: {
length: 2,
vertexStarts: [0, 1]
},
props: {
getPosition: dummyAccessor,
getColor: dummyAccessor
},
buffers: {
getPosition: new Float32Array([1, 1, 2, 2]),
getColor: {value: new Uint8ClampedArray([255, 0, 0, 0, 255, 0]), size: 3}
}
});

let attribute = attributeManager.getAttributes()['positions'];
t.deepEqual(attribute.value.slice(0, 6), [1, 1, 1, 1, 2, 2], 'positions attribute has value');

attribute = attributeManager.getAttributes()['colors'];
t.deepEqual(
attribute.value.slice(0, 12),
[255, 0, 0, 255, 255, 0, 0, 255, 0, 255, 0, 255],
'colors attribute has value'
);

t.end();
});

test('AttributeManager.invalidate', t => {
const attributeManager = new AttributeManager(gl);
attributeManager.add({positions: {size: 2, update}});
Expand Down

0 comments on commit 5c2b9fc

Please sign in to comment.