minMaxReducer.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import { Observable } from "./observable.js";
  2. import { PostProcess } from "../PostProcesses/postProcess.js";
  3. import { PostProcessManager } from "../PostProcesses/postProcessManager.js";
  4. import "../Shaders/minmaxRedux.fragment.js";
  5. /**
  6. * This class computes a min/max reduction from a texture: it means it computes the minimum
  7. * and maximum values from all values of the texture.
  8. * It is performed on the GPU for better performances, thanks to a succession of post processes.
  9. * The source values are read from the red channel of the texture.
  10. */
  11. export class MinMaxReducer {
  12. /**
  13. * Creates a min/max reducer
  14. * @param camera The camera to use for the post processes
  15. */
  16. constructor(camera) {
  17. /**
  18. * Observable triggered when the computation has been performed
  19. */
  20. this.onAfterReductionPerformed = new Observable();
  21. this._forceFullscreenViewport = true;
  22. this._activated = false;
  23. this._camera = camera;
  24. this._postProcessManager = new PostProcessManager(camera.getScene());
  25. this._onContextRestoredObserver = camera.getEngine().onContextRestoredObservable.add(() => {
  26. this._postProcessManager._rebuild();
  27. });
  28. }
  29. /**
  30. * Gets the texture used to read the values from.
  31. */
  32. get sourceTexture() {
  33. return this._sourceTexture;
  34. }
  35. /**
  36. * Sets the source texture to read the values from.
  37. * One must indicate if the texture is a depth texture or not through the depthRedux parameter
  38. * because in such textures '1' value must not be taken into account to compute the maximum
  39. * as this value is used to clear the texture.
  40. * Note that the computation is not activated by calling this function, you must call activate() for that!
  41. * @param sourceTexture The texture to read the values from. The values should be in the red channel.
  42. * @param depthRedux Indicates if the texture is a depth texture or not
  43. * @param type The type of the textures created for the reduction (defaults to TEXTURETYPE_HALF_FLOAT)
  44. * @param forceFullscreenViewport Forces the post processes used for the reduction to be applied without taking into account viewport (defaults to true)
  45. */
  46. setSourceTexture(sourceTexture, depthRedux, type = 2, forceFullscreenViewport = true) {
  47. if (sourceTexture === this._sourceTexture) {
  48. return;
  49. }
  50. this.dispose(false);
  51. this._sourceTexture = sourceTexture;
  52. this._reductionSteps = [];
  53. this._forceFullscreenViewport = forceFullscreenViewport;
  54. const scene = this._camera.getScene();
  55. // create the first step
  56. const reductionInitial = new PostProcess("Initial reduction phase", "minmaxRedux", // shader
  57. ["texSize"], ["sourceTexture"], // textures
  58. 1.0, // options
  59. null, // camera
  60. 1, // sampling
  61. scene.getEngine(), // engine
  62. false, // reusable
  63. "#define INITIAL" + (depthRedux ? "\n#define DEPTH_REDUX" : ""), // defines
  64. type, undefined, undefined, undefined, 7);
  65. reductionInitial.autoClear = false;
  66. reductionInitial.forceFullscreenViewport = forceFullscreenViewport;
  67. let w = this._sourceTexture.getRenderWidth(), h = this._sourceTexture.getRenderHeight();
  68. reductionInitial.onApply = ((w, h) => {
  69. return (effect) => {
  70. effect.setTexture("sourceTexture", this._sourceTexture);
  71. effect.setFloat2("texSize", w, h);
  72. };
  73. })(w, h);
  74. this._reductionSteps.push(reductionInitial);
  75. let index = 1;
  76. // create the additional steps
  77. while (w > 1 || h > 1) {
  78. w = Math.max(Math.round(w / 2), 1);
  79. h = Math.max(Math.round(h / 2), 1);
  80. const reduction = new PostProcess("Reduction phase " + index, "minmaxRedux", // shader
  81. ["texSize"], null, { width: w, height: h }, // options
  82. null, // camera
  83. 1, // sampling
  84. scene.getEngine(), // engine
  85. false, // reusable
  86. "#define " + (w == 1 && h == 1 ? "LAST" : w == 1 || h == 1 ? "ONEBEFORELAST" : "MAIN"), // defines
  87. type, undefined, undefined, undefined, 7);
  88. reduction.autoClear = false;
  89. reduction.forceFullscreenViewport = forceFullscreenViewport;
  90. reduction.onApply = ((w, h) => {
  91. return (effect) => {
  92. if (w == 1 || h == 1) {
  93. effect.setInt2("texSize", w, h);
  94. }
  95. else {
  96. effect.setFloat2("texSize", w, h);
  97. }
  98. };
  99. })(w, h);
  100. this._reductionSteps.push(reduction);
  101. index++;
  102. if (w == 1 && h == 1) {
  103. const func = (w, h, reduction) => {
  104. const buffer = new Float32Array(4 * w * h), minmax = { min: 0, max: 0 };
  105. return () => {
  106. scene.getEngine()._readTexturePixels(reduction.inputTexture.texture, w, h, -1, 0, buffer, false);
  107. minmax.min = buffer[0];
  108. minmax.max = buffer[1];
  109. this.onAfterReductionPerformed.notifyObservers(minmax);
  110. };
  111. };
  112. reduction.onAfterRenderObservable.add(func(w, h, reduction));
  113. }
  114. }
  115. }
  116. /**
  117. * Defines the refresh rate of the computation.
  118. * Use 0 to compute just once, 1 to compute on every frame, 2 to compute every two frames and so on...
  119. */
  120. get refreshRate() {
  121. return this._sourceTexture ? this._sourceTexture.refreshRate : -1;
  122. }
  123. set refreshRate(value) {
  124. if (this._sourceTexture) {
  125. this._sourceTexture.refreshRate = value;
  126. }
  127. }
  128. /**
  129. * Gets the activation status of the reducer
  130. */
  131. get activated() {
  132. return this._activated;
  133. }
  134. /**
  135. * Activates the reduction computation.
  136. * When activated, the observers registered in onAfterReductionPerformed are
  137. * called after the computation is performed
  138. */
  139. activate() {
  140. if (this._onAfterUnbindObserver || !this._sourceTexture) {
  141. return;
  142. }
  143. this._onAfterUnbindObserver = this._sourceTexture.onAfterUnbindObservable.add(() => {
  144. const engine = this._camera.getScene().getEngine();
  145. engine._debugPushGroup?.(`min max reduction`, 1);
  146. this._reductionSteps[0].activate(this._camera);
  147. this._postProcessManager.directRender(this._reductionSteps, this._reductionSteps[0].inputTexture, this._forceFullscreenViewport);
  148. engine.unBindFramebuffer(this._reductionSteps[0].inputTexture, false);
  149. engine._debugPopGroup?.(1);
  150. });
  151. this._activated = true;
  152. }
  153. /**
  154. * Deactivates the reduction computation.
  155. */
  156. deactivate() {
  157. if (!this._onAfterUnbindObserver || !this._sourceTexture) {
  158. return;
  159. }
  160. this._sourceTexture.onAfterUnbindObservable.remove(this._onAfterUnbindObserver);
  161. this._onAfterUnbindObserver = null;
  162. this._activated = false;
  163. }
  164. /**
  165. * Disposes the min/max reducer
  166. * @param disposeAll true to dispose all the resources. You should always call this function with true as the parameter (or without any parameter as it is the default one). This flag is meant to be used internally.
  167. */
  168. dispose(disposeAll = true) {
  169. if (disposeAll) {
  170. this.onAfterReductionPerformed.clear();
  171. if (this._onContextRestoredObserver) {
  172. this._camera.getEngine().onContextRestoredObservable.remove(this._onContextRestoredObserver);
  173. this._onContextRestoredObserver = null;
  174. }
  175. }
  176. this.deactivate();
  177. if (this._reductionSteps) {
  178. for (let i = 0; i < this._reductionSteps.length; ++i) {
  179. this._reductionSteps[i].dispose();
  180. }
  181. this._reductionSteps = null;
  182. }
  183. if (this._postProcessManager && disposeAll) {
  184. this._postProcessManager.dispose();
  185. }
  186. this._sourceTexture = null;
  187. }
  188. }
  189. //# sourceMappingURL=minMaxReducer.js.map