@woosh/meep-engine 3.12.0 → 3.13.1

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 (301) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  3. package/src/core/geom/3d/shape/ConvexHullShape3D.js +41 -25
  4. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  5. package/src/core/geom/3d/shape/PosedShape3D.js +7 -2
  6. package/src/engine/graphics3/decal/GPUDecalRenderer.js +3 -3
  7. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  8. package/src/engine/graphics3/decal/shader_decal_composite.js +4 -2
  9. package/src/engine/graphics3/highlight/GPUHighlightRenderer.d.ts.map +1 -1
  10. package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +2 -3
  11. package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
  12. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  13. package/src/shade/RENDERER_CONTRACT.md +2 -1
  14. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts +18 -0
  15. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts.map +1 -1
  16. package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.js +73 -0
  17. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +755 -0
  18. package/src/shade/device/ShadeGPUCommandContext.d.ts +10 -16
  19. package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
  20. package/src/shade/device/ShadeGPUCommandContext.js +801 -994
  21. package/src/shade/device/pass/ShadeGPUComputePassEncoder.d.ts +107 -0
  22. package/src/shade/device/pass/ShadeGPUComputePassEncoder.d.ts.map +1 -0
  23. package/src/shade/device/pass/ShadeGPUComputePassEncoder.js +222 -0
  24. package/src/shade/device/pass/ShadeGPURenderPassEncoder.d.ts +168 -0
  25. package/src/shade/device/pass/ShadeGPURenderPassEncoder.d.ts.map +1 -0
  26. package/src/shade/device/pass/ShadeGPURenderPassEncoder.js +317 -0
  27. package/src/shade/device/timing/GPUTimerArray.d.ts +4 -9
  28. package/src/shade/device/timing/GPUTimerArray.d.ts.map +1 -1
  29. package/src/shade/device/timing/GPUTimerArray.js +261 -289
  30. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +20 -2
  31. package/src/shade/device/timing/ShadeGPUContextProfiler.d.ts +59 -0
  32. package/src/shade/device/timing/ShadeGPUContextProfiler.d.ts.map +1 -0
  33. package/src/shade/device/timing/ShadeGPUContextProfiler.js +182 -0
  34. package/src/shade/device/timing/profile/GPUProfileSession.d.ts +4 -4
  35. package/src/shade/device/timing/profile/GPUProfileSession.js +4 -4
  36. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_leaf_triangles.d.ts.map +1 -1
  37. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_leaf_triangles.js +3 -1
  38. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_points.d.ts.map +1 -1
  39. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_points.js +3 -1
  40. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_visibility.d.ts.map +1 -1
  41. package/src/shade/playground/skinned_blas_refit/shader_blas_audit_visibility.js +3 -1
  42. package/src/shade/playground/skinned_blas_refit/verify_leaf_triangles.js +2 -2
  43. package/src/shade/playground/skinned_blas_refit/verify_traversal_reachability.js +2 -2
  44. package/src/shade/renderer/Renderer.d.ts +0 -6
  45. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  46. package/src/shade/renderer/Renderer.js +0 -29
  47. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  48. package/src/shade/renderer/animation/GPUAnimationManager.js +1 -3
  49. package/src/shade/renderer/animation/shader_animation_tick.d.ts.map +1 -1
  50. package/src/shade/renderer/animation/shader_animation_tick.js +128 -125
  51. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts.map +1 -1
  52. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +8 -18
  53. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_geometry_sphere_derive.d.ts.map +1 -1
  54. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_geometry_sphere_derive.js +100 -98
  55. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.d.ts.map +1 -1
  56. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.js +215 -212
  57. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.d.ts +1 -1
  58. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.d.ts.map +1 -1
  59. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_scatter.js +135 -120
  60. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_skinned_mesh_bounds_refresh.d.ts.map +1 -1
  61. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_skinned_mesh_bounds_refresh.js +77 -75
  62. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.d.ts.map +1 -1
  63. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.js +375 -371
  64. package/src/shade/renderer/atmosphere/shader_transmittance_lut.js +3 -3
  65. package/src/shade/renderer/buffer/graph_inspect_gpu_buffer.js +1 -1
  66. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  67. package/src/shade/renderer/buffer/table/GPUDatabase.js +14 -19
  68. package/src/shade/renderer/buffer/table/gpu_table_element_upload_shader.d.ts.map +1 -1
  69. package/src/shade/renderer/buffer/table/gpu_table_element_upload_shader.js +7 -2
  70. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.d.ts.map +1 -1
  71. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.js +325 -319
  72. package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.d.ts.map +1 -1
  73. package/src/shade/renderer/buffer/util/shader_buffer_copy_storage_to_storage.js +3 -1
  74. package/src/shade/renderer/deferred/shader_tonemap_HDR.d.ts.map +1 -1
  75. package/src/shade/renderer/deferred/shader_tonemap_HDR.js +4 -1
  76. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.d.ts +2 -2
  77. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.d.ts.map +1 -1
  78. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
  79. package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +3 -3
  80. package/src/shade/renderer/fow/shader_fog_of_war.d.ts.map +1 -1
  81. package/src/shade/renderer/fow/shader_fog_of_war.js +3 -1
  82. package/src/shade/renderer/geometry/bvh/record_blas_refit.js +102 -102
  83. package/src/shade/renderer/geometry/bvh/shader_blas_refit_leaf_bounds.d.ts.map +1 -1
  84. package/src/shade/renderer/geometry/bvh/shader_blas_refit_leaf_bounds.js +125 -121
  85. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  86. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +766 -774
  87. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.d.ts.map +1 -1
  88. package/src/shade/renderer/geometry/meshlet/shader_meshlet_metadata_address_patch.js +72 -69
  89. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts.map +1 -1
  90. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +5 -10
  91. package/src/shade/renderer/geometry/sdf/shader_build_sdf.js +129 -129
  92. package/src/shade/renderer/geometry/sdf/shader_sdf_visualize.d.ts.map +1 -1
  93. package/src/shade/renderer/geometry/sdf/shader_sdf_visualize.js +5 -3
  94. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.d.ts.map +1 -1
  95. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.js +4 -1
  96. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_resolve_probes.d.ts.map +1 -1
  97. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_resolve_probes.js +3 -1
  98. package/src/shade/renderer/global_illumination/clipmap/shader_update_clipmap_cascade.d.ts.map +1 -1
  99. package/src/shade/renderer/global_illumination/clipmap/shader_update_clipmap_cascade.js +114 -112
  100. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lpv_store_sh.d.ts.map +1 -1
  101. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lpv_store_sh.js +4 -1
  102. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lvp_atlas_to_sh.d.ts.map +1 -1
  103. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_lvp_atlas_to_sh.js +187 -184
  104. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_reduce_atlas_to_sh.d.ts.map +1 -1
  105. package/src/shade/renderer/global_illumination/probe/octahedral/convolve/graph_reduce_atlas_to_sh.js +4 -1
  106. package/src/shade/renderer/global_illumination/sharc/SpatialHashRadianceCache.d.ts.map +1 -1
  107. package/src/shade/renderer/global_illumination/sharc/SpatialHashRadianceCache.js +6 -8
  108. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.d.ts.map +1 -1
  109. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.js +3 -1
  110. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_update.d.ts.map +1 -1
  111. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_update.js +3 -1
  112. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +3 -3
  113. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +3 -3
  114. package/src/shade/renderer/gpu_primitive/bvh/graph_bvh_build_lbvh.js +217 -217
  115. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.d.ts.map +1 -1
  116. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.js +145 -142
  117. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.d.ts.map +1 -1
  118. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.js +87 -82
  119. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_refit.d.ts.map +1 -1
  120. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_refit.js +143 -140
  121. package/src/shade/renderer/gpu_primitive/bvh/verify_gpu_bvh_build.d.ts +2 -2
  122. package/src/shade/renderer/gpu_primitive/bvh/verify_gpu_bvh_build.d.ts.map +1 -1
  123. package/src/shade/renderer/gpu_primitive/bvh/verify_gpu_bvh_build.js +43 -8
  124. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts.map +1 -1
  125. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.js +4 -5
  126. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.d.ts.map +1 -1
  127. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_histogram.js +88 -84
  128. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.d.ts.map +1 -1
  129. package/src/shade/renderer/gpu_primitive/sort/shader_radix_sort_scatter.js +194 -191
  130. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  131. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +33 -69
  132. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  133. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +6 -1
  134. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +1 -1
  135. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.js +23 -7
  136. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
  137. package/src/shade/renderer/light/environment/execute_filter_environment_map.js +1 -2
  138. package/src/shade/renderer/light/environment/shader_convolve_reflection_map.d.ts.map +1 -1
  139. package/src/shade/renderer/light/environment/shader_convolve_reflection_map.js +6 -1
  140. package/src/shade/renderer/lightmap/bake/raster/graph_draw_lightmap_viz_buffer.d.ts.map +1 -1
  141. package/src/shade/renderer/lightmap/bake/raster/graph_draw_lightmap_viz_buffer.js +234 -219
  142. package/src/shade/renderer/lightmap/bake/raster/shader_lightmap_to_viz_buffer.js +2 -2
  143. package/src/shade/renderer/lightmap/bake/shader_lightmap_denoise_sh.js +191 -191
  144. package/src/shade/renderer/lightmap/bake/tile/shader_lightmap_accummulate_tile.d.ts.map +1 -1
  145. package/src/shade/renderer/lightmap/bake/tile/shader_lightmap_accummulate_tile.js +85 -82
  146. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  147. package/src/shade/renderer/particles/graph_particles.js +5 -3
  148. package/src/shade/renderer/particles/prototypeParticleSystem.js +3 -4
  149. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  150. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +132 -117
  151. package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
  152. package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +79 -77
  153. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  154. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +111 -99
  155. package/src/shade/renderer/particles/sort/shader_particle_sort.d.ts.map +1 -1
  156. package/src/shade/renderer/particles/sort/shader_particle_sort.js +187 -158
  157. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.d.ts.map +1 -1
  158. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.js +7 -10
  159. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.d.ts.map +1 -1
  160. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.js +4 -1
  161. package/src/shade/renderer/postprocess/bloom/shader_bloom_mix.d.ts.map +1 -1
  162. package/src/shade/renderer/postprocess/bloom/shader_bloom_mix.js +3 -1
  163. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.d.ts.map +1 -1
  164. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +4 -2
  165. package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.js +147 -147
  166. package/src/shade/renderer/postprocess/denoise/graph_atorus_denoise_luma.d.ts.map +1 -1
  167. package/src/shade/renderer/postprocess/denoise/graph_atorus_denoise_luma.js +121 -119
  168. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.d.ts.map +1 -1
  169. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +1 -2
  170. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.d.ts.map +1 -1
  171. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +4 -1
  172. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.d.ts.map +1 -1
  173. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +4 -1
  174. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.d.ts.map +1 -1
  175. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +4 -1
  176. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.d.ts.map +1 -1
  177. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +4 -1
  178. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.d.ts.map +1 -1
  179. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +2 -4
  180. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.d.ts.map +1 -1
  181. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +4 -1
  182. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.d.ts.map +1 -1
  183. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +4 -1
  184. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch.d.ts.map +1 -1
  185. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch.js +4 -1
  186. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.d.ts.map +1 -1
  187. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +269 -266
  188. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.d.ts.map +1 -1
  189. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +273 -270
  190. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.d.ts.map +1 -1
  191. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +4 -1
  192. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts.map +1 -1
  193. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.js +8 -11
  194. package/src/shade/renderer/postprocess/eye/shader_autoexposure_adapt.d.ts.map +1 -1
  195. package/src/shade/renderer/postprocess/eye/shader_autoexposure_adapt.js +5 -1
  196. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
  197. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +11 -8
  198. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.d.ts.map +1 -1
  199. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +7 -8
  200. package/src/shade/renderer/postprocess/motion_blur/shader_motion_blur_reconstruct.d.ts.map +1 -1
  201. package/src/shade/renderer/postprocess/motion_blur/shader_motion_blur_reconstruct.js +3 -1
  202. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  203. package/src/shade/renderer/postprocess/nss/shader_nss_concat.d.ts.map +1 -1
  204. package/src/shade/renderer/postprocess/nss/shader_nss_concat.js +70 -68
  205. package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts.map +1 -1
  206. package/src/shade/renderer/postprocess/ssr/depth_max_shader.js +70 -68
  207. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_IBL.d.ts.map +1 -1
  208. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_IBL.js +3 -1
  209. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_LPV.d.ts.map +1 -1
  210. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader_LPV.js +4 -1
  211. package/src/shade/renderer/postprocess/ssr/shader_downsample_encoded_normal.d.ts.map +1 -1
  212. package/src/shade/renderer/postprocess/ssr/shader_downsample_encoded_normal.js +103 -101
  213. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.d.ts.map +1 -1
  214. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.js +4 -2
  215. package/src/shade/renderer/postprocess/ssr/ssr_trace_shader.d.ts.map +1 -1
  216. package/src/shade/renderer/postprocess/ssr/ssr_trace_shader.js +3 -1
  217. package/src/shade/renderer/postprocess/taa/shader_taa.d.ts.map +1 -1
  218. package/src/shade/renderer/postprocess/taa/shader_taa.js +4 -1
  219. package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +5 -5
  220. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  221. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +339 -341
  222. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
  223. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +114 -109
  224. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
  225. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +118 -115
  226. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
  227. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +69 -65
  228. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +722 -722
  229. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  230. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +1 -9
  231. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  232. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +1 -6
  233. package/src/shade/renderer/restir/di/graph_restir_di.d.ts.map +1 -1
  234. package/src/shade/renderer/restir/di/graph_restir_di.js +2 -3
  235. package/src/shade/renderer/restir/di/graph_restir_di_denoise.d.ts.map +1 -1
  236. package/src/shade/renderer/restir/di/graph_restir_di_denoise.js +1 -2
  237. package/src/shade/renderer/restir/di/shader_restir_di_denoise.d.ts.map +1 -1
  238. package/src/shade/renderer/restir/di/shader_restir_di_denoise.js +139 -136
  239. package/src/shade/renderer/restir/di/shader_restir_di_resample.d.ts.map +1 -1
  240. package/src/shade/renderer/restir/di/shader_restir_di_resample.js +131 -129
  241. package/src/shade/renderer/restir/di/shader_restir_di_resolve.d.ts.map +1 -1
  242. package/src/shade/renderer/restir/di/shader_restir_di_resolve.js +101 -99
  243. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.d.ts.map +1 -1
  244. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.js +379 -383
  245. package/src/shade/renderer/scene/Mesh.d.ts.map +1 -1
  246. package/src/shade/renderer/scene/Mesh.js +4 -6
  247. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.d.ts.map +1 -1
  248. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.js +147 -144
  249. package/src/shade/renderer/shader/ComputeShader.d.ts +2 -2
  250. package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
  251. package/src/shade/renderer/shader/ComputeShader.js +14 -17
  252. package/src/shade/renderer/shader/pass/RenderPassDescriptor.d.ts +2 -2
  253. package/src/shade/renderer/shader/pass/RenderPassDescriptor.d.ts.map +1 -1
  254. package/src/shade/renderer/shader/pass/RenderPassDescriptor.js +189 -189
  255. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
  256. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +3 -1
  257. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts +16 -7
  258. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  259. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +1342 -1326
  260. package/src/shade/renderer/shadow/map/Shadowmap.d.ts +13 -0
  261. package/src/shade/renderer/shadow/map/Shadowmap.d.ts.map +1 -1
  262. package/src/shade/renderer/shadow/map/Shadowmap.js +14 -0
  263. package/src/shade/renderer/shadow/map/ShadowmapDrawSelector.d.ts +79 -0
  264. package/src/shade/renderer/shadow/map/ShadowmapDrawSelector.d.ts.map +1 -0
  265. package/src/shade/renderer/shadow/map/ShadowmapDrawSelector.js +196 -0
  266. package/src/shade/renderer/shadow/map/ShadowmapRegistry.d.ts +91 -0
  267. package/src/shade/renderer/shadow/map/ShadowmapRegistry.d.ts.map +1 -0
  268. package/src/shade/renderer/shadow/map/ShadowmapRegistry.js +156 -0
  269. package/src/shade/renderer/shadow/map/shader/shader_shadowmap_remap_cube_to_octahedral.d.ts.map +1 -1
  270. package/src/shade/renderer/shadow/map/shader/shader_shadowmap_remap_cube_to_octahedral.js +5 -1
  271. package/src/shade/renderer/shadow/ray/shader_shadow_upscale.d.ts.map +1 -1
  272. package/src/shade/renderer/shadow/ray/shader_shadow_upscale.js +3 -1
  273. package/src/shade/renderer/shadow/ray/staged/gen/graph_gen_rays.js +73 -73
  274. package/src/shade/renderer/shadow/ray/staged/gen/shader_generate_rays.d.ts.map +1 -1
  275. package/src/shade/renderer/shadow/ray/staged/gen/shader_generate_rays.js +151 -148
  276. package/src/shade/renderer/shadow/ray/staged/trace/graph_trace_rays.d.ts.map +1 -1
  277. package/src/shade/renderer/shadow/ray/staged/trace/graph_trace_rays.js +4 -2
  278. package/src/shade/renderer/shadow/ray/staged/trace/shader_trace_rays.d.ts.map +1 -1
  279. package/src/shade/renderer/shadow/ray/staged/trace/shader_trace_rays.js +18 -4
  280. package/src/shade/renderer/shadow/sdf/cascade/CascadedSceneSDF.d.ts.map +1 -1
  281. package/src/shade/renderer/shadow/sdf/cascade/CascadedSceneSDF.js +7 -8
  282. package/src/shade/renderer/shadow/sdf/cascade/shader_build_scene_sdf_f32.d.ts.map +1 -1
  283. package/src/shade/renderer/shadow/sdf/cascade/shader_build_scene_sdf_f32.js +6 -2
  284. package/src/shade/renderer/shadow/sdf/cascade/shader_scene_sdf_debug_viz.d.ts.map +1 -1
  285. package/src/shade/renderer/shadow/sdf/cascade/shader_scene_sdf_debug_viz.js +129 -127
  286. package/src/shade/renderer/shadow/sdf/graph_draw_sdf_shadows.d.ts.map +1 -1
  287. package/src/shade/renderer/shadow/sdf/graph_draw_sdf_shadows.js +5 -3
  288. package/src/shade/renderer/texture/virtual/VirtualTextureManager.d.ts.map +1 -1
  289. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +2 -5
  290. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.d.ts.map +1 -1
  291. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +233 -231
  292. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  293. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +2 -4
  294. package/src/shade/wgsl/validate_wgsl_source.d.ts.map +1 -1
  295. package/src/shade/wgsl/validate_wgsl_source.js +8 -3
  296. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts +0 -21
  297. package/src/shade/device/timing/profile/make_profiling_pass_encoder.d.ts.map +0 -1
  298. package/src/shade/device/timing/profile/make_profiling_pass_encoder.js +0 -114
  299. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts +0 -20
  300. package/src/shade/device/timing/profile/parse_workgroup_size.d.ts.map +0 -1
  301. package/src/shade/device/timing/profile/parse_workgroup_size.js +0 -43
