diff --git a/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/gpu-aggregator.ts b/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/gpu-aggregator.ts index d6e6af1c018..e2efd903494 100644 --- a/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/gpu-aggregator.ts +++ b/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/gpu-aggregator.ts @@ -19,9 +19,9 @@ export type GPUAggregatorSettings = { * `void getBin(out int binId)`: if dimensions=1 * `void getBin(out ivec2 binId)`: if dimensions=2 * And a shader function with one of the signatures - * `void getWeight(out float weight)`: if numChannels=1 - * `void getWeight(out vec2 weight)`: if numChannels=2 - * `void getWeight(out vec3 weight)`: if numChannels=3 + * `void getValue(out float value)`: if numChannels=1 + * `void getValue(out vec2 value)`: if numChannels=2 + * `void getValue(out vec3 value)`: if numChannels=3 */ vs: string; /** Shader modules @@ -132,11 +132,13 @@ export class GPUAggregator implements Aggregator { const count = pixel[3]; const value: number[] = []; for (let channel = 0; channel < this.numChannels; channel++) { - if (count === 0) { + const operation = this.props.operations[channel]; + if (operation === 'COUNT') { + value[channel] = count; + } else if (count === 0) { value[channel] = NaN; } else { - value[channel] = - this.props.operations[channel] === 'MEAN' ? pixel[channel] / count : pixel[channel]; + value[channel] = operation === 'MEAN' ? pixel[channel] / count : pixel[channel]; } } return {id, value, count}; @@ -222,11 +224,13 @@ export class GPUAggregator implements Aggregator { } } + update() {} + /** Run aggregation */ - update( + preDraw( /** Parameters only available at runtime */ opts: { - moduleSettings?: ModelProps['moduleSettings']; + moduleSettings: any; } ) { if (!this.needsUpdate.some(Boolean)) { diff --git a/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/webgl-aggregation-transform.ts b/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/webgl-aggregation-transform.ts index 5b821662443..d429fdbac19 100644 --- a/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/webgl-aggregation-transform.ts +++ b/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/webgl-aggregation-transform.ts @@ -91,6 +91,7 @@ export class WebGLAggregationTransform { const target = this.domainFBO; transform.model.setUniforms({ + isCount: Array.from({length: 3}, (_, i) => (operations[i] === 'COUNT' ? 1 : 0)), isMean: Array.from({length: 3}, (_, i) => (operations[i] === 'MEAN' ? 1 : 0)) }); transform.model.setBindings({bins}); @@ -117,6 +118,7 @@ function createTransform(device: Device, settings: GPUAggregatorSettings): Buffe #define SHADER_NAME gpu-aggregation-domain-vertex uniform ivec4 binIdRange; +uniform bvec3 isCount; uniform bvec3 isMean; uniform float naN; uniform sampler2D bins; @@ -136,10 +138,14 @@ out vec3 values; #endif void main() { - int row = gl_VertexID / SAMEPLER_WIDTH; - int col = gl_VertexID - row * SAMEPLER_WIDTH; + int row = gl_VertexID / SAMPLER_WIDTH; + int col = gl_VertexID - row * SAMPLER_WIDTH; vec4 weights = texelFetch(bins, ivec2(col, row), 0); - vec3 value3 = mix(weights.rgb, weights.rgb / max(weights.a, 1.0), isMean); + vec3 value3 = mix( + mix(weights.rgb, vec3(weights.a), isCount), + weights.rgb / max(weights.a, 1.0), + isMean + ); if (weights.a == 0.0) { value3 = vec3(naN); } @@ -162,6 +168,8 @@ void main() { #endif gl_Position = vec4(0., 0., 0., 1.); + // This model renders into a 2x1 texture to obtain min and max simultaneously. + // See comments in fragment shader gl_PointSize = 2.0; } `; @@ -222,7 +230,7 @@ void main() { defines: { NUM_DIMS: settings.dimensions, NUM_CHANNELS: settings.numChannels, - SAMEPLER_WIDTH: TEXTURE_WIDTH + SAMPLER_WIDTH: TEXTURE_WIDTH }, uniforms: { // Passed in as uniform because 1) there is no GLSL symbol for NaN 2) any expression that exploits undefined behavior to produces NaN diff --git a/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/webgl-bin-sorter.ts b/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/webgl-bin-sorter.ts index 223084b3102..cb486aded14 100644 --- a/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/webgl-bin-sorter.ts +++ b/modules/aggregation-layers/src/aggregation-layer-v9/gpu-aggregator/webgl-bin-sorter.ts @@ -7,7 +7,7 @@ import type {AggregationOperation} from '../aggregator'; const COLOR_CHANNELS = [0x1, 0x2, 0x4, 0x8]; // GPU color mask RED, GREEN, BLUE, ALPHA const MAX_FLOAT32 = 3e38; -const EMPTY_MASKS = {SUM: 0, MEAN: 0, MIN: 0, MAX: 0}; +const EMPTY_MASKS = {SUM: 0, MEAN: 0, MIN: 0, MAX: 0, COUNT: 0}; export const TEXTURE_WIDTH = 1024; @@ -23,8 +23,8 @@ export class WebGLBinSorter { * A packed texture in which each pixel represents a bin. * The index of the pixel in the memory layout is the bin index. * Alpha value is the count of data points that fall into this bin - * R,G,B values are the aggregated weights of each channel: - * - Sum of all data points if operation is 'SUM' or 'MEAN' + * R,G,B values are the aggregated values of each channel: + * - Sum of all data points if operation is 'SUM', or 'MEAN' * - Min of all data points if operation is 'MIN' * - Max of all data points if operation is 'MAX' */ @@ -202,7 +202,7 @@ uniform ivec2 targetSize; ${userVs} -out vec3 v_Weight; +out vec3 v_Value; void main() { int binIndex; @@ -219,11 +219,11 @@ void main() { gl_PointSize = 1.0; #if NUM_CHANNELS == 3 - getWeight(v_Weight); + getValue(v_Value); #elif NUM_CHANNELS == 2 - getWeight(v_Weight.xy); + getValue(v_Value.xy); #else - getWeight(v_Weight.x); + getValue(v_Value.x); #endif } `; @@ -233,11 +233,11 @@ void main() { precision highp float; -in vec3 v_Weight; +in vec3 v_Value; out vec4 fragColor; void main() { - fragColor.xyz = v_Weight; + fragColor.xyz = v_Value; #ifdef MODULE_GEOMETRY geometry.uv = vec2(0.); diff --git a/test/modules/aggregation-layers/aggregation-layer-v9/gpu-aggregator.spec.ts b/test/modules/aggregation-layers/aggregation-layer-v9/gpu-aggregator.spec.ts index 7549a7c48ff..0df359e3704 100644 --- a/test/modules/aggregation-layers/aggregation-layer-v9/gpu-aggregator.spec.ts +++ b/test/modules/aggregation-layers/aggregation-layer-v9/gpu-aggregator.spec.ts @@ -23,13 +23,13 @@ test('GPUAggregator#resources', t => { void getBin(out int binId) { binId = int(education); } - void getWeight(out float weight) { - weight = income; + void getValue(out float value) { + value = income; } ` }); - t.doesNotThrow(() => aggregator.update({}), 'Calling update() without setting props'); + t.doesNotThrow(() => aggregator.update(), 'Calling update() without setting props'); t.notOk(aggregator.getResult(0)); t.notOk(aggregator.getBin(0)); @@ -52,7 +52,7 @@ test('GPUAggregator#resources', t => { operations: ['MEAN'] }); - aggregator.update({}); + aggregator.update(); t.ok(aggregator.getResult(0)); t.ok(aggregator.getBin(0)); @@ -60,7 +60,7 @@ test('GPUAggregator#resources', t => { aggregator.setProps({ binIdRange: [[0, 15]] }); - aggregator.update({}); + aggregator.update(); t.ok(aggregator.getResult(0)); t.ok(aggregator.getBin(0)); @@ -93,8 +93,8 @@ test('GPUAggregator#1D', t => { void getBin(out int binId) { binId = int(floor(age / ageGroupSize)); } - void getWeight(out vec3 weight) { - weight = vec3(1.0, income, education); + void getValue(out vec3 value) { + value = vec3(1.0, income, education); } ` }); @@ -121,7 +121,8 @@ test('GPUAggregator#1D', t => { binOptions: {ageGroupSize: 5} }); - aggregator.update({}); + aggregator.update(); + aggregator.preDraw({moduleSettings: {}}); t.is(aggregator.numBins, 15, 'numBins'); @@ -191,8 +192,8 @@ test('GPUAggregator#2D', t => { binId.x = int(floor(age / ageGroupSize)); binId.y = int(education); } - void getWeight(out vec2 weight) { - weight = vec2(1.0, income); + void getValue(out vec2 value) { + value = vec2(5.0, income); } ` }); @@ -218,11 +219,12 @@ test('GPUAggregator#2D', t => { [1, 6] ], // age: 20..59, education: 1..5 attributes, - operations: ['SUM', 'MEAN'], + operations: ['COUNT', 'MEAN'], binOptions: {ageGroupSize: 10} }); - aggregator.update({}); + aggregator.update(); + aggregator.preDraw({moduleSettings: {}}); t.is(aggregator.numBins, 20, 'numBins'); @@ -260,7 +262,7 @@ test('GPUAggregator#2D', t => { t.deepEqual(aggregator.getResultDomain(1), [10, 320], 'getResultDomain() - mean income'); // Empty bin - t.deepEqual(aggregator.getBin(0), {id: [2, 1], count: 0, value: [NaN, NaN]}, 'getBin() - empty'); + t.deepEqual(aggregator.getBin(0), {id: [2, 1], count: 0, value: [0, NaN]}, 'getBin() - empty'); // {age: 40, household: 4, income: 140, education: 4}, // {age: 44, household: 4, income: 500, education: 4}, t.deepEqual(aggregator.getBin(14), {id: [4, 4], count: 2, value: [2, 320]}, 'getBin()');