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

add Canvas3DContext

- can be used to create multiple Canvas3D objects
parent 07f35188
No related branches found
No related tags found
No related merge requests found
......@@ -85,6 +85,105 @@ export type PartialCanvas3DProps = {
[K in keyof Canvas3DProps]?: Canvas3DProps[K] extends { name: string, params: any } ? Canvas3DProps[K] : Partial<Canvas3DProps[K]>
}
export { Canvas3DContext };
/** Can be used to create multiple Canvas3D objects */
interface Canvas3DContext {
readonly webgl: WebGLContext
readonly input: InputObserver
readonly passes: Passes
readonly attribs: Readonly<Canvas3DContext.Attribs>
readonly contextLost: BehaviorSubject<now.Timestamp>
readonly contextRestored: BehaviorSubject<now.Timestamp>
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
}
namespace Canvas3DContext {
const DefaultAttribs = {
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: true,
/** true to support multiple Canvas3D objects with a single context */
preserveDrawingBuffer: true,
pixelScale: 1,
pickScale: 0.25,
enableWboit: true
};
export type Attribs = typeof DefaultAttribs
export function fromCanvas(canvas: HTMLCanvasElement, attribs: Partial<Attribs> = {}): Canvas3DContext {
const a = { ...DefaultAttribs, ...attribs };
const { antialias, preserveDrawingBuffer, pixelScale } = a;
const gl = getGLContext(canvas, {
antialias,
preserveDrawingBuffer,
alpha: true, // the renderer requires an alpha channel
depth: true, // the renderer requires a depth buffer
premultipliedAlpha: true, // the renderer outputs PMA
});
if (gl === null) throw new Error('Could not create a WebGL rendering context');
const input = InputObserver.fromElement(canvas, { pixelScale });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
if (isDebugMode) {
const loseContextExt = gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
/** Hold down shift+ctrl+alt and press any mouse button to trigger lose context */
canvas.addEventListener('mousedown', e => {
if (webgl.isContextLost) return;
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
if (isDebugMode) console.log('lose context');
loseContextExt.loseContext();
setTimeout(() => {
if (!webgl.isContextLost) return;
if (isDebugMode) console.log('restore context');
loseContextExt.restoreContext();
}, 1000);
}, false);
}
}
// https://www.khronos.org/webgl/wiki/HandlingContextLost
const contextLost = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const handleWebglContextLost = (e: Event) => {
webgl.setContextLost();
e.preventDefault();
if (isDebugMode) console.log('context lost');
contextLost.next(now());
};
const handlewWebglContextRestored = () => {
if (!webgl.isContextLost) return;
webgl.handleContextRestored();
if (isDebugMode) console.log('context restored');
};
canvas.addEventListener('webglcontextlost', handleWebglContextLost, false);
canvas.addEventListener('webglcontextrestored', handlewWebglContextRestored, false);
return {
webgl,
input,
passes,
attribs: a,
contextLost,
contextRestored: webgl.contextRestored,
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => {
input.dispose();
canvas.removeEventListener('webglcontextlost', handleWebglContextLost, false);
canvas.removeEventListener('webglcontextrestored', handlewWebglContextRestored, false);
webgl.destroy(options);
}
};
}
}
export { Canvas3D };
interface Canvas3D {
......@@ -135,7 +234,7 @@ interface Canvas3D {
readonly stats: RendererStats
readonly interaction: Canvas3dInteractionHelper['events']
dispose(options?: { doNotForceWebGLContextLoss?: boolean }): void
dispose(): void
}
const requestAnimationFrame = typeof window !== 'undefined'
......@@ -150,69 +249,7 @@ namespace Canvas3D {
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
type Attribs = {
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: boolean,
/** true to support multiple viewports with a single context */
preserveDrawingBuffer: boolean,
pixelScale: number,
pickScale: number,
enableWboit: boolean
}
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, attribs: Partial<Attribs> = {}) {
const gl = getGLContext(canvas, {
antialias: attribs.antialias ?? true,
preserveDrawingBuffer: attribs.preserveDrawingBuffer ?? true,
alpha: true, // the renderer requires an alpha channel
depth: true, // the renderer requires a depth buffer
premultipliedAlpha: true, // the renderer outputs PMA
});
if (gl === null) throw new Error('Could not create a WebGL rendering context');
const { pixelScale } = attribs;
const input = InputObserver.fromElement(canvas, { pixelScale });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
if (isDebugMode) {
const loseContextExt = gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
canvas.addEventListener('mousedown', e => {
if (webgl.isContextLost) return;
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
if (isDebugMode) console.log('lose context');
loseContextExt.loseContext();
setTimeout(() => {
if (!webgl.isContextLost) return;
if (isDebugMode) console.log('restore context');
loseContextExt.restoreContext();
}, 1000);
}, false);
}
}
// https://www.khronos.org/webgl/wiki/HandlingContextLost
canvas.addEventListener('webglcontextlost', e => {
webgl.setContextLost();
e.preventDefault();
if (isDebugMode) console.log('context lost');
}, false);
canvas.addEventListener('webglcontextrestored', () => {
if (!webgl.isContextLost) return;
webgl.handleContextRestored();
if (isDebugMode) console.log('context restored');
}, false);
return create(webgl, input, passes, props, { pixelScale });
}
export function create(webgl: WebGLContext, input: InputObserver, passes: Passes, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ pixelScale: number }>): Canvas3D {
export function create({ webgl, input, passes, attribs }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
......@@ -705,17 +742,14 @@ namespace Canvas3D {
get interaction() {
return interactionHelper.events;
},
dispose: (options?: { doNotForceWebGLContextLoss?: boolean }) => {
dispose: () => {
contextRestoredSub.unsubscribe();
scene.clear();
helper.debug.clear();
input.dispose();
controls.dispose();
renderer.dispose();
interactionHelper.dispose();
if (!options?.doNotForceWebGLContextLoss) gl.getExtension('WEBGL_lose_context')?.loseContext();
}
};
......
......@@ -210,7 +210,7 @@ export interface WebGLContext {
waitForGpuCommandsCompleteSync: () => void
getDrawingBufferPixelData: () => PixelData
clear: (red: number, green: number, blue: number, alpha: number) => void
destroy: () => void
destroy: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
}
export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScale: number }> = {}): WebGLContext {
......@@ -232,7 +232,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
}
let isContextLost = false;
let contextRestored = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const contextRestored = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
let readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>;
if (isWebGL2(gl)) {
......@@ -347,9 +347,12 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
},
destroy: () => {
destroy: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => {
resources.destroy();
unbindResources(gl);
// to aid GC
if (!options?.doNotForceWebGLContextLoss) gl.getExtension('WEBGL_lose_context')?.loseContext();
}
};
}
\ No newline at end of file
......@@ -8,7 +8,7 @@
import produce, { setAutoFreeze } from 'immer';
import { List } from 'immutable';
import { merge } from 'rxjs';
import { Canvas3D, DefaultCanvas3DParams } from '../mol-canvas3d/canvas3d';
import { Canvas3D, Canvas3DContext, DefaultCanvas3DParams } from '../mol-canvas3d/canvas3d';
import { CustomProperty } from '../mol-model-props/common/custom-property';
import { Model, Structure } from '../mol-model/structure';
import { DataBuilder } from '../mol-plugin-state/builder/data';
......@@ -104,6 +104,7 @@ export class PluginContext {
}
} as const;
readonly canvas3dContext: Canvas3DContext | undefined;
readonly canvas3d: Canvas3D | undefined;
readonly animationLoop = new PluginAnimationLoop(this);
readonly layout = new PluginLayout(this);
......@@ -193,7 +194,8 @@ export class PluginContext {
const pixelScale = this.config.get(PluginConfig.General.PixelScale) || 1;
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
(this.canvas3d as Canvas3D) = Canvas3D.fromCanvas(canvas, {}, { antialias, preserveDrawingBuffer, pixelScale, enableWboit, pickScale });
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit });
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
this.canvas3dInit.next(true);
let props = this.spec.components?.viewport?.canvas3d;
......@@ -259,7 +261,8 @@ export class PluginContext {
dispose(options?: { doNotForceWebGLContextLoss?: boolean }) {
if (this.disposed) return;
this.commands.dispose();
this.canvas3d?.dispose(options);
this.canvas3d?.dispose();
this.canvas3dContext?.dispose(options);
this.ev.dispose();
this.state.dispose();
this.managers.task.dispose();
......
......@@ -6,7 +6,7 @@
import './index.html';
import { resizeCanvas } from '../../mol-canvas3d/util';
import { Canvas3DParams, Canvas3D } from '../../mol-canvas3d/canvas3d';
import { Canvas3DParams, Canvas3D, Canvas3DContext } from '../../mol-canvas3d/canvas3d';
import { ColorNames } from '../../mol-util/color/names';
import { PositionData, Box3D, Sphere3D } from '../../mol-math/geometry';
import { OrderedSet } from '../../mol-data/int';
......@@ -31,7 +31,7 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.fromCanvas(canvas, PD.merge(Canvas3DParams, PD.getDefaultValues(Canvas3DParams), {
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas), PD.merge(Canvas3DParams, PD.getDefaultValues(Canvas3DParams), {
renderer: { backgroundColor: ColorNames.white },
camera: { mode: 'orthographic' }
}));
......
......@@ -6,7 +6,7 @@
import './index.html';
import { resizeCanvas } from '../../mol-canvas3d/util';
import { Canvas3D } from '../../mol-canvas3d/canvas3d';
import { Canvas3D, Canvas3DContext } from '../../mol-canvas3d/canvas3d';
import { LinesBuilder } from '../../mol-geo/geometry/lines/lines-builder';
import { Mat4 } from '../../mol-math/linear-algebra';
import { DodecahedronCage } from '../../mol-geo/primitive/dodecahedron';
......@@ -23,7 +23,7 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.fromCanvas(canvas);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
canvas3d.animate();
function linesRepr() {
......
......@@ -6,7 +6,7 @@
import './index.html';
import { resizeCanvas } from '../../mol-canvas3d/util';
import { Canvas3D } from '../../mol-canvas3d/canvas3d';
import { Canvas3D, Canvas3DContext } from '../../mol-canvas3d/canvas3d';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { Mat4 } from '../../mol-math/linear-algebra';
import { HexagonalPrismCage } from '../../mol-geo/primitive/prism';
......@@ -24,7 +24,7 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.fromCanvas(canvas);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
canvas3d.animate();
function meshRepr() {
......
......@@ -7,7 +7,7 @@
import './index.html';
import { resizeCanvas } from '../../mol-canvas3d/util';
import { Representation } from '../../mol-repr/representation';
import { Canvas3D } from '../../mol-canvas3d/canvas3d';
import { Canvas3D, Canvas3DContext } from '../../mol-canvas3d/canvas3d';
import { lociLabel } from '../../mol-theme/label';
import { MarkerAction } from '../../mol-util/marker-action';
import { EveryLoci } from '../../mol-model/loci';
......@@ -38,7 +38,7 @@ info.style.color = 'white';
parent.appendChild(info);
let prevReprLoci = Representation.Loci.Empty;
const canvas3d = Canvas3D.fromCanvas(canvas);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
canvas3d.animate();
canvas3d.input.move.subscribe(({x, y}) => {
const pickingId = canvas3d.identify(x, y)?.id;
......
......@@ -6,7 +6,7 @@
import './index.html';
import { resizeCanvas } from '../../mol-canvas3d/util';
import { Canvas3D } from '../../mol-canvas3d/canvas3d';
import { Canvas3D, Canvas3DContext } from '../../mol-canvas3d/canvas3d';
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
import { Color } from '../../mol-util/color';
......@@ -21,7 +21,7 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.fromCanvas(canvas);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
canvas3d.animate();
function spheresRepr() {
......
......@@ -5,7 +5,7 @@
*/
import './index.html';
import { Canvas3D } from '../../mol-canvas3d/canvas3d';
import { Canvas3D, Canvas3DContext } from '../../mol-canvas3d/canvas3d';
import { CIF, CifFrame } from '../../mol-io/reader/cif';
import { Model, Structure } from '../../mol-model/structure';
import { ColorTheme } from '../../mol-theme/color';
......@@ -37,7 +37,7 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.fromCanvas(canvas);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
canvas3d.animate();
const info = document.createElement('div');
......
......@@ -5,7 +5,7 @@
*/
import './index.html';
import { Canvas3D } from '../../mol-canvas3d/canvas3d';
import { Canvas3D, Canvas3DContext } from '../../mol-canvas3d/canvas3d';
import { TextBuilder } from '../../mol-geo/geometry/text/text-builder';
import { Text } from '../../mol-geo/geometry/text/text';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
......@@ -24,7 +24,7 @@ const canvas = document.createElement('canvas');
parent.appendChild(canvas);
resizeCanvas(canvas, parent);
const canvas3d = Canvas3D.fromCanvas(canvas);
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
canvas3d.animate();
function textRepr() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment