diff --git a/package-lock.json b/package-lock.json index 6f49e4eb57913c4f02a5937dc4225fa292c0b0b8..758f2f8a4e758a83401da9cc9f9eae6f43fe01b5 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/src/apps/render-test/components/color-theme.tsx b/src/apps/render-test/components/color-theme.tsx index 138d23008640390ca8c5174367074599d392a1ad..9f80a040225e12616fae4ca26f8ff61253de1678 100644 --- a/src/apps/render-test/components/color-theme.tsx +++ b/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> } } \ No newline at end of file diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts index dabfc2729b650e9dcf8207b9942327f2ecab0db0..6ad858c4ff6f455f982158e8a40c60804adaa2eb 100644 --- a/src/apps/render-test/state.ts +++ b/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) } diff --git a/src/helpers.d.ts b/src/helpers.d.ts index 27cbb9bcb166ddccae443ba4b17ef8b1880b55c3..3dbdd2dc79bbbf6f3878bba7ea672785785c6490 100644 --- a/src/helpers.d.ts +++ b/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] } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts index e1ffca4b33a9d6eb913dd937af3ac26da6f385c1..bfb39ee0dda23ec002b73f0dff7eb8cbbffc8862 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/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 }) } } diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 05ac8cb750773bd098ac3f6c9cce59c2a04137af..9e2e665b87e3ceb4efa1e0c65abd8ca5deeb68b8 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/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, diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index d46b8931f58b002accf73344c27b778f39eb4941..12529faf606007b09d9023b16fbd7e6a7320ce76 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/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() diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index 273b951e5a6df2230670570327f24f2577b59d41..3a4b0d2ca4a8e7186be3118ff17333fe2bcdf3a4 100644 --- a/src/mol-gl/renderable.ts +++ b/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 } \ No newline at end of file diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index 420e9f055e42ba086ab5de14fde009d46a043994..2210b9833011c036873767cc1d8d86691dc0b749 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/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) diff --git a/src/mol-gl/renderable/point.ts b/src/mol-gl/renderable/point.ts index d35dd0b73e19fe1ab6e4e83b5120bad58069c67a..f68d69e9d95b92cd183263c739d6632701b7e4ea 100644 --- a/src/mol-gl/renderable/point.ts +++ b/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) diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index 3c78cd3903deefdab253e08da30c9ac01828f2b8..8828a8036148194df28a84e3ce7d4852861a3470 100644 --- a/src/mol-gl/renderable/util.ts +++ b/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 } diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 70525b7a0163693e855f509e7a900cee5368eabe..384fd9972a71fe611d15fa548baf4d2593ac211d 100644 --- a/src/mol-gl/renderer.ts +++ b/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() } } } diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index c26f442af1a6cde5fa54e02db527d0c3dbc595a0..853149dec013cfcf90279374a033b2ff11ad1cf9 100644 --- a/src/mol-gl/scene.ts +++ b/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() { diff --git a/src/mol-gl/shader/simple.frag b/src/mol-gl/shader/simple.frag new file mode 100644 index 0000000000000000000000000000000000000000..d92357264030d9967c5e2e5a11e65e373e8e2098 --- /dev/null +++ b/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); +} \ No newline at end of file diff --git a/src/mol-gl/shader/simple.vert b/src/mol-gl/shader/simple.vert new file mode 100644 index 0000000000000000000000000000000000000000..ccfe98164278eeb8ab2f79a501545023594a8f00 --- /dev/null +++ b/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); +} \ No newline at end of file diff --git a/src/mol-gl/shaders.ts b/src/mol-gl/shaders.ts index 07f1cdc31bfe51692593d71b781240a0ec2c48da..97d352ac022193f049d9d6f4a8f96e2a7a27e990 100644 --- a/src/mol-gl/shaders.ts +++ b/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' | diff --git a/src/mol-gl/stats.ts b/src/mol-gl/stats.ts deleted file mode 100644 index 206e58525428af33ea8d3daae194f0bf8a159103..0000000000000000000000000000000000000000 --- a/src/mol-gl/stats.ts +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/src/mol-gl/webgl/buffer.ts b/src/mol-gl/webgl/buffer.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ee4ace07854534d43d218dc0b2522b9a35dce73 --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts new file mode 100644 index 0000000000000000000000000000000000000000..36cfd07a763e65f003c902b45f472b3da2ef9180 --- /dev/null +++ b/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 } + } +} \ No newline at end of file diff --git a/src/mol-gl/webgl/program.ts b/src/mol-gl/webgl/program.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd267fe64e9e2ac1f2e254b4adc8dd358507f496 --- /dev/null +++ b/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 + } + } +} \ No newline at end of file diff --git a/src/mol-gl/webgl/shader.ts b/src/mol-gl/webgl/shader.ts new file mode 100644 index 0000000000000000000000000000000000000000..e99b9ae2bf5c245e941cf3d8980396ab24c2d822 --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts new file mode 100644 index 0000000000000000000000000000000000000000..22897d970b6b60aa3996f6f17129ccf4229e2131 --- /dev/null +++ b/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> +} diff --git a/src/mol-gl/webgl/uniform.ts b/src/mol-gl/webgl/uniform.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a38a5524cfe2f8ddfe717c748b4a05b0e9f88b1 --- /dev/null +++ b/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> +} \ No newline at end of file diff --git a/src/mol-util/index.ts b/src/mol-util/index.ts index 16d00860d8b51105c8e5fd8ac951091612ad895e..8cb116ef3a21bb50e51bffc14530f58647a1d0a2 100644 --- a/src/mol-util/index.ts +++ b/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 } \ No newline at end of file diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 4bd6189ffefe65fcf719d53dbc05a3ba2e821f63..645ac8cb5df14ecfe73ea6a89df85727d668e3eb 100644 --- a/src/mol-view/viewer.ts +++ b/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()