canvasView.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import {mat3, vec2} from 'gl-matrix';
  2. import {FlamegraphCanvas} from 'sentry/utils/profiling/flamegraphCanvas';
  3. import {
  4. computeClampedConfigView,
  5. Rect,
  6. transformMatrixBetweenRect,
  7. } from 'sentry/utils/profiling/gl/utils';
  8. export class CanvasView<T extends {configSpace: Rect}> {
  9. configView: Rect = Rect.Empty();
  10. configSpace: Readonly<Rect> = Rect.Empty();
  11. configSpaceTransform: mat3 = mat3.create();
  12. inverted: boolean;
  13. minWidth: number;
  14. depthOffset: number;
  15. barHeight: number;
  16. model: T;
  17. constructor({
  18. canvas,
  19. options,
  20. model,
  21. }: {
  22. canvas: FlamegraphCanvas;
  23. model: T;
  24. options: {
  25. barHeight: number;
  26. configSpaceTransform?: Rect;
  27. depthOffset?: number;
  28. inverted?: boolean;
  29. minWidth?: number;
  30. };
  31. }) {
  32. this.inverted = !!options.inverted;
  33. this.minWidth = options.minWidth ?? 0;
  34. this.model = model;
  35. this.depthOffset = options.depthOffset ?? 0;
  36. this.barHeight = options.barHeight ? options.barHeight * window.devicePixelRatio : 0;
  37. // This is a transformation matrix that is applied to the configView, it allows us to
  38. // transform an entire view and render it without having to recompute the models.
  39. // This is useful for example when we want to offset a profile by some duration.
  40. this.configSpaceTransform = options.configSpaceTransform
  41. ? mat3.fromValues(
  42. options.configSpaceTransform.width || 1,
  43. 0,
  44. 0,
  45. 0,
  46. options.configSpaceTransform.height || 1,
  47. 0,
  48. options.configSpaceTransform.x || 0,
  49. options.configSpaceTransform.y || 0,
  50. 1
  51. )
  52. : mat3.create();
  53. this.initConfigSpace(canvas);
  54. }
  55. private _initConfigSpace(canvas: FlamegraphCanvas): void {
  56. this.configSpace = new Rect(
  57. 0,
  58. 0,
  59. this.model.configSpace.width,
  60. Math.max(
  61. this.model.configSpace.height + this.depthOffset,
  62. canvas.physicalSpace.height / this.barHeight
  63. )
  64. );
  65. }
  66. private _initConfigView(canvas: FlamegraphCanvas, space: Rect): void {
  67. this.configView = Rect.From(space).withHeight(
  68. canvas.physicalSpace.height / this.barHeight
  69. );
  70. }
  71. initConfigSpace(canvas: FlamegraphCanvas): void {
  72. this._initConfigSpace(canvas);
  73. this._initConfigView(canvas, this.configSpace);
  74. }
  75. resizeConfigSpace(canvas: FlamegraphCanvas): void {
  76. this._initConfigSpace(canvas);
  77. this._initConfigView(canvas, this.configView);
  78. }
  79. resetConfigView(canvas: FlamegraphCanvas): void {
  80. this._initConfigView(canvas, this.configSpace);
  81. }
  82. setConfigView(configView: Rect) {
  83. this.configView = computeClampedConfigView(configView, {
  84. width: {
  85. min: this.minWidth,
  86. max: this.configSpace.width,
  87. },
  88. height: {
  89. min: 0,
  90. max: this.configSpace.height,
  91. },
  92. });
  93. }
  94. transformConfigView(transformation: mat3) {
  95. this.setConfigView(this.configView.transformRect(transformation));
  96. }
  97. toConfigSpace(space: Rect): mat3 {
  98. const toConfigSpace = transformMatrixBetweenRect(space, this.configSpace);
  99. if (this.inverted) {
  100. mat3.multiply(toConfigSpace, this.configSpace.invertYTransform(), toConfigSpace);
  101. }
  102. return toConfigSpace;
  103. }
  104. toConfigView(space: Rect): mat3 {
  105. const toConfigView = transformMatrixBetweenRect(space, this.configView);
  106. if (this.inverted) {
  107. mat3.multiply(toConfigView, this.configView.invertYTransform(), toConfigView);
  108. }
  109. return toConfigView;
  110. }
  111. fromConfigSpace(space: Rect): mat3 {
  112. const fromConfigSpace = transformMatrixBetweenRect(this.configSpace, space);
  113. if (this.inverted) {
  114. mat3.multiply(fromConfigSpace, space.invertYTransform(), fromConfigSpace);
  115. }
  116. return fromConfigSpace;
  117. }
  118. fromConfigView(space: Rect): mat3 {
  119. const fromConfigView = transformMatrixBetweenRect(this.configView, space);
  120. if (this.inverted) {
  121. mat3.multiply(fromConfigView, space.invertYTransform(), fromConfigView);
  122. }
  123. return fromConfigView;
  124. }
  125. fromTransformedConfigView(space: Rect): mat3 {
  126. const fromConfigView = mat3.multiply(
  127. mat3.create(),
  128. transformMatrixBetweenRect(this.configView, space),
  129. this.configSpaceTransform
  130. );
  131. if (this.inverted) {
  132. mat3.multiply(fromConfigView, space.invertYTransform(), fromConfigView);
  133. }
  134. return fromConfigView;
  135. }
  136. fromTransformedConfigSpace(space: Rect): mat3 {
  137. const fromConfigView = mat3.multiply(
  138. mat3.create(),
  139. transformMatrixBetweenRect(this.configSpace, space),
  140. this.configSpaceTransform
  141. );
  142. if (this.inverted) {
  143. mat3.multiply(fromConfigView, space.invertYTransform(), fromConfigView);
  144. }
  145. return fromConfigView;
  146. }
  147. getConfigSpaceCursor(logicalSpaceCursor: vec2, canvas: FlamegraphCanvas): vec2 {
  148. const physicalSpaceCursor = vec2.transformMat3(
  149. vec2.create(),
  150. logicalSpaceCursor,
  151. canvas.logicalToPhysicalSpace
  152. );
  153. return vec2.transformMat3(
  154. vec2.create(),
  155. physicalSpaceCursor,
  156. this.toConfigSpace(canvas.physicalSpace)
  157. );
  158. }
  159. getTransformedConfigSpaceCursor(
  160. logicalSpaceCursor: vec2,
  161. canvas: FlamegraphCanvas
  162. ): vec2 {
  163. const physicalSpaceCursor = vec2.transformMat3(
  164. vec2.create(),
  165. logicalSpaceCursor,
  166. canvas.logicalToPhysicalSpace
  167. );
  168. const finalMatrix = mat3.multiply(
  169. mat3.create(),
  170. mat3.invert(mat3.create(), this.configSpaceTransform),
  171. this.toConfigSpace(canvas.physicalSpace)
  172. );
  173. const configViewCursor = vec2.transformMat3(
  174. vec2.create(),
  175. physicalSpaceCursor,
  176. finalMatrix
  177. );
  178. return configViewCursor;
  179. }
  180. getConfigViewCursor(logicalSpaceCursor: vec2, canvas: FlamegraphCanvas): vec2 {
  181. const physicalSpaceCursor = vec2.transformMat3(
  182. vec2.create(),
  183. logicalSpaceCursor,
  184. canvas.logicalToPhysicalSpace
  185. );
  186. return vec2.transformMat3(
  187. vec2.create(),
  188. physicalSpaceCursor,
  189. this.toConfigView(canvas.physicalSpace)
  190. );
  191. }
  192. getTransformedConfigViewCursor(
  193. logicalSpaceCursor: vec2,
  194. canvas: FlamegraphCanvas
  195. ): vec2 {
  196. const physicalSpaceCursor = vec2.transformMat3(
  197. vec2.create(),
  198. logicalSpaceCursor,
  199. canvas.logicalToPhysicalSpace
  200. );
  201. const finalMatrix = mat3.multiply(
  202. mat3.create(),
  203. mat3.invert(mat3.create(), this.configSpaceTransform),
  204. this.toConfigView(canvas.physicalSpace)
  205. );
  206. const configViewCursor = vec2.transformMat3(
  207. vec2.create(),
  208. physicalSpaceCursor,
  209. finalMatrix
  210. );
  211. return configViewCursor;
  212. }
  213. /**
  214. * Applies the inverse of the config space transform to the given config space rect
  215. * @returns Rect
  216. */
  217. toOriginConfigView(space: Rect): Rect {
  218. return space.transformRect(mat3.invert(mat3.create(), this.configSpaceTransform));
  219. }
  220. }