molstar
Version:
A comprehensive macromolecular library.
194 lines (193 loc) • 10 kB
JavaScript
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createComputeRenderable } from '../../renderable';
import { createComputeRenderItem } from '../../webgl/render-item';
import { TextureSpec, UniformSpec, DefineSpec } from '../../renderable/schema';
import { ShaderCode } from '../../../mol-gl/shader-code';
import { ValueCell } from '../../../mol-util';
import { QuadSchema, QuadValues } from '../util';
import { createHistogramPyramid } from '../histogram-pyramid/reduction';
import { getTriIndices } from './tables';
import { quad_vert } from '../../../mol-gl/shader/quad.vert';
import { isosurface_frag } from '../../../mol-gl/shader/marching-cubes/isosurface.frag';
import { calcActiveVoxels } from './active-voxels';
import { isWebGL2 } from '../../webgl/compat';
import { isTimingMode } from '../../../mol-util/debug';
const IsosurfaceSchema = {
...QuadSchema,
tTriIndices: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
tActiveVoxelsPyramid: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tActiveVoxelsBase: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tVolumeData: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
dValueChannel: DefineSpec('string', ['red', 'alpha']),
uIsoValue: UniformSpec('f'),
uSize: UniformSpec('f'),
uLevels: UniformSpec('f'),
uCount: UniformSpec('f'),
uInvert: UniformSpec('b'),
uGridDim: UniformSpec('v3'),
uGridTexDim: UniformSpec('v3'),
uGridTransform: UniformSpec('m4'),
uScale: UniformSpec('v2'),
dPackedGroup: DefineSpec('boolean'),
dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
dConstantGroup: DefineSpec('boolean'),
};
const IsosurfaceName = 'isosurface';
function valueChannel(ctx, volumeData) {
return isWebGL2(ctx.gl) && volumeData.format === ctx.gl.RED ? 'red' : 'alpha';
}
function getIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup) {
if (ctx.namedComputeRenderables[IsosurfaceName]) {
const v = ctx.namedComputeRenderables[IsosurfaceName].values;
ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid);
ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase);
ValueCell.update(v.tVolumeData, volumeData);
ValueCell.update(v.dValueChannel, valueChannel(ctx, volumeData));
ValueCell.updateIfChanged(v.uIsoValue, isoValue);
ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels));
ValueCell.updateIfChanged(v.uLevels, levels);
ValueCell.updateIfChanged(v.uCount, count);
ValueCell.updateIfChanged(v.uInvert, invert);
ValueCell.update(v.uGridDim, gridDim);
ValueCell.update(v.uGridTexDim, gridTexDim);
ValueCell.update(v.uGridTransform, transform);
ValueCell.update(v.uScale, scale);
ValueCell.updateIfChanged(v.dPackedGroup, packedGroup);
ValueCell.updateIfChanged(v.dAxisOrder, axisOrder.join(''));
ValueCell.updateIfChanged(v.dConstantGroup, constantGroup);
ctx.namedComputeRenderables[IsosurfaceName].update();
}
else {
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
}
return ctx.namedComputeRenderables[IsosurfaceName];
}
function createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup) {
// console.log('uSize', Math.pow(2, levels))
const values = {
...QuadValues,
tTriIndices: ValueCell.create(getTriIndices()),
tActiveVoxelsPyramid: ValueCell.create(activeVoxelsPyramid),
tActiveVoxelsBase: ValueCell.create(activeVoxelsBase),
tVolumeData: ValueCell.create(volumeData),
dValueChannel: ValueCell.create(valueChannel(ctx, volumeData)),
uIsoValue: ValueCell.create(isoValue),
uSize: ValueCell.create(Math.pow(2, levels)),
uLevels: ValueCell.create(levels),
uCount: ValueCell.create(count),
uInvert: ValueCell.create(invert),
uGridDim: ValueCell.create(gridDim),
uGridTexDim: ValueCell.create(gridTexDim),
uGridTransform: ValueCell.create(transform),
uScale: ValueCell.create(scale),
dPackedGroup: ValueCell.create(packedGroup),
dAxisOrder: ValueCell.create(axisOrder.join('')),
dConstantGroup: ValueCell.create(constantGroup),
};
const schema = { ...IsosurfaceSchema };
const shaderCode = ShaderCode('isosurface', quad_vert, isosurface_frag, { drawBuffers: 'required' });
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function setRenderingDefaults(ctx) {
const { gl, state } = ctx;
state.disable(gl.CULL_FACE);
state.disable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.disable(gl.SCISSOR_TEST);
state.depthMask(false);
state.colorMask(true, true, true, true);
state.clearColor(0, 0, 0, 0);
}
export function createIsosurfaceBuffers(ctx, activeVoxelsBase, volumeData, histogramPyramid, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture) {
const { drawBuffers } = ctx.extensions;
if (!drawBuffers)
throw new Error('need WebGL draw buffers');
if (isTimingMode)
ctx.timer.mark('createIsosurfaceBuffers');
const { gl, state, resources, extensions } = ctx;
const { pyramidTex, height, levels, scale, count } = histogramPyramid;
const width = pyramidTex.getWidth();
// console.log('width', width, 'height', height);
// console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim);
// console.log('iso volumeData', volumeData);
if (!ctx.namedFramebuffers[IsosurfaceName]) {
ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
}
const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
if (isWebGL2(gl)) {
if (!vertexTexture) {
vertexTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!groupTexture) {
groupTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
}
if (!normalTexture) {
normalTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
: resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
}
else {
// webgl1 requires consistent bit plane counts
// this is quite wasteful but good enough for medium size meshes
if (!vertexTexture) {
vertexTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!groupTexture) {
groupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
if (!normalTexture) {
normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
}
}
vertexTexture.define(width, height);
groupTexture.define(width, height);
normalTexture.define(width, height);
vertexTexture.attachFramebuffer(framebuffer, 0);
groupTexture.attachFramebuffer(framebuffer, 1);
normalTexture.attachFramebuffer(framebuffer, 2);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup);
ctx.state.currentRenderItemId = -1;
framebuffer.bind();
drawBuffers.drawBuffers([
drawBuffers.COLOR_ATTACHMENT0,
drawBuffers.COLOR_ATTACHMENT1,
drawBuffers.COLOR_ATTACHMENT2,
]);
setRenderingDefaults(ctx);
state.viewport(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
renderable.render();
gl.finish();
if (isTimingMode)
ctx.timer.markEnd('createIsosurfaceBuffers');
// printTextureImage(readTexture(ctx, vertexTexture, new Float32Array(width * height * 4)), { scale: 0.75, normalize: true });
// printTextureImage(readTexture(ctx, groupTexture, new Uint8Array(width * height * 4)), { scale: 0.75, normalize: true });
// printTextureImage(readTexture(ctx, normalTexture, new Float32Array(width * height * 4)), { scale: 0.75, normalize: true });
return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
}
//
/**
* GPU isosurface extraction
*
* Algorithm from "High‐speed Marching Cubes using HistoPyramids"
* by C Dyken, G Ziegler, C Theobalt, HP Seidel
* https://doi.org/10.1111/j.1467-8659.2008.01182.x
*
* Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
*/
export function extractIsosurface(ctx, volumeData, gridDim, gridTexDim, gridTexScale, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture) {
if (isTimingMode)
ctx.timer.mark('extractIsosurface');
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture);
if (isTimingMode)
ctx.timer.markEnd('extractIsosurface');
return gv;
}