@luma.gl/core 9.2.6 → 9.3.0-alpha.10

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 (280) hide show
  1. package/dist/adapter/canvas-context.d.ts +6 -162
  2. package/dist/adapter/canvas-context.d.ts.map +1 -1
  3. package/dist/adapter/canvas-context.js +5 -419
  4. package/dist/adapter/canvas-context.js.map +1 -1
  5. package/dist/adapter/canvas-observer.d.ts +32 -0
  6. package/dist/adapter/canvas-observer.d.ts.map +1 -0
  7. package/dist/adapter/canvas-observer.js +90 -0
  8. package/dist/adapter/canvas-observer.js.map +1 -0
  9. package/dist/adapter/canvas-surface.d.ts +150 -0
  10. package/dist/adapter/canvas-surface.d.ts.map +1 -0
  11. package/dist/adapter/canvas-surface.js +392 -0
  12. package/dist/adapter/canvas-surface.js.map +1 -0
  13. package/dist/adapter/device.d.ts +81 -16
  14. package/dist/adapter/device.d.ts.map +1 -1
  15. package/dist/adapter/device.js +191 -10
  16. package/dist/adapter/device.js.map +1 -1
  17. package/dist/adapter/luma.js +1 -1
  18. package/dist/adapter/luma.js.map +1 -1
  19. package/dist/adapter/presentation-context.d.ts +11 -0
  20. package/dist/adapter/presentation-context.d.ts.map +1 -0
  21. package/dist/adapter/presentation-context.js +12 -0
  22. package/dist/adapter/presentation-context.js.map +1 -0
  23. package/dist/adapter/resources/buffer.d.ts +1 -1
  24. package/dist/adapter/resources/buffer.d.ts.map +1 -1
  25. package/dist/adapter/resources/buffer.js +14 -6
  26. package/dist/adapter/resources/buffer.js.map +1 -1
  27. package/dist/adapter/resources/command-buffer.d.ts +3 -1
  28. package/dist/adapter/resources/command-buffer.d.ts.map +1 -1
  29. package/dist/adapter/resources/command-buffer.js +3 -1
  30. package/dist/adapter/resources/command-buffer.js.map +1 -1
  31. package/dist/adapter/resources/command-encoder.d.ts +30 -7
  32. package/dist/adapter/resources/command-encoder.d.ts.map +1 -1
  33. package/dist/adapter/resources/command-encoder.js +68 -2
  34. package/dist/adapter/resources/command-encoder.js.map +1 -1
  35. package/dist/adapter/resources/compute-pipeline.d.ts +2 -2
  36. package/dist/adapter/resources/compute-pipeline.d.ts.map +1 -1
  37. package/dist/adapter/resources/fence.d.ts +16 -0
  38. package/dist/adapter/resources/fence.d.ts.map +1 -0
  39. package/dist/adapter/resources/fence.js +17 -0
  40. package/dist/adapter/resources/fence.js.map +1 -0
  41. package/dist/adapter/resources/framebuffer.d.ts +1 -1
  42. package/dist/adapter/resources/framebuffer.d.ts.map +1 -1
  43. package/dist/adapter/resources/framebuffer.js +15 -12
  44. package/dist/adapter/resources/framebuffer.js.map +1 -1
  45. package/dist/adapter/resources/query-set.d.ts +17 -1
  46. package/dist/adapter/resources/query-set.d.ts.map +1 -1
  47. package/dist/adapter/resources/query-set.js.map +1 -1
  48. package/dist/adapter/resources/render-pipeline.d.ts +28 -10
  49. package/dist/adapter/resources/render-pipeline.d.ts.map +1 -1
  50. package/dist/adapter/resources/render-pipeline.js +21 -2
  51. package/dist/adapter/resources/render-pipeline.js.map +1 -1
  52. package/dist/adapter/resources/resource.d.ts +13 -0
  53. package/dist/adapter/resources/resource.d.ts.map +1 -1
  54. package/dist/adapter/resources/resource.js +243 -14
  55. package/dist/adapter/resources/resource.js.map +1 -1
  56. package/dist/adapter/resources/shader.js +27 -25
  57. package/dist/adapter/resources/shader.js.map +1 -1
  58. package/dist/adapter/resources/shared-render-pipeline.d.ts +22 -0
  59. package/dist/adapter/resources/shared-render-pipeline.d.ts.map +1 -0
  60. package/dist/adapter/resources/shared-render-pipeline.js +25 -0
  61. package/dist/adapter/resources/shared-render-pipeline.js.map +1 -0
  62. package/dist/adapter/resources/texture-view.d.ts +1 -1
  63. package/dist/adapter/resources/texture-view.d.ts.map +1 -1
  64. package/dist/adapter/resources/texture.d.ts +168 -28
  65. package/dist/adapter/resources/texture.d.ts.map +1 -1
  66. package/dist/adapter/resources/texture.js +284 -25
  67. package/dist/adapter/resources/texture.js.map +1 -1
  68. package/dist/adapter/types/attachments.d.ts +1 -1
  69. package/dist/adapter/types/attachments.d.ts.map +1 -1
  70. package/dist/adapter/types/buffer-layout.d.ts +1 -1
  71. package/dist/adapter/types/buffer-layout.d.ts.map +1 -1
  72. package/dist/adapter/types/parameters.d.ts +3 -1
  73. package/dist/adapter/types/parameters.d.ts.map +1 -1
  74. package/dist/adapter/types/parameters.js +1 -0
  75. package/dist/adapter/types/parameters.js.map +1 -1
  76. package/dist/adapter/types/shader-layout.d.ts +10 -6
  77. package/dist/adapter/types/shader-layout.d.ts.map +1 -1
  78. package/dist/adapter/types/uniforms.d.ts +6 -0
  79. package/dist/adapter/types/uniforms.d.ts.map +1 -1
  80. package/dist/adapter-utils/bind-groups.d.ts +9 -0
  81. package/dist/adapter-utils/bind-groups.d.ts.map +1 -0
  82. package/dist/adapter-utils/bind-groups.js +41 -0
  83. package/dist/adapter-utils/bind-groups.js.map +1 -0
  84. package/dist/adapter-utils/format-compiler-log.d.ts.map +1 -1
  85. package/dist/adapter-utils/format-compiler-log.js +23 -15
  86. package/dist/adapter-utils/format-compiler-log.js.map +1 -1
  87. package/dist/adapter-utils/get-attribute-from-layouts.d.ts +2 -2
  88. package/dist/adapter-utils/get-attribute-from-layouts.d.ts.map +1 -1
  89. package/dist/adapter-utils/get-attribute-from-layouts.js +6 -6
  90. package/dist/adapter-utils/get-attribute-from-layouts.js.map +1 -1
  91. package/dist/dist.dev.js +2686 -644
  92. package/dist/dist.min.js +10 -9
  93. package/dist/factories/bind-group-factory.d.ts +20 -0
  94. package/dist/factories/bind-group-factory.d.ts.map +1 -0
  95. package/dist/factories/bind-group-factory.js +79 -0
  96. package/dist/factories/bind-group-factory.js.map +1 -0
  97. package/dist/factories/core-module-state.d.ts +7 -0
  98. package/dist/factories/core-module-state.d.ts.map +1 -0
  99. package/dist/{shadertypes/data-types/shader-types.js → factories/core-module-state.js} +1 -1
  100. package/dist/factories/core-module-state.js.map +1 -0
  101. package/dist/factories/pipeline-factory.d.ts +54 -0
  102. package/dist/factories/pipeline-factory.d.ts.map +1 -0
  103. package/dist/factories/pipeline-factory.js +270 -0
  104. package/dist/factories/pipeline-factory.js.map +1 -0
  105. package/dist/factories/shader-factory.d.ts +20 -0
  106. package/dist/factories/shader-factory.d.ts.map +1 -0
  107. package/dist/factories/shader-factory.js +84 -0
  108. package/dist/factories/shader-factory.js.map +1 -0
  109. package/dist/index.cjs +2422 -554
  110. package/dist/index.cjs.map +4 -4
  111. package/dist/index.d.ts +30 -14
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +19 -7
  114. package/dist/index.js.map +1 -1
  115. package/dist/portable/shader-block-writer.d.ts +51 -0
  116. package/dist/portable/shader-block-writer.d.ts.map +1 -0
  117. package/dist/portable/shader-block-writer.js +185 -0
  118. package/dist/portable/shader-block-writer.js.map +1 -0
  119. package/dist/portable/uniform-block.d.ts +1 -1
  120. package/dist/portable/uniform-block.d.ts.map +1 -1
  121. package/dist/portable/uniform-store.d.ts +55 -24
  122. package/dist/portable/uniform-store.d.ts.map +1 -1
  123. package/dist/portable/uniform-store.js +73 -25
  124. package/dist/portable/uniform-store.js.map +1 -1
  125. package/dist/shadertypes/data-types/data-type-decoder.d.ts +20 -0
  126. package/dist/shadertypes/data-types/data-type-decoder.d.ts.map +1 -0
  127. package/dist/shadertypes/data-types/data-type-decoder.js +79 -0
  128. package/dist/shadertypes/data-types/data-type-decoder.js.map +1 -0
  129. package/dist/shadertypes/data-types/data-types.d.ts +31 -12
  130. package/dist/shadertypes/data-types/data-types.d.ts.map +1 -1
  131. package/dist/shadertypes/data-types/decode-data-types.d.ts.map +1 -1
  132. package/dist/shadertypes/data-types/decode-data-types.js +2 -1
  133. package/dist/shadertypes/data-types/decode-data-types.js.map +1 -1
  134. package/dist/{image-utils → shadertypes/image-types}/image-types.d.ts +0 -6
  135. package/dist/shadertypes/image-types/image-types.d.ts.map +1 -0
  136. package/dist/shadertypes/image-types/image-types.js.map +1 -0
  137. package/dist/shadertypes/shader-types/shader-block-layout.d.ts +72 -0
  138. package/dist/shadertypes/shader-types/shader-block-layout.d.ts.map +1 -0
  139. package/dist/shadertypes/shader-types/shader-block-layout.js +209 -0
  140. package/dist/shadertypes/shader-types/shader-block-layout.js.map +1 -0
  141. package/dist/shadertypes/shader-types/shader-type-decoder.d.ts +41 -0
  142. package/dist/shadertypes/shader-types/shader-type-decoder.d.ts.map +1 -0
  143. package/dist/shadertypes/{data-types/decode-shader-types.js → shader-types/shader-type-decoder.js} +43 -4
  144. package/dist/shadertypes/shader-types/shader-type-decoder.js.map +1 -0
  145. package/dist/shadertypes/shader-types/shader-types.d.ts +101 -0
  146. package/dist/shadertypes/shader-types/shader-types.d.ts.map +1 -0
  147. package/dist/shadertypes/shader-types/shader-types.js +30 -0
  148. package/dist/shadertypes/shader-types/shader-types.js.map +1 -0
  149. package/dist/shadertypes/texture-types/pixel-utils.d.ts.map +1 -0
  150. package/dist/shadertypes/{textures → texture-types}/pixel-utils.js +4 -4
  151. package/dist/shadertypes/texture-types/pixel-utils.js.map +1 -0
  152. package/dist/shadertypes/texture-types/texture-format-decoder.d.ts +36 -0
  153. package/dist/shadertypes/texture-types/texture-format-decoder.d.ts.map +1 -0
  154. package/dist/shadertypes/{textures → texture-types}/texture-format-decoder.js +109 -37
  155. package/dist/shadertypes/texture-types/texture-format-decoder.js.map +1 -0
  156. package/dist/shadertypes/texture-types/texture-format-generics.d.ts +34 -0
  157. package/dist/shadertypes/texture-types/texture-format-generics.d.ts.map +1 -0
  158. package/dist/shadertypes/texture-types/texture-format-generics.js.map +1 -0
  159. package/dist/shadertypes/texture-types/texture-format-table.d.ts.map +1 -0
  160. package/dist/shadertypes/{textures → texture-types}/texture-format-table.js +10 -9
  161. package/dist/shadertypes/texture-types/texture-format-table.js.map +1 -0
  162. package/dist/shadertypes/{textures → texture-types}/texture-formats.d.ts +51 -17
  163. package/dist/shadertypes/texture-types/texture-formats.d.ts.map +1 -0
  164. package/dist/shadertypes/{textures → texture-types}/texture-formats.js +1 -0
  165. package/dist/shadertypes/texture-types/texture-formats.js.map +1 -0
  166. package/dist/shadertypes/texture-types/texture-layout.d.ts +5 -0
  167. package/dist/shadertypes/texture-types/texture-layout.d.ts.map +1 -0
  168. package/dist/shadertypes/texture-types/texture-layout.js +41 -0
  169. package/dist/shadertypes/texture-types/texture-layout.js.map +1 -0
  170. package/dist/shadertypes/vertex-types/vertex-format-decoder.d.ts +24 -0
  171. package/dist/shadertypes/vertex-types/vertex-format-decoder.d.ts.map +1 -0
  172. package/dist/shadertypes/vertex-types/vertex-format-decoder.js +106 -0
  173. package/dist/shadertypes/vertex-types/vertex-format-decoder.js.map +1 -0
  174. package/dist/shadertypes/vertex-types/vertex-formats.d.ts +50 -0
  175. package/dist/shadertypes/vertex-types/vertex-formats.d.ts.map +1 -0
  176. package/dist/shadertypes/vertex-types/vertex-formats.js.map +1 -0
  177. package/dist/utils/array-equal.d.ts +1 -1
  178. package/dist/utils/array-equal.d.ts.map +1 -1
  179. package/dist/utils/array-equal.js +15 -9
  180. package/dist/utils/array-equal.js.map +1 -1
  181. package/dist/utils/assert.d.ts +5 -0
  182. package/dist/utils/assert.d.ts.map +1 -0
  183. package/dist/utils/assert.js +17 -0
  184. package/dist/utils/assert.js.map +1 -0
  185. package/dist/utils/stats-manager.d.ts.map +1 -1
  186. package/dist/utils/stats-manager.js +61 -1
  187. package/dist/utils/stats-manager.js.map +1 -1
  188. package/package.json +6 -6
  189. package/src/adapter/canvas-context.ts +7 -556
  190. package/src/adapter/canvas-observer.ts +130 -0
  191. package/src/adapter/canvas-surface.ts +521 -0
  192. package/src/adapter/device.ts +308 -24
  193. package/src/adapter/presentation-context.ts +16 -0
  194. package/src/adapter/resources/buffer.ts +13 -5
  195. package/src/adapter/resources/command-buffer.ts +4 -2
  196. package/src/adapter/resources/command-encoder.ts +101 -10
  197. package/src/adapter/resources/compute-pipeline.ts +2 -2
  198. package/src/adapter/resources/fence.ts +32 -0
  199. package/src/adapter/resources/framebuffer.ts +16 -13
  200. package/src/adapter/resources/query-set.ts +17 -1
  201. package/src/adapter/resources/render-pipeline.ts +52 -16
  202. package/src/adapter/resources/resource.ts +289 -14
  203. package/src/adapter/resources/shader.ts +28 -28
  204. package/src/adapter/resources/shared-render-pipeline.ts +40 -0
  205. package/src/adapter/resources/texture-view.ts +1 -1
  206. package/src/adapter/resources/texture.ts +427 -49
  207. package/src/adapter/types/attachments.ts +1 -1
  208. package/src/adapter/types/buffer-layout.ts +1 -1
  209. package/src/adapter/types/parameters.ts +4 -1
  210. package/src/adapter/types/shader-layout.ts +15 -9
  211. package/src/adapter/types/uniforms.ts +12 -0
  212. package/src/adapter-utils/bind-groups.ts +71 -0
  213. package/src/adapter-utils/format-compiler-log.ts +23 -15
  214. package/src/adapter-utils/get-attribute-from-layouts.ts +8 -11
  215. package/src/factories/bind-group-factory.ts +139 -0
  216. package/src/factories/core-module-state.ts +11 -0
  217. package/src/factories/pipeline-factory.ts +328 -0
  218. package/src/factories/shader-factory.ts +103 -0
  219. package/src/index.ts +70 -26
  220. package/src/portable/shader-block-writer.ts +254 -0
  221. package/src/portable/uniform-block.ts +1 -1
  222. package/src/portable/uniform-store.ts +98 -40
  223. package/src/shadertypes/data-types/data-type-decoder.ts +105 -0
  224. package/src/shadertypes/data-types/data-types.ts +100 -48
  225. package/src/shadertypes/data-types/decode-data-types.ts +2 -1
  226. package/src/{image-utils → shadertypes/image-types}/image-types.ts +0 -7
  227. package/src/shadertypes/shader-types/shader-block-layout.ts +340 -0
  228. package/src/shadertypes/{data-types/decode-shader-types.ts → shader-types/shader-type-decoder.ts} +88 -14
  229. package/src/shadertypes/shader-types/shader-types.ts +207 -0
  230. package/src/shadertypes/{textures → texture-types}/pixel-utils.ts +4 -4
  231. package/src/shadertypes/{textures → texture-types}/texture-format-decoder.ts +166 -45
  232. package/src/shadertypes/{textures → texture-types}/texture-format-generics.ts +42 -48
  233. package/src/shadertypes/{textures → texture-types}/texture-format-table.ts +10 -9
  234. package/src/shadertypes/{textures → texture-types}/texture-formats.ts +73 -17
  235. package/src/shadertypes/texture-types/texture-layout.ts +60 -0
  236. package/src/shadertypes/vertex-types/vertex-format-decoder.ts +131 -0
  237. package/src/shadertypes/vertex-types/vertex-formats.ts +183 -0
  238. package/src/utils/array-equal.ts +21 -9
  239. package/src/utils/assert.ts +18 -0
  240. package/src/utils/stats-manager.ts +76 -2
  241. package/dist/image-utils/image-types.d.ts.map +0 -1
  242. package/dist/image-utils/image-types.js.map +0 -1
  243. package/dist/portable/uniform-buffer-layout.d.ts +0 -28
  244. package/dist/portable/uniform-buffer-layout.d.ts.map +0 -1
  245. package/dist/portable/uniform-buffer-layout.js +0 -96
  246. package/dist/portable/uniform-buffer-layout.js.map +0 -1
  247. package/dist/shadertypes/data-types/decode-shader-types.d.ts +0 -17
  248. package/dist/shadertypes/data-types/decode-shader-types.d.ts.map +0 -1
  249. package/dist/shadertypes/data-types/decode-shader-types.js.map +0 -1
  250. package/dist/shadertypes/data-types/shader-types.d.ts +0 -45
  251. package/dist/shadertypes/data-types/shader-types.d.ts.map +0 -1
  252. package/dist/shadertypes/data-types/shader-types.js.map +0 -1
  253. package/dist/shadertypes/textures/pixel-utils.d.ts.map +0 -1
  254. package/dist/shadertypes/textures/pixel-utils.js.map +0 -1
  255. package/dist/shadertypes/textures/texture-format-decoder.d.ts +0 -18
  256. package/dist/shadertypes/textures/texture-format-decoder.d.ts.map +0 -1
  257. package/dist/shadertypes/textures/texture-format-decoder.js.map +0 -1
  258. package/dist/shadertypes/textures/texture-format-generics.d.ts +0 -33
  259. package/dist/shadertypes/textures/texture-format-generics.d.ts.map +0 -1
  260. package/dist/shadertypes/textures/texture-format-generics.js.map +0 -1
  261. package/dist/shadertypes/textures/texture-format-table.d.ts.map +0 -1
  262. package/dist/shadertypes/textures/texture-format-table.js.map +0 -1
  263. package/dist/shadertypes/textures/texture-formats.d.ts.map +0 -1
  264. package/dist/shadertypes/textures/texture-formats.js.map +0 -1
  265. package/dist/shadertypes/vertex-arrays/decode-vertex-format.d.ts +0 -18
  266. package/dist/shadertypes/vertex-arrays/decode-vertex-format.d.ts.map +0 -1
  267. package/dist/shadertypes/vertex-arrays/decode-vertex-format.js +0 -100
  268. package/dist/shadertypes/vertex-arrays/decode-vertex-format.js.map +0 -1
  269. package/dist/shadertypes/vertex-arrays/vertex-formats.d.ts +0 -27
  270. package/dist/shadertypes/vertex-arrays/vertex-formats.d.ts.map +0 -1
  271. package/dist/shadertypes/vertex-arrays/vertex-formats.js.map +0 -1
  272. package/src/portable/uniform-buffer-layout.ts +0 -118
  273. package/src/shadertypes/data-types/shader-types.ts +0 -87
  274. package/src/shadertypes/vertex-arrays/decode-vertex-format.ts +0 -124
  275. package/src/shadertypes/vertex-arrays/vertex-formats.ts +0 -91
  276. /package/dist/{image-utils → shadertypes/image-types}/image-types.js +0 -0
  277. /package/dist/shadertypes/{textures → texture-types}/pixel-utils.d.ts +0 -0
  278. /package/dist/shadertypes/{textures → texture-types}/texture-format-generics.js +0 -0
  279. /package/dist/shadertypes/{textures → texture-types}/texture-format-table.d.ts +0 -0
  280. /package/dist/shadertypes/{vertex-arrays → vertex-types}/vertex-formats.js +0 -0
package/dist/dist.dev.js CHANGED
@@ -43,39 +43,51 @@ var __exports__ = (() => {
43
43
  DeviceFeatures: () => DeviceFeatures,
44
44
  DeviceLimits: () => DeviceLimits,
45
45
  ExternalTexture: () => ExternalTexture,
46
+ Fence: () => Fence,
46
47
  Framebuffer: () => Framebuffer,
48
+ PipelineFactory: () => PipelineFactory,
47
49
  PipelineLayout: () => PipelineLayout,
50
+ PresentationContext: () => PresentationContext,
48
51
  QuerySet: () => QuerySet,
49
52
  RenderPass: () => RenderPass,
50
53
  RenderPipeline: () => RenderPipeline,
51
54
  Resource: () => Resource,
52
55
  Sampler: () => Sampler,
53
56
  Shader: () => Shader,
57
+ ShaderBlockWriter: () => ShaderBlockWriter,
58
+ ShaderFactory: () => ShaderFactory,
59
+ SharedRenderPipeline: () => SharedRenderPipeline,
54
60
  Texture: () => Texture,
55
- TextureFormatDecoder: () => TextureFormatDecoder,
56
61
  TextureView: () => TextureView,
57
62
  TransformFeedback: () => TransformFeedback,
58
63
  UniformBlock: () => UniformBlock,
59
- UniformBufferLayout: () => UniformBufferLayout,
60
64
  UniformStore: () => UniformStore,
61
65
  VertexArray: () => VertexArray,
66
+ _getDefaultBindGroupFactory: () => _getDefaultBindGroupFactory,
62
67
  _getTextureFormatDefinition: () => getTextureFormatDefinition,
63
68
  _getTextureFormatTable: () => getTextureFormatTable,
69
+ assert: () => assert2,
70
+ assertDefined: () => assertDefined,
71
+ dataTypeDecoder: () => dataTypeDecoder,
72
+ flattenBindingsByGroup: () => flattenBindingsByGroup,
64
73
  getAttributeInfosFromLayouts: () => getAttributeInfosFromLayouts,
65
74
  getAttributeShaderTypeInfo: () => getAttributeShaderTypeInfo,
66
- getDataType: () => getDataType,
67
- getDataTypeInfo: () => getDataTypeInfo,
68
- getNormalizedDataType: () => getNormalizedDataType,
75
+ getExternalImageSize: () => getExternalImageSize,
69
76
  getScratchArray: () => getScratchArray,
77
+ getShaderLayoutBinding: () => getShaderLayoutBinding,
78
+ getTextureImageView: () => getTextureImageView,
70
79
  getTypedArrayConstructor: () => getTypedArrayConstructor,
71
80
  getVariableShaderTypeInfo: () => getVariableShaderTypeInfo,
72
- getVertexFormatFromAttribute: () => getVertexFormatFromAttribute,
73
- getVertexFormatInfo: () => getVertexFormatInfo,
81
+ isExternalImage: () => isExternalImage,
74
82
  log: () => log,
75
83
  luma: () => luma,
76
- makeVertexFormat: () => makeVertexFormat,
84
+ makeShaderBlockLayout: () => makeShaderBlockLayout,
85
+ normalizeBindingsByGroup: () => normalizeBindingsByGroup,
77
86
  readPixel: () => readPixel,
87
+ setTextureImageData: () => setTextureImageData,
88
+ shaderTypeDecoder: () => shaderTypeDecoder,
78
89
  textureFormatDecoder: () => textureFormatDecoder,
90
+ vertexFormatDecoder: () => vertexFormatDecoder,
79
91
  writePixel: () => writePixel
80
92
  });
81
93
 
