reduction.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { createComputeRenderable } 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, TextureFilter, TextureFormat, TextureKind, TextureType } 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 } 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. const HistogramPyramidName = 'histogram-pyramid';
  27. function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
  28. if (ctx.namedComputeRenderables[HistogramPyramidName]) {
  29. const v = ctx.namedComputeRenderables[HistogramPyramidName].values;
  30. ValueCell.update(v.tPreviousLevel, initialTexture);
  31. ctx.namedComputeRenderables[HistogramPyramidName].update();
  32. } else {
  33. ctx.namedComputeRenderables[HistogramPyramidName] = createHistopyramidReductionRenderable(ctx, initialTexture);
  34. }
  35. return ctx.namedComputeRenderables[HistogramPyramidName];
  36. }
  37. function createHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
  38. const values: Values<typeof HistopyramidReductionSchema> = {
  39. ...QuadValues,
  40. tPreviousLevel: ValueCell.create(initialTexture),
  41. uSize: ValueCell.create(0),
  42. uTexSize: ValueCell.create(0),
  43. };
  44. const schema = { ...HistopyramidReductionSchema };
  45. const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
  46. const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
  47. return createComputeRenderable(renderItem, values);
  48. }
  49. type TextureFramebuffer = { texture: Texture, framebuffer: Framebuffer }
  50. const LevelTexturesFramebuffers: TextureFramebuffer[] = [];
  51. function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
  52. let textureFramebuffer = LevelTexturesFramebuffers[level];
  53. const size = Math.pow(2, level);
  54. if (textureFramebuffer === undefined) {
  55. const texture = getTexture(`level${level}`, ctx, 'image-float32', 'rgba', 'float', 'nearest');
  56. const framebuffer = getFramebuffer(`level${level}`, ctx);
  57. texture.attachFramebuffer(framebuffer, 0);
  58. textureFramebuffer = { texture, framebuffer };
  59. textureFramebuffer.texture.define(size, size);
  60. LevelTexturesFramebuffers[level] = textureFramebuffer;
  61. }
  62. return textureFramebuffer;
  63. }
  64. function setRenderingDefaults(ctx: WebGLContext) {
  65. const { gl, state } = ctx;
  66. state.disable(gl.CULL_FACE);
  67. state.disable(gl.BLEND);
  68. state.disable(gl.DEPTH_TEST);
  69. state.disable(gl.SCISSOR_TEST);
  70. state.depthMask(false);
  71. state.colorMask(true, true, true, true);
  72. state.clearColor(0, 0, 0, 0);
  73. }
  74. function getFramebuffer(name: string, webgl: WebGLContext): Framebuffer {
  75. const _name = `${HistogramPyramidName}-${name}`;
  76. if (!webgl.namedFramebuffers[_name]) {
  77. webgl.namedFramebuffers[_name] = webgl.resources.framebuffer();
  78. }
  79. return webgl.namedFramebuffers[_name];
  80. }
  81. function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter): Texture {
  82. const _name = `${HistogramPyramidName}-${name}`;
  83. if (!webgl.namedTextures[_name]) {
  84. webgl.namedTextures[_name] = webgl.resources.texture(kind, format, type, filter);
  85. }
  86. return webgl.namedTextures[_name];
  87. }
  88. export interface HistogramPyramid {
  89. pyramidTex: Texture
  90. count: number
  91. height: number
  92. levels: number
  93. scale: Vec2
  94. }
  95. export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2): HistogramPyramid {
  96. const { gl } = ctx;
  97. // printTexture(ctx, inputTexture, 2)
  98. if (inputTexture.getWidth() !== inputTexture.getHeight() || !isPowerOfTwo(inputTexture.getWidth())) {
  99. throw new Error('inputTexture must be of square power-of-two size');
  100. }
  101. // This part set the levels
  102. const levels = Math.ceil(Math.log(inputTexture.getWidth()) / Math.log(2));
  103. const maxSize = Math.pow(2, levels);
  104. // console.log('levels', levels, 'maxSize', maxSize, 'input', inputTexture.getWidth());
  105. const pyramidTexture = getTexture('pyramid', ctx, 'image-float32', 'rgba', 'float', 'nearest');
  106. pyramidTexture.define(maxSize, maxSize);
  107. const framebuffer = getFramebuffer('pyramid', ctx);
  108. pyramidTexture.attachFramebuffer(framebuffer, 0);
  109. gl.viewport(0, 0, maxSize, maxSize);
  110. gl.clear(gl.COLOR_BUFFER_BIT);
  111. const levelTexturesFramebuffers: TextureFramebuffer[] = [];
  112. for (let i = 0; i < levels; ++i) levelTexturesFramebuffers.push(getLevelTextureFramebuffer(ctx, i));
  113. const renderable = getHistopyramidReductionRenderable(ctx, inputTexture);
  114. ctx.state.currentRenderItemId = -1;
  115. setRenderingDefaults(ctx);
  116. let offset = 0;
  117. for (let i = 0; i < levels; i++) {
  118. const currLevel = levels - 1 - i;
  119. const tf = levelTexturesFramebuffers[currLevel];
  120. tf.framebuffer.bind();
  121. // levelTextures[currLevel].attachFramebuffer(framebuffer, 0)
  122. const size = Math.pow(2, currLevel);
  123. // console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i)
  124. ValueCell.update(renderable.values.uSize, Math.pow(2, i + 1) / maxSize);
  125. ValueCell.update(renderable.values.uTexSize, size);
  126. if (i > 0) {
  127. ValueCell.update(renderable.values.tPreviousLevel, levelTexturesFramebuffers[levels - i].texture);
  128. renderable.update();
  129. }
  130. ctx.state.currentRenderItemId = -1;
  131. gl.viewport(0, 0, size, size);
  132. gl.clear(gl.COLOR_BUFFER_BIT);
  133. renderable.render();
  134. pyramidTexture.bind(0);
  135. gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, offset, 0, 0, 0, size, size);
  136. pyramidTexture.unbind(0);
  137. offset += size;
  138. }
  139. gl.finish();
  140. // printTexture(ctx, pyramidTexture, 2)
  141. //
  142. const finalCount = getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture);
  143. const height = Math.ceil(finalCount / Math.pow(2, levels));
  144. // const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height)
  145. // console.log('height', height, 'finalCount', finalCount, 'scale', scale)
  146. return {
  147. pyramidTex: pyramidTexture,
  148. count: finalCount,
  149. height,
  150. levels,
  151. scale
  152. };
  153. }