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

wip

parent 40124be6
Branches
Tags
No related merge requests found
Showing
with 656 additions and 186 deletions
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
...@@ -13,14 +13,16 @@ import Select from 'material-ui/Select'; ...@@ -13,14 +13,16 @@ import Select from 'material-ui/Select';
import State, { ColorTheme as _ColorTheme } from '../state' import State, { ColorTheme as _ColorTheme } from '../state'
import Observer from './observer'; import Observer from './observer';
import { Color, ColorNames } from 'mol-util/color';
interface ColorThemeState { interface ColorThemeState {
loading: boolean loading: boolean
name: _ColorTheme name: _ColorTheme
value: Color
} }
export default class ColorTheme extends Observer<{ state: State } & WithStyles, ColorThemeState> { 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() { componentDidMount() {
this.subscribe(this.props.state.loading, value => { this.subscribe(this.props.state.loading, value => {
...@@ -29,32 +31,59 @@ export default class ColorTheme extends Observer<{ state: State } & WithStyles, ...@@ -29,32 +31,59 @@ export default class ColorTheme extends Observer<{ state: State } & WithStyles,
this.subscribe(this.props.state.colorTheme, value => { this.subscribe(this.props.state.colorTheme, value => {
this.setState({ name: value }); this.setState({ name: value });
}); });
this.subscribe(this.props.state.colorValue, value => {
this.setState({ value: value });
});
} }
handleNameChange = (event: React.ChangeEvent<any>) => { handleNameChange = (event: React.ChangeEvent<any>) => {
this.props.state.colorTheme.next(event.target.value) this.props.state.colorTheme.next(event.target.value)
} }
handleValueChange = (event: React.ChangeEvent<any>) => {
this.props.state.colorValue.next(event.target.value)
}
render() { render() {
const { classes } = this.props; 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 <MenuItem key={idx} value={name}>{name}</MenuItem>
}) })
return <FormControl className={classes.formControl}> const colorValueItems = Object.keys(ColorNames).map((name, idx) => {
<InputLabel htmlFor='color-theme-name'>Color Theme</InputLabel> return <MenuItem key={idx} value={(ColorNames as any)[name]}>{name}</MenuItem>
<Select })
className={classes.selectField}
value={this.state.name} return <div>
onChange={this.handleNameChange} <FormControl className={classes.formControl}>
inputProps={{ <InputLabel htmlFor='color-theme-name'>Color Theme</InputLabel>
name: 'name', <Select
id: 'color-theme-name', className={classes.selectField}
}} value={this.state.name}
> onChange={this.handleNameChange}
{items} inputProps={{
</Select> name: 'name',
</FormControl> 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
...@@ -22,6 +22,7 @@ import { Symmetry, Structure } from 'mol-model/structure' ...@@ -22,6 +22,7 @@ import { Symmetry, Structure } from 'mol-model/structure'
// import mcubes from './utils/mcubes' // import mcubes from './utils/mcubes'
import { getStructuresFromPdbId, getStructuresFromFile, log } from './utils' import { getStructuresFromPdbId, getStructuresFromFile, log } from './utils'
import { StructureRepresentation } from 'mol-geo/representation/structure'; import { StructureRepresentation } from 'mol-geo/representation/structure';
import { Color } from 'mol-util/color';
// import Cylinder from 'mol-geo/primitive/cylinder'; // import Cylinder from 'mol-geo/primitive/cylinder';
...@@ -29,7 +30,8 @@ export const ColorTheme = { ...@@ -29,7 +30,8 @@ export const ColorTheme = {
'atom-index': {}, 'atom-index': {},
'chain-id': {}, 'chain-id': {},
'element-symbol': {}, 'element-symbol': {},
'instance-index': {} 'instance-index': {},
'uniform': {}
} }
export type ColorTheme = keyof typeof ColorTheme export type ColorTheme = keyof typeof ColorTheme
...@@ -39,7 +41,8 @@ export default class State { ...@@ -39,7 +41,8 @@ export default class State {
initialized = new BehaviorSubject<boolean>(false) initialized = new BehaviorSubject<boolean>(false)
loading = 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) detail = new BehaviorSubject<number>(2)
pointVisibility = new BehaviorSubject<boolean>(true) pointVisibility = new BehaviorSubject<boolean>(true)
...@@ -50,6 +53,7 @@ export default class State { ...@@ -50,6 +53,7 @@ export default class State {
constructor() { constructor() {
this.colorTheme.subscribe(() => this.update()) this.colorTheme.subscribe(() => this.update())
this.colorValue.subscribe(() => this.update())
this.detail.subscribe(() => this.update()) this.detail.subscribe(() => this.update())
this.pointVisibility.subscribe(() => this.updateVisibility()) this.pointVisibility.subscribe(() => this.updateVisibility())
...@@ -57,16 +61,22 @@ export default class State { ...@@ -57,16 +61,22 @@ export default class State {
} }
getSpacefillProps (): SpacefillProps { getSpacefillProps (): SpacefillProps {
const colorThemeName = this.colorTheme.getValue()
return { return {
detail: this.detail.getValue(), detail: this.detail.getValue(),
colorTheme: { name: this.colorTheme.getValue() }, colorTheme: colorThemeName === 'uniform' ?
{ name: colorThemeName, value: this.colorValue.getValue() } :
{ name: colorThemeName }
} }
} }
getPointProps (): PointProps { getPointProps (): PointProps {
const colorThemeName = this.colorTheme.getValue()
return { 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 { ...@@ -88,8 +98,8 @@ export default class State {
viewer.add(this.pointRepr) viewer.add(this.pointRepr)
this.spacefillRepr = StructureRepresentation(Spacefill) this.spacefillRepr = StructureRepresentation(Spacefill)
await Run(this.spacefillRepr.create(struct, this.getSpacefillProps()), log, 100) // await Run(this.spacefillRepr.create(struct, this.getSpacefillProps()), log, 100)
viewer.add(this.spacefillRepr) // viewer.add(this.spacefillRepr)
this.updateVisibility() this.updateVisibility()
viewer.requestDraw() viewer.requestDraw()
...@@ -121,6 +131,7 @@ export default class State { ...@@ -121,6 +131,7 @@ export default class State {
await Run(this.pointRepr.update(this.getPointProps()), log, 100) await Run(this.pointRepr.update(this.getPointProps()), log, 100)
this.viewer.add(this.spacefillRepr) this.viewer.add(this.spacefillRepr)
this.viewer.add(this.pointRepr) this.viewer.add(this.pointRepr)
this.viewer.update()
this.viewer.requestDraw() this.viewer.requestDraw()
console.log(this.viewer.stats) console.log(this.viewer.stats)
} }
......
...@@ -12,4 +12,5 @@ declare module Helpers { ...@@ -12,4 +12,5 @@ declare module Helpers {
export type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array export type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array
export type NumberArray = TypedArray | number[] export type NumberArray = TypedArray | number[]
export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[] export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[]
export type ValueOf<T> = T[keyof T]
} }
\ No newline at end of file
...@@ -5,18 +5,17 @@ ...@@ -5,18 +5,17 @@
*/ */
import { ValueCell } from 'mol-util/value-cell' import { ValueCell } from 'mol-util/value-cell'
import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/scene' import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/scene'
import { OrderedSet } from 'mol-data/int' import { OrderedSet } from 'mol-data/int'
import { Unit, ElementGroup } from 'mol-model/structure'; import { Unit, ElementGroup } from 'mol-model/structure';
import { RepresentationProps, UnitsRepresentation } from './index';
import { Task } from 'mol-task' import { Task } from 'mol-task'
import { fillSerial } from 'mol-gl/renderable/util'; import { fillSerial } from 'mol-gl/renderable/util';
import { RepresentationProps, UnitsRepresentation } from './index';
import VertexMap from '../../shape/vertex-map'; import VertexMap from '../../shape/vertex-map';
import { ColorTheme, SizeTheme } from '../../theme'; import { ColorTheme, SizeTheme } from '../../theme';
import { createTransforms, createColors, createSizes } from './utils'; import { createTransforms, createColors, createSizes } from './utils';
import { deepEqual } from 'mol-util';
export const DefaultPointProps = { export const DefaultPointProps = {
colorTheme: { name: 'instance-index' } as ColorTheme, colorTheme: { name: 'instance-index' } as ColorTheme,
...@@ -41,14 +40,22 @@ export function createPointVertices(unit: Unit, elementGroup: ElementGroup) { ...@@ -41,14 +40,22 @@ export function createPointVertices(unit: Unit, elementGroup: ElementGroup) {
export default function Point(): UnitsRepresentation<PointProps> { export default function Point(): UnitsRepresentation<PointProps> {
const renderObjects: RenderObject[] = [] const renderObjects: RenderObject[] = []
let points: PointRenderObject let points: PointRenderObject
let curProps = DefaultPointProps
let _units: ReadonlyArray<Unit>
let _elementGroup: ElementGroup
return { return {
renderObjects, renderObjects,
create(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: PointProps = {}) { create(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: PointProps = {}) {
return Task.create('Point.create', async ctx => { return Task.create('Point.create', async ctx => {
renderObjects.length = 0 // clear 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 elementCount = OrderedSet.size(elementGroup.elements)
const unitCount = units.length const unitCount = units.length
...@@ -76,8 +83,8 @@ export default function Point(): UnitsRepresentation<PointProps> { ...@@ -76,8 +83,8 @@ export default function Point(): UnitsRepresentation<PointProps> {
position: ValueCell.create(vertices), position: ValueCell.create(vertices),
id: ValueCell.create(fillSerial(new Float32Array(elementCount))), id: ValueCell.create(fillSerial(new Float32Array(elementCount))),
size, size: ValueCell.create(size),
color, color: ValueCell.create(color),
transform: ValueCell.create(transforms), transform: ValueCell.create(transforms),
instanceCount: unitCount, instanceCount: unitCount,
...@@ -91,9 +98,37 @@ export default function Point(): UnitsRepresentation<PointProps> { ...@@ -91,9 +98,37 @@ export default function Point(): UnitsRepresentation<PointProps> {
}, },
update(props: RepresentationProps) { update(props: RepresentationProps) {
return Task.create('Point.update', async ctx => { 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
}) })
} }
} }
......
...@@ -86,7 +86,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { ...@@ -86,7 +86,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
position: mesh.vertexBuffer, position: mesh.vertexBuffer,
normal: mesh.normalBuffer as ValueCell<Float32Array>, normal: mesh.normalBuffer as ValueCell<Float32Array>,
color: color, color: ValueCell.create(color),
id: mesh.idBuffer as ValueCell<Float32Array>, id: mesh.idBuffer as ValueCell<Float32Array>,
transform: ValueCell.create(transforms), transform: ValueCell.create(transforms),
index: mesh.indexBuffer, index: mesh.indexBuffer,
......
...@@ -39,8 +39,8 @@ function createRenderer(gl: WebGLRenderingContext) { ...@@ -39,8 +39,8 @@ function createRenderer(gl: WebGLRenderingContext) {
function createPoints() { function createPoints() {
const position = ValueCell.create(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0])) const position = ValueCell.create(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0]))
const id = ValueCell.create(fillSerial(new Float32Array(3))) const id = ValueCell.create(fillSerial(new Float32Array(3)))
const color = createUniformColor({ value: 0xFF0000 }) const color = ValueCell.create(createUniformColor({ value: 0xFF0000 }))
const size = createUniformSize({ value: 1 }) const size = ValueCell.create(createUniformSize({ value: 1 }))
const transform = ValueCell.create(new Float32Array(16)) const transform = ValueCell.create(new Float32Array(16))
const m4 = Mat4.identity() const m4 = Mat4.identity()
......
...@@ -4,24 +4,70 @@ ...@@ -4,24 +4,70 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
import REGL = require('regl');
import Attribute from './attribute'
import PointRenderable from './renderable/point' import PointRenderable from './renderable/point'
import MeshRenderable from './renderable/mesh' 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 RenderableProps = {
export type AttributesData = { [k: string]: Helpers.TypedArray } shaders: Shaders
export type Attributes<T extends AttributesData> = { [K in keyof T]: Attribute<T[K]> } uniform: UniformDefs
export type AttributesBuffers<T extends AttributesData> = { [K in keyof T]: REGL.AttributeConfig } attribute: AttributeDefs
texture: TextureDefs
export interface Renderable { }
draw(): void
dispose(): void export type RenderableState<T extends RenderableProps> = {
stats: REGL.CommandStats uniform: UniformValues<T['uniform']>
name: string attribute: AttributeValues<T['attribute']>
// isPicking: () => boolean texture: TextureValues<T['texture']>
// isVisible: () => boolean
// isTransparent: () => boolean 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 } export { PointRenderable, MeshRenderable }
\ No newline at end of file
...@@ -22,7 +22,7 @@ namespace Mesh { ...@@ -22,7 +22,7 @@ namespace Mesh {
normal?: ValueCell<Float32Array> normal?: ValueCell<Float32Array>
id: ValueCell<Float32Array> id: ValueCell<Float32Array>
color: ColorData color: ValueCell<ColorData>
transform: ValueCell<Float32Array> transform: ValueCell<Float32Array>
index: ValueCell<Uint32Array> index: ValueCell<Uint32Array>
...@@ -58,6 +58,9 @@ namespace Mesh { ...@@ -58,6 +58,9 @@ namespace Mesh {
return command.stats return command.stats
}, },
name: 'mesh', name: 'mesh',
update: (newProps: Data) => {
console.log('Updating mesh renderable')
},
dispose: () => { dispose: () => {
destroyAttributes(attributes) destroyAttributes(attributes)
destroyUniforms(uniforms) destroyUniforms(uniforms)
......
...@@ -8,7 +8,7 @@ import REGL = require('regl'); ...@@ -8,7 +8,7 @@ import REGL = require('regl');
import { ValueCell } from 'mol-util/value-cell' import { ValueCell } from 'mol-util/value-cell'
import { Renderable } from '../renderable' 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 { PointShaders, addDefines } from '../shaders'
import { ColorData } from 'mol-geo/util/color-data'; import { ColorData } from 'mol-geo/util/color-data';
import { SizeData } from 'mol-geo/util/size-data'; import { SizeData } from 'mol-geo/util/size-data';
...@@ -22,8 +22,8 @@ namespace Point { ...@@ -22,8 +22,8 @@ namespace Point {
position: ValueCell<Float32Array> position: ValueCell<Float32Array>
id: ValueCell<Float32Array> id: ValueCell<Float32Array>
size: SizeData size: ValueCell<SizeData>
color: ColorData color: ValueCell<ColorData>
transform: ValueCell<Float32Array> transform: ValueCell<Float32Array>
instanceCount: number instanceCount: number
...@@ -34,6 +34,8 @@ namespace Point { ...@@ -34,6 +34,8 @@ namespace Point {
} }
export function create(regl: REGL.Regl, props: Data): Renderable { export function create(regl: REGL.Regl, props: Data): Renderable {
let curProps = props
const defines = createBaseDefines(regl, props) const defines = createBaseDefines(regl, props)
const uniforms = createBaseUniforms(regl, props) const uniforms = createBaseUniforms(regl, props)
const attributes = createBaseAttributes(regl, props) const attributes = createBaseAttributes(regl, props)
...@@ -54,6 +56,13 @@ namespace Point { ...@@ -54,6 +56,13 @@ namespace Point {
return command.stats return command.stats
}, },
name: 'point', 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: () => { dispose: () => {
destroyAttributes(attributes) destroyAttributes(attributes)
destroyUniforms(uniforms) destroyUniforms(uniforms)
......
...@@ -12,6 +12,9 @@ import { SizeData } from 'mol-geo/util/size-data'; ...@@ -12,6 +12,9 @@ import { SizeData } from 'mol-geo/util/size-data';
import { Attributes, AttributesData, AttributesBuffers } from '../renderable' import { Attributes, AttributesData, AttributesBuffers } from '../renderable'
import Attribute from '../attribute' import Attribute from '../attribute'
import { ShaderDefines } from '../shaders'; 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 ReglUniforms = { [k: string]: REGL.Uniform | REGL.Texture }
export type ReglAttributes = { [k: string]: REGL.AttributeConfig } export type ReglAttributes = { [k: string]: REGL.AttributeConfig }
...@@ -67,9 +70,9 @@ export function createColorUniforms (regl: REGL.Regl, color: ValueCell<Texture>) ...@@ -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 = {} const defines: ShaderDefines = {}
switch (color.type) { switch (color.ref.value.type) {
case 'uniform': defines.UNIFORM_COLOR = ''; break; case 'uniform': defines.UNIFORM_COLOR = ''; break;
case 'attribute': defines.ATTRIBUTE_COLOR = ''; break; case 'attribute': defines.ATTRIBUTE_COLOR = ''; break;
case 'element': defines.ELEMENT_COLOR = ''; break; case 'element': defines.ELEMENT_COLOR = ''; break;
...@@ -79,9 +82,9 @@ export function getColorDefines(color: ColorData) { ...@@ -79,9 +82,9 @@ export function getColorDefines(color: ColorData) {
return defines return defines
} }
export function getSizeDefines(size: SizeData) { export function getSizeDefines(size: ValueCell<SizeData>) {
const defines: ShaderDefines = {} const defines: ShaderDefines = {}
switch (size.type) { switch (size.ref.value.type) {
case 'uniform': defines.UNIFORM_SIZE = ''; break; case 'uniform': defines.UNIFORM_SIZE = ''; break;
case 'attribute': defines.ATTRIBUTE_SIZE = ''; break; case 'attribute': defines.ATTRIBUTE_SIZE = ''; break;
} }
...@@ -113,26 +116,80 @@ interface BaseProps { ...@@ -113,26 +116,80 @@ interface BaseProps {
id: ValueCell<Float32Array> id: ValueCell<Float32Array>
transform: ValueCell<Float32Array> transform: ValueCell<Float32Array>
size?: SizeData size?: ValueCell<SizeData>
color: ColorData 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 { export function getBaseUniformValues(props: BaseProps) {
const { objectId, instanceCount, elementCount, color, size } = props const { objectId, instanceCount, elementCount } = props
const uniforms = { objectId, instanceCount, elementCount } 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') { 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') { } 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') { 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 { 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 instanceId = ValueCell.create(fillSerial(new Float32Array(instanceCount)))
const attributes = getBuffers({ const attributes = getBuffers({
instanceId: Attribute.create(regl, instanceId, instanceCount, { size: 1, divisor: 1 }), instanceId: Attribute.create(regl, instanceId, instanceCount, { size: 1, divisor: 1 }),
...@@ -143,9 +200,11 @@ export function createBaseAttributes(regl: REGL.Regl, props: BaseProps): ReglAtt ...@@ -143,9 +200,11 @@ export function createBaseAttributes(regl: REGL.Regl, props: BaseProps): ReglAtt
if (normal) { if (normal) {
attributes.normal = Attribute.create(regl, normal as any, positionCount, { size: 3 }).buffer attributes.normal = Attribute.create(regl, normal as any, positionCount, { size: 3 }).buffer
} }
const color = props.color.ref.value
if (color.type === 'attribute') { if (color.type === 'attribute') {
attributes.color = Attribute.create(regl, color.value, positionCount, { size: 3 }).buffer 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') { if (size && size.type === 'attribute') {
attributes.size = Attribute.create(regl, size.value, positionCount, { size: 1 }).buffer attributes.size = Attribute.create(regl, size.value, positionCount, { size: 1 }).buffer
} }
......
...@@ -8,8 +8,10 @@ import { Vec3, Mat4 } from 'mol-math/linear-algebra' ...@@ -8,8 +8,10 @@ import { Vec3, Mat4 } from 'mol-math/linear-algebra'
import { Viewport } from 'mol-view/camera/util'; import { Viewport } from 'mol-view/camera/util';
import { Camera } from 'mol-view/camera/base'; import { Camera } from 'mol-view/camera/base';
import * as glContext from './context'
import Scene, { RenderObject } from './scene'; import Scene, { RenderObject } from './scene';
import { createContext } from './webgl/context';
import { SimpleShaders } from './shaders';
import { createRenderable, RenderableProps, RenderableState } from './renderable';
export interface RendererStats { export interface RendererStats {
elementsCount: number elementsCount: number
...@@ -22,6 +24,7 @@ export interface RendererStats { ...@@ -22,6 +24,7 @@ export interface RendererStats {
interface Renderer { interface Renderer {
add: (o: RenderObject) => void add: (o: RenderObject) => void
remove: (o: RenderObject) => void remove: (o: RenderObject) => void
update: () => void
clear: () => void clear: () => void
draw: () => void draw: () => void
...@@ -45,69 +48,112 @@ function getPixelRatio() { ...@@ -45,69 +48,112 @@ function getPixelRatio() {
namespace Renderer { namespace Renderer {
export function create(gl: WebGLRenderingContext, camera: Camera): 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({ const ctx = createContext(gl)
context: {
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(), model: Mat4.identity(),
transform: Mat4.identity(),
view: camera.view, view: camera.view,
projection: camera.projection, projection: camera.projection
}, },
uniforms: { attribute: {
pixelRatio: getPixelRatio(), position: new Float32Array([0, 0, 0, 10, 10, 0, -10, 0, 0])
viewportHeight: regl.context('viewportHeight'), },
texture: {
model: regl.context('model' as any),
transform: regl.context('transform' as any), },
view: regl.context('view' as any),
projection: regl.context('projection' as any), drawCount: 3
}
'light.position': Vec3.create(0, 0, -100),
'light.color': Vec3.create(1.0, 1.0, 1.0), const renderable = createRenderable(ctx, renderableProps, renderableState)
'light.ambient': Vec3.create(0.5, 0.5, 0.5),
'light.falloff': 0, // const regl = glContext.create({ gl, extensions, optionalExtensions, profile: false })
'light.radius': 500 // 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 = () => { const draw = () => {
regl.poll() // updates timers and viewport // regl.poll() // updates timers and viewport
baseContext(state => { // baseContext(state => {
regl.clear({ color: [0, 0, 0, 1] }) // regl.clear({ color: [0, 0, 0, 1] })
// TODO painters sort, filter visible, filter picking, visibility culling? // // TODO painters sort, filter visible, filter picking, visibility culling?
scene.forEach((r, o) => { // scene.forEach((r, o) => {
if (o.visible) r.draw() // if (o.visible) r.draw()
}) // })
}) // })
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
renderable.draw()
} }
return { return {
add: (o: RenderObject) => { add: (o: RenderObject) => {
scene.add(o) // scene.add(o)
}, },
remove: (o: RenderObject) => { remove: (o: RenderObject) => {
scene.remove(o) // scene.remove(o)
},
update: () => {
// scene.forEach((r, o) => r.update(o))
}, },
clear: () => { clear: () => {
scene.clear() // scene.clear()
}, },
draw, draw,
setViewport: (viewport: Viewport) => { setViewport: (viewport: Viewport) => {
regl({ viewport }) gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height)
// regl({ viewport })
}, },
get stats() { get stats() {
return { return {
elementsCount: regl.stats.elementsCount, // elementsCount: regl.stats.elementsCount,
bufferCount: regl.stats.bufferCount, // bufferCount: regl.stats.bufferCount,
textureCount: regl.stats.textureCount, // textureCount: regl.stats.textureCount,
shaderCount: regl.stats.shaderCount, // shaderCount: regl.stats.shaderCount,
renderableCount: scene.count // renderableCount: scene.count
} } as any
}, },
dispose: () => { dispose: () => {
regl.destroy() // regl.destroy()
} }
} }
} }
......
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
import REGL = require('regl');
import { PointRenderable, MeshRenderable, Renderable } from './renderable' import { PointRenderable, MeshRenderable, Renderable } from './renderable'
import { ValueCell } from 'mol-util'; import { ValueCell } from 'mol-util';
import { Context } from './webgl/context';
let _renderObjectId = 0; let _renderObjectId = 0;
function getNextId() { function getNextId() {
...@@ -28,10 +28,10 @@ export function createPointRenderObject(props: PointRenderable.Data): PointRende ...@@ -28,10 +28,10 @@ export function createPointRenderObject(props: PointRenderable.Data): PointRende
return { id: getNextId(), type: 'point', props, visible: true } 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) { switch (o.type) {
case 'mesh': return MeshRenderable.create(regl, o.props) case 'mesh': return MeshRenderable.create(ctx, o.props)
case 'point': return PointRenderable.create(regl, o.props) case 'point': return PointRenderable.create(ctx, o.props)
} }
} }
...@@ -39,18 +39,18 @@ interface Scene { ...@@ -39,18 +39,18 @@ interface Scene {
add: (o: RenderObject) => void add: (o: RenderObject) => void
remove: (o: RenderObject) => void remove: (o: RenderObject) => void
clear: () => void clear: () => void
forEach: (callbackFn: (value: Renderable, key: RenderObject) => void) => void forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => void
count: number count: number
} }
namespace Scene { namespace Scene {
export function create(regl: REGL.Regl): Scene { export function create(ctx: Context): Scene {
const renderableMap = new Map<RenderObject, Renderable>() const renderableMap = new Map<RenderObject, Renderable<any>>()
return { return {
add: (o: RenderObject) => { add: (o: RenderObject) => {
if (!renderableMap.has(o)) { if (!renderableMap.has(o)) {
renderableMap.set(o, createRenderable(regl, o)) renderableMap.set(o, createRenderable(ctx, o))
} else { } else {
console.warn(`RenderObject with id '${o.id}' already present`) console.warn(`RenderObject with id '${o.id}' already present`)
} }
...@@ -66,7 +66,7 @@ namespace Scene { ...@@ -66,7 +66,7 @@ namespace Scene {
renderableMap.forEach(renderable => renderable.dispose()) renderableMap.forEach(renderable => renderable.dispose())
renderableMap.clear() renderableMap.clear()
}, },
forEach: (callbackFn: (value: Renderable, key: RenderObject) => void) => { forEach: (callbackFn: (value: Renderable<any>, key: RenderObject) => void) => {
renderableMap.forEach(callbackFn) renderableMap.forEach(callbackFn)
}, },
get count() { get count() {
......
precision highp float;
void main(void) {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
\ No newline at end of file
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
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Alexander Rose <alexander.rose@weirdbyte.de>
*/ */
interface Shaders { export interface Shaders {
vert: string vert: string
frag: string frag: string
} }
...@@ -20,6 +20,11 @@ export const MeshShaders = { ...@@ -20,6 +20,11 @@ export const MeshShaders = {
frag: require('mol-gl/shader/mesh.frag') 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 = ( type ShaderDefine = (
'UNIFORM_COLOR' | 'ATTRIBUTE_COLOR' | 'INSTANCE_COLOR' | 'ELEMENT_COLOR' | 'ELEMENT_INSTANCE_COLOR' | 'UNIFORM_COLOR' | 'ATTRIBUTE_COLOR' | 'INSTANCE_COLOR' | 'ELEMENT_COLOR' | 'ELEMENT_INSTANCE_COLOR' |
'UNIFORM_SIZE' | 'ATTRIBUTE_SIZE' | 'UNIFORM_SIZE' | 'ATTRIBUTE_SIZE' |
......
/**
* 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
/**
* 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
/**
* 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
/**
* 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment