reduction.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { createComputeRenderable, ComputeRenderable } from '../../renderable'
  7. import { WebGLContext } from '../../webgl/context';
  8. import { createComputeRenderItem } from '../../webgl/render-item';
  9. import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
  10. import { Texture, createTexture } from 'mol-gl/webgl/texture';
  11. import { ShaderCode } from 'mol-gl/shader-code';
  12. import { ValueCell } from 'mol-util';
  13. import { QuadSchema, QuadValues } from '../util';
  14. import { Vec2 } from 'mol-math/linear-algebra';
  15. import { getHistopyramidSum } from './sum';
  16. import { Framebuffer, createFramebuffer } from 'mol-gl/webgl/framebuffer';
  17. import { isPowerOfTwo } from 'mol-math/misc';
  18. import quad_vert from 'mol-gl/shader/quad.vert'
  19. import reduction_frag from 'mol-gl/shader/histogram-pyramid/reduction.frag'
  20. const HistopyramidReductionSchema = {
  21. ...QuadSchema,
  22. tPreviousLevel: TextureSpec('texture', 'rgba', 'float', 'nearest'),
  23. uSize: UniformSpec('f'),
  24. uTexSize: UniformSpec('f'),
  25. }
  26. let HistopyramidReductionRenderable: ComputeRenderable<Values<typeof HistopyramidReductionSchema>>
  27. function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
  28. if (HistopyramidReductionRenderable) {
  29. ValueCell.update(HistopyramidReductionRenderable.values.tPreviousLevel, initialTexture)
  30. HistopyramidReductionRenderable.update()
  31. return HistopyramidReductionRenderable
  32. } else {
  33. const values: Values<typeof HistopyramidReductionSchema> = {
  34. ...QuadValues,
  35. tPreviousLevel: ValueCell.create(initialTexture),
  36. uSize: ValueCell.create(0),
  37. uTexSize: ValueCell.create(0),
  38. }
  39. const schema = { ...HistopyramidReductionSchema }
  40. const shaderCode = ShaderCode(quad_vert, reduction_frag)
  41. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
  42. HistopyramidReductionRenderable = createComputeRenderable(renderItem, values);
  43. return HistopyramidReductionRenderable
  44. }
  45. }
  46. type TextureFramebuffer = { texture: Texture, framebuffer: Framebuffer }
  47. const LevelTexturesFramebuffers: TextureFramebuffer[] = []
  48. function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
  49. let textureFramebuffer = LevelTexturesFramebuffers[level]
  50. const size = Math.pow(2, level)
  51. if (textureFramebuffer === undefined) {
  52. const texture = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
  53. const framebuffer = createFramebuffer(ctx.gl, ctx.stats)
  54. texture.attachFramebuffer(framebuffer, 0)
  55. textureFramebuffer = { texture, framebuffer }
  56. textureFramebuffer.texture.define(size, size)
  57. LevelTexturesFramebuffers[level] = textureFramebuffer
  58. }
  59. return textureFramebuffer
  60. }
  61. function setRenderingDefaults(ctx: WebGLContext) {
  62. const { gl, state } = ctx
  63. state.disable(gl.CULL_FACE)
  64. state.disable(gl.BLEND)
  65. state.disable(gl.DEPTH_TEST)
  66. state.disable(gl.SCISSOR_TEST)
  67. state.depthMask(false)
  68. state.colorMask(true, true, true, true)
  69. state.clearColor(0, 0, 0, 0)
  70. }
  71. export interface HistogramPyramid {
  72. pyramidTex: Texture
  73. count: number
  74. height: number
  75. levels: number
  76. scale: Vec2
  77. }
  78. export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2): HistogramPyramid {
  79. const { gl, framebufferCache } = ctx
  80. // printTexture(ctx, inputTexture, 2)
  81. if (inputTexture.width !== inputTexture.height || !isPowerOfTwo(inputTexture.width)) {
  82. throw new Error('inputTexture must be of square power-of-two size')
  83. }
  84. // This part set the levels
  85. const levels = Math.ceil(Math.log(inputTexture.width) / Math.log(2))
  86. const maxSize = Math.pow(2, levels)
  87. // console.log('levels', levels, 'maxSize', maxSize)
  88. const pyramidTexture = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
  89. pyramidTexture.define(maxSize, maxSize)
  90. const framebuffer = framebufferCache.get('reduction').value
  91. pyramidTexture.attachFramebuffer(framebuffer, 0)
  92. gl.clear(gl.COLOR_BUFFER_BIT)
  93. const levelTexturesFramebuffers: TextureFramebuffer[] = []
  94. for (let i = 0; i < levels; ++i) levelTexturesFramebuffers.push(getLevelTextureFramebuffer(ctx, i))
  95. const renderable = getHistopyramidReductionRenderable(ctx, inputTexture)
  96. ctx.state.currentRenderItemId = -1
  97. setRenderingDefaults(ctx)
  98. let offset = 0;
  99. for (let i = 0; i < levels; i++) {
  100. const currLevel = levels - 1 - i
  101. const tf = levelTexturesFramebuffers[currLevel]
  102. tf.framebuffer.bind()
  103. // levelTextures[currLevel].attachFramebuffer(framebuffer, 0)
  104. const size = Math.pow(2, currLevel)
  105. // console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i)
  106. gl.clear(gl.COLOR_BUFFER_BIT)
  107. gl.viewport(0, 0, size, size)
  108. ValueCell.update(renderable.values.uSize, Math.pow(2, i + 1) / maxSize)
  109. ValueCell.update(renderable.values.uTexSize, size)
  110. if (i > 0) {
  111. ValueCell.update(renderable.values.tPreviousLevel, levelTexturesFramebuffers[levels - i].texture)
  112. renderable.update()
  113. }
  114. ctx.state.currentRenderItemId = -1
  115. renderable.render()
  116. pyramidTexture.bind(0)
  117. gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, offset, 0, 0, 0, size, size);
  118. pyramidTexture.unbind(0)
  119. offset += size;
  120. }
  121. gl.finish()
  122. // printTexture(ctx, pyramidTexture, 2)
  123. //
  124. const finalCount = getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture)
  125. const height = Math.ceil(finalCount / Math.pow(2, levels))
  126. // const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height)
  127. // console.log('height', height, 'finalCount', finalCount, 'scale', scale)
  128. return {
  129. pyramidTex: pyramidTexture,
  130. count: finalCount,
  131. height,
  132. levels,
  133. scale
  134. }
  135. }