UNPKG

molstar

Version:

A comprehensive macromolecular library.

198 lines (197 loc) 10.9 kB
"use strict"; /** * Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.createIsosurfaceBuffers = createIsosurfaceBuffers; exports.extractIsosurface = extractIsosurface; const renderable_1 = require("../../renderable"); const render_item_1 = require("../../webgl/render-item"); const schema_1 = require("../../renderable/schema"); const shader_code_1 = require("../../../mol-gl/shader-code"); const mol_util_1 = require("../../../mol-util"); const util_1 = require("../util"); const reduction_1 = require("../histogram-pyramid/reduction"); const tables_1 = require("./tables"); const quad_vert_1 = require("../../../mol-gl/shader/quad.vert"); const isosurface_frag_1 = require("../../../mol-gl/shader/marching-cubes/isosurface.frag"); const active_voxels_1 = require("./active-voxels"); const compat_1 = require("../../webgl/compat"); const debug_1 = require("../../../mol-util/debug"); const IsosurfaceSchema = { ...util_1.QuadSchema, tTriIndices: (0, schema_1.TextureSpec)('image-uint8', 'alpha', 'ubyte', 'nearest'), tActiveVoxelsPyramid: (0, schema_1.TextureSpec)('texture', 'rgba', 'float', 'nearest'), tActiveVoxelsBase: (0, schema_1.TextureSpec)('texture', 'rgba', 'float', 'nearest'), tVolumeData: (0, schema_1.TextureSpec)('texture', 'rgba', 'ubyte', 'nearest'), dValueChannel: (0, schema_1.DefineSpec)('string', ['red', 'alpha']), uIsoValue: (0, schema_1.UniformSpec)('f'), uSize: (0, schema_1.UniformSpec)('f'), uLevels: (0, schema_1.UniformSpec)('f'), uCount: (0, schema_1.UniformSpec)('f'), uInvert: (0, schema_1.UniformSpec)('b'), uGridDim: (0, schema_1.UniformSpec)('v3'), uGridTexDim: (0, schema_1.UniformSpec)('v3'), uGridTransform: (0, schema_1.UniformSpec)('m4'), uScale: (0, schema_1.UniformSpec)('v2'), dPackedGroup: (0, schema_1.DefineSpec)('boolean'), dAxisOrder: (0, schema_1.DefineSpec)('string', ['012', '021', '102', '120', '201', '210']), dConstantGroup: (0, schema_1.DefineSpec)('boolean'), }; const IsosurfaceName = 'isosurface'; function valueChannel(ctx, volumeData) { return (0, compat_1.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; mol_util_1.ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid); mol_util_1.ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase); mol_util_1.ValueCell.update(v.tVolumeData, volumeData); mol_util_1.ValueCell.update(v.dValueChannel, valueChannel(ctx, volumeData)); mol_util_1.ValueCell.updateIfChanged(v.uIsoValue, isoValue); mol_util_1.ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels)); mol_util_1.ValueCell.updateIfChanged(v.uLevels, levels); mol_util_1.ValueCell.updateIfChanged(v.uCount, count); mol_util_1.ValueCell.updateIfChanged(v.uInvert, invert); mol_util_1.ValueCell.update(v.uGridDim, gridDim); mol_util_1.ValueCell.update(v.uGridTexDim, gridTexDim); mol_util_1.ValueCell.update(v.uGridTransform, transform); mol_util_1.ValueCell.update(v.uScale, scale); mol_util_1.ValueCell.updateIfChanged(v.dPackedGroup, packedGroup); mol_util_1.ValueCell.updateIfChanged(v.dAxisOrder, axisOrder.join('')); mol_util_1.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 = { ...util_1.QuadValues, tTriIndices: mol_util_1.ValueCell.create((0, tables_1.getTriIndices)()), tActiveVoxelsPyramid: mol_util_1.ValueCell.create(activeVoxelsPyramid), tActiveVoxelsBase: mol_util_1.ValueCell.create(activeVoxelsBase), tVolumeData: mol_util_1.ValueCell.create(volumeData), dValueChannel: mol_util_1.ValueCell.create(valueChannel(ctx, volumeData)), uIsoValue: mol_util_1.ValueCell.create(isoValue), uSize: mol_util_1.ValueCell.create(Math.pow(2, levels)), uLevels: mol_util_1.ValueCell.create(levels), uCount: mol_util_1.ValueCell.create(count), uInvert: mol_util_1.ValueCell.create(invert), uGridDim: mol_util_1.ValueCell.create(gridDim), uGridTexDim: mol_util_1.ValueCell.create(gridTexDim), uGridTransform: mol_util_1.ValueCell.create(transform), uScale: mol_util_1.ValueCell.create(scale), dPackedGroup: mol_util_1.ValueCell.create(packedGroup), dAxisOrder: mol_util_1.ValueCell.create(axisOrder.join('')), dConstantGroup: mol_util_1.ValueCell.create(constantGroup), }; const schema = { ...IsosurfaceSchema }; const shaderCode = (0, shader_code_1.ShaderCode)('isosurface', quad_vert_1.quad_vert, isosurface_frag_1.isosurface_frag, { drawBuffers: 'required' }); const renderItem = (0, render_item_1.createComputeRenderItem)(ctx, 'triangles', shaderCode, schema, values); return (0, renderable_1.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); } 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 (debug_1.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 ((0, compat_1.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 (debug_1.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/ */ function extractIsosurface(ctx, volumeData, gridDim, gridTexDim, gridTexScale, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture) { if (debug_1.isTimingMode) ctx.timer.mark('extractIsosurface'); const activeVoxelsTex = (0, active_voxels_1.calcActiveVoxels)(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale); const compacted = (0, reduction_1.createHistogramPyramid)(ctx, activeVoxelsTex, gridTexScale, gridTexDim); const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture); if (debug_1.isTimingMode) ctx.timer.markEnd('extractIsosurface'); return gv; }