program.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /**
  2. * Copyright (c) 2018-2022 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, isWebGL2 } 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 '${attribType}' for ${name}, expected '${type}'`);
  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 (isWebGL2(gl)) {
  99. if (type !== gl.SAMPLER_3D) {
  100. throw new Error(`unexpected sampler type for '${name}'`);
  101. }
  102. } else {
  103. throw new Error(`WebGL2 is required to use SAMPLER_3D`);
  104. }
  105. } else {
  106. // TODO
  107. }
  108. } else {
  109. throw new Error(`'${name}' must be of type 'uniform' or 'texture' but is '${spec.type}'`);
  110. }
  111. }
  112. }
  113. }
  114. function checkProgram(gl: GLRenderingContext, program: WebGLProgram) {
  115. // no-op in FF on Mac, see https://bugzilla.mozilla.org/show_bug.cgi?id=1284425
  116. // gl.validateProgram(program)
  117. if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  118. throw new Error(`Could not compile WebGL program. \n\n${gl.getProgramInfoLog(program)}`);
  119. }
  120. }
  121. export interface ProgramProps {
  122. defineValues: DefineValues,
  123. shaderCode: ShaderCode,
  124. schema: RenderableSchema
  125. }
  126. export function getProgram(gl: GLRenderingContext) {
  127. const program = gl.createProgram();
  128. if (program === null) {
  129. throw new Error('Could not create WebGL program');
  130. }
  131. return program;
  132. }
  133. type ShaderGetter = (type: ShaderType, source: string) => Shader
  134. export function createProgram(gl: GLRenderingContext, state: WebGLState, extensions: WebGLExtensions, getShader: ShaderGetter, props: ProgramProps): Program {
  135. const { defineValues, shaderCode: _shaderCode, schema } = props;
  136. let program = getProgram(gl);
  137. const programId = getNextProgramId();
  138. const shaderCode = addShaderDefines(gl, extensions, defineValues, _shaderCode);
  139. const vertShader = getShader('vert', shaderCode.vert);
  140. const fragShader = getShader('frag', shaderCode.frag);
  141. let locations: Locations;
  142. let uniformSetters: UniformSetters;
  143. function init() {
  144. vertShader.attach(program);
  145. fragShader.attach(program);
  146. gl.linkProgram(program);
  147. if (isDebugMode) {
  148. checkProgram(gl, program);
  149. }
  150. locations = getLocations(gl, program, schema);
  151. uniformSetters = getUniformSetters(schema);
  152. if (isDebugMode) {
  153. checkActiveAttributes(gl, program, schema);
  154. checkActiveUniforms(gl, program, schema);
  155. }
  156. }
  157. init();
  158. let destroyed = false;
  159. return {
  160. id: programId,
  161. use: () => {
  162. // console.log('use', programId)
  163. state.currentProgramId = programId;
  164. gl.useProgram(program);
  165. },
  166. setUniforms: (uniformValues: UniformsList) => {
  167. for (let i = 0, il = uniformValues.length; i < il; ++i) {
  168. const [k, v] = uniformValues[i];
  169. if (v) {
  170. const l = locations[k];
  171. if (l !== null) uniformSetters[k](gl, l, v.ref.value);
  172. }
  173. }
  174. },
  175. bindAttributes: (attributeBuffers: AttributeBuffers) => {
  176. state.clearVertexAttribsState();
  177. for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
  178. const [k, buffer] = attributeBuffers[i];
  179. const l = locations[k];
  180. if (l !== -1) buffer.bind(l);
  181. }
  182. state.disableUnusedVertexAttribs();
  183. },
  184. bindTextures: (textures: Textures, startingTargetUnit: number) => {
  185. for (let i = 0, il = textures.length; i < il; ++i) {
  186. const [k, texture] = textures[i];
  187. const l = locations[k];
  188. if (l !== null && l !== undefined) {
  189. texture.bind((i + startingTargetUnit) as TextureId);
  190. uniformSetters[k](gl, l, (i + startingTargetUnit) as TextureId);
  191. }
  192. }
  193. },
  194. reset: () => {
  195. program = getProgram(gl);
  196. init();
  197. },
  198. destroy: () => {
  199. if (destroyed) return;
  200. vertShader.destroy();
  201. fragShader.destroy();
  202. gl.deleteProgram(program);
  203. destroyed = true;
  204. }
  205. };
  206. }