program.ts 9.2 KB

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