Skip to content
Snippets Groups Projects
Commit 991d2e3a authored by Alexander Rose's avatar Alexander Rose
Browse files

async gl repr object handling

parent 7f4ac678
No related branches found
No related tags found
No related merge requests found
...@@ -31,6 +31,7 @@ import { PixelData } from '../mol-util/image'; ...@@ -31,6 +31,7 @@ import { PixelData } from '../mol-util/image';
import { readTexture } from '../mol-gl/compute/util'; import { readTexture } from '../mol-gl/compute/util';
import { DrawPass } from './passes/draw'; import { DrawPass } from './passes/draw';
import { PickPass } from './passes/pick'; import { PickPass } from './passes/pick';
import { Task } from '../mol-task';
export const Canvas3DParams = { export const Canvas3DParams = {
// TODO: FPS cap? // TODO: FPS cap?
...@@ -66,6 +67,7 @@ interface Canvas3D { ...@@ -66,6 +67,7 @@ interface Canvas3D {
getLoci: (pickingId: PickingId) => Representation.Loci getLoci: (pickingId: PickingId) => Representation.Loci
readonly didDraw: BehaviorSubject<now.Timestamp> readonly didDraw: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
handleResize: () => void handleResize: () => void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */ /** Focuses camera on scene's bounding sphere, centered and zoomed. */
...@@ -85,12 +87,13 @@ interface Canvas3D { ...@@ -85,12 +87,13 @@ interface Canvas3D {
} }
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now())) const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
const DefaultRunTask = (task: Task<unknown>) => task.run()
namespace Canvas3D { namespace Canvas3D {
export interface HighlightEvent { current: Representation.Loci, modifiers?: ModifiersKeys } export interface HighlightEvent { current: Representation.Loci, modifiers?: ModifiersKeys }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys } export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) { export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
const gl = getGLContext(canvas, { const gl = getGLContext(canvas, {
alpha: false, alpha: false,
antialias: true, antialias: true,
...@@ -99,10 +102,10 @@ namespace Canvas3D { ...@@ -99,10 +102,10 @@ namespace Canvas3D {
}) })
if (gl === null) throw new Error('Could not create a WebGL rendering context') if (gl === null) throw new Error('Could not create a WebGL rendering context')
const input = InputObserver.fromElement(canvas) const input = InputObserver.fromElement(canvas)
return Canvas3D.create(gl, input, props) return Canvas3D.create(gl, input, props, runTask)
} }
export function create(gl: GLRenderingContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D { export function create(gl: GLRenderingContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
const p = { ...PD.getDefaultValues(Canvas3DParams), ...props } const p = { ...PD.getDefaultValues(Canvas3DParams), ...props }
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>() const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
...@@ -137,6 +140,7 @@ namespace Canvas3D { ...@@ -137,6 +140,7 @@ namespace Canvas3D {
let isUpdating = false let isUpdating = false
let drawPending = false let drawPending = false
let cameraResetRequested = false
function getLoci(pickingId: PickingId) { function getLoci(pickingId: PickingId) {
let loci: Loci = EmptyLoci let loci: Loci = EmptyLoci
...@@ -201,7 +205,7 @@ namespace Canvas3D { ...@@ -201,7 +205,7 @@ namespace Canvas3D {
} }
function render(variant: 'pick' | 'draw', force: boolean) { function render(variant: 'pick' | 'draw', force: boolean) {
if (isUpdating) return false if (isUpdating || scene.isCommiting) return false
let didRender = false let didRender = false
controls.update(currentTime); controls.update(currentTime);
...@@ -279,8 +283,15 @@ namespace Canvas3D { ...@@ -279,8 +283,15 @@ namespace Canvas3D {
scene.update(repr.renderObjects, false) scene.update(repr.renderObjects, false)
if (debugHelper.isEnabled) debugHelper.update() if (debugHelper.isEnabled) debugHelper.update()
isUpdating = false isUpdating = false
requestDraw(true)
reprCount.next(reprRenderObjects.size) runTask(scene.commit()).then(() => {
if (cameraResetRequested && !scene.isCommiting) {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
cameraResetRequested = false
}
requestDraw(true)
reprCount.next(reprRenderObjects.size)
})
} }
handleResize() handleResize()
...@@ -307,8 +318,15 @@ namespace Canvas3D { ...@@ -307,8 +318,15 @@ namespace Canvas3D {
scene.update(void 0, false) scene.update(void 0, false)
if (debugHelper.isEnabled) debugHelper.update() if (debugHelper.isEnabled) debugHelper.update()
isUpdating = false isUpdating = false
requestDraw(true)
reprCount.next(reprRenderObjects.size) runTask(scene.commit()).then(() => {
if (cameraResetRequested && !scene.isCommiting) {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
cameraResetRequested = false
}
requestDraw(true)
reprCount.next(reprRenderObjects.size)
})
} }
}, },
update: (repr, keepSphere) => { update: (repr, keepSphere) => {
...@@ -334,8 +352,12 @@ namespace Canvas3D { ...@@ -334,8 +352,12 @@ namespace Canvas3D {
handleResize, handleResize,
resetCamera: () => { resetCamera: () => {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius) if (scene.isCommiting) {
requestDraw(true); cameraResetRequested = true
} else {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
requestDraw(true);
}
}, },
camera, camera,
downloadScreenshot: () => { downloadScreenshot: () => {
...@@ -351,6 +373,7 @@ namespace Canvas3D { ...@@ -351,6 +373,7 @@ namespace Canvas3D {
} }
}, },
didDraw, didDraw,
reprCount,
setProps: (props: Partial<Canvas3DProps>) => { setProps: (props: Partial<Canvas3DProps>) => {
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) { if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
camera.setState({ mode: props.cameraMode }) camera.setState({ mode: props.cameraMode })
......
/** /**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* *
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
...@@ -12,6 +12,8 @@ import { Object3D } from './object3d'; ...@@ -12,6 +12,8 @@ import { Object3D } from './object3d';
import { Sphere3D } from '../mol-math/geometry'; import { Sphere3D } from '../mol-math/geometry';
import { Vec3 } from '../mol-math/linear-algebra'; import { Vec3 } from '../mol-math/linear-algebra';
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper'; import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
import { RuntimeContext, Task } from '../mol-task';
import { AsyncQueue } from '../mol-util/async-queue';
const boundaryHelper = new BoundaryHelper(); const boundaryHelper = new BoundaryHelper();
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D { function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
...@@ -54,10 +56,12 @@ interface Scene extends Object3D { ...@@ -54,10 +56,12 @@ interface Scene extends Object3D {
readonly count: number readonly count: number
readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>> readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
readonly boundingSphere: Sphere3D readonly boundingSphere: Sphere3D
readonly isCommiting: boolean
update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void
add: (o: GraphicsRenderObject) => Renderable<any> add: (o: GraphicsRenderObject) => void // Renderable<any>
remove: (o: GraphicsRenderObject) => void remove: (o: GraphicsRenderObject) => void
commit: () => Task<void>
has: (o: GraphicsRenderObject) => boolean has: (o: GraphicsRenderObject) => boolean
clear: () => void clear: () => void
forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void
...@@ -68,15 +72,63 @@ namespace Scene { ...@@ -68,15 +72,63 @@ namespace Scene {
const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>() const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
const renderables: Renderable<RenderableValues & BaseValues>[] = [] const renderables: Renderable<RenderableValues & BaseValues>[] = []
const boundingSphere = Sphere3D.zero() const boundingSphere = Sphere3D.zero()
let boundingSphereDirty = true let boundingSphereDirty = true
const object3d = Object3D.create() const object3d = Object3D.create()
const add = (o: GraphicsRenderObject) => {
if (!renderableMap.has(o)) {
const renderable = createRenderable(ctx, o)
renderables.push(renderable)
renderableMap.set(o, renderable)
boundingSphereDirty = true
return renderable;
} else {
console.warn(`RenderObject with id '${o.id}' already present`)
return renderableMap.get(o)!
}
}
const remove = (o: GraphicsRenderObject) => {
const renderable = renderableMap.get(o)
if (renderable) {
renderable.dispose()
renderables.splice(renderables.indexOf(renderable), 1)
renderableMap.delete(o)
boundingSphereDirty = true
}
}
const commitQueue = new AsyncQueue<any>();
const toAdd: GraphicsRenderObject[] = []
const toRemove: GraphicsRenderObject[] = []
type CommitParams = { toAdd: GraphicsRenderObject[], toRemove: GraphicsRenderObject[] }
const step = 100
const handle = async (ctx: RuntimeContext, arr: GraphicsRenderObject[], fn: (o: GraphicsRenderObject) => void, message: string) => {
for (let i = 0, il = arr.length; i < il; i += step) {
if (ctx.shouldUpdate) await ctx.update({ message, current: i, max: il })
for (let j = i, jl = Math.min(i + step, il); j < jl; ++j) {
fn(arr[j])
}
}
}
const commit = async (ctx: RuntimeContext, p: CommitParams) => {
await handle(ctx, p.toRemove, remove, 'Removing GraphicsRenderObjects')
await handle(ctx, p.toAdd, add, 'Adding GraphicsRenderObjects')
if (ctx.shouldUpdate) await ctx.update({ message: 'Sorting GraphicsRenderObjects' })
renderables.sort(renderableSort)
}
return { return {
get view () { return object3d.view }, get view () { return object3d.view },
get position () { return object3d.position }, get position () { return object3d.position },
get direction () { return object3d.direction }, get direction () { return object3d.direction },
get up () { return object3d.up }, get up () { return object3d.up },
get isCommiting () { return commitQueue.length > 0 },
update(objects, keepBoundingSphere) { update(objects, keepBoundingSphere) {
Object3D.update(object3d) Object3D.update(object3d)
...@@ -94,27 +146,28 @@ namespace Scene { ...@@ -94,27 +146,28 @@ namespace Scene {
if (!keepBoundingSphere) boundingSphereDirty = true if (!keepBoundingSphere) boundingSphereDirty = true
}, },
add: (o: GraphicsRenderObject) => { add: (o: GraphicsRenderObject) => {
if (!renderableMap.has(o)) { toAdd.push(o)
const renderable = createRenderable(ctx, o)
renderables.push(renderable)
renderables.sort(renderableSort)
renderableMap.set(o, renderable)
boundingSphereDirty = true
return renderable;
} else {
console.warn(`RenderObject with id '${o.id}' already present`)
return renderableMap.get(o)!
}
}, },
remove: (o: GraphicsRenderObject) => { remove: (o: GraphicsRenderObject) => {
const renderable = renderableMap.get(o) toRemove.push(o)
if (renderable) { },
renderable.dispose() commit: () => {
renderables.splice(renderables.indexOf(renderable), 1) const params = { toAdd: [ ...toAdd ], toRemove: [ ...toRemove ] }
renderables.sort(renderableSort) toAdd.length = 0
renderableMap.delete(o) toRemove.length = 0
boundingSphereDirty = true
} return Task.create('Commiting GraphicsRenderObjects', async ctx => {
const removed = await commitQueue.enqueue(params);
if (!removed) return;
try {
await commit(ctx, params);
} finally {
commitQueue.handled(params);
}
}, () => {
commitQueue.remove(params);
})
}, },
has: (o: GraphicsRenderObject) => { has: (o: GraphicsRenderObject) => {
return renderableMap.has(o) return renderableMap.has(o)
......
...@@ -18,15 +18,19 @@ export function registerDefault(ctx: PluginContext) { ...@@ -18,15 +18,19 @@ export function registerDefault(ctx: PluginContext) {
export function SyncRepresentationToCanvas(ctx: PluginContext) { export function SyncRepresentationToCanvas(ctx: PluginContext) {
let reprCount = 0; let reprCount = 0;
ctx.events.canvas3d.initialized.subscribe(() => {
ctx.canvas3d.reprCount.subscribe(v => {
if (reprCount === 0) ctx.canvas3d.resetCamera();
reprCount = v;
});
})
const events = ctx.state.dataState.events; const events = ctx.state.dataState.events;
events.object.created.subscribe(e => { events.object.created.subscribe(e => {
if (!SO.isRepresentation3D(e.obj)) return; if (!SO.isRepresentation3D(e.obj)) return;
updateVisibility(e.state.cells.get(e.ref)!, e.obj.data.repr); updateVisibility(e.state.cells.get(e.ref)!, e.obj.data.repr);
e.obj.data.repr.setState({ syncManually: true }); e.obj.data.repr.setState({ syncManually: true });
ctx.canvas3d.add(e.obj.data.repr); ctx.canvas3d.add(e.obj.data.repr);
if (reprCount === 0) ctx.canvas3d.resetCamera();
reprCount++;
}); });
events.object.updated.subscribe(e => { events.object.updated.subscribe(e => {
if (e.oldObj && SO.isRepresentation3D(e.oldObj)) { if (e.oldObj && SO.isRepresentation3D(e.oldObj)) {
...@@ -50,7 +54,6 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) { ...@@ -50,7 +54,6 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
ctx.canvas3d.remove(e.obj.data.repr); ctx.canvas3d.remove(e.obj.data.repr);
ctx.canvas3d.requestDraw(true); ctx.canvas3d.requestDraw(true);
e.obj.data.repr.destroy(); e.obj.data.repr.destroy();
reprCount--;
}); });
} }
......
...@@ -73,6 +73,7 @@ export class PluginContext { ...@@ -73,6 +73,7 @@ export class PluginContext {
log: this.ev<LogEntry>(), log: this.ev<LogEntry>(),
task: this.tasks.events, task: this.tasks.events,
canvas3d: { canvas3d: {
initialized: this.ev(),
settingsUpdated: this.ev() settingsUpdated: this.ev()
}, },
interactivity: { interactivity: {
...@@ -127,7 +128,9 @@ export class PluginContext { ...@@ -127,7 +128,9 @@ export class PluginContext {
try { try {
this.layout.setRoot(container); this.layout.setRoot(container);
if (this.spec.layout && this.spec.layout.initial) this.layout.setProps(this.spec.layout.initial); if (this.spec.layout && this.spec.layout.initial) this.layout.setProps(this.spec.layout.initial);
(this.canvas3d as Canvas3D) = Canvas3D.fromCanvas(canvas); (this.canvas3d as Canvas3D) = Canvas3D.fromCanvas(canvas, {}, t => this.runTask(t));
this.events.canvas3d.initialized.next()
this.events.canvas3d.initialized.isStopped = true // TODO is this a good way?
const renderer = this.canvas3d.props.renderer; const renderer = this.canvas3d.props.renderer;
PluginCommands.Canvas3D.SetSettings.dispatch(this, { settings: { renderer: { ...renderer, backgroundColor: Color(0xFCFBF9) } } }); PluginCommands.Canvas3D.SetSettings.dispatch(this, { settings: { renderer: { ...renderer, backgroundColor: Color(0xFCFBF9) } } });
this.canvas3d.animate(); this.canvas3d.animate();
......
...@@ -11,6 +11,8 @@ export class AsyncQueue<T> { ...@@ -11,6 +11,8 @@ export class AsyncQueue<T> {
private queue: T[] = []; private queue: T[] = [];
private signal = new Subject<{ v: T, stillPresent: boolean }>(); private signal = new Subject<{ v: T, stillPresent: boolean }>();
get length() { return this.queue.length }
enqueue(v: T) { enqueue(v: T) {
this.queue.push(v); this.queue.push(v);
if (this.queue.length === 1) return true; if (this.queue.length === 1) return true;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment