program.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ShaderCode, DefineValues, addShaderDefines } from '../shader-code'
  7. import { WebGLContext } from './context';
  8. import { UniformValues, getUniformSetters } from './uniform';
  9. import { AttributeBuffers } from './buffer';
  10. import { Textures, TextureId } from './texture';
  11. import { createReferenceCache, ReferenceCache } from 'mol-util/reference-cache';
  12. import { idFactory } from 'mol-util/id-factory';
  13. import { RenderableSchema } from '../renderable/schema';
  14. import { hashFnv32a, hashString } from 'mol-data/util';
  15. const getNextProgramId = idFactory()
  16. export interface Program {
  17. readonly id: number
  18. use: () => void
  19. setUniforms: (uniformValues: UniformValues) => void
  20. bindAttributes: (attribueBuffers: AttributeBuffers) => void
  21. bindTextures: (textures: Textures) => void
  22. destroy: () => void
  23. }
  24. type Locations = { [k: string]: number }
  25. function getLocations(ctx: WebGLContext, program: WebGLProgram, schema: RenderableSchema) {
  26. const { gl } = ctx
  27. const locations: Locations = {}
  28. Object.keys(schema).forEach(k => {
  29. const spec = schema[k]
  30. if (spec.type === 'attribute') {
  31. const loc = gl.getAttribLocation(program, k)
  32. // if (loc === -1) console.info(`Could not get attribute location for '${k}'`)
  33. locations[k] = loc
  34. } else if (spec.type === 'uniform' || spec.type === 'texture') {
  35. const loc = gl.getUniformLocation(program, k)
  36. // if (loc === null) console.info(`Could not get uniform location for '${k}'`)
  37. locations[k] = loc as number
  38. }
  39. })
  40. return locations
  41. }
  42. export interface ProgramProps {
  43. defineValues: DefineValues,
  44. shaderCode: ShaderCode,
  45. schema: RenderableSchema
  46. }
  47. export function createProgram(ctx: WebGLContext, props: ProgramProps): Program {
  48. const { gl, shaderCache } = ctx
  49. const { defineValues, shaderCode: _shaderCode, schema } = props
  50. const program = gl.createProgram()
  51. if (program === null) {
  52. throw new Error('Could not create WebGL program')
  53. }
  54. const programId = getNextProgramId()
  55. const shaderCode = addShaderDefines(ctx, defineValues, _shaderCode)
  56. const vertShaderRef = shaderCache.get(ctx, { type: 'vert', source: shaderCode.vert })
  57. const fragShaderRef = shaderCache.get(ctx, { type: 'frag', source: shaderCode.frag })
  58. vertShaderRef.value.attach(program)
  59. fragShaderRef.value.attach(program)
  60. gl.linkProgram(program)
  61. if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  62. throw new Error(`Could not compile WebGL program. \n\n${gl.getProgramInfoLog(program)}`);
  63. }
  64. const locations = getLocations(ctx, program, schema)
  65. const uniformSetters = getUniformSetters(schema)
  66. let destroyed = false
  67. return {
  68. id: programId,
  69. use: () => {
  70. // console.log('use', programId)
  71. ctx.currentProgramId = programId
  72. gl.useProgram(program)
  73. },
  74. setUniforms: (uniformValues: UniformValues) => {
  75. const uniformKeys = Object.keys(uniformValues)
  76. for (let i = 0, il = uniformKeys.length; i < il; ++i) {
  77. const k = uniformKeys[i]
  78. const l = locations[k]
  79. const v = uniformValues[k]
  80. if (v) uniformSetters[k](gl, l, v.ref.value)
  81. }
  82. },
  83. bindAttributes: (attribueBuffers: AttributeBuffers) => {
  84. const attributeKeys = Object.keys(attribueBuffers)
  85. for (let i = 0, il = attributeKeys.length; i < il; ++i) {
  86. const k = attributeKeys[i]
  87. const l = locations[k]
  88. if (l !== -1) attribueBuffers[k].bind(l)
  89. }
  90. },
  91. bindTextures: (textures: Textures) => {
  92. const textureKeys = Object.keys(textures)
  93. for (let i = 0, il = textureKeys.length; i < il; ++i) {
  94. const k = textureKeys[i]
  95. const l = locations[k]
  96. textures[k].bind(i as TextureId)
  97. uniformSetters[k](gl, l, i as TextureId)
  98. }
  99. },
  100. destroy: () => {
  101. if (destroyed) return
  102. vertShaderRef.free()
  103. fragShaderRef.free()
  104. gl.deleteProgram(program)
  105. destroyed = true
  106. }
  107. }
  108. }
  109. export type ProgramCache = ReferenceCache<Program, ProgramProps, WebGLContext>
  110. function defineValueHash(v: boolean | number | string): number {
  111. return typeof v === 'boolean' ? (v ? 1 : 0) :
  112. typeof v === 'number' ? v : hashString(v)
  113. }
  114. export function createProgramCache(): ProgramCache {
  115. return createReferenceCache(
  116. (props: ProgramProps) => {
  117. const array = [ props.shaderCode.id ]
  118. Object.keys(props.defineValues).forEach(k => {
  119. const v = props.defineValues[k].ref.value
  120. array.push(hashString(k), defineValueHash(v))
  121. })
  122. return hashFnv32a(array).toString()
  123. },
  124. (ctx: WebGLContext, props: ProgramProps) => createProgram(ctx, props),
  125. (program: Program) => { program.destroy() }
  126. )
  127. }