Alexander Rose 7 年 前
コミット
8c23b31c58

+ 5 - 0
package-lock.json

@@ -5343,6 +5343,11 @@
       "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
       "dev": true
     },
+    "immutable": {
+      "version": "3.8.2",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
+      "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
+    },
     "import-local": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz",

+ 45 - 16
src/apps/render-test/components/color-theme.tsx

@@ -13,14 +13,16 @@ import Select from 'material-ui/Select';
 
 import State, { ColorTheme as _ColorTheme } from '../state'
 import Observer from './observer';
+import { Color, ColorNames } from 'mol-util/color';
 
 interface ColorThemeState {
     loading: boolean
     name: _ColorTheme
+    value: Color
 }
 
 export default class ColorTheme extends Observer<{ state: State } & WithStyles, ColorThemeState> {
-    state = { loading: false, name: 'element-symbol' as _ColorTheme }
+    state = { loading: false, name: 'element-symbol' as _ColorTheme, value: 0xFF0000 }
 
     componentDidMount() {
         this.subscribe(this.props.state.loading, value => {
@@ -29,32 +31,59 @@ export default class ColorTheme extends Observer<{ state: State } & WithStyles,
         this.subscribe(this.props.state.colorTheme, value => {
             this.setState({ name: value });
          });
+         this.subscribe(this.props.state.colorValue, value => {
+            this.setState({ value: value });
+         });
     }
 
     handleNameChange = (event: React.ChangeEvent<any>) => {
         this.props.state.colorTheme.next(event.target.value)
     }
 
+    handleValueChange = (event: React.ChangeEvent<any>) => {
+        this.props.state.colorValue.next(event.target.value)
+    }
+
     render() {
         const { classes } = this.props;
 
-        const items = Object.keys(_ColorTheme).map((name, idx) => {
+        const colorThemeItems = Object.keys(_ColorTheme).map((name, idx) => {
             return <MenuItem key={idx} value={name}>{name}</MenuItem>
         })
 
-        return <FormControl className={classes.formControl}>
-            <InputLabel htmlFor='color-theme-name'>Color Theme</InputLabel>
-            <Select
-                className={classes.selectField}
-                value={this.state.name}
-                onChange={this.handleNameChange}
-                inputProps={{
-                    name: 'name',
-                    id: 'color-theme-name',
-                }}
-            >
-                {items}
-            </Select>
-        </FormControl>
+        const colorValueItems = Object.keys(ColorNames).map((name, idx) => {
+            return <MenuItem key={idx} value={(ColorNames as any)[name]}>{name}</MenuItem>
+        })
+
+        return <div>
+            <FormControl className={classes.formControl}>
+                <InputLabel htmlFor='color-theme-name'>Color Theme</InputLabel>
+                <Select
+                    className={classes.selectField}
+                    value={this.state.name}
+                    onChange={this.handleNameChange}
+                    inputProps={{
+                        name: 'name',
+                        id: 'color-theme-name',
+                    }}
+                >
+                    {colorThemeItems}
+                </Select>
+            </FormControl>
+            <FormControl className={classes.formControl}>
+                    <InputLabel htmlFor='uniform-color-value'>Color Value</InputLabel>
+                    <Select
+                        className={classes.selectField}
+                        value={this.state.value}
+                        onChange={this.handleValueChange}
+                        inputProps={{
+                            name: 'value',
+                            id: 'uniform-color-value',
+                        }}
+                    >
+                        {colorValueItems}
+                    </Select>
+            </FormControl>
+        </div>
     }
 }

+ 18 - 7
src/apps/render-test/state.ts

@@ -22,6 +22,7 @@ import { Symmetry, Structure } from 'mol-model/structure'
 // import mcubes from './utils/mcubes'
 import { getStructuresFromPdbId, getStructuresFromFile, log } from './utils'
 import { StructureRepresentation } from 'mol-geo/representation/structure';
+import { Color } from 'mol-util/color';
 // import Cylinder from 'mol-geo/primitive/cylinder';
 
 
@@ -29,7 +30,8 @@ export const ColorTheme = {
     'atom-index': {},
     'chain-id': {},
     'element-symbol': {},
-    'instance-index': {}
+    'instance-index': {},
+    'uniform': {}
 }
 export type ColorTheme = keyof typeof ColorTheme
 
@@ -39,7 +41,8 @@ export default class State {
     initialized = new BehaviorSubject<boolean>(false)
     loading = new BehaviorSubject<boolean>(false)
 
-    colorTheme = new BehaviorSubject<ColorTheme>('atom-index')
+    colorTheme = new BehaviorSubject<ColorTheme>('uniform')
+    colorValue = new BehaviorSubject<Color>(0xFF0000)
     detail = new BehaviorSubject<number>(2)
 
     pointVisibility = new BehaviorSubject<boolean>(true)
@@ -50,6 +53,7 @@ export default class State {
 
     constructor() {
         this.colorTheme.subscribe(() => this.update())
+        this.colorValue.subscribe(() => this.update())
         this.detail.subscribe(() => this.update())
 
         this.pointVisibility.subscribe(() => this.updateVisibility())
@@ -57,16 +61,22 @@ export default class State {
     }
 
     getSpacefillProps (): SpacefillProps {
+        const colorThemeName = this.colorTheme.getValue()
         return {
             detail: this.detail.getValue(),
-            colorTheme: { name: this.colorTheme.getValue() },
+            colorTheme: colorThemeName === 'uniform' ?
+                { name: colorThemeName, value: this.colorValue.getValue() } :
+                { name: colorThemeName }
         }
     }
 
     getPointProps (): PointProps {
+        const colorThemeName = this.colorTheme.getValue()
         return {
-            colorTheme: { name: this.colorTheme.getValue() },
-            sizeTheme: { name: 'uniform', value: 0.1 }
+            sizeTheme: { name: 'uniform', value: 0.1 },
+            colorTheme: colorThemeName === 'uniform' ?
+                { name: colorThemeName, value: this.colorValue.getValue() } :
+                { name: colorThemeName }
         }
     }
 
@@ -88,8 +98,8 @@ export default class State {
         viewer.add(this.pointRepr)
 
         this.spacefillRepr = StructureRepresentation(Spacefill)
-        await Run(this.spacefillRepr.create(struct, this.getSpacefillProps()), log, 100)
-        viewer.add(this.spacefillRepr)
+        // await Run(this.spacefillRepr.create(struct, this.getSpacefillProps()), log, 100)
+        // viewer.add(this.spacefillRepr)
 
         this.updateVisibility()
         viewer.requestDraw()
@@ -121,6 +131,7 @@ export default class State {
         await Run(this.pointRepr.update(this.getPointProps()), log, 100)
         this.viewer.add(this.spacefillRepr)
         this.viewer.add(this.pointRepr)
+        this.viewer.update()
         this.viewer.requestDraw()
         console.log(this.viewer.stats)
     }

+ 1 - 0
src/helpers.d.ts

@@ -12,4 +12,5 @@ declare module Helpers {
     export type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array
     export type NumberArray = TypedArray | number[]
     export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[]
+    export type ValueOf<T> = T[keyof T]
 }

+ 43 - 8
src/mol-geo/representation/structure/point.ts

@@ -5,18 +5,17 @@
  */
 
 import { ValueCell } from 'mol-util/value-cell'
-
 import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/scene'
-
 import { OrderedSet } from 'mol-data/int'
 import { Unit, ElementGroup } from 'mol-model/structure';
-import { RepresentationProps, UnitsRepresentation } from './index';
 import { Task } from 'mol-task'
 import { fillSerial } from 'mol-gl/renderable/util';
 
+import { RepresentationProps, UnitsRepresentation } from './index';
 import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { createTransforms, createColors, createSizes } from './utils';
+import { deepEqual } from 'mol-util';
 
 export const DefaultPointProps = {
     colorTheme: { name: 'instance-index' } as ColorTheme,
@@ -41,14 +40,22 @@ export function createPointVertices(unit: Unit, elementGroup: ElementGroup) {
 export default function Point(): UnitsRepresentation<PointProps> {
     const renderObjects: RenderObject[] = []
     let points: PointRenderObject
+    let curProps = DefaultPointProps
+
+    let _units: ReadonlyArray<Unit>
+    let _elementGroup: ElementGroup
 
     return {
         renderObjects,
         create(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: PointProps = {}) {
             return Task.create('Point.create', async ctx => {
                 renderObjects.length = 0 // clear
+                curProps = { ...DefaultPointProps, ...props }
 
-                const { colorTheme, sizeTheme } = { ...DefaultPointProps, ...props }
+                _units = units
+                _elementGroup = elementGroup
+
+                const { colorTheme, sizeTheme } = curProps
                 const elementCount = OrderedSet.size(elementGroup.elements)
                 const unitCount = units.length
 
@@ -76,8 +83,8 @@ export default function Point(): UnitsRepresentation<PointProps> {
 
                     position: ValueCell.create(vertices),
                     id: ValueCell.create(fillSerial(new Float32Array(elementCount))),
-                    size,
-                    color,
+                    size: ValueCell.create(size),
+                    color: ValueCell.create(color),
                     transform: ValueCell.create(transforms),
 
                     instanceCount: unitCount,
@@ -91,9 +98,37 @@ export default function Point(): UnitsRepresentation<PointProps> {
         },
         update(props: RepresentationProps) {
             return Task.create('Point.update', async ctx => {
-                if (!points) return false
+                if (!points || !_units || !_elementGroup) return false
+
+                const newProps = { ...curProps, ...props }
+                if (deepEqual(curProps, newProps)) {
+                    console.log('props identical, nothing to change')
+                    return true
+                }
+
+                const elementCount = OrderedSet.size(_elementGroup.elements)
+                // const unitCount = _units.length
+
+                const vertexMap = VertexMap.create(
+                    elementCount,
+                    elementCount + 1,
+                    fillSerial(new Uint32Array(elementCount)),
+                    fillSerial(new Uint32Array(elementCount + 1))
+                )
+
+                if (!deepEqual(curProps.colorTheme, newProps.colorTheme)) {
+                    console.log('colorTheme changed', curProps.colorTheme, newProps.colorTheme)
+                    await ctx.update('Computing point colors');
+                    const color = createColors(_units, _elementGroup, vertexMap, newProps.colorTheme)
+                    ValueCell.update(points.props.color, color)
+                }
+
+                if (!deepEqual(curProps.sizeTheme, newProps.sizeTheme)) {
+                    console.log('sizeTheme changed', curProps.sizeTheme, newProps.sizeTheme)
+                }
 
-                return false
+                curProps = newProps
+                return true
             })
         }
     }

+ 1 - 1
src/mol-geo/representation/structure/spacefill.ts

@@ -86,7 +86,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
 
                     position: mesh.vertexBuffer,
                     normal: mesh.normalBuffer as ValueCell<Float32Array>,
-                    color: color,
+                    color: ValueCell.create(color),
                     id: mesh.idBuffer as ValueCell<Float32Array>,
                     transform: ValueCell.create(transforms),
                     index: mesh.indexBuffer,

+ 2 - 2
src/mol-gl/_spec/renderer.spec.ts

@@ -39,8 +39,8 @@ function createRenderer(gl: WebGLRenderingContext) {
 function createPoints() {
     const position = ValueCell.create(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0]))
     const id = ValueCell.create(fillSerial(new Float32Array(3)))
-    const color = createUniformColor({ value: 0xFF0000 })
-    const size = createUniformSize({ value: 1 })
+    const color = ValueCell.create(createUniformColor({ value: 0xFF0000 }))
+    const size = ValueCell.create(createUniformSize({ value: 1 }))
 
     const transform = ValueCell.create(new Float32Array(16))
     const m4 = Mat4.identity()

+ 61 - 15
src/mol-gl/renderable.ts

@@ -4,24 +4,70 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import REGL = require('regl');
-import Attribute from './attribute'
 import PointRenderable from './renderable/point'
 import MeshRenderable from './renderable/mesh'
+import { Shaders } from './shaders';
+import { UniformDefs, UniformValues } from './webgl/uniform';
+import { AttributeDefs, AttributeValues, createAttributeBuffers } from './webgl/buffer';
+import { TextureDefs, TextureValues, createTextures } from './webgl/texture';
+import { Context } from './webgl/context';
+import { createProgram } from './webgl/program';
 
-export type AttributesMutator<T extends AttributesData> = (data: T) => (boolean | void)
-export type AttributesData = { [k: string]: Helpers.TypedArray }
-export type Attributes<T extends AttributesData> = { [K in keyof T]: Attribute<T[K]> }
-export type AttributesBuffers<T extends AttributesData> = { [K in keyof T]: REGL.AttributeConfig }
-
-export interface Renderable {
-    draw(): void
-    dispose(): void
-    stats: REGL.CommandStats
-    name: string
-    // isPicking: () => boolean
-    // isVisible: () => boolean
-    // isTransparent: () => boolean
+export type RenderableProps = {
+    shaders: Shaders
+    uniform: UniformDefs
+    attribute: AttributeDefs
+    texture: TextureDefs
+}
+
+export type RenderableState<T extends RenderableProps> = {
+    uniform: UniformValues<T['uniform']>
+    attribute: AttributeValues<T['attribute']>
+    texture: TextureValues<T['texture']>
+
+    drawCount: number
+}
+
+export interface Renderable<T extends RenderableProps> {
+    readonly hash: string
+    readonly programId: number
+
+    loadAttributes: (state: Partial<AttributeValues<T['attribute']>>) => void
+
+    draw: () => void
+    dispose: () => void
+}
+
+export function createRenderable<T extends RenderableProps>(ctx: Context, props: T, state: RenderableState<T>): Renderable<T> {
+    const { gl } = ctx
+    const hash = JSON.stringify(props)
+    const program = createProgram(ctx, props.shaders, props.uniform, props.attribute, props.texture)
+    const attributeBuffers = createAttributeBuffers(ctx, props.attribute, state.attribute)
+    const textures = createTextures(gl, props.texture, state.texture)
+
+    function loadAttributes(state: Partial<AttributeValues<T['attribute']>>) {
+        Object.keys(state).forEach(k => {
+            const value = state[k]
+            if (value !== undefined) attributeBuffers[k].updateData(value)
+        })
+    }
+
+    return {
+        hash,
+        programId: program.id,
+
+        loadAttributes,
+
+        draw: () => {
+            program.setUniforms(state.uniform)
+            program.bindAttributes(attributeBuffers)
+            program.bindTextures(textures)
+            gl.drawArrays(gl.TRIANGLES, 0, state.drawCount);
+        },
+        dispose: () => {
+            // TODO
+        }
+    }
 }
 
 export { PointRenderable, MeshRenderable }

+ 4 - 1
src/mol-gl/renderable/mesh.ts

@@ -22,7 +22,7 @@ namespace Mesh {
         normal?: ValueCell<Float32Array>
         id: ValueCell<Float32Array>
 
-        color: ColorData
+        color: ValueCell<ColorData>
         transform: ValueCell<Float32Array>
         index: ValueCell<Uint32Array>
 
@@ -58,6 +58,9 @@ namespace Mesh {
                 return command.stats
             },
             name: 'mesh',
+            update: (newProps: Data) => {
+                console.log('Updating mesh renderable')
+            },
             dispose: () => {
                 destroyAttributes(attributes)
                 destroyUniforms(uniforms)

+ 12 - 3
src/mol-gl/renderable/point.ts

@@ -8,7 +8,7 @@ import REGL = require('regl');
 import { ValueCell } from 'mol-util/value-cell'
 
 import { Renderable } from '../renderable'
-import { createBaseDefines, createBaseUniforms, createBaseAttributes, destroyUniforms, destroyAttributes } from './util'
+import { createBaseDefines, createBaseUniforms, createBaseAttributes, destroyUniforms, destroyAttributes, updateBaseUniforms } from './util'
 import { PointShaders, addDefines } from '../shaders'
 import { ColorData } from 'mol-geo/util/color-data';
 import { SizeData } from 'mol-geo/util/size-data';
@@ -22,8 +22,8 @@ namespace Point {
         position: ValueCell<Float32Array>
         id: ValueCell<Float32Array>
 
-        size: SizeData
-        color: ColorData
+        size: ValueCell<SizeData>
+        color: ValueCell<ColorData>
         transform: ValueCell<Float32Array>
 
         instanceCount: number
@@ -34,6 +34,8 @@ namespace Point {
     }
 
     export function create(regl: REGL.Regl, props: Data): Renderable {
+        let curProps = props
+
         const defines = createBaseDefines(regl, props)
         const uniforms = createBaseUniforms(regl, props)
         const attributes = createBaseAttributes(regl, props)
@@ -54,6 +56,13 @@ namespace Point {
                 return command.stats
             },
             name: 'point',
+            update: (newProps: Data) => {
+                console.log('Updating point renderable')
+                // const newUniforms = updateBaseUniforms(regl, uniforms, newProps, curProps)
+                const newUniforms = { ...uniforms, color: 0xFF4411 }
+                console.log(newUniforms)
+                // command({ uniforms: newUniforms })
+            },
             dispose: () => {
                 destroyAttributes(attributes)
                 destroyUniforms(uniforms)

+ 73 - 14
src/mol-gl/renderable/util.ts

@@ -12,6 +12,9 @@ import { SizeData } from 'mol-geo/util/size-data';
 import { Attributes, AttributesData, AttributesBuffers } from '../renderable'
 import Attribute from '../attribute'
 import { ShaderDefines } from '../shaders';
+import { UniformDefs, UniformValues } from '../webgl/uniform';
+import { AttributeDefs } from '../webgl/buffer';
+
 
 export type ReglUniforms = { [k: string]: REGL.Uniform | REGL.Texture }
 export type ReglAttributes = { [k: string]: REGL.AttributeConfig }
@@ -67,9 +70,9 @@ export function createColorUniforms (regl: REGL.Regl, color: ValueCell<Texture>)
     }
 }
 
-export function getColorDefines(color: ColorData) {
+export function getColorDefines(color: ValueCell<ColorData>) {
     const defines: ShaderDefines = {}
-    switch (color.type) {
+    switch (color.ref.value.type) {
         case 'uniform': defines.UNIFORM_COLOR = ''; break;
         case 'attribute': defines.ATTRIBUTE_COLOR = ''; break;
         case 'element': defines.ELEMENT_COLOR = ''; break;
@@ -79,9 +82,9 @@ export function getColorDefines(color: ColorData) {
     return defines
 }
 
-export function getSizeDefines(size: SizeData) {
+export function getSizeDefines(size: ValueCell<SizeData>) {
     const defines: ShaderDefines = {}
-    switch (size.type) {
+    switch (size.ref.value.type) {
         case 'uniform': defines.UNIFORM_SIZE = ''; break;
         case 'attribute': defines.ATTRIBUTE_SIZE = ''; break;
     }
@@ -113,26 +116,80 @@ interface BaseProps {
     id: ValueCell<Float32Array>
     transform: ValueCell<Float32Array>
 
-    size?: SizeData
-    color: ColorData
+    size?: ValueCell<SizeData>
+    color: ValueCell<ColorData>
+}
+
+export function getBaseUniformDefs(props: BaseProps) {
+    const uniformDefs: UniformDefs = {
+        model: 'm4',
+        view: 'm4',
+        projection: 'm4',
+
+        objectId: 'i',
+        instanceCount: 'i',
+        elementCount: 'i'
+    }
+    const color = props.color.ref.value
+    if (color.type === 'instance' || color.type === 'element' || color.type === 'element-instance') {
+        uniformDefs.colorTexSize = 'v2'
+        uniformDefs.colorTex = 't2'
+    } else if (color.type === 'uniform') {
+        uniformDefs.color = 'v3'
+    }
+    const size = props.size ? props.size.ref.value : undefined
+    if (size && size.type === 'uniform') {
+        uniformDefs.size = 'f'
+    }
+    return uniformDefs
 }
 
-export function createBaseUniforms(regl: REGL.Regl, props: BaseProps): ReglUniforms {
-    const { objectId, instanceCount, elementCount, color, size } = props
-    const uniforms = { objectId, instanceCount, elementCount }
+export function getBaseUniformValues(props: BaseProps) {
+    const { objectId, instanceCount, elementCount } = props
+    const uniformValues: UniformValues<any> = {
+        objectId, instanceCount, elementCount
+    }
+    const color = props.color.ref.value
+    if (color.type === 'instance' || color.type === 'element' || color.type === 'element-instance') {
+        const { width, height } = color.value.ref.value
+        uniformValues.colorTex = new ImageData(new Uint8ClampedArray(color.value.ref.value), width, height)
+        uniformValues.colorTexSize = [ width, height ]
+    } else if (color.type === 'uniform') {
+        uniformValues.color = color.value
+    }
+    const size = props.size ? props.size.ref.value : undefined
+    if (size && size.type === 'uniform') {
+        uniformValues.size = size.value
+    }
+    return uniformValues
+}
+
+export function getBaseAttributeDefs(props: BaseProps) {
+    const attributeDefs: AttributeDefs = {
+        instanceId: { kind: 'float32', itemSize: 1, divisor: 1 },
+        position: { kind: 'float32', itemSize: 1, divisor: 0 },
+        elementId: { kind: 'float32', itemSize: 1, divisor: 0 },
+        transformColumn0: { kind: 'float32', itemSize: 4, divisor: 1 },
+        transformColumn1: { kind: 'float32', itemSize: 4, divisor: 1 },
+        transformColumn2: { kind: 'float32', itemSize: 4, divisor: 1 },
+        transformColumn3: { kind: 'float32', itemSize: 4, divisor: 1 },
+    }
+    const color = props.color.ref.value
     if (color.type === 'instance' || color.type === 'element' || color.type === 'element-instance') {
-        Object.assign(uniforms, createColorUniforms(regl, color.value))
+        uniformDefs.colorTexSize = 'v2'
+        uniformDefs.colorTex = 't2'
     } else if (color.type === 'uniform') {
-        Object.assign(uniforms, { color: color.value })
+        uniformDefs.color = 'v3'
     }
+    const size = props.size ? props.size.ref.value : undefined
     if (size && size.type === 'uniform') {
-        Object.assign(uniforms, { size: size.value })
+        uniformDefs.size = 'f'
     }
-    return uniforms
+    return attributeDefs
 }
 
 export function createBaseAttributes(regl: REGL.Regl, props: BaseProps): ReglAttributes {
-    const { instanceCount, positionCount, position, color, id, normal, size, transform } = props
+    const { instanceCount, positionCount, position, id, normal, transform } = props
     const instanceId = ValueCell.create(fillSerial(new Float32Array(instanceCount)))
     const attributes = getBuffers({
         instanceId: Attribute.create(regl, instanceId, instanceCount, { size: 1, divisor: 1 }),
@@ -143,9 +200,11 @@ export function createBaseAttributes(regl: REGL.Regl, props: BaseProps): ReglAtt
     if (normal) {
         attributes.normal = Attribute.create(regl, normal as any, positionCount, { size: 3 }).buffer
     }
+    const color = props.color.ref.value
     if (color.type === 'attribute') {
         attributes.color = Attribute.create(regl, color.value, positionCount, { size: 3 }).buffer
     }
+    const size = props.size ? props.size.ref.value : undefined
     if (size && size.type === 'attribute') {
         attributes.size = Attribute.create(regl, size.value, positionCount, { size: 1 }).buffer
     }

+ 88 - 42
src/mol-gl/renderer.ts

@@ -8,8 +8,10 @@ import { Vec3, Mat4 } from 'mol-math/linear-algebra'
 import { Viewport } from 'mol-view/camera/util';
 import { Camera } from 'mol-view/camera/base';
 
-import * as glContext from './context'
 import Scene, { RenderObject } from './scene';
+import { createContext } from './webgl/context';
+import { SimpleShaders } from './shaders';
+import { createRenderable, RenderableProps, RenderableState } from './renderable';
 
 export interface RendererStats {
     elementsCount: number
@@ -22,6 +24,7 @@ export interface RendererStats {
 interface Renderer {
     add: (o: RenderObject) => void
     remove: (o: RenderObject) => void
+    update: () => void
     clear: () => void
     draw: () => void
 
@@ -45,69 +48,112 @@ function getPixelRatio() {
 
 namespace Renderer {
     export function create(gl: WebGLRenderingContext, camera: Camera): Renderer {
-        const regl = glContext.create({ gl, extensions, optionalExtensions, profile: false })
-        const scene = Scene.create(regl)
 
-        const baseContext = regl({
-            context: {
+        const ctx = createContext(gl)
+
+        const renderableProps: RenderableProps = {
+            shaders: SimpleShaders,
+            uniform: {
+                model: 'm4',
+                view: 'm4',
+                projection: 'm4'
+            },
+            attribute: {
+                position: { kind: 'float32', itemSize: 3, divisor: 0 }
+            },
+            texture: {
+
+            }
+        }
+
+        const renderableState: RenderableState<typeof renderableProps> = {
+            uniform: {
                 model: Mat4.identity(),
-                transform: Mat4.identity(),
                 view: camera.view,
-                projection: camera.projection,
+                projection: camera.projection
             },
-            uniforms: {
-                pixelRatio: getPixelRatio(),
-                viewportHeight: regl.context('viewportHeight'),
-
-                model: regl.context('model' as any),
-                transform: regl.context('transform' as any),
-                view: regl.context('view' as any),
-                projection: regl.context('projection' as any),
-
-                'light.position': Vec3.create(0, 0, -100),
-                'light.color': Vec3.create(1.0, 1.0, 1.0),
-                'light.ambient': Vec3.create(0.5, 0.5, 0.5),
-                'light.falloff': 0,
-                'light.radius': 500
-            }
-        })
+            attribute: {
+                position: new Float32Array([0, 0, 0, 10, 10, 0, -10, 0, 0])
+            },
+            texture: {
+
+            },
+
+            drawCount: 3
+        }
+
+        const renderable = createRenderable(ctx, renderableProps, renderableState)
+
+        // const regl = glContext.create({ gl, extensions, optionalExtensions, profile: false })
+        // const scene = Scene.create(regl)
+
+        // const baseContext = regl({
+        //     context: {
+        //         model: Mat4.identity(),
+        //         transform: Mat4.identity(),
+        //         view: camera.view,
+        //         projection: camera.projection,
+        //     },
+        //     uniforms: {
+        //         pixelRatio: getPixelRatio(),
+        //         viewportHeight: regl.context('viewportHeight'),
+
+        //         model: regl.context('model' as any),
+        //         transform: regl.context('transform' as any),
+        //         view: regl.context('view' as any),
+        //         projection: regl.context('projection' as any),
+
+        //         'light.position': Vec3.create(0, 0, -100),
+        //         'light.color': Vec3.create(1.0, 1.0, 1.0),
+        //         'light.ambient': Vec3.create(0.5, 0.5, 0.5),
+        //         'light.falloff': 0,
+        //         'light.radius': 500
+        //     }
+        // })
 
         const draw = () => {
-            regl.poll() // updates timers and viewport
-            baseContext(state => {
-                regl.clear({ color: [0, 0, 0, 1] })
-                // TODO painters sort, filter visible, filter picking, visibility culling?
-                scene.forEach((r, o) => {
-                    if (o.visible) r.draw()
-                })
-            })
+            // regl.poll() // updates timers and viewport
+            // baseContext(state => {
+            //     regl.clear({ color: [0, 0, 0, 1] })
+            //     // TODO painters sort, filter visible, filter picking, visibility culling?
+            //     scene.forEach((r, o) => {
+            //         if (o.visible) r.draw()
+            //     })
+            // })
+
+            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+            renderable.draw()
         }
 
         return {
             add: (o: RenderObject) => {
-                scene.add(o)
+                // scene.add(o)
             },
             remove: (o: RenderObject) => {
-                scene.remove(o)
+                // scene.remove(o)
+            },
+            update: () => {
+                // scene.forEach((r, o) => r.update(o))
             },
             clear: () => {
-                scene.clear()
+                // scene.clear()
             },
             draw,
             setViewport: (viewport: Viewport) => {
-                regl({ viewport })
+                gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height)
+                // regl({ viewport })
             },
             get stats() {
                 return {
-                    elementsCount: regl.stats.elementsCount,
-                    bufferCount: regl.stats.bufferCount,
-                    textureCount: regl.stats.textureCount,
-                    shaderCount: regl.stats.shaderCount,
-                    renderableCount: scene.count
-                }
+                    // elementsCount: regl.stats.elementsCount,
+                    // bufferCount: regl.stats.bufferCount,
+                    // textureCount: regl.stats.textureCount,
+                    // shaderCount: regl.stats.shaderCount,
+                    // renderableCount: scene.count
+                } as any
             },
             dispose: () => {
-                regl.destroy()
+                // regl.destroy()
             }
         }
     }

+ 9 - 9
src/mol-gl/scene.ts

@@ -4,10 +4,10 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import REGL = require('regl');
 import { PointRenderable, MeshRenderable, Renderable } from './renderable'
 
 import { ValueCell } from 'mol-util';
+import { Context } from './webgl/context';
 
 let _renderObjectId = 0;
 function getNextId() {
@@ -28,10 +28,10 @@ export function createPointRenderObject(props: PointRenderable.Data): PointRende
     return { id: getNextId(), type: 'point', props, visible: true }
 }
 
-export function createRenderable(regl: REGL.Regl, o: RenderObject) {
+export function createRenderable(ctx: Context, o: RenderObject) {
     switch (o.type) {
-        case 'mesh': return MeshRenderable.create(regl, o.props)
-        case 'point': return PointRenderable.create(regl, o.props)
+        case 'mesh': return MeshRenderable.create(ctx, o.props)
+        case 'point': return PointRenderable.create(ctx, o.props)
     }
 }
 
@@ -39,18 +39,18 @@ interface Scene {
     add: (o: RenderObject) => void
     remove: (o: RenderObject) => void
     clear: () => void
-    forEach: (callbackFn: (value: Renderable, key: RenderObject) => void) => void
+    forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void
     count: number
 }
 
 namespace Scene {
-    export function create(regl: REGL.Regl): Scene {
-        const renderableMap = new Map<RenderObject, Renderable>()
+    export function create(ctx: Context): Scene {
+        const renderableMap = new Map<RenderObject, Renderable<any>>()
 
         return {
             add: (o: RenderObject) => {
                 if (!renderableMap.has(o)) {
-                    renderableMap.set(o, createRenderable(regl, o))
+                    renderableMap.set(o, createRenderable(ctx, o))
                 } else {
                     console.warn(`RenderObject with id '${o.id}' already present`)
                 }
@@ -66,7 +66,7 @@ namespace Scene {
                 renderableMap.forEach(renderable => renderable.dispose())
                 renderableMap.clear()
             },
-            forEach: (callbackFn: (value: Renderable, key: RenderObject) => void) => {
+            forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => {
                 renderableMap.forEach(callbackFn)
             },
             get count() {

+ 5 - 0
src/mol-gl/shader/simple.frag

@@ -0,0 +1,5 @@
+precision highp float;
+
+void main(void) {
+    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+}

+ 8 - 0
src/mol-gl/shader/simple.vert

@@ -0,0 +1,8 @@
+precision highp float;
+
+attribute vec3 position;
+uniform mat4 model, view, projection;
+
+void main(void) {
+    gl_Position = projection * view * model * vec4(position, 1.0);
+}

+ 6 - 1
src/mol-gl/shaders.ts

@@ -5,7 +5,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-interface Shaders {
+export interface Shaders {
     vert: string
     frag: string
 }
@@ -20,6 +20,11 @@ export const MeshShaders = {
     frag: require('mol-gl/shader/mesh.frag')
 }
 
+export const SimpleShaders = {
+    vert: require('mol-gl/shader/simple.vert'),
+    frag: require('mol-gl/shader/simple.frag')
+}
+
 type ShaderDefine = (
     'UNIFORM_COLOR' | 'ATTRIBUTE_COLOR' | 'INSTANCE_COLOR' | 'ELEMENT_COLOR' | 'ELEMENT_INSTANCE_COLOR' |
     'UNIFORM_SIZE' | 'ATTRIBUTE_SIZE' |

+ 0 - 67
src/mol-gl/stats.ts

@@ -1,67 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { Renderable } from './renderable';
-
-export default function createStats (renderables: Renderable[]) {
-    const prevGpuTimes: number[] = []
-    for (let i = 0; i < renderables.length; i++) {
-        prevGpuTimes[i] = 0
-    }
-
-    let frameTimeCount = 0
-    let totalTime = 1.1
-    let N = 50
-
-    const totalFrameTime: number[] = []
-    const avgFrameTime: number[] = []
-    for (let i = 0; i < renderables.length; ++i) {
-        totalFrameTime[i] = 0.0
-        avgFrameTime[i] = 0.0
-    }
-
-    return {
-        add: (renderable: Renderable) => {
-            renderables.push(renderable)
-            prevGpuTimes.push(0)
-            totalFrameTime.push(0)
-            avgFrameTime.push(0)
-        },
-        update: (deltaTime: number) => {
-            totalTime += deltaTime
-            if (totalTime > 1.0) {
-                totalTime = 0
-
-                // for (let i = 0; i < renderables.length; i++) {
-                //     const renderable = renderables[i]
-                //     const str = `${renderable.name}: ${Math.round(100.0 * avgFrameTime[i]) / 100.0}ms`
-                //     console.log(str)
-                // }
-
-                const sumFrameTime = avgFrameTime.reduce((x: number, y: number) => x + y, 0)
-                const str = `${Math.round(100.0 * sumFrameTime) / 100.0}ms`
-                console.log(str)
-            }
-
-            frameTimeCount++
-
-            for (let i = 0; i < renderables.length; i++) {
-                const renderable = renderables[i]
-                const frameTime = renderable.stats.gpuTime - prevGpuTimes[i]
-                totalFrameTime[i] += frameTime
-
-                if (frameTimeCount === N) {
-                    avgFrameTime[i] = totalFrameTime[i] / N
-                    totalFrameTime[i] = 0.0
-                }
-
-                prevGpuTimes[i] = renderable.stats.gpuTime
-            }
-
-            if (frameTimeCount === N) frameTimeCount = 0
-        }
-    }
-}

+ 154 - 0
src/mol-gl/webgl/buffer.ts

@@ -0,0 +1,154 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Context } from './context'
+
+export type UsageHint = 'static' | 'dynamic' | 'stream'
+export type DataType = 'uint8' | 'int8' | 'uint16' | 'int16' | 'uint32' | 'int32' | 'float32'
+export type BufferType = 'attribute' | 'element'
+
+export type DataTypeArrayType = {
+    'uint8': Uint8Array
+    'int8': Int8Array
+    'uint16': Uint16Array
+    'int16': Int16Array
+    'uint32': Uint32Array
+    'int32': Int32Array
+    'float32': Float32Array
+}
+export type ArrayType = Helpers.ValueOf<DataTypeArrayType>
+export type ArrayKind = keyof DataTypeArrayType
+
+export type BufferItemSize = 1 | 2 | 3 | 4
+
+export function getUsageHint(gl: WebGLRenderingContext, usageHint: UsageHint) {
+    switch (usageHint) {
+        case 'static': return gl.STATIC_DRAW
+        case 'dynamic': return gl.DYNAMIC_DRAW
+        case 'stream': return gl.STREAM_DRAW
+    }
+}
+
+export function getDataType(gl: WebGLRenderingContext, dataType: DataType) {
+    switch (dataType) {
+        case 'uint8': return gl.UNSIGNED_BYTE
+        case 'int8': return gl.BYTE
+        case 'uint16': return gl.UNSIGNED_SHORT
+        case 'int16': return gl.SHORT
+        case 'uint32': return gl.UNSIGNED_INT
+        case 'int32': return gl.INT
+        case 'float32': return gl.FLOAT
+    }
+}
+
+function dataTypeFromArray(gl: WebGLRenderingContext, array: ArrayType) {
+    if (array instanceof Uint8Array) {
+        return gl.UNSIGNED_BYTE
+    } else if (array instanceof Int8Array) {
+        return gl.BYTE
+    } else if (array instanceof Uint16Array) {
+        return gl.UNSIGNED_SHORT
+    } else if (array instanceof Int16Array) {
+        return gl.SHORT
+    } else if (array instanceof Uint32Array) {
+        return gl.UNSIGNED_INT
+    } else if (array instanceof Int32Array) {
+        return gl.INT
+    } else if (array instanceof Float32Array) {
+        return gl.FLOAT
+    } else {
+        throw new Error('Should nevver happen')
+    }
+}
+
+export function getBufferType(gl: WebGLRenderingContext, bufferType: BufferType) {
+    switch (bufferType) {
+        case 'attribute': return gl.ARRAY_BUFFER
+        case 'element': return gl.ELEMENT_ARRAY_BUFFER
+    }
+}
+
+export interface Buffer<T extends ArrayType, S extends BufferItemSize, B extends BufferType> {
+    updateData: (array: T) => void
+    updateSubData: (array: T, offset: number, count: number) => void
+    bind: (location: number, stride: number, offset: number) => void
+    destroy: () => void
+}
+
+export function createBuffer<T extends ArrayType, S extends BufferItemSize, B extends BufferType>(ctx: Context, array: T, itemSize: S, usageHint: UsageHint, bufferType: B): Buffer<T, S, B> {
+    const { gl } = ctx
+    const buffer = gl.createBuffer()
+    if (buffer === null) {
+        throw new Error('Could not create WebGL buffer')
+    }
+
+    const _usageHint = getUsageHint(gl, usageHint)
+    const _bufferType = getBufferType(gl, bufferType)
+    const _dataType = dataTypeFromArray(gl, array)
+
+    function updateData(array: T) {
+        gl.bindBuffer(_bufferType, buffer)
+        gl.bufferData(_bufferType, array, _usageHint)
+    }
+    updateData(array)
+
+    return {
+        updateData,
+        updateSubData: (array: T, offset: number, count: number) => {
+            gl.bindBuffer(_bufferType, buffer)
+            gl.bufferSubData(_bufferType, offset * array.BYTES_PER_ELEMENT, array.subarray(offset, offset + count))
+        },
+        bind: (location: number, stride: number, offset: number) => {
+            gl.bindBuffer(_bufferType, buffer);
+            gl.enableVertexAttribArray(location);
+            gl.vertexAttribPointer(location, itemSize, _dataType, false, stride, offset);
+        },
+        destroy: () => {
+            gl.deleteBuffer(buffer)
+        }
+    }
+}
+
+export type AttributeDefs = { [k: string]: { kind: ArrayKind, itemSize: BufferItemSize, divisor: number } }
+export type AttributeValues<T extends AttributeDefs> = { [K in keyof T]: ArrayType }
+export type AttributeBuffers<T extends AttributeDefs> = {
+    [K in keyof T]: AttributeBuffer<DataTypeArrayType[T[K]['kind']], T[K]['itemSize']>
+}
+
+export interface AttributeBuffer<T extends ArrayType, S extends BufferItemSize> extends Buffer<T, S, 'attribute'> {}
+
+export function createAttributeBuffer<T extends ArrayType, S extends BufferItemSize>(ctx: Context, array: T, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer<T, S> {
+    const buffer = createBuffer(ctx, array, itemSize, usageHint, 'attribute')
+    const { angleInstancedArrays } = ctx.extensions
+
+    return {
+        ...buffer,
+        bind: (location: number, stride: number, offset: number) => {
+            buffer.bind(location, stride, offset)
+            angleInstancedArrays.vertexAttribDivisorANGLE(location, divisor)
+        }
+    }
+}
+
+export function createAttributeBuffers<T extends AttributeDefs>(ctx: Context, props: T, state: AttributeValues<T>) {
+    const buffers: Partial<AttributeBuffers<T>> = {}
+    Object.keys(props).forEach(k => {
+        buffers[k] = createAttributeBuffer(ctx, state[k], props[k].itemSize, props[k].divisor)
+    })
+    return buffers as AttributeBuffers<T>
+}
+
+export type ElementType = Uint16Array | Uint32Array
+
+export interface ElementBuffer<T extends ElementType> extends Buffer<T, 3, 'element'> {}
+
+export function createElementBuffer<T extends ElementType>(ctx: Context, array: T, usageHint: UsageHint = 'static'): ElementBuffer<T> {
+    const buffer = createBuffer(ctx, array, 3, usageHint, 'element')
+
+    return {
+        ...buffer
+    }
+}

+ 32 - 0
src/mol-gl/webgl/context.ts

@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+interface Reference<T> { usageCount: number, value: T }
+
+export interface Context {
+    gl: WebGLRenderingContext
+    shaderCache: Map<string, Reference<WebGLShader>>
+    extensions: {
+        angleInstancedArrays: ANGLE_instanced_arrays
+        oesElementIndexUint: OES_element_index_uint
+    }
+}
+
+export function createContext(gl: WebGLRenderingContext): Context {
+    const angleInstancedArrays = gl.getExtension('ANGLE_instanced_arrays')
+    if (angleInstancedArrays === null) {
+        throw new Error('Could not get "ANGLE_instanced_arrays" extension')
+    }
+    const oesElementIndexUint = gl.getExtension('OES_element_index_uint')
+    if (oesElementIndexUint === null) {
+        throw new Error('Could not get "OES_element_index_uint" extension')
+    }
+    return {
+        gl,
+        shaderCache: new Map(),
+        extensions: { angleInstancedArrays, oesElementIndexUint }
+    }
+}

+ 94 - 0
src/mol-gl/webgl/program.ts

@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Shaders } from '../shaders'
+import { getShader } from './shader'
+import { Context } from './context';
+import { getUniformSetters, UniformDefs, UniformValues } from './uniform';
+import {AttributeDefs, AttributeBuffers } from './buffer';
+import { TextureId, TextureDefs, TextureUniforms, Textures } from './texture';
+
+export interface Program<U extends UniformDefs, A extends AttributeDefs, T extends TextureDefs> {
+    readonly id: number
+
+    setUniforms: (uniformValues: Partial<UniformValues<U>>) => void
+    bindAttributes: (attribueBuffers: AttributeBuffers<A>) => void
+    bindTextures: (textures: Textures<T>) => void
+
+    destroy: () => void
+}
+
+type AttributeLocations<T extends AttributeDefs> = { [K in keyof T]: number }
+
+function getAttributeLocations<A extends AttributeDefs>(gl: WebGLRenderingContext, program: WebGLProgram, attributes: A) {
+    gl.useProgram(program)
+    const locations: Partial<AttributeLocations<A>> = {}
+    Object.keys(attributes).forEach(k => {
+        const loc = gl.getAttribLocation(program, k)
+        gl.enableVertexAttribArray(loc)
+        locations[k] = loc
+    })
+    return locations as AttributeLocations<A>
+}
+
+function getTextureUniforms<T extends TextureDefs>(textures: T) {
+    const textureUniforms: Partial<TextureUniforms<T>> = {}
+    Object.keys(textureUniforms).forEach(k => textureUniforms[k] = 't2')
+    return textureUniforms as TextureUniforms<T>
+}
+
+export function createProgram<U extends UniformDefs, A extends AttributeDefs, T extends TextureDefs>(ctx: Context, shaders: Shaders, uniformDefs: U, attributeDefs: A, textureDefs: T): Program<U, A, T> {
+    const { gl } = ctx
+
+    const program = gl.createProgram()
+    if (program === null) {
+        throw new Error('Could not create WebGL program')
+    }
+
+    const glVertShader = getShader(ctx, 'vert', shaders.vert)
+    const glFragShader = getShader(ctx, 'frag', shaders.frag)
+
+    gl.attachShader(program, glVertShader.value)
+    gl.attachShader(program, glFragShader.value)
+    gl.linkProgram(program)
+
+    const uniformSetters = getUniformSetters(gl, program, uniformDefs)
+    const attributeLocations = getAttributeLocations(gl, program, attributeDefs)
+    const textureUniforms = getTextureUniforms(textureDefs)
+    const textureUniformSetters = getUniformSetters(gl, program, textureUniforms)
+
+    let destroyed = false
+
+    return {
+        id: 0,
+
+        setUniforms: (uniformValues: Partial<UniformValues<U>>) => {
+            Object.keys(uniformValues).forEach(k => {
+                const value = uniformValues[k]
+                if (value !== undefined) uniformSetters[k](value)
+            })
+        },
+        bindAttributes: (attribueBuffers: AttributeBuffers<A>) => {
+            Object.keys(attribueBuffers).forEach(k => {
+                attribueBuffers[k].bind(attributeLocations[k], 0, 0)
+            })
+        },
+        bindTextures: (textures: Textures<T>) => {
+            Object.keys(textures).forEach((k, i) => {
+                textures[k].bind(i as TextureId)
+                textureUniformSetters[k](i)
+            })
+        },
+
+        destroy: () => {
+            if (destroyed) return
+            glVertShader.free()
+            glFragShader.free()
+            gl.deleteProgram(program)
+            destroyed = true
+        }
+    }
+}

+ 53 - 0
src/mol-gl/webgl/shader.ts

@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Context } from './context'
+
+function addLineNumbers(source: string) {
+    const lines = source.split('\n')
+    for (let i = 0; i < lines.length; ++i) {
+        lines[i] = (i + 1) + ': ' + lines[i]
+    }
+    return lines.join('\n')
+}
+
+type ShaderType = 'vert' | 'frag'
+
+function createShader(gl: WebGLRenderingContext, type: ShaderType, source: string) {
+    const shader = gl.createShader(type === 'vert' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER)
+
+    if (shader === null) {
+        throw new Error(`Error creating ${type} shader`)
+    }
+
+    gl.shaderSource(shader, source)
+    gl.compileShader(shader)
+
+    if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) === false) {
+        console.warn(`'${type}' shader info log '${gl.getShaderInfoLog(shader)}'\n${addLineNumbers(source)}`)
+        throw new Error(`Error compiling ${type} shader`)
+    }
+
+    return shader
+}
+
+export function getShader(ctx: Context, type: ShaderType, source: string) {
+    let shaderRef = ctx.shaderCache.get(source)
+    if (!shaderRef) {
+        shaderRef = { usageCount: 0, value: createShader(ctx.gl, type, source) }
+        ctx.shaderCache.set(source, shaderRef)
+    }
+    shaderRef.usageCount += 1
+    return {
+        free: () => {
+            if (shaderRef) {
+                shaderRef.usageCount -= 1
+                shaderRef = undefined
+            }
+        },
+        value: shaderRef.value
+    }
+}

+ 52 - 0
src/mol-gl/webgl/texture.ts

@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+// import { Context } from './context'
+
+export interface Texture {
+    load: (image: ImageData) => void
+    bind: (id: TextureId) => void
+}
+
+export type TextureId = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
+export type TextureTarget = 'TEXTURE0' | 'TEXTURE1' | 'TEXTURE2' | 'TEXTURE3' | 'TEXTURE4' | 'TEXTURE5' | 'TEXTURE6' | 'TEXTURE7' | 'TEXTURE8' | 'TEXTURE9' | 'TEXTURE10' | 'TEXTURE11' | 'TEXTURE12' | 'TEXTURE13' | 'TEXTURE14' | 'TEXTURE15'
+
+export type TextureDefs = { [k: string]: '' }
+export type TextureUniforms<T extends TextureDefs> = { [k in keyof T]: 't2' }
+export type TextureValues<T extends TextureDefs> = { [k in keyof T]: ImageData }
+export type Textures<T extends TextureDefs> = { [k in keyof T]: Texture }
+
+export function createTexture(gl: WebGLRenderingContext): Texture {
+    const texture = gl.createTexture()
+    if (texture === null) {
+        throw new Error('Could not create WebGL texture')
+    }
+
+    return {
+        load: (image: ImageData) => {
+            gl.bindTexture(gl.TEXTURE_2D, texture)
+            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
+            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
+            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
+            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
+            gl.bindTexture(gl.TEXTURE_2D, null)
+        },
+        bind: (id: TextureId) => {
+            gl.activeTexture(gl[`TEXTURE${id}` as TextureTarget])
+            gl.bindTexture(gl.TEXTURE_2D, texture)
+        }
+    }
+}
+
+export function createTextures<T extends TextureDefs>(gl: WebGLRenderingContext, props: T, state: TextureValues<T>) {
+    const textures: Partial<Textures<T>> = {}
+    Object.keys(props).forEach(k => {
+        const texture = createTexture(gl)
+        texture.load(state[k])
+        textures[k] = texture
+    })
+    return textures as Textures<T>
+}

+ 45 - 0
src/mol-gl/webgl/uniform.ts

@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Mat3, Mat4, Vec2, Vec3, Vec4 } from 'mol-math/linear-algebra'
+
+export type UniformKindValue = {
+    'f': number
+    'i': number
+    'v2': Vec2
+    'v3': Vec3
+    'v4': Vec4
+    'm3': Mat3
+    'm4': Mat4
+    't2': number
+}
+export type UniformKind = keyof UniformKindValue
+
+export type UniformDefs = { [k: string]: UniformKind }
+export type UniformValues<T extends UniformDefs> = { [K in keyof T]: UniformKindValue[T[K]] }
+export type UniformSetters<T extends UniformDefs> = { [K in keyof T]: (value: UniformKindValue[T[K]]) => void }
+
+export function createUniformSetter<K extends UniformKind, V = UniformKindValue[K]>(gl: WebGLRenderingContext, program: WebGLProgram, name: string, kind: K): (value: V) => void {
+    const location = gl.getUniformLocation(program, name)
+    switch (kind) {
+        case 'f' as K: return (value: V) => gl.uniform1f(location, value as any as number)
+        case 'i': case 't2': return (value: V) => gl.uniform1i(location, value as any as number)
+        case 'v2': return (value: V) => gl.uniform2fv(location, value as any as Vec2)
+        case 'v3': return (value: V) => gl.uniform3fv(location, value as any as Vec3)
+        case 'v4': return (value: V) => gl.uniform4fv(location, value as any as Vec4)
+        case 'm3': return (value: V) => gl.uniformMatrix3fv(location, false, value as any as Mat3)
+        case 'm4': return (value: V) => gl.uniformMatrix4fv(location, false, value as any as Mat4)
+    }
+    throw new Error('Should never happen')
+}
+
+export function getUniformSetters<T extends UniformDefs, K = keyof T>(gl: WebGLRenderingContext, program: WebGLProgram, uniforms: T) {
+    const setters: Partial<UniformSetters<T>> = {}
+    Object.keys(uniforms).forEach(k => {
+        setters[k] = createUniformSetter(gl, program, k, uniforms[k])
+    })
+    return setters as UniformSetters<T>
+}

+ 46 - 1
src/mol-util/index.ts

@@ -24,6 +24,51 @@ export function arrayEqual<T>(arr1: T[], arr2: T[]) {
     return true
 }
 
-export function defaults (value: any, defaultValue: any) {
+export function deepEqual(a: any, b: any) {
+    // from https://github.com/epoberezkin/fast-deep-equal MIT
+    if (a === b) return true;
+
+    const arrA = Array.isArray(a)
+    const arrB = Array.isArray(b)
+
+    if (arrA && arrB) {
+        if (a.length !== b.length) return false
+        for (let i = 0; i < a.length; i++) {
+            if (!deepEqual(a[i], b[i])) return false
+        }
+        return true
+    }
+
+    if (arrA !== arrB) return false
+
+    if (a && b && typeof a === 'object' && typeof b === 'object') {
+        const keys = Object.keys(a)
+        if (keys.length !== Object.keys(b).length) return false;
+
+        const dateA = a instanceof Date
+        const dateB = b instanceof Date
+        if (dateA && dateB) return a.getTime() === b.getTime()
+        if (dateA !== dateB) return false
+
+        const regexpA = a instanceof RegExp
+        const regexpB = b instanceof RegExp
+        if (regexpA && regexpB) return a.toString() === b.toString()
+        if (regexpA !== regexpB) return false
+
+        for (let i = 0; i < keys.length; i++) {
+            if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false
+        }
+
+        for (let i = 0; i < keys.length; i++) {
+            if (!deepEqual(a[keys[i]], b[keys[i]])) return false
+        }
+
+        return true
+    }
+
+    return false
+}
+
+export function defaults(value: any, defaultValue: any) {
     return value !== undefined ? value : defaultValue
 }

+ 2 - 0
src/mol-view/viewer.ts

@@ -22,6 +22,7 @@ interface Viewer {
 
     add: (repr: StructureRepresentation) => void
     remove: (repr: StructureRepresentation) => void
+    update: () => void
     clear: () => void
 
     draw: (force?: boolean) => void
@@ -119,6 +120,7 @@ namespace Viewer {
                 const renderObjectSet = reprMap.get(repr)
                 if (renderObjectSet) renderObjectSet.forEach(o => renderer.remove(o))
             },
+            update: () => renderer.update(),
             clear: () => {
                 reprMap.clear()
                 renderer.clear()