@@ -273,6 +285,24 @@ var __exports__ = (() => {
273
285
  };
274
286
 
275
287
  // src/utils/stats-manager.ts
288
+ var GPU_TIME_AND_MEMORY_STATS = "GPU Time and Memory";
289
+ var GPU_TIME_AND_MEMORY_STAT_ORDER = [
290
+ "Adapter",
291
+ "GPU",
292
+ "GPU Type",
293
+ "GPU Backend",
294
+ "Frame Rate",
295
+ "CPU Time",
296
+ "GPU Time",
297
+ "GPU Memory",
298
+ "Buffer Memory",
299
+ "Texture Memory",
300
+ "Referenced Buffer Memory",
301
+ "Referenced Texture Memory",
302
+ "Swap Chain Texture"
303
+ ];
304
+ var ORDERED_STATS_CACHE = /* @__PURE__ */ new WeakMap();
305
+ var ORDERED_STAT_NAME_SET_CACHE = /* @__PURE__ */ new WeakMap();
276
306
  var StatsManager = class {
277
307
  stats = /* @__PURE__ */ new Map();
278
308
  getStats(name2) {
@@ -282,10 +312,50 @@ var __exports__ = (() => {
282
312
  if (!this.stats.has(name2)) {
283
313
  this.stats.set(name2, new Stats({ id: name2 }));
284
314
  }
285
- return this.stats.get(name2);
315
+ const stats = this.stats.get(name2);
316
+ if (name2 === GPU_TIME_AND_MEMORY_STATS) {
317
+ initializeStats(stats, GPU_TIME_AND_MEMORY_STAT_ORDER);
318
+ }
319
+ return stats;
286
320
  }
287
321
  };
288
322
  var lumaStats = new StatsManager();
323
+ function initializeStats(stats, orderedStatNames) {
324
+ const statsMap = stats.stats;
325
+ let addedOrderedStat = false;
326
+ for (const statName of orderedStatNames) {
327
+ if (!statsMap[statName]) {
328
+ stats.get(statName);
329
+ addedOrderedStat = true;
330
+ }
331
+ }
332
+ const statCount = Object.keys(statsMap).length;
333
+ const cachedStats = ORDERED_STATS_CACHE.get(stats);
334
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
335
+ return;
336
+ }
337
+ const reorderedStats = {};
338
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE.get(orderedStatNames);
339
+ if (!orderedStatNamesSet) {
340
+ orderedStatNamesSet = new Set(orderedStatNames);
341
+ ORDERED_STAT_NAME_SET_CACHE.set(orderedStatNames, orderedStatNamesSet);
342
+ }
343
+ for (const statName of orderedStatNames) {
344
+ if (statsMap[statName]) {
345
+ reorderedStats[statName] = statsMap[statName];
346
+ }
347
+ }
348
+ for (const [statName, stat] of Object.entries(statsMap)) {
349
+ if (!orderedStatNamesSet.has(statName)) {
350
+ reorderedStats[statName] = stat;
351
+ }
352
+ }
353
+ for (const statName of Object.keys(statsMap)) {
354
+ delete statsMap[statName];
355
+ }
356
+ Object.assign(statsMap, reorderedStats);
357
+ ORDERED_STATS_CACHE.set(stats, { orderedStatNames, statCount });
358
+ }
289
359
 
290
360
  // ../../node_modules/@probe.gl/env/dist/lib/globals.js
291
361
  var window_ = globalThis;
@@ -317,7 +387,139 @@ var __exports__ = (() => {
317
387
  }
318
388
 
319
389
  // ../../node_modules/@probe.gl/env/dist/index.js
320
- var VERSION = true ? "4.1.0" : "untranspiled source";
390
+ var VERSION = true ? "4.1.1" : "untranspiled source";
391
+
392
+ // ../../node_modules/@probe.gl/log/dist/utils/assert.js
393
+ function assert(condition, message) {
394
+ if (!condition) {
395
+ throw new Error(message || "Assertion failed");
396
+ }
397
+ }
398
+
399
+ // ../../node_modules/@probe.gl/log/dist/loggers/log-utils.js
400
+ function normalizeLogLevel(logLevel) {
401
+ if (!logLevel) {
402
+ return 0;
403
+ }
404
+ let resolvedLevel;
405
+ switch (typeof logLevel) {
406
+ case "number":
407
+ resolvedLevel = logLevel;
408
+ break;
409
+ case "object":
410
+ resolvedLevel = logLevel.logLevel || logLevel.priority || 0;
411
+ break;
412
+ default:
413
+ return 0;
414
+ }
415
+ assert(Number.isFinite(resolvedLevel) && resolvedLevel >= 0);
416
+ return resolvedLevel;
417
+ }
418
+ function normalizeArguments(opts) {
419
+ const { logLevel, message } = opts;
420
+ opts.logLevel = normalizeLogLevel(logLevel);
421
+ const args = opts.args ? Array.from(opts.args) : [];
422
+ while (args.length && args.shift() !== message) {
423
+ }
424
+ switch (typeof logLevel) {
425
+ case "string":
426
+ case "function":
427
+ if (message !== void 0) {
428
+ args.unshift(message);
429
+ }
430
+ opts.message = logLevel;
431
+ break;
432
+ case "object":
433
+ Object.assign(opts, logLevel);
434
+ break;
435
+ default:
436
+ }
437
+ if (typeof opts.message === "function") {
438
+ opts.message = opts.message();
439
+ }
440
+ const messageType = typeof opts.message;
441
+ assert(messageType === "string" || messageType === "object");
442
+ return Object.assign(opts, { args }, opts.opts);
443
+ }
444
+
445
+ // ../../node_modules/@probe.gl/log/dist/loggers/base-log.js
446
+ var noop = () => {
447
+ };
448
+ var BaseLog = class {
449
+ constructor({ level = 0 } = {}) {
450
+ this.userData = {};
451
+ this._onceCache = /* @__PURE__ */ new Set();
452
+ this._level = level;
453
+ }
454
+ set level(newLevel) {
455
+ this.setLevel(newLevel);
456
+ }
457
+ get level() {
458
+ return this.getLevel();
459
+ }
460
+ setLevel(level) {
461
+ this._level = level;
462
+ return this;
463
+ }
464
+ getLevel() {
465
+ return this._level;
466
+ }
467
+ // Unconditional logging
468
+ warn(message, ...args) {
469
+ return this._log("warn", 0, message, args, { once: true });
470
+ }
471
+ error(message, ...args) {
472
+ return this._log("error", 0, message, args);
473
+ }
474
+ // Conditional logging
475
+ log(logLevel, message, ...args) {
476
+ return this._log("log", logLevel, message, args);
477
+ }
478
+ info(logLevel, message, ...args) {
479
+ return this._log("info", logLevel, message, args);
480
+ }
481
+ once(logLevel, message, ...args) {
482
+ return this._log("once", logLevel, message, args, { once: true });
483
+ }
484
+ _log(type, logLevel, message, args, options = {}) {
485
+ const normalized = normalizeArguments({
486
+ logLevel,
487
+ message,
488
+ args: this._buildArgs(logLevel, message, args),
489
+ opts: options
490
+ });
491
+ return this._createLogFunction(type, normalized, options);
492
+ }
493
+ _buildArgs(logLevel, message, args) {
494
+ return [logLevel, message, ...args];
495
+ }
496
+ _createLogFunction(type, normalized, options) {
497
+ if (!this._shouldLog(normalized.logLevel)) {
498
+ return noop;
499
+ }
500
+ const tag = this._getOnceTag(options.tag ?? normalized.tag ?? normalized.message);
501
+ if ((options.once || normalized.once) && tag !== void 0) {
502
+ if (this._onceCache.has(tag)) {
503
+ return noop;
504
+ }
505
+ this._onceCache.add(tag);
506
+ }
507
+ return this._emit(type, normalized);
508
+ }
509
+ _shouldLog(logLevel) {
510
+ return this.getLevel() >= normalizeLogLevel(logLevel);
511
+ }
512
+ _getOnceTag(tag) {
513
+ if (tag === void 0) {
514
+ return void 0;
515
+ }
516
+ try {
517
+ return typeof tag === "string" ? tag : String(tag);
518
+ } catch {
519
+ return void 0;
520
+ }
521
+ }
522
+ };
321
523
 
322
524
  // ../../node_modules/@probe.gl/log/dist/utils/local-storage.js
323
525
  function getStorage(type) {
@@ -436,13 +638,6 @@ var __exports__ = (() => {
436
638
  }
437
639
  }
438
640
 
439
- // ../../node_modules/@probe.gl/log/dist/utils/assert.js
440
- function assert(condition, message) {
441
- if (!condition) {
442
- throw new Error(message || "Assertion failed");
443
- }
444
- }
445
-
446
641
  // ../../node_modules/@probe.gl/log/dist/utils/hi-res-timestamp.js
447
642
  function getHiResTimestamp2() {
448
643
  let timestamp;
@@ -457,7 +652,7 @@ var __exports__ = (() => {
457
652
  return timestamp;
458
653
  }
459
654
 
460
- // ../../node_modules/@probe.gl/log/dist/log.js
655
+ // ../../node_modules/@probe.gl/log/dist/loggers/probe-log.js
461
656
  var originalConsole = {
462
657
  debug: isBrowser() ? console.debug || console.log : console.log,
463
658
  log: console.log,
@@ -469,12 +664,9 @@ var __exports__ = (() => {
469
664
  enabled: true,
470
665
  level: 0
471
666
  };
472
- function noop() {
473
- }
474
- var cache = {};
475
- var ONCE = { once: true };
476
- var Log = class {
667
+ var ProbeLog = class extends BaseLog {
477
668
  constructor({ id } = { id: "" }) {
669
+ super({ level: 0 });
478
670
  this.VERSION = VERSION;
479
671
  this._startTs = getHiResTimestamp2();
480
672
  this._deltaTs = getHiResTimestamp2();
@@ -482,22 +674,16 @@ var __exports__ = (() => {
482
674
  this.LOG_THROTTLE_TIMEOUT = 0;
483
675
  this.id = id;
484
676
  this.userData = {};
485
- this._storage = new LocalStorage(`__probe-${this.id}__`, DEFAULT_LOG_CONFIGURATION);
677
+ this._storage = new LocalStorage(`__probe-${this.id}__`, { [this.id]: DEFAULT_LOG_CONFIGURATION });
486
678
  this.timeStamp(`${this.id} started`);
487
679
  autobind(this);
488
680
  Object.seal(this);
489
681
  }
490
- set level(newLevel) {
491
- this.setLevel(newLevel);
492
- }
493
- get level() {
494
- return this.getLevel();
495
- }
496
682
  isEnabled() {
497
- return this._storage.config.enabled;
683
+ return this._getConfiguration().enabled;
498
684
  }
499
685
  getLevel() {
500
- return this._storage.config.level;
686
+ return this._getConfiguration().level;
501
687
  }
502
688
  /** @return milliseconds, with fractions */
503
689
  getTotal() {
@@ -521,20 +707,20 @@ var __exports__ = (() => {
521
707
  }
522
708
  // Configure
523
709
  enable(enabled = true) {
524
- this._storage.setConfiguration({ enabled });
710
+ this._updateConfiguration({ enabled });
525
711
  return this;
526
712
  }
527
713
  setLevel(level) {
528
- this._storage.setConfiguration({ level });
714
+ this._updateConfiguration({ level });
529
715
  return this;
530
716
  }
531
717
  /** return the current status of the setting */
532
718
  get(setting) {
533
- return this._storage.config[setting];
719
+ return this._getConfiguration()[setting];
534
720
  }
535
721
  // update the status of the setting
536
722
  set(setting, value) {
537
- this._storage.setConfiguration({ [setting]: value });
723
+ this._updateConfiguration({ [setting]: value });
538
724
  }
539
725
  /** Logs the current settings as a table */
540
726
  settings() {
@@ -550,11 +736,16 @@ var __exports__ = (() => {
550
736
  throw new Error(message || "Assertion failed");
551
737
  }
552
738
  }
553
- warn(message) {
554
- return this._getLogFunction(0, message, originalConsole.warn, arguments, ONCE);
739
+ warn(message, ...args) {
740
+ return this._log("warn", 0, message, args, {
741
+ method: originalConsole.warn,
742
+ once: true
743
+ });
555
744
  }
556
- error(message) {
557
- return this._getLogFunction(0, message, originalConsole.error, arguments);
745
+ error(message, ...args) {
746
+ return this._log("error", 0, message, args, {
747
+ method: originalConsole.error
748
+ });
558
749
  }
559
750
  /** Print a deprecation warning */
560
751
  deprecated(oldUsage, newUsage) {
@@ -564,50 +755,63 @@ var __exports__ = (() => {
564
755
  removed(oldUsage, newUsage) {
565
756
  return this.error(`\`${oldUsage}\` has been removed. Use \`${newUsage}\` instead`);
566
757
  }
567
- probe(logLevel, message) {
568
- return this._getLogFunction(logLevel, message, originalConsole.log, arguments, {
758
+ probe(logLevel, message, ...args) {
759
+ return this._log("log", logLevel, message, args, {
760
+ method: originalConsole.log,
569
761
  time: true,
570
762
  once: true
571
763
  });
572
764
  }
573
- log(logLevel, message) {
574
- return this._getLogFunction(logLevel, message, originalConsole.debug, arguments);
765
+ log(logLevel, message, ...args) {
766
+ return this._log("log", logLevel, message, args, {
767
+ method: originalConsole.debug
768
+ });
575
769
  }
576
- info(logLevel, message) {
577
- return this._getLogFunction(logLevel, message, console.info, arguments);
770
+ info(logLevel, message, ...args) {
771
+ return this._log("info", logLevel, message, args, { method: console.info });
578
772
  }
579
- once(logLevel, message) {
580
- return this._getLogFunction(logLevel, message, originalConsole.debug || originalConsole.info, arguments, ONCE);
773
+ once(logLevel, message, ...args) {
774
+ return this._log("once", logLevel, message, args, {
775
+ method: originalConsole.debug || originalConsole.info,
776
+ once: true
777
+ });
581
778
  }
582
779
  /** Logs an object as a table */
583
780
  table(logLevel, table, columns) {
584
781
  if (table) {
585
- return this._getLogFunction(logLevel, table, console.table || noop, columns && [columns], {
782
+ return this._log("table", logLevel, table, columns && [columns] || [], {
783
+ method: console.table || noop,
586
784
  tag: getTableHeader(table)
587
785
  });
588
786
  }
589
787
  return noop;
590
788
  }
591
789
  time(logLevel, message) {
592
- return this._getLogFunction(logLevel, message, console.time ? console.time : console.info);
790
+ return this._log("time", logLevel, message, [], {
791
+ method: console.time ? console.time : console.info
792
+ });
593
793
  }
594
794
  timeEnd(logLevel, message) {
595
- return this._getLogFunction(logLevel, message, console.timeEnd ? console.timeEnd : console.info);
795
+ return this._log("time", logLevel, message, [], {
796
+ method: console.timeEnd ? console.timeEnd : console.info
797
+ });
596
798
  }
597
799
  timeStamp(logLevel, message) {
598
- return this._getLogFunction(logLevel, message, console.timeStamp || noop);
800
+ return this._log("time", logLevel, message, [], {
801
+ method: console.timeStamp || noop
802
+ });
599
803
  }
600
804
  group(logLevel, message, opts = { collapsed: false }) {
601
- const options = normalizeArguments({ logLevel, message, opts });
602
- const { collapsed } = opts;
603
- options.method = (collapsed ? console.groupCollapsed : console.group) || console.info;
604
- return this._getLogFunction(options);
805
+ const method = (opts.collapsed ? console.groupCollapsed : console.group) || console.info;
806
+ return this._log("group", logLevel, message, [], { method });
605
807
  }
606
808
  groupCollapsed(logLevel, message, opts = {}) {
607
809
  return this.group(logLevel, message, Object.assign({}, opts, { collapsed: true }));
608
810
  }
609
811
  groupEnd(logLevel) {
610
- return this._getLogFunction(logLevel, "", console.groupEnd || noop);
812
+ return this._log("groupEnd", logLevel, "", [], {
813
+ method: console.groupEnd || noop
814
+ });
611
815
  }
612
816
  // EXPERIMENTAL
613
817
  withGroup(logLevel, message, func) {
@@ -623,78 +827,34 @@ var __exports__ = (() => {
623
827
  console.trace();
624
828
  }
625
829
  }
626
- // PRIVATE METHODS
627
- /** Deduces log level from a variety of arguments */
628
830
  _shouldLog(logLevel) {
629
- return this.isEnabled() && this.getLevel() >= normalizeLogLevel(logLevel);
630
- }
631
- _getLogFunction(logLevel, message, method, args, opts) {
632
- if (this._shouldLog(logLevel)) {
633
- opts = normalizeArguments({ logLevel, message, args, opts });
634
- method = method || opts.method;
635
- assert(method);
636
- opts.total = this.getTotal();
637
- opts.delta = this.getDelta();
638
- this._deltaTs = getHiResTimestamp2();
639
- const tag = opts.tag || opts.message;
640
- if (opts.once && tag) {
641
- if (!cache[tag]) {
642
- cache[tag] = getHiResTimestamp2();
643
- } else {
644
- return noop;
645
- }
646
- }
647
- message = decorateMessage(this.id, opts.message, opts);
648
- return method.bind(console, message, ...opts.args);
649
- }
650
- return noop;
831
+ return this.isEnabled() && super._shouldLog(logLevel);
651
832
  }
652
- };
653
- Log.VERSION = VERSION;
654
- function normalizeLogLevel(logLevel) {
655
- if (!logLevel) {
656
- return 0;
657
- }
658
- let resolvedLevel;
659
- switch (typeof logLevel) {
660
- case "number":
661
- resolvedLevel = logLevel;
662
- break;
663
- case "object":
664
- resolvedLevel = logLevel.logLevel || logLevel.priority || 0;
665
- break;
666
- default:
667
- return 0;
668
- }
669
- assert(Number.isFinite(resolvedLevel) && resolvedLevel >= 0);
670
- return resolvedLevel;
671
- }
672
- function normalizeArguments(opts) {
673
- const { logLevel, message } = opts;
674
- opts.logLevel = normalizeLogLevel(logLevel);
675
- const args = opts.args ? Array.from(opts.args) : [];
676
- while (args.length && args.shift() !== message) {
833
+ _emit(_type, normalized) {
834
+ const method = normalized.method;
835
+ assert(method);
836
+ normalized.total = this.getTotal();
837
+ normalized.delta = this.getDelta();
838
+ this._deltaTs = getHiResTimestamp2();
839
+ const message = decorateMessage(this.id, normalized.message, normalized);
840
+ return method.bind(console, message, ...normalized.args);
677
841
  }
678
- switch (typeof logLevel) {
679
- case "string":
680
- case "function":
681
- if (message !== void 0) {
682
- args.unshift(message);
683
- }
684
- opts.message = logLevel;
685
- break;
686
- case "object":
687
- Object.assign(opts, logLevel);
688
- break;
689
- default:
842
+ _getConfiguration() {
843
+ if (!this._storage.config[this.id]) {
844
+ this._updateConfiguration(DEFAULT_LOG_CONFIGURATION);
845
+ }
846
+ return this._storage.config[this.id];
690
847
  }
691
- if (typeof opts.message === "function") {
692
- opts.message = opts.message();
848
+ _updateConfiguration(configuration) {
849
+ const currentConfiguration = this._storage.config[this.id] || {
850
+ ...DEFAULT_LOG_CONFIGURATION
851
+ };
852
+ this._storage.setConfiguration({
853
+ [this.id]: { ...currentConfiguration, ...configuration }
854
+ });
693
855
  }
694
- const messageType = typeof opts.message;
695
- assert(messageType === "string" || messageType === "object");
696
- return Object.assign(opts, { args }, opts.opts);
697
- }
856
+ };
857
+ ProbeLog.VERSION = VERSION;
698
858
  function decorateMessage(id, message, opts) {
699
859
  if (typeof message === "string") {
700
860
  const time = opts.time ? leftPad(formatTime(opts.total)) : "";
@@ -716,10 +876,10 @@ var __exports__ = (() => {
716
876
  globalThis.probe = {};
717
877
 
718
878
  // ../../node_modules/@probe.gl/log/dist/index.js
719
- var dist_default = new Log({ id: "@probe.gl/log" });
879
+ var dist_default = new ProbeLog({ id: "@probe.gl/log" });
720
880
 
721
881
  // src/utils/log.ts
722
- var log = new Log({ id: "luma.gl" });
882
+ var log = new ProbeLog({ id: "luma.gl" });
723
883
 
724
884
  // src/utils/uid.ts
725
885
  var uidCounters = {};
@@ -730,19 +890,75 @@ var __exports__ = (() => {
730
890
  }
731
891
 
732
892
  // src/adapter/resources/resource.ts
893
+ var CPU_HOTSPOT_PROFILER_MODULE = "cpu-hotspot-profiler";
894
+ var RESOURCE_COUNTS_STATS = "GPU Resource Counts";
895
+ var LEGACY_RESOURCE_COUNTS_STATS = "Resource Counts";
896
+ var GPU_TIME_AND_MEMORY_STATS2 = "GPU Time and Memory";
897
+ var BASE_RESOURCE_COUNT_ORDER = [
898
+ "Resources",
899
+ "Buffers",
900
+ "Textures",
901
+ "Samplers",
902
+ "TextureViews",
903
+ "Framebuffers",
904
+ "QuerySets",
905
+ "Shaders",
906
+ "RenderPipelines",
907
+ "ComputePipelines",
908
+ "PipelineLayouts",
909
+ "VertexArrays",
910
+ "RenderPasss",
911
+ "ComputePasss",
912
+ "CommandEncoders",
913
+ "CommandBuffers"
914
+ ];
915
+ var WEBGL_RESOURCE_COUNT_ORDER = [
916
+ "Resources",
917
+ "Buffers",
918
+ "Textures",
919
+ "Samplers",
920
+ "TextureViews",
921
+ "Framebuffers",
922
+ "QuerySets",
923
+ "Shaders",
924
+ "RenderPipelines",
925
+ "SharedRenderPipelines",
926
+ "ComputePipelines",
927
+ "PipelineLayouts",
928
+ "VertexArrays",
929
+ "RenderPasss",
930
+ "ComputePasss",
931
+ "CommandEncoders",
932
+ "CommandBuffers"
933
+ ];
934
+ var BASE_RESOURCE_COUNT_STAT_ORDER = BASE_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
935
+ `${resourceType} Created`,
936
+ `${resourceType} Active`
937
+ ]);
938
+ var WEBGL_RESOURCE_COUNT_STAT_ORDER = WEBGL_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
939
+ `${resourceType} Created`,
940
+ `${resourceType} Active`
941
+ ]);
942
+ var ORDERED_STATS_CACHE2 = /* @__PURE__ */ new WeakMap();
943
+ var ORDERED_STAT_NAME_SET_CACHE2 = /* @__PURE__ */ new WeakMap();
733
944
  var Resource = class {
734
945
  toString() {
735
946
  return `${this[Symbol.toStringTag] || this.constructor.name}:"${this.id}"`;
736
947
  }
737
948
  /** props.id, for debugging. */
738
949
  id;
950
+ /** The props that this resource was created with */
739
951
  props;
952
+ /** User data object, reserved for the application */
740
953
  userData = {};
954
+ /** The device that this resource is associated with - TODO can we remove this dup? */
741
955
  _device;
742
956
  /** Whether this resource has been destroyed */
743
957
  destroyed = false;
744
958
  /** For resources that allocate GPU memory */
745
959
  allocatedBytes = 0;
960
+ /** Stats bucket currently holding the tracked allocation */
961
+ allocatedBytesName = null;
746
962
  /** Attached resources will be destroyed when this resource is destroyed. Tracks auto-created "sub" resources. */
747
963
  _attachedResources = /* @__PURE__ */ new Set();
748
964
  /**
@@ -764,6 +980,9 @@ var __exports__ = (() => {
764
980
  * destroy can be called on any resource to release it before it is garbage collected.
765
981
  */
766
982
  destroy() {
983
+ if (this.destroyed) {
984
+ return;
985
+ }
767
986
  this.destroyResource();
768
987
  }
769
988
  /** @deprecated Use destroy() */
@@ -802,7 +1021,7 @@ var __exports__ = (() => {
802
1021
  }
803
1022
  /** Destroy all owned resources. Make sure the resources are no longer needed before calling. */
804
1023
  destroyAttachedResources() {
805
- for (const resource of Object.values(this._attachedResources)) {
1024
+ for (const resource of this._attachedResources) {
806
1025
  resource.destroy();
807
1026
  }
808
1027
  this._attachedResources = /* @__PURE__ */ new Set();
@@ -810,37 +1029,107 @@ var __exports__ = (() => {
810
1029
  // PROTECTED METHODS
811
1030
  /** Perform all destroy steps. Can be called by derived resources when overriding destroy() */
812
1031
  destroyResource() {
1032
+ if (this.destroyed) {
1033
+ return;
1034
+ }
813
1035
  this.destroyAttachedResources();
814
1036
  this.removeStats();
815
1037
  this.destroyed = true;
816
1038
  }
817
1039
  /** Called by .destroy() to track object destruction. Subclass must call if overriding destroy() */
818
1040
  removeStats() {
819
- const stats = this._device.statsManager.getStats("Resource Counts");
820
- const name2 = this[Symbol.toStringTag];
821
- stats.get(`${name2}s Active`).decrementCount();
1041
+ const profiler = getCpuHotspotProfiler(this._device);
1042
+ const startTime = profiler ? getTimestamp() : 0;
1043
+ const statsObjects = [
1044
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1045
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1046
+ ];
1047
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1048
+ for (const stats of statsObjects) {
1049
+ initializeStats2(stats, orderedStatNames);
1050
+ }
1051
+ const name2 = this.getStatsName();
1052
+ for (const stats of statsObjects) {
1053
+ stats.get("Resources Active").decrementCount();
1054
+ stats.get(`${name2}s Active`).decrementCount();
1055
+ }
1056
+ if (profiler) {
1057
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1058
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1059
+ }
822
1060
  }
823
1061
  /** Called by subclass to track memory allocations */
824
- trackAllocatedMemory(bytes, name2 = this[Symbol.toStringTag]) {
825
- const stats = this._device.statsManager.getStats("Resource Counts");
1062
+ trackAllocatedMemory(bytes, name2 = this.getStatsName()) {
1063
+ const profiler = getCpuHotspotProfiler(this._device);
1064
+ const startTime = profiler ? getTimestamp() : 0;
1065
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
1066
+ if (this.allocatedBytes > 0 && this.allocatedBytesName) {
1067
+ stats.get("GPU Memory").subtractCount(this.allocatedBytes);
1068
+ stats.get(`${this.allocatedBytesName} Memory`).subtractCount(this.allocatedBytes);
1069
+ }
826
1070
  stats.get("GPU Memory").addCount(bytes);
827
1071
  stats.get(`${name2} Memory`).addCount(bytes);
1072
+ if (profiler) {
1073
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1074
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1075
+ }
828
1076
  this.allocatedBytes = bytes;
1077
+ this.allocatedBytesName = name2;
1078
+ }
1079
+ /** Called by subclass to track handle-backed memory allocations separately from owned allocations */
1080
+ trackReferencedMemory(bytes, name2 = this.getStatsName()) {
1081
+ this.trackAllocatedMemory(bytes, `Referenced ${name2}`);
829
1082
  }
830
1083
  /** Called by subclass to track memory deallocations */
831
- trackDeallocatedMemory(name2 = this[Symbol.toStringTag]) {
832
- const stats = this._device.statsManager.getStats("Resource Counts");
1084
+ trackDeallocatedMemory(name2 = this.getStatsName()) {
1085
+ if (this.allocatedBytes === 0) {
1086
+ this.allocatedBytesName = null;
1087
+ return;
1088
+ }
1089
+ const profiler = getCpuHotspotProfiler(this._device);
1090
+ const startTime = profiler ? getTimestamp() : 0;
1091
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
833
1092
  stats.get("GPU Memory").subtractCount(this.allocatedBytes);
834
- stats.get(`${name2} Memory`).subtractCount(this.allocatedBytes);
1093
+ stats.get(`${this.allocatedBytesName || name2} Memory`).subtractCount(this.allocatedBytes);
1094
+ if (profiler) {
1095
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1096
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1097
+ }
835
1098
  this.allocatedBytes = 0;
1099
+ this.allocatedBytesName = null;
1100
+ }
1101
+ /** Called by subclass to deallocate handle-backed memory tracked via trackReferencedMemory() */
1102
+ trackDeallocatedReferencedMemory(name2 = this.getStatsName()) {
1103
+ this.trackDeallocatedMemory(`Referenced ${name2}`);
836
1104
  }
837
1105
  /** Called by resource constructor to track object creation */
838
1106
  addStats() {
839
- const stats = this._device.statsManager.getStats("Resource Counts");
840
- const name2 = this[Symbol.toStringTag];
841
- stats.get("Resources Created").incrementCount();
842
- stats.get(`${name2}s Created`).incrementCount();
843
- stats.get(`${name2}s Active`).incrementCount();
1107
+ const name2 = this.getStatsName();
1108
+ const profiler = getCpuHotspotProfiler(this._device);
1109
+ const startTime = profiler ? getTimestamp() : 0;
1110
+ const statsObjects = [
1111
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1112
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1113
+ ];
1114
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1115
+ for (const stats of statsObjects) {
1116
+ initializeStats2(stats, orderedStatNames);
1117
+ }
1118
+ for (const stats of statsObjects) {
1119
+ stats.get("Resources Created").incrementCount();
1120
+ stats.get("Resources Active").incrementCount();
1121
+ stats.get(`${name2}s Created`).incrementCount();
1122
+ stats.get(`${name2}s Active`).incrementCount();
1123
+ }
1124
+ if (profiler) {
1125
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1126
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1127
+ }
1128
+ recordTransientCanvasResourceCreate(this._device, name2);
1129
+ }
1130
+ /** Canonical resource name used for stats buckets. */
1131
+ getStatsName() {
1132
+ return getCanonicalResourceName(this);
844
1133
  }
845
1134
  };
846
1135
  /** Default properties for resource */
@@ -858,6 +1147,96 @@ var __exports__ = (() => {
858
1147
  }
859
1148
  return mergedProps;
860
1149
  }
1150
+ function initializeStats2(stats, orderedStatNames) {
1151
+ const statsMap = stats.stats;
1152
+ let addedOrderedStat = false;
1153
+ for (const statName of orderedStatNames) {
1154
+ if (!statsMap[statName]) {
1155
+ stats.get(statName);
1156
+ addedOrderedStat = true;
1157
+ }
1158
+ }
1159
+ const statCount = Object.keys(statsMap).length;
1160
+ const cachedStats = ORDERED_STATS_CACHE2.get(stats);
1161
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
1162
+ return;
1163
+ }
1164
+ const reorderedStats = {};
1165
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE2.get(orderedStatNames);
1166
+ if (!orderedStatNamesSet) {
1167
+ orderedStatNamesSet = new Set(orderedStatNames);
1168
+ ORDERED_STAT_NAME_SET_CACHE2.set(orderedStatNames, orderedStatNamesSet);
1169
+ }
1170
+ for (const statName of orderedStatNames) {
1171
+ if (statsMap[statName]) {
1172
+ reorderedStats[statName] = statsMap[statName];
1173
+ }
1174
+ }
1175
+ for (const [statName, stat] of Object.entries(statsMap)) {
1176
+ if (!orderedStatNamesSet.has(statName)) {
1177
+ reorderedStats[statName] = stat;
1178
+ }
1179
+ }
1180
+ for (const statName of Object.keys(statsMap)) {
1181
+ delete statsMap[statName];
1182
+ }
1183
+ Object.assign(statsMap, reorderedStats);
1184
+ ORDERED_STATS_CACHE2.set(stats, { orderedStatNames, statCount });
1185
+ }
1186
+ function getResourceCountStatOrder(device) {
1187
+ return device.type === "webgl" ? WEBGL_RESOURCE_COUNT_STAT_ORDER : BASE_RESOURCE_COUNT_STAT_ORDER;
1188
+ }
1189
+ function getCpuHotspotProfiler(device) {
1190
+ const profiler = device.userData[CPU_HOTSPOT_PROFILER_MODULE];
1191
+ return profiler?.enabled ? profiler : null;
1192
+ }
1193
+ function getTimestamp() {
1194
+ return globalThis.performance?.now?.() ?? Date.now();
1195
+ }
1196
+ function recordTransientCanvasResourceCreate(device, name2) {
1197
+ const profiler = getCpuHotspotProfiler(device);
1198
+ if (!profiler || !profiler.activeDefaultFramebufferAcquireDepth) {
1199
+ return;
1200
+ }
1201
+ profiler.transientCanvasResourceCreates = (profiler.transientCanvasResourceCreates || 0) + 1;
1202
+ switch (name2) {
1203
+ case "Texture":
1204
+ profiler.transientCanvasTextureCreates = (profiler.transientCanvasTextureCreates || 0) + 1;
1205
+ break;
1206
+ case "TextureView":
1207
+ profiler.transientCanvasTextureViewCreates = (profiler.transientCanvasTextureViewCreates || 0) + 1;
1208
+ break;
1209
+ case "Sampler":
1210
+ profiler.transientCanvasSamplerCreates = (profiler.transientCanvasSamplerCreates || 0) + 1;
1211
+ break;
1212
+ case "Framebuffer":
1213
+ profiler.transientCanvasFramebufferCreates = (profiler.transientCanvasFramebufferCreates || 0) + 1;
1214
+ break;
1215
+ default:
1216
+ break;
1217
+ }
1218
+ }
1219
+ function getCanonicalResourceName(resource) {
1220
+ let prototype = Object.getPrototypeOf(resource);
1221
+ while (prototype) {
1222
+ const parentPrototype = Object.getPrototypeOf(prototype);
1223
+ if (!parentPrototype || parentPrototype === Resource.prototype) {
1224
+ return getPrototypeToStringTag(prototype) || resource[Symbol.toStringTag] || resource.constructor.name;
1225
+ }
1226
+ prototype = parentPrototype;
1227
+ }
1228
+ return resource[Symbol.toStringTag] || resource.constructor.name;
1229
+ }
1230
+ function getPrototypeToStringTag(prototype) {
1231
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, Symbol.toStringTag);
1232
+ if (typeof descriptor?.get === "function") {
1233
+ return descriptor.get.call(prototype);
1234
+ }
1235
+ if (typeof descriptor?.value === "string") {
1236
+ return descriptor.value;
1237
+ }
1238
+ return null;
1239
+ }
861
1240
 
862
1241
  // src/adapter/resources/buffer.ts
863
1242
  var _Buffer = class extends Resource {
@@ -897,18 +1276,26 @@ var __exports__ = (() => {
897
1276
  /** A partial CPU-side copy of the data in this buffer, for debugging purposes */
898
1277
  debugData = new ArrayBuffer(0);
899
1278
  /** This doesn't handle partial non-zero offset updates correctly */
900
- _setDebugData(data, byteOffset, byteLength) {
901
- const arrayBuffer2 = ArrayBuffer.isView(data) ? data.buffer : data;
1279
+ _setDebugData(data, _byteOffset, byteLength) {
1280
+ let arrayBufferView = null;
1281
+ let arrayBuffer2;
1282
+ if (ArrayBuffer.isView(data)) {
1283
+ arrayBufferView = data;
1284
+ arrayBuffer2 = data.buffer;
1285
+ } else {
1286
+ arrayBuffer2 = data;
1287
+ }
902
1288
  const debugDataLength = Math.min(
903
1289
  data ? data.byteLength : byteLength,
904
1290
  _Buffer.DEBUG_DATA_MAX_LENGTH
905
1291
  );
906
1292
  if (arrayBuffer2 === null) {
907
1293
  this.debugData = new ArrayBuffer(debugDataLength);
908
- } else if (byteOffset === 0 && byteLength === arrayBuffer2.byteLength) {
909
- this.debugData = arrayBuffer2.slice(0, debugDataLength);
910
1294
  } else {
911
- this.debugData = arrayBuffer2.slice(byteOffset, byteOffset + debugDataLength);
1295
+ const sourceByteOffset = Math.min(arrayBufferView?.byteOffset || 0, arrayBuffer2.byteLength);
1296
+ const availableByteLength = Math.max(0, arrayBuffer2.byteLength - sourceByteOffset);
1297
+ const copyByteLength = Math.min(debugDataLength, availableByteLength);
1298
+ this.debugData = new Uint8Array(arrayBuffer2, sourceByteOffset, copyByteLength).slice().buffer;
912
1299
  }
913
1300
  }
914
1301
  };
@@ -942,61 +1329,73 @@ var __exports__ = (() => {
942
1329
  onMapped: void 0
943
1330
  });
944
1331
 
945
- // src/shadertypes/data-types/decode-data-types.ts
946
- function getDataTypeInfo(type) {
947
- const [signedType, primitiveType, byteLength] = NORMALIZED_TYPE_MAP[type];
948
- const normalized = type.includes("norm");
949
- const integer = !normalized && !type.startsWith("float");
950
- const signed = type.startsWith("s");
951
- return {
952
- signedType,
953
- primitiveType,
954
- byteLength,
955
- normalized,
956
- integer,
957
- signed
958
- };
959
- }
960
- function getNormalizedDataType(signedDataType) {
961
- const dataType = signedDataType;
962
- switch (dataType) {
963
- case "uint8":
964
- return "unorm8";
965
- case "sint8":
966
- return "snorm8";
967
- case "uint16":
968
- return "unorm16";
969
- case "sint16":
970
- return "snorm16";
971
- default:
972
- return dataType;
1332
+ // src/shadertypes/data-types/data-type-decoder.ts
1333
+ var DataTypeDecoder = class {
1334
+ /**
1335
+ * Gets info about a data type constant (signed or normalized)
1336
+ * @returns underlying primitive / signed types, byte length, normalization, integer, signed flags
1337
+ */
1338
+ getDataTypeInfo(type) {
1339
+ const [signedType, primitiveType, byteLength] = NORMALIZED_TYPE_MAP[type];
1340
+ const normalized = type.includes("norm");
1341
+ const integer = !normalized && !type.startsWith("float");
1342
+ const signed = type.startsWith("s");
1343
+ return {
1344
+ signedType,
1345
+ primitiveType,
1346
+ byteLength,
1347
+ normalized,
1348
+ integer,
1349
+ signed
1350
+ // TODO - add webglOnly flag
1351
+ };
973
1352
  }
974
- }
975
- function alignTo(size, count) {
976
- switch (count) {
977
- case 1:
978
- return size;
979
- case 2:
980
- return size + size % 2;
981
- default:
982
- return size + (4 - size % 4) % 4;
1353
+ /** Build a vertex format from a signed data type and a component */
1354
+ getNormalizedDataType(signedDataType) {
1355
+ const dataType = signedDataType;
1356
+ switch (dataType) {
1357
+ case "uint8":
1358
+ return "unorm8";
1359
+ case "sint8":
1360
+ return "snorm8";
1361
+ case "uint16":
1362
+ return "unorm16";
1363
+ case "sint16":
1364
+ return "snorm16";
1365
+ default:
1366
+ return dataType;
1367
+ }
983
1368
  }
984
- }
985
- function getDataType(arrayOrType) {
986
- const Constructor = ArrayBuffer.isView(arrayOrType) ? arrayOrType.constructor : arrayOrType;
987
- if (Constructor === Uint8ClampedArray) {
988
- return "uint8";
1369
+ /** Align offset to 1, 2 or 4 elements (4, 8 or 16 bytes) */
1370
+ alignTo(size, count) {
1371
+ switch (count) {
1372
+ case 1:
1373
+ return size;
1374
+ case 2:
1375
+ return size + size % 2;
1376
+ default:
1377
+ return size + (4 - size % 4) % 4;
1378
+ }
989
1379
  }
990
- const info = Object.values(NORMALIZED_TYPE_MAP).find((entry) => Constructor === entry[4]);
991
- if (!info) {
992
- throw new Error(Constructor.name);
1380
+ /** Returns the VariableShaderType that corresponds to a typed array */
1381
+ getDataType(arrayOrType) {
1382
+ const Constructor = ArrayBuffer.isView(arrayOrType) ? arrayOrType.constructor : arrayOrType;
1383
+ if (Constructor === Uint8ClampedArray) {
1384
+ return "uint8";
1385
+ }
1386
+ const info = Object.values(NORMALIZED_TYPE_MAP).find((entry) => Constructor === entry[4]);
1387
+ if (!info) {
1388
+ throw new Error(Constructor.name);
1389
+ }
1390
+ return info[0];
993
1391
  }
994
- return info[0];
995
- }
996
- function getTypedArrayConstructor(type) {
997
- const [, , , , Constructor] = NORMALIZED_TYPE_MAP[type];
998
- return Constructor;
999
- }
1392
+ /** Returns the TypedArray that corresponds to a shader data type */
1393
+ getTypedArrayConstructor(type) {
1394
+ const [, , , , Constructor] = NORMALIZED_TYPE_MAP[type];
1395
+ return Constructor;
1396
+ }
1397
+ };
1398
+ var dataTypeDecoder = new DataTypeDecoder();
1000
1399
  var NORMALIZED_TYPE_MAP = {
1001
1400
  uint8: ["uint8", "u32", 1, false, Uint8Array],
1002
1401
  sint8: ["sint8", "i32", 1, false, Int8Array],
@@ -1012,87 +1411,99 @@ var __exports__ = (() => {
1012
1411
  sint32: ["sint32", "i32", 4, false, Int32Array]
1013
1412
  };
1014
1413
 
1015
- // src/shadertypes/vertex-arrays/decode-vertex-format.ts
1016
- function getVertexFormatInfo(format) {
1017
- let webglOnly;
1018
- if (format.endsWith("-webgl")) {
1019
- format.replace("-webgl", "");
1020
- webglOnly = true;
1021
- }
1022
- const [type_, count] = format.split("x");
1023
- const type = type_;
1024
- const components = count ? parseInt(count) : 1;
1025
- const decodedType = getDataTypeInfo(type);
1026
- const result = {
1027
- type,
1028
- components,
1029
- byteLength: decodedType.byteLength * components,
1030
- integer: decodedType.integer,
1031
- signed: decodedType.signed,
1032
- normalized: decodedType.normalized
1033
- };
1034
- if (webglOnly) {
1035
- result.webglOnly = true;
1036
- }
1037
- return result;
1038
- }
1039
- function makeVertexFormat(signedDataType, components, normalized) {
1040
- const dataType = normalized ? getNormalizedDataType(signedDataType) : signedDataType;
1041
- switch (dataType) {
1042
- case "unorm8":
1043
- if (components === 1) {
1044
- return "unorm8";
1045
- }
1046
- if (components === 3) {
1047
- return "unorm8x3-webgl";
1048
- }
1049
- return `${dataType}x${components}`;
1050
- case "snorm8":
1051
- case "uint8":
1052
- case "sint8":
1053
- case "uint16":
1054
- case "sint16":
1055
- case "unorm16":
1056
- case "snorm16":
1057
- case "float16":
1058
- if (components === 1 || components === 3) {
1059
- throw new Error(`size: ${components}`);
1060
- }
1061
- return `${dataType}x${components}`;
1062
- default:
1063
- return components === 1 ? dataType : `${dataType}x${components}`;
1414
+ // src/shadertypes/vertex-types/vertex-format-decoder.ts
1415
+ var VertexFormatDecoder = class {
1416
+ /**
1417
+ * Decodes a vertex format, returning type, components, byte length and flags (integer, signed, normalized)
1418
+ */
1419
+ getVertexFormatInfo(format) {
1420
+ let webglOnly;
1421
+ if (format.endsWith("-webgl")) {
1422
+ format.replace("-webgl", "");
1423
+ webglOnly = true;
1424
+ }
1425
+ const [type_, count] = format.split("x");
1426
+ const type = type_;
1427
+ const components = count ? parseInt(count) : 1;
1428
+ const decodedType = dataTypeDecoder.getDataTypeInfo(type);
1429
+ const result = {
1430
+ type,
1431
+ components,
1432
+ byteLength: decodedType.byteLength * components,
1433
+ integer: decodedType.integer,
1434
+ signed: decodedType.signed,
1435
+ normalized: decodedType.normalized
1436
+ };
1437
+ if (webglOnly) {
1438
+ result.webglOnly = true;
1439
+ }
1440
+ return result;
1064
1441
  }
1065
- }
1066
- function getVertexFormatFromAttribute(typedArray, size, normalized) {
1067
- if (!size || size > 4) {
1068
- throw new Error(`size ${size}`);
1442
+ /** Build a vertex format from a signed data type and a component */
1443
+ makeVertexFormat(signedDataType, components, normalized) {
1444
+ const dataType = normalized ? dataTypeDecoder.getNormalizedDataType(signedDataType) : signedDataType;
1445
+ switch (dataType) {
1446
+ case "unorm8":
1447
+ if (components === 1) {
1448
+ return "unorm8";
1449
+ }
1450
+ if (components === 3) {
1451
+ return "unorm8x3-webgl";
1452
+ }
1453
+ return `${dataType}x${components}`;
1454
+ case "snorm8":
1455
+ case "uint8":
1456
+ case "sint8":
1457
+ case "uint16":
1458
+ case "sint16":
1459
+ case "unorm16":
1460
+ case "snorm16":
1461
+ case "float16":
1462
+ if (components === 1 || components === 3) {
1463
+ throw new Error(`size: ${components}`);
1464
+ }
1465
+ return `${dataType}x${components}`;
1466
+ default:
1467
+ return components === 1 ? dataType : `${dataType}x${components}`;
1468
+ }
1069
1469
  }
1070
- const components = size;
1071
- const signedDataType = getDataType(typedArray);
1072
- return makeVertexFormat(signedDataType, components, normalized);
1073
- }
1074
- function getCompatibleVertexFormat(opts) {
1075
- let vertexType;
1076
- switch (opts.primitiveType) {
1077
- case "f32":
1078
- vertexType = "float32";
1079
- break;
1080
- case "i32":
1081
- vertexType = "sint32";
1082
- break;
1083
- case "u32":
1084
- vertexType = "uint32";
1085
- break;
1086
- case "f16":
1087
- return opts.components <= 2 ? "float16x2" : "float16x4";
1470
+ /** Get the vertex format for an attribute with TypedArray and size */
1471
+ getVertexFormatFromAttribute(typedArray, size, normalized) {
1472
+ if (!size || size > 4) {
1473
+ throw new Error(`size ${size}`);
1474
+ }
1475
+ const components = size;
1476
+ const signedDataType = dataTypeDecoder.getDataType(typedArray);
1477
+ return this.makeVertexFormat(signedDataType, components, normalized);
1088
1478
  }
1089
- if (opts.components === 1) {
1090
- return vertexType;
1479
+ /**
1480
+ * Return a "default" vertex format for a certain shader data type
1481
+ * The simplest vertex format that matches the shader attribute's data type
1482
+ */
1483
+ getCompatibleVertexFormat(opts) {
1484
+ let vertexType;
1485
+ switch (opts.primitiveType) {
1486
+ case "f32":
1487
+ vertexType = "float32";
1488
+ break;
1489
+ case "i32":
1490
+ vertexType = "sint32";
1491
+ break;
1492
+ case "u32":
1493
+ vertexType = "uint32";
1494
+ break;
1495
+ case "f16":
1496
+ return opts.components <= 2 ? "float16x2" : "float16x4";
1497
+ }
1498
+ if (opts.components === 1) {
1499
+ return vertexType;
1500
+ }
1501
+ return `${vertexType}x${opts.components}`;
1091
1502
  }
1092
- return `${vertexType}x${opts.components}`;
1093
- }
1503
+ };
1504
+ var vertexFormatDecoder = new VertexFormatDecoder();
1094
1505
 
1095
- // src/shadertypes/textures/texture-format-table.ts
1506
+ // src/shadertypes/texture-types/texture-format-table.ts
1096
1507
  var texture_compression_bc = "texture-compression-bc";
1097
1508
  var texture_compression_astc = "texture-compression-astc";
1098
1509
  var texture_compression_etc2 = "texture-compression-etc2";
@@ -1103,6 +1514,7 @@ var __exports__ = (() => {
1103
1514
  var float16_renderable = "float16-renderable-webgl";
1104
1515
  var rgb9e5ufloat_renderable = "rgb9e5ufloat-renderable-webgl";
1105
1516
  var snorm8_renderable = "snorm8-renderable-webgl";
1517
+ var norm16_webgl = "norm16-webgl";
1106
1518
  var norm16_renderable = "norm16-renderable-webgl";
1107
1519
  var snorm16_renderable = "snorm16-renderable-webgl";
1108
1520
  var float32_filterable = "float32-filterable";
@@ -1136,16 +1548,16 @@ var __exports__ = (() => {
1136
1548
  "rgba8sint": {},
1137
1549
  "bgra8unorm": {},
1138
1550
  "bgra8unorm-srgb": {},
1139
- "r16unorm": { f: norm16_renderable },
1140
- "rg16unorm": { render: norm16_renderable },
1141
- "rgb16unorm-webgl": { f: norm16_renderable },
1551
+ "r16unorm": { f: norm16_webgl, render: norm16_renderable },
1552
+ "rg16unorm": { f: norm16_webgl, render: norm16_renderable },
1553
+ "rgb16unorm-webgl": { f: norm16_webgl, render: false },
1142
1554
  // rgb not renderable
1143
- "rgba16unorm": { render: norm16_renderable },
1144
- "r16snorm": { f: snorm16_renderable },
1145
- "rg16snorm": { render: snorm16_renderable },
1146
- "rgb16snorm-webgl": { f: norm16_renderable },
1555
+ "rgba16unorm": { f: norm16_webgl, render: norm16_renderable },
1556
+ "r16snorm": { f: norm16_webgl, render: snorm16_renderable },
1557
+ "rg16snorm": { f: norm16_webgl, render: snorm16_renderable },
1558
+ "rgb16snorm-webgl": { f: norm16_webgl, render: false },
1147
1559
  // rgb not renderable
1148
- "rgba16snorm": { render: snorm16_renderable },
1560
+ "rgba16snorm": { f: norm16_webgl, render: snorm16_renderable },
1149
1561
  "r16uint": {},
1150
1562
  "rg16uint": {},
1151
1563
  "rgba16uint": {},
@@ -1248,7 +1660,7 @@ var __exports__ = (() => {
1248
1660
  // WEBGL_compressed_texture_pvrtc
1249
1661
  "pvrtc-rgb4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1250
1662
  "pvrtc-rgba4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1251
- "pvrtc-rbg2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1663
+ "pvrtc-rgb2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1252
1664
  "pvrtc-rgba2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1253
1665
  // WEBGL_compressed_texture_etc1
1254
1666
  "etc1-rbg-unorm-webgl": { f: texture_compression_etc1_webgl },
@@ -1262,7 +1674,10 @@ var __exports__ = (() => {
1262
1674
  ...TEXTURE_FORMAT_COMPRESSED_TABLE
1263
1675
  };
1264
1676
 
1265
- // src/shadertypes/textures/texture-format-decoder.ts
1677
+ // src/shadertypes/texture-types/texture-format-decoder.ts
1678
+ var RGB_FORMAT_REGEX = /^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/;
1679
+ var COLOR_FORMAT_PREFIXES = ["rgb", "rgba", "bgra"];
1680
+ var DEPTH_FORMAT_PREFIXES = ["depth", "stencil"];
1266
1681
  var COMPRESSED_TEXTURE_FORMAT_PREFIXES = [
1267
1682
  "bc1",
1268
1683
  "bc2",
@@ -1278,49 +1693,83 @@ var __exports__ = (() => {
1278
1693
  "astc",
1279
1694
  "pvrtc"
1280
1695
  ];
1281
- var RGB_FORMAT_REGEX = /^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/;
1282
1696
  var TextureFormatDecoder = class {
1283
- /** Returns information about a texture format, e.g. attatchment type, components, byte length and flags (integer, signed, normalized) */
1284
- getInfo(format) {
1285
- return getTextureFormatInfo(format);
1286
- }
1287
1697
  /** Checks if a texture format is color */
1288
1698
  isColor(format) {
1289
- return format.startsWith("rgba") || format.startsWith("bgra") || format.startsWith("rgb");
1699
+ return COLOR_FORMAT_PREFIXES.some((prefix) => format.startsWith(prefix));
1290
1700
  }
1291
1701
  /** Checks if a texture format is depth or stencil */
1292
1702
  isDepthStencil(format) {
1293
- return format.startsWith("depth") || format.startsWith("stencil");
1703
+ return DEPTH_FORMAT_PREFIXES.some((prefix) => format.startsWith(prefix));
1294
1704
  }
1295
1705
  /** Checks if a texture format is compressed */
1296
1706
  isCompressed(format) {
1297
1707
  return COMPRESSED_TEXTURE_FORMAT_PREFIXES.some((prefix) => format.startsWith(prefix));
1298
1708
  }
1299
- /**
1300
- * Returns the "static" capabilities of a texture format.
1301
- * @note Needs to be checked against current device
1302
- */
1709
+ /** Returns information about a texture format, e.g. attachment type, components, byte length and flags (integer, signed, normalized) */
1710
+ getInfo(format) {
1711
+ return getTextureFormatInfo(format);
1712
+ }
1713
+ /** "static" capabilities of a texture format. @note Needs to be adjusted against current device */
1303
1714
  getCapabilities(format) {
1304
- const info = getTextureFormatDefinition(format);
1305
- const formatCapabilities = {
1306
- format,
1307
- create: info.f ?? true,
1308
- render: info.render ?? true,
1309
- filter: info.filter ?? true,
1310
- blend: info.blend ?? true,
1311
- store: info.store ?? true
1312
- };
1313
- const formatInfo = getTextureFormatInfo(format);
1314
- const isDepthStencil = format.startsWith("depth") || format.startsWith("stencil");
1315
- const isSigned = formatInfo?.signed;
1316
- const isInteger = formatInfo?.integer;
1317
- const isWebGLSpecific = formatInfo?.webgl;
1318
- formatCapabilities.render &&= !isSigned;
1319
- formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1320
- return formatCapabilities;
1715
+ return getTextureFormatCapabilities(format);
1716
+ }
1717
+ /** Computes the memory layout for a texture, in particular including row byte alignment */
1718
+ computeMemoryLayout(opts) {
1719
+ return computeTextureMemoryLayout(opts);
1321
1720
  }
1322
1721
  };
1323
1722
  var textureFormatDecoder = new TextureFormatDecoder();
1723
+ function computeTextureMemoryLayout({
1724
+ format,
1725
+ width,
1726
+ height,
1727
+ depth,
1728
+ byteAlignment
1729
+ }) {
1730
+ const formatInfo = textureFormatDecoder.getInfo(format);
1731
+ const {
1732
+ bytesPerPixel,
1733
+ bytesPerBlock = bytesPerPixel,
1734
+ blockWidth = 1,
1735
+ blockHeight = 1,
1736
+ compressed = false
1737
+ } = formatInfo;
1738
+ const blockColumns = compressed ? Math.ceil(width / blockWidth) : width;
1739
+ const blockRows = compressed ? Math.ceil(height / blockHeight) : height;
1740
+ const unpaddedBytesPerRow = blockColumns * bytesPerBlock;
1741
+ const bytesPerRow = Math.ceil(unpaddedBytesPerRow / byteAlignment) * byteAlignment;
1742
+ const rowsPerImage = blockRows;
1743
+ const byteLength = bytesPerRow * rowsPerImage * depth;
1744
+ return {
1745
+ bytesPerPixel,
1746
+ bytesPerRow,
1747
+ rowsPerImage,
1748
+ depthOrArrayLayers: depth,
1749
+ bytesPerImage: bytesPerRow * rowsPerImage,
1750
+ byteLength
1751
+ };
1752
+ }
1753
+ function getTextureFormatCapabilities(format) {
1754
+ const info = getTextureFormatDefinition(format);
1755
+ const formatCapabilities = {
1756
+ format,
1757
+ create: info.f ?? true,
1758
+ render: info.render ?? true,
1759
+ filter: info.filter ?? true,
1760
+ blend: info.blend ?? true,
1761
+ store: info.store ?? true
1762
+ };
1763
+ const formatInfo = getTextureFormatInfo(format);
1764
+ const isDepthStencil = format.startsWith("depth") || format.startsWith("stencil");
1765
+ const isSigned = formatInfo?.signed;
1766
+ const isInteger = formatInfo?.integer;
1767
+ const isWebGLSpecific = formatInfo?.webgl;
1768
+ const isCompressed = Boolean(formatInfo?.compressed);
1769
+ formatCapabilities.render &&= !isDepthStencil && !isCompressed;
1770
+ formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1771
+ return formatCapabilities;
1772
+ }
1324
1773
  function getTextureFormatInfo(format) {
1325
1774
  let formatInfo = getTextureFormatInfoUsingTable(format);
1326
1775
  if (textureFormatDecoder.isCompressed(format)) {
@@ -1329,19 +1778,20 @@ var __exports__ = (() => {
1329
1778
  formatInfo.bytesPerPixel = 1;
1330
1779
  formatInfo.srgb = false;
1331
1780
  formatInfo.compressed = true;
1781
+ formatInfo.bytesPerBlock = getCompressedTextureBlockByteLength(format);
1332
1782
  const blockSize = getCompressedTextureBlockSize(format);
1333
1783
  if (blockSize) {
1334
1784
  formatInfo.blockWidth = blockSize.blockWidth;
1335
1785
  formatInfo.blockHeight = blockSize.blockHeight;
1336
1786
  }
1337
1787
  }
1338
- const matches = RGB_FORMAT_REGEX.exec(format);
1788
+ const matches = !formatInfo.packed ? RGB_FORMAT_REGEX.exec(format) : null;
1339
1789
  if (matches) {
1340
1790
  const [, channels, length, type, srgb, suffix] = matches;
1341
1791
  const dataType = `${type}${length}`;
1342
- const decodedType = getDataTypeInfo(dataType);
1792
+ const decodedType = dataTypeDecoder.getDataTypeInfo(dataType);
1343
1793
  const bits = decodedType.byteLength * 8;
1344
- const components = channels.length;
1794
+ const components = channels?.length ?? 1;
1345
1795
  const bitsPerChannel = [
1346
1796
  bits,
1347
1797
  components >= 2 ? bits : 0,
@@ -1358,7 +1808,7 @@ var __exports__ = (() => {
1358
1808
  signed: decodedType.signed,
1359
1809
  normalized: decodedType.normalized,
1360
1810
  bitsPerChannel,
1361
- bytesPerPixel: decodedType.byteLength * channels.length,
1811
+ bytesPerPixel: decodedType.byteLength * components,
1362
1812
  packed: formatInfo.packed,
1363
1813
  srgb: formatInfo.srgb
1364
1814
  };
@@ -1414,10 +1864,31 @@ var __exports__ = (() => {
1414
1864
  const [, blockWidth, blockHeight] = matches;
1415
1865
  return { blockWidth: Number(blockWidth), blockHeight: Number(blockHeight) };
1416
1866
  }
1867
+ if (format.startsWith("bc") || format.startsWith("etc1") || format.startsWith("etc2") || format.startsWith("eac") || format.startsWith("atc")) {
1868
+ return { blockWidth: 4, blockHeight: 4 };
1869
+ }
1870
+ if (format.startsWith("pvrtc-rgb4") || format.startsWith("pvrtc-rgba4")) {
1871
+ return { blockWidth: 4, blockHeight: 4 };
1872
+ }
1873
+ if (format.startsWith("pvrtc-rgb2") || format.startsWith("pvrtc-rgba2")) {
1874
+ return { blockWidth: 8, blockHeight: 4 };
1875
+ }
1417
1876
  return null;
1418
1877
  }
1878
+ function getCompressedTextureBlockByteLength(format) {
1879
+ if (format.startsWith("bc1") || format.startsWith("bc4") || format.startsWith("etc1") || format.startsWith("etc2-rgb8") || format.startsWith("etc2-rgb8a1") || format.startsWith("eac-r11") || format === "atc-rgb-unorm-webgl") {
1880
+ return 8;
1881
+ }
1882
+ if (format.startsWith("bc2") || format.startsWith("bc3") || format.startsWith("bc5") || format.startsWith("bc6h") || format.startsWith("bc7") || format.startsWith("etc2-rgba8") || format.startsWith("eac-rg11") || format.startsWith("astc") || format === "atc-rgba-unorm-webgl" || format === "atc-rgbai-unorm-webgl") {
1883
+ return 16;
1884
+ }
1885
+ if (format.startsWith("pvrtc")) {
1886
+ return 8;
1887
+ }
1888
+ return 16;
1889
+ }
1419
1890
 
1420
- // src/image-utils/image-types.ts
1891
+ // src/shadertypes/image-types/image-types.ts
1421
1892
  function isExternalImage(data) {
1422
1893
  return typeof ImageData !== "undefined" && data instanceof ImageData || typeof ImageBitmap !== "undefined" && data instanceof ImageBitmap || typeof HTMLImageElement !== "undefined" && data instanceof HTMLImageElement || typeof HTMLVideoElement !== "undefined" && data instanceof HTMLVideoElement || typeof VideoFrame !== "undefined" && data instanceof VideoFrame || typeof HTMLCanvasElement !== "undefined" && data instanceof HTMLCanvasElement || typeof OffscreenCanvas !== "undefined" && data instanceof OffscreenCanvas;
1423
1894
  }
@@ -1440,6 +1911,52 @@ var __exports__ = (() => {
1440
1911
  // src/adapter/device.ts
1441
1912
  var DeviceLimits = class {
1442
1913
  };
1914
+ function formatErrorLogArguments(context, args) {
1915
+ const formattedContext = formatErrorLogValue(context);
1916
+ const formattedArgs = args.map(formatErrorLogValue).filter((arg) => arg !== void 0);
1917
+ return [formattedContext, ...formattedArgs].filter((arg) => arg !== void 0);
1918
+ }
1919
+ function formatErrorLogValue(value) {
1920
+ if (value === void 0) {
1921
+ return void 0;
1922
+ }
1923
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1924
+ return value;
1925
+ }
1926
+ if (value instanceof Error) {
1927
+ return value.message;
1928
+ }
1929
+ if (Array.isArray(value)) {
1930
+ return value.map(formatErrorLogValue);
1931
+ }
1932
+ if (typeof value === "object") {
1933
+ if (hasCustomToString(value)) {
1934
+ const stringValue = String(value);
1935
+ if (stringValue !== "[object Object]") {
1936
+ return stringValue;
1937
+ }
1938
+ }
1939
+ if (looksLikeGPUCompilationMessage(value)) {
1940
+ return formatGPUCompilationMessage(value);
1941
+ }
1942
+ return value.constructor?.name || "Object";
1943
+ }
1944
+ return String(value);
1945
+ }
1946
+ function hasCustomToString(value) {
1947
+ return "toString" in value && typeof value.toString === "function" && value.toString !== Object.prototype.toString;
1948
+ }
1949
+ function looksLikeGPUCompilationMessage(value) {
1950
+ return "message" in value && "type" in value;
1951
+ }
1952
+ function formatGPUCompilationMessage(value) {
1953
+ const type = typeof value.type === "string" ? value.type : "message";
1954
+ const message = typeof value.message === "string" ? value.message : "";
1955
+ const lineNum = typeof value.lineNum === "number" ? value.lineNum : null;
1956
+ const linePos = typeof value.linePos === "number" ? value.linePos : null;
1957
+ const location = lineNum !== null && linePos !== null ? ` @ ${lineNum}:${linePos}` : lineNum !== null ? ` @ ${lineNum}` : "";
1958
+ return `${type}${location}: ${message}`.trim();
1959
+ }
1443
1960
  var DeviceFeatures = class {
1444
1961
  features;
1445
1962
  disabledFeatures;
@@ -1469,19 +1986,24 @@ var __exports__ = (() => {
1469
1986
  userData = {};
1470
1987
  /** stats */
1471
1988
  statsManager = lumaStats;
1989
+ /** Internal per-device factory storage */
1990
+ _factories = {};
1472
1991
  /** An abstract timestamp used for change tracking */
1473
1992
  timestamp = 0;
1474
1993
  /** True if this device has been reused during device creation (app has multiple references) */
1475
1994
  _reused = false;
1476
1995
  /** Used by other luma.gl modules to store data on the device */
1477
- _lumaData = {};
1996
+ _moduleData = {};
1478
1997
  _textureCaps = {};
1998
+ /** Internal timestamp query set used when GPU timing collection is enabled for this device. */
1999
+ _debugGPUTimeQuery = null;
1479
2000
  constructor(props) {
1480
2001
  this.props = { ..._Device.defaultProps, ...props };
1481
2002
  this.id = this.props.id || uid(this[Symbol.toStringTag].toLowerCase());
1482
2003
  }
2004
+ // TODO - just expose the shadertypes decoders?
1483
2005
  getVertexFormatInfo(format) {
1484
- return getVertexFormatInfo(format);
2006
+ return vertexFormatDecoder.getVertexFormatInfo(format);
1485
2007
  }
1486
2008
  isVertexFormatSupported(format) {
1487
2009
  return true;
@@ -1529,6 +2051,16 @@ var __exports__ = (() => {
1529
2051
  isTextureFormatCompressed(format) {
1530
2052
  return textureFormatDecoder.isCompressed(format);
1531
2053
  }
2054
+ /** Returns the compressed texture formats that can be created and sampled on this device */
2055
+ getSupportedCompressedTextureFormats() {
2056
+ const supportedFormats = [];
2057
+ for (const format of Object.keys(getTextureFormatTable())) {
2058
+ if (this.isTextureFormatCompressed(format) && this.isTextureFormatSupported(format)) {
2059
+ supportedFormats.push(format);
2060
+ }
2061
+ }
2062
+ return supportedFormats;
2063
+ }
1532
2064
  // DEBUG METHODS
1533
2065
  pushDebugGroup(groupLabel) {
1534
2066
  this.commandEncoder.pushDebugGroup(groupLabel);
@@ -1571,7 +2103,13 @@ var __exports__ = (() => {
1571
2103
  reportError(error, context, ...args) {
1572
2104
  const isHandled = this.props.onError(error, context);
1573
2105
  if (!isHandled) {
1574
- return log.error(error.message, context, ...args);
2106
+ const logArguments = formatErrorLogArguments(context, args);
2107
+ return log.error(
2108
+ this.type === "webgl" ? "%cWebGL" : "%cWebGPU",
2109
+ "color: white; background: red; padding: 2px 6px; border-radius: 3px;",
2110
+ error.message,
2111
+ ...logArguments
2112
+ );
1575
2113
  }
1576
2114
  return () => {
1577
2115
  };
@@ -1593,6 +2131,10 @@ or create a device with the 'debug: true' prop.`;
1593
2131
  }
1594
2132
  return this.canvasContext;
1595
2133
  }
2134
+ /** Create a fence sync object */
2135
+ createFence() {
2136
+ throw new Error("createFence() not implemented");
2137
+ }
1596
2138
  /** Create a RenderPass using the default CommandEncoder */
1597
2139
  beginRenderPass(props) {
1598
2140
  return this.commandEncoder.beginRenderPass(props);
@@ -1601,6 +2143,78 @@ or create a device with the 'debug: true' prop.`;
1601
2143
  beginComputePass(props) {
1602
2144
  return this.commandEncoder.beginComputePass(props);
1603
2145
  }
2146
+ /**
2147
+ * Generate mipmaps for a WebGPU texture.
2148
+ * WebGPU textures must be created up front with the required mip count, usage flags, and a format that supports the chosen generation path.
2149
+ * WebGL uses `Texture.generateMipmapsWebGL()` directly because the backend manages mip generation on the texture object itself.
2150
+ */
2151
+ generateMipmapsWebGPU(_texture) {
2152
+ throw new Error("not implemented");
2153
+ }
2154
+ /** Internal helper for creating a shareable WebGL render-pipeline implementation. */
2155
+ _createSharedRenderPipelineWebGL(_props) {
2156
+ throw new Error("_createSharedRenderPipelineWebGL() not implemented");
2157
+ }
2158
+ /** Internal WebGPU-only helper for retrieving the native bind-group layout for a pipeline group. */
2159
+ _createBindGroupLayoutWebGPU(_pipeline, _group) {
2160
+ throw new Error("_createBindGroupLayoutWebGPU() not implemented");
2161
+ }
2162
+ /** Internal WebGPU-only helper for creating a native bind group. */
2163
+ _createBindGroupWebGPU(_bindGroupLayout, _shaderLayout, _bindings, _group) {
2164
+ throw new Error("_createBindGroupWebGPU() not implemented");
2165
+ }
2166
+ /**
2167
+ * Internal helper that returns `true` when timestamp-query GPU timing should be
2168
+ * collected for this device.
2169
+ */
2170
+ _supportsDebugGPUTime() {
2171
+ return this.features.has("timestamp-query") && Boolean(this.props.debug || this.props.debugGPUTime);
2172
+ }
2173
+ /**
2174
+ * Internal helper that enables device-managed GPU timing collection on the
2175
+ * default command encoder. Reuses the existing query set if timing is already enabled.
2176
+ *
2177
+ * @param queryCount - Number of timestamp slots reserved for profiled passes.
2178
+ * @returns The device-managed timestamp QuerySet, or `null` when timing is not supported or could not be enabled.
2179
+ */
2180
+ _enableDebugGPUTime(queryCount = 256) {
2181
+ if (!this._supportsDebugGPUTime()) {
2182
+ return null;
2183
+ }
2184
+ if (this._debugGPUTimeQuery) {
2185
+ return this._debugGPUTimeQuery;
2186
+ }
2187
+ try {
2188
+ this._debugGPUTimeQuery = this.createQuerySet({ type: "timestamp", count: queryCount });
2189
+ this.commandEncoder = this.createCommandEncoder({
2190
+ id: this.commandEncoder.props.id,
2191
+ timeProfilingQuerySet: this._debugGPUTimeQuery
2192
+ });
2193
+ } catch {
2194
+ this._debugGPUTimeQuery = null;
2195
+ }
2196
+ return this._debugGPUTimeQuery;
2197
+ }
2198
+ /**
2199
+ * Internal helper that disables device-managed GPU timing collection and restores
2200
+ * the default command encoder to an unprofiled state.
2201
+ */
2202
+ _disableDebugGPUTime() {
2203
+ if (!this._debugGPUTimeQuery) {
2204
+ return;
2205
+ }
2206
+ if (this.commandEncoder.getTimeProfilingQuerySet() === this._debugGPUTimeQuery) {
2207
+ this.commandEncoder = this.createCommandEncoder({
2208
+ id: this.commandEncoder.props.id
2209
+ });
2210
+ }
2211
+ this._debugGPUTimeQuery.destroy();
2212
+ this._debugGPUTimeQuery = null;
2213
+ }
2214
+ /** Internal helper that returns `true` when device-managed GPU timing is currently active. */
2215
+ _isDebugGPUTimeEnabled() {
2216
+ return this._debugGPUTimeQuery !== null;
2217
+ }
1604
2218
  // DEPRECATED METHODS
1605
2219
  /** @deprecated Use getDefaultCanvasContext() */
1606
2220
  getCanvasContext() {
@@ -1636,6 +2250,12 @@ or create a device with the 'debug: true' prop.`;
1636
2250
  resetWebGL() {
1637
2251
  throw new Error("not implemented");
1638
2252
  }
2253
+ // INTERNAL LUMA.GL METHODS
2254
+ getModuleData(moduleName) {
2255
+ this._moduleData[moduleName] ||= {};
2256
+ return this._moduleData[moduleName];
2257
+ }
2258
+ // INTERNAL HELPERS
1639
2259
  // IMPLEMENTATION
1640
2260
  /** Helper to get the canvas context props */
1641
2261
  static _getCanvasContextProps(props) {
@@ -1702,7 +2322,8 @@ or create a device with the 'debug: true' prop.`;
1702
2322
  onVisibilityChange: (context) => log.log(1, `${context} Visibility changed ${context.isVisible}`)(),
1703
2323
  onDevicePixelRatioChange: (context, info) => log.log(1, `${context} DPR changed ${info.oldRatio} => ${context.devicePixelRatio}`)(),
1704
2324
  // Debug flags
1705
- debug: log.get("debug") || void 0,
2325
+ debug: getDefaultDebugValue(),
2326
+ debugGPUTime: false,
1706
2327
  debugShaders: log.get("debug-shaders") || void 0,
1707
2328
  debugFramebuffers: Boolean(log.get("debug-framebuffers")),
1708
2329
  debugFactories: Boolean(log.get("debug-factories")),
@@ -1713,9 +2334,11 @@ or create a device with the 'debug: true' prop.`;
1713
2334
  // Experimental
1714
2335
  _reuseDevices: false,
1715
2336
  _requestMaxLimits: true,
1716
- _cacheShaders: false,
1717
- _cachePipelines: false,
1718
- _cacheDestroyPolicy: "unused",
2337
+ _cacheShaders: true,
2338
+ _destroyShaders: false,
2339
+ _cachePipelines: true,
2340
+ _sharePipelines: true,
2341
+ _destroyPipelines: false,
1719
2342
  // TODO - Change these after confirming things work as expected
1720
2343
  _initializeFeatures: true,
1721
2344
  _disabledFeatures: {
@@ -1724,6 +2347,25 @@ or create a device with the 'debug: true' prop.`;
1724
2347
  // INTERNAL
1725
2348
  _handle: void 0
1726
2349
  });
2350
+ function _getDefaultDebugValue(logDebugValue, nodeEnv) {
2351
+ if (logDebugValue !== void 0 && logDebugValue !== null) {
2352
+ return Boolean(logDebugValue);
2353
+ }
2354
+ if (nodeEnv !== void 0) {
2355
+ return nodeEnv !== "production";
2356
+ }
2357
+ return false;
2358
+ }
2359
+ function getDefaultDebugValue() {
2360
+ return _getDefaultDebugValue(log.get("debug"), getNodeEnv());
2361
+ }
2362
+ function getNodeEnv() {
2363
+ const processObject = globalThis.process;
2364
+ if (!processObject?.env) {
2365
+ return void 0;
2366
+ }
2367
+ return processObject.env["NODE_ENV"];
2368
+ }
1727
2369
 
1728
2370
  // src/adapter/luma.ts
1729
2371
  var STARTUP_MESSAGE = "set luma.log.level=1 (or higher) to trace rendering";
@@ -1901,6 +2543,100 @@ or create a device with the 'debug: true' prop.`;
1901
2543
  return pageLoadPromise;
1902
2544
  }
1903
2545
 
2546
+ // src/adapter/canvas-observer.ts
2547
+ var CanvasObserver = class {
2548
+ props;
2549
+ _resizeObserver;
2550
+ _intersectionObserver;
2551
+ _observeDevicePixelRatioTimeout = null;
2552
+ _observeDevicePixelRatioMediaQuery = null;
2553
+ _handleDevicePixelRatioChange = () => this._refreshDevicePixelRatio();
2554
+ _trackPositionInterval = null;
2555
+ _started = false;
2556
+ get started() {
2557
+ return this._started;
2558
+ }
2559
+ constructor(props) {
2560
+ this.props = props;
2561
+ }
2562
+ start() {
2563
+ if (this._started || !this.props.canvas) {
2564
+ return;
2565
+ }
2566
+ this._started = true;
2567
+ this._intersectionObserver ||= new IntersectionObserver(
2568
+ (entries) => this.props.onIntersection(entries)
2569
+ );
2570
+ this._resizeObserver ||= new ResizeObserver((entries) => this.props.onResize(entries));
2571
+ this._intersectionObserver.observe(this.props.canvas);
2572
+ try {
2573
+ this._resizeObserver.observe(this.props.canvas, { box: "device-pixel-content-box" });
2574
+ } catch {
2575
+ this._resizeObserver.observe(this.props.canvas, { box: "content-box" });
2576
+ }
2577
+ this._observeDevicePixelRatioTimeout = setTimeout(() => this._refreshDevicePixelRatio(), 0);
2578
+ if (this.props.trackPosition) {
2579
+ this._trackPosition();
2580
+ }
2581
+ }
2582
+ stop() {
2583
+ if (!this._started) {
2584
+ return;
2585
+ }
2586
+ this._started = false;
2587
+ if (this._observeDevicePixelRatioTimeout) {
2588
+ clearTimeout(this._observeDevicePixelRatioTimeout);
2589
+ this._observeDevicePixelRatioTimeout = null;
2590
+ }
2591
+ if (this._observeDevicePixelRatioMediaQuery) {
2592
+ this._observeDevicePixelRatioMediaQuery.removeEventListener(
2593
+ "change",
2594
+ this._handleDevicePixelRatioChange
2595
+ );
2596
+ this._observeDevicePixelRatioMediaQuery = null;
2597
+ }
2598
+ if (this._trackPositionInterval) {
2599
+ clearInterval(this._trackPositionInterval);
2600
+ this._trackPositionInterval = null;
2601
+ }
2602
+ this._resizeObserver?.disconnect();
2603
+ this._intersectionObserver?.disconnect();
2604
+ }
2605
+ _refreshDevicePixelRatio() {
2606
+ if (!this._started) {
2607
+ return;
2608
+ }
2609
+ this.props.onDevicePixelRatioChange();
2610
+ this._observeDevicePixelRatioMediaQuery?.removeEventListener(
2611
+ "change",
2612
+ this._handleDevicePixelRatioChange
2613
+ );
2614
+ this._observeDevicePixelRatioMediaQuery = matchMedia(
2615
+ `(resolution: ${window.devicePixelRatio}dppx)`
2616
+ );
2617
+ this._observeDevicePixelRatioMediaQuery.addEventListener(
2618
+ "change",
2619
+ this._handleDevicePixelRatioChange,
2620
+ { once: true }
2621
+ );
2622
+ }
2623
+ _trackPosition(intervalMs = 100) {
2624
+ if (this._trackPositionInterval) {
2625
+ return;
2626
+ }
2627
+ this._trackPositionInterval = setInterval(() => {
2628
+ if (!this._started) {
2629
+ if (this._trackPositionInterval) {
2630
+ clearInterval(this._trackPositionInterval);
2631
+ this._trackPositionInterval = null;
2632
+ }
2633
+ } else {
2634
+ this.props.onPositionChange();
2635
+ }
2636
+ }, intervalMs);
2637
+ }
2638
+ };
2639
+
1904
2640
  // src/utils/promise-utils.ts
1905
2641
  function withResolvers() {
1906
2642
  let resolve;
@@ -1912,8 +2648,21 @@ or create a device with the 'debug: true' prop.`;
1912
2648
  return { promise, resolve, reject };
1913
2649
  }
1914
2650
 
1915
- // src/adapter/canvas-context.ts
1916
- var _CanvasContext = class {
2651
+ // src/utils/assert.ts
2652
+ function assert2(condition, message) {
2653
+ if (!condition) {
2654
+ const error = new Error(message ?? "luma.gl assertion failed.");
2655
+ Error.captureStackTrace?.(error, assert2);
2656
+ throw error;
2657
+ }
2658
+ }
2659
+ function assertDefined(value, message) {
2660
+ assert2(value, message);
2661
+ return value;
2662
+ }
2663
+
2664
+ // src/adapter/canvas-surface.ts
2665
+ var _CanvasSurface = class {
1917
2666
  static isHTMLCanvas(canvas) {
1918
2667
  return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
1919
2668
  }
@@ -1947,16 +2696,20 @@ or create a device with the 'debug: true' prop.`;
1947
2696
  drawingBufferWidth;
1948
2697
  /** Height of drawing buffer: automatically tracks this.pixelHeight if props.autoResize is true */
1949
2698
  drawingBufferHeight;
2699
+ /** Resolves when the canvas is initialized, i.e. when the ResizeObserver has updated the pixel size */
1950
2700
  _initializedResolvers = withResolvers();
1951
- _resizeObserver;
1952
- _intersectionObserver;
1953
- _position;
2701
+ _canvasObserver;
2702
+ /** Position of the canvas in the document, updated by a timer */
2703
+ _position = [0, 0];
2704
+ /** Whether this canvas context has been destroyed */
1954
2705
  destroyed = false;
2706
+ /** Whether the drawing buffer size needs to be resized (deferred resizing to avoid flicker) */
2707
+ _needsDrawingBufferResize = true;
1955
2708
  toString() {
1956
2709
  return `${this[Symbol.toStringTag]}(${this.id})`;
1957
2710
  }
1958
2711
  constructor(props) {
1959
- this.props = { ..._CanvasContext.defaultProps, ...props };
2712
+ this.props = { ..._CanvasSurface.defaultProps, ...props };
1960
2713
  props = this.props;
1961
2714
  this.initialized = this._initializedResolvers.promise;
1962
2715
  if (!isBrowser()) {
@@ -1968,11 +2721,11 @@ or create a device with the 'debug: true' prop.`;
1968
2721
  } else {
1969
2722
  this.canvas = props.canvas;
1970
2723
  }
1971
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2724
+ if (_CanvasSurface.isHTMLCanvas(this.canvas)) {
1972
2725
  this.id = props.id || this.canvas.id;
1973
2726
  this.type = "html-canvas";
1974
2727
  this.htmlCanvas = this.canvas;
1975
- } else if (_CanvasContext.isOffscreenCanvas(this.canvas)) {
2728
+ } else if (_CanvasSurface.isOffscreenCanvas(this.canvas)) {
1976
2729
  this.id = props.id || "offscreen-canvas";
1977
2730
  this.type = "offscreen-canvas";
1978
2731
  this.offscreenCanvas = this.canvas;
@@ -1988,25 +2741,21 @@ or create a device with the 'debug: true' prop.`;
1988
2741
  this.drawingBufferHeight = this.canvas.height;
1989
2742
  this.devicePixelRatio = globalThis.devicePixelRatio || 1;
1990
2743
  this._position = [0, 0];
1991
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
1992
- this._intersectionObserver = new IntersectionObserver(
1993
- (entries) => this._handleIntersection(entries)
1994
- );
1995
- this._intersectionObserver.observe(this.canvas);
1996
- this._resizeObserver = new ResizeObserver((entries) => this._handleResize(entries));
1997
- try {
1998
- this._resizeObserver.observe(this.canvas, { box: "device-pixel-content-box" });
1999
- } catch {
2000
- this._resizeObserver.observe(this.canvas, { box: "content-box" });
2001
- }
2002
- setTimeout(() => this._observeDevicePixelRatio(), 0);
2003
- if (this.props.trackPosition) {
2004
- this._trackPosition();
2005
- }
2006
- }
2744
+ this._canvasObserver = new CanvasObserver({
2745
+ canvas: this.htmlCanvas,
2746
+ trackPosition: this.props.trackPosition,
2747
+ onResize: (entries) => this._handleResize(entries),
2748
+ onIntersection: (entries) => this._handleIntersection(entries),
2749
+ onDevicePixelRatioChange: () => this._observeDevicePixelRatio(),
2750
+ onPositionChange: () => this.updatePosition()
2751
+ });
2007
2752
  }
2008
2753
  destroy() {
2009
- this.destroyed = true;
2754
+ if (!this.destroyed) {
2755
+ this.destroyed = true;
2756
+ this._stopObservers();
2757
+ this.device = null;
2758
+ }
2010
2759
  }
2011
2760
  setProps(props) {
2012
2761
  if ("useDevicePixels" in props) {
@@ -2015,55 +2764,41 @@ or create a device with the 'debug: true' prop.`;
2015
2764
  }
2016
2765
  return this;
2017
2766
  }
2018
- // SIZE METHODS
2019
- /**
2020
- * Returns the size covered by the canvas in CSS pixels
2021
- * @note This can be different from the actual device pixel size of a canvas due to DPR scaling, and rounding to integer pixels
2022
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2023
- */
2767
+ /** Returns a framebuffer with properly resized current 'swap chain' textures */
2768
+ getCurrentFramebuffer(options) {
2769
+ this._resizeDrawingBufferIfNeeded();
2770
+ return this._getCurrentFramebuffer(options);
2771
+ }
2024
2772
  getCSSSize() {
2025
2773
  return [this.cssWidth, this.cssHeight];
2026
2774
  }
2027
2775
  getPosition() {
2028
2776
  return this._position;
2029
2777
  }
2030
- /**
2031
- * Returns the size covered by the canvas in actual device pixels.
2032
- * @note This can be different from the 'CSS' size of a canvas due to DPR scaling, and rounding to integer pixels
2033
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2034
- */
2035
2778
  getDevicePixelSize() {
2036
2779
  return [this.devicePixelWidth, this.devicePixelHeight];
2037
2780
  }
2038
- /** Get the drawing buffer size (number of pixels GPU is rendering into, can be different from CSS size) */
2039
2781
  getDrawingBufferSize() {
2040
2782
  return [this.drawingBufferWidth, this.drawingBufferHeight];
2041
2783
  }
2042
- /** Returns the biggest allowed framebuffer size. @todo Allow the application to limit this? */
2043
2784
  getMaxDrawingBufferSize() {
2044
2785
  const maxTextureDimension = this.device.limits.maxTextureDimension2D;
2045
2786
  return [maxTextureDimension, maxTextureDimension];
2046
2787
  }
2047
- /** Update the canvas drawing buffer size. Called automatically if props.autoResize is true. */
2048
2788
  setDrawingBufferSize(width, height) {
2049
- this.canvas.width = width;
2050
- this.canvas.height = height;
2789
+ width = Math.floor(width);
2790
+ height = Math.floor(height);
2791
+ if (this.drawingBufferWidth === width && this.drawingBufferHeight === height) {
2792
+ return;
2793
+ }
2051
2794
  this.drawingBufferWidth = width;
2052
2795
  this.drawingBufferHeight = height;
2796
+ this._needsDrawingBufferResize = true;
2053
2797
  }
2054
- /**
2055
- * Returns the current DPR (number of physical pixels per CSS pixel), if props.useDevicePixels is true
2056
- * @note This can be a fractional (non-integer) number, e.g. when the user zooms in the browser.
2057
- * @note This function handles the non-HTML canvas cases
2058
- */
2059
2798
  getDevicePixelRatio() {
2060
- const dpr = typeof window !== "undefined" && window.devicePixelRatio;
2061
- return dpr || 1;
2799
+ const devicePixelRatio2 = typeof window !== "undefined" && window.devicePixelRatio;
2800
+ return devicePixelRatio2 || 1;
2062
2801
  }
2063
- // DEPRECATED METHODS
2064
- /**
2065
- * Maps CSS pixel position to device pixel position
2066
- */
2067
2802
  cssToDevicePixels(cssPixel, yInvert = true) {
2068
2803
  const ratio = this.cssToDeviceRatio();
2069
2804
  const [width, height] = this.getDrawingBufferSize();
@@ -2073,10 +2808,10 @@ or create a device with the 'debug: true' prop.`;
2073
2808
  getPixelSize() {
2074
2809
  return this.getDevicePixelSize();
2075
2810
  }
2076
- /** @deprecated - TODO which values should we use for aspect */
2811
+ /** @deprecated Use the current drawing buffer size for projection setup. */
2077
2812
  getAspect() {
2078
- const [width, height] = this.getDevicePixelSize();
2079
- return width / height;
2813
+ const [width, height] = this.getDrawingBufferSize();
2814
+ return width > 0 && height > 0 ? width / height : 1;
2080
2815
  }
2081
2816
  /** @deprecated Returns multiplier need to convert CSS size to Device size */
2082
2817
  cssToDeviceRatio() {
@@ -2092,18 +2827,40 @@ or create a device with the 'debug: true' prop.`;
2092
2827
  resize(size) {
2093
2828
  this.setDrawingBufferSize(size.width, size.height);
2094
2829
  }
2095
- // IMPLEMENTATION
2096
- /**
2097
- * Allows subclass constructor to override the canvas id for auto created canvases.
2098
- * This can really help when debugging DOM in apps that create multiple devices
2099
- */
2100
2830
  _setAutoCreatedCanvasId(id) {
2101
2831
  if (this.htmlCanvas?.id === "lumagl-auto-created-canvas") {
2102
2832
  this.htmlCanvas.id = id;
2103
2833
  }
2104
2834
  }
2105
- /** reacts to an observed intersection */
2835
+ /**
2836
+ * Starts DOM observation after the derived context and its device are fully initialized.
2837
+ *
2838
+ * `CanvasSurface` construction runs before subclasses can assign `this.device`, and the
2839
+ * default WebGL canvas context is created before `WebGLDevice` has initialized `limits`,
2840
+ * `features`, and the rest of its runtime state. Deferring observer startup avoids early
2841
+ * `ResizeObserver` and DPR callbacks running against a partially initialized device.
2842
+ */
2843
+ _startObservers() {
2844
+ if (this.destroyed) {
2845
+ return;
2846
+ }
2847
+ this._canvasObserver.start();
2848
+ }
2849
+ /**
2850
+ * Stops all DOM observation and timers associated with a canvas surface.
2851
+ *
2852
+ * This pairs with `_startObservers()` so teardown uses the same lifecycle whether a context is
2853
+ * explicitly destroyed, abandoned during device reuse, or temporarily has not started observing
2854
+ * yet. Centralizing shutdown here keeps resize/DPR/position watchers from surviving past the
2855
+ * lifetime of the owning device.
2856
+ */
2857
+ _stopObservers() {
2858
+ this._canvasObserver.stop();
2859
+ }
2106
2860
  _handleIntersection(entries) {
2861
+ if (this.destroyed) {
2862
+ return;
2863
+ }
2107
2864
  const entry = entries.find((entry_) => entry_.target === this.canvas);
2108
2865
  if (!entry) {
2109
2866
  return;
@@ -2114,21 +2871,20 @@ or create a device with the 'debug: true' prop.`;
2114
2871
  this.device.props.onVisibilityChange(this);
2115
2872
  }
2116
2873
  }
2117
- /**
2118
- * Reacts to an observed resize by using the most accurate pixel size information the browser can provide
2119
- * @see https://web.dev/articles/device-pixel-content-box
2120
- * @see https://webgpufundamentals.org/webgpu/lessons/webgpu-resizing-the-canvas.html
2121
- */
2122
2874
  _handleResize(entries) {
2875
+ if (this.destroyed) {
2876
+ return;
2877
+ }
2123
2878
  const entry = entries.find((entry_) => entry_.target === this.canvas);
2124
2879
  if (!entry) {
2125
2880
  return;
2126
2881
  }
2127
- this.cssWidth = entry.contentBoxSize[0].inlineSize;
2128
- this.cssHeight = entry.contentBoxSize[0].blockSize;
2882
+ const contentBoxSize = assertDefined(entry.contentBoxSize?.[0]);
2883
+ this.cssWidth = contentBoxSize.inlineSize;
2884
+ this.cssHeight = contentBoxSize.blockSize;
2129
2885
  const oldPixelSize = this.getDevicePixelSize();
2130
- const devicePixelWidth = entry.devicePixelContentBoxSize?.[0].inlineSize || entry.contentBoxSize[0].inlineSize * devicePixelRatio;
2131
- const devicePixelHeight = entry.devicePixelContentBoxSize?.[0].blockSize || entry.contentBoxSize[0].blockSize * devicePixelRatio;
2886
+ const devicePixelWidth = entry.devicePixelContentBoxSize?.[0]?.inlineSize || contentBoxSize.inlineSize * devicePixelRatio;
2887
+ const devicePixelHeight = entry.devicePixelContentBoxSize?.[0]?.blockSize || contentBoxSize.blockSize * devicePixelRatio;
2132
2888
  const [maxDevicePixelWidth, maxDevicePixelHeight] = this.getMaxDrawingBufferSize();
2133
2889
  this.devicePixelWidth = Math.max(1, Math.min(devicePixelWidth, maxDevicePixelWidth));
2134
2890
  this.devicePixelHeight = Math.max(1, Math.min(devicePixelHeight, maxDevicePixelHeight));
@@ -2138,47 +2894,47 @@ or create a device with the 'debug: true' prop.`;
2138
2894
  _updateDrawingBufferSize() {
2139
2895
  if (this.props.autoResize) {
2140
2896
  if (typeof this.props.useDevicePixels === "number") {
2141
- const dpr = this.props.useDevicePixels;
2142
- this.setDrawingBufferSize(this.cssWidth * dpr, this.cssHeight * dpr);
2897
+ const devicePixelRatio2 = this.props.useDevicePixels;
2898
+ this.setDrawingBufferSize(
2899
+ this.cssWidth * devicePixelRatio2,
2900
+ this.cssHeight * devicePixelRatio2
2901
+ );
2143
2902
  } else if (this.props.useDevicePixels) {
2144
2903
  this.setDrawingBufferSize(this.devicePixelWidth, this.devicePixelHeight);
2145
2904
  } else {
2146
2905
  this.setDrawingBufferSize(this.cssWidth, this.cssHeight);
2147
2906
  }
2148
- this._updateDevice();
2149
2907
  }
2150
2908
  this._initializedResolvers.resolve();
2151
2909
  this.isInitialized = true;
2152
2910
  this.updatePosition();
2153
2911
  }
2154
- /** Monitor DPR changes */
2912
+ _resizeDrawingBufferIfNeeded() {
2913
+ if (this._needsDrawingBufferResize) {
2914
+ this._needsDrawingBufferResize = false;
2915
+ const sizeChanged = this.drawingBufferWidth !== this.canvas.width || this.drawingBufferHeight !== this.canvas.height;
2916
+ if (sizeChanged) {
2917
+ this.canvas.width = this.drawingBufferWidth;
2918
+ this.canvas.height = this.drawingBufferHeight;
2919
+ this._configureDevice();
2920
+ }
2921
+ }
2922
+ }
2155
2923
  _observeDevicePixelRatio() {
2924
+ if (this.destroyed || !this._canvasObserver.started) {
2925
+ return;
2926
+ }
2156
2927
  const oldRatio = this.devicePixelRatio;
2157
2928
  this.devicePixelRatio = window.devicePixelRatio;
2158
2929
  this.updatePosition();
2159
- this.device.props.onDevicePixelRatioChange(this, { oldRatio });
2160
- matchMedia(`(resolution: ${this.devicePixelRatio}dppx)`).addEventListener(
2161
- "change",
2162
- () => this._observeDevicePixelRatio(),
2163
- { once: true }
2164
- );
2165
- }
2166
- /** Start tracking positions with a timer */
2167
- _trackPosition(intervalMs = 100) {
2168
- const intervalId = setInterval(() => {
2169
- if (this.destroyed) {
2170
- clearInterval(intervalId);
2171
- } else {
2172
- this.updatePosition();
2173
- }
2174
- }, intervalMs);
2930
+ this.device.props.onDevicePixelRatioChange?.(this, {
2931
+ oldRatio
2932
+ });
2175
2933
  }
2176
- /**
2177
- * Calculated the absolute position of the canvas
2178
- * @note - getBoundingClientRect() is normally cheap but can be expensive
2179
- * if called before browser has finished a reflow. Should not be the case here.
2180
- */
2181
2934
  updatePosition() {
2935
+ if (this.destroyed) {
2936
+ return;
2937
+ }
2182
2938
  const newRect = this.htmlCanvas?.getBoundingClientRect();
2183
2939
  if (newRect) {
2184
2940
  const position = [newRect.left, newRect.top];
@@ -2187,13 +2943,15 @@ or create a device with the 'debug: true' prop.`;
2187
2943
  if (positionChanged) {
2188
2944
  const oldPosition = this._position;
2189
2945
  this._position = position;
2190
- this.device.props.onPositionChange?.(this, { oldPosition });
2946
+ this.device.props.onPositionChange?.(this, {
2947
+ oldPosition
2948
+ });
2191
2949
  }
2192
2950
  }
2193
2951
  }
2194
2952
  };
2195
- var CanvasContext = _CanvasContext;
2196
- __publicField(CanvasContext, "defaultProps", {
2953
+ var CanvasSurface = _CanvasSurface;
2954
+ __publicField(CanvasSurface, "defaultProps", {
2197
2955
  id: void 0,
2198
2956
  canvas: null,
2199
2957
  width: 800,
@@ -2221,7 +2979,7 @@ or create a device with the 'debug: true' prop.`;
2221
2979
  }
2222
2980
  function getCanvasFromDOM(canvasId) {
2223
2981
  const canvas = document.getElementById(canvasId);
2224
- if (!CanvasContext.isHTMLCanvas(canvas)) {
2982
+ if (!CanvasSurface.isHTMLCanvas(canvas)) {
2225
2983
  throw new Error("Object is not a canvas element");
2226
2984
  }
2227
2985
  return canvas;
@@ -2245,33 +3003,40 @@ or create a device with the 'debug: true' prop.`;
2245
3003
  const point = pixel;
2246
3004
  const x = scaleX(point[0], ratio, width);
2247
3005
  let y = scaleY(point[1], ratio, height, yInvert);
2248
- let t = scaleX(point[0] + 1, ratio, width);
2249
- const xHigh = t === width - 1 ? t : t - 1;
2250
- t = scaleY(point[1] + 1, ratio, height, yInvert);
3006
+ let temporary = scaleX(point[0] + 1, ratio, width);
3007
+ const xHigh = temporary === width - 1 ? temporary : temporary - 1;
3008
+ temporary = scaleY(point[1] + 1, ratio, height, yInvert);
2251
3009
  let yHigh;
2252
3010
  if (yInvert) {
2253
- t = t === 0 ? t : t + 1;
3011
+ temporary = temporary === 0 ? temporary : temporary + 1;
2254
3012
  yHigh = y;
2255
- y = t;
3013
+ y = temporary;
2256
3014
  } else {
2257
- yHigh = t === height - 1 ? t : t - 1;
3015
+ yHigh = temporary === height - 1 ? temporary : temporary - 1;
2258
3016
  }
2259
3017
  return {
2260
3018
  x,
2261
3019
  y,
2262
- // when ratio < 1, current css pixel and next css pixel may point to same device pixel, set width/height to 1 in those cases.
2263
3020
  width: Math.max(xHigh - x + 1, 1),
2264
3021
  height: Math.max(yHigh - y + 1, 1)
2265
3022
  };
2266
3023
  }
2267
3024
  function scaleX(x, ratio, width) {
2268
- const r = Math.min(Math.round(x * ratio), width - 1);
2269
- return r;
3025
+ return Math.min(Math.round(x * ratio), width - 1);
2270
3026
  }
2271
3027
  function scaleY(y, ratio, height, yInvert) {
2272
3028
  return yInvert ? Math.max(0, height - 1 - Math.round(y * ratio)) : Math.min(Math.round(y * ratio), height - 1);
2273
3029
  }
2274
3030
 
3031
+ // src/adapter/canvas-context.ts
3032
+ var CanvasContext = class extends CanvasSurface {
3033
+ };
3034
+ __publicField(CanvasContext, "defaultProps", CanvasSurface.defaultProps);
3035
+
3036
+ // src/adapter/presentation-context.ts
3037
+ var PresentationContext = class extends CanvasSurface {
3038
+ };
3039
+
2275
3040
  // src/adapter/resources/sampler.ts
2276
3041
  var _Sampler = class extends Resource {
2277
3042
  get [Symbol.toStringTag]() {
@@ -2326,7 +3091,15 @@ or create a device with the 'debug: true' prop.`;
2326
3091
  depth;
2327
3092
  /** mip levels in this texture */
2328
3093
  mipLevels;
2329
- /** "Time" of last update. Monotonically increasing timestamp. TODO move to AsyncTexture? */
3094
+ /** sample count */
3095
+ samples;
3096
+ /** Rows are multiples of this length, padded with extra bytes if needed */
3097
+ byteAlignment;
3098
+ /** The ready promise is always resolved. It is provided for type compatibility with DynamicTexture. */
3099
+ ready = Promise.resolve(this);
3100
+ /** isReady is always true. It is provided for type compatibility with DynamicTexture. */
3101
+ isReady = true;
3102
+ /** "Time" of last update. Monotonically increasing timestamp. TODO move to DynamicTexture? */
2330
3103
  updateTimestamp;
2331
3104
  get [Symbol.toStringTag]() {
2332
3105
  return "Texture";
@@ -2335,7 +3108,7 @@ or create a device with the 'debug: true' prop.`;
2335
3108
  return `Texture(${this.id},${this.format},${this.width}x${this.height})`;
2336
3109
  }
2337
3110
  /** Do not use directly. Create with device.createTexture() */
2338
- constructor(device, props) {
3111
+ constructor(device, props, backendProps) {
2339
3112
  props = _Texture.normalizeProps(device, props);
2340
3113
  super(device, props, _Texture.defaultProps);
2341
3114
  this.dimension = this.props.dimension;
@@ -2345,6 +3118,10 @@ or create a device with the 'debug: true' prop.`;
2345
3118
  this.height = this.props.height;
2346
3119
  this.depth = this.props.depth;
2347
3120
  this.mipLevels = this.props.mipLevels;
3121
+ this.samples = this.props.samples || 1;
3122
+ if (this.dimension === "cube") {
3123
+ this.depth = 6;
3124
+ }
2348
3125
  if (this.props.width === void 0 || this.props.height === void 0) {
2349
3126
  if (device.isExternalImage(props.data)) {
2350
3127
  const size = device.getExternalImageSize(props.data);
@@ -2355,17 +3132,14 @@ or create a device with the 'debug: true' prop.`;
2355
3132
  this.height = 1;
2356
3133
  if (this.props.width === void 0 || this.props.height === void 0) {
2357
3134
  log.warn(
2358
- `${this} created with undefined width or height. This is deprecated. Use AsyncTexture instead.`
3135
+ `${this} created with undefined width or height. This is deprecated. Use DynamicTexture instead.`
2359
3136
  )();
2360
3137
  }
2361
3138
  }
2362
3139
  }
3140
+ this.byteAlignment = backendProps?.byteAlignment || 1;
2363
3141
  this.updateTimestamp = device.incrementTimestamp();
2364
3142
  }
2365
- /** Set sampler props associated with this texture */
2366
- setSampler(sampler) {
2367
- this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
2368
- }
2369
3143
  /**
2370
3144
  * Create a new texture with the same parameters and optionally a different size
2371
3145
  * @note Textures are immutable and cannot be resized after creation, but we can create a similar texture with the same parameters but a new size.
@@ -2374,6 +3148,105 @@ or create a device with the 'debug: true' prop.`;
2374
3148
  clone(size) {
2375
3149
  return this.device.createTexture({ ...this.props, ...size });
2376
3150
  }
3151
+ /** Set sampler props associated with this texture */
3152
+ setSampler(sampler) {
3153
+ this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
3154
+ }
3155
+ /**
3156
+ * Copy raw image data (bytes) into the texture.
3157
+ *
3158
+ * @note Deprecated compatibility wrapper over {@link writeData}.
3159
+ * @note Uses the same layout defaults and alignment rules as {@link writeData}.
3160
+ * @note Tightly packed CPU uploads can omit `bytesPerRow` and `rowsPerImage`.
3161
+ * @note If the CPU source rows are padded, pass explicit `bytesPerRow` and `rowsPerImage`.
3162
+ * @deprecated Use writeData()
3163
+ */
3164
+ copyImageData(options) {
3165
+ const { data, depth, ...writeOptions } = options;
3166
+ this.writeData(data, {
3167
+ ...writeOptions,
3168
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3169
+ });
3170
+ }
3171
+ /**
3172
+ * Calculates the memory layout of the texture, required when reading and writing data.
3173
+ * @return the backend-aligned linear layout, in particular bytesPerRow which includes any required padding for buffer copy/read paths
3174
+ */
3175
+ computeMemoryLayout(options_ = {}) {
3176
+ const options = this._normalizeTextureReadOptions(options_);
3177
+ const { width = this.width, height = this.height, depthOrArrayLayers = this.depth } = options;
3178
+ const { format, byteAlignment } = this;
3179
+ return textureFormatDecoder.computeMemoryLayout({
3180
+ format,
3181
+ width,
3182
+ height,
3183
+ depth: depthOrArrayLayers,
3184
+ byteAlignment
3185
+ });
3186
+ }
3187
+ /**
3188
+ * Read the contents of a texture into a GPU Buffer.
3189
+ * @returns A Buffer containing the texture data.
3190
+ *
3191
+ * @note The memory layout of the texture data is determined by the texture format and dimensions.
3192
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3193
+ * @note The application can call Buffer.readAsync() to read the returned buffer on the CPU.
3194
+ * @note The destination buffer must be supplied by the caller and must be large enough for the requested region.
3195
+ * @note On WebGPU this corresponds to a texture-to-buffer copy and uses buffer-copy alignment rules.
3196
+ * @note On WebGL, luma.gl emulates the same logical readback behavior.
3197
+ */
3198
+ readBuffer(options, buffer) {
3199
+ throw new Error("readBuffer not implemented");
3200
+ }
3201
+ /**
3202
+ * Reads data from a texture into an ArrayBuffer.
3203
+ * @returns An ArrayBuffer containing the texture data.
3204
+ *
3205
+ * @note The memory layout of the texture data is determined by the texture format and dimensions.
3206
+ * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3207
+ * @deprecated Use Texture.readBuffer() with an explicit destination buffer, or DynamicTexture.readAsync() for convenience readback.
3208
+ */
3209
+ readDataAsync(options) {
3210
+ throw new Error("readBuffer not implemented");
3211
+ }
3212
+ /**
3213
+ * Writes a GPU Buffer into a texture.
3214
+ *
3215
+ * @param buffer - Source GPU buffer.
3216
+ * @param options - Destination subresource, extent, and source layout options.
3217
+ * @note The memory layout of the texture data is determined by the texture format and dimensions.
3218
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3219
+ * @note On WebGPU this corresponds to a buffer-to-texture copy and uses buffer-copy alignment rules.
3220
+ * @note On WebGL, luma.gl emulates the same destination and layout semantics.
3221
+ */
3222
+ writeBuffer(buffer, options) {
3223
+ throw new Error("readBuffer not implemented");
3224
+ }
3225
+ /**
3226
+ * Writes an array buffer into a texture.
3227
+ *
3228
+ * @param data - Source texel data.
3229
+ * @param options - Destination subresource, extent, and source layout options.
3230
+ * @note If `bytesPerRow` and `rowsPerImage` are omitted, luma.gl computes a tightly packed CPU-memory layout for the requested region.
3231
+ * @note On WebGPU this corresponds to `GPUQueue.writeTexture()` and does not implicitly pad rows to 256 bytes.
3232
+ * @note On WebGL, padded CPU data is supported via the same `bytesPerRow` and `rowsPerImage` options.
3233
+ */
3234
+ writeData(data, options) {
3235
+ throw new Error("readBuffer not implemented");
3236
+ }
3237
+ // IMPLEMENTATION SPECIFIC
3238
+ /**
3239
+ * WebGL can read data synchronously.
3240
+ * @note While it is convenient, the performance penalty is very significant
3241
+ */
3242
+ readDataSyncWebGL(options) {
3243
+ throw new Error("readDataSyncWebGL not available");
3244
+ }
3245
+ /** Generate mipmaps (WebGL only) */
3246
+ generateMipmapsWebGL() {
3247
+ throw new Error("generateMipmapsWebGL not available");
3248
+ }
3249
+ // HELPERS
2377
3250
  /** Ensure we have integer coordinates */
2378
3251
  static normalizeProps(device, props) {
2379
3252
  const newProps = { ...props };
@@ -2386,7 +3259,6 @@ or create a device with the 'debug: true' prop.`;
2386
3259
  }
2387
3260
  return newProps;
2388
3261
  }
2389
- // HELPERS
2390
3262
  /** Initialize texture with supplied props */
2391
3263
  // eslint-disable-next-line max-statements
2392
3264
  _initializeData(data) {
@@ -2420,23 +3292,166 @@ or create a device with the 'debug: true' prop.`;
2420
3292
  }
2421
3293
  }
2422
3294
  _normalizeCopyImageDataOptions(options_) {
2423
- const { width, height, depth } = this;
2424
- const options = { ..._Texture.defaultCopyDataOptions, width, height, depth, ...options_ };
2425
- const info = this.device.getTextureFormatInfo(this.format);
2426
- if (!options_.bytesPerRow && !info.bytesPerPixel) {
2427
- throw new Error(`bytesPerRow must be provided for texture format ${this.format}`);
2428
- }
2429
- options.bytesPerRow = options_.bytesPerRow || width * (info.bytesPerPixel || 4);
2430
- options.rowsPerImage = options_.rowsPerImage || height;
2431
- return options;
3295
+ const { data, depth, ...writeOptions } = options_;
3296
+ const options = this._normalizeTextureWriteOptions({
3297
+ ...writeOptions,
3298
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3299
+ });
3300
+ return { data, depth: options.depthOrArrayLayers, ...options };
2432
3301
  }
2433
3302
  _normalizeCopyExternalImageOptions(options_) {
3303
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3304
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3305
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
2434
3306
  const size = this.device.getExternalImageSize(options_.image);
2435
- const options = { ..._Texture.defaultCopyExternalImageOptions, ...size, ...options_ };
2436
- options.width = Math.min(options.width, this.width - options.x);
2437
- options.height = Math.min(options.height, this.height - options.y);
3307
+ const options = {
3308
+ ..._Texture.defaultCopyExternalImageOptions,
3309
+ ...mipLevelSize,
3310
+ ...size,
3311
+ ...optionsWithoutUndefined
3312
+ };
3313
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3314
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3315
+ options.depth = Math.min(options.depth, mipLevelSize.depthOrArrayLayers - options.z);
3316
+ return options;
3317
+ }
3318
+ _normalizeTextureReadOptions(options_) {
3319
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3320
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3321
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3322
+ const options = {
3323
+ ..._Texture.defaultTextureReadOptions,
3324
+ ...mipLevelSize,
3325
+ ...optionsWithoutUndefined
3326
+ };
3327
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3328
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3329
+ options.depthOrArrayLayers = Math.min(
3330
+ options.depthOrArrayLayers,
3331
+ mipLevelSize.depthOrArrayLayers - options.z
3332
+ );
2438
3333
  return options;
2439
3334
  }
3335
+ /**
3336
+ * Normalizes a texture read request and validates the color-only readback contract used by the
3337
+ * current texture read APIs. Supported dimensions are `2d`, `cube`, `cube-array`,
3338
+ * `2d-array`, and `3d`.
3339
+ *
3340
+ * @throws if the texture format, aspect, or dimension is not supported by the first-pass
3341
+ * color-read implementation.
3342
+ */
3343
+ _getSupportedColorReadOptions(options_) {
3344
+ const options = this._normalizeTextureReadOptions(options_);
3345
+ const formatInfo = textureFormatDecoder.getInfo(this.format);
3346
+ this._validateColorReadAspect(options);
3347
+ this._validateColorReadFormat(formatInfo);
3348
+ switch (this.dimension) {
3349
+ case "2d":
3350
+ case "cube":
3351
+ case "cube-array":
3352
+ case "2d-array":
3353
+ case "3d":
3354
+ return options;
3355
+ default:
3356
+ throw new Error(`${this} color readback does not support ${this.dimension} textures`);
3357
+ }
3358
+ }
3359
+ /** Validates that a read request targets the full color aspect of the texture. */
3360
+ _validateColorReadAspect(options) {
3361
+ if (options.aspect !== "all") {
3362
+ throw new Error(`${this} color readback only supports aspect 'all'`);
3363
+ }
3364
+ }
3365
+ /** Validates that a read request targets an uncompressed color-renderable texture format. */
3366
+ _validateColorReadFormat(formatInfo) {
3367
+ if (formatInfo.compressed) {
3368
+ throw new Error(
3369
+ `${this} color readback does not support compressed formats (${this.format})`
3370
+ );
3371
+ }
3372
+ switch (formatInfo.attachment) {
3373
+ case "color":
3374
+ return;
3375
+ case "depth":
3376
+ throw new Error(`${this} color readback does not support depth formats (${this.format})`);
3377
+ case "stencil":
3378
+ throw new Error(`${this} color readback does not support stencil formats (${this.format})`);
3379
+ case "depth-stencil":
3380
+ throw new Error(
3381
+ `${this} color readback does not support depth-stencil formats (${this.format})`
3382
+ );
3383
+ default:
3384
+ throw new Error(`${this} color readback does not support format ${this.format}`);
3385
+ }
3386
+ }
3387
+ _normalizeTextureWriteOptions(options_) {
3388
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3389
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3390
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3391
+ const options = {
3392
+ ..._Texture.defaultTextureWriteOptions,
3393
+ ...mipLevelSize,
3394
+ ...optionsWithoutUndefined
3395
+ };
3396
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3397
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3398
+ options.depthOrArrayLayers = Math.min(
3399
+ options.depthOrArrayLayers,
3400
+ mipLevelSize.depthOrArrayLayers - options.z
3401
+ );
3402
+ const layout = textureFormatDecoder.computeMemoryLayout({
3403
+ format: this.format,
3404
+ width: options.width,
3405
+ height: options.height,
3406
+ depth: options.depthOrArrayLayers,
3407
+ byteAlignment: this.byteAlignment
3408
+ });
3409
+ const minimumBytesPerRow = layout.bytesPerPixel * options.width;
3410
+ options.bytesPerRow = optionsWithoutUndefined.bytesPerRow ?? layout.bytesPerRow;
3411
+ options.rowsPerImage = optionsWithoutUndefined.rowsPerImage ?? options.height;
3412
+ if (options.bytesPerRow < minimumBytesPerRow) {
3413
+ throw new Error(
3414
+ `bytesPerRow (${options.bytesPerRow}) must be at least ${minimumBytesPerRow} for ${this.format}`
3415
+ );
3416
+ }
3417
+ if (options.rowsPerImage < options.height) {
3418
+ throw new Error(
3419
+ `rowsPerImage (${options.rowsPerImage}) must be at least ${options.height} for ${this.format}`
3420
+ );
3421
+ }
3422
+ const bytesPerPixel = this.device.getTextureFormatInfo(this.format).bytesPerPixel;
3423
+ if (bytesPerPixel && options.bytesPerRow % bytesPerPixel !== 0) {
3424
+ throw new Error(
3425
+ `bytesPerRow (${options.bytesPerRow}) must be a multiple of bytesPerPixel (${bytesPerPixel}) for ${this.format}`
3426
+ );
3427
+ }
3428
+ return options;
3429
+ }
3430
+ _getMipLevelSize(mipLevel) {
3431
+ const width = Math.max(1, this.width >> mipLevel);
3432
+ const height = this.baseDimension === "1d" ? 1 : Math.max(1, this.height >> mipLevel);
3433
+ const depthOrArrayLayers = this.dimension === "3d" ? Math.max(1, this.depth >> mipLevel) : this.depth;
3434
+ return { width, height, depthOrArrayLayers };
3435
+ }
3436
+ getAllocatedByteLength() {
3437
+ let allocatedByteLength = 0;
3438
+ for (let mipLevel = 0; mipLevel < this.mipLevels; mipLevel++) {
3439
+ const { width, height, depthOrArrayLayers } = this._getMipLevelSize(mipLevel);
3440
+ allocatedByteLength += textureFormatDecoder.computeMemoryLayout({
3441
+ format: this.format,
3442
+ width,
3443
+ height,
3444
+ depth: depthOrArrayLayers,
3445
+ byteAlignment: 1
3446
+ }).byteLength;
3447
+ }
3448
+ return allocatedByteLength * this.samples;
3449
+ }
3450
+ static _omitUndefined(options) {
3451
+ return Object.fromEntries(
3452
+ Object.entries(options).filter(([, value]) => value !== void 0)
3453
+ );
3454
+ }
2440
3455
  };
2441
3456
  var Texture = _Texture;
2442
3457
  /** The texture can be bound for use as a sampled texture in a shader */
@@ -2453,13 +3468,12 @@ or create a device with the 'debug: true' prop.`;
2453
3468
  __publicField(Texture, "TEXTURE", 4);
2454
3469
  /** @deprecated Use Texture.RENDER */
2455
3470
  __publicField(Texture, "RENDER_ATTACHMENT", 16);
2456
- /** Default options */
2457
3471
  __publicField(Texture, "defaultProps", {
2458
3472
  ...Resource.defaultProps,
2459
3473
  data: null,
2460
3474
  dimension: "2d",
2461
3475
  format: "rgba8unorm",
2462
- usage: _Texture.TEXTURE | _Texture.RENDER_ATTACHMENT | _Texture.COPY_DST,
3476
+ usage: _Texture.SAMPLE | _Texture.RENDER | _Texture.COPY_DST,
2463
3477
  width: void 0,
2464
3478
  height: void 0,
2465
3479
  depth: 1,
@@ -2473,6 +3487,10 @@ or create a device with the 'debug: true' prop.`;
2473
3487
  byteOffset: 0,
2474
3488
  bytesPerRow: void 0,
2475
3489
  rowsPerImage: void 0,
3490
+ width: void 0,
3491
+ height: void 0,
3492
+ depthOrArrayLayers: void 0,
3493
+ depth: 1,
2476
3494
  mipLevel: 0,
2477
3495
  x: 0,
2478
3496
  y: 0,
@@ -2496,6 +3514,29 @@ or create a device with the 'debug: true' prop.`;
2496
3514
  premultipliedAlpha: false,
2497
3515
  flipY: false
2498
3516
  });
3517
+ __publicField(Texture, "defaultTextureReadOptions", {
3518
+ x: 0,
3519
+ y: 0,
3520
+ z: 0,
3521
+ width: void 0,
3522
+ height: void 0,
3523
+ depthOrArrayLayers: 1,
3524
+ mipLevel: 0,
3525
+ aspect: "all"
3526
+ });
3527
+ __publicField(Texture, "defaultTextureWriteOptions", {
3528
+ byteOffset: 0,
3529
+ bytesPerRow: void 0,
3530
+ rowsPerImage: void 0,
3531
+ x: 0,
3532
+ y: 0,
3533
+ z: 0,
3534
+ width: void 0,
3535
+ height: void 0,
3536
+ depthOrArrayLayers: 1,
3537
+ mipLevel: 0,
3538
+ aspect: "all"
3539
+ });
2499
3540
 
2500
3541
  // src/adapter/resources/texture-view.ts
2501
3542
  var _TextureView = class extends Resource {
@@ -2542,24 +3583,32 @@ or create a device with the 'debug: true' prop.`;
2542
3583
  const log2 = shaderLog.slice().sort((a, b) => a.lineNum - b.lineNum);
2543
3584
  switch (options?.showSourceCode || "no") {
2544
3585
  case "all":
2545
- let currentMessage = 0;
3586
+ let currentMessageIndex = 0;
2546
3587
  for (let lineNum = 1; lineNum <= lines.length; lineNum++) {
2547
- formattedLog += getNumberedLine(lines[lineNum - 1], lineNum, options);
2548
- while (log2.length > currentMessage && log2[currentMessage].lineNum === lineNum) {
2549
- const message = log2[currentMessage++];
2550
- formattedLog += formatCompilerMessage(message, lines, message.lineNum, {
3588
+ const line = lines[lineNum - 1];
3589
+ const currentMessage = log2[currentMessageIndex];
3590
+ if (line && currentMessage) {
3591
+ formattedLog += getNumberedLine(line, lineNum, options);
3592
+ }
3593
+ while (log2.length > currentMessageIndex && currentMessage.lineNum === lineNum) {
3594
+ const message = log2[currentMessageIndex++];
3595
+ if (message) {
3596
+ formattedLog += formatCompilerMessage(message, lines, message.lineNum, {
3597
+ ...options,
3598
+ inlineSource: false
3599
+ });
3600
+ }
3601
+ }
3602
+ }
3603
+ while (log2.length > currentMessageIndex) {
3604
+ const message = log2[currentMessageIndex++];
3605
+ if (message) {
3606
+ formattedLog += formatCompilerMessage(message, [], 0, {
2551
3607
  ...options,
2552
3608
  inlineSource: false
2553
3609
  });
2554
3610
  }
2555
3611
  }
2556
- while (log2.length > currentMessage) {
2557
- const message = log2[currentMessage++];
2558
- formattedLog += formatCompilerMessage(message, [], 0, {
2559
- ...options,
2560
- inlineSource: false
2561
- });
2562
- }
2563
3612
  return formattedLog;
2564
3613
  case "issues":
2565
3614
  case "no":
@@ -2581,8 +3630,8 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
2581
3630
 
2582
3631
  `;
2583
3632
  }
2584
- const color = message.type === "error" ? "red" : "#8B4000";
2585
- return options?.html ? `<div class='luma-compiler-log-error' style="color:${color};"><b> ${message.type.toUpperCase()}: ${message.message}</b></div>` : `${message.type.toUpperCase()}: ${message.message}`;
3633
+ const color = message.type === "error" ? "red" : "orange";
3634
+ return options?.html ? `<div class='luma-compiler-log-${message.type}' style="color:${color};"><b> ${message.type.toUpperCase()}: ${message.message}</b></div>` : `${message.type.toUpperCase()}: ${message.message}`;
2586
3635
  }
2587
3636
  function getNumberedLines(lines, lineNum, options) {
2588
3637
  let numberedLines = "";
@@ -2668,29 +3717,34 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
2668
3717
  }
2669
3718
  const shaderName = shaderId;
2670
3719
  const shaderTitle = `${this.stage} shader "${shaderName}"`;
2671
- let htmlLog = formatCompilerLog(messages, this.source, { showSourceCode: "all", html: true });
3720
+ const htmlLog = formatCompilerLog(messages, this.source, { showSourceCode: "all", html: true });
2672
3721
  const translatedSource = this.getTranslatedSource();
3722
+ const container = document.createElement("div");
3723
+ container.innerHTML = `<h1>Compilation error in ${shaderTitle}</h1>
3724
+ <div style="display:flex;position:fixed;top:10px;right:20px;gap:2px;">
3725
+ <button id="copy">Copy source</button><br/>
3726
+ <button id="close">Close</button>
3727
+ </div>
3728
+ <code><pre>${htmlLog}</pre></code>`;
2673
3729
  if (translatedSource) {
2674
- htmlLog += `<br /><br /><h1>Translated Source</h1><br /><br /><code style="user-select:text;"><pre>${translatedSource}</pre></code>`;
2675
- }
2676
- const button = document.createElement("Button");
2677
- button.innerHTML = `
2678
- <h1>Compilation error in ${shaderTitle}</h1><br /><br />
2679
- <code style="user-select:text;"><pre>
2680
- ${htmlLog}
2681
- </pre></code>`;
2682
- button.style.top = "10px";
2683
- button.style.left = "10px";
2684
- button.style.position = "absolute";
2685
- button.style.zIndex = "9999";
2686
- button.style.width = "100%";
2687
- button.style.textAlign = "left";
2688
- document.body.appendChild(button);
2689
- const errors = document.getElementsByClassName("luma-compiler-log-error");
2690
- errors[0]?.scrollIntoView();
2691
- button.onclick = () => {
2692
- const dataURI = `data:text/plain,${encodeURIComponent(this.source)}`;
2693
- navigator.clipboard.writeText(dataURI);
3730
+ container.innerHTML += `<br /><h1>Translated Source</h1><br /><br /><code><pre>${translatedSource}</pre></code>`;
3731
+ }
3732
+ container.style.top = "0";
3733
+ container.style.left = "0";
3734
+ container.style.background = "white";
3735
+ container.style.position = "fixed";
3736
+ container.style.zIndex = "9999";
3737
+ container.style.maxWidth = "100vw";
3738
+ container.style.maxHeight = "100vh";
3739
+ container.style.overflowY = "auto";
3740
+ document.body.appendChild(container);
3741
+ const error = container.querySelector(".luma-compiler-log-error");
3742
+ error?.scrollIntoView();
3743
+ container.querySelector("button#close").onclick = () => {
3744
+ container.remove();
3745
+ };
3746
+ container.querySelector("button#copy").onclick = () => {
3747
+ navigator.clipboard.writeText(this.source);
2694
3748
  };
2695
3749
  }
2696
3750
  };
@@ -2710,7 +3764,7 @@ ${htmlLog}
2710
3764
  function getShaderName(shader, defaultName = "unnamed") {
2711
3765
  const SHADER_NAME_REGEXP = /#define[\s*]SHADER_NAME[\s*]([A-Za-z0-9_-]+)[\s*]/;
2712
3766
  const match = SHADER_NAME_REGEXP.exec(shader);
2713
- return match ? match[1] : defaultName;
3767
+ return match?.[1] ?? defaultName;
2714
3768
  }
2715
3769
 
2716
3770
  // src/adapter/resources/framebuffer.ts
@@ -2736,7 +3790,12 @@ ${htmlLog}
2736
3790
  (colorAttachment) => colorAttachment.texture.clone(size)
2737
3791
  );
2738
3792
  const depthStencilAttachment = this.depthStencilAttachment && this.depthStencilAttachment.texture.clone(size);
2739
- return this.device.createFramebuffer({ ...this.props, colorAttachments, depthStencilAttachment });
3793
+ return this.device.createFramebuffer({
3794
+ ...this.props,
3795
+ ...size,
3796
+ colorAttachments,
3797
+ depthStencilAttachment
3798
+ });
2740
3799
  }
2741
3800
  resize(size) {
2742
3801
  let updateSize = !size;
@@ -2811,17 +3870,15 @@ ${htmlLog}
2811
3870
  * and destroys existing textures if owned
2812
3871
  */
2813
3872
  resizeAttachments(width, height) {
2814
- for (let i = 0; i < this.colorAttachments.length; ++i) {
2815
- if (this.colorAttachments[i]) {
2816
- const resizedTexture = this.colorAttachments[i].texture.clone({
2817
- width,
2818
- height
2819
- });
2820
- this.destroyAttachedResource(this.colorAttachments[i]);
2821
- this.colorAttachments[i] = resizedTexture.view;
2822
- this.attachResource(resizedTexture.view);
2823
- }
2824
- }
3873
+ this.colorAttachments.forEach((colorAttachment, i) => {
3874
+ const resizedTexture = colorAttachment.texture.clone({
3875
+ width,
3876
+ height
3877
+ });
3878
+ this.destroyAttachedResource(colorAttachment);
3879
+ this.colorAttachments[i] = resizedTexture.view;
3880
+ this.attachResource(resizedTexture.view);
3881
+ });
2825
3882
  if (this.depthStencilAttachment) {
2826
3883
  const resizedTexture = this.depthStencilAttachment.texture.clone({
2827
3884
  width,
@@ -2858,30 +3915,538 @@ ${htmlLog}
2858
3915
  linkStatus = "pending";
2859
3916
  /** The hash of the pipeline */
2860
3917
  hash = "";
3918
+ /** Optional shared backend implementation */
3919
+ sharedRenderPipeline = null;
3920
+ /** Whether shader or pipeline compilation/linking is still in progress */
3921
+ get isPending() {
3922
+ return this.linkStatus === "pending" || this.vs.compilationStatus === "pending" || this.fs?.compilationStatus === "pending";
3923
+ }
3924
+ /** Whether shader or pipeline compilation/linking has failed */
3925
+ get isErrored() {
3926
+ return this.linkStatus === "error" || this.vs.compilationStatus === "error" || this.fs?.compilationStatus === "error";
3927
+ }
2861
3928
  constructor(device, props) {
2862
3929
  super(device, props, _RenderPipeline.defaultProps);
2863
3930
  this.shaderLayout = this.props.shaderLayout;
2864
3931
  this.bufferLayout = this.props.bufferLayout || [];
3932
+ this.sharedRenderPipeline = this.props._sharedRenderPipeline || null;
3933
+ }
3934
+ };
3935
+ var RenderPipeline = _RenderPipeline;
3936
+ __publicField(RenderPipeline, "defaultProps", {
3937
+ ...Resource.defaultProps,
3938
+ vs: null,
3939
+ vertexEntryPoint: "vertexMain",
3940
+ vsConstants: {},
3941
+ fs: null,
3942
+ fragmentEntryPoint: "fragmentMain",
3943
+ fsConstants: {},
3944
+ shaderLayout: null,
3945
+ bufferLayout: [],
3946
+ topology: "triangle-list",
3947
+ colorAttachmentFormats: void 0,
3948
+ depthStencilAttachmentFormat: void 0,
3949
+ parameters: {},
3950
+ varyings: void 0,
3951
+ bufferMode: void 0,
3952
+ disableWarnings: false,
3953
+ _sharedRenderPipeline: void 0,
3954
+ bindings: void 0,
3955
+ bindGroups: void 0
3956
+ });
3957
+
3958
+ // src/adapter/resources/shared-render-pipeline.ts
3959
+ var SharedRenderPipeline = class extends Resource {
3960
+ get [Symbol.toStringTag]() {
3961
+ return "SharedRenderPipeline";
3962
+ }
3963
+ constructor(device, props) {
3964
+ super(device, props, {
3965
+ ...Resource.defaultProps,
3966
+ handle: void 0,
3967
+ vs: void 0,
3968
+ fs: void 0,
3969
+ varyings: void 0,
3970
+ bufferMode: void 0
3971
+ });
3972
+ }
3973
+ };
3974
+
3975
+ // src/adapter/resources/compute-pipeline.ts
3976
+ var _ComputePipeline = class extends Resource {
3977
+ get [Symbol.toStringTag]() {
3978
+ return "ComputePipeline";
3979
+ }
3980
+ hash = "";
3981
+ /** The merged shader layout */
3982
+ shaderLayout;
3983
+ constructor(device, props) {
3984
+ super(device, props, _ComputePipeline.defaultProps);
3985
+ this.shaderLayout = props.shaderLayout;
3986
+ }
3987
+ };
3988
+ var ComputePipeline = _ComputePipeline;
3989
+ __publicField(ComputePipeline, "defaultProps", {
3990
+ ...Resource.defaultProps,
3991
+ shader: void 0,
3992
+ entryPoint: void 0,
3993
+ constants: {},
3994
+ shaderLayout: void 0
3995
+ });
3996
+
3997
+ // src/factories/pipeline-factory.ts
3998
+ var _PipelineFactory = class {
3999
+ /** Get the singleton default pipeline factory for the specified device */
4000
+ static getDefaultPipelineFactory(device) {
4001
+ const moduleData = device.getModuleData("@luma.gl/core");
4002
+ moduleData.defaultPipelineFactory ||= new _PipelineFactory(device);
4003
+ return moduleData.defaultPipelineFactory;
4004
+ }
4005
+ device;
4006
+ _hashCounter = 0;
4007
+ _hashes = {};
4008
+ _renderPipelineCache = {};
4009
+ _computePipelineCache = {};
4010
+ _sharedRenderPipelineCache = {};
4011
+ get [Symbol.toStringTag]() {
4012
+ return "PipelineFactory";
4013
+ }
4014
+ toString() {
4015
+ return `PipelineFactory(${this.device.id})`;
4016
+ }
4017
+ constructor(device) {
4018
+ this.device = device;
4019
+ }
4020
+ /**
4021
+ * WebGL has two cache layers with different priorities:
4022
+ * - `_sharedRenderPipelineCache` owns `WEBGLSharedRenderPipeline` / `WebGLProgram` reuse.
4023
+ * - `_renderPipelineCache` owns `RenderPipeline` wrapper reuse.
4024
+ *
4025
+ * Shared WebGL program reuse is the hard requirement. Wrapper reuse is beneficial,
4026
+ * but wrapper cache misses are acceptable if that keeps the cache logic simple and
4027
+ * prevents incorrect cache hits.
4028
+ *
4029
+ * In particular, wrapper hash logic must never force program creation or linked-program
4030
+ * introspection just to decide whether a shared WebGL program can be reused.
4031
+ */
4032
+ /** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */
4033
+ createRenderPipeline(props) {
4034
+ if (!this.device.props._cachePipelines) {
4035
+ return this.device.createRenderPipeline(props);
4036
+ }
4037
+ const allProps = { ...RenderPipeline.defaultProps, ...props };
4038
+ const cache = this._renderPipelineCache;
4039
+ const hash = this._hashRenderPipeline(allProps);
4040
+ let pipeline = cache[hash]?.resource;
4041
+ if (!pipeline) {
4042
+ const sharedRenderPipeline = this.device.type === "webgl" && this.device.props._sharePipelines ? this.createSharedRenderPipeline(allProps) : void 0;
4043
+ pipeline = this.device.createRenderPipeline({
4044
+ ...allProps,
4045
+ id: allProps.id ? `${allProps.id}-cached` : uid("unnamed-cached"),
4046
+ _sharedRenderPipeline: sharedRenderPipeline
4047
+ });
4048
+ pipeline.hash = hash;
4049
+ cache[hash] = { resource: pipeline, useCount: 1 };
4050
+ if (this.device.props.debugFactories) {
4051
+ log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
4052
+ }
4053
+ } else {
4054
+ cache[hash].useCount++;
4055
+ if (this.device.props.debugFactories) {
4056
+ log.log(
4057
+ 3,
4058
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
4059
+ )();
4060
+ }
4061
+ }
4062
+ return pipeline;
4063
+ }
4064
+ /** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */
4065
+ createComputePipeline(props) {
4066
+ if (!this.device.props._cachePipelines) {
4067
+ return this.device.createComputePipeline(props);
4068
+ }
4069
+ const allProps = { ...ComputePipeline.defaultProps, ...props };
4070
+ const cache = this._computePipelineCache;
4071
+ const hash = this._hashComputePipeline(allProps);
4072
+ let pipeline = cache[hash]?.resource;
4073
+ if (!pipeline) {
4074
+ pipeline = this.device.createComputePipeline({
4075
+ ...allProps,
4076
+ id: allProps.id ? `${allProps.id}-cached` : void 0
4077
+ });
4078
+ pipeline.hash = hash;
4079
+ cache[hash] = { resource: pipeline, useCount: 1 };
4080
+ if (this.device.props.debugFactories) {
4081
+ log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
4082
+ }
4083
+ } else {
4084
+ cache[hash].useCount++;
4085
+ if (this.device.props.debugFactories) {
4086
+ log.log(
4087
+ 3,
4088
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
4089
+ )();
4090
+ }
4091
+ }
4092
+ return pipeline;
4093
+ }
4094
+ release(pipeline) {
4095
+ if (!this.device.props._cachePipelines) {
4096
+ pipeline.destroy();
4097
+ return;
4098
+ }
4099
+ const cache = this._getCache(pipeline);
4100
+ const hash = pipeline.hash;
4101
+ cache[hash].useCount--;
4102
+ if (cache[hash].useCount === 0) {
4103
+ this._destroyPipeline(pipeline);
4104
+ if (this.device.props.debugFactories) {
4105
+ log.log(3, `${this}: ${pipeline} released and destroyed`)();
4106
+ }
4107
+ } else if (cache[hash].useCount < 0) {
4108
+ log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)();
4109
+ cache[hash].useCount = 0;
4110
+ } else if (this.device.props.debugFactories) {
4111
+ log.log(3, `${this}: ${pipeline} released, count=${cache[hash].useCount}`)();
4112
+ }
4113
+ }
4114
+ createSharedRenderPipeline(props) {
4115
+ const sharedPipelineHash = this._hashSharedRenderPipeline(props);
4116
+ let sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
4117
+ if (!sharedCacheItem) {
4118
+ const sharedRenderPipeline = this.device._createSharedRenderPipelineWebGL(props);
4119
+ sharedCacheItem = { resource: sharedRenderPipeline, useCount: 0 };
4120
+ this._sharedRenderPipelineCache[sharedPipelineHash] = sharedCacheItem;
4121
+ }
4122
+ sharedCacheItem.useCount++;
4123
+ return sharedCacheItem.resource;
4124
+ }
4125
+ releaseSharedRenderPipeline(pipeline) {
4126
+ if (!pipeline.sharedRenderPipeline) {
4127
+ return;
4128
+ }
4129
+ const sharedPipelineHash = this._hashSharedRenderPipeline(pipeline.sharedRenderPipeline.props);
4130
+ const sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
4131
+ if (!sharedCacheItem) {
4132
+ return;
4133
+ }
4134
+ sharedCacheItem.useCount--;
4135
+ if (sharedCacheItem.useCount === 0) {
4136
+ sharedCacheItem.resource.destroy();
4137
+ delete this._sharedRenderPipelineCache[sharedPipelineHash];
4138
+ }
4139
+ }
4140
+ // PRIVATE
4141
+ /** Destroy a cached pipeline, removing it from the cache if configured to do so. */
4142
+ _destroyPipeline(pipeline) {
4143
+ const cache = this._getCache(pipeline);
4144
+ if (!this.device.props._destroyPipelines) {
4145
+ return false;
4146
+ }
4147
+ delete cache[pipeline.hash];
4148
+ pipeline.destroy();
4149
+ if (pipeline instanceof RenderPipeline) {
4150
+ this.releaseSharedRenderPipeline(pipeline);
4151
+ }
4152
+ return true;
4153
+ }
4154
+ /** Get the appropriate cache for the type of pipeline */
4155
+ _getCache(pipeline) {
4156
+ let cache;
4157
+ if (pipeline instanceof ComputePipeline) {
4158
+ cache = this._computePipelineCache;
4159
+ }
4160
+ if (pipeline instanceof RenderPipeline) {
4161
+ cache = this._renderPipelineCache;
4162
+ }
4163
+ if (!cache) {
4164
+ throw new Error(`${this}`);
4165
+ }
4166
+ if (!cache[pipeline.hash]) {
4167
+ throw new Error(`${this}: ${pipeline} matched incorrect entry`);
4168
+ }
4169
+ return cache;
4170
+ }
4171
+ /** Calculate a hash based on all the inputs for a compute pipeline */
4172
+ _hashComputePipeline(props) {
4173
+ const { type } = this.device;
4174
+ const shaderHash = this._getHash(props.shader.source);
4175
+ const shaderLayoutHash = this._getHash(JSON.stringify(props.shaderLayout));
4176
+ return `${type}/C/${shaderHash}SL${shaderLayoutHash}`;
4177
+ }
4178
+ /** Calculate a hash based on all the inputs for a render pipeline */
4179
+ _hashRenderPipeline(props) {
4180
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
4181
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
4182
+ const varyingHash = this._getWebGLVaryingHash(props);
4183
+ const shaderLayoutHash = this._getHash(JSON.stringify(props.shaderLayout));
4184
+ const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
4185
+ const { type } = this.device;
4186
+ switch (type) {
4187
+ case "webgl":
4188
+ const webglParameterHash = this._getHash(JSON.stringify(props.parameters));
4189
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${webglParameterHash}SL${shaderLayoutHash}BL${bufferLayoutHash}`;
4190
+ case "webgpu":
4191
+ default:
4192
+ const entryPointHash = this._getHash(
4193
+ JSON.stringify({
4194
+ vertexEntryPoint: props.vertexEntryPoint,
4195
+ fragmentEntryPoint: props.fragmentEntryPoint
4196
+ })
4197
+ );
4198
+ const parameterHash = this._getHash(JSON.stringify(props.parameters));
4199
+ const attachmentHash = this._getWebGPUAttachmentHash(props);
4200
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}EP${entryPointHash}P${parameterHash}SL${shaderLayoutHash}BL${bufferLayoutHash}A${attachmentHash}`;
4201
+ }
4202
+ }
4203
+ // This is the only gate for shared `WebGLProgram` reuse.
4204
+ // Only include inputs that affect program linking or transform-feedback linkage.
4205
+ // Wrapper-only concerns such as topology, parameters, attachment formats and layout
4206
+ // overrides must not be added here.
4207
+ _hashSharedRenderPipeline(props) {
4208
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
4209
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
4210
+ const varyingHash = this._getWebGLVaryingHash(props);
4211
+ return `webgl/S/${vsHash}/${fsHash}V${varyingHash}`;
4212
+ }
4213
+ _getHash(key) {
4214
+ if (this._hashes[key] === void 0) {
4215
+ this._hashes[key] = this._hashCounter++;
4216
+ }
4217
+ return this._hashes[key];
4218
+ }
4219
+ _getWebGLVaryingHash(props) {
4220
+ const { varyings = [], bufferMode = null } = props;
4221
+ return this._getHash(JSON.stringify({ varyings, bufferMode }));
4222
+ }
4223
+ _getWebGPUAttachmentHash(props) {
4224
+ const colorAttachmentFormats = props.colorAttachmentFormats ?? [
4225
+ this.device.preferredColorFormat
4226
+ ];
4227
+ const depthStencilAttachmentFormat = props.parameters?.depthWriteEnabled ? props.depthStencilAttachmentFormat || this.device.preferredDepthFormat : null;
4228
+ return this._getHash(
4229
+ JSON.stringify({
4230
+ colorAttachmentFormats,
4231
+ depthStencilAttachmentFormat
4232
+ })
4233
+ );
4234
+ }
4235
+ };
4236
+ var PipelineFactory = _PipelineFactory;
4237
+ __publicField(PipelineFactory, "defaultProps", { ...RenderPipeline.defaultProps });
4238
+
4239
+ // src/factories/shader-factory.ts
4240
+ var _ShaderFactory = class {
4241
+ /** Returns the default ShaderFactory for the given {@link Device}, creating one if necessary. */
4242
+ static getDefaultShaderFactory(device) {
4243
+ const moduleData = device.getModuleData("@luma.gl/core");
4244
+ moduleData.defaultShaderFactory ||= new _ShaderFactory(device);
4245
+ return moduleData.defaultShaderFactory;
4246
+ }
4247
+ device;
4248
+ _cache = {};
4249
+ get [Symbol.toStringTag]() {
4250
+ return "ShaderFactory";
4251
+ }
4252
+ toString() {
4253
+ return `${this[Symbol.toStringTag]}(${this.device.id})`;
4254
+ }
4255
+ /** @internal */
4256
+ constructor(device) {
4257
+ this.device = device;
4258
+ }
4259
+ /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */
4260
+ createShader(props) {
4261
+ if (!this.device.props._cacheShaders) {
4262
+ return this.device.createShader(props);
4263
+ }
4264
+ const key = this._hashShader(props);
4265
+ let cacheEntry = this._cache[key];
4266
+ if (!cacheEntry) {
4267
+ const resource = this.device.createShader({
4268
+ ...props,
4269
+ id: props.id ? `${props.id}-cached` : void 0
4270
+ });
4271
+ this._cache[key] = cacheEntry = { resource, useCount: 1 };
4272
+ if (this.device.props.debugFactories) {
4273
+ log.log(3, `${this}: Created new shader ${resource.id}`)();
4274
+ }
4275
+ } else {
4276
+ cacheEntry.useCount++;
4277
+ if (this.device.props.debugFactories) {
4278
+ log.log(
4279
+ 3,
4280
+ `${this}: Reusing shader ${cacheEntry.resource.id} count=${cacheEntry.useCount}`
4281
+ )();
4282
+ }
4283
+ }
4284
+ return cacheEntry.resource;
4285
+ }
4286
+ /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */
4287
+ release(shader) {
4288
+ if (!this.device.props._cacheShaders) {
4289
+ shader.destroy();
4290
+ return;
4291
+ }
4292
+ const key = this._hashShader(shader);
4293
+ const cacheEntry = this._cache[key];
4294
+ if (cacheEntry) {
4295
+ cacheEntry.useCount--;
4296
+ if (cacheEntry.useCount === 0) {
4297
+ if (this.device.props._destroyShaders) {
4298
+ delete this._cache[key];
4299
+ cacheEntry.resource.destroy();
4300
+ if (this.device.props.debugFactories) {
4301
+ log.log(3, `${this}: Releasing shader ${shader.id}, destroyed`)();
4302
+ }
4303
+ }
4304
+ } else if (cacheEntry.useCount < 0) {
4305
+ throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`);
4306
+ } else if (this.device.props.debugFactories) {
4307
+ log.log(3, `${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)();
4308
+ }
4309
+ }
4310
+ }
4311
+ // PRIVATE
4312
+ _hashShader(value) {
4313
+ return `${value.stage}:${value.source}`;
4314
+ }
4315
+ };
4316
+ var ShaderFactory = _ShaderFactory;
4317
+ __publicField(ShaderFactory, "defaultProps", { ...Shader.defaultProps });
4318
+
4319
+ // src/adapter-utils/bind-groups.ts
4320
+ function getShaderLayoutBinding(shaderLayout, bindingName, options) {
4321
+ const bindingLayout = shaderLayout.bindings.find(
4322
+ (binding) => binding.name === bindingName || `${binding.name.toLocaleLowerCase()}uniforms` === bindingName.toLocaleLowerCase()
4323
+ );
4324
+ if (!bindingLayout && !options?.ignoreWarnings) {
4325
+ log.warn(`Binding ${bindingName} not set: Not found in shader layout.`)();
4326
+ }
4327
+ return bindingLayout || null;
4328
+ }
4329
+ function normalizeBindingsByGroup(shaderLayout, bindingsOrBindGroups) {
4330
+ if (!bindingsOrBindGroups) {
4331
+ return {};
4332
+ }
4333
+ if (areBindingsGrouped(bindingsOrBindGroups)) {
4334
+ const bindGroups2 = bindingsOrBindGroups;
4335
+ return Object.fromEntries(
4336
+ Object.entries(bindGroups2).map(([group, bindings]) => [Number(group), { ...bindings }])
4337
+ );
4338
+ }
4339
+ const bindGroups = {};
4340
+ for (const [bindingName, binding] of Object.entries(bindingsOrBindGroups)) {
4341
+ const bindingLayout = getShaderLayoutBinding(shaderLayout, bindingName);
4342
+ const group = bindingLayout?.group ?? 0;
4343
+ bindGroups[group] ||= {};
4344
+ bindGroups[group][bindingName] = binding;
4345
+ }
4346
+ return bindGroups;
4347
+ }
4348
+ function flattenBindingsByGroup(bindGroups) {
4349
+ const bindings = {};
4350
+ for (const groupBindings of Object.values(bindGroups)) {
4351
+ Object.assign(bindings, groupBindings);
4352
+ }
4353
+ return bindings;
4354
+ }
4355
+ function areBindingsGrouped(bindingsOrBindGroups) {
4356
+ const keys = Object.keys(bindingsOrBindGroups);
4357
+ return keys.length > 0 && keys.every((key) => /^\d+$/.test(key));
4358
+ }
4359
+
4360
+ // src/factories/bind-group-factory.ts
4361
+ var BindGroupFactory = class {
4362
+ device;
4363
+ _layoutCacheByPipeline = /* @__PURE__ */ new WeakMap();
4364
+ _bindGroupCacheByLayout = /* @__PURE__ */ new WeakMap();
4365
+ constructor(device) {
4366
+ this.device = device;
4367
+ }
4368
+ getBindGroups(pipeline, bindings, bindGroupCacheKeys) {
4369
+ if (this.device.type !== "webgpu" || pipeline.shaderLayout.bindings.length === 0) {
4370
+ return {};
4371
+ }
4372
+ const bindingsByGroup = normalizeBindingsByGroup(pipeline.shaderLayout, bindings);
4373
+ const resolvedBindGroups = {};
4374
+ for (const group of getBindGroupIndicesUpToMax(pipeline.shaderLayout.bindings)) {
4375
+ const groupBindings = bindingsByGroup[group];
4376
+ const bindGroupLayout = this._getBindGroupLayout(pipeline, group);
4377
+ if (!groupBindings || Object.keys(groupBindings).length === 0) {
4378
+ if (!hasBindingsInGroup(pipeline.shaderLayout.bindings, group)) {
4379
+ resolvedBindGroups[group] = this._getEmptyBindGroup(
4380
+ bindGroupLayout,
4381
+ pipeline.shaderLayout,
4382
+ group
4383
+ );
4384
+ }
4385
+ continue;
4386
+ }
4387
+ const bindGroupCacheKey = bindGroupCacheKeys?.[group];
4388
+ if (bindGroupCacheKey) {
4389
+ const layoutCache = this._getLayoutBindGroupCache(bindGroupLayout);
4390
+ if (layoutCache.bindGroupsBySource.has(bindGroupCacheKey)) {
4391
+ resolvedBindGroups[group] = layoutCache.bindGroupsBySource.get(bindGroupCacheKey) || null;
4392
+ continue;
4393
+ }
4394
+ const bindGroup = this.device._createBindGroupWebGPU(
4395
+ bindGroupLayout,
4396
+ pipeline.shaderLayout,
4397
+ groupBindings,
4398
+ group
4399
+ );
4400
+ layoutCache.bindGroupsBySource.set(bindGroupCacheKey, bindGroup);
4401
+ resolvedBindGroups[group] = bindGroup;
4402
+ } else {
4403
+ resolvedBindGroups[group] = this.device._createBindGroupWebGPU(
4404
+ bindGroupLayout,
4405
+ pipeline.shaderLayout,
4406
+ groupBindings,
4407
+ group
4408
+ );
4409
+ }
4410
+ }
4411
+ return resolvedBindGroups;
4412
+ }
4413
+ _getBindGroupLayout(pipeline, group) {
4414
+ let layoutCache = this._layoutCacheByPipeline.get(pipeline);
4415
+ if (!layoutCache) {
4416
+ layoutCache = {};
4417
+ this._layoutCacheByPipeline.set(pipeline, layoutCache);
4418
+ }
4419
+ layoutCache[group] ||= this.device._createBindGroupLayoutWebGPU(pipeline, group);
4420
+ return layoutCache[group];
4421
+ }
4422
+ _getEmptyBindGroup(bindGroupLayout, shaderLayout, group) {
4423
+ const layoutCache = this._getLayoutBindGroupCache(bindGroupLayout);
4424
+ layoutCache.emptyBindGroup ||= this.device._createBindGroupWebGPU(bindGroupLayout, shaderLayout, {}, group) || null;
4425
+ return layoutCache.emptyBindGroup;
4426
+ }
4427
+ _getLayoutBindGroupCache(bindGroupLayout) {
4428
+ let layoutCache = this._bindGroupCacheByLayout.get(bindGroupLayout);
4429
+ if (!layoutCache) {
4430
+ layoutCache = { bindGroupsBySource: /* @__PURE__ */ new WeakMap() };
4431
+ this._bindGroupCacheByLayout.set(bindGroupLayout, layoutCache);
4432
+ }
4433
+ return layoutCache;
2865
4434
  }
2866
4435
  };
2867
- var RenderPipeline = _RenderPipeline;
2868
- __publicField(RenderPipeline, "defaultProps", {
2869
- ...Resource.defaultProps,
2870
- vs: null,
2871
- vertexEntryPoint: "vertexMain",
2872
- vsConstants: {},
2873
- fs: null,
2874
- fragmentEntryPoint: "fragmentMain",
2875
- fsConstants: {},
2876
- shaderLayout: null,
2877
- bufferLayout: [],
2878
- topology: "triangle-list",
2879
- colorAttachmentFormats: void 0,
2880
- depthStencilAttachmentFormat: void 0,
2881
- parameters: {},
2882
- bindings: {},
2883
- uniforms: {}
2884
- });
4436
+ function _getDefaultBindGroupFactory(device) {
4437
+ device._factories.bindGroupFactory ||= new BindGroupFactory(device);
4438
+ return device._factories.bindGroupFactory;
4439
+ }
4440
+ function getBindGroupIndicesUpToMax(bindings) {
4441
+ const maxGroup = bindings.reduce(
4442
+ (highestGroup, binding) => Math.max(highestGroup, binding.group),
4443
+ -1
4444
+ );
4445
+ return Array.from({ length: maxGroup + 1 }, (_, group) => group);
4446
+ }
4447
+ function hasBindingsInGroup(bindings, group) {
4448
+ return bindings.some((binding) => binding.group === group);
4449
+ }
2885
4450
 
2886
4451
  // src/adapter/resources/render-pass.ts
2887
4452
  var _RenderPass = class extends Resource {
@@ -2921,28 +4486,6 @@ ${htmlLog}
2921
4486
  endTimestampIndex: void 0
2922
4487
  });
2923
4488
 
2924
- // src/adapter/resources/compute-pipeline.ts
2925
- var _ComputePipeline = class extends Resource {
2926
- get [Symbol.toStringTag]() {
2927
- return "ComputePipeline";
2928
- }
2929
- hash = "";
2930
- /** The merged shader layout */
2931
- shaderLayout;
2932
- constructor(device, props) {
2933
- super(device, props, _ComputePipeline.defaultProps);
2934
- this.shaderLayout = props.shaderLayout;
2935
- }
2936
- };
2937
- var ComputePipeline = _ComputePipeline;
2938
- __publicField(ComputePipeline, "defaultProps", {
2939
- ...Resource.defaultProps,
2940
- shader: void 0,
2941
- entryPoint: void 0,
2942
- constants: {},
2943
- shaderLayout: void 0
2944
- });
2945
-
2946
4489
  // src/adapter/resources/compute-pass.ts
2947
4490
  var _ComputePass = class extends Resource {
2948
4491
  constructor(device, props) {
@@ -2965,8 +4508,69 @@ ${htmlLog}
2965
4508
  get [Symbol.toStringTag]() {
2966
4509
  return "CommandEncoder";
2967
4510
  }
4511
+ _timeProfilingQuerySet = null;
4512
+ _timeProfilingSlotCount = 0;
4513
+ _gpuTimeMs;
2968
4514
  constructor(device, props) {
2969
4515
  super(device, props, _CommandEncoder.defaultProps);
4516
+ this._timeProfilingQuerySet = props.timeProfilingQuerySet ?? null;
4517
+ this._timeProfilingSlotCount = 0;
4518
+ this._gpuTimeMs = void 0;
4519
+ }
4520
+ /**
4521
+ * Reads all resolved timestamp pairs on the current profiler query set and caches the sum
4522
+ * as milliseconds on this encoder.
4523
+ */
4524
+ async resolveTimeProfilingQuerySet() {
4525
+ this._gpuTimeMs = void 0;
4526
+ if (!this._timeProfilingQuerySet) {
4527
+ return;
4528
+ }
4529
+ const pairCount = Math.floor(this._timeProfilingSlotCount / 2);
4530
+ if (pairCount <= 0) {
4531
+ return;
4532
+ }
4533
+ const queryCount = pairCount * 2;
4534
+ const results = await this._timeProfilingQuerySet.readResults({
4535
+ firstQuery: 0,
4536
+ queryCount
4537
+ });
4538
+ let totalDurationNanoseconds = 0n;
4539
+ for (let queryIndex = 0; queryIndex < queryCount; queryIndex += 2) {
4540
+ totalDurationNanoseconds += results[queryIndex + 1] - results[queryIndex];
4541
+ }
4542
+ this._gpuTimeMs = Number(totalDurationNanoseconds) / 1e6;
4543
+ }
4544
+ /** Returns the number of query slots consumed by automatic pass profiling on this encoder. */
4545
+ getTimeProfilingSlotCount() {
4546
+ return this._timeProfilingSlotCount;
4547
+ }
4548
+ getTimeProfilingQuerySet() {
4549
+ return this._timeProfilingQuerySet;
4550
+ }
4551
+ /** Internal helper for auto-assigning timestamp slots to render/compute passes on this encoder. */
4552
+ _applyTimeProfilingToPassProps(props) {
4553
+ const passProps = props || {};
4554
+ if (!this._supportsTimestampQueries() || !this._timeProfilingQuerySet) {
4555
+ return passProps;
4556
+ }
4557
+ if (passProps.timestampQuerySet !== void 0 || passProps.beginTimestampIndex !== void 0 || passProps.endTimestampIndex !== void 0) {
4558
+ return passProps;
4559
+ }
4560
+ const beginTimestampIndex = this._timeProfilingSlotCount;
4561
+ if (beginTimestampIndex + 1 >= this._timeProfilingQuerySet.props.count) {
4562
+ return passProps;
4563
+ }
4564
+ this._timeProfilingSlotCount += 2;
4565
+ return {
4566
+ ...passProps,
4567
+ timestampQuerySet: this._timeProfilingQuerySet,
4568
+ beginTimestampIndex,
4569
+ endTimestampIndex: beginTimestampIndex + 1
4570
+ };
4571
+ }
4572
+ _supportsTimestampQueries() {
4573
+ return this.device.features.has("timestamp-query");
2970
4574
  }
2971
4575
  };
2972
4576
  var CommandEncoder = _CommandEncoder;
@@ -2975,7 +4579,8 @@ ${htmlLog}
2975
4579
  // beginComputePass(optional GPUComputePassDescriptor descriptor = {}): GPUComputePassEncoder;
2976
4580
  __publicField(CommandEncoder, "defaultProps", {
2977
4581
  ...Resource.defaultProps,
2978
- measureExecutionTime: void 0
4582
+ measureExecutionTime: void 0,
4583
+ timeProfilingQuerySet: void 0
2979
4584
  });
2980
4585
 
2981
4586
  // src/adapter/resources/command-buffer.ts
@@ -2992,13 +4597,22 @@ ${htmlLog}
2992
4597
  ...Resource.defaultProps
2993
4598
  });
2994
4599
 
2995
- // src/shadertypes/data-types/decode-shader-types.ts
4600
+ // src/shadertypes/shader-types/shader-type-decoder.ts
2996
4601
  function getVariableShaderTypeInfo(format) {
2997
- const decoded = UNIFORM_FORMATS[format];
4602
+ const resolvedFormat = resolveVariableShaderTypeAlias(format);
4603
+ const decoded = UNIFORM_FORMATS[resolvedFormat];
4604
+ if (!decoded) {
4605
+ throw new Error(`Unsupported variable shader type: ${format}`);
4606
+ }
2998
4607
  return decoded;
2999
4608
  }
3000
4609
  function getAttributeShaderTypeInfo(attributeType) {
3001
- const [primitiveType, components] = TYPE_INFO[attributeType];
4610
+ const resolvedAttributeType = resolveAttributeShaderTypeAlias(attributeType);
4611
+ const decoded = TYPE_INFO[resolvedAttributeType];
4612
+ if (!decoded) {
4613
+ throw new Error(`Unsupported attribute shader type: ${attributeType}`);
4614
+ }
4615
+ const [primitiveType, components] = decoded;
3002
4616
  const integer = primitiveType === "i32" || primitiveType === "u32";
3003
4617
  const signed = primitiveType !== "u32";
3004
4618
  const byteLength = PRIMITIVE_TYPE_SIZES[primitiveType] * components;
@@ -3010,6 +4624,33 @@ ${htmlLog}
3010
4624
  signed
3011
4625
  };
3012
4626
  }
4627
+ var ShaderTypeDecoder = class {
4628
+ getVariableShaderTypeInfo(format) {
4629
+ return getVariableShaderTypeInfo(format);
4630
+ }
4631
+ getAttributeShaderTypeInfo(attributeType) {
4632
+ return getAttributeShaderTypeInfo(attributeType);
4633
+ }
4634
+ makeShaderAttributeType(primitiveType, components) {
4635
+ return makeShaderAttributeType(primitiveType, components);
4636
+ }
4637
+ resolveAttributeShaderTypeAlias(alias) {
4638
+ return resolveAttributeShaderTypeAlias(alias);
4639
+ }
4640
+ resolveVariableShaderTypeAlias(alias) {
4641
+ return resolveVariableShaderTypeAlias(alias);
4642
+ }
4643
+ };
4644
+ function makeShaderAttributeType(primitiveType, components) {
4645
+ return components === 1 ? primitiveType : `vec${components}<${primitiveType}>`;
4646
+ }
4647
+ function resolveAttributeShaderTypeAlias(alias) {
4648
+ return WGSL_ATTRIBUTE_TYPE_ALIAS_MAP[alias] || alias;
4649
+ }
4650
+ function resolveVariableShaderTypeAlias(alias) {
4651
+ return WGSL_VARIABLE_TYPE_ALIAS_MAP[alias] || alias;
4652
+ }
4653
+ var shaderTypeDecoder = new ShaderTypeDecoder();
3013
4654
  var PRIMITIVE_TYPE_SIZES = {
3014
4655
  f32: 4,
3015
4656
  f16: 2,
@@ -3106,7 +4747,18 @@ ${htmlLog}
3106
4747
  vec4h: "vec4<f16>"
3107
4748
  };
3108
4749
  var WGSL_VARIABLE_TYPE_ALIAS_MAP = {
3109
- ...WGSL_ATTRIBUTE_TYPE_ALIAS_MAP,
4750
+ vec2i: "vec2<i32>",
4751
+ vec3i: "vec3<i32>",
4752
+ vec4i: "vec4<i32>",
4753
+ vec2u: "vec2<u32>",
4754
+ vec3u: "vec3<u32>",
4755
+ vec4u: "vec4<u32>",
4756
+ vec2f: "vec2<f32>",
4757
+ vec3f: "vec3<f32>",
4758
+ vec4f: "vec4<f32>",
4759
+ vec2h: "vec2<f16>",
4760
+ vec3h: "vec3<f16>",
4761
+ vec4h: "vec4<f16>",
3110
4762
  mat2x2f: "mat2x2<f32>",
3111
4763
  mat2x3f: "mat2x3<f32>",
3112
4764
  mat2x4f: "mat2x4<f32>",
@@ -3173,10 +4825,10 @@ ${htmlLog}
3173
4825
  if (!shaderDeclaration) {
3174
4826
  return null;
3175
4827
  }
3176
- const attributeTypeInfo = getAttributeShaderTypeInfo(shaderDeclaration.type);
3177
- const defaultVertexFormat = getCompatibleVertexFormat(attributeTypeInfo);
4828
+ const attributeTypeInfo = shaderTypeDecoder.getAttributeShaderTypeInfo(shaderDeclaration.type);
4829
+ const defaultVertexFormat = vertexFormatDecoder.getCompatibleVertexFormat(attributeTypeInfo);
3178
4830
  const vertexFormat = bufferMapping?.vertexFormat || defaultVertexFormat;
3179
- const vertexFormatInfo = getVertexFormatInfo(vertexFormat);
4831
+ const vertexFormatInfo = vertexFormatDecoder.getVertexFormatInfo(vertexFormat);
3180
4832
  return {
3181
4833
  attributeName: bufferMapping?.attributeName || shaderDeclaration.name,
3182
4834
  bufferName: bufferMapping?.bufferName || shaderDeclaration.name,
@@ -3244,7 +4896,7 @@ ${htmlLog}
3244
4896
  let byteStride = bufferLayout.byteStride;
3245
4897
  if (typeof bufferLayout.byteStride !== "number") {
3246
4898
  for (const attributeMapping2 of bufferLayout.attributes || []) {
3247
- const info = getVertexFormatInfo(attributeMapping2.format);
4899
+ const info = vertexFormatDecoder.getVertexFormatInfo(attributeMapping2.format);
3248
4900
  byteStride += info.byteLength;
3249
4901
  }
3250
4902
  }
@@ -3332,6 +4984,20 @@ ${htmlLog}
3332
4984
  count: void 0
3333
4985
  });
3334
4986
 
4987
+ // src/adapter/resources/fence.ts
4988
+ var _Fence = class extends Resource {
4989
+ get [Symbol.toStringTag]() {
4990
+ return "Fence";
4991
+ }
4992
+ constructor(device, props = {}) {
4993
+ super(device, props, _Fence.defaultProps);
4994
+ }
4995
+ };
4996
+ var Fence = _Fence;
4997
+ __publicField(Fence, "defaultProps", {
4998
+ ...Resource.defaultProps
4999
+ });
5000
+
3335
5001
  // src/adapter/resources/pipeline-layout.ts
3336
5002
  var _PipelineLayout = class extends Resource {
3337
5003
  get [Symbol.toStringTag]() {
@@ -3350,6 +5016,200 @@ ${htmlLog}
3350
5016
  }
3351
5017
  });
3352
5018
 
5019
+ // src/shadertypes/data-types/decode-data-types.ts
5020
+ function alignTo(size, count) {
5021
+ switch (count) {
5022
+ case 1:
5023
+ return size;
5024
+ case 2:
5025
+ return size + size % 2;
5026
+ default:
5027
+ return size + (4 - size % 4) % 4;
5028
+ }
5029
+ }
5030
+ function getTypedArrayConstructor(type) {
5031
+ const [, , , , Constructor] = NORMALIZED_TYPE_MAP2[type];
5032
+ return Constructor;
5033
+ }
5034
+ var NORMALIZED_TYPE_MAP2 = {
5035
+ uint8: ["uint8", "u32", 1, false, Uint8Array],
5036
+ sint8: ["sint8", "i32", 1, false, Int8Array],
5037
+ unorm8: ["uint8", "f32", 1, true, Uint8Array],
5038
+ snorm8: ["sint8", "f32", 1, true, Int8Array],
5039
+ uint16: ["uint16", "u32", 2, false, Uint16Array],
5040
+ sint16: ["sint16", "i32", 2, false, Int16Array],
5041
+ unorm16: ["uint16", "u32", 2, true, Uint16Array],
5042
+ snorm16: ["sint16", "i32", 2, true, Int16Array],
5043
+ float16: ["float16", "f16", 2, false, Uint16Array],
5044
+ float32: ["float32", "f32", 4, false, Float32Array],
5045
+ uint32: ["uint32", "u32", 4, false, Uint32Array],
5046
+ sint32: ["sint32", "i32", 4, false, Int32Array]
5047
+ };
5048
+
5049
+ // src/shadertypes/shader-types/shader-block-layout.ts
5050
+ function makeShaderBlockLayout(uniformTypes, options = {}) {
5051
+ const copiedUniformTypes = { ...uniformTypes };
5052
+ const layout = options.layout ?? "std140";
5053
+ const fields = {};
5054
+ let size = 0;
5055
+ for (const [key, uniformType] of Object.entries(copiedUniformTypes)) {
5056
+ size = addToLayout(fields, key, uniformType, size, layout);
5057
+ }
5058
+ size = alignTo(size, getTypeAlignment(copiedUniformTypes, layout));
5059
+ return {
5060
+ layout,
5061
+ byteLength: size * 4,
5062
+ uniformTypes: copiedUniformTypes,
5063
+ fields
5064
+ };
5065
+ }
5066
+ function getLeafLayoutInfo(type, layout) {
5067
+ const resolvedType = resolveVariableShaderTypeAlias(type);
5068
+ const decodedType = getVariableShaderTypeInfo(resolvedType);
5069
+ const matrixMatch = /^mat(\d)x(\d)<.+>$/.exec(resolvedType);
5070
+ if (matrixMatch) {
5071
+ const columns = Number(matrixMatch[1]);
5072
+ const rows = Number(matrixMatch[2]);
5073
+ const columnInfo = getVectorLayoutInfo(
5074
+ rows,
5075
+ resolvedType,
5076
+ decodedType.type,
5077
+ layout
5078
+ );
5079
+ const columnStride = getMatrixColumnStride(columnInfo.size, columnInfo.alignment, layout);
5080
+ return {
5081
+ alignment: columnInfo.alignment,
5082
+ size: columns * columnStride,
5083
+ components: columns * rows,
5084
+ columns,
5085
+ rows,
5086
+ columnStride,
5087
+ shaderType: resolvedType,
5088
+ type: decodedType.type
5089
+ };
5090
+ }
5091
+ const vectorMatch = /^vec(\d)<.+>$/.exec(resolvedType);
5092
+ if (vectorMatch) {
5093
+ return getVectorLayoutInfo(
5094
+ Number(vectorMatch[1]),
5095
+ resolvedType,
5096
+ decodedType.type,
5097
+ layout
5098
+ );
5099
+ }
5100
+ return {
5101
+ alignment: 1,
5102
+ size: 1,
5103
+ components: 1,
5104
+ columns: 1,
5105
+ rows: 1,
5106
+ columnStride: 1,
5107
+ shaderType: resolvedType,
5108
+ type: decodedType.type
5109
+ };
5110
+ }
5111
+ function isCompositeShaderTypeStruct(value) {
5112
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5113
+ }
5114
+ function addToLayout(fields, name2, type, offset, layout) {
5115
+ if (typeof type === "string") {
5116
+ const info = getLeafLayoutInfo(type, layout);
5117
+ const alignedOffset = alignTo(offset, info.alignment);
5118
+ fields[name2] = {
5119
+ offset: alignedOffset,
5120
+ ...info
5121
+ };
5122
+ return alignedOffset + info.size;
5123
+ }
5124
+ if (Array.isArray(type)) {
5125
+ if (Array.isArray(type[0])) {
5126
+ throw new Error(`Nested arrays are not supported for ${name2}`);
5127
+ }
5128
+ const elementType = type[0];
5129
+ const length = type[1];
5130
+ const stride = getArrayStride(elementType, layout);
5131
+ const arrayOffset = alignTo(offset, getTypeAlignment(type, layout));
5132
+ for (let i = 0; i < length; i++) {
5133
+ addToLayout(fields, `${name2}[${i}]`, elementType, arrayOffset + i * stride, layout);
5134
+ }
5135
+ return arrayOffset + stride * length;
5136
+ }
5137
+ if (isCompositeShaderTypeStruct(type)) {
5138
+ const structAlignment = getTypeAlignment(type, layout);
5139
+ let structOffset = alignTo(offset, structAlignment);
5140
+ for (const [memberName, memberType] of Object.entries(type)) {
5141
+ structOffset = addToLayout(fields, `${name2}.${memberName}`, memberType, structOffset, layout);
5142
+ }
5143
+ return alignTo(structOffset, structAlignment);
5144
+ }
5145
+ throw new Error(`Unsupported CompositeShaderType for ${name2}`);
5146
+ }
5147
+ function getTypeSize(type, layout) {
5148
+ if (typeof type === "string") {
5149
+ return getLeafLayoutInfo(type, layout).size;
5150
+ }
5151
+ if (Array.isArray(type)) {
5152
+ const elementType = type[0];
5153
+ const length = type[1];
5154
+ if (Array.isArray(elementType)) {
5155
+ throw new Error("Nested arrays are not supported");
5156
+ }
5157
+ return getArrayStride(elementType, layout) * length;
5158
+ }
5159
+ let size = 0;
5160
+ for (const memberType of Object.values(type)) {
5161
+ const compositeMemberType = memberType;
5162
+ size = alignTo(size, getTypeAlignment(compositeMemberType, layout));
5163
+ size += getTypeSize(compositeMemberType, layout);
5164
+ }
5165
+ return alignTo(size, getTypeAlignment(type, layout));
5166
+ }
5167
+ function getTypeAlignment(type, layout) {
5168
+ if (typeof type === "string") {
5169
+ return getLeafLayoutInfo(type, layout).alignment;
5170
+ }
5171
+ if (Array.isArray(type)) {
5172
+ const elementType = type[0];
5173
+ const elementAlignment = getTypeAlignment(elementType, layout);
5174
+ return uses16ByteArrayAlignment(layout) ? Math.max(elementAlignment, 4) : elementAlignment;
5175
+ }
5176
+ let maxAlignment = 1;
5177
+ for (const memberType of Object.values(type)) {
5178
+ const memberAlignment = getTypeAlignment(memberType, layout);
5179
+ maxAlignment = Math.max(maxAlignment, memberAlignment);
5180
+ }
5181
+ return uses16ByteStructAlignment(layout) ? Math.max(maxAlignment, 4) : maxAlignment;
5182
+ }
5183
+ function getVectorLayoutInfo(components, shaderType, type, layout) {
5184
+ return {
5185
+ alignment: components === 2 ? 2 : 4,
5186
+ size: components === 3 ? 3 : components,
5187
+ components,
5188
+ columns: 1,
5189
+ rows: components,
5190
+ columnStride: components === 3 ? 3 : components,
5191
+ shaderType,
5192
+ type
5193
+ };
5194
+ }
5195
+ function getArrayStride(elementType, layout) {
5196
+ const elementSize = getTypeSize(elementType, layout);
5197
+ const elementAlignment = getTypeAlignment(elementType, layout);
5198
+ return getArrayLikeStride(elementSize, elementAlignment, layout);
5199
+ }
5200
+ function getArrayLikeStride(size, alignment, layout) {
5201
+ return alignTo(size, uses16ByteArrayAlignment(layout) ? 4 : alignment);
5202
+ }
5203
+ function getMatrixColumnStride(size, alignment, layout) {
5204
+ return layout === "std140" ? 4 : alignTo(size, alignment);
5205
+ }
5206
+ function uses16ByteArrayAlignment(layout) {
5207
+ return layout === "std140" || layout === "wgsl-uniform";
5208
+ }
5209
+ function uses16ByteStructAlignment(layout) {
5210
+ return layout === "std140" || layout === "wgsl-uniform";
5211
+ }
5212
+
3353
5213
  // src/utils/array-utils-flat.ts
3354
5214
  var arrayBuffer;
3355
5215
  function getScratchArrayBuffer(byteLength) {
@@ -3374,92 +5234,201 @@ ${htmlLog}
3374
5234
  return isTypedArray(value);
3375
5235
  }
3376
5236
 
3377
- // src/portable/uniform-buffer-layout.ts
3378
- var minBufferSize = 1024;
3379
- var UniformBufferLayout = class {
3380
- layout = {};
3381
- /** number of bytes needed for buffer allocation */
3382
- byteLength;
3383
- /** Create a new UniformBufferLayout given a map of attributes. */
3384
- constructor(uniformTypes, uniformSizes = {}) {
3385
- let size = 0;
3386
- for (const [key, uniformType] of Object.entries(uniformTypes)) {
3387
- const typeAndComponents = getVariableShaderTypeInfo(uniformType);
3388
- const { type, components } = typeAndComponents;
3389
- const count = components * (uniformSizes?.[key] ?? 1);
3390
- size = alignTo(size, count);
3391
- const offset = size;
3392
- size += count;
3393
- this.layout[key] = { type, size: count, offset };
3394
- }
3395
- size += (4 - size % 4) % 4;
3396
- const actualByteLength = size * 4;
3397
- this.byteLength = Math.max(actualByteLength, minBufferSize);
3398
- }
3399
- /** Get the data for the complete buffer */
5237
+ // src/portable/shader-block-writer.ts
5238
+ var ShaderBlockWriter = class {
5239
+ /** Layout metadata used to flatten and serialize values. */
5240
+ layout;
5241
+ /**
5242
+ * Creates a writer for a precomputed shader-block layout.
5243
+ */
5244
+ constructor(layout) {
5245
+ this.layout = layout;
5246
+ }
5247
+ /**
5248
+ * Returns `true` if the flattened layout contains the given field.
5249
+ */
5250
+ has(name2) {
5251
+ return Boolean(this.layout.fields[name2]);
5252
+ }
5253
+ /**
5254
+ * Returns offset and size metadata for a flattened field.
5255
+ */
5256
+ get(name2) {
5257
+ const entry = this.layout.fields[name2];
5258
+ return entry ? { offset: entry.offset, size: entry.size } : void 0;
5259
+ }
5260
+ /**
5261
+ * Flattens nested composite values into leaf-path values understood by {@link UniformBlock}.
5262
+ *
5263
+ * Top-level values may be supplied either in nested object form matching the
5264
+ * declared composite shader types or as already-flattened leaf-path values.
5265
+ */
5266
+ getFlatUniformValues(uniformValues) {
5267
+ const flattenedUniformValues = {};
5268
+ for (const [name2, value] of Object.entries(uniformValues)) {
5269
+ const uniformType = this.layout.uniformTypes[name2];
5270
+ if (uniformType) {
5271
+ this._flattenCompositeValue(flattenedUniformValues, name2, uniformType, value);
5272
+ } else if (this.layout.fields[name2]) {
5273
+ flattenedUniformValues[name2] = value;
5274
+ }
5275
+ }
5276
+ return flattenedUniformValues;
5277
+ }
5278
+ /**
5279
+ * Serializes the supplied values into buffer-backed binary data.
5280
+ *
5281
+ * The returned view length matches {@link ShaderBlockLayout.byteLength}, which
5282
+ * is the exact packed size of the block.
5283
+ */
3400
5284
  getData(uniformValues) {
3401
- const arrayBuffer2 = getScratchArrayBuffer(this.byteLength);
5285
+ const buffer = getScratchArrayBuffer(this.layout.byteLength);
5286
+ new Uint8Array(buffer, 0, this.layout.byteLength).fill(0);
3402
5287
  const typedArrays = {
3403
- i32: new Int32Array(arrayBuffer2),
3404
- u32: new Uint32Array(arrayBuffer2),
3405
- f32: new Float32Array(arrayBuffer2),
3406
- // TODO not implemented
3407
- f16: new Uint16Array(arrayBuffer2)
5288
+ i32: new Int32Array(buffer),
5289
+ u32: new Uint32Array(buffer),
5290
+ f32: new Float32Array(buffer),
5291
+ f16: new Uint16Array(buffer)
3408
5292
  };
3409
- for (const [name2, value] of Object.entries(uniformValues)) {
3410
- const uniformLayout = this.layout[name2];
3411
- if (!uniformLayout) {
3412
- log.warn(`Supplied uniform value ${name2} not present in uniform block layout`)();
3413
- continue;
5293
+ const flattenedUniformValues = this.getFlatUniformValues(uniformValues);
5294
+ for (const [name2, value] of Object.entries(flattenedUniformValues)) {
5295
+ this._writeLeafValue(typedArrays, name2, value);
5296
+ }
5297
+ return new Uint8Array(buffer, 0, this.layout.byteLength);
5298
+ }
5299
+ /**
5300
+ * Recursively flattens nested values using the declared composite shader type.
5301
+ */
5302
+ _flattenCompositeValue(flattenedUniformValues, baseName, uniformType, value) {
5303
+ if (value === void 0) {
5304
+ return;
5305
+ }
5306
+ if (typeof uniformType === "string" || this.layout.fields[baseName]) {
5307
+ flattenedUniformValues[baseName] = value;
5308
+ return;
5309
+ }
5310
+ if (Array.isArray(uniformType)) {
5311
+ const elementType = uniformType[0];
5312
+ const length = uniformType[1];
5313
+ if (Array.isArray(elementType)) {
5314
+ throw new Error(`Nested arrays are not supported for ${baseName}`);
3414
5315
  }
3415
- const { type, size, offset } = uniformLayout;
3416
- const typedArray = typedArrays[type];
3417
- if (size === 1) {
3418
- if (typeof value !== "number" && typeof value !== "boolean") {
3419
- log.warn(
3420
- `Supplied value for single component uniform ${name2} is not a number: ${value}`
3421
- )();
5316
+ if (typeof elementType === "string" && isNumberArray(value)) {
5317
+ this._flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value);
5318
+ return;
5319
+ }
5320
+ if (!Array.isArray(value)) {
5321
+ log.warn(`Unsupported uniform array value for ${baseName}:`, value)();
5322
+ return;
5323
+ }
5324
+ for (let index = 0; index < Math.min(value.length, length); index++) {
5325
+ const elementValue = value[index];
5326
+ if (elementValue === void 0) {
3422
5327
  continue;
3423
5328
  }
3424
- typedArray[offset] = Number(value);
3425
- } else {
3426
- if (!isNumberArray(value)) {
3427
- log.warn(
3428
- `Supplied value for multi component / array uniform ${name2} is not a numeric array: ${value}`
3429
- )();
5329
+ this._flattenCompositeValue(
5330
+ flattenedUniformValues,
5331
+ `${baseName}[${index}]`,
5332
+ elementType,
5333
+ elementValue
5334
+ );
5335
+ }
5336
+ return;
5337
+ }
5338
+ if (isCompositeShaderTypeStruct(uniformType) && isCompositeUniformObject(value)) {
5339
+ for (const [key, subValue] of Object.entries(value)) {
5340
+ if (subValue === void 0) {
3430
5341
  continue;
3431
5342
  }
3432
- typedArray.set(value, offset);
5343
+ const nestedName = `${baseName}.${key}`;
5344
+ this._flattenCompositeValue(flattenedUniformValues, nestedName, uniformType[key], subValue);
3433
5345
  }
5346
+ return;
3434
5347
  }
3435
- return new Uint8Array(arrayBuffer2, 0, this.byteLength);
5348
+ log.warn(`Unsupported uniform value for ${baseName}:`, value)();
3436
5349
  }
3437
- /** Does this layout have a field with specified name */
3438
- has(name2) {
3439
- return Boolean(this.layout[name2]);
5350
+ /**
5351
+ * Expands tightly packed numeric arrays into per-element leaf fields.
5352
+ */
5353
+ _flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value) {
5354
+ const numericValue = value;
5355
+ const elementLayout = getLeafLayoutInfo(elementType, this.layout.layout);
5356
+ const packedElementLength = elementLayout.components;
5357
+ for (let index = 0; index < length; index++) {
5358
+ const start = index * packedElementLength;
5359
+ if (start >= numericValue.length) {
5360
+ break;
5361
+ }
5362
+ if (packedElementLength === 1) {
5363
+ flattenedUniformValues[`${baseName}[${index}]`] = Number(numericValue[start]);
5364
+ } else {
5365
+ flattenedUniformValues[`${baseName}[${index}]`] = sliceNumericArray(
5366
+ value,
5367
+ start,
5368
+ start + packedElementLength
5369
+ );
5370
+ }
5371
+ }
3440
5372
  }
3441
- /** Get offset and size for a field with specified name */
3442
- get(name2) {
3443
- const layout = this.layout[name2];
3444
- return layout;
5373
+ /**
5374
+ * Writes one flattened leaf value into its typed-array view.
5375
+ */
5376
+ _writeLeafValue(typedArrays, name2, value) {
5377
+ const entry = this.layout.fields[name2];
5378
+ if (!entry) {
5379
+ log.warn(`Uniform ${name2} not found in layout`)();
5380
+ return;
5381
+ }
5382
+ const { type, components, columns, rows, offset, columnStride } = entry;
5383
+ const array = typedArrays[type];
5384
+ if (components === 1) {
5385
+ array[offset] = Number(value);
5386
+ return;
5387
+ }
5388
+ const sourceValue = value;
5389
+ if (columns === 1) {
5390
+ for (let componentIndex = 0; componentIndex < components; componentIndex++) {
5391
+ array[offset + componentIndex] = Number(sourceValue[componentIndex] ?? 0);
5392
+ }
5393
+ return;
5394
+ }
5395
+ let sourceIndex = 0;
5396
+ for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
5397
+ const columnOffset = offset + columnIndex * columnStride;
5398
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
5399
+ array[columnOffset + rowIndex] = Number(sourceValue[sourceIndex++] ?? 0);
5400
+ }
5401
+ }
3445
5402
  }
3446
5403
  };
5404
+ function isCompositeUniformObject(value) {
5405
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && !ArrayBuffer.isView(value);
5406
+ }
5407
+ function sliceNumericArray(value, start, end) {
5408
+ return Array.prototype.slice.call(value, start, end);
5409
+ }
3447
5410
 
3448
5411
  // src/utils/array-equal.ts
5412
+ var MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH = 128;
3449
5413
  function arrayEqual(a, b, limit = 16) {
3450
- if (a !== b) {
3451
- return false;
5414
+ if (a === b) {
5415
+ return true;
3452
5416
  }
3453
5417
  const arrayA = a;
3454
5418
  const arrayB = b;
3455
- if (!isNumberArray(arrayA)) {
5419
+ if (!isNumberArray(arrayA) || !isNumberArray(arrayB)) {
3456
5420
  return false;
3457
5421
  }
3458
- if (isNumberArray(arrayB) && arrayA.length === arrayB.length) {
3459
- for (let i = 0; i < arrayA.length; ++i) {
3460
- if (arrayB[i] !== arrayA[i]) {
3461
- return false;
3462
- }
5422
+ if (arrayA.length !== arrayB.length) {
5423
+ return false;
5424
+ }
5425
+ const maxCompareLength = Math.min(limit, MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH);
5426
+ if (arrayA.length > maxCompareLength) {
5427
+ return false;
5428
+ }
5429
+ for (let i = 0; i < arrayA.length; ++i) {
5430
+ if (arrayB[i] !== arrayA[i]) {
5431
+ return false;
3463
5432
  }
3464
5433
  }
3465
5434
  return true;
@@ -3524,27 +5493,33 @@ ${htmlLog}
3524
5493
  };
3525
5494
 
3526
5495
  // src/portable/uniform-store.ts
5496
+ var minUniformBufferSize = 1024;
3527
5497
  var UniformStore = class {
5498
+ /** Device used to infer layout and allocate buffers. */
5499
+ device;
3528
5500
  /** Stores the uniform values for each uniform block */
3529
5501
  uniformBlocks = /* @__PURE__ */ new Map();
3530
- /** Can generate data for a uniform buffer for each block from data */
3531
- uniformBufferLayouts = /* @__PURE__ */ new Map();
5502
+ /** Flattened layout metadata for each block. */
5503
+ shaderBlockLayouts = /* @__PURE__ */ new Map();
5504
+ /** Serializers for block-backed uniform data. */
5505
+ shaderBlockWriters = /* @__PURE__ */ new Map();
3532
5506
  /** Actual buffer for the blocks */
3533
5507
  uniformBuffers = /* @__PURE__ */ new Map();
3534
5508
  /**
3535
- * Create a new UniformStore instance
3536
- * @param blocks
5509
+ * Creates a new {@link UniformStore} for the supplied device and block definitions.
3537
5510
  */
3538
- constructor(blocks) {
5511
+ constructor(device, blocks) {
5512
+ this.device = device;
3539
5513
  for (const [bufferName, block] of Object.entries(blocks)) {
3540
5514
  const uniformBufferName = bufferName;
3541
- const uniformBufferLayout = new UniformBufferLayout(
3542
- block.uniformTypes ?? {},
3543
- block.uniformSizes ?? {}
3544
- );
3545
- this.uniformBufferLayouts.set(uniformBufferName, uniformBufferLayout);
5515
+ const shaderBlockLayout = makeShaderBlockLayout(block.uniformTypes ?? {}, {
5516
+ layout: block.layout ?? getDefaultUniformBufferLayout(device)
5517
+ });
5518
+ const shaderBlockWriter = new ShaderBlockWriter(shaderBlockLayout);
5519
+ this.shaderBlockLayouts.set(uniformBufferName, shaderBlockLayout);
5520
+ this.shaderBlockWriters.set(uniformBufferName, shaderBlockWriter);
3546
5521
  const uniformBlock = new UniformBlock({ name: bufferName });
3547
- uniformBlock.setUniforms(block.defaultUniforms || {});
5522
+ uniformBlock.setUniforms(shaderBlockWriter.getFlatUniformValues(block.defaultUniforms || {}));
3548
5523
  this.uniformBlocks.set(uniformBufferName, uniformBlock);
3549
5524
  }
3550
5525
  }
@@ -3556,33 +5531,51 @@ ${htmlLog}
3556
5531
  }
3557
5532
  /**
3558
5533
  * Set uniforms
3559
- * Makes all properties partial
5534
+ *
5535
+ * Makes all group properties partial and eagerly propagates changes to any
5536
+ * managed GPU buffers.
3560
5537
  */
3561
5538
  setUniforms(uniforms) {
3562
5539
  for (const [blockName, uniformValues] of Object.entries(uniforms)) {
3563
- this.uniformBlocks.get(blockName)?.setUniforms(uniformValues);
5540
+ const uniformBufferName = blockName;
5541
+ const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
5542
+ const flattenedUniforms = shaderBlockWriter?.getFlatUniformValues(
5543
+ uniformValues || {}
5544
+ );
5545
+ this.uniformBlocks.get(uniformBufferName)?.setUniforms(flattenedUniforms || {});
3564
5546
  }
3565
5547
  this.updateUniformBuffers();
3566
5548
  }
3567
- /** Get the required minimum length of the uniform buffer */
5549
+ /**
5550
+ * Returns the allocation size for the named uniform buffer.
5551
+ *
5552
+ * This may exceed the packed layout size because minimum buffer-size policy is
5553
+ * applied at the store layer.
5554
+ */
3568
5555
  getUniformBufferByteLength(uniformBufferName) {
3569
- return this.uniformBufferLayouts.get(uniformBufferName)?.byteLength || 0;
5556
+ const packedByteLength = this.shaderBlockLayouts.get(uniformBufferName)?.byteLength || 0;
5557
+ return Math.max(packedByteLength, minUniformBufferSize);
3570
5558
  }
3571
- /** Get formatted binary memory that can be uploaded to a buffer */
5559
+ /**
5560
+ * Returns packed binary data that can be uploaded to the named uniform buffer.
5561
+ *
5562
+ * The returned view length matches the packed block size and is not padded to
5563
+ * the store's minimum allocation size.
5564
+ */
3572
5565
  getUniformBufferData(uniformBufferName) {
3573
5566
  const uniformValues = this.uniformBlocks.get(uniformBufferName)?.getAllUniforms() || {};
3574
- return this.uniformBufferLayouts.get(uniformBufferName)?.getData(uniformValues);
5567
+ const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
5568
+ return shaderBlockWriter?.getData(uniformValues) || new Uint8Array(0);
3575
5569
  }
3576
5570
  /**
3577
- * Creates an unmanaged uniform buffer (umnanaged means that application is responsible for destroying it)
3578
- * The new buffer is initialized with current / supplied values
5571
+ * Creates an unmanaged uniform buffer initialized with the current or supplied values.
3579
5572
  */
3580
- createUniformBuffer(device, uniformBufferName, uniforms) {
5573
+ createUniformBuffer(uniformBufferName, uniforms) {
3581
5574
  if (uniforms) {
3582
5575
  this.setUniforms(uniforms);
3583
5576
  }
3584
5577
  const byteLength = this.getUniformBufferByteLength(uniformBufferName);
3585
- const uniformBuffer = device.createBuffer({
5578
+ const uniformBuffer = this.device.createBuffer({
3586
5579
  usage: Buffer2.UNIFORM | Buffer2.COPY_DST,
3587
5580
  byteLength
3588
5581
  });
@@ -3590,11 +5583,11 @@ ${htmlLog}
3590
5583
  uniformBuffer.write(uniformBufferData);
3591
5584
  return uniformBuffer;
3592
5585
  }
3593
- /** Get the managed uniform buffer. "managed" resources are destroyed when the uniformStore is destroyed. */
3594
- getManagedUniformBuffer(device, uniformBufferName) {
5586
+ /** Returns the managed uniform buffer for the named block. */
5587
+ getManagedUniformBuffer(uniformBufferName) {
3595
5588
  if (!this.uniformBuffers.get(uniformBufferName)) {
3596
5589
  const byteLength = this.getUniformBufferByteLength(uniformBufferName);
3597
- const uniformBuffer = device.createBuffer({
5590
+ const uniformBuffer = this.device.createBuffer({
3598
5591
  usage: Buffer2.UNIFORM | Buffer2.COPY_DST,
3599
5592
  byteLength
3600
5593
  });
@@ -3602,7 +5595,11 @@ ${htmlLog}
3602
5595
  }
3603
5596
  return this.uniformBuffers.get(uniformBufferName);
3604
5597
  }
3605
- /** Updates all uniform buffers where values have changed */
5598
+ /**
5599
+ * Updates every managed uniform buffer whose source uniforms have changed.
5600
+ *
5601
+ * @returns The first redraw reason encountered, or `false` if nothing changed.
5602
+ */
3606
5603
  updateUniformBuffers() {
3607
5604
  let reason = false;
3608
5605
  for (const uniformBufferName of this.uniformBlocks.keys()) {
@@ -3614,7 +5611,11 @@ ${htmlLog}
3614
5611
  }
3615
5612
  return reason;
3616
5613
  }
3617
- /** Update one uniform buffer. Only updates if values have changed */
5614
+ /**
5615
+ * Updates one managed uniform buffer if its corresponding block is dirty.
5616
+ *
5617
+ * @returns The redraw reason for the update, or `false` if no write occurred.
5618
+ */
3618
5619
  updateUniformBuffer(uniformBufferName) {
3619
5620
  const uniformBlock = this.uniformBlocks.get(uniformBufferName);
3620
5621
  let uniformBuffer = this.uniformBuffers.get(uniformBufferName);
@@ -3635,8 +5636,49 @@ ${htmlLog}
3635
5636
  return reason;
3636
5637
  }
3637
5638
  };
5639
+ function getDefaultUniformBufferLayout(device) {
5640
+ return device.type === "webgpu" ? "wgsl-uniform" : "std140";
5641
+ }
5642
+
5643
+ // src/shadertypes/texture-types/texture-layout.ts
5644
+ function getTextureImageView(arrayBuffer2, memoryLayout, format, image = 0) {
5645
+ const formatInfo = textureFormatDecoder.getInfo(format);
5646
+ const bytesPerComponent = formatInfo.bytesPerPixel / formatInfo.components;
5647
+ const { bytesPerImage } = memoryLayout;
5648
+ const offset = bytesPerImage * image;
5649
+ const totalPixels = memoryLayout.bytesPerImage / bytesPerComponent;
5650
+ switch (format) {
5651
+ case "rgba8unorm":
5652
+ case "bgra8unorm":
5653
+ case "rgba8uint":
5654
+ return new Uint8Array(arrayBuffer2, offset, totalPixels);
5655
+ case "r8unorm":
5656
+ return new Uint8Array(arrayBuffer2, offset, totalPixels);
5657
+ case "r16uint":
5658
+ case "rgba16uint":
5659
+ return new Uint16Array(arrayBuffer2, offset, totalPixels);
5660
+ case "r32uint":
5661
+ case "rgba32uint":
5662
+ return new Uint32Array(arrayBuffer2, offset, totalPixels);
5663
+ case "r32float":
5664
+ return new Float32Array(arrayBuffer2, offset, totalPixels);
5665
+ case "rgba16float":
5666
+ return new Uint16Array(arrayBuffer2, offset, totalPixels);
5667
+ case "rgba32float":
5668
+ return new Float32Array(arrayBuffer2, offset, totalPixels);
5669
+ default:
5670
+ throw new Error(`Unsupported format: ${format}`);
5671
+ }
5672
+ }
5673
+ function setTextureImageData(arrayBuffer2, memoryLayout, format, data, image = 0) {
5674
+ const offset = 0;
5675
+ const totalPixels = memoryLayout.bytesPerImage / memoryLayout.bytesPerPixel;
5676
+ const subArray = data.subarray(0, totalPixels);
5677
+ const typedArray = getTextureImageView(arrayBuffer2, memoryLayout, format, image);
5678
+ typedArray.set(subArray, offset);
5679
+ }
3638
5680
 
3639
- // src/shadertypes/textures/pixel-utils.ts
5681
+ // src/shadertypes/texture-types/pixel-utils.ts
3640
5682
  function readPixel(pixelData, x, y, bitsPerChannel) {
3641
5683
  if (x < 0 || x >= pixelData.width || y < 0 || y >= pixelData.height) {
3642
5684
  throw new Error("Coordinates out of bounds.");
@@ -3646,7 +5688,7 @@ ${htmlLog}
3646
5688
  let bitOffsetWithinPixel = 0;
3647
5689
  const channels = [];
3648
5690
  for (let i = 0; i < 4; i++) {
3649
- const bits = bitsPerChannel[i];
5691
+ const bits = bitsPerChannel[i] ?? 0;
3650
5692
  if (bits <= 0) {
3651
5693
  channels.push(0);
3652
5694
  } else {
@@ -3655,14 +5697,14 @@ ${htmlLog}
3655
5697
  bitOffsetWithinPixel += bits;
3656
5698
  }
3657
5699
  }
3658
- return [channels[0], channels[1], channels[2], channels[3]];
5700
+ return [channels[0] ?? 0, channels[1] ?? 0, channels[2] ?? 0, channels[3] ?? 0];
3659
5701
  }
3660
5702
  function writePixel(dataView, bitOffset, bitsPerChannel, pixel) {
3661
5703
  let currentBitOffset = bitOffset;
3662
5704
  for (let channel = 0; channel < 4; channel++) {
3663
- const bits = bitsPerChannel[channel];
5705
+ const bits = bitsPerChannel[channel] ?? 0;
3664
5706
  const maxValue = (1 << bits) - 1;
3665
- const channelValue = pixel[channel] & maxValue;
5707
+ const channelValue = (pixel[channel] ?? 0) & maxValue;
3666
5708
  writeBitsToDataView(dataView, currentBitOffset, bits, channelValue);
3667
5709
  currentBitOffset += bits;
3668
5710
  }