From bf9c52ca7a5bc9c7d5d942150911a17f3e53d7a0 Mon Sep 17 00:00:00 2001
From: Alexander Rose <alexander.rose@weirdbyte.de>
Date: Mon, 1 Oct 2018 09:21:07 -0700
Subject: [PATCH] wip, gpu gaussian density

---
 src/mol-gl/render-object.ts               |  12 +-
 src/mol-gl/renderable.ts                  |   8 +-
 src/mol-gl/renderable/gaussian-density.ts |  37 +++++
 src/mol-gl/shader-code.ts                 |   5 +
 src/mol-gl/shader/gaussian-density.frag   |  35 +++++
 src/mol-gl/shader/gaussian-density.vert   |  28 ++++
 src/mol-gl/webgl/render-item.ts           |   7 +
 src/mol-math/geometry/gaussian-density.ts | 171 +++++++++++++++++++++-
 8 files changed, 295 insertions(+), 8 deletions(-)
 create mode 100644 src/mol-gl/renderable/gaussian-density.ts
 create mode 100644 src/mol-gl/shader/gaussian-density.frag
 create mode 100644 src/mol-gl/shader/gaussian-density.vert

diff --git a/src/mol-gl/render-object.ts b/src/mol-gl/render-object.ts
index e183c1ca9..7b4648ace 100644
--- a/src/mol-gl/render-object.ts
+++ b/src/mol-gl/render-object.ts
@@ -4,10 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { PointsRenderable, MeshRenderable, RenderableState, MeshValues, PointsValues, LinesValues, LinesRenderable } from './renderable'
+import { PointsRenderable, MeshRenderable, RenderableState, MeshValues, PointsValues, LinesValues, LinesRenderable, Renderable } from './renderable'
 import { RenderableValues } from './renderable/schema';
 import { idFactory } from 'mol-util/id-factory';
 import { Context } from './webgl/context';
+import { GaussianDensityValues, GaussianDensityRenderable } from './renderable/gaussian-density';
 
 const getNextId = idFactory(0, 0x7FFFFFFF)
 
