minMaxReducer.ts 9.2 KB

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