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

Adaptive downsampling of transparent layers based on framerate #586

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
0d7a69a
temp: see what reduced viewport looks like
seankmartin Apr 25, 2024
316bcf5
temp: set downsampling factor, ds max proj
seankmartin Apr 25, 2024
8be038b
temp: quick experiment with interploation
seankmartin Apr 25, 2024
009c724
Revert "temp: quick experiment with interploation"
seankmartin Apr 25, 2024
cb626cd
feat: 4x downsampling with linear interpolation
seankmartin Apr 25, 2024
c6b0617
temp: see if build updating
seankmartin Apr 25, 2024
ce53842
temp: keep changes
seankmartin Apr 29, 2024
7a3fa12
feat: downsample on camera move - render again if settled
seankmartin Apr 29, 2024
37dc166
temp: single frame chck
seankmartin Apr 29, 2024
6c95d5f
fix: remove logs
seankmartin Apr 29, 2024
43ed997
feat: framerate counter
seankmartin Apr 30, 2024
d27a1c7
feat: downsample adaptively based on framerate
seankmartin Apr 30, 2024
0f189eb
refactor: remove variable
seankmartin Apr 30, 2024
6b35b24
feat: don't downsample after picking
seankmartin Apr 30, 2024
9ff56a7
chore: remove debug log
seankmartin Apr 30, 2024
cbc065a
fix: copy over the depth buffer from offscreen
seankmartin Apr 30, 2024
0352605
refactor: rename VR variables
seankmartin Apr 30, 2024
a521a59
feat: downsample VR based on XY data res or framerate when moving
seankmartin Apr 30, 2024
05d9988
fix: size downsample based on n visible chunks and chunk re
seankmartin May 2, 2024
544664b
chore: format
seankmartin May 2, 2024
a2101a1
fix: remove temp change
seankmartin May 2, 2024
8d8a8a3
refactor: removed unsed call
seankmartin May 2, 2024
d448081
feat: remove linear interpolation in downsample
seankmartin May 2, 2024
34499cc
refactor: remove magic numbers
seankmartin May 2, 2024
425e09f
refactor: clarify frame rate
seankmartin May 3, 2024
6b43762
test: add framerate calc test
seankmartin May 3, 2024
fe266c0
fix: remove downsampling based on VR chunk res due to possible undesi…
seankmartin May 3, 2024
eb292ef
fix: don't double draw opaque layers
seankmartin May 3, 2024
8b30708
feat: pow2 downample
seankmartin May 3, 2024
5007e2b
feat: keep downsample to the highest needed this camera move
seankmartin May 3, 2024
5d25cdb
feat: setting to turn off adaptive downsampling
seankmartin May 3, 2024
aebe5e4
feat: use median frame count for more stability
seankmartin May 3, 2024
0f51de4
feat: calculate frames over larger window for more smoothness
seankmartin May 3, 2024
4898c36
feat: less popping in downsample
seankmartin May 3, 2024
14aac39
fix: respect max downsample rate
seankmartin May 3, 2024
00de472
fix: don't use magic number
seankmartin May 3, 2024
f874d54
revert: go back to smaller frame count
seankmartin May 3, 2024
6b1167d
feat: allow to choose between mean and median in framerate
seankmartin May 3, 2024
e9779a2
fix: correct frame average
seankmartin May 3, 2024
202145f
feat: reduce size of frame counter so that bad frames take impact sooner
seankmartin May 3, 2024
7fe0728
feat: store frame deltas, not raw frame times
seankmartin May 10, 2024
3e04498
refactor: move downsample calculation
seankmartin May 10, 2024
38fa133
feat: track if downsampled this move
seankmartin May 10, 2024
620292f
feat: keep more influence on history to avoid flipping back and forth…
seankmartin May 10, 2024
8d36912
feat: allow full history influence
seankmartin May 10, 2024
97eb116
feat: add frame rate monitor with gl query
seankmartin May 21, 2024
5865899
feat: use gl query times in perspective panel
seankmartin May 21, 2024
3a963e1
Merge branch 'master' into feature/increase-vr-performance
seankmartin May 21, 2024
7cdff67
feat: add more frame timing options
seankmartin May 21, 2024
f60c734
feat: persist a high downsampling over a number of frames
seankmartin May 21, 2024
12d75fe
feat: tweak settings to try improve smoothness of rendering
seankmartin May 21, 2024
61d68e5
refactor: simplify camera move check
seankmartin May 21, 2024
1c201d3
feat: speed up calculation of frame time
seankmartin May 21, 2024
a05b3db
feat: parity between other PR in settings
seankmartin May 21, 2024
03d0d92
feat: use mean frame timing, not max
seankmartin May 29, 2024
c27df8a
fix: don't keep queries around if their time is done
seankmartin May 29, 2024
5a6a650
fix: grab correct no of frames for downsample
seankmartin May 29, 2024
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
2 changes: 2 additions & 0 deletions src/data_panel_layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface ViewerUIState
showPerspectiveSliceViews: TrackableBoolean;
showAxisLines: TrackableBoolean;
wireFrame: TrackableBoolean;
adaptiveDownsampling: TrackableBoolean;
showScaleBar: TrackableBoolean;
scaleBarOptions: TrackableValue<ScaleBarOptions>;
visibleLayerRoles: WatchableSet<RenderLayerRole>;
Expand Down Expand Up @@ -174,6 +175,7 @@ export function getCommonViewerState(viewer: ViewerUIState) {
layerManager: viewer.layerManager,
showAxisLines: viewer.showAxisLines,
wireFrame: viewer.wireFrame,
adaptiveDownsampling: viewer.adaptiveDownsampling,
visibleLayerRoles: viewer.visibleLayerRoles,
selectedLayer: viewer.selectedLayer,
visibility: viewer.visibility,
Expand Down
10 changes: 10 additions & 0 deletions src/display_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { TrackableValue } from "#src/trackable_value.js";
import { animationFrameDebounce } from "#src/util/animation_frame_debounce.js";
import type { Borrowed } from "#src/util/disposable.js";
import { RefCounted } from "#src/util/disposable.js";
import { FramerateMonitor } from "#src/util/framerate.js";
import type { mat4 } from "#src/util/geom.js";
import { parseFixedLengthArray, verifyFloat01 } from "#src/util/json.js";
import { NullarySignal } from "#src/util/signal.js";
Expand Down Expand Up @@ -396,6 +397,7 @@ export class DisplayContext extends RefCounted implements FrameNumberCounter {
rootRect: DOMRect | undefined;
resizeGeneration = 0;
boundsGeneration = -1;
private framerateMonitor = new FramerateMonitor();

// Panels ordered by `drawOrder`. If length is 0, needs to be recomputed.
private orderedPanels: RenderedPanel[] = [];
Expand Down Expand Up @@ -557,6 +559,8 @@ export class DisplayContext extends RefCounted implements FrameNumberCounter {
++this.frameNumber;
this.updateStarted.dispatch();
const gl = this.gl;
const ext = this.framerateMonitor.getTimingExtension(gl);
const query = this.framerateMonitor.startFrameTimeQuery(gl, ext);
this.ensureBoundsUpdated();
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
Expand All @@ -580,6 +584,8 @@ export class DisplayContext extends RefCounted implements FrameNumberCounter {
gl.clear(gl.COLOR_BUFFER_BIT);
this.gl.colorMask(true, true, true, true);
this.updateFinished.dispatch();
this.framerateMonitor.endFrameTimeQuery(gl, ext, query);
this.framerateMonitor.grabAnyFinishedQueryResults(gl);
}

getDepthArray(): Float32Array {
Expand Down Expand Up @@ -607,4 +613,8 @@ export class DisplayContext extends RefCounted implements FrameNumberCounter {
}
return depthArray;
}

getLastFrameTimesInMs(numberOfFrames: number = 10) {
return this.framerateMonitor.getLastFrameTimesInMs(numberOfFrames);
}
}
4 changes: 4 additions & 0 deletions src/layer_group_viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export interface LayerGroupViewerState {
mouseState: MouseSelectionState;
showAxisLines: TrackableBoolean;
wireFrame: TrackableBoolean;
adaptiveDownsampling: TrackableBoolean;
showScaleBar: TrackableBoolean;
scaleBarOptions: TrackableScaleBarOptions;
showPerspectiveSliceViews: TrackableBoolean;
Expand Down Expand Up @@ -356,6 +357,9 @@ export class LayerGroupViewer extends RefCounted {
get wireFrame() {
return this.viewerState.wireFrame;
}
get adaptiveDownsampling() {
return this.viewerState.adaptiveDownsampling;
}
get showScaleBar() {
return this.viewerState.showScaleBar;
}
Expand Down
1 change: 1 addition & 0 deletions src/layer_groups_layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ function getCommonViewerState(viewer: Viewer) {
mouseState: viewer.mouseState,
showAxisLines: viewer.showAxisLines,
wireFrame: viewer.wireFrame,
adaptiveDownsampling: viewer.adaptiveDownsampling,
showScaleBar: viewer.showScaleBar,
scaleBarOptions: viewer.scaleBarOptions,
showPerspectiveSliceViews: viewer.showPerspectiveSliceViews,
Expand Down
121 changes: 109 additions & 12 deletions src/perspective_view/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ import type { TrackableRGB } from "#src/util/color.js";
import type { Owned } from "#src/util/disposable.js";
import type { ActionEvent } from "#src/util/event_action_map.js";
import { registerActionListener } from "#src/util/event_action_map.js";
import {
DownsamplingBasedOnFrameRateCalculator,
FrameTimingMethod,
} from "#src/util/framerate.js";
import { kAxes, kZeroVec4, mat4, vec3, vec4 } from "#src/util/geom.js";
import { startRelativeMouseDrag } from "#src/util/mouse_drag.js";
import type {
Expand All @@ -81,8 +85,11 @@ import { MultipleScaleBarTextures } from "#src/widget/scale_bar.js";
import type { RPC } from "#src/worker_rpc.js";
import { SharedObject } from "#src/worker_rpc.js";

const REDRAW_DELAY_AFTER_CAMERA_MOVE = 300;

export interface PerspectiveViewerState extends RenderedDataViewerState {
wireFrame: WatchableValueInterface<boolean>;
adaptiveDownsampling: WatchableValueInterface<boolean>;
orthographicProjection: TrackableBoolean;
showSliceViews: TrackableBoolean;
showScaleBar: TrackableBoolean;
Expand Down Expand Up @@ -185,6 +192,20 @@ v4f_fragColor = vec4(accum.rgb / accum.a, revealage);
`);
}

// Copy the depth from opaque pass to the depth buffer for OIT.
// This copy is required because the OIT depth buffer might be
// smaller than the main depth buffer.
function defineDepthCopyShader(builder: ShaderBuilder) {
builder.addOutputBuffer("vec4", "v4f_fragData0", 0);
builder.addOutputBuffer("vec4", "v4f_fragData1", 1);
builder.setFragmentMain(`
v4f_fragData0 = vec4(0.0, 0.0, 0.0, 1.0);
v4f_fragData1 = vec4(0.0, 0.0, 0.0, 1.0);
vec4 v0 = getValue0();
gl_FragDepth = 1.0 - v0.r;
`);
}

// Copy the max projection color to the OIT buffer
function defineMaxProjectionColorCopyShader(builder: ShaderBuilder) {
builder.addOutputBuffer("vec4", "v4f_fragData0", 0);
Expand Down Expand Up @@ -262,6 +283,23 @@ export class PerspectivePanel extends RenderedDataPanel {
return this.navigationState.displayDimensionRenderInfo;
}

// the frame rate calculator is used to determine if downsampling should be applied
// after a camera move
// if a high downsample rate is applied, it persists for a few frames
// to avoid flickering when the camera is moving
private frameRateCalculator = new DownsamplingBasedOnFrameRateCalculator(
10 /* numberOfStoredFrameDeltas */,
8 /* maxDownsamplingFactor */,
16 /* desiredFrameTimingMs */,
60 /* downsamplingPersistenceDurationInFrames */,
);
private redrawAfterMoveTimeOutId = -1;
private hasTransparent = false;

get isCameraMoving() {
return this.redrawAfterMoveTimeOutId !== -1;
}

/**
* If boolean value is true, sliceView is shown unconditionally, regardless of the value of
* this.viewer.showSliceViews.value.
Expand Down Expand Up @@ -327,6 +365,9 @@ export class PerspectivePanel extends RenderedDataPanel {
protected maxProjectionColorCopyHelper = this.registerDisposer(
OffscreenCopyHelper.get(this.gl, defineMaxProjectionColorCopyShader, 2),
);
protected offscreenDepthCopyHelper = this.registerDisposer(
OffscreenCopyHelper.get(this.gl, defineDepthCopyShader, 1),
);
protected maxProjectionPickCopyHelper = this.registerDisposer(
OffscreenCopyHelper.get(this.gl, defineMaxProjectionPickCopyShader, 2),
);
Expand Down Expand Up @@ -417,6 +458,23 @@ export class PerspectivePanel extends RenderedDataPanel {
this,
);

this.registerDisposer(
this.viewer.navigationState.changed.add(() => {
// Don't mark camera moving on picking requests
if (this.isMovingToMousePositionOnPick) {
return;
}
if (this.redrawAfterMoveTimeOutId !== -1) {
window.clearTimeout(this.redrawAfterMoveTimeOutId);
}
this.redrawAfterMoveTimeOutId = window.setTimeout(() => {
this.redrawAfterMoveTimeOutId = -1;
this.frameRateCalculator.resetLastFrameTime();
this.context.scheduleRedraw();
}, REDRAW_DELAY_AFTER_CAMERA_MOVE);
}),
);

registerActionListener(
element,
"rotate-via-mouse-drag",
Expand Down Expand Up @@ -717,7 +775,7 @@ export class PerspectivePanel extends RenderedDataPanel {
this.gl.RGBA,
this.gl.FLOAT,
),
depthBuffer: this.offscreenFramebuffer.depthBuffer!.addRef(),
depthBuffer: new DepthStencilRenderbuffer(this.gl),
}),
);
}
Expand Down Expand Up @@ -911,9 +969,8 @@ export class PerspectivePanel extends RenderedDataPanel {

const { visibleLayers } = this.visibleLayerTracker;

let hasTransparent = false;
this.hasTransparent = false;
let hasMaxProjection = false;

let hasAnnotation = false;

// Draw fully-opaque layers first.
Expand All @@ -925,7 +982,7 @@ export class PerspectivePanel extends RenderedDataPanel {
hasAnnotation = true;
}
} else {
hasTransparent = true;
this.hasTransparent = true;
if (renderLayer.isVolumeRendering) {
hasMaxProjection =
hasMaxProjection ||
Expand Down Expand Up @@ -974,16 +1031,41 @@ export class PerspectivePanel extends RenderedDataPanel {
/*dppass=*/ WebGL2RenderingContext.KEEP,
);

if (hasTransparent) {
if (this.hasTransparent) {
//Draw transparent objects.

let volumeRenderingBufferWidth = width;
let volumeRenderingBufferHeight = height;

if (this.viewer.adaptiveDownsampling.value && this.isCameraMoving) {
this.frameRateCalculator.setFrameDeltas(
this.context.getLastFrameTimesInMs(
this.frameRateCalculator.numberOfStoredFrameDeltas,
),
);
const downsamplingFactor =
this.frameRateCalculator.calculateDownsamplingRateBasedOnFrameDeltas(
FrameTimingMethod.MEAN,
);
if (downsamplingFactor > 1) {
const originalRatio = width / height;
volumeRenderingBufferWidth = Math.round(width / downsamplingFactor);
volumeRenderingBufferHeight = Math.round(
volumeRenderingBufferWidth / originalRatio,
);
}
}

// Create max projection buffer if needed.
let bindMaxProjectionBuffer: () => void = () => {};
let bindMaxProjectionPickingBuffer: () => void = () => {};
if (hasMaxProjection) {
const { maxProjectionConfiguration } = this;
bindMaxProjectionBuffer = () => {
maxProjectionConfiguration.bind(width, height);
maxProjectionConfiguration.bind(
volumeRenderingBufferWidth,
volumeRenderingBufferHeight,
);
};
gl.depthMask(true);
bindMaxProjectionBuffer();
Expand All @@ -996,7 +1078,10 @@ export class PerspectivePanel extends RenderedDataPanel {

const { maxProjectionPickConfiguration } = this;
bindMaxProjectionPickingBuffer = () => {
maxProjectionPickConfiguration.bind(width, height);
maxProjectionPickConfiguration.bind(
volumeRenderingBufferWidth,
volumeRenderingBufferHeight,
);
};
bindMaxProjectionPickingBuffer();
gl.clear(
Expand All @@ -1005,15 +1090,25 @@ export class PerspectivePanel extends RenderedDataPanel {
);
}

// Compute accumulate and revealage textures.
gl.depthMask(false);
gl.enable(WebGL2RenderingContext.BLEND);
// Copy the depth buffer from the offscreen framebuffer to the transparent framebuffer.
gl.depthMask(true);
gl.clearDepth(1.0);
const { transparentConfiguration } = this;
renderContext.bindFramebuffer = () => {
transparentConfiguration.bind(width, height);
transparentConfiguration.bind(
volumeRenderingBufferWidth,
volumeRenderingBufferHeight,
);
};
renderContext.bindFramebuffer();
gl.clearDepth(1.0);
gl.clear(WebGL2RenderingContext.DEPTH_BUFFER_BIT);
this.offscreenDepthCopyHelper.draw(
this.offscreenFramebuffer.colorBuffers[OffscreenTextures.Z].texture,
);

// Compute accumulate and revealage textures.
gl.depthMask(false);
gl.enable(WebGL2RenderingContext.BLEND);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(WebGL2RenderingContext.COLOR_BUFFER_BIT);
renderContext.emitter = perspectivePanelEmitOIT;
Expand All @@ -1024,6 +1119,7 @@ export class PerspectivePanel extends RenderedDataPanel {
WebGL2RenderingContext.ONE_MINUS_SRC_ALPHA,
);
renderContext.emitPickID = false;

for (const [renderLayer, attachment] of visibleLayers) {
if (renderLayer.isTransparent) {
renderContext.depthBufferTexture =
Expand Down Expand Up @@ -1096,6 +1192,7 @@ export class PerspectivePanel extends RenderedDataPanel {
// Copy transparent rendering result back to primary buffer.
gl.disable(WebGL2RenderingContext.DEPTH_TEST);
this.offscreenFramebuffer.bindSingle(OffscreenTextures.COLOR);
gl.viewport(0, 0, width, height);
gl.blendFunc(
WebGL2RenderingContext.ONE_MINUS_SRC_ALPHA,
WebGL2RenderingContext.SRC_ALPHA,
Expand Down
4 changes: 4 additions & 0 deletions src/rendered_data_panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,8 @@ export abstract class RenderedDataPanel extends RenderedPanel {
this.attemptToIssuePickRequest();
}

protected isMovingToMousePositionOnPick = false;

constructor(
context: Borrowed<DisplayContext>,
element: HTMLElement,
Expand Down Expand Up @@ -616,7 +618,9 @@ export abstract class RenderedDataPanel extends RenderedPanel {
registerActionListener(element, "move-to-mouse-position", () => {
const { mouseState } = this.viewer;
if (mouseState.updateUnconditionally()) {
this.isMovingToMousePositionOnPick = true;
this.navigationState.position.value = mouseState.position;
this.isMovingToMousePositionOnPick = false;
}
});

Expand Down
1 change: 1 addition & 0 deletions src/ui/viewer_settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class ViewerSettingsPanel extends SidePanel {
);
addCheckbox("Wire frame rendering", viewer.wireFrame);
addCheckbox("Enable prefetching", viewer.chunkQueueManager.enablePrefetch);
addCheckbox("Enable adaptive downsampling", viewer.adaptiveDownsampling);

const addColor = (label: string, value: WatchableValueInterface<vec3>) => {
const labelElement = document.createElement("label");
Expand Down
Loading
Loading