@@ -15,7 +16,8 @@ export interface BaseRenderObject { id: number, type: string, values: Renderable
 export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', values: MeshValues }
 export interface PointsRenderObject extends BaseRenderObject { type: 'points', values: PointsValues }
 export interface LinesRenderObject extends BaseRenderObject { type: 'lines', values: LinesValues }
-export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject
+export interface GaussianDensityRenderObject extends BaseRenderObject { type: 'gaussian-density', values: GaussianDensityValues }
+export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject | GaussianDensityRenderObject
 
 export function createMeshRenderObject(values: MeshValues, state: RenderableState): MeshRenderObject {
     return { id: getNextId(), type: 'mesh', values, state }
@@ -26,11 +28,15 @@ export function createPointsRenderObject(values: PointsValues, state: Renderable
 export function createLinesRenderObject(values: LinesValues, state: RenderableState): LinesRenderObject {
     return { id: getNextId(), type: 'lines', values, state }
 }
+export function createGaussianDensityRenderObject(values: GaussianDensityValues, state: RenderableState): GaussianDensityRenderObject {
+    return { id: getNextId(), type: 'gaussian-density', values, state }
+}
 
-export function createRenderable(ctx: Context, o: RenderObject) {
+export function createRenderable(ctx: Context, o: RenderObject): Renderable<any> {
     switch (o.type) {
         case 'mesh': return MeshRenderable(ctx, o.id, o.values, o.state)
         case 'points': return PointsRenderable(ctx, o.id, o.values, o.state)
         case 'lines': return LinesRenderable(ctx, o.id, o.values, o.state)
+        case 'gaussian-density': return GaussianDensityRenderable(ctx, o.id, o.values, o.state)
     }
 }
\ No newline at end of file
diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts
index bc7c47cc6..156760308 100644
--- a/src/mol-gl/renderable.ts
+++ b/src/mol-gl/renderable.ts
@@ -5,7 +5,7 @@
  */
 
 import { Program } from './webgl/program';
-import { RenderableValues, Values, RenderableSchema, BaseValues } from './renderable/schema';
+import { RenderableValues, Values, RenderableSchema } from './renderable/schema';
 import { RenderVariant, RenderItem } from './webgl/render-item';
 import { Sphere3D } from 'mol-math/geometry';
 // import { calculateBoundingSphereFromValues } from './renderable/util';
@@ -17,7 +17,7 @@ export type RenderableState = {
     depthMask: boolean
 }
 
-export interface Renderable<T extends RenderableValues & BaseValues> {
+export interface Renderable<T extends RenderableValues> {
     readonly values: T
     readonly state: RenderableState
     readonly boundingSphere: Sphere3D
@@ -29,7 +29,7 @@ export interface Renderable<T extends RenderableValues & BaseValues> {
     dispose: () => void
 }
 
-export function createRenderable<T extends Values<RenderableSchema> & BaseValues>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> {
+export function createRenderable<T extends Values<RenderableSchema>>(renderItem: RenderItem, values: T, state: RenderableState): Renderable<T> {
     // TODO
     let boundingSphere: Sphere3D = Sphere3D.create(Vec3.zero(), 50)
 
@@ -43,7 +43,7 @@ export function createRenderable<T extends Values<RenderableSchema> & BaseValues
             // boundingSphere = calculateBoundingSphereFromValues(values)
             // return boundingSphere
         },
-        get opaque () { return values.uAlpha.ref.value === 1 },
+        get opaque () { return values.uAlpha && values.uAlpha.ref.value === 1 },
 
         render: (variant: RenderVariant) => renderItem.render(variant),
         getProgram: (variant: RenderVariant) => renderItem.getProgram(variant),
diff --git a/src/mol-gl/renderable/gaussian-density.ts b/src/mol-gl/renderable/gaussian-density.ts
new file mode 100644
index 000000000..c2fcad4e2
--- /dev/null
+++ b/src/mol-gl/renderable/gaussian-density.ts
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Renderable, RenderableState, createRenderable } from '../renderable'
+import { Context } from '../webgl/context';
+import { createRenderItem } from '../webgl/render-item';
+import { AttributeSpec, Values, UniformSpec, ValueSpec } from './schema';
+import { GaussianDensityShaderCode } from '../shader-code';
+
+export const GaussianDensitySchema = {
+    drawCount: ValueSpec('number'),
+    instanceCount: ValueSpec('number'),
+
+    aRadius: AttributeSpec('float32', 1, 0),
+    aPosition: AttributeSpec('float32', 3, 0),
+
+    uCurrentSlice: UniformSpec('f'),
+    uCurrentX: UniformSpec('f'),
+    uCurrentY: UniformSpec('f'),
+    uBboxMin: UniformSpec('v3'),
+    uBboxMax: UniformSpec('v3'),
+    uBboxSize: UniformSpec('v3'),
+    uGridDim: UniformSpec('v3'),
+}
+export type GaussianDensitySchema = typeof GaussianDensitySchema
+export type GaussianDensityValues = Values<GaussianDensitySchema>
+
+export function GaussianDensityRenderable(ctx: Context, id: number, values: GaussianDensityValues, state: RenderableState): Renderable<GaussianDensityValues> {
+    const schema = { ...GaussianDensitySchema }
+    const shaderCode = GaussianDensityShaderCode
+    const renderItem = createRenderItem(ctx, 'points', shaderCode, schema, { ...values })
+
+    return createRenderable(renderItem, values, state);
+}
\ No newline at end of file
diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts
index 263dd0b5f..ccca161b9 100644
--- a/src/mol-gl/shader-code.ts
+++ b/src/mol-gl/shader-code.ts
@@ -39,6 +39,11 @@ export const MeshShaderCode = ShaderCode(
     require('mol-gl/shader/mesh.frag')
 )
 
+export const GaussianDensityShaderCode = ShaderCode(
+    require('mol-gl/shader/gaussian-density.vert'),
+    require('mol-gl/shader/gaussian-density.frag')
+)
+
 export type ShaderDefines = {
     [k: string]: ValueCell<DefineType>
 }
diff --git a/src/mol-gl/shader/gaussian-density.frag b/src/mol-gl/shader/gaussian-density.frag
new file mode 100644
index 000000000..33c4e8f15
--- /dev/null
+++ b/src/mol-gl/shader/gaussian-density.frag
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2018 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>
+ */
+
+precision mediump float;
+
+varying vec3 position;
+varying float radius;
+
+uniform vec3 uBboxSize;
+uniform vec3 uBboxMin;
+uniform vec3 uBboxMax;
+uniform vec3 uGridDim;
+uniform float uCurrentSlice;
+uniform float uCurrentX;
+uniform float uCurrentY;
+
+void main() {
+    vec3 tmpVec = gl_FragCoord.xyz;
+    tmpVec.x = tmpVec.x - uCurrentX;
+    tmpVec.y = tmpVec.y - uCurrentY;
+    vec3 fragPos = vec3(
+        (tmpVec.x - 0.5) / uGridDim.x,
+        (tmpVec.y - 0.5) / uGridDim.y,
+        (uCurrentSlice) / uGridDim.z
+    );
+    float dist = length(fragPos * uBboxSize - position * uBboxSize);
+    float density = 1.0 - smoothstep( 0.0, radius * 2.0, dist);
+    gl_FragColor = vec4(1, 1, 1, density);
+    // density = 1.0 - clamp((dist - (radius + 1.4)) + 0.5, 0.0, 1.0);				
+    // gl_FragColor = vec4(vec3(density), 1.0);
+}
\ No newline at end of file
diff --git a/src/mol-gl/shader/gaussian-density.vert b/src/mol-gl/shader/gaussian-density.vert
new file mode 100644
index 000000000..acd99834b
--- /dev/null
+++ b/src/mol-gl/shader/gaussian-density.vert
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2018 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>
+ */
+
+precision mediump float;
+
+attribute vec3 aPosition;
+attribute float aRadius;
+
+varying vec3 position;
+varying float radius;
+
+uniform vec3 uBboxSize;
+uniform vec3 uBboxMin;
+uniform vec3 uBboxMax;
+uniform vec3 uGridDim;
+uniform float uCurrentSlice;
+
+void main() {
+    radius = aRadius;
+    float scale = max(uBboxSize.z, max(uBboxSize.x, uBboxSize.y));
+    gl_PointSize = (radius / scale) * max(uGridDim.x, uGridDim.y) * 10.0; // * 50.0;
+    position = (aPosition - uBboxMin) / uBboxSize;
+    gl_Position = vec4(position * 2.0 - 1.0, 1.0);
+}
\ No newline at end of file
diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts
index 7cc81d732..9795b85aa 100644
--- a/src/mol-gl/webgl/render-item.ts
+++ b/src/mol-gl/webgl/render-item.ts
@@ -60,6 +60,13 @@ interface ValueChanges {
     uniforms: boolean
 }
 
+// TODO make `RenderVariantDefines` a parameter for `createRenderItem`
+
+/**
+ * Creates a render item
+ * 
+ * - assumes that `values.drawCount` and `values.instanceCount` exist
+ */
 export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues): RenderItem {
     const id = getNextRenderItemId()
     const { programCache } = ctx
diff --git a/src/mol-math/geometry/gaussian-density.ts b/src/mol-math/geometry/gaussian-density.ts
index 23778fa4b..0e1c3638c 100644
--- a/src/mol-math/geometry/gaussian-density.ts
+++ b/src/mol-math/geometry/gaussian-density.ts
@@ -9,6 +9,12 @@ import { Vec3, Mat4, Tensor } from '../linear-algebra';
 import { RuntimeContext, Task } from 'mol-task';
 import { PositionData, DensityData } from './common';
 import { OrderedSet } from 'mol-data/int';
+import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/render-object';
+import { createContext } from 'mol-gl/webgl/context';
+import { GaussianDensityValues } from 'mol-gl/renderable/gaussian-density';
+import { RenderableState } from 'mol-gl/renderable';
+import { ValueCell } from 'mol-util';
+import { createRenderTarget } from 'mol-gl/webgl/render-target';
 
 export const DefaultGaussianDensityProps = {
     resolution: 1,
@@ -26,10 +32,17 @@ function getDelta(box: Box3D, resolution: number) {
 }
 
 export function computeGaussianDensity(position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps) {
-    return Task.create('Gaussian Density', async ctx => await GaussianDensity(ctx, position, box, radius, props));
+    return Task.create('Gaussian Density', async ctx => {
+        const foo = await GaussianDensityGPU(ctx, position, box, radius, props)
+        console.log('FOOBAR', foo)
+        return await GaussianDensity(ctx, position, box, radius, props)
+    });
 }
 
 export async function GaussianDensity(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps): Promise<DensityData> {
+    const foo = await GaussianDensityGPU(ctx, position, box, radius, props)
+    console.log('FOOBAR', foo)
+
     const { resolution, radiusOffset, smoothness } = props
 
     const { indices, x, y, z } = position
@@ -117,4 +130,160 @@ export async function GaussianDensity(ctx: RuntimeContext, position: PositionDat
     Mat4.setTranslation(transform, expandedBox.min)
 
     return { field, idField, transform }
+}
+
+export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps) { // }: Promise<DensityData> {
+    const { resolution, radiusOffset } = props
+
+    const { indices, x, y, z } = position
+    const n = OrderedSet.size(indices)
+
+    const positions = new Float32Array(n * 3)
+    const radii = new Float32Array(n)
+
+    const pad = (radiusOffset + 3) * 3 // TODO calculate max radius
+    const expandedBox = Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad));
+    const extent = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min)
+
+    const delta = getDelta(Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad)), resolution)
+    const dim = Vec3.zero()
+    Vec3.ceil(dim, Vec3.mul(dim, extent, delta))
+
+    for (let i = 0; i < n; ++i) {
+        const j = OrderedSet.getAt(indices, i);
+
+        positions[i * 3] = x[j]
+        positions[i * 3 + 1] = y[j]
+        positions[i * 3 + 2] = z[j]
+        radii[i] = radius(j) + radiusOffset
+
+        if (i % 10000 === 0 && ctx.shouldUpdate) {
+            await ctx.update({ message: 'preparing density data', current: i, max: n });
+        }
+    }
+
+    //
+
+    const values: GaussianDensityValues = {
+        drawCount: ValueCell.create(n),
+        instanceCount: ValueCell.create(1),
+
+        aRadius: ValueCell.create(radii),
+        aPosition: ValueCell.create(positions),
+
+        uCurrentSlice: ValueCell.create(0),
+        uCurrentX: ValueCell.create(0),
+        uCurrentY: ValueCell.create(0),
+        uBboxMin: ValueCell.create(expandedBox.min),
+        uBboxMax: ValueCell.create(expandedBox.max),
+        uBboxSize: ValueCell.create(extent),
+        uGridDim: ValueCell.create(dim),
+    }
+    const state: RenderableState = {
+        visible: true,
+        depthMask: false
+    }
+
+    const canvas = document.createElement('canvas')
+    const gl = canvas.getContext('webgl', {
+        alpha: false,
+        antialias: true,
+        depth: true,
+        preserveDrawingBuffer: true
+    })
+    if (!gl) throw new Error('Could not create a WebGL rendering context')
+    const webgl = createContext(gl)
+
+    const renderObject = createGaussianDensityRenderObject(values, state)
+    const renderable = createRenderable(webgl, renderObject)
+
+    //
+
+    // get actual max texture size
+	const maxTexSize = 4096; // gl. .limits.maxTextureSize;
+	let fboTexDimX = 0
+	let fboTexDimY = dim[1]
+	let fboTexRows = 1
+	let fboTexCols = dim[0]
+	if(maxTexSize < dim[0] * dim[2]) {
+		fboTexCols =  Math.floor(maxTexSize / dim[0])
+		fboTexRows = Math.ceil(dim[2] / fboTexCols)
+		fboTexDimX = fboTexCols * dim[0]
+		fboTexDimY *= fboTexRows
+	} else {
+		fboTexDimX = dim[0] * dim[2]
+	}
+
+    //
+
+    const program = renderable.getProgram('draw')
+    const renderTarget = createRenderTarget(webgl, fboTexDimX, fboTexDimY)
+    
+    program.use()
+    renderTarget.bind()
+
+    gl.disable(gl.CULL_FACE)
+    gl.frontFace(gl.CCW)
+    gl.cullFace(gl.BACK)
+
+    gl.depthMask(true)
+    gl.clearColor(0, 0, 0, 1)
+    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
+    gl.depthMask(false)
+
+    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
+    gl.blendEquation(gl.FUNC_ADD)
+    gl.enable(gl.BLEND)
+
+	gl.finish();
+	let currCol = 0;
+	let currY = 0;
+	let currX = 0;
+	for(let i = 0; i < dim[2]; ++i) {
+		if (currCol >= fboTexCols) {
+			currCol -= fboTexCols
+			currY += dim[1]
+			currX = 0
+        }
+        gl.viewport(currX, currY, dim[0], dim[1])
+        ValueCell.update(values.uCurrentSlice, i)
+        ValueCell.update(values.uCurrentX, currX)
+        ValueCell.update(values.uCurrentY, currY)
+        renderable.render('draw')
+		++currCol
+		currX += dim[0]
+	}
+    gl.finish();
+    
+    const imageData = renderTarget.getImageData()
+    console.log(imageData)
+    debugTexture(imageData, 0.4)
+
+    //
+
+    const transform = Mat4.identity()
+    Mat4.fromScaling(transform, Vec3.inverse(Vec3.zero(), delta))
+    Mat4.setTranslation(transform, expandedBox.min)
+
+    return { field: imageData, idField: undefined, transform }
+}
+
+function debugTexture(imageData: ImageData, scale: number) {
+	const canvas = document.createElement('canvas')
+	canvas.width = imageData.width
+	canvas.height = imageData.height
+    const ctx = canvas.getContext('2d')
+    if (!ctx) throw new Error('Could not create canvas 2d context')
+	ctx.putImageData(imageData, 0, 0)
+	canvas.toBlob(function(imgBlob){
+		var objectURL = window.URL.createObjectURL(imgBlob)
+		var img = document.createElement('img')
+		img.src = objectURL
+		img.style.width = imageData.width * scale + 'px'
+        img.style.height = imageData.height * scale + 'px'
+        img.style.position = 'absolute'
+        img.style.top = '0px'
+        img.style.left = '0px'
+		document.body.appendChild(img)
+	}, 'image/png')
 }
\ No newline at end of file
-- 
GitLab