Przeglądaj źródła

add Canvas3DContext

- can be used to create multiple Canvas3D objects
Alexander Rose 4 lat temu
rodzic
commit
e58da9b574

+ 102 - 68
src/mol-canvas3d/canvas3d.ts

@@ -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();
             }
         };
 

+ 6 - 3
src/mol-gl/webgl/context.ts

@@ -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();
         }
     };
 }

+ 6 - 3
src/mol-plugin/context.ts

@@ -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();

+ 2 - 2
src/tests/browser/marching-cubes.ts

@@ -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' }
 }));

+ 2 - 2
src/tests/browser/render-lines.ts

@@ -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() {

+ 2 - 2
src/tests/browser/render-mesh.ts

@@ -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() {

+ 2 - 2
src/tests/browser/render-shape.ts

@@ -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;

+ 2 - 2
src/tests/browser/render-spheres.ts

@@ -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() {

+ 2 - 2
src/tests/browser/render-structure.ts

@@ -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');

+ 2 - 2
src/tests/browser/render-text.ts

@@ -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() {