molstar
Version:
A comprehensive macromolecular library.
383 lines • 20 kB
JavaScript
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
*/
import { __assign } from "tslib";
import { Box3D } from '../../geometry';
import { OrderedSet } from '../../../mol-data/int';
import { Vec3, Tensor, Mat4, Vec2 } from '../../linear-algebra';
import { ValueCell } from '../../../mol-util';
import { createComputeRenderable } from '../../../mol-gl/renderable';
import { decodeFloatRGB } from '../../../mol-util/float-packing';
import { ShaderCode } from '../../../mol-gl/shader-code';
import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, DefineSpec } from '../../../mol-gl/renderable/schema';
import { gaussianDensity_vert } from '../../../mol-gl/shader/gaussian-density.vert';
import { gaussianDensity_frag } from '../../../mol-gl/shader/gaussian-density.frag';
var GaussianDensitySchema = {
drawCount: ValueSpec('number'),
instanceCount: ValueSpec('number'),
aRadius: AttributeSpec('float32', 1, 0),
aPosition: AttributeSpec('float32', 3, 0),
aGroup: AttributeSpec('float32', 1, 0),
uCurrentSlice: UniformSpec('f'),
uCurrentX: UniformSpec('f'),
uCurrentY: UniformSpec('f'),
uBboxMin: UniformSpec('v3', 'material'),
uBboxSize: UniformSpec('v3', 'material'),
uGridDim: UniformSpec('v3', 'material'),
uGridTexDim: UniformSpec('v3', 'material'),
uGridTexScale: UniformSpec('v2', 'material'),
uAlpha: UniformSpec('f', 'material'),
uResolution: UniformSpec('f', 'material'),
uRadiusFactorInv: UniformSpec('f', 'material'),
tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
dGridTexType: DefineSpec('string', ['2d', '3d']),
dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']),
};
var GaussianDensityName = 'gaussian-density';
function getFramebuffer(webgl) {
if (!webgl.namedFramebuffers[GaussianDensityName]) {
webgl.namedFramebuffers[GaussianDensityName] = webgl.resources.framebuffer();
}
return webgl.namedFramebuffers[GaussianDensityName];
}
function getTexture(name, webgl, kind, format, type, filter) {
var _name = GaussianDensityName + "-" + name;
if (!webgl.namedTextures[_name]) {
webgl.namedTextures[_name] = webgl.resources.texture(kind, format, type, filter);
}
return webgl.namedTextures[_name];
}
export function GaussianDensityGPU(position, box, radius, props, webgl) {
// always use texture2d when the gaussian density needs to be downloaded from the GPU,
// it's faster than texture3d
// console.time('GaussianDensityTexture2d')
var tmpTexture = getTexture('tmp', webgl, 'image-uint8', 'rgba', 'ubyte', 'linear');
var _a = calcGaussianDensityTexture2d(webgl, position, box, radius, false, props, tmpTexture), scale = _a.scale, bbox = _a.bbox, texture = _a.texture, gridDim = _a.gridDim, gridTexDim = _a.gridTexDim, radiusFactor = _a.radiusFactor, resolution = _a.resolution;
// webgl.waitForGpuCommandsCompleteSync()
// console.timeEnd('GaussianDensityTexture2d')
var _b = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim), field = _b.field, idField = _b.idField;
return { field: field, idField: idField, transform: getTransform(scale, bbox), radiusFactor: radiusFactor, resolution: resolution };
}
export function GaussianDensityTexture(webgl, position, box, radius, props, oldTexture) {
return webgl.isWebGL2 ?
GaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture) :
GaussianDensityTexture2d(webgl, position, box, radius, false, props, oldTexture);
}
export function GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, props, oldTexture) {
return finalizeGaussianDensityTexture(calcGaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, props, oldTexture));
}
export function GaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture) {
return finalizeGaussianDensityTexture(calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture));
}
function finalizeGaussianDensityTexture(_a) {
var texture = _a.texture, scale = _a.scale, bbox = _a.bbox, gridDim = _a.gridDim, gridTexDim = _a.gridTexDim, gridTexScale = _a.gridTexScale, radiusFactor = _a.radiusFactor, resolution = _a.resolution;
return { transform: getTransform(scale, bbox), texture: texture, bbox: bbox, gridDim: gridDim, gridTexDim: gridTexDim, gridTexScale: gridTexScale, radiusFactor: radiusFactor, resolution: resolution };
}
function getTransform(scale, bbox) {
var transform = Mat4.identity();
Mat4.fromScaling(transform, scale);
Mat4.setTranslation(transform, bbox.min);
return transform;
}
function calcGaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, props, texture) {
// console.log('2d');
var gl = webgl.gl, resources = webgl.resources, state = webgl.state, _a = webgl.extensions, colorBufferFloat = _a.colorBufferFloat, textureFloat = _a.textureFloat, colorBufferHalfFloat = _a.colorBufferHalfFloat, textureHalfFloat = _a.textureHalfFloat, blendMinMax = _a.blendMinMax;
var smoothness = props.smoothness, resolution = props.resolution;
var _b = prepareGaussianDensityData(position, box, radius, props), drawCount = _b.drawCount, positions = _b.positions, radii = _b.radii, groups = _b.groups, scale = _b.scale, expandedBox = _b.expandedBox, dim = _b.dim, maxRadius = _b.maxRadius;
var dx = dim[0], dy = dim[1], dz = dim[2];
var _c = getTexture2dSize(dim), texDimX = _c.texDimX, texDimY = _c.texDimY, texCols = _c.texCols, powerOfTwoSize = _c.powerOfTwoSize;
// console.log({ texDimX, texDimY, texCols, powerOfTwoSize, dim });
var gridTexDim = Vec3.create(texDimX, texDimY, 0);
var gridTexScale = Vec2.create(texDimX / powerOfTwoSize, texDimY / powerOfTwoSize);
var radiusFactor = maxRadius * 2;
var width = powerOfTwo ? powerOfTwoSize : texDimX;
var height = powerOfTwo ? powerOfTwoSize : texDimY;
var minDistTex = getTexture('min-dist-2d', webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest');
minDistTex.define(width, height);
var renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
//
var _d = renderable.values, uCurrentSlice = _d.uCurrentSlice, uCurrentX = _d.uCurrentX, uCurrentY = _d.uCurrentY;
var framebuffer = getFramebuffer(webgl);
framebuffer.bind();
setRenderingDefaults(webgl);
if (!texture)
texture = colorBufferHalfFloat && textureHalfFloat
? resources.texture('image-float16', 'rgba', 'fp16', 'linear')
: colorBufferFloat && textureFloat
? resources.texture('image-float32', 'rgba', 'float', 'linear')
: resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
texture.define(width, height);
// console.log(renderable)
function render(fbTex, clear) {
state.currentRenderItemId = -1;
fbTex.attachFramebuffer(framebuffer, 0);
if (clear) {
gl.viewport(0, 0, width, height);
gl.scissor(0, 0, width, height);
gl.clear(gl.COLOR_BUFFER_BIT);
}
ValueCell.update(uCurrentY, 0);
var currCol = 0;
var currY = 0;
var currX = 0;
for (var i = 0; i < dz; ++i) {
if (currCol >= texCols) {
currCol -= texCols;
currY += dy;
currX = 0;
ValueCell.update(uCurrentY, currY);
}
// console.log({ i, currX, currY });
ValueCell.update(uCurrentX, currX);
ValueCell.update(uCurrentSlice, i);
gl.viewport(currX, currY, dx, dy);
gl.scissor(currX, currY, dx, dy);
renderable.render();
++currCol;
currX += dx;
}
gl.flush();
}
setupDensityRendering(webgl, renderable);
render(texture, true);
if (blendMinMax) {
setupMinDistanceRendering(webgl, renderable);
render(minDistTex, true);
setupGroupIdRendering(webgl, renderable);
render(texture, false);
}
// printTexture(webgl, minDistTex, 0.75);
return { texture: texture, scale: scale, bbox: expandedBox, gridDim: dim, gridTexDim: gridTexDim, gridTexScale: gridTexScale, radiusFactor: radiusFactor, resolution: resolution };
}
function calcGaussianDensityTexture3d(webgl, position, box, radius, props, texture) {
// console.log('3d');
var gl = webgl.gl, resources = webgl.resources, state = webgl.state, _a = webgl.extensions, colorBufferFloat = _a.colorBufferFloat, textureFloat = _a.textureFloat, colorBufferHalfFloat = _a.colorBufferHalfFloat, textureHalfFloat = _a.textureHalfFloat;
var smoothness = props.smoothness, resolution = props.resolution;
var _b = prepareGaussianDensityData(position, box, radius, props), drawCount = _b.drawCount, positions = _b.positions, radii = _b.radii, groups = _b.groups, scale = _b.scale, expandedBox = _b.expandedBox, dim = _b.dim, maxRadius = _b.maxRadius;
var dx = dim[0], dy = dim[1], dz = dim[2];
var minDistTex = getTexture('min-dist-3d', webgl, 'volume-uint8', 'rgba', 'ubyte', 'nearest');
minDistTex.define(dx, dy, dz);
var gridTexScale = Vec2.create(1, 1);
var radiusFactor = maxRadius * 2;
var renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, dim, gridTexScale, smoothness, resolution, radiusFactor);
//
var uCurrentSlice = renderable.values.uCurrentSlice;
var framebuffer = getFramebuffer(webgl);
framebuffer.bind();
setRenderingDefaults(webgl);
gl.viewport(0, 0, dx, dy);
gl.scissor(0, 0, dx, dy);
if (!texture)
texture = colorBufferHalfFloat && textureHalfFloat
? resources.texture('volume-float16', 'rgba', 'fp16', 'linear')
: colorBufferFloat && textureFloat
? resources.texture('volume-float32', 'rgba', 'float', 'linear')
: resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
texture.define(dx, dy, dz);
function render(fbTex, clear) {
state.currentRenderItemId = -1;
for (var i = 0; i < dz; ++i) {
ValueCell.update(uCurrentSlice, i);
fbTex.attachFramebuffer(framebuffer, 0, i);
if (clear)
gl.clear(gl.COLOR_BUFFER_BIT);
renderable.render();
}
gl.flush();
}
setupDensityRendering(webgl, renderable);
render(texture, true);
setupMinDistanceRendering(webgl, renderable);
render(minDistTex, true);
setupGroupIdRendering(webgl, renderable);
render(texture, false);
return { texture: texture, scale: scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale: gridTexScale, radiusFactor: radiusFactor, resolution: resolution };
}
//
function prepareGaussianDensityData(position, box, radius, props) {
var resolution = props.resolution, radiusOffset = props.radiusOffset;
var scaleFactor = 1 / resolution;
var indices = position.indices, x = position.x, y = position.y, z = position.z, id = position.id;
var n = OrderedSet.size(indices);
var positions = new Float32Array(n * 3);
var radii = new Float32Array(n);
var groups = new Float32Array(n);
var maxRadius = 0;
for (var i = 0; i < n; ++i) {
var j = OrderedSet.getAt(indices, i);
positions[i * 3] = x[j];
positions[i * 3 + 1] = y[j];
positions[i * 3 + 2] = z[j];
var r = radius(j) + radiusOffset;
if (maxRadius < r)
maxRadius = r;
radii[i] = r;
groups[i] = id ? id[i] : i;
}
var pad = maxRadius * 2 + resolution * 4;
var expandedBox = Box3D.expand(Box3D(), box, Vec3.create(pad, pad, pad));
var scaledBox = Box3D.scale(Box3D(), expandedBox, scaleFactor);
var dim = Box3D.size(Vec3(), scaledBox);
Vec3.ceil(dim, dim);
var scale = Vec3.create(resolution, resolution, resolution);
return { drawCount: n, positions: positions, radii: radii, groups: groups, scale: scale, expandedBox: expandedBox, dim: dim, maxRadius: maxRadius };
}
function getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, box, gridDim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor) {
// console.log('radiusFactor', radiusFactor);
if (webgl.namedComputeRenderables[GaussianDensityName]) {
var extent = Vec3.sub(Vec3(), box.max, box.min);
var v = webgl.namedComputeRenderables[GaussianDensityName].values;
ValueCell.updateIfChanged(v.drawCount, drawCount);
ValueCell.updateIfChanged(v.instanceCount, 1);
ValueCell.update(v.aRadius, radii);
ValueCell.update(v.aPosition, positions);
ValueCell.update(v.aGroup, groups);
ValueCell.updateIfChanged(v.uCurrentSlice, 0);
ValueCell.updateIfChanged(v.uCurrentX, 0);
ValueCell.updateIfChanged(v.uCurrentY, 0);
ValueCell.update(v.uBboxMin, box.min);
ValueCell.update(v.uBboxSize, extent);
ValueCell.update(v.uGridDim, gridDim);
ValueCell.update(v.uGridTexDim, gridTexDim);
ValueCell.update(v.uGridTexScale, gridTexScale);
ValueCell.updateIfChanged(v.uAlpha, smoothness);
ValueCell.updateIfChanged(v.uResolution, resolution);
ValueCell.updateIfChanged(v.uRadiusFactorInv, 1 / radiusFactor);
ValueCell.update(v.tMinDistanceTex, minDistanceTexture);
ValueCell.updateIfChanged(v.dGridTexType, minDistanceTexture.getDepth() > 0 ? '3d' : '2d');
ValueCell.updateIfChanged(v.dCalcType, 'density');
webgl.namedComputeRenderables[GaussianDensityName].update();
}
else {
webgl.namedComputeRenderables[GaussianDensityName] = createGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, box, gridDim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
}
return webgl.namedComputeRenderables[GaussianDensityName];
}
function createGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, box, gridDim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor) {
var extent = Vec3.sub(Vec3(), box.max, box.min);
var values = {
drawCount: ValueCell.create(drawCount),
instanceCount: ValueCell.create(1),
aRadius: ValueCell.create(radii),
aPosition: ValueCell.create(positions),
aGroup: ValueCell.create(groups),
uCurrentSlice: ValueCell.create(0),
uCurrentX: ValueCell.create(0),
uCurrentY: ValueCell.create(0),
uBboxMin: ValueCell.create(box.min),
uBboxSize: ValueCell.create(extent),
uGridDim: ValueCell.create(gridDim),
uGridTexDim: ValueCell.create(gridTexDim),
uGridTexScale: ValueCell.create(gridTexScale),
uAlpha: ValueCell.create(smoothness),
uResolution: ValueCell.create(resolution),
uRadiusFactorInv: ValueCell.create(1 / radiusFactor),
tMinDistanceTex: ValueCell.create(minDistanceTexture),
dGridTexType: ValueCell.create(minDistanceTexture.getDepth() > 0 ? '3d' : '2d'),
dCalcType: ValueCell.create('density'),
};
var schema = __assign({}, GaussianDensitySchema);
var shaderCode = ShaderCode(GaussianDensityName, gaussianDensity_vert, gaussianDensity_frag);
var renderItem = createComputeRenderItem(webgl, 'points', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function setRenderingDefaults(ctx) {
var gl = ctx.gl, state = ctx.state;
state.disable(gl.CULL_FACE);
state.enable(gl.BLEND);
state.disable(gl.DEPTH_TEST);
state.enable(gl.SCISSOR_TEST);
state.depthMask(false);
state.clearColor(0, 0, 0, 0);
}
function setupMinDistanceRendering(webgl, renderable) {
var gl = webgl.gl, state = webgl.state;
ValueCell.update(renderable.values.dCalcType, 'minDistance');
renderable.update();
state.colorMask(false, false, false, true);
state.blendFunc(gl.ONE, gl.ONE);
// the shader writes 1 - dist so we set blending to MAX
if (!webgl.extensions.blendMinMax) {
throw new Error('GPU gaussian surface calculation requires EXT_blend_minmax');
}
state.blendEquation(webgl.extensions.blendMinMax.MAX);
}
function setupDensityRendering(webgl, renderable) {
var gl = webgl.gl, state = webgl.state;
ValueCell.update(renderable.values.dCalcType, 'density');
renderable.update();
state.colorMask(false, false, false, true);
state.blendFunc(gl.ONE, gl.ONE);
state.blendEquation(gl.FUNC_ADD);
}
function setupGroupIdRendering(webgl, renderable) {
var gl = webgl.gl, state = webgl.state;
ValueCell.update(renderable.values.dCalcType, 'groupId');
renderable.update();
// overwrite color, don't change alpha
state.colorMask(true, true, true, false);
state.blendFunc(gl.ONE, gl.ZERO);
state.blendEquation(gl.FUNC_ADD);
}
function getTexture2dSize(gridDim) {
var area = gridDim[0] * gridDim[1] * gridDim[2];
var squareDim = Math.sqrt(area);
var powerOfTwoSize = Math.pow(2, Math.ceil(Math.log(squareDim) / Math.log(2)));
var texDimX = 0;
var texDimY = gridDim[1];
var texRows = 1;
var texCols = gridDim[2];
if (powerOfTwoSize < gridDim[0] * gridDim[2]) {
texCols = Math.floor(powerOfTwoSize / gridDim[0]);
texRows = Math.ceil(gridDim[2] / texCols);
texDimX = texCols * gridDim[0];
texDimY *= texRows;
}
else {
texDimX = gridDim[0] * gridDim[2];
}
// console.log(texDimX, texDimY, texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2);
return { texDimX: texDimX, texDimY: texDimY, texRows: texRows, texCols: texCols, powerOfTwoSize: texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
}
function fieldFromTexture2d(ctx, texture, dim, texDim) {
// console.time('fieldFromTexture2d')
var dx = dim[0], dy = dim[1], dz = dim[2];
var width = texDim[0], height = texDim[1];
var fboTexCols = Math.floor(width / dx);
var space = Tensor.Space(dim, [2, 1, 0], Float32Array);
var data = space.create();
var field = Tensor.create(space, data);
var idData = space.create();
var idField = Tensor.create(space, idData);
var image = new Uint8Array(width * height * 4);
var framebuffer = getFramebuffer(ctx);
framebuffer.bind();
texture.attachFramebuffer(framebuffer, 0);
ctx.readPixels(0, 0, width, height, image);
// printImageData(createImageData(image, width, height), 1/3)
var j = 0;
var tmpCol = 0;
var tmpRow = 0;
for (var iz = 0; iz < dz; ++iz) {
if (tmpCol >= fboTexCols) {
tmpCol = 0;
tmpRow += dy;
}
for (var iy = 0; iy < dy; ++iy) {
for (var ix = 0; ix < dx; ++ix) {
var idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix);
data[j] = image[idx + 3] / 255;
idData[j] = decodeFloatRGB(image[idx], image[idx + 1], image[idx + 2]);
j++;
}
}
tmpCol++;
}
// console.timeEnd('fieldFromTexture2d')
return { field: field, idField: idField };
}
//# sourceMappingURL=gpu.js.map