diff --git a/modules/aggregation-layers/src/common/aggregator/gpu-aggregator/aggregation-transform-uniforms.ts b/modules/aggregation-layers/src/common/aggregator/gpu-aggregator/aggregation-transform-uniforms.ts index 2de5e58d779..8a0bccca8d3 100644 --- a/modules/aggregation-layers/src/common/aggregator/gpu-aggregator/aggregation-transform-uniforms.ts +++ b/modules/aggregation-layers/src/common/aggregator/gpu-aggregator/aggregation-transform-uniforms.ts @@ -15,7 +15,6 @@ export type AggregatorTransformProps = { binIdRange: NumberArray4; isCount: NumberArray3; isMean: NumberArray3; - naN: number; bins: Texture; }; @@ -25,7 +24,6 @@ export const aggregatorTransformUniforms = { uniformTypes: { binIdRange: 'vec4', isCount: 'vec3', - isMean: 'vec3', - naN: 'f32' + isMean: 'vec3' } } as const satisfies ShaderModule; diff --git a/modules/aggregation-layers/src/common/aggregator/gpu-aggregator/webgl-aggregation-transform.ts b/modules/aggregation-layers/src/common/aggregator/gpu-aggregator/webgl-aggregation-transform.ts index 60f6e228b04..6773a108e27 100644 --- a/modules/aggregation-layers/src/common/aggregator/gpu-aggregator/webgl-aggregation-transform.ts +++ b/modules/aggregation-layers/src/common/aggregator/gpu-aggregator/webgl-aggregation-transform.ts @@ -35,9 +35,6 @@ export class WebGLAggregationTransform { this.device = device; this.channelCount = props.channelCount; this.transform = createTransform(device, props); - // Passed in as uniform because 1) there is no GLSL symbol for NaN 2) any expression that exploits undefined behavior to produces NaN - // will subject to platform differences and shader optimization - this.transform.model.shaderInputs.setProps({aggregatorTransform: {naN: NaN}}); this.domainFBO = createRenderTarget(device, 2, 1); } @@ -145,6 +142,8 @@ flat out vec2 values; flat out vec3 values; #endif +const float NAN = intBitsToFloat(-1); + void main() { int row = gl_VertexID / SAMPLER_WIDTH; int col = gl_VertexID - row * SAMPLER_WIDTH; @@ -155,7 +154,7 @@ void main() { aggregatorTransform.isMean ); if (weights.a == 0.0) { - value3 = vec3(aggregatorTransform.naN); + value3 = vec3(NAN); } #if NUM_DIMS == 1 @@ -199,11 +198,6 @@ flat in vec3 values; out vec4 fragColor; void main() { -#if NUM_CHANNELS > 1 - if (isnan(values.x)) discard; -#else - if (isnan(values)) discard; -#endif vec3 value3; #if NUM_CHANNELS == 3 value3 = values; @@ -212,6 +206,7 @@ void main() { #else value3.x = values; #endif + if (isnan(value3.x)) discard; // This shader renders into a 2x1 texture with blending=max // The left pixel yields the max value of each channel // The right pixel yields the min value of each channel diff --git a/modules/arcgis/src/commons.ts b/modules/arcgis/src/commons.ts index ecb3c5811fc..7dbc785c3a8 100644 --- a/modules/arcgis/src/commons.ts +++ b/modules/arcgis/src/commons.ts @@ -1,9 +1,10 @@ /* eslint-disable no-invalid-this */ import {GL} from '@luma.gl/constants'; -import {Model, Geometry} from '@luma.gl/engine'; -import {Deck} from '@deck.gl/core'; import type {Device, Texture, Framebuffer} from '@luma.gl/core'; +import {Deck} from '@deck.gl/core'; +import {Model, Geometry} from '@luma.gl/engine'; +import {WebGLDevice} from '@luma.gl/webgl'; interface Renderer { redraw: () => void; @@ -146,33 +147,35 @@ export function render( ) { const {model, deck, fbo} = resources; const device = model.device; - // @ts-ignore device.getParametersWebGL should return `any` not `void`? - const screenFbo: Framebuffer = device.getParametersWebGL(GL.FRAMEBUFFER_BINDING); - const {width, height, ...viewState} = viewport; + if (device instanceof WebGLDevice) { + // @ts-ignore device.getParametersWebGL should return `any` not `void`? + const screenFbo: Framebuffer = device.getParametersWebGL(GL.FRAMEBUFFER_BINDING); + const {width, height, ...viewState} = viewport; - /* global window */ - const dpr = window.devicePixelRatio; - const pixelWidth = Math.round(width * dpr); - const pixelHeight = Math.round(height * dpr); + /* global window */ + const dpr = window.devicePixelRatio; + const pixelWidth = Math.round(width * dpr); + const pixelHeight = Math.round(height * dpr); - fbo.resize({width: pixelWidth, height: pixelHeight}); + fbo.resize({width: pixelWidth, height: pixelHeight}); - deck.setProps({viewState}); - // redraw deck immediately into deckFbo - deck.redraw('arcgis'); + deck.setProps({viewState}); + // redraw deck immediately into deckFbo + deck.redraw('arcgis'); - // We overlay the texture on top of the map using the full-screen quad. + // We overlay the texture on top of the map using the full-screen quad. - const textureToScreenPass = device.beginRenderPass({ - framebuffer: screenFbo, - parameters: {viewport: [0, 0, pixelWidth, pixelHeight]}, - clearColor: false, - clearDepth: false - }); - try { - model.draw(textureToScreenPass); - } finally { - textureToScreenPass.end(); + const textureToScreenPass = device.beginRenderPass({ + framebuffer: screenFbo, + parameters: {viewport: [0, 0, pixelWidth, pixelHeight]}, + clearColor: false, + clearDepth: false + }); + try { + model.draw(textureToScreenPass); + } finally { + textureToScreenPass.end(); + } } } diff --git a/modules/core/src/lib/deck-picker.ts b/modules/core/src/lib/deck-picker.ts index d3412226afb..0933934e276 100644 --- a/modules/core/src/lib/deck-picker.ts +++ b/modules/core/src/lib/deck-picker.ts @@ -159,7 +159,7 @@ export default class DeckPicker { } // Resize it to current canvas size (this is a noop if size hasn't changed) - const {canvas} = this.device.getCanvasContext(); + const {canvas} = this.device.getDefaultCanvasContext(); this.pickingFBO?.resize({width: canvas.width, height: canvas.height}); this.depthFBO?.resize({width: canvas.width, height: canvas.height}); } diff --git a/modules/core/src/lib/deck.ts b/modules/core/src/lib/deck.ts index 3b97d7100fc..74d29bee617 100644 --- a/modules/core/src/lib/deck.ts +++ b/modules/core/src/lib/deck.ts @@ -948,13 +948,15 @@ export default class Deck { // instrumentGLContext(this.device.gl, {enable: true, copyState: true}); } - this.device.setParametersWebGL({ - blend: true, - blendFunc: [GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA], - polygonOffsetFill: true, - depthTest: true, - depthFunc: GL.LEQUAL - }); + if (this.device instanceof WebGLDevice) { + this.device.setParametersWebGL({ + blend: true, + blendFunc: [GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA], + polygonOffsetFill: true, + depthTest: true, + depthFunc: GL.LEQUAL + }); + } this.props.onDeviceInitialized(this.device); if (this.device instanceof WebGLDevice) { diff --git a/modules/core/src/lib/layer.ts b/modules/core/src/lib/layer.ts index 44e3459fc88..57abce9de42 100644 --- a/modules/core/src/lib/layer.ts +++ b/modules/core/src/lib/layer.ts @@ -20,6 +20,7 @@ /* eslint-disable react/no-direct-mutation-state */ import {Buffer, TypedArray} from '@luma.gl/core'; +import {WebGLDevice} from '@luma.gl/webgl'; import {COORDINATE_SYSTEM} from './constants'; import AttributeManager from './attribute/attribute-manager'; import UniformTransitionManager from './uniform-transition-manager'; @@ -1153,14 +1154,27 @@ export default abstract class Layer extends Component< const {getPolygonOffset} = this.props; const offsets = (getPolygonOffset && getPolygonOffset(uniforms)) || [0, 0]; - context.device.setParametersWebGL({polygonOffset: offsets}); + if (context.device instanceof WebGLDevice) { + context.device.setParametersWebGL({polygonOffset: offsets}); + } for (const model of this.getModels()) { model.setParameters(parameters); } // Call subclass lifecycle method - context.device.withParametersWebGL(parameters, () => { + if (context.device instanceof WebGLDevice) { + context.device.withParametersWebGL(parameters, () => { + const opts = {renderPass, moduleParameters, uniforms, parameters, context}; + + // extensions + for (const extension of this.props.extensions) { + extension.draw.call(this, opts, extension); + } + + this.draw(opts); + }); + } else { const opts = {renderPass, moduleParameters, uniforms, parameters, context}; // extensions @@ -1169,7 +1183,7 @@ export default abstract class Layer extends Component< } this.draw(opts); - }); + } } finally { this.props = currentProps; } diff --git a/modules/extensions/src/data-filter/aggregator.ts b/modules/extensions/src/data-filter/aggregator.ts index 14d2333b267..80d66ea5d67 100644 --- a/modules/extensions/src/data-filter/aggregator.ts +++ b/modules/extensions/src/data-filter/aggregator.ts @@ -1,6 +1,5 @@ -import {Device, DeviceFeature, Framebuffer} from '@luma.gl/core'; -import {Model} from '@luma.gl/engine'; -import {GL} from '@luma.gl/constants'; +import {Device, DeviceFeature, Framebuffer, RenderPipelineParameters} from '@luma.gl/core'; +import {Model, ModelProps} from '@luma.gl/engine'; const AGGREGATE_VS = `\ #version 300 es @@ -84,7 +83,12 @@ export function getFramebuffer(device: Device, useFloatTarget: boolean): Framebu } // Increments the counter based on dataFilter_value -export function getModel(device: Device, shaderOptions: any, useFloatTarget: boolean): Model { +export function getModel( + device: Device, + bufferLayout: ModelProps['bufferLayout'], + shaderOptions: any, + useFloatTarget: boolean +): Model { shaderOptions.defines.NON_INSTANCED_MODEL = 1; if (useFloatTarget) { shaderOptions.defines.FLOAT_TARGET = 1; @@ -94,16 +98,21 @@ export function getModel(device: Device, shaderOptions: any, useFloatTarget: boo id: 'data-filter-aggregation-model', vertexCount: 1, isInstanced: false, - drawMode: GL.POINTS, + topology: 'point-list', vs: AGGREGATE_VS, fs: AGGREGATE_FS, + bufferLayout, ...shaderOptions }); } -export const parameters = { +export const parameters: RenderPipelineParameters = { blend: true, - blendFunc: [GL.ONE, GL.ONE, GL.ONE, GL.ONE], - blendEquation: [GL.FUNC_ADD, GL.FUNC_ADD], - depthTest: false + blendColorSrcFactor: 'one', + blendColorDstFactor: 'one', + blendAlphaSrcFactor: 'one', + blendAlphaDstFactor: 'one', + blendColorOperation: 'add', + blendAlphaOperation: 'add', + depthCompare: 'never' } as const; diff --git a/modules/extensions/src/data-filter/data-filter-extension.ts b/modules/extensions/src/data-filter/data-filter-extension.ts index 269afe67ebe..768ae8aea44 100644 --- a/modules/extensions/src/data-filter/data-filter-extension.ts +++ b/modules/extensions/src/data-filter/data-filter-extension.ts @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import type {Framebuffer} from '@luma.gl/core'; +import type {Buffer, Framebuffer} from '@luma.gl/core'; import type {Model} from '@luma.gl/engine'; import type {Layer, LayerContext, Accessor, UpdateParameters} from '@deck.gl/core'; import {_deepEqual as deepEqual, LayerExtension, log} from '@deck.gl/core'; @@ -30,6 +30,7 @@ import { dataFilter64 } from './shader-module'; import * as aggregator from './aggregator'; +import {NumberArray4} from '@math.gl/core'; const defaultProps = { getFilterValue: {type: 'accessor', value: 0}, @@ -204,7 +205,7 @@ export default class DataFilterExtension extends LayerExtension< // The vertex shader checks if a vertex has the same "index" as the previous vertex // so that we only write one count cross multiple vertices of the same object attributeManager.add({ - filterIndices: { + filterVertexIndices: { size: useFloatTarget ? 1 : 2, vertexOffset: 1, type: 'unorm8', @@ -226,6 +227,7 @@ export default class DataFilterExtension extends LayerExtension< const filterFBO = aggregator.getFramebuffer(device, useFloatTarget); const filterModel = aggregator.getModel( device, + attributeManager.getBufferLayouts({isInstanced: false}), extension.getShaders.call(this, extension), useFloatTarget ); @@ -311,32 +313,35 @@ export default class DataFilterExtension extends LayerExtension< /* eslint-disable-next-line camelcase */ if (filterNeedsUpdate && onFilteredItemsChange && filterModel) { + const attributeManager = this.getAttributeManager()!; const { - attributes: {filterValues, filterCategoryValues, filterIndices} - } = this.getAttributeManager()!; + attributes: {filterValues, filterCategoryValues, filterVertexIndices} + } = attributeManager; filterModel.setVertexCount(this.getNumInstances()); - this.context.device.clearWebGL({framebuffer: filterFBO, color: [0, 0, 0, 0]}); - - filterModel.updateModuleSettings(params.moduleParameters); - // @ts-expect-error filterValue and filterIndices should always have buffer value - filterModel.setAttributes({ + // @ts-expect-error filterValue and filterVertexIndices should always have buffer value + const attributes: Record = { ...filterValues?.getValue(), ...filterCategoryValues?.getValue(), - ...filterIndices?.getValue() + ...filterVertexIndices?.getValue() + }; + filterModel.setAttributes(attributes); + filterModel.shaderInputs.setProps({ + dataFilter: dataFilterProps }); - filterModel.shaderInputs.setProps({dataFilter: dataFilterProps}); - filterModel.device.withParametersWebGL( - { - framebuffer: filterFBO, - // ts-ignore 'readonly' cannot be assigned to the mutable type '[GLBlendEquation, GLBlendEquation]' - ...(aggregator.parameters as any), - viewport: [0, 0, filterFBO.width, filterFBO.height] - }, - () => { - filterModel.draw(this.context.renderPass); - } - ); + + const viewport = [0, 0, filterFBO.width, filterFBO.height] as NumberArray4; + + const renderPass = filterModel.device.beginRenderPass({ + id: 'data-filter-aggregation', + framebuffer: filterFBO, + parameters: {viewport}, + clearColor: [0, 0, 0, 0] + }); + filterModel.setParameters(aggregator.parameters); + filterModel.draw(renderPass); + renderPass.end(); + const color = filterModel.device.readPixelsToArrayWebGL(filterFBO); let count = 0; for (let i = 0; i < color.length; i++) { diff --git a/modules/google-maps/src/google-maps-overlay.ts b/modules/google-maps/src/google-maps-overlay.ts index c6e5450838a..68ae13c77d0 100644 --- a/modules/google-maps/src/google-maps-overlay.ts +++ b/modules/google-maps/src/google-maps-overlay.ts @@ -1,5 +1,6 @@ /* global google */ import {GL, GLParameters} from '@luma.gl/constants'; +import {WebGLDevice} from '@luma.gl/webgl'; import { createDeckInstance, destroyDeckInstance, @@ -257,14 +258,16 @@ export default class GoogleMapsOverlay { // As an optimization, some renders are to an separate framebuffer // which we need to pass onto deck - const _framebuffer = device.getParametersWebGL(GL.FRAMEBUFFER_BINDING); + if (device instanceof WebGLDevice) { + const _framebuffer = device.getParametersWebGL(GL.FRAMEBUFFER_BINDING); + } // @ts-expect-error deck.setProps({_framebuffer}); // With external gl context, animation loop doesn't resize webgl-canvas and thus fails to // calculate corrext pixel ratio. Force this manually. - device.getCanvasContext().resize(); + device.getDefaultCanvasContext().resize(); // Camera changed, will trigger a map repaint right after this // Clear any change flag triggered by setting viewState so that deck does not request @@ -273,17 +276,19 @@ export default class GoogleMapsOverlay { // Workaround for bug in Google maps where viewport state is wrong // TODO remove once fixed - device.setParametersWebGL({ - viewport: [0, 0, gl.canvas.width, gl.canvas.height], - scissor: [0, 0, gl.canvas.width, gl.canvas.height], - stencilFunc: [gl.ALWAYS, 0, 255, gl.ALWAYS, 0, 255] - }); + if (device instanceof WebGLDevice) { + device.setParametersWebGL({ + viewport: [0, 0, gl.canvas.width, gl.canvas.height], + scissor: [0, 0, gl.canvas.width, gl.canvas.height], + stencilFunc: [gl.ALWAYS, 0, 255, gl.ALWAYS, 0, 255] + }); - device.withParametersWebGL(GL_STATE, () => { - deck._drawLayers('google-vector', { - clearCanvas: false + device.withParametersWebGL(GL_STATE, () => { + deck._drawLayers('google-vector', { + clearCanvas: false + }); }); - }); + } } } diff --git a/test/modules/extensions/data-filter.spec.ts b/test/modules/extensions/data-filter.spec.ts index 1491a6eed6a..c307ec568ff 100644 --- a/test/modules/extensions/data-filter.spec.ts +++ b/test/modules/extensions/data-filter.spec.ts @@ -140,6 +140,7 @@ test('DataFilterExtension#categories', t => { test('DataFilterExtension#countItems', t => { let cbCalled = 0; + let cbCount = -1; const testCases = [ { @@ -150,12 +151,16 @@ test('DataFilterExtension#countItems', t => { ], getPosition: d => d.position, getFilterValue: d => d.timestamp, - onFilteredItemsChange: () => cbCalled++, + onFilteredItemsChange: event => { + cbCalled++; + cbCount = event.count; + }, filterRange: [80, 160], extensions: [new DataFilterExtension({filterSize: 1, countItems: true})] }, onAfterUpdate: () => { t.is(cbCalled, 1, 'onFilteredItemsChange is called'); + t.is(cbCount, 2, 'count is correct'); } }, { @@ -172,6 +177,7 @@ test('DataFilterExtension#countItems', t => { }, onAfterUpdate: () => { t.is(cbCalled, 2, 'onFilteredItemsChange is called'); + t.is(cbCount, 0, 'count is correct'); } } ];