program.ts 5.4 KB

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