@luma.gl/core 9.0.0-alpha.9 → 9.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/LICENSE +3 -1
  2. package/README.md +4 -4
  3. package/dist/adapter/attribute-utils/get-attribute-from-layouts.d.ts +52 -0
  4. package/dist/adapter/attribute-utils/get-attribute-from-layouts.d.ts.map +1 -0
  5. package/dist/adapter/attribute-utils/get-attribute-from-layouts.js +130 -0
  6. package/dist/adapter/attribute-utils/get-attribute-from-layouts.js.map +1 -0
  7. package/dist/adapter/canvas-context.d.ts +109 -0
  8. package/dist/adapter/canvas-context.d.ts.map +1 -0
  9. package/dist/adapter/canvas-context.js +254 -0
  10. package/dist/adapter/canvas-context.js.map +1 -0
  11. package/dist/adapter/device.d.ts +219 -0
  12. package/dist/adapter/device.d.ts.map +1 -0
  13. package/dist/adapter/device.js +99 -0
  14. package/dist/adapter/device.js.map +1 -0
  15. package/dist/adapter/resources/buffer.d.ts +56 -0
  16. package/dist/adapter/resources/buffer.d.ts.map +1 -0
  17. package/dist/adapter/resources/buffer.js +62 -0
  18. package/dist/adapter/resources/buffer.js.map +1 -0
  19. package/dist/adapter/resources/command-buffer.d.ts +12 -0
  20. package/dist/adapter/resources/command-buffer.d.ts.map +1 -0
  21. package/dist/adapter/resources/command-buffer.js +15 -0
  22. package/dist/adapter/resources/command-buffer.js.map +1 -0
  23. package/dist/adapter/resources/command-encoder.d.ts +113 -0
  24. package/dist/adapter/resources/command-encoder.d.ts.map +1 -0
  25. package/dist/adapter/resources/command-encoder.js +19 -0
  26. package/dist/adapter/resources/command-encoder.js.map +1 -0
  27. package/dist/adapter/resources/compute-pass.d.ts +31 -0
  28. package/dist/adapter/resources/compute-pass.d.ts.map +1 -0
  29. package/dist/adapter/resources/compute-pass.js +15 -0
  30. package/dist/adapter/resources/compute-pass.js.map +1 -0
  31. package/dist/adapter/resources/compute-pipeline.d.ts +24 -0
  32. package/dist/adapter/resources/compute-pipeline.d.ts.map +1 -0
  33. package/dist/adapter/resources/compute-pipeline.js +20 -0
  34. package/dist/adapter/resources/compute-pipeline.js.map +1 -0
  35. package/dist/adapter/resources/external-texture.d.ts +12 -0
  36. package/dist/adapter/resources/external-texture.d.ts.map +1 -0
  37. package/dist/adapter/resources/external-texture.js +17 -0
  38. package/dist/adapter/resources/external-texture.js.map +1 -0
  39. package/dist/adapter/resources/framebuffer.d.ts +50 -0
  40. package/dist/adapter/resources/framebuffer.d.ts.map +1 -0
  41. package/dist/adapter/resources/framebuffer.js +102 -0
  42. package/dist/adapter/resources/framebuffer.js.map +1 -0
  43. package/dist/adapter/resources/render-pass.d.ts +51 -0
  44. package/dist/adapter/resources/render-pass.d.ts.map +1 -0
  45. package/dist/adapter/resources/render-pass.js +23 -0
  46. package/dist/adapter/resources/render-pass.js.map +1 -0
  47. package/dist/adapter/resources/render-pipeline.d.ts +89 -0
  48. package/dist/adapter/resources/render-pipeline.d.ts.map +1 -0
  49. package/dist/adapter/resources/render-pipeline.js +36 -0
  50. package/dist/adapter/resources/render-pipeline.js.map +1 -0
  51. package/dist/adapter/resources/resource.d.ts +73 -0
  52. package/dist/adapter/resources/resource.d.ts.map +1 -0
  53. package/dist/adapter/resources/resource.js +101 -0
  54. package/dist/adapter/resources/resource.js.map +1 -0
  55. package/dist/adapter/resources/sampler.d.ts +42 -0
  56. package/dist/adapter/resources/sampler.d.ts.map +1 -0
  57. package/dist/adapter/resources/sampler.js +26 -0
  58. package/dist/adapter/resources/sampler.js.map +1 -0
  59. package/dist/adapter/resources/shader.d.ts +45 -0
  60. package/dist/adapter/resources/shader.d.ts.map +1 -0
  61. package/dist/adapter/resources/shader.js +90 -0
  62. package/dist/adapter/resources/shader.js.map +1 -0
  63. package/dist/adapter/resources/texture.d.ts +90 -0
  64. package/dist/adapter/resources/texture.d.ts.map +1 -0
  65. package/dist/adapter/resources/texture.js +45 -0
  66. package/dist/adapter/resources/texture.js.map +1 -0
  67. package/dist/adapter/resources/transform-feedback.d.ts +30 -0
  68. package/dist/adapter/resources/transform-feedback.d.ts.map +1 -0
  69. package/dist/adapter/resources/transform-feedback.js +17 -0
  70. package/dist/adapter/resources/transform-feedback.js.map +1 -0
  71. package/dist/adapter/resources/vertex-array.d.ts +40 -0
  72. package/dist/adapter/resources/vertex-array.d.ts.map +1 -0
  73. package/dist/adapter/resources/vertex-array.js +24 -0
  74. package/dist/adapter/resources/vertex-array.js.map +1 -0
  75. package/dist/adapter/type-utils/decode-attribute-type.d.ts +20 -0
  76. package/dist/adapter/type-utils/decode-attribute-type.d.ts.map +1 -0
  77. package/dist/adapter/type-utils/decode-attribute-type.js +60 -0
  78. package/dist/adapter/type-utils/decode-attribute-type.js.map +1 -0
  79. package/dist/adapter/type-utils/decode-data-type.d.ts +16 -0
  80. package/dist/adapter/type-utils/decode-data-type.d.ts.map +1 -0
  81. package/dist/adapter/type-utils/decode-data-type.js +43 -0
  82. package/dist/adapter/type-utils/decode-data-type.js.map +1 -0
  83. package/dist/adapter/type-utils/decode-shader-types.d.ts +9 -0
  84. package/dist/adapter/type-utils/decode-shader-types.d.ts.map +1 -0
  85. package/dist/adapter/type-utils/decode-shader-types.js +103 -0
  86. package/dist/adapter/type-utils/decode-shader-types.js.map +1 -0
  87. package/dist/adapter/type-utils/decode-texture-format.d.ts +19 -0
  88. package/dist/adapter/type-utils/decode-texture-format.d.ts.map +1 -0
  89. package/dist/adapter/type-utils/decode-texture-format.js +101 -0
  90. package/dist/adapter/type-utils/decode-texture-format.js.map +1 -0
  91. package/dist/adapter/type-utils/decode-vertex-format.d.ts +22 -0
  92. package/dist/adapter/type-utils/decode-vertex-format.d.ts.map +1 -0
  93. package/dist/adapter/type-utils/decode-vertex-format.js +25 -0
  94. package/dist/adapter/type-utils/decode-vertex-format.js.map +1 -0
  95. package/dist/adapter/type-utils/vertex-format-from-attribute.d.ts +10 -0
  96. package/dist/adapter/type-utils/vertex-format-from-attribute.d.ts.map +1 -0
  97. package/dist/adapter/type-utils/vertex-format-from-attribute.js +76 -0
  98. package/dist/adapter/type-utils/vertex-format-from-attribute.js.map +1 -0
  99. package/dist/adapter/type-utils/wgsl-utils.d.ts +4 -0
  100. package/dist/adapter/type-utils/wgsl-utils.d.ts.map +1 -0
  101. package/dist/adapter/type-utils/wgsl-utils.js +15 -0
  102. package/dist/adapter/type-utils/wgsl-utils.js.map +1 -0
  103. package/dist/adapter/types/accessor.d.ts +23 -0
  104. package/dist/adapter/types/accessor.d.ts.map +1 -0
  105. package/dist/adapter/types/accessor.js +2 -0
  106. package/dist/adapter/types/accessor.js.map +1 -0
  107. package/dist/adapter/types/buffer-layout.d.ts +59 -0
  108. package/dist/adapter/types/buffer-layout.d.ts.map +1 -0
  109. package/dist/adapter/types/buffer-layout.js +2 -0
  110. package/dist/adapter/types/buffer-layout.js.map +1 -0
  111. package/dist/adapter/types/parameters.d.ts +97 -0
  112. package/dist/adapter/types/parameters.d.ts.map +1 -0
  113. package/dist/adapter/types/parameters.js +28 -0
  114. package/dist/adapter/types/parameters.js.map +1 -0
  115. package/dist/adapter/types/shader-layout.d.ts +145 -0
  116. package/dist/adapter/types/shader-layout.d.ts.map +1 -0
  117. package/dist/adapter/types/shader-layout.js +2 -0
  118. package/dist/adapter/types/shader-layout.js.map +1 -0
  119. package/dist/adapter/types/shader-types.d.ts +21 -0
  120. package/dist/adapter/types/shader-types.d.ts.map +1 -0
  121. package/dist/adapter/types/shader-types.js +2 -0
  122. package/dist/adapter/types/shader-types.js.map +1 -0
  123. package/dist/adapter/types/texture-formats.d.ts +12 -0
  124. package/dist/adapter/types/texture-formats.d.ts.map +1 -0
  125. package/dist/adapter/types/texture-formats.js +2 -0
  126. package/dist/adapter/types/texture-formats.js.map +1 -0
  127. package/dist/adapter/types/types.d.ts +90 -0
  128. package/dist/adapter/types/types.d.ts.map +1 -0
  129. package/dist/adapter/types/types.js +2 -0
  130. package/dist/adapter/types/types.js.map +1 -0
  131. package/dist/adapter/types/vertex-formats.d.ts +14 -0
  132. package/dist/adapter/types/vertex-formats.d.ts.map +1 -0
  133. package/dist/adapter/types/vertex-formats.js +2 -0
  134. package/dist/adapter/types/vertex-formats.js.map +1 -0
  135. package/dist/dist.dev.js +2976 -0
  136. package/dist/index.cjs +2445 -0
  137. package/dist/index.d.ts +81 -128
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +45 -18
  140. package/dist/index.js.map +1 -1
  141. package/dist/init.d.ts +5 -0
  142. package/dist/init.d.ts.map +1 -0
  143. package/dist/init.js +24 -0
  144. package/dist/init.js.map +1 -0
  145. package/dist/lib/compiler-log/compiler-message.d.ts +8 -0
  146. package/dist/lib/compiler-log/compiler-message.d.ts.map +1 -0
  147. package/dist/lib/compiler-log/compiler-message.js +2 -0
  148. package/dist/lib/compiler-log/compiler-message.js.map +1 -0
  149. package/dist/lib/compiler-log/format-compiler-log.d.ts +8 -0
  150. package/dist/lib/compiler-log/format-compiler-log.d.ts.map +1 -0
  151. package/dist/lib/compiler-log/format-compiler-log.js +64 -0
  152. package/dist/lib/compiler-log/format-compiler-log.js.map +1 -0
  153. package/dist/lib/compiler-log/get-shader-info.d.ts +9 -0
  154. package/dist/lib/compiler-log/get-shader-info.d.ts.map +1 -0
  155. package/dist/lib/compiler-log/get-shader-info.js +25 -0
  156. package/dist/lib/compiler-log/get-shader-info.js.map +1 -0
  157. package/dist/lib/luma.d.ts +22 -0
  158. package/dist/lib/luma.d.ts.map +1 -0
  159. package/dist/lib/luma.js +63 -0
  160. package/dist/lib/luma.js.map +1 -0
  161. package/dist/lib/uniforms/uniform-block.d.ts +29 -0
  162. package/dist/lib/uniforms/uniform-block.d.ts.map +1 -0
  163. package/dist/lib/uniforms/uniform-block.js +48 -0
  164. package/dist/lib/uniforms/uniform-block.js.map +1 -0
  165. package/dist/lib/uniforms/uniform-buffer-layout.d.ts +27 -0
  166. package/dist/lib/uniforms/uniform-buffer-layout.d.ts.map +1 -0
  167. package/dist/lib/uniforms/uniform-buffer-layout.js +76 -0
  168. package/dist/lib/uniforms/uniform-buffer-layout.js.map +1 -0
  169. package/dist/lib/uniforms/uniform-store.d.ts +62 -0
  170. package/dist/lib/uniforms/uniform-store.d.ts.map +1 -0
  171. package/dist/lib/uniforms/uniform-store.js +89 -0
  172. package/dist/lib/uniforms/uniform-store.js.map +1 -0
  173. package/dist/lib/uniforms/uniform.d.ts +10 -0
  174. package/dist/lib/uniforms/uniform.d.ts.map +1 -0
  175. package/dist/lib/uniforms/uniform.js +20 -0
  176. package/dist/lib/uniforms/uniform.js.map +1 -0
  177. package/dist/types.d.ts +19 -0
  178. package/dist/types.d.ts.map +1 -0
  179. package/dist/types.js +3 -0
  180. package/dist/types.js.map +1 -0
  181. package/dist/utils/array-equal.d.ts +5 -0
  182. package/dist/utils/array-equal.d.ts.map +1 -0
  183. package/dist/utils/array-equal.js +28 -0
  184. package/dist/utils/array-equal.js.map +1 -0
  185. package/dist/utils/array-utils-flat.d.ts +10 -0
  186. package/dist/utils/array-utils-flat.d.ts.map +1 -0
  187. package/dist/utils/array-utils-flat.js +36 -0
  188. package/dist/utils/array-utils-flat.js.map +1 -0
  189. package/dist/utils/assert.d.ts +2 -0
  190. package/dist/utils/assert.d.ts.map +1 -0
  191. package/dist/utils/assert.js +6 -0
  192. package/dist/utils/assert.js.map +1 -0
  193. package/dist/utils/cast.d.ts +3 -0
  194. package/dist/utils/cast.d.ts.map +1 -0
  195. package/dist/utils/cast.js +4 -0
  196. package/dist/utils/cast.js.map +1 -0
  197. package/dist/utils/check-props.d.ts +7 -0
  198. package/dist/utils/check-props.d.ts.map +1 -0
  199. package/dist/utils/check-props.js +32 -0
  200. package/dist/utils/check-props.js.map +1 -0
  201. package/dist/utils/deep-equal.d.ts +9 -0
  202. package/dist/utils/deep-equal.d.ts.map +1 -0
  203. package/dist/utils/deep-equal.js +40 -0
  204. package/dist/utils/deep-equal.js.map +1 -0
  205. package/dist/utils/format-value.d.ts +7 -0
  206. package/dist/utils/format-value.d.ts.map +1 -0
  207. package/dist/utils/format-value.js +42 -0
  208. package/dist/utils/format-value.js.map +1 -0
  209. package/dist/utils/is-array.d.ts +16 -0
  210. package/dist/utils/is-array.d.ts.map +1 -0
  211. package/dist/utils/is-array.js +10 -0
  212. package/dist/utils/is-array.js.map +1 -0
  213. package/dist/utils/load-file.d.ts +35 -0
  214. package/dist/utils/load-file.d.ts.map +1 -0
  215. package/dist/utils/load-file.js +48 -0
  216. package/dist/utils/load-file.js.map +1 -0
  217. package/dist/utils/log.d.ts +4 -0
  218. package/dist/utils/log.d.ts.map +1 -0
  219. package/dist/utils/log.js +5 -0
  220. package/dist/utils/log.js.map +1 -0
  221. package/dist/utils/random.d.ts +5 -0
  222. package/dist/utils/random.d.ts.map +1 -0
  223. package/dist/utils/random.js +14 -0
  224. package/dist/utils/random.js.map +1 -0
  225. package/dist/utils/request-animation-frame.d.ts +3 -0
  226. package/dist/utils/request-animation-frame.d.ts.map +1 -0
  227. package/dist/utils/request-animation-frame.js +7 -0
  228. package/dist/utils/request-animation-frame.js.map +1 -0
  229. package/dist/utils/stats-manager.d.ts +12 -0
  230. package/dist/utils/stats-manager.d.ts.map +1 -0
  231. package/dist/utils/stats-manager.js +19 -0
  232. package/dist/utils/stats-manager.js.map +1 -0
  233. package/dist/utils/stub-methods.d.ts +2 -0
  234. package/dist/utils/stub-methods.d.ts.map +1 -0
  235. package/dist/utils/stub-methods.js +16 -0
  236. package/dist/utils/stub-methods.js.map +1 -0
  237. package/dist/utils/utils.d.ts +15 -0
  238. package/dist/utils/utils.d.ts.map +1 -0
  239. package/dist/utils/utils.js +19 -0
  240. package/dist/utils/utils.js.map +1 -0
  241. package/dist.min.js +33 -0
  242. package/package.json +17 -12
  243. package/src/adapter/attribute-utils/get-attribute-from-layouts.ts +259 -0
  244. package/src/adapter/canvas-context.ts +433 -0
  245. package/src/adapter/device.ts +435 -0
  246. package/src/adapter/resources/buffer.ts +106 -0
  247. package/src/adapter/resources/command-buffer.ts +38 -0
  248. package/src/adapter/resources/command-encoder.ts +172 -0
  249. package/src/adapter/resources/compute-pass.ts +52 -0
  250. package/src/adapter/resources/compute-pipeline.ts +39 -0
  251. package/src/adapter/resources/external-texture.ts +20 -0
  252. package/src/adapter/resources/framebuffer.ts +230 -0
  253. package/src/adapter/resources/render-pass.ts +117 -0
  254. package/src/adapter/resources/render-pipeline.ts +133 -0
  255. package/src/adapter/resources/resource.ts +176 -0
  256. package/src/adapter/resources/sampler.ts +69 -0
  257. package/src/adapter/resources/shader.ts +143 -0
  258. package/src/adapter/resources/texture.ts +140 -0
  259. package/src/adapter/resources/transform-feedback.ts +45 -0
  260. package/src/adapter/resources/vertex-array.ts +65 -0
  261. package/src/adapter/type-utils/decode-attribute-type.ts +82 -0
  262. package/src/adapter/type-utils/decode-data-type.ts +62 -0
  263. package/src/adapter/type-utils/decode-shader-types.ts +48 -0
  264. package/src/adapter/type-utils/decode-texture-format.ts +190 -0
  265. package/src/adapter/type-utils/decode-vertex-format.ts +49 -0
  266. package/src/adapter/type-utils/vertex-format-from-attribute.ts +103 -0
  267. package/src/adapter/type-utils/wgsl-utils.ts +18 -0
  268. package/src/adapter/types/accessor.ts +34 -0
  269. package/src/adapter/types/buffer-layout.ts +62 -0
  270. package/src/adapter/types/parameters.ts +250 -0
  271. package/src/adapter/types/shader-layout.ts +193 -0
  272. package/src/adapter/types/shader-types.ts +77 -0
  273. package/src/adapter/types/texture-formats.ts +168 -0
  274. package/src/adapter/types/types.ts +109 -0
  275. package/src/adapter/types/vertex-formats.ts +91 -0
  276. package/src/index.ts +160 -92
  277. package/src/init.ts +47 -0
  278. package/src/lib/compiler-log/compiler-message.ts +10 -0
  279. package/src/lib/compiler-log/format-compiler-log.ts +113 -0
  280. package/src/lib/compiler-log/get-shader-info.ts +41 -0
  281. package/src/lib/luma.ts +84 -0
  282. package/src/lib/uniforms/uniform-block.ts +80 -0
  283. package/src/lib/uniforms/uniform-buffer-layout.ts +109 -0
  284. package/src/lib/uniforms/uniform-store.ts +173 -0
  285. package/src/lib/uniforms/uniform.ts +25 -0
  286. package/src/types.ts +33 -0
  287. package/src/utils/array-equal.ts +33 -0
  288. package/src/utils/array-utils-flat.ts +43 -0
  289. package/src/utils/assert.ts +7 -0
  290. package/src/utils/cast.ts +4 -0
  291. package/src/utils/check-props.ts +74 -0
  292. package/src/utils/deep-equal.ts +47 -0
  293. package/src/utils/format-value.ts +40 -0
  294. package/src/utils/is-array.ts +24 -0
  295. package/src/utils/load-file.ts +89 -0
  296. package/src/utils/log.ts +4 -0
  297. package/src/utils/random.ts +17 -0
  298. package/src/utils/request-animation-frame.ts +15 -0
  299. package/src/utils/stats-manager.ts +23 -0
  300. package/src/utils/stub-methods.ts +20 -0
  301. package/src/utils/utils.ts +36 -0
  302. package/dist/bundle.d.ts +0 -2
  303. package/dist/bundle.d.ts.map +0 -1
  304. package/dist/bundle.js +0 -5
  305. package/dist/bundle.js.map +0 -1
  306. package/src/bundle.ts +0 -4
