gpu.ts 15 KB

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