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';
import { readTexture } from '../mol-gl/compute/util';
import { DrawPass } from './passes/draw';
import { PickPass } from './passes/pick';
import { Task } from '../mol-task';
export const Canvas3DParams = {
// TODO: FPS cap?
......@@ -66,6 +67,7 @@ interface Canvas3D {
getLoci: (pickingId: PickingId) => Representation.Loci
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
handleResize: () => void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
......@@ -85,12 +87,13 @@ interface Canvas3D {
}
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
const DefaultRunTask = (task: Task<unknown>) => task.run()
namespace Canvas3D {
export interface HighlightEvent { current: Representation.Loci, 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, {
alpha: false,
antialias: true,
......@@ -99,10 +102,10 @@ namespace Canvas3D {
})
if (gl === null) throw new Error('Could not create a WebGL rendering context')
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 reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
......@@ -137,6 +140,7 @@ namespace Canvas3D {
let isUpdating = false
let drawPending = false
let cameraResetRequested = false
function getLoci(pickingId: PickingId) {
let loci: Loci = EmptyLoci
......@@ -201,7 +205,7 @@ namespace Canvas3D {
}
function render(variant: 'pick' | 'draw', force: boolean) {
if (isUpdating) return false
if (isUpdating || scene.isCommiting) return false
let didRender = false
controls.update(currentTime);
......@@ -279,8 +283,15 @@ namespace Canvas3D {
scene.update(repr.renderObjects, false)
if (debugHelper.isEnabled) debugHelper.update()
isUpdating = false
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()
......@@ -307,8 +318,15 @@ namespace Canvas3D {
scene.update(void 0, false)
if (debugHelper.isEnabled) debugHelper.update()
isUpdating = false
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) => {
......@@ -334,8 +352,12 @@ namespace Canvas3D {
handleResize,
resetCamera: () => {
if (scene.isCommiting) {
cameraResetRequested = true
} else {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
requestDraw(true);
}
},
camera,
downloadScreenshot: () => {
......@@ -351,6 +373,7 @@ namespace Canvas3D {
}
},
didDraw,
reprCount,
setProps: (props: Partial<Canvas3DProps>) => {
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
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>
*/
......@@ -12,6 +12,8 @@ import { Object3D } from './object3d';
import { Sphere3D } from '../mol-math/geometry';
import { Vec3 } from '../mol-math/linear-algebra';
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();
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
......@@ -54,10 +56,12 @@ interface Scene extends Object3D {
readonly count: number
readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
readonly boundingSphere: Sphere3D
readonly isCommiting: boolean
update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void
add: (o: GraphicsRenderObject) => Renderable<any>
add: (o: GraphicsRenderObject) => void // Renderable<any>
remove: (o: GraphicsRenderObject) => void
commit: () => Task<void>
has: (o: GraphicsRenderObject) => boolean
clear: () => void
forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void
......@@ -68,15 +72,63 @@ namespace Scene {
const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
const renderables: Renderable<RenderableValues & BaseValues>[] = []
const boundingSphere = Sphere3D.zero()
let boundingSphereDirty = true
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 {
get view () { return object3d.view },
get position () { return object3d.position },
get direction () { return object3d.direction },
get up () { return object3d.up },
get isCommiting () { return commitQueue.length > 0 },
update(objects, keepBoundingSphere) {
Object3D.update(object3d)
......@@ -94,27 +146,28 @@ namespace Scene {
if (!keepBoundingSphere) boundingSphereDirty = true
},
add: (o: GraphicsRenderObject) => {
if (!renderableMap.has(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)!
}
toAdd.push(o)
},
remove: (o: GraphicsRenderObject) => {
const renderable = renderableMap.get(o)
if (renderable) {
renderable.dispose()
renderables.splice(renderables.indexOf(renderable), 1)
renderables.sort(renderableSort)
renderableMap.delete(o)
boundingSphereDirty = true
}
toRemove.push(o)
},
commit: () => {
const params = { toAdd: [ ...toAdd ], toRemove: [ ...toRemove ] }
toAdd.length = 0
toRemove.length = 0
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) => {
return renderableMap.has(o)
......
......@@ -18,15 +18,19 @@ export function registerDefault(ctx: PluginContext) {
export function SyncRepresentationToCanvas(ctx: PluginContext) {
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;
events.object.created.subscribe(e => {
if (!SO.isRepresentation3D(e.obj)) return;
updateVisibility(e.state.cells.get(e.ref)!, e.obj.data.repr);
e.obj.data.repr.setState({ syncManually: true });
ctx.canvas3d.add(e.obj.data.repr);
if (reprCount === 0) ctx.canvas3d.resetCamera();
reprCount++;
});
events.object.updated.subscribe(e => {
if (e.oldObj && SO.isRepresentation3D(e.oldObj)) {
......@@ -50,7 +54,6 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
ctx.canvas3d.remove(e.obj.data.repr);
ctx.canvas3d.requestDraw(true);
e.obj.data.repr.destroy();
reprCount--;
});
}
......
......@@ -73,6 +73,7 @@ export class PluginContext {
log: this.ev<LogEntry>(),
task: this.tasks.events,
canvas3d: {
initialized: this.ev(),
settingsUpdated: this.ev()
},
interactivity: {
......@@ -127,7 +128,9 @@ export class PluginContext {
try {
this.layout.setRoot(container);
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;
PluginCommands.Canvas3D.SetSettings.dispatch(this, { settings: { renderer: { ...renderer, backgroundColor: Color(0xFCFBF9) } } });
this.canvas3d.animate();
......
......@@ -11,6 +11,8 @@ export class AsyncQueue<T> {
private queue: T[] = [];
private signal = new Subject<{ v: T, stillPresent: boolean }>();
get length() { return this.queue.length }
enqueue(v: T) {
this.queue.push(v);
if (this.queue.length === 1) return true;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment