grid3d.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /**
  2. * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { RenderableSchema, Values, UnboxedValues, UniformSpec, TextureSpec, DefineSpec, RenderableValues } from '../renderable/schema';
  7. import { WebGLContext } from '../webgl/context';
  8. import { getRegularGrid3dDelta, RegularGrid3d } from '../../mol-math/geometry/common';
  9. import { grid3dTemplate_frag } from '../shader/util/grid3d-template.frag';
  10. import { quad_vert } from '../shader/quad.vert';
  11. import { ShaderCode } from '../shader-code';
  12. import { UUID, ValueCell } from '../../mol-util';
  13. import { objectForEach } from '../../mol-util/object';
  14. import { getUniformGlslType, isUniformValueScalar } from '../webgl/uniform';
  15. import { QuadSchema, QuadValues } from './util';
  16. import { createComputeRenderItem } from '../webgl/render-item';
  17. import { createComputeRenderable } from '../renderable';
  18. import { isLittleEndian } from '../../mol-util/is-little-endian';
  19. import { RuntimeContext } from '../../mol-task';
  20. import { isTimingMode } from '../../mol-util/debug';
  21. export function canComputeGrid3dOnGPU(webgl?: WebGLContext): webgl is WebGLContext {
  22. return !!webgl?.extensions.textureFloat;
  23. }
  24. export interface Grid3DComputeRenderableSpec<S extends RenderableSchema, P, CS> {
  25. schema: S,
  26. // indicate which params are loop bounds for WebGL1 compat
  27. loopBounds?: (keyof S)[]
  28. utilCode?: string,
  29. mainCode: string,
  30. returnCode: string,
  31. values(params: P, grid: RegularGrid3d): UnboxedValues<S>,
  32. cumulative?: {
  33. states(params: P): CS[],
  34. update(params: P, state: CS, values: Values<S>): void,
  35. // call gl.readPixes every 'yieldPeriod' states to split the computation
  36. // into multiple parts, if not set, the computation will be synchronous
  37. yieldPeriod?: number
  38. }
  39. }
  40. const FrameBufferName = 'grid3d-computable' as const;
  41. const Texture0Name = 'grid3d-computable-0' as const;
  42. const Texture1Name = 'grid3d-computable-1' as const;
  43. const SchemaBase = {
  44. ...QuadSchema,
  45. uDimensions: UniformSpec('v3'),
  46. uMin: UniformSpec('v3'),
  47. uDelta: UniformSpec('v3'),
  48. uWidth: UniformSpec('f'),
  49. uLittleEndian: UniformSpec('b'),
  50. };
  51. const CumulativeSumSchema = {
  52. tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
  53. };
  54. export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>(spec: Grid3DComputeRenderableSpec<S, P, CS>) {
  55. const id = UUID.create22();
  56. const uniforms: string[] = [];
  57. objectForEach(spec.schema, (u, k) => {
  58. if (u.type === 'define') return;
  59. if (u.kind.indexOf('[]') >= 0) throw new Error('array uniforms are not supported');
  60. const isBound = (spec.loopBounds?.indexOf(k) ?? -1) >= 0;
  61. if (isBound) uniforms.push(`#ifndef ${k}`);
  62. if (u.type === 'uniform') uniforms.push(`uniform ${getUniformGlslType(u.kind as any)} ${k};`);
  63. else if (u.type === 'texture') uniforms.push(`uniform sampler2D ${k};`);
  64. if (isBound) uniforms.push(`#endif`);
  65. });
  66. const code = grid3dTemplate_frag
  67. .replace('{UNIFORMS}', uniforms.join('\n'))
  68. .replace('{UTILS}', spec.utilCode ?? '')
  69. .replace('{MAIN}', spec.mainCode)
  70. .replace('{RETURN}', spec.returnCode);
  71. const shader = ShaderCode(id, quad_vert, code);
  72. return async (ctx: RuntimeContext, webgl: WebGLContext, grid: RegularGrid3d, params: P) => {
  73. const schema: RenderableSchema = {
  74. ...SchemaBase,
  75. ...(spec.cumulative ? CumulativeSumSchema : {}),
  76. ...spec.schema,
  77. };
  78. if (!webgl.isWebGL2) {
  79. if (spec.loopBounds) {
  80. for (const b of spec.loopBounds) {
  81. (schema as any)[b] = DefineSpec('number');
  82. }
  83. }
  84. (schema as any)['WEBGL1'] = DefineSpec('boolean');
  85. }
  86. if (spec.cumulative) {
  87. (schema as any)['CUMULATIVE'] = DefineSpec('boolean');
  88. }
  89. if (!webgl.namedFramebuffers[FrameBufferName]) {
  90. webgl.namedFramebuffers[FrameBufferName] = webgl.resources.framebuffer();
  91. }
  92. const framebuffer = webgl.namedFramebuffers[FrameBufferName];
  93. if (!webgl.namedTextures[Texture0Name]) {
  94. webgl.namedTextures[Texture0Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
  95. }
  96. if (spec.cumulative && !webgl.namedTextures[Texture1Name]) {
  97. webgl.namedTextures[Texture1Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
  98. }
  99. const tex = [webgl.namedTextures[Texture0Name], webgl.namedTextures[Texture1Name]];
  100. const [nx, ny, nz] = grid.dimensions;
  101. const uWidth = Math.ceil(Math.sqrt(nx * ny * nz));
  102. const values: UnboxedValues<S & typeof SchemaBase> = {
  103. uDimensions: grid.dimensions,
  104. uMin: grid.box.min,
  105. uDelta: getRegularGrid3dDelta(grid),
  106. uWidth,
  107. uLittleEndian: isLittleEndian(),
  108. ...spec.values(params, grid)
  109. } as any;
  110. if (!webgl.isWebGL2) {
  111. (values as any).WEBGL1 = true;
  112. }
  113. if (spec.cumulative) {
  114. (values as any).tCumulativeSum = tex[0];
  115. (values as any).CUMULATIVE = true;
  116. }
  117. let renderable = webgl.namedComputeRenderables[id];
  118. let cells: RenderableValues;
  119. if (renderable) {
  120. cells = renderable.values as RenderableValues;
  121. objectForEach(values, (c, k) => {
  122. const s = schema[k];
  123. if (s?.type === 'value' || s?.type === 'attribute') return;
  124. if (!s || !isUniformValueScalar(s.kind as any)) {
  125. ValueCell.update(cells[k], c);
  126. } else {
  127. ValueCell.updateIfChanged(cells[k], c);
  128. }
  129. });
  130. } else {
  131. cells = {} as any;
  132. objectForEach(QuadValues, (v, k) => (cells as any)[k] = v);
  133. objectForEach(values, (v, k) => (cells as any)[k] = ValueCell.create(v));
  134. renderable = createComputeRenderable(createComputeRenderItem(webgl, 'triangles', shader, schema, cells), cells);
  135. }
  136. const array = new Uint8Array(uWidth * uWidth * 4);
  137. if (spec.cumulative) {
  138. const { gl, state } = webgl;
  139. if (isTimingMode) webgl.timer.mark('Grid3dCompute.renderCumulative');
  140. const states = spec.cumulative.states(params);
  141. tex[0].define(uWidth, uWidth);
  142. tex[1].define(uWidth, uWidth);
  143. resetGl(webgl, uWidth);
  144. state.clearColor(0, 0, 0, 0);
  145. tex[0].attachFramebuffer(framebuffer, 'color0');
  146. gl.clear(gl.COLOR_BUFFER_BIT);
  147. tex[1].attachFramebuffer(framebuffer, 'color0');
  148. gl.clear(gl.COLOR_BUFFER_BIT);
  149. if (spec.cumulative.yieldPeriod && !isTimingMode) {
  150. await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: states.length });
  151. }
  152. const yieldPeriod = Math.max(1, spec.cumulative.yieldPeriod ?? 1 | 0);
  153. if (isTimingMode) webgl.timer.mark('Grid3dCompute.renderBatch');
  154. for (let i = 0; i < states.length; i++) {
  155. ValueCell.update(cells.tCumulativeSum, tex[(i + 1) % 2]);
  156. tex[i % 2].attachFramebuffer(framebuffer, 'color0');
  157. resetGl(webgl, uWidth);
  158. spec.cumulative.update(params, states[i], cells as any);
  159. renderable.update();
  160. renderable.render();
  161. if (spec.cumulative.yieldPeriod && i !== states.length - 1) {
  162. if (i % yieldPeriod === yieldPeriod - 1) {
  163. webgl.waitForGpuCommandsCompleteSync();
  164. if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.renderBatch');
  165. if (isTimingMode) webgl.timer.mark('Grid3dCompute.renderBatch');
  166. }
  167. if (ctx.shouldUpdate && !isTimingMode) {
  168. await ctx.update({ current: i + 1 });
  169. }
  170. }
  171. }
  172. if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.renderBatch');
  173. if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.renderCumulative');
  174. } else {
  175. if (isTimingMode) webgl.timer.mark('Grid3dCompute.render');
  176. tex[0].define(uWidth, uWidth);
  177. tex[0].attachFramebuffer(framebuffer, 'color0');
  178. framebuffer.bind();
  179. resetGl(webgl, uWidth);
  180. renderable.update();
  181. renderable.render();
  182. if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.render');
  183. }
  184. if (isTimingMode) webgl.timer.mark('Grid3dCompute.readPixels');
  185. webgl.readPixels(0, 0, uWidth, uWidth, array);
  186. if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.readPixels');
  187. return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
  188. };
  189. }
  190. function resetGl(webgl: WebGLContext, w: number) {
  191. const { gl, state } = webgl;
  192. gl.viewport(0, 0, w, w);
  193. gl.scissor(0, 0, w, w);
  194. state.disable(gl.SCISSOR_TEST);
  195. state.disable(gl.BLEND);
  196. state.disable(gl.DEPTH_TEST);
  197. state.depthMask(false);
  198. }