Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions modules/core/src/lib/deck-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Copyright (c) vis.gl contributors

import {Buffer, Texture} from '@luma.gl/core';
import type {CanvasContext, Device} from '@luma.gl/core';
import type {CanvasContext, Device, PresentationContext} from '@luma.gl/core';
import PickLayersPass, {PickingColorDecoder} from '../passes/pick-layers-pass';
import log from '../utils/log';
import {getClosestObject, getUniqueObjects, PickedPixel} from './picking/query-object';
Expand Down Expand Up @@ -44,7 +44,7 @@ type PickOperationContext = {
layers: Layer[];
views: Record<string, View>;
viewports: Viewport[];
canvasContext?: CanvasContext;
canvasContext?: CanvasContext | PresentationContext;
onViewportActive: (viewport: Viewport) => void;
effects: Effect[];
};
Expand Down Expand Up @@ -158,7 +158,9 @@ export default class DeckPicker {
// Private

/** Ensures that picking framebuffer exists and matches the canvas size */
_resizeBuffer(canvasContext: CanvasContext = this.device.getDefaultCanvasContext()) {
_resizeBuffer(
canvasContext: CanvasContext | PresentationContext = this.device.getDefaultCanvasContext()
) {
// Create a frame buffer if not already available
if (!this.pickingFBO) {
const pickingColorTexture = this.device.createTexture({
Expand Down Expand Up @@ -282,7 +284,8 @@ export default class DeckPicker {
deviceRect,
cullRect,
effects,
pass: `picking:${mode}`
pass: `picking:${mode}`,
canvasContext
});

pickInfo = getClosestObject({
Expand Down Expand Up @@ -316,7 +319,8 @@ export default class DeckPicker {
},
cullRect,
effects,
pass: `picking:${mode}:z`
pass: `picking:${mode}:z`,
canvasContext
},
true
);
Expand Down Expand Up @@ -446,7 +450,8 @@ export default class DeckPicker {
deviceRect,
cullRect,
effects,
pass: `picking:${mode}`
pass: `picking:${mode}`,
canvasContext
});

pickInfo = getClosestObject({
Expand Down Expand Up @@ -480,7 +485,8 @@ export default class DeckPicker {
},
cullRect,
effects,
pass: `picking:${mode}:z`
pass: `picking:${mode}:z`,
canvasContext
},
true
);
Expand Down Expand Up @@ -588,7 +594,8 @@ export default class DeckPicker {
deviceRect,
cullRect: {x, y, width, height},
effects,
pass: `picking:${mode}`
pass: `picking:${mode}`,
canvasContext
});

const pickInfos = getUniqueObjects(pickedResult);
Expand Down Expand Up @@ -693,7 +700,8 @@ export default class DeckPicker {
deviceRect,
cullRect: {x, y, width, height},
effects,
pass: `picking:${mode}`
pass: `picking:${mode}`,
canvasContext
});

const pickInfos = getUniqueObjects(pickedResult);
Expand Down Expand Up @@ -751,6 +759,7 @@ export default class DeckPicker {
onViewportActive: (viewport: Viewport) => void;
cullRect?: Rect;
effects: Effect[];
canvasContext?: CanvasContext | PresentationContext;
}): Promise<{
pickedColors: Uint8Array;
decodePickingColor: PickingColorDecoder;
Expand All @@ -767,6 +776,7 @@ export default class DeckPicker {
onViewportActive: (viewport: Viewport) => void;
cullRect?: Rect;
effects: Effect[];
canvasContext?: CanvasContext | PresentationContext;
},
pickZ: true
): Promise<{
Expand All @@ -784,7 +794,8 @@ export default class DeckPicker {
deviceRect,
cullRect,
effects,
pass
pass,
canvasContext
}: {
deviceRect: Rect;
pass: string;
Expand All @@ -794,6 +805,7 @@ export default class DeckPicker {
onViewportActive: (viewport: Viewport) => void;
cullRect?: Rect;
effects: Effect[];
canvasContext?: CanvasContext | PresentationContext;
},
pickZ: boolean = false
): Promise<{
Expand All @@ -812,6 +824,7 @@ export default class DeckPicker {
cullRect,
effects,
pass,
canvasContext,
pickZ,
preRenderStats: {},
isPicking: true
Expand Down Expand Up @@ -921,6 +934,7 @@ export default class DeckPicker {
onViewportActive: (viewport: Viewport) => void;
cullRect?: Rect;
effects: Effect[];
canvasContext?: CanvasContext | PresentationContext;
}): {
pickedColors: Uint8Array;
decodePickingColor: PickingColorDecoder;
Expand All @@ -940,6 +954,7 @@ export default class DeckPicker {
onViewportActive: (viewport: Viewport) => void;
cullRect?: Rect;
effects: Effect[];
canvasContext?: CanvasContext | PresentationContext;
},
pickZ: true
): {
Expand All @@ -957,7 +972,8 @@ export default class DeckPicker {
deviceRect,
cullRect,
effects,
pass
pass,
canvasContext
}: {
deviceRect: Rect;
pass: string;
Expand All @@ -967,6 +983,7 @@ export default class DeckPicker {
onViewportActive: (viewport: Viewport) => void;
cullRect?: Rect;
effects: Effect[];
canvasContext?: CanvasContext | PresentationContext;
},
pickZ: boolean = false
): {
Expand All @@ -985,6 +1002,7 @@ export default class DeckPicker {
cullRect,
effects,
pass,
canvasContext,
pickZ,
preRenderStats: {},
isPicking: true
Expand Down
9 changes: 5 additions & 4 deletions modules/core/src/lib/deck-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import type {Device} from '@luma.gl/core';
import type {CanvasContext, Device, PresentationContext} from '@luma.gl/core';
import {Framebuffer} from '@luma.gl/core';
import debug from '../debug/index';
import DrawLayersPass from '../passes/draw-layers-pass';
Expand Down Expand Up @@ -65,6 +65,7 @@ export default class DeckRenderer {
onViewportActive: (viewport: Viewport) => void;
effects: Effect[];
target?: Framebuffer | null;
canvasContext?: CanvasContext | PresentationContext;
layerFilter?: LayerFilter;
clearStack?: boolean;
clearCanvas?: boolean;
Expand Down Expand Up @@ -146,13 +147,13 @@ export default class DeckRenderer {
}

if (this.lastPostProcessEffect) {
this._resizeRenderBuffers();
this._resizeRenderBuffers(opts.canvasContext);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve post-process output against the selected canvas

When a non-default canvasContext is rendered with a built-in PostProcessEffect and the canvas target is null, this only sizes the intermediate buffers from the selected context. _postRender still gives the final effect opts.target, so PostProcessEffect/ScreenPass starts its final pass with a null framebuffer, which selects the device's default canvas rather than canvasContext.getCurrentFramebuffer(); the selected canvas receives no composited output while the default canvas is overwritten.

Useful? React with 👍 / 👎.

}
}

private _resizeRenderBuffers() {
private _resizeRenderBuffers(canvasContext = this.device.canvasContext!) {
const {renderBuffers} = this;
const size = this.device.canvasContext!.getDrawingBufferSize();
const size = canvasContext.getDrawingBufferSize();
const [width, height] = size;
if (renderBuffers.length === 0) {
[0, 1].map(i => {
Expand Down
26 changes: 18 additions & 8 deletions modules/core/src/passes/layers-pass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
// Copyright (c) vis.gl contributors

import type {
CanvasContext,
Device,
Parameters,
PresentationContext,
RenderPassParameters,
RenderPipelineParameters
} from '@luma.gl/core';
Expand Down Expand Up @@ -36,6 +38,8 @@ const WEBGPU_DEFAULT_DRAW_PARAMETERS: RenderPipelineParameters = {
export type LayersPassRenderOptions = {
/** @deprecated TODO v9 recommend we rename this to framebuffer to minimize confusion */
target?: Framebuffer | null;
/** Canvas context that provides framebuffer dimensions and pixel conversion. */
canvasContext?: CanvasContext | PresentationContext;
isPicking?: boolean;
pass: string;
layers: Layer[];
Expand Down Expand Up @@ -87,7 +91,7 @@ export default class LayersPass extends Pass {
}

protected _render(options: LayersPassRenderOptions): RenderStats[] {
const canvasContext = this.device.canvasContext!;
const {canvasContext = this.device.canvasContext!} = options;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Clear views on the selected canvas framebuffer

When canvasContext is a non-default presentation context and a View has clear enabled, _render resolves that context's framebuffer here, but _drawLayers still forwards only the original target (typically null or undefined) and _drawLayersInViewport opens its clear pass with framebuffer: target. The clear consequently runs against the device's default canvas rather than the framebuffer used by the outer render pass, leaving stale color/depth in the selected canvas and potentially clearing another canvas; pass the resolved framebuffer into the per-view clear.

Useful? React with 👍 / 👎.

const framebuffer = options.target ?? canvasContext.getCurrentFramebuffer();
const [width, height] = canvasContext.getDrawingBufferSize();

Expand Down Expand Up @@ -126,6 +130,7 @@ export default class LayersPass extends Pass {
/** Draw a list of layers in a list of viewports */
private _drawLayers(renderPass: RenderPass, options: LayersPassRenderOptions) {
const {
canvasContext = this.device.canvasContext!,
target,
shaderModuleProps,
viewports,
Expand Down Expand Up @@ -156,6 +161,7 @@ export default class LayersPass extends Pass {
renderPass,
{
target,
canvasContext,
shaderModuleProps,
viewport: subViewport,
view,
Expand Down Expand Up @@ -184,6 +190,7 @@ export default class LayersPass extends Pass {
cullRect,
views,
effects,
canvasContext = this.device.canvasContext!,
shaderModuleProps
}: LayersPassRenderOptions,
/** Internal flag, true if only used to determine whether each layer should be drawn */
Expand Down Expand Up @@ -223,6 +230,7 @@ export default class LayersPass extends Pass {
layer,
effects,
pass,
canvasContext,
shaderModuleProps
);
const defaultParams =
Expand Down Expand Up @@ -251,6 +259,7 @@ export default class LayersPass extends Pass {
shaderModuleProps: globalModuleParameters,
pass,
target,
canvasContext,
viewport,
view,
isPicking
Expand All @@ -259,13 +268,15 @@ export default class LayersPass extends Pass {
shaderModuleProps: Record<string, any>;
pass: string;
target?: Framebuffer | null;
canvasContext: CanvasContext | PresentationContext;
viewport: Viewport;
view?: View;
isPicking?: boolean;
},
drawLayerParams: DrawLayerParameters[]
): RenderStats {
const glViewport = getGLViewport(this.device, {
canvasContext,
shaderModuleProps: globalModuleParameters,
target,
viewport
Expand Down Expand Up @@ -431,10 +442,10 @@ export default class LayersPass extends Pass {
layer: Layer,
effects: Effect[] | undefined,
pass: string,
canvasContext: CanvasContext | PresentationContext,
overrides: any
): any {
// @ts-expect-error TODO - assuming WebGL context
const devicePixelRatio = this.device.canvasContext.cssToDeviceRatio();
const devicePixelRatio = canvasContext.cssToDeviceRatio();
const layerProps = layer.internalState?.propsInTransition || layer.props;

const shaderModuleProps = {
Expand Down Expand Up @@ -530,23 +541,22 @@ export function layerIndexResolver(
function getGLViewport(
device: Device,
{
canvasContext = device.canvasContext!,
shaderModuleProps,
target,
viewport
}: {
canvasContext?: CanvasContext | PresentationContext;
shaderModuleProps: any;
target?: Framebuffer | null;
viewport: Viewport;
}
): [number, number, number, number] {
const pixelRatio =
shaderModuleProps?.project?.devicePixelRatio ??
// @ts-expect-error TODO - assuming WebGL context
device.canvasContext.cssToDeviceRatio();
shaderModuleProps?.project?.devicePixelRatio ?? canvasContext.cssToDeviceRatio();

// Default framebuffer is used when writing to canvas
// @ts-expect-error TODO - assuming WebGL context
const [, drawingBufferHeight] = device.canvasContext.getDrawingBufferSize();
const [, drawingBufferHeight] = canvasContext.getDrawingBufferSize();
const height = target ? target.height : drawingBufferHeight;

// Convert viewport top-left CSS coordinates to bottom up WebGL coordinates
Expand Down
2 changes: 2 additions & 0 deletions modules/core/src/passes/pick-layers-pass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export default class PickLayersPass extends LayersPass {
effects,
pass = 'picking',
pickZ,
canvasContext,
shaderModuleProps,
clearColor
}: PickLayersPassRenderOptions): {
Expand All @@ -99,6 +100,7 @@ export default class PickLayersPass extends LayersPass {
cullRect,
effects: effects?.filter(e => e.useInPicking),
pass,
canvasContext,
isPicking: true,
shaderModuleProps,
clearColor: clearColor ?? [0, 0, 0, 0],
Expand Down
16 changes: 15 additions & 1 deletion test/modules/core/lib/deck-picker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {test, expect} from 'vitest';
import {test, expect, vi} from 'vitest';
import {LayerManager, MapView} from '@deck.gl/core';
import {ScatterplotLayer} from '@deck.gl/layers';
import DeckPicker from '@deck.gl/core/lib/deck-picker';
Expand Down Expand Up @@ -90,6 +90,7 @@ test('DeckPicker#pick empty', () => {
viewState: {longitude: 0, latitude: 0, zoom: 1}
});
const layerManager = new LayerManager(device, {viewport});
const drawAndSample = vi.spyOn(deckPicker, '_drawAndSample');

const opts = {
layers: [],
Expand Down Expand Up @@ -128,6 +129,12 @@ test('DeckPicker#pick empty', () => {
deckPicker.setProps({_pickable: true});
output = deckPicker.pickObject(opts);
expect(output.result[0].layer, 'Layer is picked').toBe(layer);
expect(
drawAndSample,
'the active canvas context reaches synchronous picking'
).toHaveBeenCalledWith(
expect.objectContaining({canvasContext: device.getDefaultCanvasContext()})
);

expect(deckPicker.pickingFBO, 'pickingFBO is generated').toBeTruthy();

Expand All @@ -145,6 +152,7 @@ test('DeckPicker#pick async empty', async () => {
viewState: {longitude: 0, latitude: 0, zoom: 1}
});
const layerManager = new LayerManager(device, {viewport});
const drawAndSampleAsync = vi.spyOn(deckPicker, '_drawAndSampleAsync');

const opts = {
layers: [],
Expand Down Expand Up @@ -174,6 +182,12 @@ test('DeckPicker#pick async empty', async () => {
deckPicker.setProps({_pickable: true});
output = await deckPicker.pickObjectAsync(opts);
expect(output.result[0].layer, 'Layer is picked (async)').toBe(layer);
expect(
drawAndSampleAsync,
'the active canvas context reaches asynchronous picking'
).toHaveBeenCalledWith(
expect.objectContaining({canvasContext: device.getDefaultCanvasContext()})
);

expect(deckPicker.pickingFBO, 'pickingFBO is generated (async)').toBeTruthy();

Expand Down
Loading
Loading