gpu.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. /**
  2. * Copyright (c) 2017-2019 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 { GaussianDensityGPUProps, getDelta } from '../gaussian-density'
  11. import { OrderedSet } from 'mol-data/int'
  12. import { Vec3, Tensor, Mat4 } from '../../linear-algebra'
  13. import { ValueCell } from 'mol-util'
  14. import { createComputeRenderable, ComputeRenderable } from 'mol-gl/renderable'
  15. import { WebGLContext } from 'mol-gl/webgl/context';
  16. import { createTexture, Texture } from 'mol-gl/webgl/texture';
  17. import { GLRenderingContext } from 'mol-gl/webgl/compat';
  18. import { decodeFloatRGB } from 'mol-util/float-packing';
  19. import { ShaderCode } from 'mol-gl/shader-code';
  20. import { createRenderItem } from 'mol-gl/webgl/render-item';
  21. import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, DefineSpec, Values } from 'mol-gl/renderable/schema';
  22. export const GaussianDensitySchema = {
  23. drawCount: ValueSpec('number'),
  24. instanceCount: ValueSpec('number'),
  25. aRadius: AttributeSpec('float32', 1, 0),
  26. aPosition: AttributeSpec('float32', 3, 0),
  27. aGroup: AttributeSpec('float32', 1, 0),
  28. uCurrentSlice: UniformSpec('f'),
  29. uCurrentX: UniformSpec('f'),
  30. uCurrentY: UniformSpec('f'),
  31. uBboxMin: UniformSpec('v3'),
  32. uBboxMax: UniformSpec('v3'),
  33. uBboxSize: UniformSpec('v3'),
  34. uGridDim: UniformSpec('v3'),
  35. uGridTexDim: UniformSpec('v3'),
  36. uAlpha: UniformSpec('f'),
  37. tMinDistanceTex: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
  38. dGridTexType: DefineSpec('string', ['2d', '3d']),
  39. dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']),
  40. }
  41. export const GaussianDensityShaderCode = ShaderCode(
  42. require('mol-gl/shader/gaussian-density.vert'),
  43. require('mol-gl/shader/gaussian-density.frag'),
  44. { standardDerivatives: false, fragDepth: false }
  45. )
  46. /** name for shared framebuffer used for gpu gaussian surface operations */
  47. const FramebufferName = 'gaussian-density'
  48. export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): Promise<DensityData> {
  49. // always use texture2d when the gaussian density needs to be downloaded from the GPU,
  50. // it's faster than texture3d
  51. // console.time('GaussianDensityTexture2d')
  52. const { scale, bbox, texture, dim } = await calcGaussianDensityTexture2d(ctx, webgl, position, box, radius, props)
  53. // console.timeEnd('GaussianDensityTexture2d')
  54. const { field, idField } = await fieldFromTexture2d(webgl, texture, dim)
  55. return { field, idField, transform: getTransform(scale, bbox) }
  56. }
  57. export async function GaussianDensityTexture(ctx: RuntimeContext, webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): Promise<DensityTextureData> {
  58. return webgl.isWebGL2 ?
  59. await GaussianDensityTexture3d(ctx, webgl, position, box, radius, props, oldTexture) :
  60. await GaussianDensityTexture2d(ctx, webgl, position, box, radius, props, oldTexture)
  61. }
  62. export async function GaussianDensityTexture2d(ctx: RuntimeContext, webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): Promise<DensityTextureData> {
  63. return finalizeGaussianDensityTexture(await calcGaussianDensityTexture2d(ctx, webgl, position, box, radius, props, oldTexture))
  64. }
  65. export async function GaussianDensityTexture3d(ctx: RuntimeContext, webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): Promise<DensityTextureData> {
  66. return finalizeGaussianDensityTexture(await calcGaussianDensityTexture3d(ctx, webgl, position, box, radius, props, oldTexture))
  67. }
  68. function finalizeGaussianDensityTexture({ texture, scale, bbox, dim }: GaussianDensityTextureData): DensityTextureData {
  69. return { transform: getTransform(scale, bbox), texture, bbox, gridDimension: dim }
  70. }
  71. function getTransform(scale: Vec3, bbox: Box3D) {
  72. const transform = Mat4.identity()
  73. Mat4.fromScaling(transform, scale)
  74. Mat4.setTranslation(transform, bbox.min)
  75. return transform
  76. }
  77. //
  78. type GaussianDensityTextureData = { texture: Texture, scale: Vec3, bbox: Box3D, dim: Vec3}
  79. async function calcGaussianDensityTexture2d(ctx: RuntimeContext, webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): Promise<GaussianDensityTextureData> {
  80. const { smoothness } = props
  81. const { drawCount, positions, radii, groups, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
  82. const [ dx, dy, dz ] = dim
  83. const { texDimX, texDimY, texCols } = getTexture2dSize(webgl.maxTextureSize, dim)
  84. const minDistanceTexture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest')
  85. minDistanceTexture.define(texDimX, texDimY)
  86. const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, smoothness)
  87. //
  88. const { gl, framebufferCache } = webgl
  89. const { uCurrentSlice, uCurrentX, uCurrentY } = renderable.values
  90. const framebuffer = framebufferCache.get(webgl, FramebufferName).value
  91. framebuffer.bind()
  92. setRenderingDefaults(gl)
  93. if (!texture) texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
  94. texture.define(texDimX, texDimY)
  95. function render(fbTex: Texture) {
  96. fbTex.attachFramebuffer(framebuffer, 0)
  97. let currCol = 0
  98. let currY = 0
  99. let currX = 0
  100. for (let i = 0; i < dz; ++i) {
  101. if (currCol >= texCols) {
  102. currCol -= texCols
  103. currY += dy
  104. currX = 0
  105. }
  106. gl.viewport(currX, currY, dx, dy)
  107. ValueCell.update(uCurrentSlice, i)
  108. ValueCell.update(uCurrentX, currX)
  109. ValueCell.update(uCurrentY, currY)
  110. renderable.render()
  111. ++currCol
  112. currX += dx
  113. }
  114. }
  115. setupMinDistanceRendering(webgl, renderable)
  116. render(minDistanceTexture)
  117. setupDensityRendering(webgl, renderable)
  118. render(texture)
  119. setupGroupIdRendering(webgl, renderable)
  120. render(texture)
  121. if (ctx.shouldUpdate) await ctx.update({ message: 'gpu gaussian density calculation' })
  122. await webgl.waitForGpuCommandsCompleteSync()
  123. return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim }
  124. }
  125. async function calcGaussianDensityTexture3d(ctx: RuntimeContext, webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): Promise<GaussianDensityTextureData> {
  126. const { smoothness } = props
  127. const { drawCount, positions, radii, groups, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
  128. const [ dx, dy, dz ] = dim
  129. const minDistanceTexture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'nearest')
  130. minDistanceTexture.define(dx, dy, dz)
  131. const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, smoothness)
  132. //
  133. const { gl, framebufferCache } = webgl
  134. const { uCurrentSlice } = renderable.values
  135. const framebuffer = framebufferCache.get(webgl, FramebufferName).value
  136. framebuffer.bind()
  137. setRenderingDefaults(gl)
  138. gl.viewport(0, 0, dx, dy)
  139. if (!texture) texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
  140. texture.define(dx, dy, dz)
  141. function render(fbTex: Texture) {
  142. for (let i = 0; i < dz; ++i) {
  143. ValueCell.update(uCurrentSlice, i)
  144. fbTex.attachFramebuffer(framebuffer, 0, i)
  145. renderable.render()
  146. }
  147. }
  148. setupMinDistanceRendering(webgl, renderable)
  149. render(minDistanceTexture)
  150. setupDensityRendering(webgl, renderable)
  151. render(texture)
  152. setupGroupIdRendering(webgl, renderable)
  153. render(texture)
  154. await ctx.update({ message: 'gpu gaussian density calculation' });
  155. await webgl.waitForGpuCommandsCompleteSync()
  156. return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim }
  157. }
  158. //
  159. async function prepareGaussianDensityData(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps) {
  160. const { resolution, radiusOffset } = props
  161. const { indices, x, y, z } = position
  162. const n = OrderedSet.size(indices)
  163. const positions = new Float32Array(n * 3)
  164. const radii = new Float32Array(n)
  165. const groups = new Float32Array(n)
  166. let maxRadius = 0
  167. for (let i = 0; i < n; ++i) {
  168. const j = OrderedSet.getAt(indices, i);
  169. positions[i * 3] = x[j]
  170. positions[i * 3 + 1] = y[j]
  171. positions[i * 3 + 2] = z[j]
  172. const r = radius(j) + radiusOffset
  173. if (maxRadius < r) maxRadius = r
  174. radii[i] = r
  175. groups[i] = i
  176. if (i % 10000 === 0 && ctx.shouldUpdate) {
  177. await ctx.update({ message: 'preparing density data', current: i, max: n })
  178. }
  179. }
  180. const pad = maxRadius * 2 + resolution
  181. const expandedBox = Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad));
  182. const extent = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min)
  183. const delta = getDelta(expandedBox, resolution)
  184. const dim = Vec3.zero()
  185. Vec3.ceil(dim, Vec3.mul(dim, extent, delta))
  186. // console.log('grid dim gpu', dim)
  187. return { drawCount: n, positions, radii, groups, delta, expandedBox, dim }
  188. }
  189. function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, dimensions: Vec3, smoothness: number) {
  190. const extent = Vec3.sub(Vec3.zero(), box.max, box.min)
  191. const { texDimX, texDimY } = getTexture2dSize(webgl.maxTextureSize, dimensions)
  192. const values: Values<typeof GaussianDensitySchema> = {
  193. drawCount: ValueCell.create(drawCount),
  194. instanceCount: ValueCell.create(1),
  195. aRadius: ValueCell.create(radii),
  196. aPosition: ValueCell.create(positions),
  197. aGroup: ValueCell.create(groups),
  198. uCurrentSlice: ValueCell.create(0),
  199. uCurrentX: ValueCell.create(0),
  200. uCurrentY: ValueCell.create(0),
  201. uBboxMin: ValueCell.create(box.min),
  202. uBboxMax: ValueCell.create(box.max),
  203. uBboxSize: ValueCell.create(extent),
  204. uGridDim: ValueCell.create(dimensions),
  205. uGridTexDim: ValueCell.create(Vec3.create(texDimX, texDimY, 0)),
  206. uAlpha: ValueCell.create(smoothness),
  207. tMinDistanceTex: ValueCell.create(minDistanceTexture),
  208. dGridTexType: ValueCell.create(minDistanceTexture.depth > 0 ? '3d' : '2d'),
  209. dCalcType: ValueCell.create('density'),
  210. }
  211. const schema = { ...GaussianDensitySchema }
  212. const shaderCode = GaussianDensityShaderCode
  213. const renderItem = createRenderItem(webgl, 'points', shaderCode, schema, values, -1)
  214. return createComputeRenderable(renderItem, values)
  215. }
  216. function setRenderingDefaults(gl: GLRenderingContext) {
  217. gl.disable(gl.CULL_FACE)
  218. gl.frontFace(gl.CCW)
  219. gl.cullFace(gl.BACK)
  220. gl.enable(gl.BLEND)
  221. }
  222. function setupMinDistanceRendering(webgl: WebGLContext, renderable: ComputeRenderable<any>) {
  223. const { gl } = webgl
  224. ValueCell.update(renderable.values.dCalcType, 'minDistance')
  225. renderable.update()
  226. renderable.use()
  227. gl.blendFunc(gl.ONE, gl.ONE)
  228. // the shader writes 1 - dist so we set blending to MAX
  229. gl.blendEquation(webgl.extensions.blendMinMax.MAX)
  230. }
  231. function setupDensityRendering(webgl: WebGLContext, renderable: ComputeRenderable<any>) {
  232. const { gl } = webgl
  233. ValueCell.update(renderable.values.dCalcType, 'density')
  234. renderable.update()
  235. renderable.use()
  236. gl.blendFunc(gl.ONE, gl.ONE)
  237. gl.blendEquation(gl.FUNC_ADD)
  238. }
  239. function setupGroupIdRendering(webgl: WebGLContext, renderable: ComputeRenderable<any>) {
  240. const { gl } = webgl
  241. ValueCell.update(renderable.values.dCalcType, 'groupId')
  242. renderable.update()
  243. renderable.use()
  244. // overwrite color, don't change alpha
  245. gl.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ZERO, gl.ONE)
  246. gl.blendEquation(gl.FUNC_ADD)
  247. }
  248. function getTexture2dSize(maxTexSize: number, gridDim: Vec3) {
  249. let texDimX = 0
  250. let texDimY = gridDim[1]
  251. let texRows = 1
  252. let texCols = gridDim[2]
  253. if (maxTexSize < gridDim[0] * gridDim[2]) {
  254. texCols = Math.floor(maxTexSize / gridDim[0])
  255. texRows = Math.ceil(gridDim[2] / texCols)
  256. texDimX = texCols * gridDim[0]
  257. texDimY *= texRows
  258. } else {
  259. texDimX = gridDim[0] * gridDim[2]
  260. }
  261. return { texDimX, texDimY, texRows, texCols }
  262. }
  263. async function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3) {
  264. // console.time('fieldFromTexture2d')
  265. const { framebufferCache } = ctx
  266. const [ dx, dy, dz ] = dim
  267. const { width, height } = texture
  268. const fboTexCols = Math.floor(width / dx)
  269. const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
  270. const data = space.create()
  271. const field = Tensor.create(space, data)
  272. const idData = space.create()
  273. const idField = Tensor.create(space, idData)
  274. const image = new Uint8Array(width * height * 4)
  275. const framebuffer = framebufferCache.get(ctx, FramebufferName).value
  276. framebuffer.bind()
  277. texture.attachFramebuffer(framebuffer, 0)
  278. // TODO too slow, why? Too many checks if gpu ready???
  279. // await ctx.readPixelsAsync(0, 0, width, height, image)
  280. ctx.readPixels(0, 0, width, height, image)
  281. // printImageData(createImageData(image, width, height), 1/3)
  282. let j = 0
  283. let tmpCol = 0
  284. let tmpRow = 0
  285. for (let iz = 0; iz < dz; ++iz) {
  286. if (tmpCol >= fboTexCols ) {
  287. tmpCol = 0
  288. tmpRow += dy
  289. }
  290. for (let iy = 0; iy < dy; ++iy) {
  291. for (let ix = 0; ix < dx; ++ix) {
  292. const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix)
  293. data[j] = image[idx + 3] / 255
  294. idData[j] = decodeFloatRGB(image[idx], image[idx + 1], image[idx + 2])
  295. j++
  296. }
  297. }
  298. tmpCol++
  299. }
  300. // console.timeEnd('fieldFromTexture2d')
  301. return { field, idField }
  302. }