@@ -0,0 +1,259 @@
1
+ // luma.gl, MIT license
2
+ // Copyright (c) vis.gl contributors
3
+
4
+ import {log} from '../../utils/log';
5
+ import type {ShaderLayout, AttributeDeclaration} from '../types/shader-layout';
6
+ import type {BufferLayout} from '../types/buffer-layout';
7
+ import type {ShaderDataType, ShaderAttributeType} from '../types/shader-types';
8
+ import {decodeShaderAttributeType} from '../type-utils/decode-attribute-type';
9
+ import type {VertexFormat, VertexType} from '../types/vertex-formats';
10
+ import {decodeVertexFormat} from '../type-utils/decode-vertex-format';
11
+
12
+ /** Resolved info for a buffer / attribute combination to help backend configure it correctly */
13
+ export type AttributeInfo = {
14
+ /** Attribute name */
15
+ attributeName: string;
16
+ /** Location in shader */
17
+ location: number;
18
+ /** Type / precision used in shader (buffer values may be converted) */
19
+ shaderType: ShaderAttributeType;
20
+ /** Calculations are done in this type in the shader's attribute declaration */
21
+ shaderDataType: ShaderDataType;
22
+ /** Components refer to the number of components in the shader's attribute declaration */
23
+ shaderComponents: 1 | 2 | 3 | 4;
24
+ /** It is the shader attribute declaration that determines whether GPU will process as integer or float */
25
+ integer: boolean;
26
+
27
+ /** BufferName */
28
+ bufferName: string;
29
+ /** Format of buffer data */
30
+ vertexFormat: VertexFormat;
31
+ /** Memory data type refers to the data type in the buffer */
32
+ bufferDataType: VertexType;
33
+ /** Components refer to the number of components in the buffer's vertex format */
34
+ bufferComponents: 1 | 2 | 3 | 4;
35
+ /** Normalization is encoded in the buffer layout's vertex format... */
36
+ normalized: boolean;
37
+
38
+ /** If not specified, the step mode is inferred from the attribute name in the shader (contains string instance) */
39
+ stepMode: 'vertex' | 'instance';
40
+
41
+ /** The byteOffset is encoded in or calculated from the buffer layout */
42
+ byteOffset: number;
43
+ /** The byteStride is encoded in or calculated from the buffer layout */
44
+ byteStride: number;
45
+ };
46
+
47
+ type BufferAttributeInfo = {
48
+ attributeName: string;
49
+ bufferName: string;
50
+ stepMode?: 'vertex' | 'instance';
51
+ vertexFormat: VertexFormat;
52
+ byteOffset: number;
53
+ byteStride: number;
54
+ };
55
+
56
+ /**
57
+ * Map from "attribute names" to "resolved attribute infos"
58
+ * containing information about both buffer layouts and shader attribute declarations
59
+ */
60
+ export function getAttributeInfosFromLayouts(
61
+ shaderLayout: ShaderLayout,
62
+ bufferLayout: BufferLayout[]
63
+ ): Record<string, AttributeInfo> {
64
+ const attributeInfos: Record<string, AttributeInfo> = {};
65
+ for (const attribute of shaderLayout.attributes) {
66
+ attributeInfos[attribute.name] = getAttributeInfoFromLayouts(
67
+ shaderLayout,
68
+ bufferLayout,
69
+ attribute.name
70
+ );
71
+ }
72
+ return attributeInfos;
73
+ }
74
+
75
+ /**
76
+ * Array indexed by "location" holding "resolved attribute infos"
77
+ */
78
+ export function getAttributeInfosByLocation(
79
+ shaderLayout: ShaderLayout,
80
+ bufferLayout: BufferLayout[],
81
+ maxVertexAttributes: number = 16
82
+ ): AttributeInfo[] {
83
+ const attributeInfos = getAttributeInfosFromLayouts(shaderLayout, bufferLayout);
84
+ const locationInfos: AttributeInfo[] = new Array(maxVertexAttributes).fill(null);
85
+ for (const attributeInfo of Object.values(attributeInfos)) {
86
+ locationInfos[attributeInfo.location] = attributeInfo;
87
+ }
88
+ return locationInfos;
89
+ }
90
+
91
+ /**
92
+ * Get the combined information from a shader layout and a buffer layout for a specific attribute
93
+ */
94
+ function getAttributeInfoFromLayouts(
95
+ shaderLayout: ShaderLayout,
96
+ bufferLayout: BufferLayout[],
97
+ name: string
98
+ ): AttributeInfo | null {
99
+ const shaderDeclaration = getAttributeFromShaderLayout(shaderLayout, name);
100
+ const bufferMapping: BufferAttributeInfo = getAttributeFromBufferLayout(bufferLayout, name);
101
+
102
+ // TODO should no longer happen
103
+ if (!shaderDeclaration) {
104
+ // || !bufferMapping
105
+ return null;
106
+ }
107
+
108
+ const attributeTypeInfo = decodeShaderAttributeType(shaderDeclaration.type);
109
+ const vertexFormat = bufferMapping?.vertexFormat || attributeTypeInfo.defaultVertexFormat;
110
+ const vertexFormatInfo = decodeVertexFormat(vertexFormat);
111
+
112
+ return {
113
+ attributeName: bufferMapping?.attributeName || shaderDeclaration.name,
114
+ bufferName: bufferMapping?.bufferName || shaderDeclaration.name,
115
+ location: shaderDeclaration.location,
116
+ shaderType: shaderDeclaration.type,
117
+ shaderDataType: attributeTypeInfo.dataType,
118
+ shaderComponents: attributeTypeInfo.components,
119
+ vertexFormat,
120
+ bufferDataType: vertexFormatInfo.type,
121
+ bufferComponents: vertexFormatInfo.components,
122
+ // normalized is a property of the buffer's vertex format
123
+ normalized: vertexFormatInfo.normalized,
124
+ // integer is a property of the shader declaration
125
+ integer: attributeTypeInfo.integer,
126
+ stepMode: bufferMapping?.stepMode || shaderDeclaration.stepMode,
127
+ byteOffset: bufferMapping?.byteOffset || 0,
128
+ byteStride: bufferMapping?.byteStride || 0
129
+ };
130
+ }
131
+
132
+ function getAttributeFromShaderLayout(
133
+ shaderLayout: ShaderLayout,
134
+ name: string
135
+ ): AttributeDeclaration | null {
136
+ const attribute = shaderLayout.attributes.find(attr => attr.name === name);
137
+ if (!attribute) {
138
+ log.warn(`shader layout attribute "${name}" not present in shader`);
139
+ }
140
+ return attribute || null;
141
+ }
142
+
143
+ function getAttributeFromBufferLayout(
144
+ bufferLayouts: BufferLayout[],
145
+ name: string
146
+ ): BufferAttributeInfo | null {
147
+ // Check that bufferLayouts are valid (each either has format or attribute)
148
+ checkBufferLayouts(bufferLayouts);
149
+
150
+ let bufferLayoutInfo = getAttributeFromShortHand(bufferLayouts, name);
151
+ if (bufferLayoutInfo) {
152
+ return bufferLayoutInfo;
153
+ }
154
+
155
+ bufferLayoutInfo = getAttributeFromAttributesList(bufferLayouts, name);
156
+ if (bufferLayoutInfo) {
157
+ return bufferLayoutInfo;
158
+ }
159
+
160
+ // Didn't find...
161
+ log.warn(`layout for attribute "${name}" not present in buffer layout`);
162
+ return null;
163
+ }
164
+
165
+ /** Check that bufferLayouts are valid (each either has format or attribute) */
166
+ function checkBufferLayouts(bufferLayouts: BufferLayout[]) {
167
+ for (const bufferLayout of bufferLayouts) {
168
+ if (
169
+ (bufferLayout.attributes && bufferLayout.format) ||
170
+ (!bufferLayout.attributes && !bufferLayout.format)
171
+ ) {
172
+ log.warn(`BufferLayout ${name} must have either 'attributes' or 'format' field`);
173
+ }
174
+ }
175
+ }
176
+
177
+ /** Get attribute from format shorthand if specified */
178
+ function getAttributeFromShortHand(
179
+ bufferLayouts: BufferLayout[],
180
+ name: string
181
+ ): BufferAttributeInfo | null {
182
+ for (const bufferLayout of bufferLayouts) {
183
+ if (bufferLayout.format && bufferLayout.name === name) {
184
+ return {
185
+ attributeName: bufferLayout.name,
186
+ bufferName: name,
187
+ stepMode: bufferLayout.stepMode,
188
+ vertexFormat: bufferLayout.format,
189
+ // If offset is needed, use `attributes` field.
190
+ byteOffset: 0,
191
+ byteStride: bufferLayout.byteStride || 0
192
+ };
193
+ }
194
+ }
195
+ return null;
196
+ }
197
+
198
+ /**
199
+ * Search attribute mappings (e.g. interleaved attributes) for buffer mapping.
200
+ * Not the name of the buffer might be the same as one of the interleaved attributes.
201
+ */
202
+ function getAttributeFromAttributesList(
203
+ bufferLayouts: BufferLayout[],
204
+ name: string
205
+ ): BufferAttributeInfo | null {
206
+ for (const bufferLayout of bufferLayouts) {
207
+ let byteStride: number | undefined = bufferLayout.byteStride;
208
+
209
+ // Calculate a default byte stride if not provided
210
+ if (typeof bufferLayout.byteStride !== 'number') {
211
+ for (const attributeMapping of bufferLayout.attributes || []) {
212
+ const info = decodeVertexFormat(attributeMapping.format);
213
+ byteStride += info.byteLength;
214
+ }
215
+ }
216
+
217
+ const attributeMapping = bufferLayout.attributes?.find(mapping => mapping.attribute === name);
218
+ if (attributeMapping) {
219
+ return {
220
+ attributeName: attributeMapping.attribute,
221
+ bufferName: bufferLayout.name,
222
+ stepMode: bufferLayout.stepMode,
223
+ vertexFormat: attributeMapping.format,
224
+ byteOffset: attributeMapping.byteOffset,
225
+ byteStride
226
+ };
227
+ }
228
+ }
229
+
230
+ return null;
231
+ }
232
+
233
+ /**
234
+ * Merges an provided shader layout into a base shader layout
235
+ * In WebGL, this allows the auto generated shader layout to be overridden by the application
236
+ * Typically to change the format of the vertex attributes (from float32x4 to uint8x4 etc).
237
+ * @todo Drop this? Aren't all use cases covered by mergeBufferLayout()?
238
+ */
239
+ export function mergeShaderLayout(
240
+ baseLayout: ShaderLayout,
241
+ overrideLayout: ShaderLayout
242
+ ): ShaderLayout {
243
+ // Deep clone the base layout
244
+ const mergedLayout: ShaderLayout = {
245
+ ...baseLayout,
246
+ attributes: baseLayout.attributes.map(attribute => ({...attribute}))
247
+ };
248
+ // Merge the attributes
249
+ for (const attribute of overrideLayout?.attributes || []) {
250
+ const baseAttribute = mergedLayout.attributes.find(attr => attr.name === attribute.name);
251
+ if (!baseAttribute) {
252
+ log.warn(`shader layout attribute ${attribute.name} not present in shader`);
253
+ } else {
254
+ baseAttribute.type = attribute.type || baseAttribute.type;
255
+ baseAttribute.stepMode = attribute.stepMode || baseAttribute.stepMode;
256
+ }
257
+ }
258
+ return mergedLayout;
259
+ }
@@ -0,0 +1,433 @@
1
+ // luma.gl, MIT license
2
+ import {isBrowser} from '@probe.gl/env';
3
+ import type {Device} from './device';
4
+ import type {Framebuffer} from './resources/framebuffer';
5
+ import {log} from '../utils/log';
6
+
7
+ const isPage: boolean = isBrowser() && typeof document !== 'undefined';
8
+ const isPageLoaded: () => boolean = () => isPage && document.readyState === 'complete';
9
+
10
+ /** Properties for a CanvasContext */
11
+ export type CanvasContextProps = {
12
+ /** If canvas not supplied, will be created and added to the DOM. If string, will be looked up in the DOM */
13
+ canvas?: HTMLCanvasElement | OffscreenCanvas | string | null;
14
+ /** If new canvas is created, it will be created in the specified container, otherwise appended to body */
15
+ container?: HTMLElement | string | null;
16
+ /** Width in pixels of the canvas */
17
+ width?: number;
18
+ /** Height in pixels of the canvas */
19
+ height?: number;
20
+ /** Whether to apply a device pixels scale factor (`true` uses browser DPI) */
21
+ useDevicePixels?: boolean | number;
22
+ /** Whether to track resizes (if not ) */
23
+ autoResize?: boolean;
24
+ /** Visibility (only used if new canvas is created). */
25
+ visible?: boolean;
26
+ /** WebGPU only https://www.w3.org/TR/webgpu/#canvas-configuration */
27
+ colorSpace?: 'srgb'; // GPUPredefinedColorSpace
28
+ /** WebGPU only https://www.w3.org/TR/webgpu/#canvas-configuration */
29
+ alphaMode?: 'opaque' | 'premultiplied';
30
+ };
31
+
32
+ const DEFAULT_CANVAS_CONTEXT_PROPS: Required<CanvasContextProps> = {
33
+ canvas: null,
34
+ width: 800, // width are height are only used by headless gl
35
+ height: 600,
36
+ useDevicePixels: true,
37
+ autoResize: true,
38
+ container: null,
39
+ visible: true,
40
+ colorSpace: 'srgb',
41
+ alphaMode: 'opaque'
42
+ };
43
+
44
+ /**
45
+ * Manages a canvas. Supports both HTML or offscreen canvas
46
+ * - Creates a new canvas or looks up a canvas from the DOM
47
+ * - Provides check for DOM loaded
48
+ * @todo commit(): https://github.com/w3ctag/design-reviews/issues/288
49
+ * @todo transferControlToOffscreen: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/transferControlToOffscreen
50
+ */
51
+ export abstract class CanvasContext {
52
+ abstract readonly device: Device;
53
+ readonly id: string;
54
+ readonly props: Required<CanvasContextProps>;
55
+ readonly canvas: HTMLCanvasElement | OffscreenCanvas;
56
+ readonly htmlCanvas?: HTMLCanvasElement;
57
+ readonly offscreenCanvas?: OffscreenCanvas;
58
+ readonly type: 'html-canvas' | 'offscreen-canvas' | 'node';
59
+
60
+ width: number = 1;
61
+ height: number = 1;
62
+
63
+ readonly resizeObserver: ResizeObserver | undefined;
64
+
65
+ /** State used by luma.gl classes: TODO - move to canvasContext*/
66
+ readonly _canvasSizeInfo = {clientWidth: 0, clientHeight: 0, devicePixelRatio: 1};
67
+
68
+ /** Check if the DOM is loaded */
69
+ static get isPageLoaded(): boolean {
70
+ return isPageLoaded();
71
+ }
72
+
73
+ /**
74
+ * Get a 'lazy' promise that resolves when the DOM is loaded.
75
+ * @note Since there may be limitations on number of `load` event listeners,
76
+ * it is recommended avoid calling this function until actually needed.
77
+ * I.e. don't call it until you know that you will be looking up a string in the DOM.
78
+ */
79
+ static pageLoaded: Promise<void> = getPageLoadPromise();
80
+
81
+ constructor(props?: CanvasContextProps) {
82
+ this.props = {...DEFAULT_CANVAS_CONTEXT_PROPS, ...props};
83
+ props = this.props;
84
+
85
+ if (!isBrowser()) {
86
+ this.id = 'node-canvas-context';
87
+ this.type = 'node';
88
+ this.width = this.props.width;
89
+ this.height = this.props.height;
90
+ // TODO - does this prevent app from using jsdom style polyfills?
91
+ this.canvas = null!;
92
+ return;
93
+ }
94
+
95
+ if (!props.canvas) {
96
+ const canvas = createCanvas(props);
97
+ const container = getContainer(props?.container || null);
98
+ container.insertBefore(canvas, container.firstChild);
99
+
100
+ this.canvas = canvas;
101
+
102
+ if (!props?.visible) {
103
+ this.canvas.style.visibility = 'hidden';
104
+ }
105
+ } else if (typeof props.canvas === 'string') {
106
+ this.canvas = getCanvasFromDOM(props.canvas);
107
+ } else {
108
+ this.canvas = props.canvas;
109
+ }
110
+
111
+ if (this.canvas instanceof HTMLCanvasElement) {
112
+ this.id = this.canvas.id;
113
+ this.type = 'html-canvas';
114
+ this.htmlCanvas = this.canvas;
115
+ } else {
116
+ this.id = 'offscreen-canvas';
117
+ this.type = 'offscreen-canvas';
118
+ this.offscreenCanvas = this.canvas;
119
+ }
120
+
121
+ // React to size changes
122
+ if (this.canvas instanceof HTMLCanvasElement && props.autoResize) {
123
+ this.resizeObserver = new ResizeObserver((entries) => {
124
+ for (const entry of entries) {
125
+ if (entry.target === this.canvas) {
126
+ this.update();
127
+ }
128
+ }
129
+ });
130
+ this.resizeObserver.observe(this.canvas);
131
+ }
132
+ }
133
+
134
+ /** Returns a framebuffer with properly resized current 'swap chain' textures */
135
+ abstract getCurrentFramebuffer(): Framebuffer;
136
+
137
+ /**
138
+ * Returns the current DPR, if props.useDevicePixels is true
139
+ * Device refers to physical
140
+ */
141
+ getDevicePixelRatio(useDevicePixels?: boolean | number): number {
142
+ if (typeof OffscreenCanvas !== 'undefined' && this.canvas instanceof OffscreenCanvas) {
143
+ return 1;
144
+ }
145
+
146
+ useDevicePixels = useDevicePixels === undefined ? this.props.useDevicePixels : useDevicePixels;
147
+
148
+ if (!useDevicePixels || useDevicePixels as number <= 0) {
149
+ return 1;
150
+ }
151
+
152
+ // The param was mainly provide to support the test cases, could be removed
153
+ if (useDevicePixels === true) {
154
+ const dpr = typeof window !== 'undefined' && window.devicePixelRatio;
155
+ return dpr || 1;
156
+ }
157
+
158
+ return useDevicePixels;
159
+ }
160
+
161
+ /**
162
+ * Returns the size of drawing buffer in device pixels.
163
+ * @note This can be different from the 'CSS' size of a canvas, and also from the
164
+ * canvas' internal drawing buffer size (.width, .height).
165
+ * This is the size required to cover the canvas, adjusted for DPR
166
+ */
167
+ getPixelSize(): [number, number] {
168
+ switch (this.type) {
169
+ case 'node':
170
+ return [this.width, this.height];
171
+ case 'offscreen-canvas':
172
+ return [this.canvas.width, this.canvas.height];
173
+ case 'html-canvas':
174
+ const dpr = this.getDevicePixelRatio();
175
+ const canvas = this.canvas as HTMLCanvasElement;
176
+ // If not attached to DOM client size can be 0
177
+ return canvas.parentElement
178
+ ? [canvas.clientWidth * dpr, canvas.clientHeight * dpr]
179
+ : [this.canvas.width, this.canvas.height];
180
+ default:
181
+ throw new Error(this.type);
182
+ }
183
+ }
184
+
185
+ getAspect(): number {
186
+ const [width, height] = this.getPixelSize();
187
+ return width / height;
188
+ }
189
+
190
+ /**
191
+ * Returns multiplier need to convert CSS size to Device size
192
+ */
193
+ cssToDeviceRatio(): number {
194
+ try {
195
+ // For headless gl we might have used custom width and height
196
+ // hence use cached clientWidth
197
+ const [drawingBufferWidth] = this.getDrawingBufferSize();
198
+ const {clientWidth} = this._canvasSizeInfo;
199
+ return clientWidth ? drawingBufferWidth / clientWidth : 1;
200
+ } catch {
201
+ return 1;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Maps CSS pixel position to device pixel position
207
+ */
208
+ cssToDevicePixels(
209
+ cssPixel: number[],
210
+ yInvert: boolean = true
211
+ ): {
212
+ x: number;
213
+ y: number;
214
+ width: number;
215
+ height: number;
216
+ } {
217
+ const ratio = this.cssToDeviceRatio();
218
+ const [width, height] = this.getDrawingBufferSize();
219
+ return scalePixels(cssPixel, ratio, width, height, yInvert);
220
+ }
221
+
222
+ /**
223
+ * Use devicePixelRatio to set canvas width and height
224
+ * @note this is a raw port of luma.gl v8 code. Might be worth a review
225
+ */
226
+ setDevicePixelRatio(
227
+ devicePixelRatio: number,
228
+ options: {width?: number; height?: number} = {}
229
+ ): void {
230
+ if (!this.htmlCanvas) {
231
+ return;
232
+ }
233
+
234
+ // NOTE: if options.width and options.height not used remove in v8
235
+ let clientWidth = 'width' in options ? options.width : this.htmlCanvas.clientWidth;
236
+ let clientHeight = 'height' in options ? options.height : this.htmlCanvas.clientHeight;
237
+
238
+ if (!clientWidth || !clientHeight) {
239
+ log.log(1, 'Canvas clientWidth/clientHeight is 0')();
240
+ // by forcing devicePixel ratio to 1, we do not scale canvas.width and height in each frame.
241
+ devicePixelRatio = 1;
242
+ clientWidth = this.htmlCanvas.width || 1;
243
+ clientHeight = this.htmlCanvas.height || 1;
244
+ }
245
+
246
+ const cachedSize = this._canvasSizeInfo;
247
+ // Check if canvas needs to be resized
248
+ if (
249
+ cachedSize.clientWidth !== clientWidth ||
250
+ cachedSize.clientHeight !== clientHeight ||
251
+ cachedSize.devicePixelRatio !== devicePixelRatio
252
+ ) {
253
+ let clampedPixelRatio = devicePixelRatio;
254
+
255
+ const canvasWidth = Math.floor(clientWidth * clampedPixelRatio);
256
+ const canvasHeight = Math.floor(clientHeight * clampedPixelRatio);
257
+ this.htmlCanvas.width = canvasWidth;
258
+ this.htmlCanvas.height = canvasHeight;
259
+
260
+ // Note: when devicePixelRatio is too high, it is possible we might hit system limit for
261
+ // drawing buffer width and hight, in those cases they get clamped and resulting aspect ration may not be maintained
262
+ // for those cases, reduce devicePixelRatio.
263
+ const [drawingBufferWidth, drawingBufferHeight] = this.getDrawingBufferSize();
264
+
265
+ if (drawingBufferWidth !== canvasWidth || drawingBufferHeight !== canvasHeight) {
266
+ clampedPixelRatio = Math.min(
267
+ drawingBufferWidth / clientWidth,
268
+ drawingBufferHeight / clientHeight
269
+ );
270
+
271
+ this.htmlCanvas.width = Math.floor(clientWidth * clampedPixelRatio);
272
+ this.htmlCanvas.height = Math.floor(clientHeight * clampedPixelRatio);
273
+
274
+ log.warn('Device pixel ratio clamped')();
275
+ }
276
+
277
+ this._canvasSizeInfo.clientWidth = clientWidth;
278
+ this._canvasSizeInfo.clientHeight = clientHeight;
279
+ this._canvasSizeInfo.devicePixelRatio = devicePixelRatio;
280
+ }
281
+ }
282
+
283
+ // PRIVATE
284
+
285
+ /** @todo Major hack done to port the CSS methods above, base canvas context should not depend on WebGL */
286
+ getDrawingBufferSize(): [number, number] {
287
+ // @ts-expect-error This only works for WebGL
288
+ const gl = this.device.gl;
289
+ if (!gl) {
290
+ // use default device pixel ratio
291
+ throw new Error('canvas size');
292
+ }
293
+ return [gl.drawingBufferWidth, gl.drawingBufferHeight];
294
+ }
295
+
296
+ abstract resize(options?: {
297
+ width?: number;
298
+ height?: number;
299
+ useDevicePixels?: boolean | number;
300
+ }): void;
301
+
302
+ /** Perform platform specific updates (WebGPU vs WebGL) */
303
+ protected abstract update(): void;
304
+
305
+ /**
306
+ * Allows subclass constructor to override the canvas id for auto created canvases.
307
+ * This can really help when debugging DOM in apps that create multiple devices
308
+ */
309
+ protected _setAutoCreatedCanvasId(id: string) {
310
+ if (this.htmlCanvas?.id === 'lumagl-auto-created-canvas') {
311
+ this.htmlCanvas.id = id;
312
+ }
313
+ }
314
+ }
315
+
316
+ // HELPER FUNCTIONS
317
+
318
+ /** Returns a promise that resolves when the page is loaded */
319
+ function getPageLoadPromise(): Promise<void> {
320
+ if (isPageLoaded() || typeof window === 'undefined') {
321
+ return Promise.resolve();
322
+ }
323
+ return new Promise((resolve) => {
324
+ window.addEventListener('load', () => resolve());
325
+ });
326
+ }
327
+
328
+ function getContainer(container: HTMLElement | string | null): HTMLElement {
329
+ if (typeof container === 'string') {
330
+ const element = document.getElementById(container);
331
+ if (!element && !isPageLoaded()) {
332
+ throw new Error(`Accessing '${container}' before page was loaded`);
333
+ }
334
+ if (!element) {
335
+ throw new Error(`${container} is not an HTML element`);
336
+ }
337
+ return element;
338
+ } else if (container) {
339
+ return container;
340
+ }
341
+ return document.body;
342
+ }
343
+
344
+ /** Get a Canvas element from DOM id */
345
+ function getCanvasFromDOM(canvasId: string): HTMLCanvasElement {
346
+ const canvas = document.getElementById(canvasId);
347
+ if (!canvas && !isPageLoaded()) {
348
+ throw new Error(`Accessing '${canvasId}' before page was loaded`);
349
+ }
350
+ if (!(canvas instanceof HTMLCanvasElement)) {
351
+ throw new Error('Object is not a canvas element');
352
+ }
353
+ return canvas;
354
+ }
355
+
356
+ /** Create a new canvas */
357
+ function createCanvas(props: CanvasContextProps) {
358
+ const {width, height} = props;
359
+ const targetCanvas = document.createElement('canvas');
360
+ targetCanvas.id = 'lumagl-auto-created-canvas';
361
+ targetCanvas.width = width || 1;
362
+ targetCanvas.height = height || 1;
363
+ targetCanvas.style.width = Number.isFinite(width) ? `${width}px` : '100%';
364
+ targetCanvas.style.height = Number.isFinite(height) ? `${height}px` : '100%';
365
+ return targetCanvas;
366
+ }
367
+
368
+ /**
369
+ *
370
+ * @param pixel
371
+ * @param ratio
372
+ * @param width
373
+ * @param height
374
+ * @param yInvert
375
+ * @returns
376
+ */
377
+ function scalePixels(
378
+ pixel: number[],
379
+ ratio: number,
380
+ width: number,
381
+ height: number,
382
+ yInvert: boolean
383
+ ): {
384
+ x: number;
385
+ y: number;
386
+ width: number;
387
+ height: number;
388
+ } {
389
+ const point = pixel as [number, number];
390
+
391
+ const x = scaleX(point[0], ratio, width);
392
+ let y = scaleY(point[1], ratio, height, yInvert);
393
+
394
+ // Find boundaries of next pixel to provide valid range of device pixel locations
395
+
396
+ let t = scaleX(point[0] + 1, ratio, width);
397
+ // If next pixel's position is clamped to boundary, use it as is, otherwise subtract 1 for current pixel boundary
398
+ const xHigh = t === width - 1 ? t : t - 1;
399
+
400
+ t = scaleY(point[1] + 1, ratio, height, yInvert);
401
+ let yHigh;
402
+ if (yInvert) {
403
+ // If next pixel's position is clamped to boundary, use it as is, otherwise clamp it to valid range
404
+ t = t === 0 ? t : t + 1;
405
+ // swap y and yHigh
406
+ yHigh = y;
407
+ y = t;
408
+ } else {
409
+ // If next pixel's position is clamped to boundary, use it as is, otherwise clamp it to valid range
410
+ yHigh = t === height - 1 ? t : t - 1;
411
+ // y remains same
412
+ }
413
+ return {
414
+ x,
415
+ y,
416
+ // when ratio < 1, current css pixel and next css pixel may point to same device pixel, set width/height to 1 in those cases.
417
+ width: Math.max(xHigh - x + 1, 1),
418
+ height: Math.max(yHigh - y + 1, 1)
419
+ };
420
+ }
421
+
422
+ function scaleX(x: number, ratio: number, width: number): number {
423
+ // since we are rounding to nearest, when ratio > 1, edge pixels may point to out of bounds value, clamp to the limit
424
+ const r = Math.min(Math.round(x * ratio), width - 1);
425
+ return r;
426
+ }
427
+
428
+ function scaleY(y: number, ratio: number, height: number, yInvert: boolean): number {
429
+ // since we are rounding to nearest, when ratio > 1, edge pixels may point to out of bounds value, clamp to the limit
430
+ return yInvert
431
+ ? Math.max(0, height - 1 - Math.round(y * ratio))
432
+ : Math.min(Math.round(y * ratio), height - 1);
433
+ }