@@ -1,995 +1,802 @@
1
- import { assert } from "../../core/assert.js";
2
- import { BinaryBuffer } from "../../core/binary/BinaryBuffer.js";
3
- import { platform_compute_endianness } from "../../core/binary/platform_compute_endianness.js";
4
- import { array_push_if_unique } from "../../core/collection/array/array_push_if_unique.js";
5
- import { array_remove_first } from "../../core/collection/array/array_remove_first.js";
6
- import { array_buffer_copy } from "../../core/collection/array/typed/array_buffer_copy.js";
7
- import Signal from "../../core/events/signal/Signal.js";
8
- import { noop } from "../../core/function/noop.js";
9
- import { GPUTimerArray } from "./timing/GPUTimerArray.js";
10
- import { make_profiling_pass_encoder } from "./timing/profile/make_profiling_pass_encoder.js";
11
- import { parse_workgroup_size } from "./timing/profile/parse_workgroup_size.js";
12
- import { warn_limited } from "../util/warn_limited.js";
13
- import { write_gpu_typed_buffer } from "../renderer/shader/type/serde/write_gpu_typed_buffer.js";
14
- import { BufferResourceDescriptor } from "./graph/BufferResourceDescriptor.js";
15
- import { GraphicsExecutionContext } from "./graph/GraphicsExecutionContext.js";
16
-
17
- /**
18
- * Keep track of how many contexts are currently open (unfinished)
19
- * Important because contexts can have transient resources allocated, and those are not cleaned up until the context is `finished`
20
- * @type {number}
21
- */
22
- let debug_open_context_count = 0;
23
-
24
- /**
25
- *
26
- * @type {ShadeGPUCommandContext[]}
27
- */
28
- const debug_open_contexts = [];
29
-
30
- const scratch_buffer_descriptor = new BufferResourceDescriptor();
31
-
32
- let id_counter = 0;
33
-
34
- /**
35
- * Abstraction on top of {@link GPUCommandEncoder}.
36
- *
37
- * Inspired by Intel's "MiniEngine" CommandContext
38
- * @see https://github.com/GameTechDev/TAA/blob/39786709cf70a1e0906196c600f6079571a33ceb/MiniEngine/Core/CommandContext.h
39
- * @see https://www.w3.org/TR/webgpu/#gpucommandencoder
40
- */
41
- export class ShadeGPUCommandContext {
42
-
43
- #id = id_counter++;
44
-
45
- /**
46
- * Unique identifier for this context, useful for debugging
47
- *
48
- * @return {number}
49
- */
50
- get id() {
51
- return this.#id;
52
- }
53
-
54
- /**
55
- *
56
- * @returns {boolean}
57
- */
58
- get isGPUCommandContext() {
59
- return true;
60
- }
61
-
62
-
63
- /**
64
- * Actual underlying encoder
65
- * @type {GPUCommandEncoder}
66
- */
67
- #gpu_encoder
68
-
69
- /**
70
- * Don't circumvent {@link ShadeGPUCommandContext} unless you know what you're doing.
71
- * {@link ShadeGPUCommandContext} is a wrapper around {@link GPUCommandEncoder} that provides additional spec-compliance asserts to help find errors sooner.
72
- * @returns {GPUCommandEncoder}
73
- */
74
- get gpu_encoder() {
75
- return this.#gpu_encoder;
76
- }
77
-
78
- /**
79
- * @type {GPUTimerArray|undefined}
80
- */
81
- #debug_timers;
82
-
83
- /**
84
- *
85
- * @type {function}
86
- */
87
- #debug_timers_callback = noop;
88
-
89
- /**
90
- *
91
- * @param {function} debug_timers_callback
92
- */
93
- enable_debug_timers(debug_timers_callback) {
94
- if (this.#debug_timers !== undefined) {
95
- // already enabled
96
- return;
97
- }
98
-
99
- this.#debug_timers = new GPUTimerArray(this.device);
100
- this.#debug_timers_callback = debug_timers_callback;
101
- }
102
-
103
- /**
104
- * The frame recorder collecting this context's spans, or null.
105
- *
106
- * @type {GPUFrameRecorder|null}
107
- */
108
- #profile_recorder = null;
109
-
110
- /**
111
- * Which query set this context is, within its frame. Carried into every span it records —
112
- * timestamps from two query sets are not specified to share a clock domain, and a frame has
113
- * one set per context.
114
- *
115
- * @type {number}
116
- */
117
- #profile_query_set_id = 0;
118
-
119
- /**
120
- * The work record for the pass currently being opened.
121
- *
122
- * Set by {@link #with_timestamp_writes} and consumed immediately by the `begin*Pass` that asked
123
- * for it — the two are one operation, split only because the descriptor has to be built before
124
- * the encoder exists.
125
- *
126
- * @type {GPUProfileWork|null}
127
- */
128
- #pending_work = null;
129
-
130
- /**
131
- * Wrap `encoder` so its dispatches and draws are recorded, when a capture asked for them.
132
- *
133
- * @param {GPUComputePassEncoder|GPURenderPassEncoder} encoder
134
- * @returns {GPUComputePassEncoder|GPURenderPassEncoder}
135
- */
136
- #instrument_pass(encoder) {
137
- const work = this.#pending_work;
138
-
139
- if (work === null) {
140
- this.#last_work = null;
141
-
142
- return encoder;
143
- }
144
-
145
- this.#pending_work = null;
146
- this.#last_work = work;
147
-
148
- return make_profiling_pass_encoder(encoder, work);
149
- }
150
-
151
- /**
152
- * Resolves once this context's timings have reached its recorder.
153
- *
154
- * @type {Promise<void>}
155
- */
156
- #profiling_absorbed = Promise.resolve();
157
-
158
- /**
159
- * @type {function}
160
- */
161
- #profiling_absorbed_resolve = noop;
162
-
163
- /**
164
- * Resolves after this context's timings have been read back and handed to its recorder.
165
- *
166
- * **Not the same moment as {@link done}.** `done` resolves from `onFinished`, which fires
167
- * synchronously at submit — before any timestamp has been read. A caller that closes its frame
168
- * on `done` commits an empty one, every time. Already-resolved when nothing is profiling.
169
- *
170
- * @returns {Promise<void>}
171
- */
172
- get profiling_absorbed() {
173
- return this.#profiling_absorbed;
174
- }
175
-
176
- /**
177
- * Record this context's pass timings into `recorder`.
178
- *
179
- * Independent of {@link enable_debug_timers}, which is the console-shaped one-off; both share
180
- * the underlying {@link GPUTimerArray} and either may enable it.
181
- *
182
- * @param {GPUFrameRecorder} recorder
183
- * @param {number} query_set_id this context's index within the frame
184
- * @returns {void}
185
- */
186
- enable_profiling(recorder, query_set_id) {
187
- assert.defined(recorder, 'recorder');
188
- assert.isNonNegativeInteger(query_set_id, 'query_set_id');
189
- assert.notOk(this.#finished, 'already finished');
190
-
191
- if (this.#debug_timers === undefined) {
192
- this.#debug_timers = new GPUTimerArray(this.device);
193
- }
194
-
195
- this.#profile_recorder = recorder;
196
- this.#profile_query_set_id = query_set_id;
197
-
198
- this.#profiling_absorbed = new Promise(resolve => {
199
- this.#profiling_absorbed_resolve = resolve;
200
- });
201
- }
202
-
203
- /**
204
- * @type {GraphicsContext}
205
- */
206
- #graphics
207
-
208
- /**
209
- *
210
- * @return {GPUDevice}
211
- */
212
- get device() {
213
- return this.#graphics.device;
214
- }
215
-
216
- /**
217
- *
218
- * @return {TextureManager}
219
- */
220
- get textures() {
221
- return this.#graphics.textures;
222
- }
223
-
224
- /**
225
- * These are to be destroyed at the end of the context's lifetime.
226
- * Need to be released at the end of the lifetime.
227
- * @type {GPUBuffer[]}
228
- */
229
- #transient_buffers = [];
230
-
231
- /**
232
- *
233
- * @type {GPUBuffer[]}
234
- */
235
- #staging_buffers = [];
236
-
237
- /**
238
- * Once finished, the encoder becomes inactive, we use this flag to track potential misuse and inform the user
239
- * @type {boolean}
240
- */
241
- #finished = false;
242
-
243
- /**
244
- * @readonly
245
- * @type {Signal<this>}
246
- */
247
- onFinished = new Signal();
248
-
249
- /**
250
- * @readonly
251
- * @type {Signal<this>}
252
- */
253
- onBeforeFinish = new Signal();
254
-
255
- /**
256
- *
257
- * @return {Promise<void>}
258
- */
259
- get done() {
260
- if (this.#finished) {
261
- return Promise.resolve();
262
- }
263
-
264
- return new Promise(resolve => {
265
- this.onFinished.addOne(resolve);
266
-
267
- if (this.#finished) {
268
- // special case, double-checking to avoid race conditions
269
- resolve();
270
- }
271
-
272
- });
273
- }
274
-
275
- /**
276
- * Do not call directly, always use {@link create}
277
- * @private
278
- */
279
- constructor() {
280
- // private, do not call from the outside
281
- }
282
-
283
- /**
284
- *
285
- * @param {GraphicsContext} graphics
286
- * @param {string} [label]
287
- * @returns {ShadeGPUCommandContext}
288
- */
289
- static create(
290
- graphics,
291
- label = ""
292
- ) {
293
-
294
- assert.defined(graphics, 'graphics');
295
- assert.equal(graphics.isGraphicsContext, true, "graphics.isGraphicsContext !== true");
296
-
297
- assert.isString(label, 'label');
298
-
299
- const r = new ShadeGPUCommandContext();
300
-
301
- r.#graphics = graphics;
302
- r.#gpu_encoder = graphics.device.createCommandEncoder({
303
- label
304
- });
305
-
306
- // track how many we have open
307
- debug_open_context_count++;
308
- array_push_if_unique(debug_open_contexts, r);
309
-
310
- if (debug_open_context_count > 1024) {
311
- warn_limited(`Too many open GPU contexts`, 20);
312
- }
313
-
314
- return r;
315
-
316
- }
317
-
318
- /**
319
- *
320
- * @returns {GraphicsExecutionContext}
321
- */
322
- createFrameGraphContext() {
323
- return new GraphicsExecutionContext(this.#graphics, this);
324
- }
325
-
326
- /**
327
- *
328
- * @param {FrameGraph} graph
329
- */
330
- encodeGraph(graph) {
331
- assert.defined(graph, 'graph');
332
- assert.isObject(graph, 'graph');
333
- assert.equal(graph.isFrameGraph, true, "graph.isFrameGraph !== true");
334
-
335
- // TODO check if the graph is empty, if so, we can skip the whole thing
336
-
337
- const ctx = this.createFrameGraphContext();
338
-
339
- graph.compile();
340
- graph.execute(ctx);
341
- }
342
-
343
- /**
344
- * @see https://www.w3.org/TR/webgpu/#dom-gpucommandencoder-clearbuffer
345
- * @param {GPUBuffer} buffer
346
- * @param {number} [offset=0]
347
- * @param {number} [size]
348
- */
349
- clearBuffer(buffer, offset = 0, size) {
350
- assert.defined(buffer, 'buffer');
351
- assert.equal(buffer.usage & GPUBufferUsage.COPY_DST, GPUBufferUsage.COPY_DST, 'buffer must include COPY_DST usage flag');
352
- assert.isNonNegativeInteger(offset, 'offset');
353
- assert.equal(offset % 4, 0, 'offset must be a multiple of 4');
354
-
355
- // console.log(`cmd.clearBuffer(${buffer.label}, ${offset}, ${size})`);
356
-
357
- this.#gpu_encoder.clearBuffer(buffer, offset, size);
358
- }
359
-
360
- /**
361
- * Not supported, only here to offer documentation hint
362
- * see {@link texture_fill_rectangle}
363
- * @throws {Error} Always, documentation hint only
364
- */
365
- clearTexture() {
366
- throw new Error(`Documentation hint only, use texture_fill_rectangle instead`);
367
- }
368
-
369
- /**
370
- * NOTE: only the explicit verbose form of the API is supported, shorthand such as `copyBufferToBuffer(source, destination, size)` are not supported.
371
- * see https://www.w3.org/TR/webgpu/#gpucommandencoder-copybuffertobuffer
372
- * @see GPUCommandEncoder.copyBufferToBuffer
373
- * @param {GPUBuffer} source The GPUBuffer to copy from.
374
- * @param {number} sourceOffset Offset in bytes into the source to begin copying from.
375
- * @param {GPUBuffer} destination The GPUBuffer to copy to.
376
- * @param {number} destinationOffset Offset in bytes into destination to place the copied data.
377
- * @param {number} [size] Bytes to copy. In not specified, defaults to `source.size - sourceOffset`
378
- */
379
- copyBufferToBuffer(
380
- source,
381
- sourceOffset,
382
- destination,
383
- destinationOffset,
384
- size
385
- ) {
386
-
387
- assert.notEqual(source.usage & GPUBufferUsage.COPY_SRC, 0, 'source must include COPY_SRC usage flag');
388
- assert.isNonNegativeInteger(sourceOffset, 'sourceOffset');
389
- assert.equal(sourceOffset % 4, 0, 'sourceOffset must be a multiple of 4');
390
-
391
- assert.notEqual(destination.usage & GPUBufferUsage.COPY_DST, 0, 'destination must include COPY_DST usage flag');
392
- assert.isNonNegativeInteger(destinationOffset, 'destinationOffset');
393
- assert.equal(destinationOffset % 4, 0, 'destinationOffset must be a multiple of 4');
394
-
395
- if (size !== undefined) {
396
- assert.isNonNegativeInteger(size, 'size');
397
- assert.equal(size % 4, 0, 'size must be a multiple of 4');
398
-
399
- assert.greaterThanOrEqual(source.size, sourceOffset + size, 'source underflow');
400
- assert.greaterThanOrEqual(destination.size, destinationOffset + size, 'destination underflow');
401
- }
402
-
403
- assert.notEqual(source, destination, 'source and destination must be different');
404
-
405
- // console.log(`cmd.copyBufferToBuffer("${source.label}", ${sourceOffset}, "${destination.label}", ${destinationOffset}, ${size})`);
406
-
407
- this.#gpu_encoder.copyBufferToBuffer(
408
- source,
409
- sourceOffset,
410
- destination,
411
- destinationOffset,
412
- size
413
- );
414
- }
415
-
416
-
417
- /**
418
- *
419
- * @param {GPUImageCopyTexture} source
420
- * @param {GPUImageCopyTexture} destination
421
- * @param {GPUExtent3DStrict} copySize
422
- */
423
- copyTextureToTexture(source,
424
- destination,
425
- copySize
426
- ) {
427
- this.#gpu_encoder.copyTextureToTexture(source, destination, copySize);
428
- }
429
-
430
- /**
431
- *
432
- * @param {GPUComputePassDescriptor} [descriptor]
433
- * @returns {GPUComputePassEncoder}
434
- */
435
- beginComputePass(descriptor) {
436
- const encoder = this.#gpu_encoder;
437
-
438
- const _descriptor = this.#with_timestamp_writes(descriptor, 'compute');
439
-
440
- // console.log(`cmd.beginComputePass(${descriptor.label})`);
441
-
442
- return this.#instrument_pass(encoder.beginComputePass(_descriptor))
443
- }
444
-
445
- /**
446
- * The descriptor a pass should actually be begun with, once timing has had its say.
447
- *
448
- * Copies rather than writing through. Several call sites hand in a descriptor they reuse across
449
- * passes, and stamping `timestampWrites` onto it left the next pass carrying the previous
450
- * pass's query indices. The copy is one shallow spread per pass and only when timers are on.
451
- *
452
- * Returns the descriptor unchanged when timing is off, when the device withheld the feature, or
453
- * when the timer array has no slot left ({@link GPUTimerArray#dropped_count}). An untimed pass
454
- * is the correct outcome in all three; a pass carrying indices the query set does not have is a
455
- * validation error.
456
- *
457
- * @param {GPUComputePassDescriptor|GPURenderPassDescriptor} [descriptor]
458
- * @param {string} kind one of 'compute', 'render'
459
- * @returns {GPUComputePassDescriptor|GPURenderPassDescriptor}
460
- */
461
- #with_timestamp_writes(descriptor, kind) {
462
- const timers = this.#debug_timers;
463
-
464
- if (timers === undefined) {
465
- return descriptor;
466
- }
467
-
468
- if (!this.#graphics.device.features.has('timestamp-query')) {
469
- return descriptor;
470
- }
471
-
472
- // `beginComputePass()` is legal with no descriptor at all; reading `.label` off nothing is
473
- // not, and used to throw here the moment timers were switched on
474
- const source = descriptor !== undefined ? descriptor : {};
475
-
476
- const writes = kind === 'compute'
477
- ? timers.getComputeWrites(source.label)
478
- : timers.getRenderWrites(source.label);
479
-
480
- if (writes === undefined) {
481
- return descriptor;
482
- }
483
-
484
- const recorder = this.#profile_recorder;
485
-
486
- if (recorder !== null) {
487
- /*
488
- The one moment the join can be made. The recorder knows which frame graph pass is
489
- executing right now; the slot is what the timings will come back keyed by. Neither
490
- fact is recoverable later — see GPUFrameRecorder.
491
- */
492
- this.#pending_work = recorder.note_pass_slot(
493
- this.#profile_query_set_id,
494
- writes.beginningOfPassWriteIndex / 2
495
- );
496
- } else {
497
- this.#pending_work = null;
498
- }
499
-
500
- return {
501
- ...source,
502
- timestampWrites: writes
503
- };
504
- }
505
-
506
- /**
507
- *
508
- * @param {ComputePipelineDescriptor} pipeline
509
- * @param {string} [label]
510
- * @param {GPUBindingResource[][]} [bindings]
511
- * @returns {GPUComputePassEncoder}
512
- */
513
- constructComputePass({
514
- pipeline,
515
- label,
516
- bindings = [],
517
- immediate
518
- }) {
519
-
520
- const pass_descriptor = {
521
- label
522
- };
523
-
524
- if (label === undefined) {
525
- pass_descriptor.label = pipeline.label;
526
- }
527
-
528
- const graphics = this.#graphics;
529
-
530
- const pass = this.beginComputePass(pass_descriptor);
531
-
532
- const gpu_pipeline = graphics.compute_pipelines.obtain(pipeline);
533
-
534
- pass.setPipeline(gpu_pipeline);
535
-
536
- this.#note_pipeline(pipeline, true);
537
-
538
- graphics.setPipelineBindings(pass, pipeline, bindings, immediate);
539
-
540
- // console.warn(`cmd.constructComputePass(${pass_descriptor.label})`)
541
-
542
- return pass;
543
- }
544
-
545
- /**
546
- * Record which pipeline a pass ran, and for a compute pipeline its workgroup size.
547
- *
548
- * Only reachable from `constructComputePass` / `constructRenderPass`, which is the only place a
549
- * *descriptor* is in hand. `setPipeline` receives the compiled `GPUComputePipeline`, and the
550
- * WGSL that carries `@workgroup_size` is not on it — so a pass begun the long way records its
551
- * counts but not what ran them, which the model represents as an unknown rather than a zero.
552
- *
553
- * @param {ComputePipelineDescriptor|RenderPipelineDescriptor} pipeline
554
- * @param {boolean} is_compute
555
- * @returns {void}
556
- */
557
- #note_pipeline(pipeline, is_compute) {
558
- const work = this.#last_work;
559
-
560
- if (work === null) {
561
- return;
562
- }
563
-
564
- work.pipeline = pipeline.label ?? "";
565
-
566
- if (is_compute) {
567
- work.workgroup_size = parse_workgroup_size(pipeline.compute?.module?.code);
568
- }
569
- }
570
-
571
- /**
572
- * The work record of the most recently opened pass, so `construct*Pass` can annotate it after
573
- * the encoder has been made.
574
- *
575
- * @type {GPUProfileWork|null}
576
- */
577
- #last_work = null;
578
-
579
- /**
580
- * Begins a labeled debug group containing subsequent commands.
581
- * @see https://gpuweb.github.io/gpuweb/#debug-markers
582
- * @param {string} label
583
- */
584
- pushDebugGroup(label) {
585
- assert.isString(label, 'label');
586
- this.#gpu_encoder.pushDebugGroup(label);
587
-
588
- // console.group("cmd/debug_group=" + label)
589
- }
590
-
591
- /**
592
- * Ends the labeled debug group most recently started by pushDebugGroup().
593
- * @see https://gpuweb.github.io/gpuweb/#debug-markers
594
- */
595
- popDebugGroup() {
596
- this.#gpu_encoder.popDebugGroup();
597
-
598
- // console.groupEnd();
599
- }
600
-
601
- /**
602
- * Marks a point in a stream of commands with a label.
603
- * @see https://gpuweb.github.io/gpuweb/#debug-markers
604
- * @param {string} label
605
- */
606
- insertDebugMarker(label) {
607
- assert.isString(label, 'label');
608
- this.#gpu_encoder.insertDebugMarker(label);
609
- }
610
-
611
- /**
612
- *
613
- * @param {string} [label]
614
- * @param {RenderPipelineDescriptor} pipeline
615
- * @param {GPUBindingResource[][]} [bindings]
616
- * @param {GPURenderPassColorAttachment[]} colorAttachments
617
- * @param {GPURenderPassDepthStencilAttachment} [depthStencilAttachment]
618
- * @returns {GPURenderPassEncoder}
619
- */
620
- constructRenderPass({
621
- label,
622
- pipeline,
623
- bindings,
624
- colorAttachments,
625
- depthStencilAttachment,
626
- immediate,
627
- }) {
628
- assert.defined(pipeline, 'pipeline');
629
- assert.equal(pipeline.isRenderPipelineDescriptor, true, "pipeline.isRenderPipelineDescriptor !== true");
630
-
631
- const pass_descriptor = {
632
- label,
633
- colorAttachments,
634
- depthStencilAttachment
635
- };
636
-
637
- if (label === undefined) {
638
- pass_descriptor.label = pipeline.label;
639
- }
640
-
641
- const graphics = this.#graphics;
642
-
643
- // TODO rewrite the pipeline if necessary to achieve compatibility with colorAttachment formats
644
- const gpu_pipeline = graphics.render_pipelines.obtain(pipeline);
645
-
646
- const pass = this.beginRenderPass(pass_descriptor);
647
-
648
- pass.setPipeline(gpu_pipeline);
649
-
650
- this.#note_pipeline(pipeline, false);
651
-
652
- graphics.setPipelineBindings(pass, pipeline, bindings, immediate);
653
-
654
- // console.warn(`cmd.constructRenderPass(${pass_descriptor.label})`)
655
-
656
- return pass;
657
- }
658
-
659
- /**
660
- *
661
- * @param {GPURenderPassDescriptor} descriptor
662
- * @returns {GPURenderPassEncoder}
663
- */
664
- beginRenderPass(descriptor) {
665
-
666
- const _descriptor = this.#with_timestamp_writes(descriptor, 'render');
667
-
668
- // console.log(`cmd.beginRenderPass(${descriptor.label})`);
669
-
670
- return this.#instrument_pass(this.#gpu_encoder.beginRenderPass(_descriptor))
671
- }
672
-
673
- /**
674
- *
675
- * @param {GPUQuerySet} querySet
676
- * @param {number} firstQuery
677
- * @param {number} queryCount
678
- * @param {GPUBuffer} destination
679
- * @param {number} destinationOffset
680
- */
681
- resolveQuerySet(querySet, firstQuery, queryCount, destination, destinationOffset) {
682
- this.#gpu_encoder.resolveQuerySet(querySet, firstQuery, queryCount, destination, destinationOffset);
683
- }
684
-
685
- /**
686
- * Transient buffers are automatically released at the end of the context's lifetime;
687
- * however, we can explicitly release it earlier to allow for reuse.
688
- *
689
- * @param {GPUBuffer} buffer
690
- * @return {boolean} true if buffer was released, false if it was not found
691
- */
692
- releaseTransientBuffer(buffer) {
693
- assert.defined(buffer, 'buffer');
694
-
695
- if (!array_remove_first(this.#transient_buffers, buffer)) {
696
- return false;
697
- }
698
-
699
- const allocator = this.#transient_allocator;
700
-
701
- return allocator.release(buffer);
702
- }
703
-
704
- /**
705
- *
706
- * @return {GPUBufferAllocator}
707
- */
708
- get #transient_allocator() {
709
- return this.#graphics.buffer_allocator_main;
710
- }
711
-
712
- /**
713
- * Allocated buffer will only be valid until the context is finished.
714
- *
715
- * @param {number|GPUBufferUsage} usage
716
- * @param {number} size
717
- * @returns {GPUBuffer} managed buffer
718
- */
719
- allocateTransientBuffer(
720
- usage = GPUBufferUsage.UNIFORM,
721
- size
722
- ) {
723
- assert.isNonNegativeInteger(size, 'size');
724
- assert.isInteger(usage, 'usage');
725
-
726
- assert.notOk(this.#finished, 'already finished');
727
-
728
- scratch_buffer_descriptor.size = size;
729
- // COPY_DST is needed to write the data
730
- scratch_buffer_descriptor.usage = usage | GPUBufferUsage.COPY_DST;
731
-
732
- const allocator = this.#transient_allocator;
733
- const buffer = allocator.get(scratch_buffer_descriptor, this);
734
-
735
- // remember so we can clean up at the end
736
- this.#transient_buffers.push(buffer);
737
-
738
- return buffer;
739
- }
740
-
741
- /**
742
- *
743
- * @param {ArrayBuffer} data CPU-side data to place into the GPU buffer
744
- * @param {number|GPUBufferUsage} [usage=GPUBufferUsage.UNIFORM]
745
- * @param {number} [offset] offset into source data where to start copying from
746
- * @param {number} [size] in bytes
747
- * @returns {GPUBuffer} managed buffer, will be automatically destroyed when context is finished
748
- */
749
- allocateTransientBufferAndLoad(
750
- data,
751
- usage = GPUBufferUsage.UNIFORM,
752
- offset = 0,
753
- size = data.byteLength
754
- ) {
755
- assert.isInstanceOf(data, ArrayBuffer, 'data');
756
- assert.isInteger(usage, 'usage');
757
- assert.isNonNegativeInteger(offset, 'offset');
758
- assert.isNonNegativeInteger(size, 'size');
759
-
760
- assert.notOk(this.#finished, 'already finished');
761
-
762
- const buffer = this.allocateTransientBuffer(
763
- usage,
764
- size
765
- );
766
-
767
- this.writeBuffer(buffer, 0, data, offset, size);
768
-
769
- return buffer;
770
- }
771
-
772
- /**
773
- * Scratch for immediate data, created on first use and reused for the life of the context.
774
- *
775
- * One buffer is enough, and `position` returns to 0 for every write, because `setImmediates`
776
- * copies the bytes it is given at the point of the call — the region is free again the moment
777
- * the pass encoder has taken it. Nothing here ever grows: the default capacity is far above the
778
- * 64-byte ceiling {@link ShaderResourceSetDescriptor#setImmediate} enforces, so the backing
779
- * `ArrayBuffer` is allocated once and never reallocated.
780
- *
781
- * @type {BinaryBuffer|null}
782
- */
783
- #immediate_buffer = null;
784
-
785
- /**
786
- * Serialise a shader's immediate data ready for `setImmediates`, or return undefined when the
787
- * shader declares none.
788
- *
789
- * Allocates nothing after the first call.
790
- *
791
- * @param {ShaderResourceSetDescriptor} resources
792
- * @param {Object} map the shader's binding map
793
- * @returns {{buffer: BinaryBuffer, offset: number, size: number}|undefined}
794
- */
795
- writeImmediate(resources, map) {
796
- if (resources.immediateSize === 0) {
797
- return undefined;
798
- }
799
-
800
- let buffer = this.#immediate_buffer;
801
-
802
- if (buffer === null) {
803
- buffer = new BinaryBuffer();
804
-
805
- // GPU buffer contents must match the host, and BinaryBuffer's own default is
806
- // little-endian regardless of platform. Getting this wrong produces bytes that are
807
- // correct on every machine anyone is likely to test on and wrong on the rest.
808
- buffer.endianness = platform_compute_endianness();
809
-
810
- this.#immediate_buffer = buffer;
811
- }
812
-
813
- buffer.position = 0;
814
-
815
- if (!resources.writeImmediate(map, buffer)) {
816
- return undefined;
817
- }
818
-
819
- return { buffer, offset: 0, size: buffer.position };
820
- }
821
-
822
- /**
823
- * @template T
824
- * @param {WebGPUType} type
825
- * @param {T} value
826
- * @param {number|GPUBufferUsage} [usage]
827
- * @return {GPUBuffer}
828
- */
829
- allocateTransientValueBuffer(
830
- type,
831
- value,
832
- usage
833
- ) {
834
- assert.defined(type, 'type');
835
- assert.notNull(type, 'type');
836
-
837
- const buffer = this.allocateTransientBuffer(usage, type.aligned_size);
838
-
839
- this.writeValueBuffer(buffer, 0, type, value);
840
-
841
- return buffer;
842
- }
843
-
844
- /**
845
- * @template T
846
- * @param {GPUBuffer} buffer
847
- * @param {number} buffer_offset
848
- * @param {WebGPUType} type
849
- * @param {T} value
850
- */
851
- writeValueBuffer(buffer, buffer_offset, type, value) {
852
-
853
- assert.notOk(this.#finished, 'already finished');
854
-
855
- const gfx = this.#graphics;
856
-
857
- const size = type.aligned_size;
858
-
859
- const stage = gfx.buffer_allocator_staging.get(size);
860
-
861
- const mapped = stage.getMappedRange(0, size);
862
-
863
- write_gpu_typed_buffer(value, type, mapped, 0);
864
-
865
- stage.unmap();
866
-
867
- this.#staging_buffers.push(stage);
868
-
869
- this.copyBufferToBuffer(stage, 0, buffer, buffer_offset, size);
870
- }
871
-
872
- /**
873
- * Very similar to {@link GPUQueue.writeBuffer}, but instead of doing the operation direction on the queue - uses a staging buffer under the hood and performs the actual write operation as part of the commmand buffer
874
- * @param {GPUBuffer} buffer
875
- * @param {number} buffer_offset
876
- * @param {ArrayBuffer} data
877
- * @param {number} data_offset
878
- * @param {number} size
879
- */
880
- writeBuffer(buffer, buffer_offset, data, data_offset, size) {
881
- assert.defined(buffer, 'buffer');
882
- assert.isNonNegativeInteger(buffer_offset, 'buffer_offset');
883
- assert.defined(data, 'data');
884
- assert.isNonNegativeInteger(data_offset, 'data_offset');
885
- assert.isNonNegativeInteger(size, 'size');
886
-
887
- const gfx = this.#graphics;
888
-
889
- const stage = gfx.buffer_allocator_staging.get(size);
890
-
891
- const mapped = stage.getMappedRange(0, size);
892
-
893
- array_buffer_copy(data, data_offset, mapped, 0, size);
894
-
895
- stage.unmap();
896
-
897
- this.#staging_buffers.push(stage);
898
-
899
- this.copyBufferToBuffer(stage, 0, buffer, buffer_offset, size);
900
-
901
- }
902
-
903
- #cleanup() {
904
-
905
- // console.warn('cmd.cleanup()');
906
-
907
- const transient_buffers = this.#transient_buffers;
908
- const transient_buffer_count = transient_buffers.length;
909
-
910
- const buffer_allocator_transient = this.#transient_allocator;
911
-
912
- // release in reverse order to make it more likely that we'd end up in the same acquisition order next time, which is desirable for caching
913
- for (let i = transient_buffer_count - 1; i >= 0; i--) {
914
- const buffer = transient_buffers[i];
915
-
916
- buffer_allocator_transient.release(buffer);
917
-
918
- }
919
-
920
- // staging buffers
921
- const buffer_allocator_staging = this.#graphics.buffer_allocator_staging;
922
- for (const staging of this.#staging_buffers) {
923
- buffer_allocator_staging.release(staging);
924
- }
925
- }
926
-
927
- /**
928
- * Performs both {@link GPUCommandEncoder.finish} and flushed command buffer
929
- */
930
- finish() {
931
- if (this.#finished) {
932
- console.warn('Context already finished');
933
- return;
934
- }
935
-
936
- this.onBeforeFinish.send1(this);
937
-
938
- debug_open_context_count--;
939
- array_remove_first(debug_open_contexts, this);
940
-
941
- const encoder = this.#gpu_encoder;
942
-
943
- const debug_timers = this.#debug_timers;
944
- if (debug_timers !== undefined) {
945
- debug_timers.resolve(encoder);
946
- }
947
-
948
- this.#finished = true;
949
-
950
- // release encoder reference
951
- this.#gpu_encoder = undefined;
952
-
953
- const command_buffer = encoder.finish();
954
-
955
- const graphics = this.#graphics;
956
- const queue = graphics.device.queue;
957
-
958
- queue.submit([command_buffer]);
959
-
960
- // cleanup allocated memory
961
- this.#cleanup();
962
-
963
- if (debug_timers !== undefined) {
964
- const recorder = this.#profile_recorder;
965
- const query_set_id = this.#profile_query_set_id;
966
-
967
- debug_timers
968
- .download_results()
969
- .then(() => {
970
- if (recorder !== null) {
971
- recorder.absorb(debug_timers, query_set_id);
972
- }
973
-
974
- // The console-shaped path stays exactly as it was; a context can be doing both.
975
- const table = debug_timers.results_to_console_table();
976
-
977
- this.#debug_timers_callback(table);
978
- })
979
- .finally(() => {
980
- debug_timers.destroy();
981
-
982
- // In `finally` so a failed readback still releases whoever is waiting on the
983
- // frame. A profile session that hangs because one download rejected is worse
984
- // than one missing a frame.
985
- this.#profiling_absorbed_resolve();
986
- });
987
-
988
- this.#debug_timers = undefined;
989
- this.#profile_recorder = null;
990
- }
991
-
992
- this.onFinished.send1(this);
993
-
994
- }
1
+ import { assert } from "../../core/assert.js";
2
+ import { BinaryBuffer } from "../../core/binary/BinaryBuffer.js";
3
+ import { platform_compute_endianness } from "../../core/binary/platform_compute_endianness.js";
4
+ import { array_push_if_unique } from "../../core/collection/array/array_push_if_unique.js";
5
+ import { array_remove_first } from "../../core/collection/array/array_remove_first.js";
6
+ import { array_buffer_copy } from "../../core/collection/array/typed/array_buffer_copy.js";
7
+ import Signal from "../../core/events/signal/Signal.js";
8
+ import { ShadeGPUComputePassEncoder } from "./pass/ShadeGPUComputePassEncoder.js";
9
+ import { ShadeGPURenderPassEncoder } from "./pass/ShadeGPURenderPassEncoder.js";
10
+ import { ShadeGPUContextProfiler } from "./timing/ShadeGPUContextProfiler.js";
11
+ import { warn_limited } from "../util/warn_limited.js";
12
+ import { write_gpu_typed_buffer } from "../renderer/shader/type/serde/write_gpu_typed_buffer.js";
13
+ import { BufferResourceDescriptor } from "./graph/BufferResourceDescriptor.js";
14
+ import { GraphicsExecutionContext } from "./graph/GraphicsExecutionContext.js";
15
+
16
+ /**
17
+ * Keep track of how many contexts are currently open (unfinished)
18
+ * Important because contexts can have transient resources allocated, and those are not cleaned up until the context is `finished`
19
+ * @type {number}
20
+ */
21
+ let debug_open_context_count = 0;
22
+
23
+ /**
24
+ *
25
+ * @type {ShadeGPUCommandContext[]}
26
+ */
27
+ const debug_open_contexts = [];
28
+
29
+ const scratch_buffer_descriptor = new BufferResourceDescriptor();
30
+
31
+ let id_counter = 0;
32
+
33
+ /**
34
+ * Abstraction on top of {@link GPUCommandEncoder}.
35
+ *
36
+ * Inspired by Intel's "MiniEngine" CommandContext
37
+ * @see https://github.com/GameTechDev/TAA/blob/39786709cf70a1e0906196c600f6079571a33ceb/MiniEngine/Core/CommandContext.h
38
+ * @see https://www.w3.org/TR/webgpu/#gpucommandencoder
39
+ */
40
+ export class ShadeGPUCommandContext {
41
+
42
+ #id = id_counter++;
43
+
44
+ /**
45
+ * Unique identifier for this context, useful for debugging
46
+ *
47
+ * @return {number}
48
+ */
49
+ get id() {
50
+ return this.#id;
51
+ }
52
+
53
+ /**
54
+ *
55
+ * @returns {boolean}
56
+ */
57
+ get isGPUCommandContext() {
58
+ return true;
59
+ }
60
+
61
+
62
+ /**
63
+ * Actual underlying encoder
64
+ * @type {GPUCommandEncoder}
65
+ */
66
+ #gpu_encoder
67
+
68
+ /**
69
+ * Don't circumvent {@link ShadeGPUCommandContext} unless you know what you're doing.
70
+ * {@link ShadeGPUCommandContext} is a wrapper around {@link GPUCommandEncoder} that provides additional spec-compliance asserts to help find errors sooner.
71
+ * @returns {GPUCommandEncoder}
72
+ */
73
+ get gpu_encoder() {
74
+ return this.#gpu_encoder;
75
+ }
76
+
77
+ /**
78
+ * This context's profiling attachment, or null when nothing is profiling — which is the
79
+ * ordinary case and costs one null check per pass.
80
+ *
81
+ * **Never cleared.** `finish()` is followed by a read of {@link profiling_absorbed}, and a null
82
+ * here reports an already-resolved promise, which closes the frame before a single timestamp has
83
+ * come back. It holds no GPU resources after `absorb()` destroys the timer array, so keeping the
84
+ * reference costs nothing.
85
+ *
86
+ * @type {ShadeGPUContextProfiler|null}
87
+ */
88
+ #profiler = null;
89
+
90
+ /**
91
+ * Resolves after this context's timings have been read back and handed to its recorder.
92
+ *
93
+ * **Not the same moment as {@link done}.** `done` resolves from `onFinished`, which fires
94
+ * synchronously at submit — before any timestamp has been read. A caller that closes its frame
95
+ * on `done` commits an empty one, every time. Already-resolved when nothing is profiling.
96
+ *
97
+ * @returns {Promise<void>}
98
+ */
99
+ get profiling_absorbed() {
100
+ const profiler = this.#profiler;
101
+
102
+ return profiler === null ? Promise.resolve() : profiler.absorbed;
103
+ }
104
+
105
+ /**
106
+ * Record this context's pass timings into `recorder`.
107
+ *
108
+ * @param {GPUFrameRecorder} recorder
109
+ * @param {number} query_set_id this context's index within the frame
110
+ * @returns {void}
111
+ */
112
+ enable_profiling(recorder, query_set_id) {
113
+ assert.notOk(this.#finished, 'already finished');
114
+ assert.isNull(this.#profiler, 'already profiling');
115
+
116
+ this.#profiler = new ShadeGPUContextProfiler(this.device, recorder, query_set_id);
117
+ }
118
+
119
+ /**
120
+ * @type {GraphicsContext}
121
+ */
122
+ #graphics
123
+
124
+ /**
125
+ *
126
+ * @return {GPUDevice}
127
+ */
128
+ get device() {
129
+ return this.#graphics.device;
130
+ }
131
+
132
+ /**
133
+ *
134
+ * @return {TextureManager}
135
+ */
136
+ get textures() {
137
+ return this.#graphics.textures;
138
+ }
139
+
140
+ /**
141
+ * These are to be destroyed at the end of the context's lifetime.
142
+ * Need to be released at the end of the lifetime.
143
+ * @type {GPUBuffer[]}
144
+ */
145
+ #transient_buffers = [];
146
+
147
+ /**
148
+ *
149
+ * @type {GPUBuffer[]}
150
+ */
151
+ #staging_buffers = [];
152
+
153
+ /**
154
+ * Once finished, the encoder becomes inactive, we use this flag to track potential misuse and inform the user
155
+ * @type {boolean}
156
+ */
157
+ #finished = false;
158
+
159
+ /**
160
+ * @readonly
161
+ * @type {Signal<this>}
162
+ */
163
+ onFinished = new Signal();
164
+
165
+ /**
166
+ * @readonly
167
+ * @type {Signal<this>}
168
+ */
169
+ onBeforeFinish = new Signal();
170
+
171
+ /**
172
+ *
173
+ * @return {Promise<void>}
174
+ */
175
+ get done() {
176
+ if (this.#finished) {
177
+ return Promise.resolve();
178
+ }
179
+
180
+ return new Promise(resolve => {
181
+ this.onFinished.addOne(resolve);
182
+
183
+ if (this.#finished) {
184
+ // special case, double-checking to avoid race conditions
185
+ resolve();
186
+ }
187
+
188
+ });
189
+ }
190
+
191
+ /**
192
+ * Do not call directly, always use {@link create}
193
+ * @private
194
+ */
195
+ constructor() {
196
+ // private, do not call from the outside
197
+ }
198
+
199
+ /**
200
+ *
201
+ * @param {GraphicsContext} graphics
202
+ * @param {string} [label]
203
+ * @returns {ShadeGPUCommandContext}
204
+ */
205
+ static create(
206
+ graphics,
207
+ label = ""
208
+ ) {
209
+
210
+ assert.defined(graphics, 'graphics');
211
+ assert.equal(graphics.isGraphicsContext, true, "graphics.isGraphicsContext !== true");
212
+
213
+ assert.isString(label, 'label');
214
+
215
+ const r = new ShadeGPUCommandContext();
216
+
217
+ r.#graphics = graphics;
218
+ r.#gpu_encoder = graphics.device.createCommandEncoder({
219
+ label
220
+ });
221
+
222
+ // track how many we have open
223
+ debug_open_context_count++;
224
+ array_push_if_unique(debug_open_contexts, r);
225
+
226
+ if (debug_open_context_count > 1024) {
227
+ warn_limited(`Too many open GPU contexts`, 20);
228
+ }
229
+
230
+ return r;
231
+
232
+ }
233
+
234
+ /**
235
+ *
236
+ * @returns {GraphicsExecutionContext}
237
+ */
238
+ createFrameGraphContext() {
239
+ return new GraphicsExecutionContext(this.#graphics, this);
240
+ }
241
+
242
+ /**
243
+ *
244
+ * @param {FrameGraph} graph
245
+ */
246
+ encodeGraph(graph) {
247
+ assert.defined(graph, 'graph');
248
+ assert.isObject(graph, 'graph');
249
+ assert.equal(graph.isFrameGraph, true, "graph.isFrameGraph !== true");
250
+
251
+ // TODO check if the graph is empty, if so, we can skip the whole thing
252
+
253
+ const ctx = this.createFrameGraphContext();
254
+
255
+ graph.compile();
256
+ graph.execute(ctx);
257
+ }
258
+
259
+ /**
260
+ * @see https://www.w3.org/TR/webgpu/#dom-gpucommandencoder-clearbuffer
261
+ * @param {GPUBuffer} buffer
262
+ * @param {number} [offset=0]
263
+ * @param {number} [size]
264
+ */
265
+ clearBuffer(buffer, offset = 0, size) {
266
+ assert.defined(buffer, 'buffer');
267
+ assert.equal(buffer.usage & GPUBufferUsage.COPY_DST, GPUBufferUsage.COPY_DST, 'buffer must include COPY_DST usage flag');
268
+ assert.isNonNegativeInteger(offset, 'offset');
269
+ assert.equal(offset % 4, 0, 'offset must be a multiple of 4');
270
+
271
+ // console.log(`cmd.clearBuffer(${buffer.label}, ${offset}, ${size})`);
272
+
273
+ this.#gpu_encoder.clearBuffer(buffer, offset, size);
274
+ }
275
+
276
+ /**
277
+ * Not supported, only here to offer documentation hint
278
+ * see {@link texture_fill_rectangle}
279
+ * @throws {Error} Always, documentation hint only
280
+ */
281
+ clearTexture() {
282
+ throw new Error(`Documentation hint only, use texture_fill_rectangle instead`);
283
+ }
284
+
285
+ /**
286
+ * NOTE: only the explicit verbose form of the API is supported, shorthand such as `copyBufferToBuffer(source, destination, size)` are not supported.
287
+ * see https://www.w3.org/TR/webgpu/#gpucommandencoder-copybuffertobuffer
288
+ * @see GPUCommandEncoder.copyBufferToBuffer
289
+ * @param {GPUBuffer} source The GPUBuffer to copy from.
290
+ * @param {number} sourceOffset Offset in bytes into the source to begin copying from.
291
+ * @param {GPUBuffer} destination The GPUBuffer to copy to.
292
+ * @param {number} destinationOffset Offset in bytes into destination to place the copied data.
293
+ * @param {number} [size] Bytes to copy. In not specified, defaults to `source.size - sourceOffset`
294
+ */
295
+ copyBufferToBuffer(
296
+ source,
297
+ sourceOffset,
298
+ destination,
299
+ destinationOffset,
300
+ size
301
+ ) {
302
+
303
+ assert.notEqual(source.usage & GPUBufferUsage.COPY_SRC, 0, 'source must include COPY_SRC usage flag');
304
+ assert.isNonNegativeInteger(sourceOffset, 'sourceOffset');
305
+ assert.equal(sourceOffset % 4, 0, 'sourceOffset must be a multiple of 4');
306
+
307
+ assert.notEqual(destination.usage & GPUBufferUsage.COPY_DST, 0, 'destination must include COPY_DST usage flag');
308
+ assert.isNonNegativeInteger(destinationOffset, 'destinationOffset');
309
+ assert.equal(destinationOffset % 4, 0, 'destinationOffset must be a multiple of 4');
310
+
311
+ if (size !== undefined) {
312
+ assert.isNonNegativeInteger(size, 'size');
313
+ assert.equal(size % 4, 0, 'size must be a multiple of 4');
314
+
315
+ assert.greaterThanOrEqual(source.size, sourceOffset + size, 'source underflow');
316
+ assert.greaterThanOrEqual(destination.size, destinationOffset + size, 'destination underflow');
317
+ }
318
+
319
+ assert.notEqual(source, destination, 'source and destination must be different');
320
+
321
+ // console.log(`cmd.copyBufferToBuffer("${source.label}", ${sourceOffset}, "${destination.label}", ${destinationOffset}, ${size})`);
322
+
323
+ this.#gpu_encoder.copyBufferToBuffer(
324
+ source,
325
+ sourceOffset,
326
+ destination,
327
+ destinationOffset,
328
+ size
329
+ );
330
+ }
331
+
332
+
333
+ /**
334
+ *
335
+ * @param {GPUImageCopyTexture} source
336
+ * @param {GPUImageCopyTexture} destination
337
+ * @param {GPUExtent3DStrict} copySize
338
+ */
339
+ copyTextureToTexture(source,
340
+ destination,
341
+ copySize
342
+ ) {
343
+ this.#gpu_encoder.copyTextureToTexture(source, destination, copySize);
344
+ }
345
+
346
+ /**
347
+ *
348
+ * @param {GPUComputePassDescriptor} [descriptor]
349
+ * @returns {ShadeGPUComputePassEncoder}
350
+ */
351
+ beginComputePass(descriptor) {
352
+ const encoder = this.#gpu_encoder;
353
+ const profiler = this.#profiler;
354
+
355
+ // console.log(`cmd.beginComputePass(${descriptor.label})`);
356
+
357
+ if (profiler === null) {
358
+ return new ShadeGPUComputePassEncoder(encoder.beginComputePass(descriptor), null);
359
+ }
360
+
361
+ return profiler.begin_compute_pass(encoder, descriptor);
362
+ }
363
+
364
+
365
+ /**
366
+ *
367
+ * @param {ComputePipelineDescriptor} pipeline
368
+ * @param {string} [label]
369
+ * @param {GPUBindingResource[][]} [bindings]
370
+ * @returns {ShadeGPUComputePassEncoder}
371
+ */
372
+ constructComputePass({
373
+ pipeline,
374
+ label,
375
+ bindings = [],
376
+ immediate
377
+ }) {
378
+
379
+ const pass_descriptor = {
380
+ label
381
+ };
382
+
383
+ if (label === undefined) {
384
+ pass_descriptor.label = pipeline.label;
385
+ }
386
+
387
+ const graphics = this.#graphics;
388
+
389
+ const pass = this.beginComputePass(pass_descriptor);
390
+
391
+ const gpu_pipeline = graphics.compute_pipelines.obtain(pipeline);
392
+
393
+ pass.setPipeline(gpu_pipeline);
394
+
395
+ pass.record_pipeline(pipeline);
396
+
397
+ graphics.setPipelineBindings(pass, pipeline, bindings, immediate);
398
+
399
+ // console.warn(`cmd.constructComputePass(${pass_descriptor.label})`)
400
+
401
+ return pass;
402
+ }
403
+
404
+ /**
405
+ * Begins a labeled debug group containing subsequent commands.
406
+ * @see https://gpuweb.github.io/gpuweb/#debug-markers
407
+ * @param {string} label
408
+ */
409
+ pushDebugGroup(label) {
410
+ assert.isString(label, 'label');
411
+ this.#gpu_encoder.pushDebugGroup(label);
412
+
413
+ // console.group("cmd/debug_group=" + label)
414
+ }
415
+
416
+ /**
417
+ * Ends the labeled debug group most recently started by pushDebugGroup().
418
+ * @see https://gpuweb.github.io/gpuweb/#debug-markers
419
+ */
420
+ popDebugGroup() {
421
+ this.#gpu_encoder.popDebugGroup();
422
+
423
+ // console.groupEnd();
424
+ }
425
+
426
+ /**
427
+ * Marks a point in a stream of commands with a label.
428
+ * @see https://gpuweb.github.io/gpuweb/#debug-markers
429
+ * @param {string} label
430
+ */
431
+ insertDebugMarker(label) {
432
+ assert.isString(label, 'label');
433
+ this.#gpu_encoder.insertDebugMarker(label);
434
+ }
435
+
436
+ /**
437
+ *
438
+ * @param {string} [label]
439
+ * @param {RenderPipelineDescriptor} pipeline
440
+ * @param {GPUBindingResource[][]} [bindings]
441
+ * @param {GPURenderPassColorAttachment[]} colorAttachments
442
+ * @param {GPURenderPassDepthStencilAttachment} [depthStencilAttachment]
443
+ * @returns {ShadeGPURenderPassEncoder}
444
+ */
445
+ constructRenderPass({
446
+ label,
447
+ pipeline,
448
+ bindings,
449
+ colorAttachments,
450
+ depthStencilAttachment,
451
+ immediate,
452
+ }) {
453
+ assert.defined(pipeline, 'pipeline');
454
+ assert.equal(pipeline.isRenderPipelineDescriptor, true, "pipeline.isRenderPipelineDescriptor !== true");
455
+
456
+ const pass_descriptor = {
457
+ label,
458
+ colorAttachments,
459
+ depthStencilAttachment
460
+ };
461
+
462
+ if (label === undefined) {
463
+ pass_descriptor.label = pipeline.label;
464
+ }
465
+
466
+ const graphics = this.#graphics;
467
+
468
+ // TODO rewrite the pipeline if necessary to achieve compatibility with colorAttachment formats
469
+ const gpu_pipeline = graphics.render_pipelines.obtain(pipeline);
470
+
471
+ const pass = this.beginRenderPass(pass_descriptor);
472
+
473
+ pass.setPipeline(gpu_pipeline);
474
+
475
+ pass.record_pipeline(pipeline);
476
+
477
+ graphics.setPipelineBindings(pass, pipeline, bindings, immediate);
478
+
479
+ // console.warn(`cmd.constructRenderPass(${pass_descriptor.label})`)
480
+
481
+ return pass;
482
+ }
483
+
484
+ /**
485
+ *
486
+ * @param {GPURenderPassDescriptor} descriptor
487
+ * @returns {ShadeGPURenderPassEncoder}
488
+ */
489
+ beginRenderPass(descriptor) {
490
+ const encoder = this.#gpu_encoder;
491
+ const profiler = this.#profiler;
492
+
493
+ // console.log(`cmd.beginRenderPass(${descriptor.label})`);
494
+
495
+ if (profiler === null) {
496
+ return new ShadeGPURenderPassEncoder(encoder.beginRenderPass(descriptor), null);
497
+ }
498
+
499
+ return profiler.begin_render_pass(encoder, descriptor);
500
+ }
501
+
502
+ /**
503
+ *
504
+ * @param {GPUQuerySet} querySet
505
+ * @param {number} firstQuery
506
+ * @param {number} queryCount
507
+ * @param {GPUBuffer} destination
508
+ * @param {number} destinationOffset
509
+ */
510
+ resolveQuerySet(querySet, firstQuery, queryCount, destination, destinationOffset) {
511
+ this.#gpu_encoder.resolveQuerySet(querySet, firstQuery, queryCount, destination, destinationOffset);
512
+ }
513
+
514
+ /**
515
+ * Transient buffers are automatically released at the end of the context's lifetime;
516
+ * however, we can explicitly release it earlier to allow for reuse.
517
+ *
518
+ * @param {GPUBuffer} buffer
519
+ * @return {boolean} true if buffer was released, false if it was not found
520
+ */
521
+ releaseTransientBuffer(buffer) {
522
+ assert.defined(buffer, 'buffer');
523
+
524
+ if (!array_remove_first(this.#transient_buffers, buffer)) {
525
+ return false;
526
+ }
527
+
528
+ const allocator = this.#transient_allocator;
529
+
530
+ return allocator.release(buffer);
531
+ }
532
+
533
+ /**
534
+ *
535
+ * @return {GPUBufferAllocator}
536
+ */
537
+ get #transient_allocator() {
538
+ return this.#graphics.buffer_allocator_main;
539
+ }
540
+
541
+ /**
542
+ * Allocated buffer will only be valid until the context is finished.
543
+ *
544
+ * @param {number|GPUBufferUsage} usage
545
+ * @param {number} size
546
+ * @returns {GPUBuffer} managed buffer
547
+ */
548
+ allocateTransientBuffer(
549
+ usage = GPUBufferUsage.UNIFORM,
550
+ size
551
+ ) {
552
+ assert.isNonNegativeInteger(size, 'size');
553
+ assert.isInteger(usage, 'usage');
554
+
555
+ assert.notOk(this.#finished, 'already finished');
556
+
557
+ scratch_buffer_descriptor.size = size;
558
+ // COPY_DST is needed to write the data
559
+ scratch_buffer_descriptor.usage = usage | GPUBufferUsage.COPY_DST;
560
+
561
+ const allocator = this.#transient_allocator;
562
+ const buffer = allocator.get(scratch_buffer_descriptor, this);
563
+
564
+ // remember so we can clean up at the end
565
+ this.#transient_buffers.push(buffer);
566
+
567
+ return buffer;
568
+ }
569
+
570
+ /**
571
+ *
572
+ * @param {ArrayBuffer} data CPU-side data to place into the GPU buffer
573
+ * @param {number|GPUBufferUsage} [usage=GPUBufferUsage.UNIFORM]
574
+ * @param {number} [offset] offset into source data where to start copying from
575
+ * @param {number} [size] in bytes
576
+ * @returns {GPUBuffer} managed buffer, will be automatically destroyed when context is finished
577
+ */
578
+ allocateTransientBufferAndLoad(
579
+ data,
580
+ usage = GPUBufferUsage.UNIFORM,
581
+ offset = 0,
582
+ size = data.byteLength
583
+ ) {
584
+ assert.isInstanceOf(data, ArrayBuffer, 'data');
585
+ assert.isInteger(usage, 'usage');
586
+ assert.isNonNegativeInteger(offset, 'offset');
587
+ assert.isNonNegativeInteger(size, 'size');
588
+
589
+ assert.notOk(this.#finished, 'already finished');
590
+
591
+ const buffer = this.allocateTransientBuffer(
592
+ usage,
593
+ size
594
+ );
595
+
596
+ this.writeBuffer(buffer, 0, data, offset, size);
597
+
598
+ return buffer;
599
+ }
600
+
601
+ /**
602
+ * Scratch for immediate data, created on first use and reused for the life of the context.
603
+ *
604
+ * One buffer is enough, and `position` returns to 0 for every write, because `setImmediates`
605
+ * copies the bytes it is given at the point of the call — the region is free again the moment
606
+ * the pass encoder has taken it. Nothing here ever grows: the default capacity is far above the
607
+ * 64-byte ceiling {@link ShaderResourceSetDescriptor#setImmediate} enforces, so the backing
608
+ * `ArrayBuffer` is allocated once and never reallocated.
609
+ *
610
+ * @type {BinaryBuffer|null}
611
+ */
612
+ #immediate_buffer = null;
613
+
614
+ /**
615
+ * Serialise a shader's immediate data ready for `setImmediates`, or return undefined when the
616
+ * shader declares none.
617
+ *
618
+ * Allocates nothing after the first call.
619
+ *
620
+ * @param {ShaderResourceSetDescriptor} resources
621
+ * @param {Object} map the shader's binding map
622
+ * @returns {{buffer: BinaryBuffer, offset: number, size: number}|undefined}
623
+ */
624
+ writeImmediate(resources, map) {
625
+ if (resources.immediateSize === 0) {
626
+ return undefined;
627
+ }
628
+
629
+ let buffer = this.#immediate_buffer;
630
+
631
+ if (buffer === null) {
632
+ buffer = new BinaryBuffer();
633
+
634
+ // GPU buffer contents must match the host, and BinaryBuffer's own default is
635
+ // little-endian regardless of platform. Getting this wrong produces bytes that are
636
+ // correct on every machine anyone is likely to test on and wrong on the rest.
637
+ buffer.endianness = platform_compute_endianness();
638
+
639
+ this.#immediate_buffer = buffer;
640
+ }
641
+
642
+ buffer.position = 0;
643
+
644
+ if (!resources.writeImmediate(map, buffer)) {
645
+ return undefined;
646
+ }
647
+
648
+ return { buffer, offset: 0, size: buffer.position };
649
+ }
650
+
651
+ /**
652
+ * @template T
653
+ * @param {WebGPUType} type
654
+ * @param {T} value
655
+ * @param {number|GPUBufferUsage} [usage]
656
+ * @return {GPUBuffer}
657
+ */
658
+ allocateTransientValueBuffer(
659
+ type,
660
+ value,
661
+ usage
662
+ ) {
663
+ assert.defined(type, 'type');
664
+ assert.notNull(type, 'type');
665
+
666
+ const buffer = this.allocateTransientBuffer(usage, type.aligned_size);
667
+
668
+ this.writeValueBuffer(buffer, 0, type, value);
669
+
670
+ return buffer;
671
+ }
672
+
673
+ /**
674
+ * @template T
675
+ * @param {GPUBuffer} buffer
676
+ * @param {number} buffer_offset
677
+ * @param {WebGPUType} type
678
+ * @param {T} value
679
+ */
680
+ writeValueBuffer(buffer, buffer_offset, type, value) {
681
+
682
+ assert.notOk(this.#finished, 'already finished');
683
+
684
+ const gfx = this.#graphics;
685
+
686
+ const size = type.aligned_size;
687
+
688
+ const stage = gfx.buffer_allocator_staging.get(size);
689
+
690
+ const mapped = stage.getMappedRange(0, size);
691
+
692
+ write_gpu_typed_buffer(value, type, mapped, 0);
693
+
694
+ stage.unmap();
695
+
696
+ this.#staging_buffers.push(stage);
697
+
698
+ this.copyBufferToBuffer(stage, 0, buffer, buffer_offset, size);
699
+ }
700
+
701
+ /**
702
+ * Very similar to {@link GPUQueue.writeBuffer}, but instead of doing the operation direction on the queue - uses a staging buffer under the hood and performs the actual write operation as part of the commmand buffer
703
+ * @param {GPUBuffer} buffer
704
+ * @param {number} buffer_offset
705
+ * @param {ArrayBuffer} data
706
+ * @param {number} data_offset
707
+ * @param {number} size
708
+ */
709
+ writeBuffer(buffer, buffer_offset, data, data_offset, size) {
710
+ assert.defined(buffer, 'buffer');
711
+ assert.isNonNegativeInteger(buffer_offset, 'buffer_offset');
712
+ assert.defined(data, 'data');
713
+ assert.isNonNegativeInteger(data_offset, 'data_offset');
714
+ assert.isNonNegativeInteger(size, 'size');
715
+
716
+ const gfx = this.#graphics;
717
+
718
+ const stage = gfx.buffer_allocator_staging.get(size);
719
+
720
+ const mapped = stage.getMappedRange(0, size);
721
+
722
+ array_buffer_copy(data, data_offset, mapped, 0, size);
723
+
724
+ stage.unmap();
725
+
726
+ this.#staging_buffers.push(stage);
727
+
728
+ this.copyBufferToBuffer(stage, 0, buffer, buffer_offset, size);
729
+
730
+ }
731
+
732
+ #cleanup() {
733
+
734
+ // console.warn('cmd.cleanup()');
735
+
736
+ const transient_buffers = this.#transient_buffers;
737
+ const transient_buffer_count = transient_buffers.length;
738
+
739
+ const buffer_allocator_transient = this.#transient_allocator;
740
+
741
+ // release in reverse order to make it more likely that we'd end up in the same acquisition order next time, which is desirable for caching
742
+ for (let i = transient_buffer_count - 1; i >= 0; i--) {
743
+ const buffer = transient_buffers[i];
744
+
745
+ buffer_allocator_transient.release(buffer);
746
+
747
+ }
748
+
749
+ // staging buffers
750
+ const buffer_allocator_staging = this.#graphics.buffer_allocator_staging;
751
+ for (const staging of this.#staging_buffers) {
752
+ buffer_allocator_staging.release(staging);
753
+ }
754
+ }
755
+
756
+ /**
757
+ * Performs both {@link GPUCommandEncoder.finish} and flushed command buffer
758
+ */
759
+ finish() {
760
+ if (this.#finished) {
761
+ console.warn('Context already finished');
762
+ return;
763
+ }
764
+
765
+ this.onBeforeFinish.send1(this);
766
+
767
+ debug_open_context_count--;
768
+ array_remove_first(debug_open_contexts, this);
769
+
770
+ const encoder = this.#gpu_encoder;
771
+
772
+ const profiler = this.#profiler;
773
+
774
+ if (profiler !== null) {
775
+ profiler.resolve(encoder);
776
+ }
777
+
778
+ this.#finished = true;
779
+
780
+ // release encoder reference
781
+ this.#gpu_encoder = undefined;
782
+
783
+ const command_buffer = encoder.finish();
784
+
785
+ const graphics = this.#graphics;
786
+ const queue = graphics.device.queue;
787
+
788
+ queue.submit([command_buffer]);
789
+
790
+ // cleanup allocated memory
791
+ this.#cleanup();
792
+
793
+ if (profiler !== null) {
794
+ // Kept, not cleared — `profiling_absorbed` is read after `finish()` and a null here
795
+ // would report an already-resolved promise. See the field.
796
+ profiler.absorb();
797
+ }
798
+
799
+ this.onFinished.send1(this);
800
+
801
+ }
995
802
  }