123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- /**
- * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
- import { RenderableSchema, Values, UnboxedValues, UniformSpec, TextureSpec, DefineSpec, RenderableValues } from '../renderable/schema';
- import { WebGLContext } from '../webgl/context';
- import { getRegularGrid3dDelta, RegularGrid3d } from '../../mol-math/geometry/common';
- import { grid3dTemplate_frag } from '../shader/util/grid3d-template.frag';
- import { quad_vert } from '../shader/quad.vert';
- import { ShaderCode } from '../shader-code';
- import { UUID, ValueCell } from '../../mol-util';
- import { objectForEach } from '../../mol-util/object';
- import { getUniformGlslType, isUniformValueScalar } from '../webgl/uniform';
- import { QuadSchema, QuadValues } from './util';
- import { createComputeRenderItem } from '../webgl/render-item';
- import { createComputeRenderable } from '../renderable';
- import { isLittleEndian } from '../../mol-util/is-little-endian';
- import { RuntimeContext } from '../../mol-task';
- import { isTimingMode } from '../../mol-util/debug';
- export function canComputeGrid3dOnGPU(webgl?: WebGLContext): webgl is WebGLContext {
- return !!webgl?.extensions.textureFloat;
- }
- export interface Grid3DComputeRenderableSpec<S extends RenderableSchema, P, CS> {
- schema: S,
- // indicate which params are loop bounds for WebGL1 compat
- loopBounds?: (keyof S)[]
- utilCode?: string,
- mainCode: string,
- returnCode: string,
- values(params: P, grid: RegularGrid3d): UnboxedValues<S>,
- cumulative?: {
- states(params: P): CS[],
- update(params: P, state: CS, values: Values<S>): void,
- // call gl.readPixes every 'yieldPeriod' states to split the computation
- // into multiple parts, if not set, the computation will be synchronous
- yieldPeriod?: number
- }
- }
- const FrameBufferName = 'grid3d-computable' as const;
- const Texture0Name = 'grid3d-computable-0' as const;
- const Texture1Name = 'grid3d-computable-1' as const;
- const SchemaBase = {
- ...QuadSchema,
- uDimensions: UniformSpec('v3'),
- uMin: UniformSpec('v3'),
- uDelta: UniformSpec('v3'),
- uWidth: UniformSpec('f'),
- uLittleEndian: UniformSpec('b'),
- };
- const CumulativeSumSchema = {
- tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
- };
- export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>(spec: Grid3DComputeRenderableSpec<S, P, CS>) {
- const id = UUID.create22();
- const uniforms: string[] = [];
- objectForEach(spec.schema, (u, k) => {
- if (u.type === 'define') return;
- if (u.kind.indexOf('[]') >= 0) throw new Error('array uniforms are not supported');
- const isBound = (spec.loopBounds?.indexOf(k) ?? -1) >= 0;
- if (isBound) uniforms.push(`#ifndef ${k}`);
- if (u.type === 'uniform') uniforms.push(`uniform ${getUniformGlslType(u.kind as any)} ${k};`);
- else if (u.type === 'texture') uniforms.push(`uniform sampler2D ${k};`);
- if (isBound) uniforms.push(`#endif`);
- });
- const code = grid3dTemplate_frag
- .replace('{UNIFORMS}', uniforms.join('\n'))
- .replace('{UTILS}', spec.utilCode ?? '')
- .replace('{MAIN}', spec.mainCode)
- .replace('{RETURN}', spec.returnCode);
- const shader = ShaderCode(id, quad_vert, code);
- return async (ctx: RuntimeContext, webgl: WebGLContext, grid: RegularGrid3d, params: P) => {
- const schema: RenderableSchema = {
- ...SchemaBase,
- ...(spec.cumulative ? CumulativeSumSchema : {}),
- ...spec.schema,
- };
- if (!webgl.isWebGL2) {
- if (spec.loopBounds) {
- for (const b of spec.loopBounds) {
- (schema as any)[b] = DefineSpec('number');
- }
- }
- (schema as any)['WEBGL1'] = DefineSpec('boolean');
- }
- if (spec.cumulative) {
- (schema as any)['CUMULATIVE'] = DefineSpec('boolean');
- }
- if (!webgl.namedFramebuffers[FrameBufferName]) {
- webgl.namedFramebuffers[FrameBufferName] = webgl.resources.framebuffer();
- }
- const framebuffer = webgl.namedFramebuffers[FrameBufferName];
- if (!webgl.namedTextures[Texture0Name]) {
- webgl.namedTextures[Texture0Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
- }
- if (spec.cumulative && !webgl.namedTextures[Texture1Name]) {
- webgl.namedTextures[Texture1Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
- }
- const tex = [webgl.namedTextures[Texture0Name], webgl.namedTextures[Texture1Name]];
- const [nx, ny, nz] = grid.dimensions;
- const uWidth = Math.ceil(Math.sqrt(nx * ny * nz));
- const values: UnboxedValues<S & typeof SchemaBase> = {
- uDimensions: grid.dimensions,
- uMin: grid.box.min,
- uDelta: getRegularGrid3dDelta(grid),
- uWidth,
- uLittleEndian: isLittleEndian(),
- ...spec.values(params, grid)
- } as any;
- if (!webgl.isWebGL2) {
- (values as any).WEBGL1 = true;
- }
- if (spec.cumulative) {
- (values as any).tCumulativeSum = tex[0];
- (values as any).CUMULATIVE = true;
- }
- let renderable = webgl.namedComputeRenderables[id];
- let cells: RenderableValues;
- if (renderable) {
- cells = renderable.values as RenderableValues;
- objectForEach(values, (c, k) => {
- const s = schema[k];
- if (s?.type === 'value' || s?.type === 'attribute') return;
- if (!s || !isUniformValueScalar(s.kind as any)) {
- ValueCell.update(cells[k], c);
- } else {
- ValueCell.updateIfChanged(cells[k], c);
- }
- });
- } else {
- cells = {} as any;
- objectForEach(QuadValues, (v, k) => (cells as any)[k] = v);
- objectForEach(values, (v, k) => (cells as any)[k] = ValueCell.create(v));
- renderable = createComputeRenderable(createComputeRenderItem(webgl, 'triangles', shader, schema, cells), cells);
- }
- const array = new Uint8Array(uWidth * uWidth * 4);
- if (spec.cumulative) {
- const { gl, state } = webgl;
- if (isTimingMode) webgl.timer.mark('Grid3dCompute.renderCumulative');
- const states = spec.cumulative.states(params);
- tex[0].define(uWidth, uWidth);
- tex[1].define(uWidth, uWidth);
- resetGl(webgl, uWidth);
- state.clearColor(0, 0, 0, 0);
- tex[0].attachFramebuffer(framebuffer, 'color0');
- gl.clear(gl.COLOR_BUFFER_BIT);
- tex[1].attachFramebuffer(framebuffer, 'color0');
- gl.clear(gl.COLOR_BUFFER_BIT);
- if (spec.cumulative.yieldPeriod && !isTimingMode) {
- await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: states.length });
- }
- const yieldPeriod = Math.max(1, spec.cumulative.yieldPeriod ?? 1 | 0);
- if (isTimingMode) webgl.timer.mark('Grid3dCompute.renderBatch');
- for (let i = 0; i < states.length; i++) {
- ValueCell.update(cells.tCumulativeSum, tex[(i + 1) % 2]);
- tex[i % 2].attachFramebuffer(framebuffer, 'color0');
- resetGl(webgl, uWidth);
- spec.cumulative.update(params, states[i], cells as any);
- renderable.update();
- renderable.render();
- if (spec.cumulative.yieldPeriod && i !== states.length - 1) {
- if (i % yieldPeriod === yieldPeriod - 1) {
- webgl.waitForGpuCommandsCompleteSync();
- if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.renderBatch');
- if (isTimingMode) webgl.timer.mark('Grid3dCompute.renderBatch');
- }
- if (ctx.shouldUpdate && !isTimingMode) {
- await ctx.update({ current: i + 1 });
- }
- }
- }
- if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.renderBatch');
- if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.renderCumulative');
- } else {
- if (isTimingMode) webgl.timer.mark('Grid3dCompute.render');
- tex[0].define(uWidth, uWidth);
- tex[0].attachFramebuffer(framebuffer, 'color0');
- framebuffer.bind();
- resetGl(webgl, uWidth);
- renderable.update();
- renderable.render();
- if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.render');
- }
- if (isTimingMode) webgl.timer.mark('Grid3dCompute.readPixels');
- webgl.readPixels(0, 0, uWidth, uWidth, array);
- if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.readPixels');
- return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
- };
- }
- function resetGl(webgl: WebGLContext, w: number) {
- const { gl, state } = webgl;
- gl.viewport(0, 0, w, w);
- gl.scissor(0, 0, w, w);
- state.disable(gl.SCISSOR_TEST);
- state.disable(gl.BLEND);
- state.disable(gl.DEPTH_TEST);
- state.depthMask(false);
- }
|