diff --git a/src/mol-geo/util/color-data.ts b/src/mol-geo/util/color-data.ts index fa565427133ce24ad5092f50842f84a60624d443..4bf19894e07fe429fb57cf5b730be6e5d059fc91 100644 --- a/src/mol-geo/util/color-data.ts +++ b/src/mol-geo/util/color-data.ts @@ -5,15 +5,15 @@ */ import { ValueCell } from 'mol-util'; -import { Texture, createColorTexture } from 'mol-gl/renderable/util'; +import { TextureImage, createColorTexture } from 'mol-gl/renderable/util'; import { Color } from 'mol-util/color'; import VertexMap from '../shape/vertex-map'; export type UniformColor = { type: 'uniform', value: number[] } export type AttributeColor = { type: 'attribute', value: ValueCell<Float32Array> } -export type InstanceColor = { type: 'instance', value: ValueCell<Texture> } -export type ElementColor = { type: 'element', value: ValueCell<Texture> } -export type ElementInstanceColor = { type: 'element-instance', value: ValueCell<Texture> } +export type InstanceColor = { type: 'instance', value: ValueCell<TextureImage> } +export type ElementColor = { type: 'element', value: ValueCell<TextureImage> } +export type ElementInstanceColor = { type: 'element-instance', value: ValueCell<TextureImage> } export type ColorData = UniformColor | AttributeColor | InstanceColor | ElementColor | ElementInstanceColor export interface UniformColorProps { @@ -56,7 +56,7 @@ export function createInstanceColor(props: InstanceColorProps): InstanceColor { const { colorFn, instanceCount} = props const colors = createColorTexture(instanceCount) for (let i = 0; i < instanceCount; i++) { - Color.toArray(colorFn(i), colors, i * 3) + Color.toArray(colorFn(i), colors.array, i * 3) } return { type: 'instance', value: ValueCell.create(colors) } } @@ -72,7 +72,7 @@ export function createElementColor(props: ElementColorProps): ElementColor { const elementCount = vertexMap.offsetCount - 1 const colors = createColorTexture(elementCount) for (let i = 0, il = elementCount; i < il; ++i) { - Color.toArray(colorFn(i), colors, i * 3) + Color.toArray(colorFn(i), colors.array, i * 3) } return { type: 'element', value: ValueCell.create(colors) } } @@ -92,7 +92,7 @@ export function createElementInstanceColor(props: ElementInstanceColorProps): El let colorOffset = 0 for (let i = 0; i < instanceCount; i++) { for (let j = 0, jl = elementCount; j < jl; ++j) { - Color.toArray(colorFn(i, j), colors, colorOffset) + Color.toArray(colorFn(i, j), colors.array, colorOffset) colorOffset += 3 } } diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index 12529faf606007b09d9023b16fbd7e6a7320ce76..53d1ebf2428ae3e4dd27f42bcb91762ddf197bc2 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -15,6 +15,7 @@ import { createPointRenderObject } from '../scene'; import { fillSerial } from '../renderable/util'; import { createUniformColor } from 'mol-geo/util/color-data'; import { createUniformSize } from 'mol-geo/util/size-data'; +import { createContext } from '../webgl/context'; function writeImage(gl: WebGLRenderingContext, width: number, height: number) { const pixels = new Uint8Array(width * height * 4) @@ -28,12 +29,13 @@ function writeImage(gl: WebGLRenderingContext, width: number, height: number) { } function createRenderer(gl: WebGLRenderingContext) { + const ctx = createContext(gl) const camera = PerspectiveCamera.create({ near: 0.01, far: 10000, position: Vec3.create(0, 0, 50) }) - return Renderer.create(gl, camera) + return Renderer.create(ctx, camera) } function createPoints() { diff --git a/src/mol-gl/context.ts b/src/mol-gl/context.ts deleted file mode 100644 index 0e0984bd5e87e6a531555276c28bb5619b166d24..0000000000000000000000000000000000000000 --- a/src/mol-gl/context.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import REGL = require('regl'); -import { InitializationOptions } from 'regl' - -export function create(params: InitializationOptions) { - return REGL(params) -} diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index 3a4b0d2ca4a8e7186be3118ff17333fe2bcdf3a4..78002eaefa9e73c4c9ca6491464da686aa115904 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -6,68 +6,12 @@ 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 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 +export interface Renderable<T> { draw: () => void + name: string + update: (newProps: T) => 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 2210b9833011c036873767cc1d8d86691dc0b749..137ce34207a03035a189ed2ca6a38f8038fd68c1 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -4,18 +4,19 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import REGL = require('regl'); import { ValueCell } from 'mol-util/value-cell' import { ColorData } from 'mol-geo/util/color-data'; import { Renderable } from '../renderable' -import { createBaseDefines, createBaseUniforms, createBaseAttributes, destroyAttributes, destroyUniforms } from './util' -import { MeshShaders, addDefines } from '../shaders' +import { getBaseDefs, getBaseValues, getBaseDefines } from './util' +import { MeshShaderCode, addShaderDefines } from '../shader-code' +import { Context } from '../webgl/context'; +import { createRenderItem, RenderItemProps, RenderItemState } from '../webgl/render-item'; type Mesh = 'mesh' namespace Mesh { - export type Data = { + export type Props = { objectId: number position: ValueCell<Float32Array> @@ -32,39 +33,31 @@ namespace Mesh { positionCount: number } - export function create(regl: REGL.Regl, props: Data): Renderable { - const defines = createBaseDefines(regl, props) - const uniforms = createBaseUniforms(regl, props) - const attributes = createBaseAttributes(regl, props) - const elements = regl.elements({ - data: props.index.ref.value, - primitive: 'triangles', - type: 'uint32', - count: props.indexCount * 3 - }) + export function create(ctx: Context, props: Props): Renderable<Props> { + const defs: RenderItemProps = { + ...getBaseDefs(props), + shaderCode: addShaderDefines(getBaseDefines(props), MeshShaderCode), + drawMode: 'triangles' + } + const values: RenderItemState = { + ...getBaseValues(props), + drawCount: props.indexCount * 3, + instanceCount: props.instanceCount + } + + let renderItem = createRenderItem(ctx, defs, values) + // let curProps = props - const command = regl({ - ...addDefines(defines, MeshShaders), - uniforms, - attributes, - elements, - instances: props.instanceCount, - }) return { draw: () => { - command() - }, - get stats() { - return command.stats + renderItem.draw() }, name: 'mesh', - update: (newProps: Data) => { + update: (newProps: Props) => { console.log('Updating mesh renderable') }, dispose: () => { - destroyAttributes(attributes) - destroyUniforms(uniforms) - elements.destroy() + renderItem.dispose() } } } diff --git a/src/mol-gl/renderable/point.ts b/src/mol-gl/renderable/point.ts index f68d69e9d95b92cd183263c739d6632701b7e4ea..6b739f7a9a23ee33b4a53a07b43e7740e3368fd2 100644 --- a/src/mol-gl/renderable/point.ts +++ b/src/mol-gl/renderable/point.ts @@ -4,19 +4,20 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import REGL = require('regl'); import { ValueCell } from 'mol-util/value-cell' import { Renderable } from '../renderable' -import { createBaseDefines, createBaseUniforms, createBaseAttributes, destroyUniforms, destroyAttributes, updateBaseUniforms } from './util' -import { PointShaders, addDefines } from '../shaders' +import { getBaseValues, getBaseDefs, getBaseDefines } from './util' +import { PointShaderCode, addShaderDefines } from '../shader-code' import { ColorData } from 'mol-geo/util/color-data'; import { SizeData } from 'mol-geo/util/size-data'; +import { Context } from '../webgl/context'; +import { createRenderItem, RenderItemState, RenderItemProps } from '../webgl/render-item'; type Point = 'point' namespace Point { - export type Data = { + export type Props = { objectId: number position: ValueCell<Float32Array> @@ -33,39 +34,38 @@ namespace Point { usePointSizeAttenuation?: boolean } - export function create(regl: REGL.Regl, props: Data): Renderable { - let curProps = props + export function create<T = Props>(ctx: Context, props: Props): Renderable<Props> { + const defines = getBaseDefines(props) + if (props.usePointSizeAttenuation) defines.POINT_SIZE_ATTENUATION = '' - const defines = createBaseDefines(regl, props) - const uniforms = createBaseUniforms(regl, props) - const attributes = createBaseAttributes(regl, props) + const defs: RenderItemProps = { + ...getBaseDefs(props), + shaderCode: addShaderDefines(defines, PointShaderCode), + drawMode: 'points' + } + const values: RenderItemState = { + ...getBaseValues(props), + drawCount: props.positionCount, + instanceCount: props.instanceCount + } - if (props.usePointSizeAttenuation) defines.POINT_SIZE_ATTENUATION = '' + let renderItem = createRenderItem(ctx, defs, values) + // let curProps = props - const command = regl({ - ...addDefines(defines, PointShaders), - uniforms, - attributes, - count: props.positionCount, - instances: props.instanceCount, - primitive: 'points' - }) return { - draw: () => command(), - get stats() { - return command.stats + draw: () => { + renderItem.draw() }, - name: 'point', - update: (newProps: Data) => { + name: 'mesh', + update: (newProps: Props) => { console.log('Updating point renderable') // const newUniforms = updateBaseUniforms(regl, uniforms, newProps, curProps) - const newUniforms = { ...uniforms, color: 0xFF4411 } - console.log(newUniforms) + // const newUniforms = { ...uniforms, color: 0xFF4411 } + // console.log(newUniforms) // command({ uniforms: newUniforms }) }, dispose: () => { - destroyAttributes(attributes) - destroyUniforms(uniforms) + renderItem.dispose() } } } diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index 8828a8036148194df28a84e3ce7d4852861a3470..fee8d9c2d47549d55d9003acd709066908436880 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -4,20 +4,15 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import REGL = require('regl'); import { ValueCell } from 'mol-util/value-cell' import { ColorData } from 'mol-geo/util/color-data'; import { SizeData } from 'mol-geo/util/size-data'; -import { Attributes, AttributesData, AttributesBuffers } from '../renderable' -import Attribute from '../attribute' -import { ShaderDefines } from '../shaders'; +import { ShaderDefines } from '../shader-code'; 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 } +import { AttributeDefs, AttributeValues } from '../webgl/buffer'; +import { Vec3, Vec2 } from 'mol-math/linear-algebra'; +import { TextureDefs, TextureValues } from '../webgl/texture'; export function calculateTextureInfo (n: number, itemSize: number) { const sqN = Math.sqrt(n * itemSize) @@ -27,47 +22,15 @@ export function calculateTextureInfo (n: number, itemSize: number) { return { width, height, length: width * height * itemSize } } -export interface Texture extends Uint8Array { - width: number, +export interface TextureImage { + array: Uint8Array + width: number height: number } -export function createColorTexture (n: number): Texture { - const colorTexInfo = calculateTextureInfo(n, 3) - const colorTexture = new Uint8Array(colorTexInfo.length) - return Object.assign(colorTexture, { - width: colorTexInfo.width, - height: colorTexInfo.height - }) -} - -export function createTransformAttributes (regl: REGL.Regl, transform: ValueCell<Float32Array>, count: number) { - const size = 4 - const divisor = 1 - const bpe = transform.ref.value.BYTES_PER_ELEMENT - const stride = 16 * bpe - return { - transformColumn0: Attribute.create(regl, transform, count, { size, divisor, offset: 0, stride }), - transformColumn1: Attribute.create(regl, transform, count, { size, divisor, offset: 4 * bpe, stride }), - transformColumn2: Attribute.create(regl, transform, count, { size, divisor, offset: 8 * bpe, stride }), - transformColumn3: Attribute.create(regl, transform, count, { size, divisor, offset: 12 * bpe, stride }) - } -} - -export function createColorUniforms (regl: REGL.Regl, color: ValueCell<Texture>) { - const colorTex = regl.texture({ - width: color.ref.value.width, - height: color.ref.value.height, - format: 'rgb', - type: 'uint8', - wrapS: 'clamp', - wrapT: 'clamp', - data: color.ref.value - }) - return { - colorTex, - colorTexSize: [ color.ref.value.width, color.ref.value.height ] - } +export function createColorTexture (n: number): TextureImage { + const { length, width, height } = calculateTextureInfo(n, 3) + return { array: new Uint8Array(length), width, height } } export function getColorDefines(color: ValueCell<ColorData>) { @@ -91,14 +54,6 @@ export function getSizeDefines(size: ValueCell<SizeData>) { return defines } -export function getBuffers<T extends AttributesData>(attributes: Attributes<T>): AttributesBuffers<T> { - const buffers: AttributesBuffers<any> = {} - for (const k of Object.keys(attributes)) { - buffers[k] = attributes[k].buffer - } - return buffers as AttributesBuffers<T> -} - export function fillSerial<T extends Helpers.NumberArray> (array: T) { const n = array.length for (let i = 0; i < n; ++i) array[ i ] = i @@ -146,16 +101,16 @@ export function getBaseUniformDefs(props: BaseProps) { export function getBaseUniformValues(props: BaseProps) { const { objectId, instanceCount, elementCount } = props - const uniformValues: UniformValues<any> = { + const uniformValues: UniformValues = { 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 ] + uniformValues.colorTex = new ImageData(new Uint8ClampedArray(color.value.ref.value.array), width, height) + uniformValues.colorTexSize = Vec2.create(width, height) } else if (color.type === 'uniform') { - uniformValues.color = color.value + uniformValues.color = color.value as Vec3 } const size = props.size ? props.size.ref.value : undefined if (size && size.type === 'uniform') { @@ -169,69 +124,82 @@ export function getBaseAttributeDefs(props: BaseProps) { 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 }, + transform: { kind: 'float32', itemSize: 16, divisor: 1 }, + } + if (props.normal) { + attributeDefs.normal = { kind: 'float32', itemSize: 3, divisor:0 } } 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' + if (color.type === 'attribute') { + attributeDefs.color = { kind: 'float32', itemSize: 3, divisor:0 } } const size = props.size ? props.size.ref.value : undefined - if (size && size.type === 'uniform') { - uniformDefs.size = 'f' + if (size && size.type === 'attribute') { + attributeDefs.size = { kind: 'float32', itemSize: 1, divisor:0 } } return attributeDefs } -export function createBaseAttributes(regl: REGL.Regl, props: BaseProps): ReglAttributes { - const { instanceCount, positionCount, position, id, normal, transform } = props +export function getBaseAttributeValues(props: BaseProps) { + const { instanceCount, 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 }), - position: Attribute.create(regl, position, positionCount, { size: 3 }), - elementId: Attribute.create(regl, id, positionCount, { size: 1 }), - ...createTransformAttributes(regl, transform, instanceCount) - }) + const attributeValues: AttributeValues = { + instanceId: instanceId.ref.value, + position: position.ref.value, + elementId: id.ref.value, + transform: transform.ref.value + } if (normal) { - attributes.normal = Attribute.create(regl, normal as any, positionCount, { size: 3 }).buffer + attributeValues.normal = normal.ref.value } const color = props.color.ref.value if (color.type === 'attribute') { - attributes.color = Attribute.create(regl, color.value, positionCount, { size: 3 }).buffer + attributeValues.color = color.value.ref.value } 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 + attributeValues.size = size.value.ref.value + } + return attributeValues +} + +export function getBaseTextureDefs(props: BaseProps) { + const textureDefs: TextureDefs = {} + const color = props.color.ref.value + if (color.type === 'instance' || color.type === 'element' || color.type === 'element-instance') { + textureDefs.colorTex = true } - return attributes + return textureDefs } -export function createBaseDefines(regl: REGL.Regl, props: BaseProps): ShaderDefines { +export function getBaseTextureValues(props: BaseProps) { + const textureValues: TextureValues = {} + const color = props.color.ref.value + if (color.type === 'instance' || color.type === 'element' || color.type === 'element-instance') { + textureValues.colorTex = color.value.ref.value + } + return textureValues +} + +export function getBaseDefines(props: BaseProps): ShaderDefines { return { ...getColorDefines(props.color), ...(props.size ? getSizeDefines(props.size) : undefined) } } -export function destroyAttributes(attributes: ReglAttributes) { - for (const k in attributes) { - const buffer = attributes[k].buffer - if (buffer) { - buffer.destroy() - } +export function getBaseDefs(props: BaseProps) { + return { + uniformDefs: getBaseUniformDefs(props), + attributeDefs: getBaseAttributeDefs(props), + textureDefs: getBaseTextureDefs(props), } } -export function destroyUniforms(uniforms: ReglUniforms) { - for (const k in uniforms) { - const uniform = uniforms[k] - if ((uniform as any).destroy) { - (uniform as any).destroy() - } +export function getBaseValues(props: BaseProps) { + return { + uniformValues: getBaseUniformValues(props), + attributeValues: getBaseAttributeValues(props), + textureValues: getBaseTextureValues(props), } -} \ No newline at end of file +} diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 384fd9972a71fe611d15fa548baf4d2593ac211d..2ded4fe49be042142a3ed9586328989c39bee72a 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -4,14 +4,12 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Vec3, Mat4 } from 'mol-math/linear-algebra' +// import { Vec3, Mat4 } from 'mol-math/linear-algebra' import { Viewport } from 'mol-view/camera/util'; import { Camera } from 'mol-view/camera/base'; import Scene, { RenderObject } from './scene'; -import { createContext } from './webgl/context'; -import { SimpleShaders } from './shaders'; -import { createRenderable, RenderableProps, RenderableState } from './renderable'; +import { Context } from './webgl/context'; export interface RendererStats { elementsCount: number @@ -34,58 +32,14 @@ interface Renderer { dispose: () => void } -const extensions = [ - 'OES_element_index_uint', - 'ANGLE_instanced_arrays' -] -const optionalExtensions = [ - 'EXT_disjoint_timer_query' -] - -function getPixelRatio() { - return (typeof window !== 'undefined') ? window.devicePixelRatio : 1 -} +// function getPixelRatio() { +// return (typeof window !== 'undefined') ? window.devicePixelRatio : 1 +// } namespace Renderer { - export function create(gl: WebGLRenderingContext, camera: Camera): Renderer { - - 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(), - view: camera.view, - projection: camera.projection - }, - 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) + export function create(ctx: Context, camera: Camera): Renderer { + const { gl } = ctx + const scene = Scene.create(ctx) // const baseContext = regl({ // context: { @@ -112,36 +66,31 @@ namespace Renderer { // }) 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() - // }) - // }) - + // TODO clear color gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - renderable.draw() + + // TODO painters sort, filter visible, filter picking, visibility culling? + scene.forEach((r, o) => { + if (o.visible) r.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)) + scene.forEach((r, o) => r.update(o)) }, clear: () => { - // scene.clear() + scene.clear() }, draw, setViewport: (viewport: Viewport) => { gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height) - // regl({ viewport }) }, get stats() { return { @@ -153,7 +102,7 @@ namespace Renderer { } as any }, dispose: () => { - // regl.destroy() + scene.clear() } } } diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 853149dec013cfcf90279374a033b2ff11ad1cf9..92faf5e27e14031434515bb3706acdad921d3d5d 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -17,14 +17,14 @@ function getNextId() { export type RenderData = { [k: string]: ValueCell<Helpers.TypedArray> } export interface BaseRenderObject { id: number, type: string, props: {}, visible: boolean } -export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', props: MeshRenderable.Data } -export interface PointRenderObject extends BaseRenderObject { type: 'point', props: PointRenderable.Data } +export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', props: MeshRenderable.Props } +export interface PointRenderObject extends BaseRenderObject { type: 'point', props: PointRenderable.Props } export type RenderObject = MeshRenderObject | PointRenderObject -export function createMeshRenderObject(props: MeshRenderable.Data): MeshRenderObject { +export function createMeshRenderObject(props: MeshRenderable.Props): MeshRenderObject { return { id: getNextId(), type: 'mesh', props, visible: true } } -export function createPointRenderObject(props: PointRenderable.Data): PointRenderObject { +export function createPointRenderObject(props: PointRenderable.Props): PointRenderObject { return { id: getNextId(), type: 'point', props, visible: true } } diff --git a/src/mol-gl/shaders.ts b/src/mol-gl/shader-code.ts similarity index 74% rename from src/mol-gl/shaders.ts rename to src/mol-gl/shader-code.ts index 97d352ac022193f049d9d6f4a8f96e2a7a27e990..0b51b0dc56720e08e4479b333e4ab83ac6eebb5a 100644 --- a/src/mol-gl/shaders.ts +++ b/src/mol-gl/shader-code.ts @@ -5,26 +5,21 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -export interface Shaders { +export interface ShaderCode { vert: string frag: string } -export const PointShaders = { +export const PointShaderCode: ShaderCode = { vert: require('mol-gl/shader/point.vert'), frag: require('mol-gl/shader/point.frag') } -export const MeshShaders = { +export const MeshShaderCode: ShaderCode = { vert: require('mol-gl/shader/mesh.vert'), 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' | @@ -34,7 +29,7 @@ export type ShaderDefines = { [k in ShaderDefine]?: number|string } -function getDefines (defines: ShaderDefines) { +function getDefinesCode (defines: ShaderDefines) { if (defines === undefined) return '' const lines = [] for (const name in defines) { @@ -48,8 +43,8 @@ function getDefines (defines: ShaderDefines) { return lines.join('\n') + '\n' } -export function addDefines(defines: ShaderDefines, shaders: Shaders) { - const header = getDefines(defines) +export function addShaderDefines(defines: ShaderDefines, shaders: ShaderCode) { + const header = getDefinesCode(defines) return { vert: `${header}${shaders.vert}`, frag: `${header}${shaders.frag}` diff --git a/src/mol-gl/shader/mesh.vert b/src/mol-gl/shader/mesh.vert index 225a4b878d48f938510f6c20aa271552e66bdb26..4684fc84414be2c12b007111d9b6f6c303a090f9 100644 --- a/src/mol-gl/shader/mesh.vert +++ b/src/mol-gl/shader/mesh.vert @@ -15,7 +15,7 @@ uniform int elementCount; #pragma glslify: import('./chunks/color-vert-params.glsl') attribute vec3 position; -attribute vec4 transformColumn0, transformColumn1, transformColumn2, transformColumn3; +attribute mat4 transform; attribute float instanceId; attribute float elementId; @@ -30,9 +30,7 @@ varying vec3 vViewPosition; void main(){ #pragma glslify: import('./chunks/color-assign-varying.glsl') - mat4 transform = mat4(transformColumn0, transformColumn1, transformColumn2, transformColumn3); mat4 modelView = view * model * transform; - vec4 mvPosition = modelView * vec4(position, 1.0); vViewPosition = mvPosition.xyz; gl_Position = projection * mvPosition; diff --git a/src/mol-gl/shader/point.vert b/src/mol-gl/shader/point.vert index 7a0efe818941a6ec7bc4310aacf13d8ebfece846..50990419b5f690294624098acfc6f8c8d749338a 100644 --- a/src/mol-gl/shader/point.vert +++ b/src/mol-gl/shader/point.vert @@ -24,14 +24,13 @@ uniform float viewportHeight; #endif attribute vec3 position; -attribute vec4 transformColumn0, transformColumn1, transformColumn2, transformColumn3; +attribute mat4 transform; attribute float instanceId; attribute float elementId; void main(){ #pragma glslify: import('./chunks/color-assign-varying.glsl') - mat4 transform = mat4(transformColumn0, transformColumn1, transformColumn2, transformColumn3); mat4 modelView = view * model * transform; vec4 mvPosition = modelView * vec4(position, 1.0); diff --git a/src/mol-gl/shader/simple.frag b/src/mol-gl/shader/simple.frag deleted file mode 100644 index d92357264030d9967c5e2e5a11e65e373e8e2098..0000000000000000000000000000000000000000 --- a/src/mol-gl/shader/simple.frag +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index ccfe98164278eeb8ab2f79a501545023594a8f00..0000000000000000000000000000000000000000 --- a/src/mol-gl/shader/simple.vert +++ /dev/null @@ -1,8 +0,0 @@ -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/webgl/buffer.ts b/src/mol-gl/webgl/buffer.ts index 6ee4ace07854534d43d218dc0b2522b9a35dce73..db43d97f63d476fe65c2cccd275f31c00e99ff32 100644 --- a/src/mol-gl/webgl/buffer.ts +++ b/src/mol-gl/webgl/buffer.ts @@ -8,7 +8,7 @@ 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 BufferType = 'attribute' | 'elements' export type DataTypeArrayType = { 'uint8': Uint8Array @@ -22,9 +22,10 @@ export type DataTypeArrayType = { export type ArrayType = Helpers.ValueOf<DataTypeArrayType> export type ArrayKind = keyof DataTypeArrayType -export type BufferItemSize = 1 | 2 | 3 | 4 +export type BufferItemSize = 1 | 2 | 3 | 4 | 16 -export function getUsageHint(gl: WebGLRenderingContext, usageHint: UsageHint) { +export function getUsageHint(ctx: Context, usageHint: UsageHint) { + const { gl } = ctx switch (usageHint) { case 'static': return gl.STATIC_DRAW case 'dynamic': return gl.DYNAMIC_DRAW @@ -32,7 +33,8 @@ export function getUsageHint(gl: WebGLRenderingContext, usageHint: UsageHint) { } } -export function getDataType(gl: WebGLRenderingContext, dataType: DataType) { +export function getDataType(ctx: Context, dataType: DataType) { + const { gl } = ctx switch (dataType) { case 'uint8': return gl.UNSIGNED_BYTE case 'int8': return gl.BYTE @@ -44,7 +46,8 @@ export function getDataType(gl: WebGLRenderingContext, dataType: DataType) { } } -function dataTypeFromArray(gl: WebGLRenderingContext, array: ArrayType) { +function dataTypeFromArray(ctx: Context, array: ArrayType) { + const { gl } = ctx if (array instanceof Uint8Array) { return gl.UNSIGNED_BYTE } else if (array instanceof Int8Array) { @@ -64,91 +67,125 @@ function dataTypeFromArray(gl: WebGLRenderingContext, array: ArrayType) { } } -export function getBufferType(gl: WebGLRenderingContext, bufferType: BufferType) { +export function getBufferType(ctx: Context, bufferType: BufferType) { + const { gl } = ctx switch (bufferType) { case 'attribute': return gl.ARRAY_BUFFER - case 'element': return gl.ELEMENT_ARRAY_BUFFER + case 'elements': 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 +export interface Buffer { + readonly _buffer: WebGLBuffer + readonly _usageHint: number + readonly _bufferType: number + readonly _dataType: number + readonly _bpe: number + + updateData: (array: ArrayType) => void + updateSubData: (array: ArrayType, offset: number, count: 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> { +export function createBuffer(ctx: Context, array: ArrayType, itemSize: BufferItemSize, usageHint: UsageHint, bufferType: BufferType): Buffer { const { gl } = ctx - const buffer = gl.createBuffer() - if (buffer === null) { + 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) + const _usageHint = getUsageHint(ctx, usageHint) + const _bufferType = getBufferType(ctx, bufferType) + const _dataType = dataTypeFromArray(ctx, array) + const _bpe = array.BYTES_PER_ELEMENT - function updateData(array: T) { - gl.bindBuffer(_bufferType, buffer) + function updateData(array: ArrayType) { + gl.bindBuffer(_bufferType, _buffer) gl.bufferData(_bufferType, array, _usageHint) } updateData(array) return { + _buffer, + _usageHint, + _bufferType, + _dataType, + _bpe, + 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); + updateSubData: (array: ArrayType, offset: number, count: number) => { + gl.bindBuffer(_bufferType, _buffer) + gl.bufferSubData(_bufferType, offset * _bpe, array.subarray(offset, offset + count)) }, + destroy: () => { - gl.deleteBuffer(buffer) + gl.bindBuffer(_bufferType, _buffer) + // set size to 1 before deleting + gl.bufferData(_bufferType, 1, _usageHint) + 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 type AttributeDefs = { + [k: string]: { kind: ArrayKind, itemSize: BufferItemSize, divisor: number } +} +export type AttributeValues = { [k: string]: ArrayType } +export type AttributeBuffers = { [k: string]: AttributeBuffer } + +export interface AttributeBuffer extends Buffer { + bind: (location: number) => void } -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: ArrayType, itemSize: S, divisor: number, usageHint: UsageHint = 'dynamic'): AttributeBuffer { + const { gl } = ctx + const { angleInstancedArrays } = ctx.extensions -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 + const { _buffer, _bufferType, _dataType, _bpe } = buffer return { ...buffer, - bind: (location: number, stride: number, offset: number) => { - buffer.bind(location, stride, offset) + bind: (location: number) => { + gl.bindBuffer(_bufferType, _buffer) + if (itemSize === 16) { + for (let i = 0; i < 4; ++i) { + gl.enableVertexAttribArray(location + i) + gl.vertexAttribPointer(location + i, 4, _dataType, false, 4 * _bpe, i * _bpe) + } + } else { + gl.enableVertexAttribArray(location) + gl.vertexAttribPointer(location, itemSize, _dataType, false, 0, 0) + } angleInstancedArrays.vertexAttribDivisorANGLE(location, divisor) } } } -export function createAttributeBuffers<T extends AttributeDefs>(ctx: Context, props: T, state: AttributeValues<T>) { - const buffers: Partial<AttributeBuffers<T>> = {} +export function createAttributeBuffers<T extends AttributeDefs>(ctx: Context, props: T, state: AttributeValues) { + const buffers: AttributeBuffers = {} Object.keys(props).forEach(k => { buffers[k] = createAttributeBuffer(ctx, state[k], props[k].itemSize, props[k].divisor) }) - return buffers as AttributeBuffers<T> + return buffers as AttributeBuffers } -export type ElementType = Uint16Array | Uint32Array +export type ElementsType = Uint16Array | Uint32Array +export type ElementsKind = 'uint16' | 'unit32' -export interface ElementBuffer<T extends ElementType> extends Buffer<T, 3, 'element'> {} +export interface ElementsBuffer extends Buffer { + bind: () => void +} -export function createElementBuffer<T extends ElementType>(ctx: Context, array: T, usageHint: UsageHint = 'static'): ElementBuffer<T> { - const buffer = createBuffer(ctx, array, 3, usageHint, 'element') +export function createElementsBuffer(ctx: Context, array: ElementsType, usageHint: UsageHint = 'static'): ElementsBuffer { + const { gl } = ctx + const buffer = createBuffer(ctx, array, 1, usageHint, 'elements') + const { _buffer } = buffer return { - ...buffer + ...buffer, + bind: () => { + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, _buffer); + } } } \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index 36cfd07a763e65f003c902b45f472b3da2ef9180..6173c0bceb15b060ae5fa01b6f84e2242b54c436 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -4,15 +4,52 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -interface Reference<T> { usageCount: number, value: T } +import { createProgramCache, ProgramCache } from './program' +import { createShaderCache, ShaderCache } from './shader' + +// const extensions = [ +// 'OES_element_index_uint', +// 'ANGLE_instanced_arrays' +// ] +// const optionalExtensions = [ +// 'EXT_disjoint_timer_query' +// ] + +function unbindResources (gl: WebGLRenderingContext) { + // bind null to all texture units + const maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) + for (let i = 0; i < maxTextureImageUnits; ++i) { + gl.activeTexture(gl.TEXTURE0 + i) + gl.bindTexture(gl.TEXTURE_2D, null) + gl.bindTexture(gl.TEXTURE_CUBE_MAP, null) + } + + // assign the smallest possible buffer to all attributes + const buf = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + const maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + for (let i = 0; i < maxVertexAttribs; ++i) { + gl.vertexAttribPointer(i, 1, gl.FLOAT, false, 0, 0); + } + + // bind null to all buffers + gl.bindBuffer(gl.ARRAY_BUFFER, null) + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null) + gl.bindRenderbuffer(gl.RENDERBUFFER, null) + gl.bindFramebuffer(gl.FRAMEBUFFER, null) +} + +type RequiredExtensions = { + angleInstancedArrays: ANGLE_instanced_arrays + oesElementIndexUint: OES_element_index_uint +} export interface Context { gl: WebGLRenderingContext - shaderCache: Map<string, Reference<WebGLShader>> - extensions: { - angleInstancedArrays: ANGLE_instanced_arrays - oesElementIndexUint: OES_element_index_uint - } + extensions: RequiredExtensions + shaderCache: ShaderCache + programCache: ProgramCache + destroy: () => void } export function createContext(gl: WebGLRenderingContext): Context { @@ -26,7 +63,12 @@ export function createContext(gl: WebGLRenderingContext): Context { } return { gl, - shaderCache: new Map(), - extensions: { angleInstancedArrays, oesElementIndexUint } + extensions: { angleInstancedArrays, oesElementIndexUint }, + shaderCache: createShaderCache(), + programCache: createProgramCache(), + destroy: () => { + unbindResources(gl) + // TODO destroy buffers and textures + } } } \ No newline at end of file diff --git a/src/mol-gl/webgl/program.ts b/src/mol-gl/webgl/program.ts index bd267fe64e9e2ac1f2e254b4adc8dd358507f496..ade2c2ba973da090366373437bee6fe805e84e99 100644 --- a/src/mol-gl/webgl/program.ts +++ b/src/mol-gl/webgl/program.ts @@ -4,79 +4,88 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Shaders } from '../shaders' -import { getShader } from './shader' +import { ShaderCode } from '../shader-code' import { Context } from './context'; import { getUniformSetters, UniformDefs, UniformValues } from './uniform'; import {AttributeDefs, AttributeBuffers } from './buffer'; import { TextureId, TextureDefs, TextureUniforms, Textures } from './texture'; +import { createReferenceCache, ReferenceCache } from 'mol-util/reference-cache'; -export interface Program<U extends UniformDefs, A extends AttributeDefs, T extends TextureDefs> { +export interface Program { readonly id: number - setUniforms: (uniformValues: Partial<UniformValues<U>>) => void - bindAttributes: (attribueBuffers: AttributeBuffers<A>) => void - bindTextures: (textures: Textures<T>) => void + setUniforms: (uniformValues: UniformValues) => void + bindAttributes: (attribueBuffers: AttributeBuffers) => void + bindTextures: (textures: Textures) => void destroy: () => void } -type AttributeLocations<T extends AttributeDefs> = { [K in keyof T]: number } +type AttributeLocations = { [k: string]: number } -function getAttributeLocations<A extends AttributeDefs>(gl: WebGLRenderingContext, program: WebGLProgram, attributes: A) { +function getAttributeLocations(ctx: Context, program: WebGLProgram, attributeDefs: AttributeDefs) { + const { gl } = ctx + const locations: AttributeLocations = {} gl.useProgram(program) - const locations: Partial<AttributeLocations<A>> = {} - Object.keys(attributes).forEach(k => { + Object.keys(attributeDefs).forEach(k => { const loc = gl.getAttribLocation(program, k) gl.enableVertexAttribArray(loc) locations[k] = loc }) - return locations as AttributeLocations<A> + return locations } -function getTextureUniforms<T extends TextureDefs>(textures: T) { - const textureUniforms: Partial<TextureUniforms<T>> = {} +function getTextureUniforms(textures: TextureDefs) { + const textureUniforms: TextureUniforms = {} Object.keys(textureUniforms).forEach(k => textureUniforms[k] = 't2') - return textureUniforms as TextureUniforms<T> + return textureUniforms } -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 +export interface ProgramProps { + shaderCode: ShaderCode, + uniformDefs: UniformDefs, + attributeDefs: AttributeDefs, + textureDefs: TextureDefs +} + +export function createProgram(ctx: Context, props: ProgramProps): Program { + const { gl, shaderCache } = ctx + const { shaderCode, uniformDefs, attributeDefs, textureDefs } = props 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) + const vertShaderRef = shaderCache.get(ctx, { type: 'vert', source: shaderCode.vert }) + const fragShaderRef = shaderCache.get(ctx, { type: 'frag', source: shaderCode.frag }) - gl.attachShader(program, glVertShader.value) - gl.attachShader(program, glFragShader.value) + vertShaderRef.value.attach(program) + fragShaderRef.value.attach(program) gl.linkProgram(program) - const uniformSetters = getUniformSetters(gl, program, uniformDefs) - const attributeLocations = getAttributeLocations(gl, program, attributeDefs) + const uniformSetters = getUniformSetters(ctx, program, uniformDefs) + const attributeLocations = getAttributeLocations(ctx, program, attributeDefs) const textureUniforms = getTextureUniforms(textureDefs) - const textureUniformSetters = getUniformSetters(gl, program, textureUniforms) + const textureUniformSetters = getUniformSetters(ctx, program, textureUniforms) let destroyed = false return { id: 0, - setUniforms: (uniformValues: Partial<UniformValues<U>>) => { + setUniforms: (uniformValues: UniformValues) => { Object.keys(uniformValues).forEach(k => { const value = uniformValues[k] if (value !== undefined) uniformSetters[k](value) }) }, - bindAttributes: (attribueBuffers: AttributeBuffers<A>) => { + bindAttributes: (attribueBuffers: AttributeBuffers) => { Object.keys(attribueBuffers).forEach(k => { - attribueBuffers[k].bind(attributeLocations[k], 0, 0) + attribueBuffers[k].bind(attributeLocations[k]) }) }, - bindTextures: (textures: Textures<T>) => { + bindTextures: (textures: Textures) => { Object.keys(textures).forEach((k, i) => { textures[k].bind(i as TextureId) textureUniformSetters[k](i) @@ -85,10 +94,20 @@ export function createProgram<U extends UniformDefs, A extends AttributeDefs, T destroy: () => { if (destroyed) return - glVertShader.free() - glFragShader.free() + vertShaderRef.free() + fragShaderRef.free() gl.deleteProgram(program) destroyed = true } } +} + +export type ProgramCache = ReferenceCache<Program, ProgramProps, Context> + +export function createProgramCache(): ProgramCache { + return createReferenceCache( + (props: ProgramProps) => JSON.stringify(props), + (ctx: Context, props: ProgramProps) => createProgram(ctx, props), + (program: Program) => { program.destroy() } + ) } \ No newline at end of file diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts new file mode 100644 index 0000000000000000000000000000000000000000..69302ee033459a14aa120e75a20475d44f1a81cd --- /dev/null +++ b/src/mol-gl/webgl/render-item.ts @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { UniformDefs, UniformValues } from './uniform'; +import { AttributeDefs, AttributeValues, createAttributeBuffers, createElementsBuffer, ElementsKind, ElementsBuffer } from './buffer'; +import { TextureDefs, TextureValues, createTextures } from './texture'; +import { Context } from './context'; +import { ShaderCode } from '../shader-code'; + +export type DrawMode = 'points' | 'lines' | 'line-strip' | 'line-loop' | 'triangles' | 'triangle-strip' | 'triangle-fan' + +export function getDrawMode(ctx: Context, drawMode: DrawMode) { + const { gl } = ctx + switch (drawMode) { + case 'points': return gl.POINTS + case 'lines': return gl.LINES + case 'line-strip': return gl.LINE_STRIP + case 'line-loop': return gl.LINE_LOOP + case 'triangles': return gl.TRIANGLES + case 'triangle-strip': return gl.TRIANGLE_STRIP + case 'triangle-fan': return gl.TRIANGLE_FAN + } +} + +export type RenderItemProps = { + shaderCode: ShaderCode + + uniformDefs: UniformDefs + attributeDefs: AttributeDefs + textureDefs: TextureDefs + + elementsKind?: ElementsKind + drawMode: DrawMode +} + +export type RenderItemState = { + uniformValues: UniformValues + attributeValues: AttributeValues + textureValues: TextureValues + + elements?: Uint32Array + drawCount: number + instanceCount: number +} + +export interface RenderItem { + readonly hash: string + readonly programId: number + + update: (state: RenderItemState) => void + + draw: () => void + dispose: () => void +} + +export function createRenderItem(ctx: Context, props: RenderItemProps, state: RenderItemState): RenderItem { + const { programCache } = ctx + const { angleInstancedArrays } = ctx.extensions + const { shaderCode, uniformDefs, attributeDefs, textureDefs, elementsKind } = props + const { attributeValues, textureValues, uniformValues, elements } = state + + const hash = JSON.stringify(props) + const drawMode = getDrawMode(ctx, props.drawMode) + const programRef = programCache.get(ctx, { shaderCode, uniformDefs, attributeDefs, textureDefs }) + const program = programRef.value + + const attributeBuffers = createAttributeBuffers(ctx, attributeDefs, attributeValues) + const textures = createTextures(ctx, textureDefs, textureValues) + + let elementsBuffer: ElementsBuffer + if (elements && elementsKind) { + elementsBuffer = createElementsBuffer(ctx, elements) + } + + let { drawCount, instanceCount } = state + + return { + hash, + programId: program.id, + + draw: () => { + program.setUniforms(uniformValues) + program.bindAttributes(attributeBuffers) + program.bindTextures(textures) + if (elementsBuffer) { + angleInstancedArrays.drawElementsInstancedANGLE(drawMode, drawCount, elementsBuffer._dataType, 0, instanceCount); + } else { + angleInstancedArrays.drawArraysInstancedANGLE(drawMode, 0, drawCount, instanceCount) + // gl.drawArrays(drawMode, 0, drawCount) + } + }, + update: (state: RenderItemState) => { + // TODO + const { attributeValues } = state + Object.keys(attributeValues).forEach(k => { + const value = attributeValues[k] + if (value !== undefined) attributeBuffers[k].updateData(value) + }) + }, + dispose: () => { + // TODO + programRef.free() + } + } +} \ No newline at end of file diff --git a/src/mol-gl/webgl/shader.ts b/src/mol-gl/webgl/shader.ts index e99b9ae2bf5c245e941cf3d8980396ab24c2d822..08bd601d7d46a86286c6443a10786c9283e37bb2 100644 --- a/src/mol-gl/webgl/shader.ts +++ b/src/mol-gl/webgl/shader.ts @@ -4,7 +4,8 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Context } from './context' +import { createReferenceCache, ReferenceCache } from 'mol-util/reference-cache'; +import { Context } from './context'; function addLineNumbers(source: string) { const lines = source.split('\n') @@ -14,11 +15,18 @@ function addLineNumbers(source: string) { return lines.join('\n') } -type ShaderType = 'vert' | 'frag' +export type ShaderType = 'vert' | 'frag' +export interface ShaderProps { type: ShaderType, source: string } +export interface Shader { + attach: (program: WebGLProgram) => void + destroy: () => void +} -function createShader(gl: WebGLRenderingContext, type: ShaderType, source: string) { - const shader = gl.createShader(type === 'vert' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER) +function createShader(ctx: Context, props: ShaderProps): Shader { + const { gl } = ctx + const { type, source } = props + const shader = gl.createShader(type === 'vert' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER) if (shader === null) { throw new Error(`Error creating ${type} shader`) } @@ -31,23 +39,22 @@ function createShader(gl: WebGLRenderingContext, type: ShaderType, source: strin 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 - } + attach: (program: WebGLProgram) => { + gl.attachShader(program, shader) }, - value: shaderRef.value + destroy: () => { + gl.deleteShader(shader) + } } +} + +export type ShaderCache = ReferenceCache<Shader, ShaderProps, Context> + +export function createShaderCache(): ShaderCache { + return createReferenceCache( + (props: ShaderProps) => JSON.stringify(props), + (ctx: Context, props: ShaderProps) => createShader(ctx, props), + (shader: Shader) => { shader.destroy() } + ) } \ No newline at end of file diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 22897d970b6b60aa3996f6f17129ccf4229e2131..436a93636aaadabca915020ad8d31c9c7567aa5e 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -4,49 +4,62 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -// import { Context } from './context' +import { Context } from './context' +import { TextureImage } from '../renderable/util'; export interface Texture { - load: (image: ImageData) => void + load: (image: TextureImage) => void bind: (id: TextureId) => void + unbind: (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 type TextureDefs = { [k: string]: true } +export type TextureUniforms = { [k: string]: 't2' } +export type TextureValues = { [k: string]: TextureImage } +export type Textures = { [k: string]: Texture } -export function createTexture(gl: WebGLRenderingContext): Texture { +export function createTexture(ctx: Context): Texture { + const { gl } = ctx const texture = gl.createTexture() if (texture === null) { throw new Error('Could not create WebGL texture') } + const _textureType = gl.TEXTURE_2D + const _magFilter = gl.NEAREST + const _minFilter = gl.NEAREST + const _format = gl.RGBA + const _arrayType = gl.UNSIGNED_BYTE + return { - load: (image: ImageData) => { - gl.bindTexture(gl.TEXTURE_2D, texture) + load: (image: TextureImage) => { + const { array, width, height } = image + gl.bindTexture(_textureType, 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) + gl.texImage2D(_textureType, 0, _format, width, height, 0, _format, _arrayType, array) + gl.texParameteri(_textureType, gl.TEXTURE_MAG_FILTER, _magFilter) + gl.texParameteri(_textureType, gl.TEXTURE_MIN_FILTER, _minFilter) + gl.bindTexture(_textureType, null) }, bind: (id: TextureId) => { - gl.activeTexture(gl[`TEXTURE${id}` as TextureTarget]) - gl.bindTexture(gl.TEXTURE_2D, texture) + gl.activeTexture(gl.TEXTURE0 + id) + gl.bindTexture(_textureType, texture) + }, + unbind: (id: TextureId) => { + gl.activeTexture(gl.TEXTURE0 + id) + gl.bindTexture(_textureType, null) } } } -export function createTextures<T extends TextureDefs>(gl: WebGLRenderingContext, props: T, state: TextureValues<T>) { - const textures: Partial<Textures<T>> = {} +export function createTextures(ctx: Context, props: TextureDefs, state: TextureValues) { + const textures: Textures = {} Object.keys(props).forEach(k => { - const texture = createTexture(gl) + const texture = createTexture(ctx) texture.load(state[k]) textures[k] = texture }) - return textures as Textures<T> -} + return textures +} \ No newline at end of file diff --git a/src/mol-gl/webgl/uniform.ts b/src/mol-gl/webgl/uniform.ts index 5a38a5524cfe2f8ddfe717c748b4a05b0e9f88b1..013556f0bb45bfb19b9bd7d0cc22dcf8717fd91d 100644 --- a/src/mol-gl/webgl/uniform.ts +++ b/src/mol-gl/webgl/uniform.ts @@ -5,6 +5,7 @@ */ import { Mat3, Mat4, Vec2, Vec3, Vec4 } from 'mol-math/linear-algebra' +import { Context } from './context'; export type UniformKindValue = { 'f': number @@ -17,29 +18,34 @@ export type UniformKindValue = { 't2': number } export type UniformKind = keyof UniformKindValue +export type UniformType = number | Vec2 | Vec3 | Vec4 | Mat3 | Mat4 | ImageData 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 type UniformValues = { [k: string]: UniformType } +export type UniformSetters = { [k: string]: (value: UniformType) => void } -export function createUniformSetter<K extends UniformKind, V = UniformKindValue[K]>(gl: WebGLRenderingContext, program: WebGLProgram, name: string, kind: K): (value: V) => void { +export function createUniformSetter(ctx: Context, program: WebGLProgram, name: string, kind: UniformKind): (value: any) => void { + const { gl } = ctx const location = gl.getUniformLocation(program, name) + if (location === null) { + throw new Error(`Could not get WebGL uniform location for '${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) + case 'f': return (value: number) => gl.uniform1f(location, value) + case 'i': case 't2': return (value: number) => gl.uniform1i(location, value) + case 'v2': return (value: Vec2) => gl.uniform2fv(location, value) + case 'v3': return (value: Vec3) => gl.uniform3fv(location, value) + case 'v4': return (value: Vec4) => gl.uniform4fv(location, value) + case 'm3': return (value: Mat3) => gl.uniformMatrix3fv(location, false, value) + case 'm4': return (value: Mat4) => gl.uniformMatrix4fv(location, false, value) } 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>> = {} +export function getUniformSetters(ctx: Context, program: WebGLProgram, uniforms: UniformDefs) { + const setters: UniformSetters = {} Object.keys(uniforms).forEach(k => { - setters[k] = createUniformSetter(gl, program, k, uniforms[k]) + setters[k] = createUniformSetter(ctx, program, k, uniforms[k]) }) - return setters as UniformSetters<T> + return setters } \ No newline at end of file diff --git a/src/mol-util/reference-cache.ts b/src/mol-util/reference-cache.ts new file mode 100644 index 0000000000000000000000000000000000000000..38c09d87a0e986a786a2cdd5fca178cb1ddb1a71 --- /dev/null +++ b/src/mol-util/reference-cache.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export interface Reference<T> { value: T, usageCount: number } + +export function createReference<T>(value: T, usageCount = 0) { + return { value, usageCount } +} + +export interface ReferenceItem<T> { + free: () => void + value: T +} + +export function createReferenceItem<T>(ref: Reference<T>) { + return { + free: () => { + ref.usageCount -= 1 + }, + value: ref.value + } +} + +export interface ReferenceCache<T, P, C> { + get: (ctx: C, props: P) => ReferenceItem<T> + clear: () => void + count: number +} + +export function createReferenceCache<T, P, C>(hashFn: (props: P) => string, ctor: (ctx: C, props: P) => T, deleteFn: (v: T) => void): ReferenceCache<T, P, C> { + const map: Map<string, Reference<T>> = new Map() + + return { + get: (ctx: C, props: P) => { + const id = hashFn(props) + let ref = map.get(id) + if (!ref) { + ref = createReference<T>(ctor(ctx, props)) + map.set(id, ref) + } + ref.usageCount += 1 + return createReferenceItem(ref) + }, + clear: () => { + map.forEach((ref, id) => { + if (ref.usageCount <= 0) { + if (ref.usageCount < 0) { + console.warn('Reference usageCount below zero.') + } + deleteFn(ref.value) + } + }) + }, + get count () { + return map.size + } + } +} \ No newline at end of file diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 645ac8cb5df14ecfe73ea6a89df85727d668e3eb..0a0f42a580bf9e2b7774b8fc251b2db4b8d49a91 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -15,6 +15,7 @@ import TrackballControls from './controls/trackball' import { Viewport } from './camera/util' import { PerspectiveCamera } from './camera/perspective' import { resizeCanvas } from './util'; +import { createContext } from 'mol-gl/webgl/context'; interface Viewer { hide: (repr: StructureRepresentation) => void @@ -64,9 +65,12 @@ namespace Viewer { }) const gl = getWebGLContext(canvas) - if (gl === null) throw new Error('Could not create a WebGL rendering context') + if (gl === null) { + throw new Error('Could not create a WebGL rendering context') + } + const ctx = createContext(gl) - const renderer = Renderer.create(gl, camera) + const renderer = Renderer.create(ctx, camera) let drawPending = false const prevProjectionView = Mat4.zero()