gpu.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /**
  2. * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author Michael Krone <michael.krone@uni-tuebingen.de>
  6. */
  7. import { RuntimeContext } from 'mol-task'
  8. import { PositionData, DensityData, DensityTextureData } from '../common'
  9. import { Box3D } from '../../geometry'
  10. import { GaussianDensityProps, getDelta } from '../gaussian-density'
  11. import { OrderedSet } from 'mol-data/int'
  12. import { Vec3, Tensor, Mat4 } from '../../linear-algebra'
  13. import { GaussianDensityValues } from 'mol-gl/renderable/gaussian-density'
  14. import { ValueCell } from 'mol-util'
  15. import { RenderableState, Renderable } from 'mol-gl/renderable'
  16. import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/render-object'
  17. import { WebGLContext } from 'mol-gl/webgl/context';
  18. import { createTexture, Texture } from 'mol-gl/webgl/texture';
  19. import { GLRenderingContext } from 'mol-gl/webgl/compat';
  20. import { decodeIdRGB } from 'mol-geo/geometry/picking';
  21. /** name for shared framebuffer used for gpu gaussian surface operations */
  22. const FramebufferName = 'gaussian-density-gpu'
  23. export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, webgl: WebGLContext): Promise<DensityData> {
  24. // always use texture2d when the gaussian density needs to be downloaded from the GPU,
  25. // it's faster than texture3d
  26. // console.time('GaussianDensityTexture2d')
  27. const { scale, bbox, texture, dim } = await GaussianDensityTexture2d(ctx, webgl, position, box, radius, props)
  28. // console.timeEnd('GaussianDensityTexture2d')
  29. const { field, idField } = await fieldFromTexture2d(webgl, texture, dim)
  30. const transform = Mat4.identity()
  31. Mat4.fromScaling(transform, scale)
  32. Mat4.setTranslation(transform, bbox.min)
  33. return { field, idField, transform }
  34. }
  35. export async function GaussianDensityTexture(ctx: RuntimeContext, webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, oldTexture?: Texture): Promise<DensityTextureData> {
  36. // console.time(`GaussianDensityTexture, ${webgl.isWebGL2 ? '3d' : '2d'}`)
  37. const { texture, scale, bbox, dim } = webgl.isWebGL2 ?
  38. await GaussianDensityTexture3d(ctx, webgl, position, box, radius, props, oldTexture) :
  39. await GaussianDensityTexture2d(ctx, webgl, position, box, radius, props, oldTexture)
  40. // console.timeEnd(`GaussianDensityTexture, ${webgl.isWebGL2 ? '3d' : '2d'}`)
  41. const transform = Mat4.identity()
  42. Mat4.fromScaling(transform, scale)
  43. Mat4.setTranslation(transform, bbox.min)
  44. return { transform, texture, bbox, gridDimension: dim }
  45. }
  46. //
  47. async function GaussianDensityTexture2d(ctx: RuntimeContext, webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) {
  48. const { smoothness } = props
  49. const { drawCount, positions, radii, groups, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
  50. const [ dx, dy, dz ] = dim
  51. const { texDimX, texDimY, texCols } = getTexture2dSize(webgl.maxTextureSize, dim)
  52. const minDistanceTexture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest')
  53. minDistanceTexture.define(texDimX, texDimY)
  54. const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, smoothness)
  55. const renderable = createRenderable(webgl, renderObject)
  56. //
  57. const { gl, framebufferCache } = webgl
  58. const { uCurrentSlice, uCurrentX, uCurrentY } = renderObject.values
  59. const framebuffer = framebufferCache.get(webgl, FramebufferName).value
  60. framebuffer.bind()
  61. setRenderingDefaults(gl)
  62. if (!texture) texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
  63. texture.define(texDimX, texDimY)
  64. function render(fbTex: Texture) {
  65. fbTex.attachFramebuffer(framebuffer, 0)
  66. let currCol = 0
  67. let currY = 0
  68. let currX = 0
  69. for (let i = 0; i < dz; ++i) {
  70. if (currCol >= texCols) {
  71. currCol -= texCols
  72. currY += dy
  73. currX = 0
  74. }
  75. gl.viewport(currX, currY, dx, dy)
  76. ValueCell.update(uCurrentSlice, i)
  77. ValueCell.update(uCurrentX, currX)
  78. ValueCell.update(uCurrentY, currY)
  79. renderable.render('draw')
  80. ++currCol
  81. currX += dx
  82. }
  83. }
  84. setupMinDistanceRendering(webgl, renderable)
  85. render(minDistanceTexture)
  86. setupDensityRendering(webgl, renderable)
  87. render(texture)
  88. setupGroupIdRendering(webgl, renderable)
  89. render(texture)
  90. if (ctx.shouldUpdate) await ctx.update({ message: 'gpu gaussian density calculation' })
  91. await webgl.waitForGpuCommandsComplete()
  92. return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim }
  93. }
  94. async function GaussianDensityTexture3d(ctx: RuntimeContext, webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) {
  95. const { smoothness } = props
  96. const { drawCount, positions, radii, groups, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
  97. const [ dx, dy, dz ] = dim
  98. const minDistanceTexture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'nearest')
  99. minDistanceTexture.define(dx, dy, dz)
  100. const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, smoothness)
  101. const renderable = createRenderable(webgl, renderObject)
  102. //
  103. const { gl, framebufferCache } = webgl
  104. const { uCurrentSlice } = renderObject.values
  105. const framebuffer = framebufferCache.get(webgl, FramebufferName).value
  106. framebuffer.bind()
  107. setRenderingDefaults(gl)
  108. gl.viewport(0, 0, dx, dy)
  109. if (!texture) texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
  110. texture.define(dx, dy, dz)
  111. function render(fbTex: Texture) {
  112. for (let i = 0; i < dz; ++i) {
  113. ValueCell.update(uCurrentSlice, i)
  114. fbTex.attachFramebuffer(framebuffer, 0, i)
  115. renderable.render('draw')
  116. }
  117. }
  118. setupMinDistanceRendering(webgl, renderable)
  119. render(minDistanceTexture)
  120. setupDensityRendering(webgl, renderable)
  121. render(texture)
  122. setupGroupIdRendering(webgl, renderable)
  123. render(texture)
  124. await ctx.update({ message: 'gpu gaussian density calculation' });
  125. await webgl.waitForGpuCommandsComplete()
  126. return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim }
  127. }
  128. //
  129. async function prepareGaussianDensityData(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps) {
  130. const { resolution, radiusOffset } = props
  131. const { indices, x, y, z } = position
  132. const n = OrderedSet.size(indices)
  133. const positions = new Float32Array(n * 3)
  134. const radii = new Float32Array(n)
  135. const groups = new Float32Array(n)
  136. let maxRadius = 0
  137. for (let i = 0; i < n; ++i) {
  138. const j = OrderedSet.getAt(indices, i);
  139. positions[i * 3] = x[j]
  140. positions[i * 3 + 1] = y[j]
  141. positions[i * 3 + 2] = z[j]
  142. const r = radius(j) + radiusOffset
  143. if (maxRadius < r) maxRadius = r
  144. radii[i] = r
  145. groups[i] = i
  146. if (i % 10000 === 0 && ctx.shouldUpdate) {
  147. await ctx.update({ message: 'preparing density data', current: i, max: n })
  148. }
  149. }
  150. const pad = maxRadius * 2 + resolution
  151. const expandedBox = Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad));
  152. const extent = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min)
  153. const delta = getDelta(expandedBox, resolution)
  154. const dim = Vec3.zero()
  155. Vec3.ceil(dim, Vec3.mul(dim, extent, delta))
  156. // console.log('grid dim gpu', dim)
  157. return { drawCount: n, positions, radii, groups, delta, expandedBox, dim }
  158. }
  159. function getGaussianDensityRenderObject(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, dimensions: Vec3, smoothness: number) {
  160. const extent = Vec3.sub(Vec3.zero(), box.max, box.min)
  161. const { texDimX, texDimY } = getTexture2dSize(webgl.maxTextureSize, dimensions)
  162. const values: GaussianDensityValues = {
  163. drawCount: ValueCell.create(drawCount),
  164. instanceCount: ValueCell.create(1),
  165. aRadius: ValueCell.create(radii),
  166. aPosition: ValueCell.create(positions),
  167. aGroup: ValueCell.create(groups),
  168. uCurrentSlice: ValueCell.create(0),
  169. uCurrentX: ValueCell.create(0),
  170. uCurrentY: ValueCell.create(0),
  171. uBboxMin: ValueCell.create(box.min),
  172. uBboxMax: ValueCell.create(box.max),
  173. uBboxSize: ValueCell.create(extent),
  174. uGridDim: ValueCell.create(dimensions),
  175. uGridTexDim: ValueCell.create(Vec3.create(texDimX, texDimY, 0)),
  176. uAlpha: ValueCell.create(smoothness),
  177. tMinDistanceTex: ValueCell.create(minDistanceTexture),
  178. dGridTexType: ValueCell.create(minDistanceTexture.depth > 0 ? '3d' : '2d'),
  179. dCalcType: ValueCell.create('density'),
  180. }
  181. const state: RenderableState = {
  182. visible: true,
  183. pickable: false,
  184. }
  185. const renderObject = createGaussianDensityRenderObject(values, state)
  186. return renderObject
  187. }
  188. function setRenderingDefaults(gl: GLRenderingContext) {
  189. gl.disable(gl.CULL_FACE)
  190. gl.frontFace(gl.CCW)
  191. gl.cullFace(gl.BACK)
  192. gl.enable(gl.BLEND)
  193. }
  194. function setupMinDistanceRendering(webgl: WebGLContext, renderable: Renderable<any>) {
  195. const { gl } = webgl
  196. ValueCell.update(renderable.values.dCalcType, 'minDistance')
  197. renderable.update()
  198. renderable.getProgram('draw').use()
  199. gl.blendFunc(gl.ONE, gl.ONE)
  200. // the shader writes 1 - dist so we set blending to MAX
  201. gl.blendEquation(webgl.extensions.blendMinMax.MAX)
  202. }
  203. function setupDensityRendering(webgl: WebGLContext, renderable: Renderable<any>) {
  204. const { gl } = webgl
  205. ValueCell.update(renderable.values.dCalcType, 'density')
  206. renderable.update()
  207. renderable.getProgram('draw').use()
  208. gl.blendFunc(gl.ONE, gl.ONE)
  209. gl.blendEquation(gl.FUNC_ADD)
  210. }
  211. function setupGroupIdRendering(webgl: WebGLContext, renderable: Renderable<any>) {
  212. const { gl } = webgl
  213. ValueCell.update(renderable.values.dCalcType, 'groupId')
  214. renderable.update()
  215. renderable.getProgram('draw').use()
  216. // overwrite color, don't change alpha
  217. gl.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ZERO, gl.ONE)
  218. gl.blendEquation(gl.FUNC_ADD)
  219. }
  220. function getTexture2dSize(maxTexSize: number, gridDim: Vec3) {
  221. let texDimX = 0
  222. let texDimY = gridDim[1]
  223. let texRows = 1
  224. let texCols = gridDim[0]
  225. if (maxTexSize < gridDim[0] * gridDim[2]) {
  226. texCols = Math.floor(maxTexSize / gridDim[0])
  227. texRows = Math.ceil(gridDim[2] / texCols)
  228. texDimX = texCols * gridDim[0]
  229. texDimY *= texRows
  230. } else {
  231. texDimX = gridDim[0] * gridDim[2]
  232. }
  233. return { texDimX, texDimY, texRows, texCols }
  234. }
  235. async function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3) {
  236. // console.time('fieldFromTexture2d')
  237. const { framebufferCache } = ctx
  238. const [ dx, dy, dz ] = dim
  239. const { width, height } = texture
  240. const fboTexCols = Math.floor(width / dx)
  241. const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
  242. const data = space.create()
  243. const field = Tensor.create(space, data)
  244. const idData = space.create()
  245. const idField = Tensor.create(space, idData)
  246. const image = new Uint8Array(width * height * 4)
  247. const framebuffer = framebufferCache.get(ctx, FramebufferName).value
  248. framebuffer.bind()
  249. texture.attachFramebuffer(framebuffer, 0)
  250. await ctx.readPixelsAsync(0, 0, width, height, image)
  251. let j = 0
  252. let tmpCol = 0
  253. let tmpRow = 0
  254. for (let iz = 0; iz < dz; ++iz) {
  255. if (tmpCol >= fboTexCols ) {
  256. tmpCol = 0
  257. tmpRow += dy
  258. }
  259. for (let iy = 0; iy < dy; ++iy) {
  260. for (let ix = 0; ix < dx; ++ix) {
  261. const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix)
  262. data[j] = image[idx + 3] / 255
  263. idData[j] = decodeIdRGB(image[idx], image[idx + 1], image[idx + 2])
  264. j++
  265. }
  266. }
  267. tmpCol++
  268. }
  269. // console.timeEnd('fieldFromTexture2d')
  270. return { field, idField }
  271. }