program.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /**
  2. * Copyright (c) 2018-2020 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 { WebGLState } from './state';
  8. import { WebGLExtensions } from './extensions';
  9. import { getUniformSetters, UniformsList, getUniformType, UniformSetters } from './uniform';
  10. import { AttributeBuffers, getAttribType } from './buffer';
  11. import { TextureId, Textures } from './texture';
  12. import { idFactory } from '../../mol-util/id-factory';
  13. import { RenderableSchema } from '../renderable/schema';
  14. import { isDebugMode } from '../../mol-util/debug';
  15. import { GLRenderingContext } from './compat';
  16. import { ShaderType, Shader } from './shader';
  17. const getNextProgramId = idFactory();
  18. export interface Program {
  19. readonly id: number
  20. use: () => void
  21. setUniforms: (uniformValues: UniformsList) => void
  22. bindAttributes: (attribueBuffers: AttributeBuffers) => void
  23. bindTextures: (textures: Textures, startingTargetUnit: number) => void
  24. reset: () => void
  25. destroy: () => void
  26. }
  27. type Locations = { [k: string]: number }
  28. function getLocations(gl: GLRenderingContext, program: WebGLProgram, schema: RenderableSchema) {
  29. const locations: Locations = {};
  30. Object.keys(schema).forEach(k => {
  31. const spec = schema[k];
  32. if (spec.type === 'attribute') {
  33. const loc = gl.getAttribLocation(program, k);
  34. // unused attributes will result in a `-1` location which is usually fine
  35. // if (loc === -1) console.info(`Could not get attribute location for '${k}'`)
  36. locations[k] = loc;
  37. } else if (spec.type === 'uniform' || spec.type === 'texture') {
  38. const loc = gl.getUniformLocation(program, k);
  39. // unused uniforms will result in a `null` location which is usually fine
  40. // if (loc === null) console.info(`Could not get uniform location for '${k}'`)
  41. locations[k] = loc as number;
  42. }
  43. });
  44. return locations;
  45. }
  46. function checkActiveAttributes(gl: GLRenderingContext, program: WebGLProgram, schema: RenderableSchema) {
  47. const attribCount = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
  48. for (let i = 0; i < attribCount; ++i) {
  49. const info = gl.getActiveAttrib(program, i);
  50. if (info) {
  51. const { name, type } = info;
  52. if (name.startsWith('__activeAttribute')) {
  53. // name assigned by `gl.shim.ts`, ignore for checks
  54. continue;
  55. }
  56. if (name === 'gl_InstanceID') continue; // WebGL2 built-in
  57. if (name === 'gl_VertexID') continue; // WebGL2 built-in
  58. const spec = schema[name];
  59. if (spec === undefined) {
  60. throw new Error(`missing 'uniform' or 'texture' with name '${name}' in schema`);
  61. }
  62. if (spec.type !== 'attribute') {
  63. throw new Error(`'${name}' must be of type 'attribute' but is '${spec.type}'`);
  64. }
  65. const attribType = getAttribType(gl, spec.kind, spec.itemSize);
  66. if (attribType !== type) {
  67. throw new Error(`unexpected attribute type for ${name}`);
  68. }
  69. }
  70. }
  71. }
  72. function checkActiveUniforms(gl: GLRenderingContext, program: WebGLProgram, schema: RenderableSchema) {
  73. const attribCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
  74. for (let i = 0; i < attribCount; ++i) {
  75. const info = gl.getActiveUniform(program, i);
  76. if (info) {
  77. const { name, type } = info;
  78. if (name.startsWith('__activeUniform')) {
  79. // name assigned by `gl.shim.ts`, ignore for checks
  80. continue;
  81. }
  82. const baseName = name.replace(/[[0-9]+\]$/, ''); // 'array' uniforms
  83. const spec = schema[baseName];
  84. if (spec === undefined) {
  85. throw new Error(`missing 'uniform' or 'texture' with name '${name}' in schema`);
  86. }
  87. if (spec.type === 'uniform') {
  88. const uniformType = getUniformType(gl, spec.kind);
  89. if (uniformType !== type) {
  90. throw new Error(`unexpected uniform type for ${name}`);
  91. }
  92. } else if (spec.type === 'texture') {
  93. if (spec.kind === 'image-float32' || spec.kind === 'image-uint8') {
  94. if (type !== gl.SAMPLER_2D) {
  95. throw new Error(`unexpected sampler type for '${name}'`);
  96. }
  97. } else if (spec.kind === 'volume-float32' || spec.kind === 'volume-uint8') {
  98. if (type !== (gl as WebGL2RenderingContext).SAMPLER_3D) {
  99. throw new Error(`unexpected sampler type for '${name}'`);
  100. }
  101. } else {
  102. // TODO
  103. }
  104. } else {
  105. throw new Error(`'${name}' must be of type 'uniform' or 'texture' but is '${spec.type}'`);
  106. }
  107. }
  108. }
  109. }
  110. function checkProgram(gl: GLRenderingContext, program: WebGLProgram) {
  111. // no-op in FF on Mac, see https://bugzilla.mozilla.org/show_bug.cgi?id=1284425
  112. // gl.validateProgram(program)
  113. if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  114. throw new Error(`Could not compile WebGL program. \n\n${gl.getProgramInfoLog(program)}`);
  115. }
  116. }
  117. export interface ProgramProps {
  118. defineValues: DefineValues,
  119. shaderCode: ShaderCode,
  120. schema: RenderableSchema
  121. }
  122. export function getProgram(gl: GLRenderingContext) {
  123. const program = gl.createProgram();
  124. if (program === null) {
  125. throw new Error('Could not create WebGL program');
  126. }
  127. return program;
  128. }
  129. type ShaderGetter = (type: ShaderType, source: string) => Shader
  130. export function createProgram(gl: GLRenderingContext, state: WebGLState, extensions: WebGLExtensions, getShader: ShaderGetter, props: ProgramProps): Program {
  131. const { defineValues, shaderCode: _shaderCode, schema } = props;
  132. let program = getProgram(gl);
  133. const programId = getNextProgramId();
  134. const shaderCode = addShaderDefines(gl, extensions, defineValues, _shaderCode);
  135. const vertShader = getShader('vert', shaderCode.vert);
  136. const fragShader = getShader('frag', shaderCode.frag);
  137. let locations: Locations;
  138. let uniformSetters: UniformSetters;
  139. function init() {
  140. vertShader.attach(program);
  141. fragShader.attach(program);
  142. gl.linkProgram(program);
  143. if (isDebugMode) {
  144. checkProgram(gl, program);
  145. }
  146. locations = getLocations(gl, program, schema);
  147. uniformSetters = getUniformSetters(schema);
  148. if (isDebugMode) {
  149. checkActiveAttributes(gl, program, schema);
  150. checkActiveUniforms(gl, program, schema);
  151. }
  152. }
  153. init();
  154. let destroyed = false;
  155. return {
  156. id: programId,
  157. use: () => {
  158. // console.log('use', programId)
  159. state.currentProgramId = programId;
  160. gl.useProgram(program);
  161. },
  162. setUniforms: (uniformValues: UniformsList) => {
  163. for (let i = 0, il = uniformValues.length; i < il; ++i) {
  164. const [k, v] = uniformValues[i];
  165. if (v) {
  166. const l = locations[k];
  167. if (l !== null) uniformSetters[k](gl, l, v.ref.value);
  168. }
  169. }
  170. },
  171. bindAttributes: (attributeBuffers: AttributeBuffers) => {
  172. for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
  173. const [k, buffer] = attributeBuffers[i];
  174. const l = locations[k];
  175. if (l !== -1) buffer.bind(l);
  176. }
  177. },
  178. bindTextures: (textures: Textures, startingTargetUnit: number) => {
  179. for (let i = 0, il = textures.length; i < il; ++i) {
  180. const [k, texture] = textures[i];
  181. const l = locations[k];
  182. if (l !== null && l !== undefined) {
  183. texture.bind((i + startingTargetUnit) as TextureId);
  184. uniformSetters[k](gl, l, (i + startingTargetUnit) as TextureId);
  185. }
  186. }
  187. },
  188. reset: () => {
  189. program = getProgram(gl);
  190. init();
  191. },
  192. destroy: () => {
  193. if (destroyed) return;
  194. vertShader.destroy();
  195. fragShader.destroy();
  196. gl.deleteProgram(program);
  197. destroyed = true;
  198. }
  199. };
  200. }