Przeglądaj źródła

handle context loss, add webgl resources

Alexander Rose 5 lat temu
rodzic
commit
d5c163ac48

+ 62 - 25
src/mol-canvas3d/canvas3d.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
@@ -26,7 +26,6 @@ import { SetUtils } from '../mol-util/set';
 import { Canvas3dInteractionHelper } from './helper/interaction-events';
 import { Canvas3dInteractionHelper } from './helper/interaction-events';
 import { PostprocessingParams, PostprocessingPass } from './passes/postprocessing';
 import { PostprocessingParams, PostprocessingPass } from './passes/postprocessing';
 import { MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
 import { MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
-import { GLRenderingContext } from '../mol-gl/webgl/compat';
 import { PixelData } from '../mol-util/image';
 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';
@@ -34,6 +33,7 @@ import { PickPass } from './passes/pick';
 import { Task } from '../mol-task';
 import { Task } from '../mol-task';
 import { ImagePass, ImageProps } from './passes/image';
 import { ImagePass, ImageProps } from './passes/image';
 import { Sphere3D } from '../mol-math/geometry';
 import { Sphere3D } from '../mol-math/geometry';
+import { isDebugMode } from '../mol-util/debug';
 
 
 export const Canvas3DParams = {
 export const Canvas3DParams = {
     cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
     cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
@@ -107,10 +107,45 @@ 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, runTask)
+        const webgl = createContext(gl)
+
+        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
+
+                    console.log('lose context')
+                    loseContextExt.loseContext()
+
+                    setTimeout(() => {
+                        if (!webgl.isContextLost) return
+                        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 Canvas3D.create(webgl, input, props, runTask)
     }
     }
 
 
-    export function create(gl: GLRenderingContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
+    export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
         const p = { ...DefaultCanvas3DParams, ...props }
         const p = { ...DefaultCanvas3DParams, ...props }
 
 
         const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
         const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
@@ -120,7 +155,7 @@ namespace Canvas3D {
         const startTime = now()
         const startTime = now()
         const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
         const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
 
 
-        const webgl = createContext(gl)
+        const { gl, contextRestored } = webgl
 
 
         let width = gl.drawingBufferWidth
         let width = gl.drawingBufferWidth
         let height = gl.drawingBufferHeight
         let height = gl.drawingBufferHeight
@@ -144,6 +179,11 @@ namespace Canvas3D {
         const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
         const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
         const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
         const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
 
 
+        const contextRestoredSub = contextRestored.subscribe(() => {
+            pickPass.pickDirty = true
+            draw(true)
+        })
+
         let drawPending = false
         let drawPending = false
         let cameraResetRequested = false
         let cameraResetRequested = false
 
 
@@ -179,8 +219,8 @@ namespace Canvas3D {
             }
             }
         }
         }
 
 
-        function render(variant: 'pick' | 'draw', force: boolean) {
-            if (scene.isCommiting) return false
+        function render(force: boolean) {
+            if (scene.isCommiting || webgl.isContextLost) return false
 
 
             let didRender = false
             let didRender = false
             controls.update(currentTime)
             controls.update(currentTime)
@@ -189,21 +229,14 @@ namespace Canvas3D {
             multiSample.update(force || cameraChanged, currentTime)
             multiSample.update(force || cameraChanged, currentTime)
 
 
             if (force || cameraChanged || multiSample.enabled) {
             if (force || cameraChanged || multiSample.enabled) {
-                switch (variant) {
-                    case 'pick':
-                        pickPass.render()
-                        break;
-                    case 'draw':
-                        renderer.setViewport(0, 0, width, height)
-                        if (multiSample.enabled) {
-                            multiSample.render(true, p.transparentBackground)
-                        } else {
-                            drawPass.render(!postprocessing.enabled, p.transparentBackground)
-                            if (postprocessing.enabled) postprocessing.render(true)
-                        }
-                        pickPass.pickDirty = true
-                        break;
+                renderer.setViewport(0, 0, width, height)
+                if (multiSample.enabled) {
+                    multiSample.render(true, p.transparentBackground)
+                } else {
+                    drawPass.render(!postprocessing.enabled, p.transparentBackground)
+                    if (postprocessing.enabled) postprocessing.render(true)
                 }
                 }
+                pickPass.pickDirty = true
                 didRender = true
                 didRender = true
             }
             }
 
 
@@ -214,7 +247,7 @@ namespace Canvas3D {
         let currentTime = 0;
         let currentTime = 0;
 
 
         function draw(force?: boolean) {
         function draw(force?: boolean) {
-            if (render('draw', !!force || forceNextDraw)) {
+            if (render(!!force || forceNextDraw)) {
                 didDraw.next(now() - startTime as now.Timestamp)
                 didDraw.next(now() - startTime as now.Timestamp)
             }
             }
             forceNextDraw = false;
             forceNextDraw = false;
@@ -231,15 +264,17 @@ namespace Canvas3D {
             currentTime = now();
             currentTime = now();
             camera.transition.tick(currentTime);
             camera.transition.tick(currentTime);
             draw(false);
             draw(false);
-            if (!camera.transition.inTransition) interactionHelper.tick(currentTime);
+            if (!camera.transition.inTransition && !webgl.isContextLost) {
+                interactionHelper.tick(currentTime);
+            }
             requestAnimationFrame(animate)
             requestAnimationFrame(animate)
         }
         }
 
 
         function identify(x: number, y: number): PickingId | undefined {
         function identify(x: number, y: number): PickingId | undefined {
-            return pickPass.identify(x, y)
+            return webgl.isContextLost ? undefined : pickPass.identify(x, y)
         }
         }
 
 
-        function commit(renderObjects?: readonly GraphicsRenderObject[]) {
+        async function commit(renderObjects?: readonly GraphicsRenderObject[]) {
             scene.update(renderObjects, false)
             scene.update(renderObjects, false)
 
 
             return runTask(scene.commit()).then(() => {
             return runTask(scene.commit()).then(() => {
@@ -395,6 +430,8 @@ namespace Canvas3D {
                 return interactionHelper.events
                 return interactionHelper.events
             },
             },
             dispose: () => {
             dispose: () => {
+                contextRestoredSub.unsubscribe()
+
                 scene.clear()
                 scene.clear()
                 debugHelper.clear()
                 debugHelper.clear()
                 input.dispose()
                 input.dispose()

+ 11 - 7
src/mol-canvas3d/passes/draw.ts

@@ -5,11 +5,11 @@
  */
  */
 
 
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { WebGLContext } from '../../mol-gl/webgl/context';
-import { createRenderTarget, RenderTarget } from '../../mol-gl/webgl/render-target';
+import { RenderTarget } from '../../mol-gl/webgl/render-target';
 import Renderer from '../../mol-gl/renderer';
 import Renderer from '../../mol-gl/renderer';
 import Scene from '../../mol-gl/scene';
 import Scene from '../../mol-gl/scene';
 import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
 import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
-import { createTexture, Texture } from '../../mol-gl/webgl/texture';
+import { Texture } from '../../mol-gl/webgl/texture';
 import { Camera } from '../camera';
 import { Camera } from '../camera';
 
 
 export class DrawPass {
 export class DrawPass {
@@ -20,13 +20,13 @@ export class DrawPass {
     private depthTarget: RenderTarget | null
     private depthTarget: RenderTarget | null
 
 
     constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) {
     constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) {
-        const { gl, extensions } = webgl
+        const { gl, extensions, resources } = webgl
         const width = gl.drawingBufferWidth
         const width = gl.drawingBufferWidth
         const height = gl.drawingBufferHeight
         const height = gl.drawingBufferHeight
-        this.colorTarget = createRenderTarget(webgl, width, height)
+        this.colorTarget = webgl.createRenderTarget(width, height)
         this.packedDepth = !extensions.depthTexture
         this.packedDepth = !extensions.depthTexture
-        this.depthTarget = this.packedDepth ? createRenderTarget(webgl, width, height) : null
-        this.depthTexture = this.depthTarget ? this.depthTarget.texture : createTexture(webgl, 'image-depth', 'depth', 'ushort', 'nearest')
+        this.depthTarget = this.packedDepth ? webgl.createRenderTarget(width, height) : null
+        this.depthTexture = this.depthTarget ? this.depthTarget.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest')
         if (!this.packedDepth) {
         if (!this.packedDepth) {
             this.depthTexture.define(width, height)
             this.depthTexture.define(width, height)
             this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth')
             this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth')
@@ -48,9 +48,13 @@ export class DrawPass {
             webgl.unbindFramebuffer()
             webgl.unbindFramebuffer()
         } else {
         } else {
             colorTarget.bind()
             colorTarget.bind()
+            if (!this.packedDepth) {
+                // TODO unlcear why it is not enough to call `attachFramebuffer` in `Texture.reset`
+                this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth')
+            }
         }
         }
 
 
-        renderer.setViewport(0, 0, colorTarget.width, colorTarget.height)
+        renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight())
         renderer.render(scene, camera, 'color', true, transparentBackground)
         renderer.render(scene, camera, 'color', true, transparentBackground)
         if (debugHelper.isEnabled) {
         if (debugHelper.isEnabled) {
             debugHelper.syncVisibility()
             debugHelper.syncVisibility()

+ 9 - 7
src/mol-canvas3d/passes/multi-sample.ts

@@ -14,7 +14,7 @@ import { ShaderCode } from '../../mol-gl/shader-code';
 import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
 import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
 import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
 import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { RenderTarget, createRenderTarget } from '../../mol-gl/webgl/render-target';
+import { RenderTarget } from '../../mol-gl/webgl/render-target';
 import { Camera } from '../../mol-canvas3d/camera';
 import { Camera } from '../../mol-canvas3d/camera';
 import { PostprocessingPass } from './postprocessing';
 import { PostprocessingPass } from './postprocessing';
 import { DrawPass } from './draw';
 import { DrawPass } from './draw';
@@ -35,7 +35,7 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
     const values: Values<typeof ComposeSchema> = {
     const values: Values<typeof ComposeSchema> = {
         ...QuadValues,
         ...QuadValues,
         tColor: ValueCell.create(colorTexture),
         tColor: ValueCell.create(colorTexture),
-        uTexSize: ValueCell.create(Vec2.create(colorTexture.width, colorTexture.height)),
+        uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
         uWeight: ValueCell.create(1.0),
         uWeight: ValueCell.create(1.0),
     }
     }
 
 
@@ -66,9 +66,9 @@ export class MultiSamplePass {
 
 
     constructor(private webgl: WebGLContext, private camera: Camera, private drawPass: DrawPass, private postprocessing: PostprocessingPass, props: Partial<MultiSampleProps>) {
     constructor(private webgl: WebGLContext, private camera: Camera, private drawPass: DrawPass, private postprocessing: PostprocessingPass, props: Partial<MultiSampleProps>) {
         const { gl } = webgl
         const { gl } = webgl
-        this.colorTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
-        this.composeTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
-        this.holdTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
+        this.colorTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
+        this.composeTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
+        this.holdTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
         this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture)
         this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture)
         this.props = { ...PD.getDefaultValues(MultiSampleParams), ...props }
         this.props = { ...PD.getDefaultValues(MultiSampleParams), ...props }
     }
     }
@@ -131,7 +131,8 @@ export class MultiSamplePass {
         ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture)
         ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture)
         compose.update()
         compose.update()
 
 
-        const { width, height } = drawPass.colorTarget
+        const width = drawPass.colorTarget.getWidth()
+        const height = drawPass.colorTarget.getHeight()
 
 
         // render the scene multiple times, each slightly jitter offset
         // render the scene multiple times, each slightly jitter offset
         // from the last and accumulate the results.
         // from the last and accumulate the results.
@@ -222,7 +223,8 @@ export class MultiSamplePass {
         ValueCell.update(compose.values.uWeight, sampleWeight)
         ValueCell.update(compose.values.uWeight, sampleWeight)
         compose.update()
         compose.update()
 
 
-        const { width, height } = drawPass.colorTarget
+        const width = drawPass.colorTarget.getWidth()
+        const height = drawPass.colorTarget.getHeight()
 
 
         // render the scene multiple times, each slightly jitter offset
         // render the scene multiple times, each slightly jitter offset
         // from the last and accumulate the results.
         // from the last and accumulate the results.

+ 6 - 4
src/mol-canvas3d/passes/pick.ts

@@ -5,7 +5,7 @@
  */
  */
 
 
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { WebGLContext } from '../../mol-gl/webgl/context';
-import { createRenderTarget, RenderTarget } from '../../mol-gl/webgl/render-target';
+import { RenderTarget } from '../../mol-gl/webgl/render-target';
 import Renderer from '../../mol-gl/renderer';
 import Renderer from '../../mol-gl/renderer';
 import Scene from '../../mol-gl/scene';
 import Scene from '../../mol-gl/scene';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { PickingId } from '../../mol-geo/geometry/picking';
@@ -36,9 +36,9 @@ export class PickPass {
         this.pickWidth = Math.round(width * this.pickScale)
         this.pickWidth = Math.round(width * this.pickScale)
         this.pickHeight = Math.round(height * this.pickScale)
         this.pickHeight = Math.round(height * this.pickScale)
 
 
-        this.objectPickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
-        this.instancePickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
-        this.groupPickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
+        this.objectPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight)
+        this.instancePickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight)
+        this.groupPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight)
 
 
         this.setupBuffers()
         this.setupBuffers()
     }
     }
@@ -97,6 +97,8 @@ export class PickPass {
 
 
     identify(x: number, y: number): PickingId | undefined {
     identify(x: number, y: number): PickingId | undefined {
         const { webgl, pickScale } = this
         const { webgl, pickScale } = this
+        if (webgl.isContextLost) return
+
         const { gl } = webgl
         const { gl } = webgl
         if (this.pickDirty) {
         if (this.pickDirty) {
             this.render()
             this.render()

+ 3 - 3
src/mol-canvas3d/passes/postprocessing.ts

@@ -14,7 +14,7 @@ import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
 import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
 import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
 import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
 import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { createRenderTarget, RenderTarget } from '../../mol-gl/webgl/render-target';
+import { RenderTarget } from '../../mol-gl/webgl/render-target';
 import { DrawPass } from './draw';
 import { DrawPass } from './draw';
 import { Camera } from '../../mol-canvas3d/camera';
 import { Camera } from '../../mol-canvas3d/camera';
 
 
@@ -66,7 +66,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
         ...QuadValues,
         ...QuadValues,
         tColor: ValueCell.create(colorTexture),
         tColor: ValueCell.create(colorTexture),
         tDepth: ValueCell.create(depthTexture),
         tDepth: ValueCell.create(depthTexture),
-        uTexSize: ValueCell.create(Vec2.create(colorTexture.width, colorTexture.height)),
+        uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
 
 
         dOrthographic: ValueCell.create(0),
         dOrthographic: ValueCell.create(0),
         uNear: ValueCell.create(1),
         uNear: ValueCell.create(1),
@@ -101,7 +101,7 @@ export class PostprocessingPass {
 
 
     constructor(private webgl: WebGLContext, private camera: Camera, drawPass: DrawPass, props: Partial<PostprocessingProps>) {
     constructor(private webgl: WebGLContext, private camera: Camera, drawPass: DrawPass, props: Partial<PostprocessingProps>) {
         const { gl } = webgl
         const { gl } = webgl
-        this.target = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
+        this.target = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
         this.props = { ...PD.getDefaultValues(PostprocessingParams), ...props }
         this.props = { ...PD.getDefaultValues(PostprocessingParams), ...props }
         const { colorTarget, depthTexture, packedDepth } = drawPass
         const { colorTarget, depthTexture, packedDepth } = drawPass
         this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, packedDepth, this.props)
         this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, packedDepth, this.props)

+ 4 - 2
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -46,7 +46,9 @@ export interface DirectVolume {
 
 
 export namespace DirectVolume {
 export namespace DirectVolume {
     export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume): DirectVolume {
     export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume): DirectVolume {
-        const { width, height, depth } = texture
+        const width = texture.getWidth()
+        const height = texture.getHeight()
+        const depth = texture.getDepth()
         if (directVolume) {
         if (directVolume) {
             ValueCell.update(directVolume.gridDimension, gridDimension)
             ValueCell.update(directVolume.gridDimension, gridDimension)
             ValueCell.update(directVolume.gridTextureDim, Vec3.set(directVolume.gridTextureDim.ref.value, width, height, depth))
             ValueCell.update(directVolume.gridTextureDim, Vec3.set(directVolume.gridTextureDim.ref.value, width, height, depth))
@@ -138,7 +140,7 @@ export namespace DirectVolume {
             dRenderMode: ValueCell.create(props.renderMode),
             dRenderMode: ValueCell.create(props.renderMode),
             tTransferTex: transferTex,
             tTransferTex: transferTex,
 
 
-            dGridTexType: ValueCell.create(gridTexture.ref.value.depth > 0 ? '3d' : '2d'),
+            dGridTexType: ValueCell.create(gridTexture.ref.value.getDepth() > 0 ? '3d' : '2d'),
             uGridTexDim: gridTextureDim,
             uGridTexDim: gridTextureDim,
             tGridTex: gridTexture,
             tGridTex: gridTexture,
         }
         }

+ 2 - 1
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -41,7 +41,8 @@ export interface TextureMesh {
 
 
 export namespace TextureMesh {
 export namespace TextureMesh {
     export function create(vertexCount: number, groupCount: number, vertexGroupTexture: Texture, normalTexture: Texture, boundingSphere: Sphere3D, textureMesh?: TextureMesh): TextureMesh {
     export function create(vertexCount: number, groupCount: number, vertexGroupTexture: Texture, normalTexture: Texture, boundingSphere: Sphere3D, textureMesh?: TextureMesh): TextureMesh {
-        const { width, height } = vertexGroupTexture
+        const width = vertexGroupTexture.getWidth()
+        const height = vertexGroupTexture.getHeight()
         if (textureMesh) {
         if (textureMesh) {
             ValueCell.update(textureMesh.vertexCount, vertexCount)
             ValueCell.update(textureMesh.vertexCount, vertexCount)
             ValueCell.update(textureMesh.groupCount, groupCount)
             ValueCell.update(textureMesh.groupCount, groupCount)

+ 20 - 22
src/mol-gl/_spec/renderer.spec.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
@@ -101,11 +101,11 @@ describe('renderer', () => {
         expect(ctx.gl.canvas.width).toBe(32)
         expect(ctx.gl.canvas.width).toBe(32)
         expect(ctx.gl.canvas.height).toBe(32)
         expect(ctx.gl.canvas.height).toBe(32)
 
 
-        expect(ctx.stats.bufferCount).toBe(0);
-        expect(ctx.stats.textureCount).toBe(0);
-        expect(ctx.stats.vaoCount).toBe(0);
-        expect(ctx.programCache.count).toBe(0);
-        expect(ctx.shaderCache.count).toBe(0);
+        expect(ctx.stats.resourceCounts.attribute).toBe(0);
+        expect(ctx.stats.resourceCounts.texture).toBe(0);
+        expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
+        expect(ctx.stats.resourceCounts.program).toBe(0);
+        expect(ctx.stats.resourceCounts.shader).toBe(0);
 
 
         renderer.setViewport(0, 0, 64, 48)
         renderer.setViewport(0, 0, 64, 48)
         expect(ctx.gl.getParameter(ctx.gl.VIEWPORT)[2]).toBe(64)
         expect(ctx.gl.getParameter(ctx.gl.VIEWPORT)[2]).toBe(64)
@@ -122,24 +122,22 @@ describe('renderer', () => {
 
 
         scene.add(points)
         scene.add(points)
         await scene.commit().run()
         await scene.commit().run()
-        expect(ctx.stats.bufferCount).toBe(4);
-        expect(ctx.stats.textureCount).toBe(5);
-        expect(ctx.stats.vaoCount).toBe(5);
-        expect(ctx.programCache.count).toBe(5);
-        expect(ctx.shaderCache.count).toBe(10);
+        expect(ctx.stats.resourceCounts.attribute).toBe(4);
+        expect(ctx.stats.resourceCounts.texture).toBe(5);
+        expect(ctx.stats.resourceCounts.vertexArray).toBe(5);
+        expect(ctx.stats.resourceCounts.program).toBe(5);
+        expect(ctx.stats.resourceCounts.shader).toBe(10);
 
 
         scene.remove(points)
         scene.remove(points)
         await scene.commit().run()
         await scene.commit().run()
-        expect(ctx.stats.bufferCount).toBe(0);
-        expect(ctx.stats.textureCount).toBe(0);
-        expect(ctx.stats.vaoCount).toBe(0);
-        expect(ctx.programCache.count).toBe(5);
-        expect(ctx.shaderCache.count).toBe(10);
-
-        ctx.programCache.dispose()
-        expect(ctx.programCache.count).toBe(0);
-
-        ctx.shaderCache.clear()
-        expect(ctx.shaderCache.count).toBe(0);
+        expect(ctx.stats.resourceCounts.attribute).toBe(0);
+        expect(ctx.stats.resourceCounts.texture).toBe(0);
+        expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
+        expect(ctx.stats.resourceCounts.program).toBe(5);
+        expect(ctx.stats.resourceCounts.shader).toBe(10);
+
+        ctx.resources.destroy()
+        expect(ctx.stats.resourceCounts.program).toBe(0);
+        expect(ctx.stats.resourceCounts.shader).toBe(0);
     })
     })
 })
 })

+ 9 - 9
src/mol-gl/compute/histogram-pyramid/reduction.ts

@@ -8,13 +8,13 @@ import { createComputeRenderable, ComputeRenderable } from '../../renderable'
 import { WebGLContext } from '../../webgl/context';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
 import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
-import { Texture, createTexture } from '../../../mol-gl/webgl/texture';
+import { Texture } from '../../../mol-gl/webgl/texture';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ValueCell } from '../../../mol-util';
 import { ValueCell } from '../../../mol-util';
 import { QuadSchema, QuadValues } from '../util';
 import { QuadSchema, QuadValues } from '../util';
 import { Vec2 } from '../../../mol-math/linear-algebra';
 import { Vec2 } from '../../../mol-math/linear-algebra';
 import { getHistopyramidSum } from './sum';
 import { getHistopyramidSum } from './sum';
-import { Framebuffer, createFramebuffer } from '../../../mol-gl/webgl/framebuffer';
+import { Framebuffer } from '../../../mol-gl/webgl/framebuffer';
 import { isPowerOfTwo } from '../../../mol-math/misc';
 import { isPowerOfTwo } from '../../../mol-math/misc';
 import quad_vert from '../../../mol-gl/shader/quad.vert'
 import quad_vert from '../../../mol-gl/shader/quad.vert'
 import reduction_frag from '../../../mol-gl/shader/histogram-pyramid/reduction.frag'
 import reduction_frag from '../../../mol-gl/shader/histogram-pyramid/reduction.frag'
@@ -55,8 +55,8 @@ function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
     let textureFramebuffer  = LevelTexturesFramebuffers[level]
     let textureFramebuffer  = LevelTexturesFramebuffers[level]
     const size = Math.pow(2, level)
     const size = Math.pow(2, level)
     if (textureFramebuffer === undefined) {
     if (textureFramebuffer === undefined) {
-        const texture = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
-        const framebuffer = createFramebuffer(ctx.gl, ctx.stats)
+        const texture = ctx.resources.texture('image-float32', 'rgba', 'float', 'nearest')
+        const framebuffer = ctx.resources.framebuffer()
         texture.attachFramebuffer(framebuffer, 0)
         texture.attachFramebuffer(framebuffer, 0)
         textureFramebuffer = { texture, framebuffer }
         textureFramebuffer = { texture, framebuffer }
         textureFramebuffer.texture.define(size, size)
         textureFramebuffer.texture.define(size, size)
@@ -85,22 +85,22 @@ export interface HistogramPyramid {
 }
 }
 
 
 export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2): HistogramPyramid {
 export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2): HistogramPyramid {
-    const { gl, framebufferCache } = ctx
+    const { gl, resources } = ctx
 
 
     // printTexture(ctx, inputTexture, 2)
     // printTexture(ctx, inputTexture, 2)
-    if (inputTexture.width !== inputTexture.height || !isPowerOfTwo(inputTexture.width)) {
+    if (inputTexture.getWidth() !== inputTexture.getHeight() || !isPowerOfTwo(inputTexture.getWidth())) {
         throw new Error('inputTexture must be of square power-of-two size')
         throw new Error('inputTexture must be of square power-of-two size')
     }
     }
 
 
     // This part set the levels
     // This part set the levels
-    const levels = Math.ceil(Math.log(inputTexture.width) / Math.log(2))
+    const levels = Math.ceil(Math.log(inputTexture.getWidth()) / Math.log(2))
     const maxSize = Math.pow(2, levels)
     const maxSize = Math.pow(2, levels)
     // console.log('levels', levels, 'maxSize', maxSize)
     // console.log('levels', levels, 'maxSize', maxSize)
 
 
-    const pyramidTexture = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
+    const pyramidTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest')
     pyramidTexture.define(maxSize, maxSize)
     pyramidTexture.define(maxSize, maxSize)
 
 
-    const framebuffer = framebufferCache.get('reduction').value
+    const framebuffer = resources.framebuffer()
     pyramidTexture.attachFramebuffer(framebuffer, 0)
     pyramidTexture.attachFramebuffer(framebuffer, 0)
     gl.clear(gl.COLOR_BUFFER_BIT)
     gl.clear(gl.COLOR_BUFFER_BIT)
 
 

+ 4 - 7
src/mol-gl/compute/histogram-pyramid/sum.ts

@@ -8,7 +8,7 @@ import { createComputeRenderable, ComputeRenderable } from '../../renderable'
 import { WebGLContext } from '../../webgl/context';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { Values, TextureSpec } from '../../renderable/schema';
 import { Values, TextureSpec } from '../../renderable/schema';
-import { Texture, createTexture } from '../../../mol-gl/webgl/texture';
+import { Texture } from '../../../mol-gl/webgl/texture';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ValueCell } from '../../../mol-util';
 import { ValueCell } from '../../../mol-util';
 import { decodeFloatRGB } from '../../../mol-util/float-packing';
 import { decodeFloatRGB } from '../../../mol-util/float-packing';
@@ -45,14 +45,11 @@ function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
 let SumTexture: Texture
 let SumTexture: Texture
 function getSumTexture(ctx: WebGLContext) {
 function getSumTexture(ctx: WebGLContext) {
     if (SumTexture) return SumTexture
     if (SumTexture) return SumTexture
-    SumTexture = createTexture(ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest')
+    SumTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest')
     SumTexture.define(1, 1)
     SumTexture.define(1, 1)
     return SumTexture
     return SumTexture
 }
 }
 
 
-/** name for shared framebuffer used for histogram-pyramid operations */
-const FramebufferName = 'histogram-pyramid-sum'
-
 function setRenderingDefaults(ctx: WebGLContext) {
 function setRenderingDefaults(ctx: WebGLContext) {
     const { gl, state } = ctx
     const { gl, state } = ctx
     state.disable(gl.CULL_FACE)
     state.disable(gl.CULL_FACE)
@@ -66,12 +63,12 @@ function setRenderingDefaults(ctx: WebGLContext) {
 
 
 const sumArray = new Uint8Array(4)
 const sumArray = new Uint8Array(4)
 export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture) {
 export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture) {
-    const { gl, framebufferCache } = ctx
+    const { gl, resources } = ctx
 
 
     const renderable = getHistopyramidSumRenderable(ctx, pyramidTopTexture)
     const renderable = getHistopyramidSumRenderable(ctx, pyramidTopTexture)
     ctx.state.currentRenderItemId = -1
     ctx.state.currentRenderItemId = -1
 
 
-    const framebuffer = framebufferCache.get(FramebufferName).value
+    const framebuffer = resources.framebuffer()
     const sumTexture = getSumTexture(ctx)
     const sumTexture = getSumTexture(ctx)
     sumTexture.attachFramebuffer(framebuffer, 0)
     sumTexture.attachFramebuffer(framebuffer, 0)
 
 

+ 6 - 8
src/mol-gl/compute/marching-cubes/active-voxels.ts

@@ -8,7 +8,7 @@ import { createComputeRenderable } from '../../renderable'
 import { WebGLContext } from '../../webgl/context';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
 import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
-import { Texture, createTexture } from '../../../mol-gl/webgl/texture';
+import { Texture } from '../../../mol-gl/webgl/texture';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ValueCell } from '../../../mol-util';
 import { ValueCell } from '../../../mol-util';
 import { Vec3, Vec2 } from '../../../mol-math/linear-algebra';
 import { Vec3, Vec2 } from '../../../mol-math/linear-algebra';
@@ -17,9 +17,6 @@ import { getTriCount } from './tables';
 import quad_vert from '../../../mol-gl/shader/quad.vert'
 import quad_vert from '../../../mol-gl/shader/quad.vert'
 import active_voxels_frag from '../../../mol-gl/shader/marching-cubes/active-voxels.frag'
 import active_voxels_frag from '../../../mol-gl/shader/marching-cubes/active-voxels.frag'
 
 
-/** name for shared framebuffer used for gpu marching cubes operations */
-const FramebufferName = 'marching-cubes-active-voxels'
-
 const ActiveVoxelsSchema = {
 const ActiveVoxelsSchema = {
     ...QuadSchema,
     ...QuadSchema,
 
 
@@ -67,13 +64,14 @@ function setRenderingDefaults(ctx: WebGLContext) {
 }
 }
 
 
 export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, gridScale: Vec2) {
 export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, gridScale: Vec2) {
-    const { gl, framebufferCache } = ctx
-    const { width, height } = volumeData
+    const { gl, resources } = ctx
+    const width = volumeData.getWidth()
+    const height = volumeData.getHeight()
 
 
-    const framebuffer = framebufferCache.get(FramebufferName).value
+    const framebuffer = resources.framebuffer()
     framebuffer.bind()
     framebuffer.bind()
 
 
-    const activeVoxelsTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
+    const activeVoxelsTex = resources.texture('image-float32', 'rgba', 'float', 'nearest')
     activeVoxelsTex.define(width, height)
     activeVoxelsTex.define(width, height)
 
 
     const renderable = getActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, gridScale)
     const renderable = getActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, gridScale)

+ 12 - 15
src/mol-gl/compute/marching-cubes/isosurface.ts

@@ -8,7 +8,7 @@ import { createComputeRenderable } from '../../renderable'
 import { WebGLContext } from '../../webgl/context';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
 import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
-import { Texture, createTexture } from '../../../mol-gl/webgl/texture';
+import { Texture } from '../../../mol-gl/webgl/texture';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ValueCell } from '../../../mol-util';
 import { ValueCell } from '../../../mol-util';
 import { Vec3, Vec2, Mat4 } from '../../../mol-math/linear-algebra';
 import { Vec3, Vec2, Mat4 } from '../../../mol-math/linear-algebra';
@@ -18,9 +18,6 @@ import { getTriIndices } from './tables';
 import quad_vert from '../../../mol-gl/shader/quad.vert'
 import quad_vert from '../../../mol-gl/shader/quad.vert'
 import isosurface_frag from '../../../mol-gl/shader/marching-cubes/isosurface.frag'
 import isosurface_frag from '../../../mol-gl/shader/marching-cubes/isosurface.frag'
 
 
-/** name for shared framebuffer used for gpu marching cubes operations */
-const FramebufferName = 'marching-cubes-isosurface'
-
 const IsosurfaceSchema = {
 const IsosurfaceSchema = {
     ...QuadSchema,
     ...QuadSchema,
 
 
@@ -83,30 +80,30 @@ function setRenderingDefaults(ctx: WebGLContext) {
 }
 }
 
 
 export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, vertexGroupTexture?: Texture, normalTexture?: Texture) {
 export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, vertexGroupTexture?: Texture, normalTexture?: Texture) {
-    const { gl, framebufferCache } = ctx
+    const { gl, resources } = ctx
     const { pyramidTex, height, levels, scale, count } = histogramPyramid
     const { pyramidTex, height, levels, scale, count } = histogramPyramid
 
 
     // console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim)
     // console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim)
     // console.log('iso volumeData', volumeData)
     // console.log('iso volumeData', volumeData)
 
 
-    const framebuffer = framebufferCache.get(FramebufferName).value
+    const framebuffer = resources.framebuffer()
 
 
     let needsClear = false
     let needsClear = false
 
 
     if (!vertexGroupTexture) {
     if (!vertexGroupTexture) {
-        vertexGroupTexture = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
-        vertexGroupTexture.define(pyramidTex.width, pyramidTex.height)
-    } else if (vertexGroupTexture.width !== pyramidTex.width || vertexGroupTexture.height !== pyramidTex.height) {
-        vertexGroupTexture.define(pyramidTex.width, pyramidTex.height)
+        vertexGroupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest')
+        vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight())
+    } else if (vertexGroupTexture.getWidth() !== pyramidTex.getWidth() || vertexGroupTexture.getHeight() !== pyramidTex.getHeight()) {
+        vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight())
     } else {
     } else {
         needsClear = true
         needsClear = true
     }
     }
 
 
     if (!normalTexture) {
     if (!normalTexture) {
-        normalTexture = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
-        normalTexture.define(pyramidTex.width, pyramidTex.height)
-    } else if (normalTexture.width !== pyramidTex.width || normalTexture.height !== pyramidTex.height) {
-        normalTexture.define(pyramidTex.width, pyramidTex.height)
+        normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest')
+        normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight())
+    } else if (normalTexture.getWidth() !== pyramidTex.getWidth() || normalTexture.getHeight() !== pyramidTex.getHeight()) {
+        normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight())
     } else {
     } else {
         needsClear = true
         needsClear = true
     }
     }
@@ -150,7 +147,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
     ])
     ])
 
 
     setRenderingDefaults(ctx)
     setRenderingDefaults(ctx)
-    gl.viewport(0, 0, pyramidTex.width, pyramidTex.height)
+    gl.viewport(0, 0, pyramidTex.getWidth(), pyramidTex.getHeight())
     if (needsClear) gl.clear(gl.COLOR_BUFFER_BIT)
     if (needsClear) gl.clear(gl.COLOR_BUFFER_BIT)
     renderable.render()
     renderable.render()
 
 

+ 6 - 6
src/mol-gl/compute/util.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020 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>
  */
  */
@@ -13,7 +13,7 @@ import { Vec2 } from '../../mol-math/linear-algebra';
 import { GLRenderingContext } from '../../mol-gl/webgl/compat';
 import { GLRenderingContext } from '../../mol-gl/webgl/compat';
 
 
 export const QuadPositions = new Float32Array([
 export const QuadPositions = new Float32Array([
-     1.0,  1.0,  -1.0,  1.0,  -1.0, -1.0, // First triangle
+    1.0,  1.0,  -1.0,  1.0,  -1.0, -1.0, // First triangle
     -1.0, -1.0,   1.0, -1.0,   1.0,  1.0  // Second triangle
     -1.0, -1.0,   1.0, -1.0,   1.0,  1.0  // Second triangle
 ])
 ])
 
 
@@ -42,11 +42,11 @@ function getArrayForTexture(gl: GLRenderingContext, texture: Texture, size: numb
 }
 }
 
 
 export function readTexture(ctx: WebGLContext, texture: Texture, width?: number, height?: number) {
 export function readTexture(ctx: WebGLContext, texture: Texture, width?: number, height?: number) {
-    const { gl, framebufferCache } = ctx
-    width = defaults(width, texture.width)
-    height = defaults(height, texture.height)
+    const { gl, resources } = ctx
+    width = defaults(width, texture.getWidth())
+    height = defaults(height, texture.getHeight())
     const size = width * height * 4
     const size = width * height * 4
-    const framebuffer = framebufferCache.get('read-texture').value
+    const framebuffer = resources.framebuffer()
     const array = getArrayForTexture(gl, texture, size)
     const array = getArrayForTexture(gl, texture, size)
     framebuffer.bind()
     framebuffer.bind()
     texture.attachFramebuffer(framebuffer, 0)
     texture.attachFramebuffer(framebuffer, 0)

+ 12 - 10
src/mol-gl/renderer.ts

@@ -22,11 +22,12 @@ export interface RendererStats {
     programCount: number
     programCount: number
     shaderCount: number
     shaderCount: number
 
 
-    bufferCount: number
+    attributeCount: number
+    elementsCount: number
     framebufferCount: number
     framebufferCount: number
     renderbufferCount: number
     renderbufferCount: number
     textureCount: number
     textureCount: number
-    vaoCount: number
+    vertexArrayCount: number
 
 
     drawCount: number
     drawCount: number
     instanceCount: number
     instanceCount: number
@@ -320,14 +321,15 @@ namespace Renderer {
             },
             },
             get stats(): RendererStats {
             get stats(): RendererStats {
                 return {
                 return {
-                    programCount: ctx.programCache.count,
-                    shaderCount: ctx.shaderCache.count,
-
-                    bufferCount: stats.bufferCount,
-                    framebufferCount: stats.framebufferCount,
-                    renderbufferCount: stats.renderbufferCount,
-                    textureCount: stats.textureCount,
-                    vaoCount: stats.vaoCount,
+                    programCount: ctx.stats.resourceCounts.program,
+                    shaderCount: ctx.stats.resourceCounts.shader,
+
+                    attributeCount: ctx.stats.resourceCounts.attribute,
+                    elementsCount: ctx.stats.resourceCounts.elements,
+                    framebufferCount: ctx.stats.resourceCounts.framebuffer,
+                    renderbufferCount: ctx.stats.resourceCounts.renderbuffer,
+                    textureCount: ctx.stats.resourceCounts.texture,
+                    vertexArrayCount: ctx.stats.resourceCounts.vertexArray,
 
 
                     drawCount: stats.drawCount,
                     drawCount: stats.drawCount,
                     instanceCount: stats.instanceCount,
                     instanceCount: stats.instanceCount,

+ 34 - 32
src/mol-gl/webgl/buffer.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
@@ -10,6 +10,7 @@ import { RenderableSchema } from '../renderable/schema';
 import { idFactory } from '../../mol-util/id-factory';
 import { idFactory } from '../../mol-util/id-factory';
 import { ValueOf } from '../../mol-util/type-helpers';
 import { ValueOf } from '../../mol-util/type-helpers';
 import { GLRenderingContext } from './compat';
 import { GLRenderingContext } from './compat';
+import { WebGLExtensions } from './extensions';
 
 
 const getNextBufferId = idFactory()
 const getNextBufferId = idFactory()
 
 
@@ -29,8 +30,7 @@ export type DataTypeArrayType = {
 export type ArrayType = ValueOf<DataTypeArrayType>
 export type ArrayType = ValueOf<DataTypeArrayType>
 export type ArrayKind = keyof DataTypeArrayType
 export type ArrayKind = keyof DataTypeArrayType
 
 
-export function getUsageHint(ctx: WebGLContext, usageHint: UsageHint) {
-    const { gl } = ctx
+export function getUsageHint(gl: GLRenderingContext, usageHint: UsageHint) {
     switch (usageHint) {
     switch (usageHint) {
         case 'static': return gl.STATIC_DRAW
         case 'static': return gl.STATIC_DRAW
         case 'dynamic': return gl.DYNAMIC_DRAW
         case 'dynamic': return gl.DYNAMIC_DRAW
@@ -38,8 +38,7 @@ export function getUsageHint(ctx: WebGLContext, usageHint: UsageHint) {
     }
     }
 }
 }
 
 
-export function getDataType(ctx: WebGLContext, dataType: DataType) {
-    const { gl } = ctx
+export function getDataType(gl: GLRenderingContext, dataType: DataType) {
     switch (dataType) {
     switch (dataType) {
         case 'uint8': return gl.UNSIGNED_BYTE
         case 'uint8': return gl.UNSIGNED_BYTE
         case 'int8': return gl.BYTE
         case 'int8': return gl.BYTE
@@ -51,8 +50,7 @@ export function getDataType(ctx: WebGLContext, dataType: DataType) {
     }
     }
 }
 }
 
 
-function dataTypeFromArray(ctx: WebGLContext, array: ArrayType) {
-    const { gl } = ctx
+function dataTypeFromArray(gl: GLRenderingContext, array: ArrayType) {
     if (array instanceof Uint8Array) {
     if (array instanceof Uint8Array) {
         return gl.UNSIGNED_BYTE
         return gl.UNSIGNED_BYTE
     } else if (array instanceof Int8Array) {
     } else if (array instanceof Int8Array) {
@@ -72,8 +70,7 @@ function dataTypeFromArray(ctx: WebGLContext, array: ArrayType) {
     }
     }
 }
 }
 
 
-export function getBufferType(ctx: WebGLContext, bufferType: BufferType) {
-    const { gl } = ctx
+export function getBufferType(gl: GLRenderingContext, bufferType: BufferType) {
     switch (bufferType) {
     switch (bufferType) {
         case 'attribute': return gl.ARRAY_BUFFER
         case 'attribute': return gl.ARRAY_BUFFER
         case 'elements': return gl.ELEMENT_ARRAY_BUFFER
         case 'elements': return gl.ELEMENT_ARRAY_BUFFER
@@ -84,7 +81,6 @@ export function getBufferType(ctx: WebGLContext, bufferType: BufferType) {
 export interface Buffer {
 export interface Buffer {
     readonly id: number
     readonly id: number
 
 
-    readonly _buffer: WebGLBuffer
     readonly _usageHint: number
     readonly _usageHint: number
     readonly _bufferType: number
     readonly _bufferType: number
     readonly _dataType: number
     readonly _dataType: number
@@ -92,21 +88,28 @@ export interface Buffer {
 
 
     readonly length: number
     readonly length: number
 
 
+    getBuffer: () => WebGLBuffer
     updateData: (array: ArrayType) => void
     updateData: (array: ArrayType) => void
     updateSubData: (array: ArrayType, offset: number, count: number) => void
     updateSubData: (array: ArrayType, offset: number, count: number) => void
+
+    reset: () => void
     destroy: () => void
     destroy: () => void
 }
 }
 
 
-export function createBuffer(ctx: WebGLContext, array: ArrayType, usageHint: UsageHint, bufferType: BufferType): Buffer {
-    const { gl, stats } = ctx
-    const _buffer = gl.createBuffer()
-    if (_buffer === null) {
+function getBuffer(gl: GLRenderingContext) {
+    const buffer = gl.createBuffer()
+    if (buffer === null) {
         throw new Error('Could not create WebGL buffer')
         throw new Error('Could not create WebGL buffer')
     }
     }
+    return buffer
+}
 
 
-    const _usageHint = getUsageHint(ctx, usageHint)
-    const _bufferType = getBufferType(ctx, bufferType)
-    const _dataType = dataTypeFromArray(ctx, array)
+function createBuffer(gl: GLRenderingContext, array: ArrayType, usageHint: UsageHint, bufferType: BufferType): Buffer {
+    let _buffer = getBuffer(gl)
+
+    const _usageHint = getUsageHint(gl, usageHint)
+    const _bufferType = getBufferType(gl, bufferType)
+    const _dataType = dataTypeFromArray(gl, array)
     const _bpe = array.BYTES_PER_ELEMENT
     const _bpe = array.BYTES_PER_ELEMENT
     const _length = array.length
     const _length = array.length
 
 
@@ -117,18 +120,17 @@ export function createBuffer(ctx: WebGLContext, array: ArrayType, usageHint: Usa
     updateData(array)
     updateData(array)
 
 
     let destroyed = false
     let destroyed = false
-    stats.bufferCount += 1
 
 
     return {
     return {
         id: getNextBufferId(),
         id: getNextBufferId(),
 
 
-        _buffer,
         _usageHint,
         _usageHint,
         _bufferType,
         _bufferType,
         _dataType,
         _dataType,
         _bpe,
         _bpe,
 
 
         length: _length,
         length: _length,
+        getBuffer: () => _buffer,
 
 
         updateData,
         updateData,
         updateSubData: (array: ArrayType, offset: number, count: number) => {
         updateSubData: (array: ArrayType, offset: number, count: number) => {
@@ -136,11 +138,14 @@ export function createBuffer(ctx: WebGLContext, array: ArrayType, usageHint: Usa
             gl.bufferSubData(_bufferType, offset * _bpe, array.subarray(offset, offset + count))
             gl.bufferSubData(_bufferType, offset * _bpe, array.subarray(offset, offset + count))
         },
         },
 
 
+        reset: () => {
+            _buffer = getBuffer(gl)
+            updateData(array)
+        },
         destroy: () => {
         destroy: () => {
             if (destroyed) return
             if (destroyed) return
             gl.deleteBuffer(_buffer)
             gl.deleteBuffer(_buffer)
             destroyed = true
             destroyed = true
-            stats.bufferCount -= 1
         }
         }
     }
     }
 }
 }
@@ -183,17 +188,16 @@ export interface AttributeBuffer extends Buffer {
     bind: (location: number) => void
     bind: (location: number) => void
 }
 }
 
 
-export function createAttributeBuffer<T extends ArrayType, S extends AttributeItemSize>(ctx: WebGLContext, array: T, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer {
-    const { gl } = ctx
-    const { instancedArrays } = ctx.extensions
+export function createAttributeBuffer<T extends ArrayType, S extends AttributeItemSize>(gl: GLRenderingContext, extensions: WebGLExtensions, array: T, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer {
+    const { instancedArrays } = extensions
 
 
-    const buffer = createBuffer(ctx, array, usageHint, 'attribute')
-    const { _buffer, _bufferType, _dataType, _bpe } = buffer
+    const buffer = createBuffer(gl, array, usageHint, 'attribute')
+    const { _bufferType, _dataType, _bpe } = buffer
 
 
     return {
     return {
         ...buffer,
         ...buffer,
         bind: (location: number) => {
         bind: (location: number) => {
-            gl.bindBuffer(_bufferType, _buffer)
+            gl.bindBuffer(_bufferType, buffer.getBuffer())
             if (itemSize === 16) {
             if (itemSize === 16) {
                 for (let i = 0; i < 4; ++i) {
                 for (let i = 0; i < 4; ++i) {
                     gl.enableVertexAttribArray(location + i)
                     gl.enableVertexAttribArray(location + i)
@@ -214,7 +218,7 @@ export function createAttributeBuffers(ctx: WebGLContext, schema: RenderableSche
     Object.keys(schema).forEach(k => {
     Object.keys(schema).forEach(k => {
         const spec = schema[k]
         const spec = schema[k]
         if (spec.type === 'attribute') {
         if (spec.type === 'attribute') {
-            buffers[buffers.length] = [k, createAttributeBuffer(ctx, values[k].ref.value, spec.itemSize, spec.divisor)]
+            buffers[buffers.length] = [k, ctx.resources.attribute(values[k].ref.value, spec.itemSize, spec.divisor)]
         }
         }
     })
     })
     return buffers
     return buffers
@@ -229,15 +233,13 @@ export interface ElementsBuffer extends Buffer {
     bind: () => void
     bind: () => void
 }
 }
 
 
-export function createElementsBuffer(ctx: WebGLContext, array: ElementsType, usageHint: UsageHint = 'static'): ElementsBuffer {
-    const { gl } = ctx
-    const buffer = createBuffer(ctx, array, usageHint, 'elements')
-    const { _buffer } = buffer
+export function createElementsBuffer(gl: GLRenderingContext, array: ElementsType, usageHint: UsageHint = 'static'): ElementsBuffer {
+    const buffer = createBuffer(gl, array, usageHint, 'elements')
 
 
     return {
     return {
         ...buffer,
         ...buffer,
         bind: () => {
         bind: () => {
-            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, _buffer);
+            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.getBuffer());
         }
         }
     }
     }
 }
 }

+ 72 - 41
src/mol-gl/webgl/context.ts

@@ -1,23 +1,25 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
 
 
-import { createProgramCache, ProgramCache } from './program'
-import { createShaderCache, ShaderCache } from './shader'
 import { GLRenderingContext, isWebGL2 } from './compat';
 import { GLRenderingContext, isWebGL2 } from './compat';
-import { createFramebufferCache, FramebufferCache, checkFramebufferStatus } from './framebuffer';
+import { checkFramebufferStatus } from './framebuffer';
 import { Scheduler } from '../../mol-task';
 import { Scheduler } from '../../mol-task';
 import { isDebugMode } from '../../mol-util/debug';
 import { isDebugMode } from '../../mol-util/debug';
 import { createExtensions, WebGLExtensions } from './extensions';
 import { createExtensions, WebGLExtensions } from './extensions';
 import { WebGLState, createState } from './state';
 import { WebGLState, createState } from './state';
 import { PixelData } from '../../mol-util/image';
 import { PixelData } from '../../mol-util/image';
+import { WebGLResources, createResources } from './resources';
+import { RenderTarget, createRenderTarget } from './render-target';
+import { BehaviorSubject } from 'rxjs';
+import { now } from '../../mol-util/now';
 
 
 export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null {
 export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null {
     function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') {
     function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') {
         try {
         try {
-           return canvas.getContext(contextId, contextAttributes) as GLRenderingContext | null
+            return canvas.getContext(contextId, contextAttributes) as GLRenderingContext | null
         } catch (e) {
         } catch (e) {
             return null
             return null
         }
         }
@@ -44,7 +46,9 @@ function getErrorDescription(gl: GLRenderingContext, error: number) {
 
 
 export function checkError(gl: GLRenderingContext) {
 export function checkError(gl: GLRenderingContext) {
     const error = gl.getError()
     const error = gl.getError()
-    if (error) throw new Error(`WebGL error: '${getErrorDescription(gl, error)}'`)
+    if (error !== gl.NO_ERROR) {
+        throw new Error(`WebGL error: '${getErrorDescription(gl, error)}'`)
+    }
 }
 }
 
 
 function unbindResources (gl: GLRenderingContext) {
 function unbindResources (gl: GLRenderingContext) {
@@ -123,7 +127,7 @@ function waitForGpuCommandsCompleteSync(gl: GLRenderingContext): void {
     gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
     gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
 }
 }
 
 
-function readPixels(gl: GLRenderingContext, x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) {
+export function readPixels(gl: GLRenderingContext, x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) {
     if (isDebugMode) checkFramebufferStatus(gl)
     if (isDebugMode) checkFramebufferStatus(gl)
     if (buffer instanceof Uint8Array) {
     if (buffer instanceof Uint8Array) {
         gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
         gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
@@ -147,25 +151,18 @@ function getDrawingBufferPixelData(gl: GLRenderingContext) {
 
 
 //
 //
 
 
-export type WebGLStats = {
-    bufferCount: number
-    framebufferCount: number
-    renderbufferCount: number
-    textureCount: number
-    vaoCount: number
-
-    drawCount: number
-    instanceCount: number
-    instancedDrawCount: number
-}
-
-function createStats(): WebGLStats {
+function createStats() {
     return {
     return {
-        bufferCount: 0,
-        framebufferCount: 0,
-        renderbufferCount: 0,
-        textureCount: 0,
-        vaoCount: 0,
+        resourceCounts: {
+            attribute: 0,
+            elements: 0,
+            framebuffer: 0,
+            program: 0,
+            renderbuffer: 0,
+            shader: 0,
+            texture: 0,
+            vertexArray: 0,
+        },
 
 
         drawCount: 0,
         drawCount: 0,
         instanceCount: 0,
         instanceCount: 0,
@@ -173,6 +170,8 @@ function createStats(): WebGLStats {
     }
     }
 }
 }
 
 
+export type WebGLStats = ReturnType<typeof createStats>
+
 //
 //
 
 
 /** A WebGL context object, including the rendering context, resource caches and counts */
 /** A WebGL context object, including the rendering context, resource caches and counts */
@@ -184,15 +183,18 @@ export interface WebGLContext {
     readonly extensions: WebGLExtensions
     readonly extensions: WebGLExtensions
     readonly state: WebGLState
     readonly state: WebGLState
     readonly stats: WebGLStats
     readonly stats: WebGLStats
-
-    readonly shaderCache: ShaderCache
-    readonly programCache: ProgramCache
-    readonly framebufferCache: FramebufferCache
+    readonly resources: WebGLResources
 
 
     readonly maxTextureSize: number
     readonly maxTextureSize: number
     readonly maxRenderbufferSize: number
     readonly maxRenderbufferSize: number
     readonly maxDrawBuffers: number
     readonly maxDrawBuffers: number
 
 
+    readonly isContextLost: boolean
+    readonly contextRestored: BehaviorSubject<now.Timestamp>
+    setContextLost: () => void
+    handleContextRestored: () => void
+
+    createRenderTarget: (width: number, height: number) => RenderTarget
     unbindFramebuffer: () => void
     unbindFramebuffer: () => void
     readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => void
     readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => void
     readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>
     readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>
@@ -206,10 +208,7 @@ export function createContext(gl: GLRenderingContext): WebGLContext {
     const extensions = createExtensions(gl)
     const extensions = createExtensions(gl)
     const state = createState(gl)
     const state = createState(gl)
     const stats = createStats()
     const stats = createStats()
-
-    const shaderCache: ShaderCache = createShaderCache(gl)
-    const programCache: ProgramCache = createProgramCache(gl, state, extensions, shaderCache)
-    const framebufferCache: FramebufferCache = createFramebufferCache(gl, stats)
+    const resources = createResources(gl, state, stats, extensions)
 
 
     const parameters = {
     const parameters = {
         maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
         maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
@@ -222,6 +221,9 @@ export function createContext(gl: GLRenderingContext): WebGLContext {
         throw new Error('Need "MAX_VERTEX_TEXTURE_IMAGE_UNITS" >= 8')
         throw new Error('Need "MAX_VERTEX_TEXTURE_IMAGE_UNITS" >= 8')
     }
     }
 
 
+    let isContextLost = false
+    let contextRestored = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
+
     let readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>
     let readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>
     if (isWebGL2(gl)) {
     if (isWebGL2(gl)) {
         const pbo = gl.createBuffer()
         const pbo = gl.createBuffer()
@@ -259,6 +261,8 @@ export function createContext(gl: GLRenderingContext): WebGLContext {
         }
         }
     }
     }
 
 
+    const renderTargets = new Set<RenderTarget>()
+
     return {
     return {
         gl,
         gl,
         isWebGL2: isWebGL2(gl),
         isWebGL2: isWebGL2(gl),
@@ -270,15 +274,45 @@ export function createContext(gl: GLRenderingContext): WebGLContext {
         extensions,
         extensions,
         state,
         state,
         stats,
         stats,
-
-        shaderCache,
-        programCache,
-        framebufferCache,
+        resources,
 
 
         get maxTextureSize () { return parameters.maxTextureSize },
         get maxTextureSize () { return parameters.maxTextureSize },
         get maxRenderbufferSize () { return parameters.maxRenderbufferSize },
         get maxRenderbufferSize () { return parameters.maxRenderbufferSize },
         get maxDrawBuffers () { return parameters.maxDrawBuffers },
         get maxDrawBuffers () { return parameters.maxDrawBuffers },
 
 
+        get isContextLost () {
+            return isContextLost || gl.isContextLost()
+        },
+        contextRestored,
+        setContextLost: () => {
+            isContextLost = true
+        },
+        handleContextRestored: () => {
+            Object.assign(extensions, createExtensions(gl))
+
+            state.reset()
+            state.currentMaterialId = -1
+            state.currentProgramId = -1
+            state.currentRenderItemId = -1
+
+            resources.reset()
+            renderTargets.forEach(rt => rt.reset())
+
+            isContextLost = false
+            contextRestored.next(now())
+        },
+
+        createRenderTarget: (width: number, height: number) => {
+            const renderTarget = createRenderTarget(gl, resources, width, height)
+            renderTargets.add(renderTarget)
+            return {
+                ...renderTarget,
+                destroy: () => {
+                    renderTarget.destroy()
+                    renderTargets.delete(renderTarget)
+                }
+            }
+        },
         unbindFramebuffer: () => unbindFramebuffer(gl),
         unbindFramebuffer: () => unbindFramebuffer(gl),
         readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => {
         readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => {
             readPixels(gl, x, y, width, height, buffer)
             readPixels(gl, x, y, width, height, buffer)
@@ -289,11 +323,8 @@ export function createContext(gl: GLRenderingContext): WebGLContext {
         getDrawingBufferPixelData: () => getDrawingBufferPixelData(gl),
         getDrawingBufferPixelData: () => getDrawingBufferPixelData(gl),
 
 
         destroy: () => {
         destroy: () => {
+            resources.destroy()
             unbindResources(gl)
             unbindResources(gl)
-            programCache.dispose()
-            shaderCache.dispose()
-            framebufferCache.dispose()
-            // TODO destroy buffers and textures
         }
         }
     }
     }
 }
 }

+ 14 - 19
src/mol-gl/webgl/framebuffer.ts

@@ -1,12 +1,10 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
 
 
-import { WebGLStats } from './context'
 import { idFactory } from '../../mol-util/id-factory';
 import { idFactory } from '../../mol-util/id-factory';
-import { ReferenceCache, createReferenceCache } from '../../mol-util/reference-cache';
 import { GLRenderingContext, isWebGL2 } from './compat';
 import { GLRenderingContext, isWebGL2 } from './compat';
 
 
 const getNextFramebufferId = idFactory()
 const getNextFramebufferId = idFactory()
@@ -40,37 +38,34 @@ export interface Framebuffer {
     readonly id: number
     readonly id: number
 
 
     bind: () => void
     bind: () => void
+    reset: () => void
     destroy: () => void
     destroy: () => void
 }
 }
 
 
-export function createFramebuffer (gl: GLRenderingContext, stats: WebGLStats): Framebuffer {
-    const _framebuffer = gl.createFramebuffer()
-    if (_framebuffer === null) {
+function getFramebuffer(gl: GLRenderingContext) {
+    const framebuffer = gl.createFramebuffer()
+    if (framebuffer === null) {
         throw new Error('Could not create WebGL framebuffer')
         throw new Error('Could not create WebGL framebuffer')
     }
     }
+    return framebuffer
+}
+
+export function createFramebuffer (gl: GLRenderingContext): Framebuffer {
+    let _framebuffer = getFramebuffer(gl)
 
 
     let destroyed = false
     let destroyed = false
-    stats.framebufferCount += 1
 
 
     return {
     return {
         id: getNextFramebufferId(),
         id: getNextFramebufferId(),
-
         bind: () => gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer),
         bind: () => gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer),
+
+        reset: () => {
+            _framebuffer = getFramebuffer(gl)
+        },
         destroy: () => {
         destroy: () => {
             if (destroyed) return
             if (destroyed) return
             gl.deleteFramebuffer(_framebuffer)
             gl.deleteFramebuffer(_framebuffer)
             destroyed = true
             destroyed = true
-            stats.framebufferCount -= 1
         }
         }
     }
     }
-}
-
-export type FramebufferCache = ReferenceCache<Framebuffer, string>
-
-export function createFramebufferCache(gl: GLRenderingContext, stats: WebGLStats): FramebufferCache {
-    return createReferenceCache(
-        (name: string) => name,
-        () => createFramebuffer(gl, stats),
-        (framebuffer: Framebuffer) => { framebuffer.destroy() }
-    )
 }
 }

+ 47 - 49
src/mol-gl/webgl/program.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
@@ -7,16 +7,14 @@
 import { ShaderCode, DefineValues, addShaderDefines } from '../shader-code'
 import { ShaderCode, DefineValues, addShaderDefines } from '../shader-code'
 import { WebGLState } from './state';
 import { WebGLState } from './state';
 import { WebGLExtensions } from './extensions';
 import { WebGLExtensions } from './extensions';
-import { getUniformSetters, UniformsList, getUniformType } from './uniform';
+import { getUniformSetters, UniformsList, getUniformType, UniformSetters } from './uniform';
 import { AttributeBuffers, getAttribType } from './buffer';
 import { AttributeBuffers, getAttribType } from './buffer';
 import { TextureId, Textures } from './texture';
 import { TextureId, Textures } from './texture';
-import { createReferenceCache, ReferenceCache } from '../../mol-util/reference-cache';
 import { idFactory } from '../../mol-util/id-factory';
 import { idFactory } from '../../mol-util/id-factory';
 import { RenderableSchema } from '../renderable/schema';
 import { RenderableSchema } from '../renderable/schema';
-import { hashFnv32a, hashString } from '../../mol-data/util';
 import { isDebugMode } from '../../mol-util/debug';
 import { isDebugMode } from '../../mol-util/debug';
 import { GLRenderingContext } from './compat';
 import { GLRenderingContext } from './compat';
-import { ShaderCache } from './shader';
+import { ShaderType, Shader } from './shader';
 
 
 const getNextProgramId = idFactory()
 const getNextProgramId = idFactory()
 
 
@@ -28,6 +26,7 @@ export interface Program {
     bindAttributes: (attribueBuffers: AttributeBuffers) => void
     bindAttributes: (attribueBuffers: AttributeBuffers) => void
     bindTextures: (textures: Textures) => void
     bindTextures: (textures: Textures) => void
 
 
+    reset: () => void
     destroy: () => void
     destroy: () => void
 }
 }
 
 
@@ -113,43 +112,60 @@ function checkActiveUniforms(gl: GLRenderingContext, program: WebGLProgram, sche
     }
     }
 }
 }
 
 
+function checkProgram(gl: GLRenderingContext, program: WebGLProgram) {
+    // no-op in FF on Mac, see https://bugzilla.mozilla.org/show_bug.cgi?id=1284425
+    // gl.validateProgram(program)
+    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
+        throw new Error(`Could not compile WebGL program. \n\n${gl.getProgramInfoLog(program)}`);
+    }
+}
+
 export interface ProgramProps {
 export interface ProgramProps {
     defineValues: DefineValues,
     defineValues: DefineValues,
     shaderCode: ShaderCode,
     shaderCode: ShaderCode,
     schema: RenderableSchema
     schema: RenderableSchema
 }
 }
 
 
-export function createProgram(gl: GLRenderingContext, state: WebGLState, extensions: WebGLExtensions, shaderCache: ShaderCache, props: ProgramProps): Program {
-    const { defineValues, shaderCode: _shaderCode, schema } = props
-
+function getProgram(gl: GLRenderingContext) {
     const program = gl.createProgram()
     const program = gl.createProgram()
     if (program === null) {
     if (program === null) {
         throw new Error('Could not create WebGL program')
         throw new Error('Could not create WebGL program')
     }
     }
+    return program
+}
+
+type ShaderGetter = (type: ShaderType, source: string) => Shader
+
+export function createProgram(gl: GLRenderingContext, state: WebGLState, extensions: WebGLExtensions, getShader: ShaderGetter, props: ProgramProps): Program {
+    const { defineValues, shaderCode: _shaderCode, schema } = props
+
+    let program = getProgram(gl)
     const programId = getNextProgramId()
     const programId = getNextProgramId()
 
 
     const shaderCode = addShaderDefines(gl, extensions, defineValues, _shaderCode)
     const shaderCode = addShaderDefines(gl, extensions, defineValues, _shaderCode)
-    const vertShaderRef = shaderCache.get({ type: 'vert', source: shaderCode.vert })
-    const fragShaderRef = shaderCache.get({ type: 'frag', source: shaderCode.frag })
-
-    vertShaderRef.value.attach(program)
-    fragShaderRef.value.attach(program)
-    gl.linkProgram(program)
-    if (isDebugMode) {
-        // no-op in FF on Mac, see https://bugzilla.mozilla.org/show_bug.cgi?id=1284425
-        // gl.validateProgram(program)
-        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
-            throw new Error(`Could not compile WebGL program. \n\n${gl.getProgramInfoLog(program)}`);
+    const vertShader = getShader('vert', shaderCode.vert)
+    const fragShader = getShader('frag', shaderCode.frag)
+
+    let locations: Locations // = getLocations(gl, program, schema)
+    let uniformSetters: UniformSetters // = getUniformSetters(schema)
+
+    function init() {
+        vertShader.attach(program)
+        fragShader.attach(program)
+        gl.linkProgram(program)
+        if (isDebugMode) {
+            checkProgram(gl, program)
         }
         }
-    }
 
 
-    const locations = getLocations(gl, program, schema)
-    const uniformSetters = getUniformSetters(schema)
+        locations = getLocations(gl, program, schema)
+        uniformSetters = getUniformSetters(schema)
 
 
-    if (isDebugMode) {
-        checkActiveAttributes(gl, program, schema)
-        checkActiveUniforms(gl, program, schema)
+        if (isDebugMode) {
+            checkActiveAttributes(gl, program, schema)
+            checkActiveUniforms(gl, program, schema)
+        }
     }
     }
+    init()
 
 
     let destroyed = false
     let destroyed = false
 
 
@@ -190,34 +206,16 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
             }
             }
         },
         },
 
 
+        reset: () => {
+            program = getProgram(gl)
+            init()
+        },
         destroy: () => {
         destroy: () => {
             if (destroyed) return
             if (destroyed) return
-            vertShaderRef.free()
-            fragShaderRef.free()
+            vertShader.destroy()
+            fragShader.destroy()
             gl.deleteProgram(program)
             gl.deleteProgram(program)
             destroyed = true
             destroyed = true
         }
         }
     }
     }
-}
-
-export type ProgramCache = ReferenceCache<Program, ProgramProps>
-
-function defineValueHash(v: boolean | number | string): number {
-    return typeof v === 'boolean' ? (v ? 1 : 0) :
-        typeof v === 'number' ? v : hashString(v)
-}
-
-export function createProgramCache(gl: GLRenderingContext, state: WebGLState, extensions: WebGLExtensions, shaderCache: ShaderCache): ProgramCache {
-    return createReferenceCache(
-        (props: ProgramProps) => {
-            const array = [ props.shaderCode.id ]
-            Object.keys(props.defineValues).forEach(k => {
-                const v = props.defineValues[k].ref.value
-                array.push(hashString(k), defineValueHash(v))
-            })
-            return hashFnv32a(array).toString()
-        },
-        (props: ProgramProps) => createProgram(gl, state, extensions, shaderCache, props),
-        (program: Program) => { program.destroy() }
-    )
 }
 }

+ 25 - 42
src/mol-gl/webgl/render-item.ts

@@ -1,22 +1,21 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
 
 
-import { createAttributeBuffers, createElementsBuffer, ElementsBuffer, createAttributeBuffer, AttributeKind } from './buffer';
+import { createAttributeBuffers, ElementsBuffer, AttributeKind } from './buffer';
 import { createTextures, Texture } from './texture';
 import { createTextures, Texture } from './texture';
 import { WebGLContext, checkError } from './context';
 import { WebGLContext, checkError } from './context';
 import { ShaderCode } from '../shader-code';
 import { ShaderCode } from '../shader-code';
 import { Program } from './program';
 import { Program } from './program';
 import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues, Values } from '../renderable/schema';
 import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues, Values } from '../renderable/schema';
 import { idFactory } from '../../mol-util/id-factory';
 import { idFactory } from '../../mol-util/id-factory';
-import { deleteVertexArray, createVertexArray } from './vertex-array';
 import { ValueCell } from '../../mol-util';
 import { ValueCell } from '../../mol-util';
-import { ReferenceItem } from '../../mol-util/reference-cache';
 import { TextureImage, TextureVolume } from '../../mol-gl/renderable/util';
 import { TextureImage, TextureVolume } from '../../mol-gl/renderable/util';
 import { checkFramebufferStatus } from './framebuffer';
 import { checkFramebufferStatus } from './framebuffer';
 import { isDebugMode } from '../../mol-util/debug';
 import { isDebugMode } from '../../mol-util/debug';
+import { VertexArray } from './vertex-array';
 
 
 const getNextRenderItemId = idFactory()
 const getNextRenderItemId = idFactory()
 
 
@@ -65,8 +64,8 @@ type RenderVariantDefines = typeof GraphicsRenderVariantDefines | typeof Compute
 
 
 //
 //
 
 
-type ProgramVariants = { [k: string]: ReferenceItem<Program> }
-type VertexArrayVariants = { [k: string]: WebGLVertexArrayObjectOES | null }
+type ProgramVariants = { [k: string]: Program }
+type VertexArrayVariants = { [k: string]: VertexArray | null }
 
 
 interface ValueChanges {
 interface ValueChanges {
     attributes: boolean
     attributes: boolean
@@ -108,7 +107,7 @@ export function createComputeRenderItem(ctx: WebGLContext, drawMode: DrawMode, s
  */
  */
 export function createRenderItem<T extends RenderVariantDefines, S extends keyof T & string>(ctx: WebGLContext, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues, materialId: number, renderVariantDefines: T): RenderItem<S> {
 export function createRenderItem<T extends RenderVariantDefines, S extends keyof T & string>(ctx: WebGLContext, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues, materialId: number, renderVariantDefines: T): RenderItem<S> {
     const id = getNextRenderItemId()
     const id = getNextRenderItemId()
-    const { stats, state, programCache } = ctx
+    const { stats, state, resources } = ctx
     const { instancedArrays, vertexArrayObject } = ctx.extensions
     const { instancedArrays, vertexArrayObject } = ctx.extensions
 
 
     const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues } = splitValues(schema, values)
     const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues } = splitValues(schema, values)
@@ -124,11 +123,7 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
     const programs: ProgramVariants = {}
     const programs: ProgramVariants = {}
     Object.keys(renderVariantDefines).forEach(k => {
     Object.keys(renderVariantDefines).forEach(k => {
         const variantDefineValues: Values<RenderableSchema> = (renderVariantDefines as any)[k]
         const variantDefineValues: Values<RenderableSchema> = (renderVariantDefines as any)[k]
-        programs[k] = programCache.get({
-            defineValues: { ...defineValues, ...variantDefineValues },
-            shaderCode,
-            schema
-        })
+        programs[k] = resources.program({ ...defineValues, ...variantDefineValues }, shaderCode, schema)
     })
     })
 
 
     const textures = createTextures(ctx, schema, textureValues)
     const textures = createTextures(ctx, schema, textureValues)
@@ -137,12 +132,12 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
     let elementsBuffer: ElementsBuffer | undefined
     let elementsBuffer: ElementsBuffer | undefined
     const elements = values.elements
     const elements = values.elements
     if (elements && elements.ref.value) {
     if (elements && elements.ref.value) {
-        elementsBuffer = createElementsBuffer(ctx, elements.ref.value)
+        elementsBuffer = resources.elements(elements.ref.value)
     }
     }
 
 
     const vertexArrays: VertexArrayVariants = {}
     const vertexArrays: VertexArrayVariants = {}
     Object.keys(renderVariantDefines).forEach(k => {
     Object.keys(renderVariantDefines).forEach(k => {
-        vertexArrays[k] = createVertexArray(ctx, programs[k].value, attributeBuffers, elementsBuffer)
+        vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null
     })
     })
 
 
     let drawCount = values.drawCount.ref.value
     let drawCount = values.drawCount.ref.value
@@ -160,11 +155,11 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
     return {
     return {
         id,
         id,
         materialId,
         materialId,
-        getProgram: (variant: S) => programs[variant].value,
+        getProgram: (variant: S) => programs[variant],
 
 
         render: (variant: S) => {
         render: (variant: S) => {
-            if (drawCount === 0 || instanceCount === 0) return
-            const program = programs[variant].value
+            if (drawCount === 0 || instanceCount === 0 || ctx.isContextLost) return
+            const program = programs[variant]
             if (program.id === currentProgramId && state.currentRenderItemId === id) {
             if (program.id === currentProgramId && state.currentRenderItemId === id) {
                 program.setUniforms(uniformValueEntries)
                 program.setUniforms(uniformValueEntries)
                 program.bindTextures(textures)
                 program.bindTextures(textures)
@@ -181,8 +176,8 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
                 }
                 }
                 program.setUniforms(uniformValueEntries)
                 program.setUniforms(uniformValueEntries)
                 program.bindTextures(textures)
                 program.bindTextures(textures)
-                if (vertexArrayObject && vertexArray) {
-                    vertexArrayObject.bindVertexArray(vertexArray)
+                if (vertexArray) {
+                    vertexArray.bind()
                     // need to bind elements buffer explicitly since it is not always recorded in the VAO
                     // need to bind elements buffer explicitly since it is not always recorded in the VAO
                     if (elementsBuffer) elementsBuffer.bind()
                     if (elementsBuffer) elementsBuffer.bind()
                 } else {
                 } else {
@@ -226,12 +221,8 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
                 // console.log('some defines changed, need to rebuild programs')
                 // console.log('some defines changed, need to rebuild programs')
                 Object.keys(renderVariantDefines).forEach(k => {
                 Object.keys(renderVariantDefines).forEach(k => {
                     const variantDefineValues: Values<RenderableSchema> = (renderVariantDefines as any)[k]
                     const variantDefineValues: Values<RenderableSchema> = (renderVariantDefines as any)[k]
-                    programs[k].free()
-                    programs[k] = programCache.get({
-                        defineValues: { ...defineValues, ...variantDefineValues },
-                        shaderCode,
-                        schema
-                    })
+                    programs[k].destroy()
+                    programs[k] = resources.program({ ...defineValues, ...variantDefineValues }, shaderCode, schema)
                 })
                 })
             }
             }
 
 
@@ -261,7 +252,7 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
                         // console.log('attribute array to small, need to create new attribute', k, value.ref.id, value.ref.version)
                         // console.log('attribute array to small, need to create new attribute', k, value.ref.id, value.ref.version)
                         buffer.destroy()
                         buffer.destroy()
                         const { itemSize, divisor } = schema[k] as AttributeSpec<AttributeKind>
                         const { itemSize, divisor } = schema[k] as AttributeSpec<AttributeKind>
-                        attributeBuffers[i][1] = createAttributeBuffer(ctx, value.ref.value, itemSize, divisor)
+                        attributeBuffers[i][1] = resources.attribute(value.ref.value, itemSize, divisor)
                         valueChanges.attributes = true
                         valueChanges.attributes = true
                     }
                     }
                     versions[k] = value.ref.version
                     versions[k] = value.ref.version
@@ -275,7 +266,7 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
                 } else {
                 } else {
                     // console.log('elements array to small, need to create new elements', values.elements.ref.id, values.elements.ref.version)
                     // console.log('elements array to small, need to create new elements', values.elements.ref.id, values.elements.ref.version)
                     elementsBuffer.destroy()
                     elementsBuffer.destroy()
-                    elementsBuffer = createElementsBuffer(ctx, values.elements.ref.value)
+                    elementsBuffer = resources.elements(values.elements.ref.value)
                     valueChanges.elements = true
                     valueChanges.elements = true
                 }
                 }
                 versions.elements = values.elements.ref.version
                 versions.elements = values.elements.ref.version
@@ -283,19 +274,10 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
 
 
             if (valueChanges.attributes || valueChanges.defines || valueChanges.elements) {
             if (valueChanges.attributes || valueChanges.defines || valueChanges.elements) {
                 // console.log('program/defines or buffers changed, update vaos')
                 // console.log('program/defines or buffers changed, update vaos')
-                const { vertexArrayObject } = ctx.extensions
-                if (vertexArrayObject) {
-                    Object.keys(renderVariantDefines).forEach(k => {
-                        vertexArrayObject.bindVertexArray(vertexArrays[k])
-                        if (elementsBuffer && (valueChanges.defines || valueChanges.elements)) {
-                            elementsBuffer.bind()
-                        }
-                        if (valueChanges.attributes || valueChanges.defines) {
-                            programs[k].value.bindAttributes(attributeBuffers)
-                        }
-                        vertexArrayObject.bindVertexArray(null)
-                    })
-                }
+                Object.keys(renderVariantDefines).forEach(k => {
+                    const vertexArray = vertexArrays[k]
+                    if (vertexArray) vertexArray.update()
+                })
             }
             }
 
 
             for (let i = 0, il = textures.length; i < il; ++i) {
             for (let i = 0, il = textures.length; i < il; ++i) {
@@ -319,8 +301,9 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
         destroy: () => {
         destroy: () => {
             if (!destroyed) {
             if (!destroyed) {
                 Object.keys(renderVariantDefines).forEach(k => {
                 Object.keys(renderVariantDefines).forEach(k => {
-                    programs[k].free()
-                    deleteVertexArray(ctx, vertexArrays[k])
+                    programs[k].destroy()
+                    const vertexArray = vertexArrays[k]
+                    if (vertexArray) vertexArray.destroy()
                 })
                 })
                 textures.forEach(([k, texture]) => {
                 textures.forEach(([k, texture]) => {
                     // lifetime of textures with kind 'texture' is defined externally
                     // lifetime of textures with kind 'texture' is defined externally

+ 26 - 22
src/mol-gl/webgl/render-target.ts

@@ -1,63 +1,64 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
 
 
-import { WebGLContext } from './context'
+import { readPixels } from './context'
 import { idFactory } from '../../mol-util/id-factory';
 import { idFactory } from '../../mol-util/id-factory';
-import { createTexture, Texture } from './texture';
-import { createFramebuffer, Framebuffer } from './framebuffer';
-import { createRenderbuffer } from './renderbuffer';
+import { Texture } from './texture';
+import { Framebuffer } from './framebuffer';
 import { TextureImage } from '../renderable/util';
 import { TextureImage } from '../renderable/util';
 import { Mutable } from '../../mol-util/type-helpers';
 import { Mutable } from '../../mol-util/type-helpers';
 import { PixelData } from '../../mol-util/image';
 import { PixelData } from '../../mol-util/image';
+import { WebGLResources } from './resources';
+import { GLRenderingContext } from './compat';
 
 
 const getNextRenderTargetId = idFactory()
 const getNextRenderTargetId = idFactory()
 
 
 export interface RenderTarget {
 export interface RenderTarget {
     readonly id: number
     readonly id: number
-    readonly width: number
-    readonly height: number
     readonly image: TextureImage<any>
     readonly image: TextureImage<any>
     readonly texture: Texture
     readonly texture: Texture
     readonly framebuffer: Framebuffer
     readonly framebuffer: Framebuffer
 
 
+    getWidth: () => number
+    getHeight: () => number
     /** binds framebuffer and sets viewport to rendertarget's width and height */
     /** binds framebuffer and sets viewport to rendertarget's width and height */
     bind: () => void
     bind: () => void
     setSize: (width: number, height: number) => void
     setSize: (width: number, height: number) => void
     readBuffer: (x: number, y: number, width: number, height: number, dst: Uint8Array) => void
     readBuffer: (x: number, y: number, width: number, height: number, dst: Uint8Array) => void
     getBuffer: () => Uint8Array
     getBuffer: () => Uint8Array
     getPixelData: () => PixelData
     getPixelData: () => PixelData
+    reset: () => void
     destroy: () => void
     destroy: () => void
 }
 }
 
 
-export function createRenderTarget (ctx: WebGLContext, _width: number, _height: number): RenderTarget {
-    const { gl, stats } = ctx
-
+export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResources, _width: number, _height: number): RenderTarget {
     const image: Mutable<TextureImage<Uint8Array>> = {
     const image: Mutable<TextureImage<Uint8Array>> = {
         array: new Uint8Array(_width * _height * 4),
         array: new Uint8Array(_width * _height * 4),
         width: _width,
         width: _width,
         height: _height
         height: _height
     }
     }
 
 
-    const targetTexture = createTexture(ctx, 'image-uint8', 'rgba', 'ubyte', 'linear')
-    targetTexture.load(image)
-
-    const framebuffer = createFramebuffer(gl, stats)
-
-    // attach the texture as the first color attachment
-    targetTexture.attachFramebuffer(framebuffer, 'color0')
-
+    const framebuffer = resources.framebuffer()
+    const targetTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear')
     // make a depth renderbuffer of the same size as the targetTexture
     // make a depth renderbuffer of the same size as the targetTexture
-    const depthRenderbuffer = createRenderbuffer(ctx, 'depth16', 'depth', _width, _height)
+    const depthRenderbuffer = resources.renderbuffer('depth16', 'depth', _width, _height)
+
+    function init() {
+        targetTexture.load(image)
+        targetTexture.attachFramebuffer(framebuffer, 'color0')
+        depthRenderbuffer.attachFramebuffer(framebuffer)
+    }
+    init()
 
 
     let destroyed = false
     let destroyed = false
 
 
     function readBuffer(x: number, y: number, width: number, height: number, dst: Uint8Array) {
     function readBuffer(x: number, y: number, width: number, height: number, dst: Uint8Array) {
         framebuffer.bind()
         framebuffer.bind()
         gl.viewport(0, 0, _width, _height)
         gl.viewport(0, 0, _width, _height)
-        ctx.readPixels(x, y, width, height, dst)
+        readPixels(gl, x, y, width, height, dst)
     }
     }
 
 
     function getBuffer() {
     function getBuffer() {
@@ -67,12 +68,12 @@ export function createRenderTarget (ctx: WebGLContext, _width: number, _height:
 
 
     return {
     return {
         id: getNextRenderTargetId(),
         id: getNextRenderTargetId(),
-        get width () { return _width },
-        get height () { return _height },
         image,
         image,
         texture: targetTexture,
         texture: targetTexture,
         framebuffer,
         framebuffer,
 
 
+        getWidth: () => _width,
+        getHeight: () => _height,
         bind: () => {
         bind: () => {
             framebuffer.bind()
             framebuffer.bind()
             gl.viewport(0, 0, _width, _height)
             gl.viewport(0, 0, _width, _height)
@@ -89,6 +90,9 @@ export function createRenderTarget (ctx: WebGLContext, _width: number, _height:
         readBuffer,
         readBuffer,
         getBuffer,
         getBuffer,
         getPixelData: () => PixelData.flipY(PixelData.create(getBuffer(), _width, _height)),
         getPixelData: () => PixelData.flipY(PixelData.create(getBuffer(), _width, _height)),
+        reset: () => {
+            init()
+        },
         destroy: () => {
         destroy: () => {
             if (destroyed) return
             if (destroyed) return
             targetTexture.destroy()
             targetTexture.destroy()

+ 36 - 21
src/mol-gl/webgl/renderbuffer.ts

@@ -1,19 +1,20 @@
 /**
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
 
 
-import { WebGLContext } from './context'
 import { idFactory } from '../../mol-util/id-factory';
 import { idFactory } from '../../mol-util/id-factory';
+import { GLRenderingContext } from './compat';
+import { Framebuffer, checkFramebufferStatus } from './framebuffer';
+import { isDebugMode } from '../../mol-util/debug';
 
 
 const getNextRenderbufferId = idFactory()
 const getNextRenderbufferId = idFactory()
 
 
 export type RenderbufferFormat = 'depth16' | 'stencil8' | 'rgba4' | 'depth-stencil'
 export type RenderbufferFormat = 'depth16' | 'stencil8' | 'rgba4' | 'depth-stencil'
 export type RenderbufferAttachment = 'depth' | 'stencil' | 'depth-stencil' | 'color0'
 export type RenderbufferAttachment = 'depth' | 'stencil' | 'depth-stencil' | 'color0'
 
 
-export function getFormat(ctx: WebGLContext, format: RenderbufferFormat) {
-    const { gl } = ctx
+export function getFormat(gl: GLRenderingContext, format: RenderbufferFormat) {
     switch (format) {
     switch (format) {
         case 'depth16': return gl.DEPTH_COMPONENT16
         case 'depth16': return gl.DEPTH_COMPONENT16
         case 'stencil8': return gl.STENCIL_INDEX8
         case 'stencil8': return gl.STENCIL_INDEX8
@@ -22,8 +23,7 @@ export function getFormat(ctx: WebGLContext, format: RenderbufferFormat) {
     }
     }
 }
 }
 
 
-export function getAttachment(ctx: WebGLContext, attachment: RenderbufferAttachment) {
-    const { gl } = ctx
+export function getAttachment(gl: GLRenderingContext, attachment: RenderbufferAttachment) {
     switch (attachment) {
     switch (attachment) {
         case 'depth': return gl.DEPTH_ATTACHMENT
         case 'depth': return gl.DEPTH_ATTACHMENT
         case 'stencil': return gl.STENCIL_ATTACHMENT
         case 'stencil': return gl.STENCIL_ATTACHMENT
@@ -36,43 +36,58 @@ export interface Renderbuffer {
     readonly id: number
     readonly id: number
 
 
     bind: () => void
     bind: () => void
+    attachFramebuffer: (framebuffer: Framebuffer) => void
     setSize: (width: number, height: number) => void
     setSize: (width: number, height: number) => void
-
+    reset: () => void
     destroy: () => void
     destroy: () => void
 }
 }
 
 
-export function createRenderbuffer (ctx: WebGLContext, format: RenderbufferFormat, attachment: RenderbufferAttachment, _width: number, _height: number): Renderbuffer {
-    const { gl, stats } = ctx
-    const _renderbuffer = gl.createRenderbuffer()
-    if (_renderbuffer === null) {
+function getRenderbuffer(gl: GLRenderingContext) {
+    const renderbuffer = gl.createRenderbuffer()
+    if (renderbuffer === null) {
         throw new Error('Could not create WebGL renderbuffer')
         throw new Error('Could not create WebGL renderbuffer')
     }
     }
+    return renderbuffer
+}
+
+export function createRenderbuffer (gl: GLRenderingContext, format: RenderbufferFormat, attachment: RenderbufferAttachment, _width: number, _height: number): Renderbuffer {
+    let _renderbuffer = getRenderbuffer(gl)
 
 
     const bind = () => gl.bindRenderbuffer(gl.RENDERBUFFER, _renderbuffer)
     const bind = () => gl.bindRenderbuffer(gl.RENDERBUFFER, _renderbuffer)
-    const _format = getFormat(ctx, format)
-    const _attachment = getAttachment(ctx, attachment)
+    const _format = getFormat(gl, format)
+    const _attachment = getAttachment(gl, attachment)
 
 
-    bind()
-    gl.renderbufferStorage(gl.RENDERBUFFER, _format, _width, _height)
-    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, _attachment, gl.RENDERBUFFER, _renderbuffer)
+    function init() {
+        bind()
+        gl.renderbufferStorage(gl.RENDERBUFFER, _format, _width, _height)
+    }
+    init()
 
 
     let destroyed = false
     let destroyed = false
-    stats.renderbufferCount += 1
 
 
     return {
     return {
         id: getNextRenderbufferId(),
         id: getNextRenderbufferId(),
 
 
         bind,
         bind,
-        setSize: (_width: number, _height: number) => {
+        attachFramebuffer: (framebuffer: Framebuffer) => {
+            framebuffer.bind()
             bind()
             bind()
-            gl.renderbufferStorage(gl.RENDERBUFFER, _format, _width, _height)
+            gl.framebufferRenderbuffer(gl.FRAMEBUFFER, _attachment, gl.RENDERBUFFER, _renderbuffer)
+            if (isDebugMode) checkFramebufferStatus(gl)
+        },
+        setSize: (width: number, height: number) => {
+            _width = width
+            _height = height
+            init()
+        },
+        reset: () => {
+            _renderbuffer = getRenderbuffer(gl)
+            init()
         },
         },
-
         destroy: () => {
         destroy: () => {
             if (destroyed) return
             if (destroyed) return
             gl.deleteRenderbuffer(_renderbuffer)
             gl.deleteRenderbuffer(_renderbuffer)
             destroyed = true
             destroyed = true
-            stats.framebufferCount -= 1
         }
         }
     }
     }
 }
 }

+ 155 - 0
src/mol-gl/webgl/resources.ts

@@ -0,0 +1,155 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ProgramProps, createProgram, Program } from './program'
+import { ShaderType, createShader, Shader, ShaderProps } from './shader'
+import { GLRenderingContext } from './compat';
+import { Framebuffer, createFramebuffer } from './framebuffer';
+import { WebGLExtensions } from './extensions';
+import { WebGLState } from './state';
+import { AttributeBuffer, UsageHint, ArrayType, AttributeItemSize, createAttributeBuffer, ElementsBuffer, createElementsBuffer, ElementsType, AttributeBuffers } from './buffer';
+import { createReferenceCache, ReferenceItem } from '../../mol-util/reference-cache';
+import { WebGLStats } from './context';
+import { hashString, hashFnv32a } from '../../mol-data/util';
+import { DefineValues, ShaderCode } from '../shader-code';
+import { RenderableSchema } from '../renderable/schema';
+import { createRenderbuffer, Renderbuffer, RenderbufferAttachment, RenderbufferFormat } from './renderbuffer';
+import { Texture, TextureKind, TextureFormat, TextureType, TextureFilter, createTexture } from './texture';
+import { VertexArray, createVertexArray } from './vertex-array';
+
+function defineValueHash(v: boolean | number | string): number {
+    return typeof v === 'boolean' ? (v ? 1 : 0) :
+        typeof v === 'number' ? v : hashString(v)
+}
+
+function wrapCached<T extends Resource>(resourceItem: ReferenceItem<T>) {
+    const wrapped = {
+        ...resourceItem.value,
+        destroy: () => {
+            resourceItem.free()
+        }
+    }
+
+    return wrapped
+}
+
+//
+
+interface Resource {
+    reset: () => void
+    destroy: () => void
+}
+
+type ResourceName = keyof WebGLStats['resourceCounts']
+
+export interface WebGLResources {
+    attribute: (array: ArrayType, itemSize: AttributeItemSize, divisor: number, usageHint?: UsageHint) => AttributeBuffer
+    elements: (array: ElementsType, usageHint?: UsageHint) => ElementsBuffer
+    framebuffer: () => Framebuffer
+    program: (defineValues: DefineValues, shaderCode: ShaderCode, schema: RenderableSchema) => Program
+    renderbuffer: (format: RenderbufferFormat, attachment: RenderbufferAttachment, width: number, height: number) => Renderbuffer
+    shader: (type: ShaderType, source: string) => Shader
+    texture: (kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter) => Texture,
+    vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => VertexArray,
+
+    reset: () => void
+    destroy: () => void
+}
+
+export function createResources(gl: GLRenderingContext, state: WebGLState, stats: WebGLStats, extensions: WebGLExtensions): WebGLResources {
+    const sets: { [k in ResourceName]: Set<Resource> } = {
+        attribute: new Set<Resource>(),
+        elements: new Set<Resource>(),
+        framebuffer: new Set<Resource>(),
+        program: new Set<Resource>(),
+        renderbuffer: new Set<Resource>(),
+        shader: new Set<Resource>(),
+        texture: new Set<Resource>(),
+        vertexArray: new Set<Resource>(),
+    }
+
+    function wrap<T extends Resource>(name: ResourceName, resource: T) {
+        sets[name].add(resource)
+        stats.resourceCounts[name] += 1
+        return {
+            ...resource,
+            destroy: () => {
+                resource.destroy()
+                sets[name].delete(resource)
+                stats.resourceCounts[name] -= 1
+            }
+        }
+    }
+
+    const shaderCache = createReferenceCache(
+        (props: ShaderProps) => JSON.stringify(props),
+        (props: ShaderProps) => wrap('shader', createShader(gl, props)),
+        (shader: Shader) => { shader.destroy() }
+    )
+
+    function getShader(type: ShaderType, source: string) {
+        return wrapCached(shaderCache.get({ type, source }))
+    }
+
+    const programCache = createReferenceCache(
+        (props: ProgramProps) => {
+            const array = [ props.shaderCode.id ]
+            Object.keys(props.defineValues).forEach(k => array.push(hashString(k), defineValueHash(props.defineValues[k].ref.value)))
+            return hashFnv32a(array).toString()
+        },
+        (props: ProgramProps) => wrap('program', createProgram(gl, state, extensions, getShader, props)),
+        (program: Program) => { program.destroy() }
+    )
+
+    return {
+        attribute: (array: ArrayType, itemSize: AttributeItemSize, divisor: number, usageHint?: UsageHint) => {
+            return wrap('attribute', createAttributeBuffer(gl, extensions, array, itemSize, divisor, usageHint))
+        },
+        elements: (array: ElementsType, usageHint?: UsageHint) => {
+            return wrap('elements', createElementsBuffer(gl, array, usageHint))
+        },
+        framebuffer: () => {
+            return wrap('framebuffer', createFramebuffer(gl))
+        },
+        program: (defineValues: DefineValues, shaderCode: ShaderCode, schema: RenderableSchema) => {
+            return wrapCached(programCache.get({ defineValues, shaderCode, schema }))
+        },
+        renderbuffer: (format: RenderbufferFormat, attachment: RenderbufferAttachment, width: number, height: number) => {
+            return wrap('renderbuffer', createRenderbuffer(gl, format, attachment, width, height))
+        },
+        shader: getShader,
+        texture: (kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter) => {
+            return wrap('texture', createTexture(gl, extensions, kind, format, type, filter))
+        },
+        vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => {
+            return wrap('vertexArray', createVertexArray(extensions, program, attributeBuffers, elementsBuffer))
+        },
+
+        reset: () => {
+            sets.attribute.forEach(r => r.reset())
+            sets.elements.forEach(r => r.reset())
+            sets.framebuffer.forEach(r => r.reset())
+            sets.renderbuffer.forEach(r => r.reset())
+            sets.shader.forEach(r => r.reset())
+            sets.program.forEach(r => r.reset())
+            sets.vertexArray.forEach(r => r.reset())
+            sets.texture.forEach(r => r.reset())
+        },
+        destroy: () => {
+            sets.attribute.forEach(r => r.destroy())
+            sets.elements.forEach(r => r.destroy())
+            sets.framebuffer.forEach(r => r.destroy())
+            sets.renderbuffer.forEach(r => r.destroy())
+            sets.shader.forEach(r => r.destroy())
+            sets.program.forEach(r => r.destroy())
+            sets.vertexArray.forEach(r => r.destroy())
+            sets.texture.forEach(r => r.destroy())
+
+            shaderCache.clear()
+            programCache.clear()
+        }
+    }
+}

+ 14 - 15
src/mol-gl/webgl/shader.ts

@@ -1,10 +1,9 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
 
 
-import { createReferenceCache, ReferenceCache } from '../../mol-util/reference-cache';
 import { idFactory } from '../../mol-util/id-factory';
 import { idFactory } from '../../mol-util/id-factory';
 import { GLRenderingContext } from './compat';
 import { GLRenderingContext } from './compat';
 import { isDebugMode } from '../../mol-util/debug';
 import { isDebugMode } from '../../mol-util/debug';
@@ -20,16 +19,16 @@ function addLineNumbers(source: string) {
 }
 }
 
 
 export type ShaderType = 'vert' | 'frag'
 export type ShaderType = 'vert' | 'frag'
-export interface ShaderProps { type: ShaderType, source: string }
+export type ShaderProps = { type: ShaderType, source: string }
 export interface Shader {
 export interface Shader {
     readonly id: number
     readonly id: number
     attach: (program: WebGLProgram) => void
     attach: (program: WebGLProgram) => void
+    reset: () => void
     destroy: () => void
     destroy: () => void
 }
 }
 
 
-function createShader(gl: GLRenderingContext, props: ShaderProps): Shader {
+function getShader(gl: GLRenderingContext, props: ShaderProps) {
     const { type, source } = props
     const { type, source } = props
-
     const shader = gl.createShader(type === 'vert' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER)
     const shader = gl.createShader(type === 'vert' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER)
     if (shader === null) {
     if (shader === null) {
         throw new Error(`Error creating ${type} shader`)
         throw new Error(`Error creating ${type} shader`)
@@ -43,23 +42,23 @@ function createShader(gl: GLRenderingContext, props: ShaderProps): Shader {
         throw new Error(`Error compiling ${type} shader`)
         throw new Error(`Error compiling ${type} shader`)
     }
     }
 
 
+    return shader
+}
+
+export function createShader(gl: GLRenderingContext, props: ShaderProps): Shader {
+    let shader = getShader(gl, props)
+
     return {
     return {
         id: getNextShaderId(),
         id: getNextShaderId(),
         attach: (program: WebGLProgram) => {
         attach: (program: WebGLProgram) => {
             gl.attachShader(program, shader)
             gl.attachShader(program, shader)
         },
         },
+
+        reset: () => {
+            shader = getShader(gl, props)
+        },
         destroy: () => {
         destroy: () => {
             gl.deleteShader(shader)
             gl.deleteShader(shader)
         }
         }
     }
     }
-}
-
-export type ShaderCache = ReferenceCache<Shader, ShaderProps>
-
-export function createShaderCache(gl: GLRenderingContext): ShaderCache {
-    return createReferenceCache(
-        (props: ShaderProps) => JSON.stringify(props),
-        (props: ShaderProps) => createShader(gl, props),
-        (shader: Shader) => { shader.destroy() }
-    )
 }
 }

+ 21 - 1
src/mol-gl/webgl/state.ts

@@ -58,10 +58,12 @@ export type WebGLState = {
     blendEquation: (mode: number) => void
     blendEquation: (mode: number) => void
     /** set the RGB blend equation and alpha blend equation separately, determines how a new pixel is combined with an existing */
     /** set the RGB blend equation and alpha blend equation separately, determines how a new pixel is combined with an existing */
     blendEquationSeparate: (modeRGB: number, modeAlpha: number) => void
     blendEquationSeparate: (modeRGB: number, modeAlpha: number) => void
+
+    reset: () => void
 }
 }
 
 
 export function createState(gl: GLRenderingContext): WebGLState {
 export function createState(gl: GLRenderingContext): WebGLState {
-    const enabledCapabilities: { [k: number]: boolean } = {}
+    let enabledCapabilities: { [k: number]: boolean } = {}
 
 
     let currentFrontFace = gl.getParameter(gl.FRONT_FACE)
     let currentFrontFace = gl.getParameter(gl.FRONT_FACE)
     let currentCullFace = gl.getParameter(gl.CULL_FACE_MODE)
     let currentCullFace = gl.getParameter(gl.CULL_FACE_MODE)
@@ -164,6 +166,24 @@ export function createState(gl: GLRenderingContext): WebGLState {
                 currentBlendEqRGB = modeRGB
                 currentBlendEqRGB = modeRGB
                 currentBlendEqAlpha = modeAlpha
                 currentBlendEqAlpha = modeAlpha
             }
             }
+        },
+
+        reset: () => {
+            enabledCapabilities = {}
+
+            currentFrontFace = gl.getParameter(gl.FRONT_FACE)
+            currentCullFace = gl.getParameter(gl.CULL_FACE_MODE)
+            currentDepthMask = gl.getParameter(gl.DEPTH_WRITEMASK)
+            currentColorMask = gl.getParameter(gl.COLOR_WRITEMASK)
+            currentClearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE)
+
+            currentBlendSrcRGB = gl.getParameter(gl.BLEND_SRC_RGB)
+            currentBlendDstRGB = gl.getParameter(gl.BLEND_DST_RGB)
+            currentBlendSrcAlpha = gl.getParameter(gl.BLEND_SRC_ALPHA)
+            currentBlendDstAlpha = gl.getParameter(gl.BLEND_DST_ALPHA)
+
+            currentBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB)
+            currentBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA)
         }
         }
     }
     }
 }
 }

+ 120 - 81
src/mol-gl/webgl/texture.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
@@ -10,8 +10,9 @@ import { ValueCell } from '../../mol-util';
 import { RenderableSchema } from '../renderable/schema';
 import { RenderableSchema } from '../renderable/schema';
 import { idFactory } from '../../mol-util/id-factory';
 import { idFactory } from '../../mol-util/id-factory';
 import { Framebuffer } from './framebuffer';
 import { Framebuffer } from './framebuffer';
-import { isWebGL2 } from './compat';
+import { isWebGL2, GLRenderingContext } from './compat';
 import { ValueOf } from '../../mol-util/type-helpers';
 import { ValueOf } from '../../mol-util/type-helpers';
+import { WebGLExtensions } from './extensions';
 
 
 const getNextTextureId = idFactory()
 const getNextTextureId = idFactory()
 
 
@@ -31,8 +32,7 @@ export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth'
 export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
 export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
 export type TextureFilter = 'nearest' | 'linear'
 export type TextureFilter = 'nearest' | 'linear'
 
 
-export function getTarget(ctx: WebGLContext, kind: TextureKind): number {
-    const { gl } = ctx
+export function getTarget(gl: GLRenderingContext, kind: TextureKind): number {
     switch (kind) {
     switch (kind) {
         case 'image-uint8': return gl.TEXTURE_2D
         case 'image-uint8': return gl.TEXTURE_2D
         case 'image-float32': return gl.TEXTURE_2D
         case 'image-float32': return gl.TEXTURE_2D
@@ -47,8 +47,7 @@ export function getTarget(ctx: WebGLContext, kind: TextureKind): number {
     throw new Error(`unknown texture kind '${kind}'`)
     throw new Error(`unknown texture kind '${kind}'`)
 }
 }
 
 
-export function getFormat(ctx: WebGLContext, format: TextureFormat, type: TextureType): number {
-    const { gl } = ctx
+export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: TextureType): number {
     switch (format) {
     switch (format) {
         case 'alpha':
         case 'alpha':
             if (isWebGL2(gl) && type === 'float') return gl.RED
             if (isWebGL2(gl) && type === 'float') return gl.RED
@@ -59,8 +58,7 @@ export function getFormat(ctx: WebGLContext, format: TextureFormat, type: Textur
     }
     }
 }
 }
 
 
-export function getInternalFormat(ctx: WebGLContext, format: TextureFormat, type: TextureType): number {
-    const { gl } = ctx
+export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat, type: TextureType): number {
     if (isWebGL2(gl)) {
     if (isWebGL2(gl)) {
         switch (format) {
         switch (format) {
             case 'alpha':
             case 'alpha':
@@ -82,11 +80,10 @@ export function getInternalFormat(ctx: WebGLContext, format: TextureFormat, type
                 return gl.DEPTH_COMPONENT16
                 return gl.DEPTH_COMPONENT16
         }
         }
     }
     }
-    return getFormat(ctx, format, type)
+    return getFormat(gl, format, type)
 }
 }
 
 
-export function getType(ctx: WebGLContext, type: TextureType): number {
-    const { gl } = ctx
+export function getType(gl: GLRenderingContext, type: TextureType): number {
     switch (type) {
     switch (type) {
         case 'ubyte': return gl.UNSIGNED_BYTE
         case 'ubyte': return gl.UNSIGNED_BYTE
         case 'ushort': return gl.UNSIGNED_SHORT
         case 'ushort': return gl.UNSIGNED_SHORT
@@ -94,16 +91,14 @@ export function getType(ctx: WebGLContext, type: TextureType): number {
     }
     }
 }
 }
 
 
-export function getFilter(ctx: WebGLContext, type: TextureFilter): number {
-    const { gl } = ctx
+export function getFilter(gl: GLRenderingContext, type: TextureFilter): number {
     switch (type) {
     switch (type) {
         case 'nearest': return gl.NEAREST
         case 'nearest': return gl.NEAREST
         case 'linear': return gl.LINEAR
         case 'linear': return gl.LINEAR
     }
     }
 }
 }
 
 
-export function getAttachment(ctx: WebGLContext, attachment: TextureAttachment): number {
-    const { gl, extensions } = ctx
+export function getAttachment(gl: GLRenderingContext, extensions: WebGLExtensions, attachment: TextureAttachment): number {
     switch (attachment) {
     switch (attachment) {
         case 'depth': return gl.DEPTH_ATTACHMENT
         case 'depth': return gl.DEPTH_ATTACHMENT
         case 'stencil': return gl.STENCIL_ATTACHMENT
         case 'stencil': return gl.STENCIL_ATTACHMENT
@@ -130,9 +125,9 @@ export interface Texture {
     readonly internalFormat: number
     readonly internalFormat: number
     readonly type: number
     readonly type: number
 
 
-    readonly width: number
-    readonly height: number
-    readonly depth: number
+    getWidth: () => number
+    getHeight: () => number
+    getDepth: () => number
 
 
     define: (width: number, height: number, depth?: number) => void
     define: (width: number, height: number, depth?: number) => void
     load: (image: TextureImage<any> | TextureVolume<any>) => void
     load: (image: TextureImage<any> | TextureVolume<any>) => void
@@ -141,6 +136,8 @@ export interface Texture {
     /** Use `layer` to attach a z-slice of a 3D texture */
     /** Use `layer` to attach a z-slice of a 3D texture */
     attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) => void
     attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) => void
     detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => void
     detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => void
+
+    reset: () => void
     destroy: () => void
     destroy: () => void
 }
 }
 
 
@@ -149,13 +146,23 @@ export type TextureId = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 1
 export type TextureValues = { [k: string]: ValueCell<TextureValueType> }
 export type TextureValues = { [k: string]: ValueCell<TextureValueType> }
 export type Textures = [string, Texture][]
 export type Textures = [string, Texture][]
 
 
-export function createTexture(ctx: WebGLContext, kind: TextureKind, _format: TextureFormat, _type: TextureType, _filter: TextureFilter): Texture {
-    const id = getNextTextureId()
-    const { gl, stats } = ctx
+type FramebufferAttachment = {
+    framebuffer: Framebuffer
+    attachment: TextureAttachment
+    layer?: number
+}
+
+function getTexture(gl: GLRenderingContext) {
     const texture = gl.createTexture()
     const texture = gl.createTexture()
     if (texture === null) {
     if (texture === null) {
         throw new Error('Could not create WebGL texture')
         throw new Error('Could not create WebGL texture')
     }
     }
+    return texture
+}
+// export type TextureProps = { kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter }
+export function createTexture(gl: GLRenderingContext, extensions: WebGLExtensions, kind: TextureKind, _format: TextureFormat, _type: TextureType, _filter: TextureFilter): Texture {
+    const id = getNextTextureId()
+    let texture = getTexture(gl)
 
 
     // check texture kind and type compatability
     // check texture kind and type compatability
     if (
     if (
@@ -166,24 +173,77 @@ export function createTexture(ctx: WebGLContext, kind: TextureKind, _format: Tex
         throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`)
         throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`)
     }
     }
 
 
-    const target = getTarget(ctx, kind)
-    const filter = getFilter(ctx, _filter)
-    const format = getFormat(ctx, _format, _type)
-    const internalFormat = getInternalFormat(ctx, _format, _type)
-    const type = getType(ctx, _type)
+    const target = getTarget(gl, kind)
+    const filter = getFilter(gl, _filter)
+    const format = getFormat(gl, _format, _type)
+    const internalFormat = getInternalFormat(gl, _format, _type)
+    const type = getType(gl, _type)
 
 
-    gl.bindTexture(target, texture)
-    gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, filter)
-    gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter)
-    // clamp-to-edge needed for non-power-of-two textures in webgl
-    gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
-    gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-    gl.bindTexture(target, null)
+    function init() {
+        gl.bindTexture(target, texture)
+        gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, filter)
+        gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter)
+        // clamp-to-edge needed for non-power-of-two textures in webgl
+        gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+        gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+        gl.bindTexture(target, null)
+    }
+    init()
 
 
-    let width = 0, height = 0, depth = 0
+    let fba: undefined | FramebufferAttachment = undefined
 
 
+    let width = 0, height = 0, depth = 0
+    let loadedData: undefined | TextureImage<any> | TextureVolume<any>
     let destroyed = false
     let destroyed = false
-    stats.textureCount += 1
+
+    function define(_width: number, _height: number, _depth?: number) {
+        width = _width, height = _height, depth = _depth || 0
+        gl.bindTexture(target, texture)
+        if (target === gl.TEXTURE_2D) {
+            gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, null)
+        } else if (isWebGL2(gl) && target === gl.TEXTURE_3D && depth !== undefined) {
+            gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, null)
+        } else {
+            throw new Error('unknown texture target')
+        }
+    }
+
+    function load(data: TextureImage<any> | TextureVolume<any>) {
+        gl.bindTexture(target, texture)
+        // unpack alignment of 1 since we use textures only for data
+        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
+        gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
+        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
+        if (target === gl.TEXTURE_2D) {
+            const { array, width: _width, height: _height } = data as TextureImage<any>
+            width = _width, height = _height;
+            gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, array)
+        } else if (isWebGL2(gl) && target === gl.TEXTURE_3D) {
+            const { array, width: _width, height: _height, depth: _depth } = data as TextureVolume<any>
+            width = _width, height = _height, depth = _depth
+            gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, array)
+        } else {
+            throw new Error('unknown texture target')
+        }
+        gl.bindTexture(target, null)
+        loadedData = data
+    }
+
+    function attachFramebuffer(framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) {
+        if (fba && fba.framebuffer === framebuffer && fba.attachment === attachment && fba.layer === layer) {
+            return
+        }
+        framebuffer.bind()
+        if (target === gl.TEXTURE_2D) {
+            gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), gl.TEXTURE_2D, texture, 0)
+        } else if (isWebGL2(gl) && target === gl.TEXTURE_3D) {
+            if (layer === undefined) throw new Error('need `layer` to attach 3D texture')
+            gl.framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), texture, 0, layer)
+        } else {
+            throw new Error('unknown texture target')
+        }
+        fba = { framebuffer, attachment, layer }
+    }
 
 
     return {
     return {
         id,
         id,
@@ -192,40 +252,12 @@ export function createTexture(ctx: WebGLContext, kind: TextureKind, _format: Tex
         internalFormat,
         internalFormat,
         type,
         type,
 
 
-        get width () { return width },
-        get height () { return height },
-        get depth () { return depth },
+        getWidth: () => width,
+        getHeight: () => height,
+        getDepth: () => depth,
 
 
-        define: (_width: number, _height: number, _depth?: number) => {
-            width = _width, height = _height, depth = _depth || 0
-            gl.bindTexture(target, texture)
-            if (target === gl.TEXTURE_2D) {
-                gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, null)
-            } else if (isWebGL2(gl) && target === gl.TEXTURE_3D && depth !== undefined) {
-                gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, null)
-            } else {
-                throw new Error('unknown texture target')
-            }
-        },
-        load: (data: TextureImage<any> | TextureVolume<any>) => {
-            gl.bindTexture(target, texture)
-            // unpack alignment of 1 since we use textures only for data
-            gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
-            gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
-            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
-            if (target === gl.TEXTURE_2D) {
-                const { array, width: _width, height: _height } = data as TextureImage<any>
-                width = _width, height = _height;
-                gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, array)
-            } else if (isWebGL2(gl) && target === gl.TEXTURE_3D) {
-                const { array, width: _width, height: _height, depth: _depth } = data as TextureVolume<any>
-                width = _width, height = _height, depth = _depth
-                gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, array)
-            } else {
-                throw new Error('unknown texture target')
-            }
-            gl.bindTexture(target, null)
-        },
+        define,
+        load,
         bind: (id: TextureId) => {
         bind: (id: TextureId) => {
             gl.activeTexture(gl.TEXTURE0 + id)
             gl.activeTexture(gl.TEXTURE0 + id)
             gl.bindTexture(target, texture)
             gl.bindTexture(target, texture)
@@ -234,37 +266,44 @@ export function createTexture(ctx: WebGLContext, kind: TextureKind, _format: Tex
             gl.activeTexture(gl.TEXTURE0 + id)
             gl.activeTexture(gl.TEXTURE0 + id)
             gl.bindTexture(target, null)
             gl.bindTexture(target, null)
         },
         },
-        attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) => {
+        attachFramebuffer,
+        detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => {
             framebuffer.bind()
             framebuffer.bind()
             if (target === gl.TEXTURE_2D) {
             if (target === gl.TEXTURE_2D) {
-                gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(ctx, attachment), gl.TEXTURE_2D, texture, 0)
+                gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), gl.TEXTURE_2D, null, 0)
             } else if (isWebGL2(gl) && target === gl.TEXTURE_3D) {
             } else if (isWebGL2(gl) && target === gl.TEXTURE_3D) {
-                if (layer === undefined) throw new Error('need `layer` to attach 3D texture')
-                gl.framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(ctx, attachment), texture, 0, layer)
+                gl.framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), null, 0, 0)
             } else {
             } else {
                 throw new Error('unknown texture target')
                 throw new Error('unknown texture target')
             }
             }
+            fba = undefined
         },
         },
-        detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => {
-            framebuffer.bind()
-            if (target === gl.TEXTURE_2D) {
-                gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(ctx, attachment), gl.TEXTURE_2D, null, 0)
-            } else if (isWebGL2(gl) && target === gl.TEXTURE_3D) {
-                gl.framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(ctx, attachment), null, 0, 0)
+        reset: () => {
+            texture = getTexture(gl)
+            init()
+
+            if (loadedData) {
+                load(loadedData)
             } else {
             } else {
-                throw new Error('unknown texture target')
+                define(width, height, depth)
+            }
+
+            if (fba) {
+                // TODO unclear why calling `attachFramebuffer` here does not work reliably after context loss
+                // e.g. it still needs to be called in `DrawPass` to work
+                fba = undefined
             }
             }
         },
         },
         destroy: () => {
         destroy: () => {
             if (destroyed) return
             if (destroyed) return
             gl.deleteTexture(texture)
             gl.deleteTexture(texture)
             destroyed = true
             destroyed = true
-            stats.textureCount -= 1
         }
         }
     }
     }
 }
 }
 
 
 export function createTextures(ctx: WebGLContext, schema: RenderableSchema, values: TextureValues) {
 export function createTextures(ctx: WebGLContext, schema: RenderableSchema, values: TextureValues) {
+    const { resources } = ctx
     const textures: Textures = []
     const textures: Textures = []
     Object.keys(schema).forEach(k => {
     Object.keys(schema).forEach(k => {
         const spec = schema[k]
         const spec = schema[k]
@@ -272,7 +311,7 @@ export function createTextures(ctx: WebGLContext, schema: RenderableSchema, valu
             if (spec.kind === 'texture') {
             if (spec.kind === 'texture') {
                 textures[textures.length] = [k, values[k].ref.value as Texture]
                 textures[textures.length] = [k, values[k].ref.value as Texture]
             } else {
             } else {
-                const texture = createTexture(ctx, spec.kind, spec.format, spec.dataType, spec.filter)
+                const texture = resources.texture(spec.kind, spec.format, spec.dataType, spec.filter)
                 texture.load(values[k].ref.value as TextureImage<any> | TextureVolume<any>)
                 texture.load(values[k].ref.value as TextureImage<any> | TextureVolume<any>)
                 textures[textures.length] = [k, texture]
                 textures[textures.length] = [k, texture]
             }
             }

+ 55 - 22
src/mol-gl/webgl/vertex-array.ts

@@ -1,42 +1,75 @@
 /**
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
 
 
-import { WebGLContext } from './context';
 import { Program } from './program';
 import { Program } from './program';
 import { ElementsBuffer, AttributeBuffers } from './buffer';
 import { ElementsBuffer, AttributeBuffers } from './buffer';
+import { WebGLExtensions } from './extensions';
+import { idFactory } from '../../mol-util/id-factory';
 
 
-export function createVertexArray(ctx: WebGLContext, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) {
-    const { vertexArrayObject } = ctx.extensions
-    let vertexArray: WebGLVertexArrayObject | null = null
-    if (vertexArrayObject) {
-        vertexArray = vertexArrayObject.createVertexArray()
-        if (vertexArray) {
-            updateVertexArray(ctx, vertexArray, program, attributeBuffers, elementsBuffer)
-            ctx.stats.vaoCount += 1
-        } else {
-            console.warn('Could not create WebGL vertex array')
-        }
+const getNextVertexArrayId = idFactory()
+
+function getVertexArray(extensions: WebGLExtensions): WebGLVertexArrayObject {
+    const { vertexArrayObject } = extensions
+    if (!vertexArrayObject) {
+        throw new Error('VertexArrayObject not supported')
+    }
+    const vertexArray = vertexArrayObject.createVertexArray()
+    if (!vertexArray) {
+        throw new Error('Could not create WebGL vertex array')
     }
     }
     return vertexArray
     return vertexArray
 }
 }
 
 
-export function updateVertexArray(ctx: WebGLContext, vertexArray: WebGLVertexArrayObject | null, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) {
-    const { vertexArrayObject } = ctx.extensions
-    if (vertexArrayObject && vertexArray) {
+function getVertexArrayObject(extensions: WebGLExtensions) {
+    const { vertexArrayObject } = extensions
+    if (vertexArrayObject === null) {
+        throw new Error('VertexArrayObject not supported')
+    }
+    return vertexArrayObject
+}
+
+export interface VertexArray {
+    readonly id: number
+
+    bind: () => void
+    update: () => void
+    reset: () => void
+    destroy: () => void
+}
+
+export function createVertexArray(extensions: WebGLExtensions, program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer): VertexArray {
+    const id = getNextVertexArrayId()
+    let vertexArray = getVertexArray(extensions)
+    let vertexArrayObject = getVertexArrayObject(extensions)
+
+    function update() {
         vertexArrayObject.bindVertexArray(vertexArray)
         vertexArrayObject.bindVertexArray(vertexArray)
         if (elementsBuffer) elementsBuffer.bind()
         if (elementsBuffer) elementsBuffer.bind()
         program.bindAttributes(attributeBuffers)
         program.bindAttributes(attributeBuffers)
         vertexArrayObject.bindVertexArray(null)
         vertexArrayObject.bindVertexArray(null)
     }
     }
-}
 
 
-export function deleteVertexArray(ctx: WebGLContext, vertexArray: WebGLVertexArrayObject | null) {
-    const { vertexArrayObject } = ctx.extensions
-    if (vertexArrayObject && vertexArray) {
-        vertexArrayObject.deleteVertexArray(vertexArray)
-        ctx.stats.vaoCount -= 1
+    update()
+    let destroyed = false
+
+    return {
+        id,
+        bind: () => {
+            vertexArrayObject.bindVertexArray(vertexArray)
+        },
+        update,
+        reset: () => {
+            vertexArray = getVertexArray(extensions)
+            vertexArrayObject = getVertexArrayObject(extensions)
+            update()
+        },
+        destroy: () => {
+            if (destroyed) return
+            vertexArrayObject.deleteVertexArray(vertexArray)
+            destroyed = true
+        }
     }
     }
 }
 }

+ 14 - 17
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 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>
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -13,7 +13,7 @@ import { Vec3, Tensor, Mat4, Vec2 } from '../../linear-algebra'
 import { ValueCell } from '../../../mol-util'
 import { ValueCell } from '../../../mol-util'
 import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable'
 import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable'
 import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
-import { createTexture, Texture } from '../../../mol-gl/webgl/texture';
+import { Texture } from '../../../mol-gl/webgl/texture';
 import { decodeFloatRGB } from '../../../mol-util/float-packing';
 import { decodeFloatRGB } from '../../../mol-util/float-packing';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
 import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
@@ -50,9 +50,6 @@ export const GaussianDensityShaderCode = ShaderCode(
     { standardDerivatives: false, fragDepth: false }
     { standardDerivatives: false, fragDepth: false }
 )
 )
 
 
-/** name for shared framebuffer used for gpu gaussian surface operations */
-const FramebufferName = 'gaussian-density'
-
 export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): DensityData {
 export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): DensityData {
     // always use texture2d when the gaussian density needs to be downloaded from the GPU,
     // always use texture2d when the gaussian density needs to be downloaded from the GPU,
     // it's faster than texture3d
     // it's faster than texture3d
@@ -111,24 +108,24 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
     const gridTexDim = Vec3.create(texDimX, texDimY, 0)
     const gridTexDim = Vec3.create(texDimX, texDimY, 0)
     const gridTexScale = Vec2.create(texDimX / powerOfTwoSize, texDimY / powerOfTwoSize)
     const gridTexScale = Vec2.create(texDimX / powerOfTwoSize, texDimY / powerOfTwoSize)
 
 
-    const minDistanceTexture = createTexture(webgl, 'image-float32', 'rgba', 'float', 'nearest')
+    const minDistanceTexture = webgl.resources.texture('image-float32', 'rgba', 'float', 'nearest')
     minDistanceTexture.define(powerOfTwoSize, powerOfTwoSize)
     minDistanceTexture.define(powerOfTwoSize, powerOfTwoSize)
 
 
     const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, gridTexDim, gridTexScale, smoothness, props.resolution)
     const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, gridTexDim, gridTexScale, smoothness, props.resolution)
 
 
     //
     //
 
 
-    const { gl, framebufferCache, state } = webgl
+    const { gl, resources, state } = webgl
     const { uCurrentSlice, uCurrentX, uCurrentY } = renderable.values
     const { uCurrentSlice, uCurrentX, uCurrentY } = renderable.values
 
 
-    const framebuffer = framebufferCache.get(FramebufferName).value
+    const framebuffer = resources.framebuffer()
     framebuffer.bind()
     framebuffer.bind()
     setRenderingDefaults(webgl)
     setRenderingDefaults(webgl)
 
 
     if (!texture) {
     if (!texture) {
-        texture = createTexture(webgl, 'image-float32', 'rgba', 'float', 'nearest')
+        texture = resources.texture('image-float32', 'rgba', 'float', 'nearest')
         texture.define(powerOfTwoSize, powerOfTwoSize)
         texture.define(powerOfTwoSize, powerOfTwoSize)
-    } else if (texture.width !== powerOfTwoSize || texture.height !== powerOfTwoSize) {
+    } else if (texture.getWidth() !== powerOfTwoSize || texture.getHeight() !== powerOfTwoSize) {
         texture.define(powerOfTwoSize, powerOfTwoSize)
         texture.define(powerOfTwoSize, powerOfTwoSize)
     }
     }
 
 
@@ -174,11 +171,12 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
 }
 }
 
 
 function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
 function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
+    const { gl, resources } = webgl
     const { smoothness } = props
     const { smoothness } = props
 
 
     const { drawCount, positions, radii, groups, scale, expandedBox, dim } = prepareGaussianDensityData(position, box, radius, props)
     const { drawCount, positions, radii, groups, scale, expandedBox, dim } = prepareGaussianDensityData(position, box, radius, props)
     const [ dx, dy, dz ] = dim
     const [ dx, dy, dz ] = dim
-    const minDistanceTexture = createTexture(webgl, 'volume-float32', 'rgba', 'float', 'nearest')
+    const minDistanceTexture = resources.texture('volume-float32', 'rgba', 'float', 'nearest')
     minDistanceTexture.define(dx, dy, dz)
     minDistanceTexture.define(dx, dy, dz)
 
 
     const gridTexScale = Vec2.create(1, 1)
     const gridTexScale = Vec2.create(1, 1)
@@ -187,15 +185,14 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
 
 
     //
     //
 
 
-    const { gl, framebufferCache } = webgl
     const { uCurrentSlice } = renderable.values
     const { uCurrentSlice } = renderable.values
 
 
-    const framebuffer = framebufferCache.get(FramebufferName).value
+    const framebuffer = resources.framebuffer()
     framebuffer.bind()
     framebuffer.bind()
     setRenderingDefaults(webgl)
     setRenderingDefaults(webgl)
     gl.viewport(0, 0, dx, dy)
     gl.viewport(0, 0, dx, dy)
 
 
-    if (!texture) texture = createTexture(webgl, 'volume-float32', 'rgba', 'float', 'nearest')
+    if (!texture) texture = resources.texture('volume-float32', 'rgba', 'float', 'nearest')
     texture.define(dx, dy, dz)
     texture.define(dx, dy, dz)
 
 
     function render(fbTex: Texture) {
     function render(fbTex: Texture) {
@@ -279,7 +276,7 @@ function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, po
         uResolution: ValueCell.create(resolution),
         uResolution: ValueCell.create(resolution),
         tMinDistanceTex: ValueCell.create(minDistanceTexture),
         tMinDistanceTex: ValueCell.create(minDistanceTexture),
 
 
-        dGridTexType: ValueCell.create(minDistanceTexture.depth > 0 ? '3d' : '2d'),
+        dGridTexType: ValueCell.create(minDistanceTexture.getDepth() > 0 ? '3d' : '2d'),
         dCalcType: ValueCell.create('minDistance'),
         dCalcType: ValueCell.create('minDistance'),
     }
     }
 
 
@@ -356,7 +353,7 @@ function getTexture2dSize(gridDim: Vec3) {
 
 
 export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {
 export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {
     // console.time('fieldFromTexture2d')
     // console.time('fieldFromTexture2d')
-    const { framebufferCache } = ctx
+    const { resources } = ctx
     const [ dx, dy, dz ] = dim
     const [ dx, dy, dz ] = dim
     // const { width, height } = texture
     // const { width, height } = texture
     const [ width, height ] = texDim
     const [ width, height ] = texDim
@@ -371,7 +368,7 @@ export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec
     // const image = new Uint8Array(width * height * 4)
     // const image = new Uint8Array(width * height * 4)
     const image = new Float32Array(width * height * 4)
     const image = new Float32Array(width * height * 4)
 
 
-    const framebuffer = framebufferCache.get(FramebufferName).value
+    const framebuffer = resources.framebuffer()
     framebuffer.bind()
     framebuffer.bind()
     texture.attachFramebuffer(framebuffer, 0)
     texture.attachFramebuffer(framebuffer, 0)
     ctx.readPixels(0, 0, width, height, image)
     ctx.readPixels(0, 0, width, height, image)

+ 2 - 3
src/mol-repr/volume/direct-volume.ts

@@ -11,7 +11,6 @@ import { VolumeData } from '../../mol-model/volume';
 import { RuntimeContext } from '../../mol-task';
 import { RuntimeContext } from '../../mol-task';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
 import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
-import { createTexture } from '../../mol-gl/webgl/texture';
 import { VisualContext } from '../visual';
 import { VisualContext } from '../visual';
 import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
 import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
 import { BaseGeometry } from '../../mol-geo/geometry/base';
 import { BaseGeometry } from '../../mol-geo/geometry/base';
@@ -95,7 +94,7 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v
     dim[0] += 1 // horizontal padding
     dim[0] += 1 // horizontal padding
     dim[0] += 1 // vertical padding
     dim[0] += 1 // vertical padding
 
 
-    const texture = directVolume ? directVolume.gridTexture.ref.value : createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
+    const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear')
     texture.load(textureImage)
     texture.load(textureImage)
 
 
     return DirectVolume.create(bbox, dim, transform, texture, directVolume)
     return DirectVolume.create(bbox, dim, transform, texture, directVolume)
@@ -135,7 +134,7 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
     // Mat4.invert(transform, transform)
     // Mat4.invert(transform, transform)
     const bbox = getBoundingBox(gridDimension, transform)
     const bbox = getBoundingBox(gridDimension, transform)
 
 
-    const texture = directVolume ? directVolume.gridTexture.ref.value : createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
+    const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear')
     texture.load(textureVolume)
     texture.load(textureVolume)
 
 
     return DirectVolume.create(bbox, gridDimension, transform, texture, directVolume)
     return DirectVolume.create(bbox, gridDimension, transform, texture, directVolume)

+ 2 - 2
src/mol-util/reference-cache.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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>
  */
  */
@@ -31,7 +31,7 @@ export interface ReferenceCache<T, P> {
     dispose: () => void
     dispose: () => void
 }
 }
 
 
-export function createReferenceCache<T, P, C>(hashFn: (props: P) => string, ctor: (props: P) => T, deleteFn: (v: T) => void): ReferenceCache<T, P> {
+export function createReferenceCache<T, P>(hashFn: (props: P) => string, ctor: (props: P) => T, deleteFn: (v: T) => void): ReferenceCache<T, P> {
     const map: Map<string, Reference<T>> = new Map()
     const map: Map<string, Reference<T>> = new Map()
 
 
     return {
     return {

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

@@ -102,7 +102,7 @@ async function init() {
     console.timeEnd('gpu mc vert')
     console.timeEnd('gpu mc vert')
     console.timeEnd('gpu mc')
     console.timeEnd('gpu mc')
 
 
-    console.log({ ...webgl.stats, programCount: webgl.programCache.count, shaderCount: webgl.shaderCache.count })
+    console.log({ ...webgl.stats, programCount: webgl.stats.resourceCounts.program, shaderCount: webgl.stats.resourceCounts.shader })
 
 
     const mcBoundingSphere = Sphere3D.fromBox3D(Sphere3D(), densityTextureData.bbox)
     const mcBoundingSphere = Sphere3D.fromBox3D(Sphere3D(), densityTextureData.bbox)
     const mcIsosurface = TextureMesh.create(gv.vertexCount, 1, gv.vertexGroupTexture, gv.normalTexture, mcBoundingSphere)
     const mcIsosurface = TextureMesh.create(gv.vertexCount, 1, gv.vertexGroupTexture, gv.normalTexture, mcBoundingSphere)