@bornengine/engine 0.4.16

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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/native/android/Cargo.lock +1848 -0
  4. package/native/android/Cargo.toml +24 -0
  5. package/native/android/src/lib.rs +702 -0
  6. package/native/ios/Cargo.lock +1690 -0
  7. package/native/ios/Cargo.toml +32 -0
  8. package/native/ios/src/lib.rs +1267 -0
  9. package/native/linux/Cargo.lock +3279 -0
  10. package/native/linux/Cargo.toml +29 -0
  11. package/native/linux/src/lib.rs +1331 -0
  12. package/native/macos/Cargo.lock +3310 -0
  13. package/native/macos/Cargo.toml +46 -0
  14. package/native/macos/src/lib.rs +1302 -0
  15. package/native/shared/Cargo.lock +1899 -0
  16. package/native/shared/Cargo.toml +62 -0
  17. package/native/shared/assets/default_font.ttf +0 -0
  18. package/native/shared/build.rs +270 -0
  19. package/native/shared/shaders/common/clouds.wgsl +122 -0
  20. package/native/shared/shaders/common/fog.wgsl +16 -0
  21. package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
  22. package/native/shared/shaders/common/imposter.wgsl +112 -0
  23. package/native/shared/shaders/common/pbr.wgsl +186 -0
  24. package/native/shared/shaders/common/shadows.wgsl +186 -0
  25. package/native/shared/shaders/common/sky.wgsl +8 -0
  26. package/native/shared/shaders/common/tonemap.wgsl +25 -0
  27. package/native/shared/shaders/impulse_field.wgsl +57 -0
  28. package/native/shared/shaders/material_abi.wgsl +383 -0
  29. package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
  30. package/native/shared/src/anim_mixer.rs +61 -0
  31. package/native/shared/src/attach.rs +263 -0
  32. package/native/shared/src/audio/decode.rs +123 -0
  33. package/native/shared/src/audio/mod.rs +863 -0
  34. package/native/shared/src/audio/render.rs +892 -0
  35. package/native/shared/src/audio/spsc.rs +156 -0
  36. package/native/shared/src/audio/stream.rs +226 -0
  37. package/native/shared/src/custom_shaders.rs +104 -0
  38. package/native/shared/src/decals.rs +245 -0
  39. package/native/shared/src/drs.rs +211 -0
  40. package/native/shared/src/engine.rs +261 -0
  41. package/native/shared/src/ffi.rs +116 -0
  42. package/native/shared/src/ffi_core/assets.rs +388 -0
  43. package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
  44. package/native/shared/src/ffi_core/draw.rs +334 -0
  45. package/native/shared/src/ffi_core/game_loop.rs +577 -0
  46. package/native/shared/src/ffi_core/input.rs +234 -0
  47. package/native/shared/src/ffi_core/mod.rs +127 -0
  48. package/native/shared/src/ffi_core/models.rs +1154 -0
  49. package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
  50. package/native/shared/src/ffi_core/scene.rs +626 -0
  51. package/native/shared/src/ffi_core/vfx.rs +212 -0
  52. package/native/shared/src/ffi_core/visual.rs +691 -0
  53. package/native/shared/src/frame_callbacks.rs +122 -0
  54. package/native/shared/src/geometry.rs +236 -0
  55. package/native/shared/src/handles.rs +182 -0
  56. package/native/shared/src/input.rs +448 -0
  57. package/native/shared/src/jolt_sys.rs +822 -0
  58. package/native/shared/src/lib.rs +55 -0
  59. package/native/shared/src/models.rs +1093 -0
  60. package/native/shared/src/models_gltf.rs +1280 -0
  61. package/native/shared/src/particles.rs +391 -0
  62. package/native/shared/src/physics_jolt.rs +1908 -0
  63. package/native/shared/src/picking.rs +298 -0
  64. package/native/shared/src/postfx.rs +345 -0
  65. package/native/shared/src/profiler.rs +492 -0
  66. package/native/shared/src/ragdoll.rs +474 -0
  67. package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
  68. package/native/shared/src/renderer/brdf_lut.rs +154 -0
  69. package/native/shared/src/renderer/draw2d.rs +143 -0
  70. package/native/shared/src/renderer/formats.rs +822 -0
  71. package/native/shared/src/renderer/froxel.rs +421 -0
  72. package/native/shared/src/renderer/gi_bake.rs +653 -0
  73. package/native/shared/src/renderer/graph.rs +462 -0
  74. package/native/shared/src/renderer/hiz.rs +269 -0
  75. package/native/shared/src/renderer/hot_reload.rs +390 -0
  76. package/native/shared/src/renderer/impulse_field.rs +456 -0
  77. package/native/shared/src/renderer/lighting.rs +154 -0
  78. package/native/shared/src/renderer/material_instancing.rs +171 -0
  79. package/native/shared/src/renderer/material_pipeline.rs +700 -0
  80. package/native/shared/src/renderer/material_system.rs +1996 -0
  81. package/native/shared/src/renderer/material_system_tests.rs +601 -0
  82. package/native/shared/src/renderer/material_system_wasm.rs +41 -0
  83. package/native/shared/src/renderer/mod.rs +12556 -0
  84. package/native/shared/src/renderer/model_draw.rs +641 -0
  85. package/native/shared/src/renderer/occlusion.rs +429 -0
  86. package/native/shared/src/renderer/planar_pass.rs +593 -0
  87. package/native/shared/src/renderer/planar_reflection.rs +499 -0
  88. package/native/shared/src/renderer/post_pass.rs +249 -0
  89. package/native/shared/src/renderer/postfx_chain.rs +728 -0
  90. package/native/shared/src/renderer/pt_pass.rs +577 -0
  91. package/native/shared/src/renderer/scene_pass.rs +607 -0
  92. package/native/shared/src/renderer/shader_include.rs +205 -0
  93. package/native/shared/src/renderer/shader_library.rs +135 -0
  94. package/native/shared/src/renderer/shaders/ao.rs +570 -0
  95. package/native/shared/src/renderer/shaders/core.rs +1243 -0
  96. package/native/shared/src/renderer/shaders/env.rs +907 -0
  97. package/native/shared/src/renderer/shaders/gi.rs +810 -0
  98. package/native/shared/src/renderer/shaders/mod.rs +19 -0
  99. package/native/shared/src/renderer/shaders/post.rs +1558 -0
  100. package/native/shared/src/renderer/shaders/pt.rs +1859 -0
  101. package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
  102. package/native/shared/src/renderer/shadow_pass.rs +731 -0
  103. package/native/shared/src/renderer/ssgi_pass.rs +392 -0
  104. package/native/shared/src/renderer/ssr_pass.rs +188 -0
  105. package/native/shared/src/renderer/texture_store.rs +473 -0
  106. package/native/shared/src/renderer/transient.rs +591 -0
  107. package/native/shared/src/renderer/types.rs +941 -0
  108. package/native/shared/src/renderer/util.rs +152 -0
  109. package/native/shared/src/scene.rs +1362 -0
  110. package/native/shared/src/sdf_cache.rs +274 -0
  111. package/native/shared/src/shadows.rs +1036 -0
  112. package/native/shared/src/staging.rs +102 -0
  113. package/native/shared/src/string_header.rs +266 -0
  114. package/native/shared/src/text_renderer.rs +502 -0
  115. package/native/shared/src/textures.rs +197 -0
  116. package/native/tvos/Cargo.lock +1693 -0
  117. package/native/tvos/Cargo.toml +36 -0
  118. package/native/tvos/metal-patched/Cargo.toml +178 -0
  119. package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
  120. package/native/tvos/metal-patched/LICENSE-MIT +25 -0
  121. package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
  122. package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
  123. package/native/tvos/metal-patched/src/argument.rs +366 -0
  124. package/native/tvos/metal-patched/src/blitpass.rs +102 -0
  125. package/native/tvos/metal-patched/src/buffer.rs +71 -0
  126. package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
  127. package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
  128. package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
  129. package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
  130. package/native/tvos/metal-patched/src/computepass.rs +107 -0
  131. package/native/tvos/metal-patched/src/constants.rs +152 -0
  132. package/native/tvos/metal-patched/src/counters.rs +119 -0
  133. package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
  134. package/native/tvos/metal-patched/src/device.rs +2134 -0
  135. package/native/tvos/metal-patched/src/drawable.rs +39 -0
  136. package/native/tvos/metal-patched/src/encoder.rs +2041 -0
  137. package/native/tvos/metal-patched/src/heap.rs +281 -0
  138. package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
  139. package/native/tvos/metal-patched/src/lib.rs +657 -0
  140. package/native/tvos/metal-patched/src/library.rs +902 -0
  141. package/native/tvos/metal-patched/src/mps.rs +575 -0
  142. package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
  143. package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
  144. package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
  145. package/native/tvos/metal-patched/src/renderpass.rs +443 -0
  146. package/native/tvos/metal-patched/src/resource.rs +182 -0
  147. package/native/tvos/metal-patched/src/sampler.rs +165 -0
  148. package/native/tvos/metal-patched/src/sync.rs +178 -0
  149. package/native/tvos/metal-patched/src/texture.rs +352 -0
  150. package/native/tvos/metal-patched/src/types.rs +90 -0
  151. package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
  152. package/native/tvos/src/audio_backend.rs +197 -0
  153. package/native/tvos/src/lib.rs +1891 -0
  154. package/native/visionos/Cargo.lock +1693 -0
  155. package/native/visionos/Cargo.toml +40 -0
  156. package/native/visionos/src/audio_backend.rs +197 -0
  157. package/native/visionos/src/lib.rs +1887 -0
  158. package/native/watchos/Cargo.lock +16 -0
  159. package/native/watchos/Cargo.toml +19 -0
  160. package/native/watchos/shaders/bloom_postfx.metal +99 -0
  161. package/native/watchos/src/BloomWatchApp.swift +1267 -0
  162. package/native/watchos/src/BloomWatchAudio.swift +179 -0
  163. package/native/watchos/src/audio.rs +55 -0
  164. package/native/watchos/src/draw_list.rs +229 -0
  165. package/native/watchos/src/ffi_stubs.rs +915 -0
  166. package/native/watchos/src/ffi_stubs_manual.rs +35 -0
  167. package/native/watchos/src/lib.rs +1124 -0
  168. package/native/watchos/src/models.rs +746 -0
  169. package/native/watchos/src/postfx.rs +95 -0
  170. package/native/watchos/src/scene.rs +534 -0
  171. package/native/watchos/src/textures.rs +184 -0
  172. package/native/web/Cargo.lock +1657 -0
  173. package/native/web/Cargo.toml +43 -0
  174. package/native/web/bloom_glue.js +695 -0
  175. package/native/web/build.sh +131 -0
  176. package/native/web/index.html +35 -0
  177. package/native/web/jolt_bridge.js +1519 -0
  178. package/native/web/src/input_ffi.rs +286 -0
  179. package/native/web/src/lib.rs +1796 -0
  180. package/native/web/src/material_ffi.rs +710 -0
  181. package/native/web/src/parity_ffi.rs +343 -0
  182. package/native/web/src/physics_ffi.rs +643 -0
  183. package/native/web/src/ragdoll_ffi.rs +250 -0
  184. package/native/web/src/render_settings.rs +98 -0
  185. package/native/windows/Cargo.lock +1815 -0
  186. package/native/windows/Cargo.toml +68 -0
  187. package/native/windows/src/lib.rs +1486 -0
  188. package/package.json +4279 -0
  189. package/src/audio/index.ts +315 -0
  190. package/src/core/colors.ts +63 -0
  191. package/src/core/index.ts +1206 -0
  192. package/src/core/keys.ts +63 -0
  193. package/src/core/types.ts +104 -0
  194. package/src/index.ts +171 -0
  195. package/src/math/index.ts +516 -0
  196. package/src/mobile/index.ts +294 -0
  197. package/src/models/index.ts +1258 -0
  198. package/src/physics/index.ts +1134 -0
  199. package/src/scene/index.ts +698 -0
  200. package/src/shapes/index.ts +120 -0
  201. package/src/text/index.ts +48 -0
  202. package/src/textures/index.ts +187 -0
  203. package/src/vfx/index.ts +191 -0
  204. package/src/world/index.ts +24 -0
  205. package/src/world/loader.ts +423 -0
  206. package/src/world/prefab.ts +217 -0
  207. package/src/world/render.ts +172 -0
  208. package/src/world/saver.ts +108 -0
  209. package/src/world/serialize.ts +301 -0
  210. package/src/world/terrain.ts +355 -0
  211. package/src/world/types.ts +160 -0
  212. package/src/world/validate.ts +319 -0
  213. package/src/world/version.ts +114 -0
@@ -0,0 +1,1206 @@
1
+ import { Color, Camera2D, Camera3D } from './types';
2
+
3
+ export type { Color, Vec2, Vec3, Vec4, Rect, Camera2D, Camera3D, Texture, Font, Sound, Music, Quat, Ray, BoundingBox, Model, Mat4, RayHit, FrustumPlanes } from './types';
4
+ // GH #53 — `Color` is deliberately NOT re-exported from './colors' any more.
5
+ // `Color` is the RGBA TYPE (`./types`, re-exported above); './colors' exports a
6
+ // palette MAP that also happened to be called `Color`, so the name arrived at
7
+ // every consumer as both a type and a value. It shadowed the thing people
8
+ // actually annotate with. The palette has always had two other names —
9
+ // `Colors` and `ColorConstants` (the latter is literally `= Color`) — so
10
+ // nothing is lost by dropping the third.
11
+ //
12
+ // Verified before removing: no code in engine, shooter, editor, garden or jump
13
+ // reads the palette through the name `Color` (i.e. `Color.Red`). `jump` imports
14
+ // `Color` from here, but only ever in TYPE position (`const WHITE: Color = {...}`),
15
+ // which the type re-export above still serves.
16
+ export { ColorConstants, Colors } from './colors';
17
+ export { Key, MouseButton } from './keys';
18
+
19
+ // FFI declarations
20
+ declare function bloom_init_window(width: number, height: number, title: number, fullscreen: number): void;
21
+ declare function bloom_attach_native(handle: number, width: number, height: number): number;
22
+ declare function bloom_close_window(): void;
23
+ declare function bloom_attach_hwnd(hwnd: number, width: number, height: number): void;
24
+ declare function bloom_resize(physW: number, physH: number, logW: number, logH: number): void;
25
+ declare function bloom_window_should_close(): number;
26
+ declare function bloom_begin_drawing(): void;
27
+ declare function bloom_end_drawing(): void;
28
+ declare function bloom_take_screenshot(path: number): void;
29
+ declare function bloom_clear_background(r: number, g: number, b: number, a: number): void;
30
+ declare function bloom_set_env_clear_from_hdr(path: number): void;
31
+ declare function bloom_set_fog(r: number, g: number, b: number, density: number, height_ref: number, height_falloff: number): void;
32
+ declare function bloom_set_chromatic_aberration(strength: number): void;
33
+ declare function bloom_set_vignette(strength: number, softness: number): void;
34
+ declare function bloom_set_film_grain(strength: number): void;
35
+ declare function bloom_set_sharpen_strength(strength: number): void;
36
+ declare function bloom_set_present_mode(mode: number): void;
37
+ declare function bloom_set_sun_shafts(strength: number, decay: number, r: number, g: number, b: number): void;
38
+ declare function bloom_set_auto_exposure(on: number): void;
39
+ declare function bloom_set_taa_enabled(on: number): void;
40
+ declare function bloom_set_occlusion_culling(on: number): void;
41
+ declare function bloom_set_render_scale(scale: number): void;
42
+ declare function bloom_set_output_scale(scale: number): void;
43
+ declare function bloom_launch_process(cmd: string, args: string, cwd: string): number;
44
+ declare function bloom_get_output_scale(): number;
45
+ declare function bloom_get_render_scale(): number;
46
+ declare function bloom_set_upscale_mode(mode: number): void;
47
+ declare function bloom_set_cas_strength(strength: number): void;
48
+ declare function bloom_get_physical_width(): number;
49
+ declare function bloom_get_physical_height(): number;
50
+ declare function bloom_set_auto_resolution(targetHz: number, enabled: number): void;
51
+ declare function bloom_set_manual_exposure(value: number): void;
52
+ declare function bloom_set_env_intensity(intensity: number): void;
53
+ declare function bloom_set_ssgi_enabled(on: number): void;
54
+ declare function bloom_set_path_tracing(mode: number): void;
55
+ declare function bloom_path_tracing_supported(): number;
56
+ declare function bloom_set_ssgi_intensity(intensity: number): void;
57
+ declare function bloom_set_ssgi_radius(radius: number): void;
58
+ declare function bloom_set_dof(enabled: number, focusDistance: number, aperture: number): void;
59
+ declare function bloom_set_quality_preset(preset: number): void;
60
+ declare function bloom_set_shadows_enabled(on: number): void;
61
+ declare function bloom_set_shadows_always_fresh(on: number): void;
62
+ declare function bloom_set_bloom_enabled(on: number): void;
63
+ declare function bloom_set_bloom_intensity(value: number): void;
64
+ declare function bloom_set_tonemap(kind: number): void;
65
+ declare function bloom_set_auto_exposure_key(key: number): void;
66
+ declare function bloom_set_auto_exposure_rate(rate: number): void;
67
+ declare function bloom_set_ssao_enabled(on: number): void;
68
+ declare function bloom_set_post_pass(source: number): number;
69
+ declare function bloom_clear_post_pass(): void;
70
+ declare function bloom_add_post_pass(source: number): number;
71
+ declare function bloom_clear_all_post_passes(): void;
72
+ declare function bloom_set_ssao_intensity(intensity: number): void;
73
+ declare function bloom_set_ssao_radius(worldRadius: number): void;
74
+ declare function bloom_set_wind(dirX: number, dirZ: number, amplitude: number, frequency: number): void;
75
+ declare function bloom_set_cloud_shadows(strength: number, deckHeight: number, featureScale: number, driftSpeed: number): void;
76
+ declare function bloom_set_ssr_enabled(on: number): void;
77
+ declare function bloom_set_motion_blur_enabled(on: number): void;
78
+ declare function bloom_set_sss_enabled(on: number): void;
79
+ declare function bloom_set_profiler_enabled(on: number): void;
80
+ declare function bloom_get_profiler_frame_cpu_us(): number;
81
+ declare function bloom_get_profiler_frame_gpu_us(): number;
82
+ declare function bloom_print_profiler_summary(): void;
83
+ declare function bloom_profiler_overlay_text(): string;
84
+ declare function bloom_profiler_frame_history(): string;
85
+ // EN-020 — numeric profiler ABI. Perry 0.5.x's split()/parseFloat()
86
+ // overread their own exact-sized slice allocations; parsing a packed
87
+ // text blob every overlay frame crashes within seconds once a slice
88
+ // lands at the end of a heap page. Numbers cross as f64, labels cross
89
+ // whole and are only ever drawn, never parsed.
90
+ declare function bloom_profiler_row_count(): number;
91
+ declare function bloom_profiler_row_label(i: number): string;
92
+ declare function bloom_profiler_row_cpu_us(i: number): number;
93
+ declare function bloom_profiler_row_gpu_us(i: number): number;
94
+ declare function bloom_profiler_hist_count(): number;
95
+ declare function bloom_profiler_hist_cpu_us(i: number): number;
96
+ declare function bloom_profiler_hist_gpu_us(i: number): number;
97
+ declare function bloom_splat_impulse(x: number, z: number, radius: number, strength: number): void;
98
+ declare function bloom_set_material_params_scratch(handle: number, paramCount: number): void;
99
+ declare function bloom_mesh_scratch_reset(): void;
100
+ declare function bloom_mesh_scratch_push_f32(v: number): void;
101
+ declare function bloom_set_target_fps(fps: number): void;
102
+ declare function bloom_set_direct_2d_mode(on: number): void;
103
+ declare function bloom_get_delta_time(): number;
104
+ declare function bloom_get_fps(): number;
105
+ declare function bloom_get_screen_width(): number;
106
+ declare function bloom_get_screen_height(): number;
107
+ declare function bloom_is_key_pressed(key: number): number;
108
+ declare function bloom_is_key_repeated(key: number): number;
109
+ declare function bloom_is_key_down(key: number): number;
110
+ declare function bloom_is_key_released(key: number): number;
111
+ declare function bloom_get_mouse_x(): number;
112
+ declare function bloom_get_mouse_y(): number;
113
+ declare function bloom_is_mouse_button_pressed(btn: number): number;
114
+ declare function bloom_is_mouse_button_down(btn: number): number;
115
+ declare function bloom_is_mouse_button_released(btn: number): number;
116
+
117
+ // Camera FFI
118
+ declare function bloom_begin_mode_2d(ox: number, oy: number, tx: number, ty: number, rot: number, zoom: number): void;
119
+ declare function bloom_end_mode_2d(): void;
120
+ declare function bloom_begin_mode_3d(px: number, py: number, pz: number, tx: number, ty: number, tz: number, ux: number, uy: number, uz: number, fovy: number, proj: number): void;
121
+ declare function bloom_end_mode_3d(): void;
122
+
123
+ // Gamepad FFI
124
+ declare function bloom_is_gamepad_available(): number;
125
+ declare function bloom_get_gamepad_axis(axis: number): number;
126
+ declare function bloom_is_gamepad_button_pressed(btn: number): number;
127
+ declare function bloom_is_gamepad_button_down(btn: number): number;
128
+ declare function bloom_is_gamepad_button_released(btn: number): number;
129
+ declare function bloom_get_gamepad_axis_count(): number;
130
+
131
+ // Touch FFI
132
+ declare function bloom_get_touch_x(index: number): number;
133
+ declare function bloom_get_touch_y(index: number): number;
134
+ declare function bloom_get_touch_count(): number;
135
+ declare function bloom_is_touch_active(index: number): number;
136
+ declare function bloom_get_max_touch_points(): number;
137
+
138
+ // Input injection FFI
139
+ declare function bloom_inject_key_down(key: number): void;
140
+ declare function bloom_inject_key_up(key: number): void;
141
+ declare function bloom_inject_gamepad_axis(axis: number, value: number): void;
142
+ declare function bloom_inject_gamepad_button_down(button: number): void;
143
+ declare function bloom_inject_gamepad_button_up(button: number): void;
144
+ declare function bloom_get_platform(): number;
145
+ declare function bloom_get_language(): number;
146
+ declare function bloom_is_any_input_pressed(): number;
147
+ declare function bloom_get_crown_rotation(): number;
148
+
149
+ // Utility FFI
150
+ declare function bloom_toggle_fullscreen(): void;
151
+ declare function bloom_set_window_title(title: number): void;
152
+ declare function bloom_get_time(): number;
153
+ declare function bloom_set_window_icon(path: number): void;
154
+ declare function bloom_disable_cursor(): void;
155
+ declare function bloom_enable_cursor(): void;
156
+ declare function bloom_get_mouse_delta_x(): number;
157
+ declare function bloom_get_mouse_delta_y(): number;
158
+ declare function bloom_get_mouse_wheel(): number;
159
+ declare function bloom_get_char_pressed(): number;
160
+ declare function bloom_set_cursor_shape(shape: number): void;
161
+ declare function bloom_set_clipboard_text(text: number): void;
162
+ declare function bloom_get_clipboard_text(): number;
163
+ declare function bloom_open_file_dialog(filter: number, title: number): number;
164
+ declare function bloom_save_file_dialog(defaultName: number, title: number): number;
165
+ declare function bloom_write_file(path: number, data: number): number;
166
+ declare function bloom_file_exists(path: number): number;
167
+ declare function bloom_read_file(path: number): number;
168
+ declare function bloom_run_game(callback: number): void;
169
+
170
+ // Window management
171
+
172
+ export function initWindow(width: number, height: number, title: string, fullscreen: boolean = false): void {
173
+ bloom_init_window(width, height, title as any, fullscreen ? 1.0 : 0.0);
174
+ }
175
+
176
+ /**
177
+ * Attach the engine to a host-owned native render surface instead of
178
+ * creating its own window (PerryTS/perry#5519). `handle` is the
179
+ * platform's native view / window / surface pointer — e.g. the `NSView*`
180
+ * / `UIView*` / `GtkWidget*` / `ANativeWindow*` / `HWND` returned by
181
+ * Perry UI's `bloomViewGetNativeHandle`. `width`/`height` are the host
182
+ * view's size in logical points. On success the host owns the run loop
183
+ * and drives frames with `beginDrawing()` / `endDrawing()` as usual.
184
+ *
185
+ * Returns `true` if the engine attached and built its surface, `false`
186
+ * on a null/invalid handle or if surface bring-up failed. Idempotent: a
187
+ * second call once attached is a no-op that returns `true`.
188
+ *
189
+ * The platform-named aliases below forward to this same entry point; use
190
+ * whichever reads clearest for the target you're building.
191
+ */
192
+ export function attachToNativeView(handle: number, width: number, height: number): boolean {
193
+ return bloom_attach_native(handle, width, height) !== 0;
194
+ }
195
+
196
+ /** macOS — attach to a host `NSView*`. See {@link attachToNativeView}. */
197
+ export function attachToNSView(view: number, width: number, height: number): boolean {
198
+ return bloom_attach_native(view, width, height) !== 0;
199
+ }
200
+
201
+ /** iOS / tvOS / visionOS — attach to a host `UIView*`. See {@link attachToNativeView}. */
202
+ export function attachToUIView(view: number, width: number, height: number): boolean {
203
+ return bloom_attach_native(view, width, height) !== 0;
204
+ }
205
+
206
+ /**
207
+ * Linux/GTK4 (`GtkWidget*`), Android (`ANativeWindow*`), Windows (`HWND`)
208
+ * — attach to a host surface handle. See {@link attachToNativeView}.
209
+ */
210
+ export function attachToSurface(handle: number, width: number, height: number): boolean {
211
+ return bloom_attach_native(handle, width, height) !== 0;
212
+ }
213
+
214
+ export function closeWindow(): void {
215
+ bloom_close_window();
216
+ }
217
+
218
+ /**
219
+ * @deprecated Windows-only, and returns nothing so you cannot tell whether the
220
+ * attach actually worked. Use {@link attachToNativeView} — it is portable
221
+ * (NSView* / UIView* / GtkWidget* / ANativeWindow* / HWND) and returns a
222
+ * boolean. Perry's `bloomViewGetHwnd` is likewise a deprecated alias of
223
+ * `bloomViewGetNativeHandle` (perry #5519), so a caller on this path is using
224
+ * the old name at BOTH ends.
225
+ *
226
+ * Embed Bloom inside a host-provided native window — e.g. a Perry UI
227
+ * `BloomView` widget. Pass the window handle and the logical viewport size.
228
+ * Bloom builds its render surface on that window and subclasses it for
229
+ * resize/input; the host owns the message loop, so drive frames yourself with
230
+ * `beginDrawing()` / `update` / `endDrawing()` (do NOT call `runGame`, which
231
+ * blocks). Call once, after the host window is shown and laid out — i.e. on the
232
+ * first frame where the handle is non-zero, not merely the first tick.
233
+ */
234
+ export function attachToHwnd(hwnd: number, width: number, height: number): void {
235
+ bloom_attach_hwnd(hwnd, width, height);
236
+ }
237
+
238
+ /** Resize the embedded surface explicitly (physical + logical pixels). */
239
+ export function resize(physW: number, physH: number, logW: number, logH: number): void {
240
+ bloom_resize(physW, physH, logW, logH);
241
+ }
242
+
243
+ export function windowShouldClose(): boolean {
244
+ return bloom_window_should_close() !== 0;
245
+ }
246
+
247
+ // Drawing lifecycle
248
+
249
+ export function beginDrawing(): void {
250
+ bloom_begin_drawing();
251
+ }
252
+
253
+ export function endDrawing(): void {
254
+ bloom_end_drawing();
255
+ }
256
+
257
+ /**
258
+ * Capture the next rendered frame and write it as a PNG to `path`.
259
+ * The actual capture happens during the next `endDrawing()` call —
260
+ * call this immediately before that endDrawing(), and the file will
261
+ * be on disk afterwards.
262
+ *
263
+ * Used by `bloom-diff` and CI image regression workflows.
264
+ */
265
+ export function takeScreenshot(path: string): void {
266
+ bloom_take_screenshot(path as any);
267
+ }
268
+
269
+ export function clearBackground(color: Color): void {
270
+ bloom_clear_background(color.r, color.g, color.b, color.a);
271
+ }
272
+
273
+ /**
274
+ * Set the clear color from the average luminance-weighted color of
275
+ * an HDR environment map (.hdr / Radiance format). A stand-in for
276
+ * proper equirect-sky-pass rendering until that lands — lets us
277
+ * immediately close most of the background-color gap between Bloom's
278
+ * realtime output and the path-traced reference.
279
+ */
280
+ export function setEnvClearFromHdr(path: string): void {
281
+ bloom_set_env_clear_from_hdr(path as any);
282
+ }
283
+
284
+ // ---- Post-FX knobs ----
285
+ // All default to off. Calling these turns the corresponding
286
+ // composite-pass / TAA-pass effect on for the rest of the run
287
+ // (or until called again with 0 / disabled values).
288
+
289
+ /** Height-based exponential fog. Density 0 = off. */
290
+ export function setFog(r: number, g: number, b: number, density: number, heightRef: number, heightFalloff: number): void {
291
+ bloom_set_fog(r, g, b, density, heightRef, heightFalloff);
292
+ }
293
+
294
+ /** Radial RGB-channel split at the screen edges. 0 = off. */
295
+ export function setChromaticAberration(strength: number): void {
296
+ bloom_set_chromatic_aberration(strength);
297
+ }
298
+
299
+ /** Smooth radial darkening of the corners. strength 0..1, softness 0..1. */
300
+ export function setVignette(strength: number, softness: number): void {
301
+ bloom_set_vignette(strength, softness);
302
+ }
303
+
304
+ /** Animated film grain post-tonemap. 0 = off. */
305
+ export function setFilmGrain(strength: number): void {
306
+ bloom_set_film_grain(strength);
307
+ }
308
+
309
+ /**
310
+ * Composite unsharp-mask strength. Engine default 0.8; 0 disables the
311
+ * sharpen taps entirely. At high output resolutions the default visibly
312
+ * halos high-contrast silhouettes — tune per game.
313
+ */
314
+ export function setSharpenStrength(strength: number): void {
315
+ bloom_set_sharpen_strength(strength);
316
+ }
317
+
318
+ /**
319
+ * Swapchain present mode: 0 = Fifo (vsync, default), 1 = Mailbox
320
+ * (uncapped, no tearing), 2 = Immediate (uncapped, tearing allowed).
321
+ * With a non-vsync mode active, `setTargetFPS`'s sleep-based cap
322
+ * becomes effective — under Fifo it is inert by design.
323
+ */
324
+ export function setPresentMode(mode: number): void {
325
+ bloom_set_present_mode(mode);
326
+ }
327
+
328
+ /** Screen-space sun shafts (god rays). strength 0 = off. */
329
+ export function setSunShafts(strength: number, decay: number, r: number, g: number, b: number): void {
330
+ bloom_set_sun_shafts(strength, decay, r, g, b);
331
+ }
332
+
333
+ /** Toggle physically-based auto-exposure. 18% gray target, log-average metered. */
334
+ export function setAutoExposure(on: boolean): void {
335
+ bloom_set_auto_exposure(on ? 1 : 0);
336
+ }
337
+
338
+ /** Toggle temporal anti-aliasing (sub-pixel jitter + reprojected history blend). */
339
+ /**
340
+ * Hi-Z occlusion culling: scene nodes provably hidden behind other
341
+ * geometry (per last frame's depth) are skipped in the main camera
342
+ * pass. Conservative with one frame of latency; on by default. This is
343
+ * the kill switch for debugging or for scenes that pathologically
344
+ * defeat it (e.g. every object visible every frame).
345
+ */
346
+ export function setOcclusionCulling(on: boolean): void {
347
+ bloom_set_occlusion_culling(on ? 1 : 0);
348
+ }
349
+
350
+ export function setTaaEnabled(on: boolean): void {
351
+ bloom_set_taa_enabled(on ? 1 : 0);
352
+ }
353
+
354
+ /**
355
+ * Render-resolution multiplier. 0.5 = quarter-pixel shading (cheap, soft);
356
+ * 1.0 = native (sharp, expensive). Clamped to [0.5, 1.0]. Once called
357
+ * explicitly, the choice sticks across `setTaaEnabled` toggles instead of
358
+ * being overridden by the legacy 0.5↔1.0 coupling.
359
+ *
360
+ * On a 4K display: 0.75 hits a quality/perf sweet spot for 3D scenes.
361
+ * Catmull-Rom is the default upscale filter (see `setUpscaleMode`).
362
+ */
363
+ export function setRenderScale(scale: number): void {
364
+ bloom_set_render_scale(Math.min(1.0, Math.max(0.5, scale)));
365
+ }
366
+
367
+ /// OUTPUT scale — configure the swapchain at this fraction of the window's real
368
+ /// size and let the display stretch it back up.
369
+ ///
370
+ /// This is NOT `setRenderScale`, and the difference is the whole point.
371
+ /// `setRenderScale` shrinks the G-buffer and everything that runs at render
372
+ /// resolution, then TSR upscales to the swapchain. `setOutputScale` shrinks the
373
+ /// swapchain ITSELF — so it is the only knob that touches the fixed cost of that
374
+ /// upscale and the final composite. On a 4K display those two passes were measured
375
+ /// at 3.1 ms + 2.4 ms and did not care what the render scale was.
376
+ ///
377
+ /// 1.0 = native. Expose it to players: at 4K it is the difference between a locked
378
+ /// frame rate and a pretty one, and which of those they want is not the game's call.
379
+ export function setOutputScale(scale: number): void {
380
+ bloom_set_output_scale(Math.min(1.0, Math.max(0.25, scale)));
381
+ }
382
+ export function getOutputScale(): number { return bloom_get_output_scale(); }
383
+ export function getRenderScale(): number { return bloom_get_render_scale(); }
384
+
385
+ /** Upscale filter when render_scale < 1 and TAA is off. "bilinear" = cheap/soft, "catmull-rom" = sharper (default). */
386
+ export type UpscaleMode = "bilinear" | "catmull-rom";
387
+ export function setUpscaleMode(mode: UpscaleMode): void {
388
+ bloom_set_upscale_mode(mode === "catmull-rom" ? 1 : 0);
389
+ }
390
+
391
+ /**
392
+ * Contrast-adaptive sharpen strength. 0 = off (default, pass skipped);
393
+ * 0.3 subtle; 0.6 punchy; 1.0 max. Useful at any render_scale — pairs
394
+ * particularly well with TAA-softened native or Catmull-Rom upscale.
395
+ */
396
+ export function setCasStrength(strength: number): void {
397
+ bloom_set_cas_strength(strength);
398
+ }
399
+
400
+ /** Physical-pixel size of the GPU surface (HiDPI-aware on macOS today). */
401
+ export function getPhysicalWidth(): number { return bloom_get_physical_width(); }
402
+ export function getPhysicalHeight(): number { return bloom_get_physical_height(); }
403
+
404
+ /**
405
+ * Dynamic resolution scaling. When enabled, the engine self-tunes
406
+ * `render_scale` toward the given target framerate using a 6-rung
407
+ * ladder (0.50–1.00) with EMA-smoothed frame time, asymmetric
408
+ * hysteresis, and a 30-frame cooldown between steps.
409
+ *
410
+ * Call with `enabled = false` to disarm. Manual `setRenderScale`
411
+ * still works while DRS is on (DRS will simply step away from the
412
+ * value on its next eligible frame).
413
+ */
414
+ export function setAutoResolution(targetHz: number, enabled: boolean = true): void {
415
+ bloom_set_auto_resolution(targetHz, enabled ? 1 : 0);
416
+ }
417
+
418
+ /** Manual exposure multiplier (ignored when auto-exposure is on). 1.0 = default. */
419
+ export function setManualExposure(value: number): void {
420
+ bloom_set_manual_exposure(value);
421
+ }
422
+
423
+ /** Env-map intensity multiplier for IBL + sky pass. 1.0 = reference, 0.2–0.5 typical for bright outdoor HDRs. */
424
+ export function setEnvIntensity(intensity: number): void {
425
+ bloom_set_env_intensity(intensity);
426
+ }
427
+
428
+ /** Toggle screen-space global illumination (single-bounce indirect diffuse). Default on. */
429
+ export function setSsgiEnabled(on: boolean): void {
430
+ bloom_set_ssgi_enabled(on ? 1 : 0);
431
+ }
432
+
433
+ /**
434
+ * Path-tracing mode (engine docs/pt/pt-roadmap.md):
435
+ * 0 — off: the normal raster + Lumen pipeline (default).
436
+ * 1 — progressive: 1 sample/frame accumulated while the camera is still;
437
+ * resets on movement. Converges to ground truth — the "final quality"
438
+ * view for the editor and for stills.
439
+ * 2 — realtime: denoised 1-sample path tracing for gameplay.
440
+ * Requires hardware ray query (see isPathTracingSupported); on devices
441
+ * without it the request is a no-op and the engine stays on Lumen.
442
+ */
443
+ export function setPathTracing(mode: number): void {
444
+ bloom_set_path_tracing(mode);
445
+ }
446
+
447
+ /** True when the device can hardware-path-trace (ray query + TLAS). */
448
+ export function isPathTracingSupported(): boolean {
449
+ return bloom_path_tracing_supported() !== 0;
450
+ }
451
+
452
+ /** SSGI intensity multiplier. 0 = off, 0.5 = default, 1+ = strong. */
453
+ export function setSsgiIntensity(intensity: number): void {
454
+ bloom_set_ssgi_intensity(intensity);
455
+ }
456
+
457
+ /** SSGI max view-space march distance in meters. Default 20. Tune to scene scale. */
458
+ export function setSsgiRadius(radius: number): void {
459
+ bloom_set_ssgi_radius(radius);
460
+ }
461
+
462
+ /** Depth of field. focusDistance = view-space distance in world units. aperture = blur strength (0 = off, 0.03 = subtle, 0.1 = heavy). */
463
+ export function setDepthOfField(focusDistance: number, aperture: number): void {
464
+ bloom_set_dof(aperture > 0 ? 1 : 0, focusDistance, aperture);
465
+ }
466
+
467
+ // ============================================================
468
+ // Render quality — let games pick a preset for the target device
469
+ // or toggle individual effects. Presets apply a known-good set of
470
+ // flags; individual setters can override afterward.
471
+ // ============================================================
472
+
473
+ export enum QualityPreset {
474
+ /** Bare minimum — no shadows, no SSAO, no bloom, no TAA, no SSR/SSGI/DoF/MB/SSS. */
475
+ Off = 0,
476
+ /** Base pipeline only: HDR tonemap + bloom. No shadows/SSAO/TAA. */
477
+ Low = 1,
478
+ /** Balanced default: shadows + SSAO + bloom + TAA. No SSR/SSGI/cinematic FX. */
479
+ Medium = 2,
480
+ /** + SSR, SSGI, subtle chromatic aberration. */
481
+ High = 3,
482
+ /** Everything on (plus DoF if aperture > 0). */
483
+ Ultra = 4,
484
+ }
485
+
486
+ /** Apply a quality preset in one call. Call individual setters after for fine-tuning. */
487
+ export function setQualityPreset(preset: QualityPreset): void {
488
+ bloom_set_quality_preset(preset);
489
+ }
490
+
491
+ /** Toggle cascaded shadow maps. Default on. Disable on low-end GPUs — biggest single win. */
492
+ export function setShadowsEnabled(on: boolean): void {
493
+ bloom_set_shadows_enabled(on ? 1 : 0);
494
+ }
495
+
496
+ /**
497
+ * Force cascaded shadow maps to re-render every frame, bypassing the
498
+ * static-caster cache (ticket 004). Default off. Turn on for games
499
+ * with continuously-changing light state (day/night cycles set from
500
+ * native code, heavily-deformable casters) where the cache hit rate
501
+ * would be ~zero anyway, so skipping the check saves a few µs.
502
+ */
503
+ export function setShadowsAlwaysFresh(on: boolean): void {
504
+ bloom_set_shadows_always_fresh(on ? 1 : 0);
505
+ }
506
+
507
+ /** Toggle the bloom down/upsample chain (~10 passes). Default on. */
508
+ export function setBloomEnabled(on: boolean): void {
509
+ bloom_set_bloom_enabled(on ? 1 : 0);
510
+ }
511
+
512
+ /**
513
+ * Bloom contribution strength added to the HDR scene before tonemap.
514
+ * 0 = none, ~0.04 subtle default, higher = stronger glow around bright pixels.
515
+ */
516
+ export function setBloomIntensity(intensity: number): void {
517
+ bloom_set_bloom_intensity(intensity);
518
+ }
519
+
520
+ /** Tonemap operator selection. */
521
+ export enum Tonemap {
522
+ /** Filmic ACES (default). */
523
+ ACES = 0,
524
+ /** AgX — more filmic highlight roll-off + a punchier, better-saturated look. */
525
+ AgX = 1,
526
+ }
527
+
528
+ /** Choose the tonemap operator applied in the composite pass. */
529
+ export function setTonemap(kind: Tonemap): void {
530
+ bloom_set_tonemap(kind);
531
+ }
532
+
533
+ /**
534
+ * Auto-exposure target (scene-average luma key). Lower aims for a darker,
535
+ * more saturated midpoint (counteracts wash-out from very bright skies);
536
+ * higher aims brighter. Only affects frames where auto-exposure is on.
537
+ */
538
+ export function setAutoExposureKey(key: number): void {
539
+ bloom_set_auto_exposure_key(key);
540
+ }
541
+
542
+ /** Auto-exposure adaptation rate per frame (0 = frozen, ~0.05 smooth, 1 = instant). */
543
+ export function setAutoExposureRate(rate: number): void {
544
+ bloom_set_auto_exposure_rate(rate);
545
+ }
546
+
547
+ /** Toggle screen-space ambient occlusion + its bilateral blur. Default on. */
548
+ export function setSsaoEnabled(on: boolean): void {
549
+ bloom_set_ssao_enabled(on ? 1 : 0);
550
+ }
551
+
552
+ /** SSAO strength. 0 disables corner darkening, 1 is default, 2 is heavy. */
553
+ export function setSsaoIntensity(intensity: number): void {
554
+ bloom_set_ssao_intensity(intensity);
555
+ }
556
+
557
+ /** SSAO sampling radius in world units. 0.1..2.0 m is the sane range. */
558
+ export function setSsaoRadius(worldRadius: number): void {
559
+ bloom_set_ssao_radius(worldRadius);
560
+ }
561
+
562
+ /// EN-017 — install a game-supplied fullscreen WGSL post-pass.
563
+ /// Runs after composite + tonemapping, before the 2D overlay, so
564
+ /// the HUD stays crisp. The fragment shader sees scene_color_tex
565
+ /// (LDR, post-tonemap) and scene_depth_tex at @group(0).
566
+ ///
567
+ /// Example — underwater tint:
568
+ /// setPostPass(`
569
+ /// @fragment fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
570
+ /// let scene = textureSample(scene_color_tex, scene_color_samp, uv);
571
+ /// return vec4<f32>(scene.rgb * vec3<f32>(0.4, 0.7, 0.9), 1.0);
572
+ /// }
573
+ /// `);
574
+ ///
575
+ /// Returns true on successful compile, false on shader error.
576
+ /// In V2 this is shorthand for `clearAllPostPasses()` followed by
577
+ /// `addPostPass(wgsl)` — the existing stack is wiped before the new
578
+ /// pass is installed, matching V1 single-slot semantics.
579
+ export function setPostPass(wgslSource: string): boolean {
580
+ return bloom_set_post_pass(wgslSource as any) > 0;
581
+ }
582
+
583
+ /// EN-017 — uninstall the active post-pass. The composite output
584
+ /// goes directly to the swapchain again (zero post-pass cost).
585
+ /// V2 alias for `clearAllPostPasses()`.
586
+ export function clearPostPass(): void {
587
+ bloom_clear_post_pass();
588
+ }
589
+
590
+ /// EN-017 V2 — append a fullscreen WGSL post-pass to the stack.
591
+ /// Each pass samples the previous pass's output (or scene_color_tex
592
+ /// for the first pass) and writes either to the next intermediate
593
+ /// (if more passes follow) or to the swapchain (if last).
594
+ ///
595
+ /// Stack order matters: addPostPass(A); addPostPass(B); means A
596
+ /// runs first, then B sees A's output. Compose effects (e.g.
597
+ /// underwater tint, then damage flash, then scope vignette) in the
598
+ /// order they should apply.
599
+ ///
600
+ /// Returns the 0-based index of the newly added pass on success,
601
+ /// or -1 if the shader failed to compile (existing stack untouched).
602
+ export function addPostPass(wgslSource: string): number {
603
+ const r = bloom_add_post_pass(wgslSource as any);
604
+ return r > 0 ? (r - 1) : -1;
605
+ }
606
+
607
+ /// EN-017 V2 — wipe the entire post-pass stack. The composite
608
+ /// output goes directly to the swapchain again (zero post-pass cost).
609
+ export function clearAllPostPasses(): void {
610
+ bloom_clear_all_post_passes();
611
+ }
612
+
613
+ /// Set the global wind field used by foliage materials.
614
+ /// dirX/dirZ define the wind direction in the XZ plane (need not be
615
+ /// normalised; magnitude scales effective amplitude).
616
+ /// amplitude is the displacement scale (~0.1 m typical for grass).
617
+ /// frequency is in Hz (~1.0 typical).
618
+ export function setWind(dirX: number, dirZ: number, amplitude: number, frequency: number): void {
619
+ bloom_set_wind(dirX, dirZ, amplitude, frequency);
620
+ }
621
+
622
+ /// Cloud deck — the clouds the sky draws and the shadows they cast, from ONE
623
+ /// field. Look up at a cloud, and the shadow you are standing in is its shadow.
624
+ ///
625
+ /// `strength` is the only argument most callers need. 0 (the default) leaves the
626
+ /// world unshadowed and the clouds sky-only; ~0.45 dims a shadowed surface to a
627
+ /// bit over half its direct sunlight, which is about right for a bright day. It
628
+ /// scales DIRECT sun only — a cloud blocks the sun, it does not stop the sky
629
+ /// from being blue.
630
+ ///
631
+ /// `deckHeight` and `featureScale` are not independent knobs for "cloud size
632
+ /// overhead" and "shadow size underfoot": they are the same cloud seen from two
633
+ /// directions. Raise the deck and the puffs look smaller overhead while their
634
+ /// shadows stay the same size; lower `featureScale` and the clouds get bigger
635
+ /// both above and below.
636
+ ///
637
+ /// Drift direction comes from `setWind`, so the deck travels the way the foliage
638
+ /// beneath it is leaning.
639
+ /// (No default parameter values: Perry 0.5.x silently drops the call.)
640
+ export function setCloudShadows(
641
+ strength: number,
642
+ deckHeight: number,
643
+ featureScale: number,
644
+ driftSpeed: number,
645
+ ): void {
646
+ bloom_set_cloud_shadows(strength, deckHeight, featureScale, driftSpeed);
647
+ }
648
+
649
+ /** Toggle screen-space reflections. Default on. */
650
+ export function setSsrEnabled(on: boolean): void {
651
+ bloom_set_ssr_enabled(on ? 1 : 0);
652
+ }
653
+
654
+ /** Toggle per-object motion blur. Default off. */
655
+ export function setMotionBlurEnabled(on: boolean): void {
656
+ bloom_set_motion_blur_enabled(on ? 1 : 0);
657
+ }
658
+
659
+ /** Toggle subsurface scattering (for skin/wax materials). Default off. */
660
+ export function setSssEnabled(on: boolean): void {
661
+ bloom_set_sss_enabled(on ? 1 : 0);
662
+ }
663
+
664
+ // ============================================================
665
+ // Profiler — measure CPU phase timings and (on supported GPUs)
666
+ // per-pass GPU times. Off by default; enable at runtime.
667
+ // ============================================================
668
+
669
+ /** Enable/disable the frame profiler. When off, it has zero per-frame cost. */
670
+ export function setProfilerEnabled(on: boolean): void {
671
+ bloom_set_profiler_enabled(on ? 1 : 0);
672
+ }
673
+
674
+ /** Average total CPU frame time (sum of all phases) over the rolling window, in microseconds. */
675
+ export function getProfilerFrameCpuUs(): number {
676
+ return bloom_get_profiler_frame_cpu_us();
677
+ }
678
+
679
+ /** Average total GPU frame time over the rolling window, in microseconds. 0 if GPU timing unavailable. */
680
+ export function getProfilerFrameGpuUs(): number {
681
+ return bloom_get_profiler_frame_gpu_us();
682
+ }
683
+
684
+ /** Print a per-phase CPU/GPU timing table to stdout. Useful for quick diagnostics. */
685
+ export function printProfilerSummary(): void {
686
+ bloom_print_profiler_summary();
687
+ }
688
+
689
+ /**
690
+ * Phase 7 — submit a world-space impulse splat. Per-frame compute
691
+ * accumulates + decays into a 256×256 top-down field covering a 128m
692
+ * centred square. Refractive/translucent materials sampling
693
+ * `impulse_tex` (group 4 binding 4) see the result. Up to 16 splats
694
+ * per frame; excess is dropped. Typical uses:
695
+ * - Footsteps in mud / wet pavement
696
+ * - Splashes when the player enters water
697
+ * - Explosion rings, impact ripples
698
+ */
699
+ export function splatImpulse(x: number, z: number, radius: number, strength: number): void {
700
+ bloom_splat_impulse(x, z, radius, strength);
701
+ }
702
+
703
+ /**
704
+ * Phase 5 — set per-material `user_params` (ABI §1.4). Bytes are
705
+ * uploaded to `@group(2) @binding(11)` for the next dispatch of the
706
+ * given material. The shader casts them to whatever struct it
707
+ * declared. Up to 64 floats (256-byte ABI cap).
708
+ *
709
+ * Pass an empty array to revert to the default zero-initialised UBO.
710
+ *
711
+ * Example: a water material with a dynamic tint + wave amplitude
712
+ * `setMaterialParams(matWater, [0.10, 0.30, 0.40, 1.0, 0.20])`
713
+ * lets game code change colour per-zone without recompiling WGSL.
714
+ */
715
+ export function setMaterialParams(handle: number, params: number[]): void {
716
+ // Perry 0.5.x rejects JS arrays passed into pointer params, so the floats
717
+ // go through the all-f64 mesh scratch (same fix as createMesh /
718
+ // createInstanceBuffer). ≤ 64 floats per the 256-byte UBO cap, so the
719
+ // per-float FFI cost is negligible.
720
+ bloom_mesh_scratch_reset();
721
+ for (let i = 0; i < params.length; i++) bloom_mesh_scratch_push_f32(params[i]);
722
+ bloom_set_material_params_scratch(handle, params.length);
723
+ }
724
+
725
+ /**
726
+ * Per-pass timings, sorted descending by CPU time. Each row:
727
+ * `{ label, cpuUs, gpuUs }` (gpuUs = -1 when the device has no
728
+ * TIMESTAMP_QUERY feature).
729
+ * Intended for an in-game overlay — games call it at draw time and
730
+ * render one `drawText` per entry.
731
+ */
732
+ export function getProfilerOverlay(): { label: string, cpuUs: number, gpuUs: number }[] {
733
+ // EN-020: per-row numeric FFI — do NOT reintroduce a packed-text +
734
+ // split()/parseFloat() path here (Perry runtime overread, crashes).
735
+ const out: { label: string, cpuUs: number, gpuUs: number }[] = [];
736
+ const n = bloom_profiler_row_count();
737
+ for (let i = 0; i < n; i++) {
738
+ out.push({
739
+ label: bloom_profiler_row_label(i),
740
+ cpuUs: bloom_profiler_row_cpu_us(i),
741
+ gpuUs: bloom_profiler_row_gpu_us(i),
742
+ });
743
+ }
744
+ return out;
745
+ }
746
+
747
+ /**
748
+ * Phase 8 — last ~120 frames' CPU + GPU totals, oldest first.
749
+ * Useful for an overlay bar-chart of frame-time variance. GPU time
750
+ * is 0 when the device lacks TIMESTAMP_QUERY.
751
+ */
752
+ export function getProfilerFrameHistory(): { cpuUs: number, gpuUs: number }[] {
753
+ // EN-020: numeric FFI — see getProfilerOverlay.
754
+ const out: { cpuUs: number, gpuUs: number }[] = [];
755
+ const n = bloom_profiler_hist_count();
756
+ for (let i = 0; i < n; i++) {
757
+ out.push({
758
+ cpuUs: bloom_profiler_hist_cpu_us(i),
759
+ gpuUs: bloom_profiler_hist_gpu_us(i),
760
+ });
761
+ }
762
+ return out;
763
+ }
764
+
765
+ // Timing
766
+
767
+ export function setTargetFPS(fps: number): void {
768
+ bloom_set_target_fps(fps);
769
+ }
770
+
771
+ /**
772
+ * Enable direct-to-swapchain 2D mode. Skips scene prep, shadow maps,
773
+ * HDR/tonemap, SSAO, bloom, SDF/WSRC bakes and mesh-card passes —
774
+ * rendering goes straight through the batched 2D pipeline. Intended
775
+ * for pure-2D games that never populate the scene graph; on mobile
776
+ * GPUs this is the difference between ~15 fps and 60 fps.
777
+ *
778
+ * Call once after initWindow(). Off by default.
779
+ */
780
+ export function setDirect2DMode(on: boolean): void {
781
+ bloom_set_direct_2d_mode(on ? 1.0 : 0.0);
782
+ }
783
+
784
+ export function getDeltaTime(): number {
785
+ return bloom_get_delta_time();
786
+ }
787
+
788
+ export function getFPS(): number {
789
+ return bloom_get_fps();
790
+ }
791
+
792
+ export function getTime(): number {
793
+ return bloom_get_time();
794
+ }
795
+
796
+ // Screen
797
+
798
+ export function getScreenWidth(): number {
799
+ return bloom_get_screen_width();
800
+ }
801
+
802
+ export function getScreenHeight(): number {
803
+ return bloom_get_screen_height();
804
+ }
805
+
806
+ // Keyboard input
807
+
808
+ export function isKeyPressed(key: number): boolean {
809
+ return bloom_is_key_pressed(key) !== 0;
810
+ }
811
+
812
+ /**
813
+ * True for one frame each time the OS auto-repeats a held key. A SEPARATE
814
+ * edge from isKeyPressed (which stays initial-press-only, so a held jump key
815
+ * never machine-guns). Use `isKeyPressed(k) || isKeyRepeated(k)` for caret
816
+ * navigation and other hold-to-repeat UI.
817
+ */
818
+ export function isKeyRepeated(key: number): boolean {
819
+ return bloom_is_key_repeated(key) !== 0;
820
+ }
821
+
822
+ export function isKeyDown(key: number): boolean {
823
+ return bloom_is_key_down(key) !== 0;
824
+ }
825
+
826
+ export function isKeyReleased(key: number): boolean {
827
+ return bloom_is_key_released(key) !== 0;
828
+ }
829
+
830
+ // Mouse input
831
+
832
+ export function getMouseX(): number {
833
+ return bloom_get_mouse_x();
834
+ }
835
+
836
+ export function getMouseY(): number {
837
+ return bloom_get_mouse_y();
838
+ }
839
+
840
+ export function isMouseButtonPressed(button: number): boolean {
841
+ return bloom_is_mouse_button_pressed(button) !== 0;
842
+ }
843
+
844
+ export function isMouseButtonDown(button: number): boolean {
845
+ return bloom_is_mouse_button_down(button) !== 0;
846
+ }
847
+
848
+ export function isMouseButtonReleased(button: number): boolean {
849
+ return bloom_is_mouse_button_released(button) !== 0;
850
+ }
851
+
852
+ // Convenience wrappers
853
+
854
+ export function getMousePosition(): { x: number; y: number } {
855
+ return { x: bloom_get_mouse_x(), y: bloom_get_mouse_y() };
856
+ }
857
+
858
+ export function getTouchPosition(index: number): { x: number; y: number } {
859
+ return { x: bloom_get_touch_x(index), y: bloom_get_touch_y(index) };
860
+ }
861
+
862
+ // Camera 2D
863
+
864
+ export function beginMode2D(camera: Camera2D): void {
865
+ bloom_begin_mode_2d(camera.offset.x, camera.offset.y, camera.target.x, camera.target.y, camera.rotation, camera.zoom);
866
+ }
867
+
868
+ // Raw variant: takes primitives directly. Workaround for aarch64 Android
869
+ // Perry miscompilation where obj.field reads feeding f64 FFI args arrive as NaN.
870
+ export function beginMode2DRaw(offsetX: number, offsetY: number, targetX: number, targetY: number, rotation: number, zoom: number): void {
871
+ bloom_begin_mode_2d(offsetX, offsetY, targetX, targetY, rotation, zoom);
872
+ }
873
+
874
+ export function endMode2D(): void {
875
+ bloom_end_mode_2d();
876
+ }
877
+
878
+ // Camera 3D
879
+
880
+ export function beginMode3D(camera: Camera3D): void {
881
+ const proj = camera.projection === "orthographic" ? 1 : 0;
882
+ bloom_begin_mode_3d(
883
+ camera.position.x, camera.position.y, camera.position.z,
884
+ camera.target.x, camera.target.y, camera.target.z,
885
+ camera.up.x, camera.up.y, camera.up.z,
886
+ camera.fovy, proj,
887
+ );
888
+ }
889
+
890
+ export function endMode3D(): void {
891
+ bloom_end_mode_3d();
892
+ }
893
+
894
+ // Gamepad — spec-compliant signatures with gamepad ID
895
+
896
+ export function isGamepadAvailable(id?: number): boolean {
897
+ return bloom_is_gamepad_available() !== 0;
898
+ }
899
+
900
+ export function getGamepadAxisValue(id: number, axis: number): number {
901
+ return bloom_get_gamepad_axis(axis);
902
+ }
903
+
904
+ export function getGamepadAxis(axis: number): number {
905
+ return bloom_get_gamepad_axis(axis);
906
+ }
907
+
908
+ export function isGamepadButtonPressed(button: number): boolean {
909
+ return bloom_is_gamepad_button_pressed(button) !== 0;
910
+ }
911
+
912
+ export function isGamepadButtonDown(button: number): boolean {
913
+ return bloom_is_gamepad_button_down(button) !== 0;
914
+ }
915
+
916
+ declare function bloom_gamepad_rumble(low: number, high: number, seconds: number): void;
917
+
918
+ /// EN-031 — vibrate the pad. `low` drives the heavy (low-frequency) motor and
919
+ /// `high` the light one, both 0..1; `seconds` is how long before it stops on
920
+ /// its own, so callers never have to remember to switch it off.
921
+ ///
922
+ /// A no-op on platforms with no motor, and silently ignored if no pad is
923
+ /// connected.
924
+ export function gamepadRumble(low: number, high: number, seconds: number): void {
925
+ bloom_gamepad_rumble(low, high, seconds);
926
+ }
927
+
928
+ export function isGamepadButtonReleased(button: number): boolean {
929
+ return bloom_is_gamepad_button_released(button) !== 0;
930
+ }
931
+
932
+ export function getGamepadAxisCount(): number {
933
+ return bloom_get_gamepad_axis_count();
934
+ }
935
+
936
+ // Touch
937
+
938
+ export function getTouchX(index: number): number {
939
+ return bloom_get_touch_x(index);
940
+ }
941
+
942
+ export function getTouchY(index: number): number {
943
+ return bloom_get_touch_y(index);
944
+ }
945
+
946
+ export function getTouchCount(): number {
947
+ return bloom_get_touch_count();
948
+ }
949
+
950
+ export function getTouchPointCount(): number {
951
+ return bloom_get_touch_count();
952
+ }
953
+
954
+ /// Is touch *slot* `index` holding a live finger?
955
+ ///
956
+ /// `getTouchCount()` is the number of active fingers, but touch points are
957
+ /// addressed by slot, and slots go sparse the moment a finger lifts out of
958
+ /// order: hold two fingers, lift the first, and the live finger is at slot 1
959
+ /// while the count is 1. Iterating `0..getTouchCount()` then reads slot 0 —
960
+ /// released, but still carrying its last coordinates — as though it were live,
961
+ /// which reads as a finger frozen at the point where it left the glass.
962
+ ///
963
+ /// Track fingers by scanning `0..getMaxTouchPoints()` and skipping the slots
964
+ /// this returns false for.
965
+ export function isTouchActive(index: number): boolean {
966
+ return bloom_is_touch_active(index) > 0.5;
967
+ }
968
+
969
+ export function getMaxTouchPoints(): number {
970
+ return bloom_get_max_touch_points();
971
+ }
972
+
973
+ // Utility
974
+
975
+ export function toggleFullscreen(): void {
976
+ bloom_toggle_fullscreen();
977
+ }
978
+
979
+ export function setWindowTitle(title: string): void {
980
+ bloom_set_window_title(title as any);
981
+ }
982
+
983
+ export function setWindowIcon(path: string): void {
984
+ bloom_set_window_icon(path as any);
985
+ }
986
+
987
+ export function disableCursor(): void {
988
+ bloom_disable_cursor();
989
+ }
990
+
991
+ export function enableCursor(): void {
992
+ bloom_enable_cursor();
993
+ }
994
+
995
+ export function getMouseDeltaX(): number {
996
+ return bloom_get_mouse_delta_x();
997
+ }
998
+
999
+ export function getMouseDeltaY(): number {
1000
+ return bloom_get_mouse_delta_y();
1001
+ }
1002
+
1003
+ /**
1004
+ * Accumulated vertical scroll-wheel delta since the last call to this
1005
+ * function. Positive values mean scrolling up (away from user on macOS);
1006
+ * use this for camera zoom and scrollable UI panels. Reading consumes
1007
+ * the value, so call it exactly once per frame.
1008
+ */
1009
+ export function getMouseWheel(): number {
1010
+ return bloom_get_mouse_wheel();
1011
+ }
1012
+
1013
+ /**
1014
+ * Dequeue the next typed character as a Unicode codepoint. Returns 0 when
1015
+ * the queue is empty. Call in a loop each frame to consume all pending
1016
+ * characters:
1017
+ *
1018
+ * let c = getCharPressed();
1019
+ * while (c !== 0) {
1020
+ * // handle character c
1021
+ * c = getCharPressed();
1022
+ * }
1023
+ *
1024
+ * Printable characters (codepoint >= 32) plus backspace (8), return (13),
1025
+ * and tab (9) are enqueued. Platform-specific text input methods (NSEvent
1026
+ * characters on macOS, WM_CHAR on Windows, etc.) feed this queue.
1027
+ */
1028
+ export function getCharPressed(): number {
1029
+ return bloom_get_char_pressed();
1030
+ }
1031
+
1032
+ /**
1033
+ * Set the mouse cursor shape. Values:
1034
+ * 0 = default (arrow), 1 = hand, 2 = move, 3 = text (I-beam),
1035
+ * 4 = resize horizontal, 5 = resize vertical, 6 = crosshair.
1036
+ * Applied per-frame by the platform event loop.
1037
+ */
1038
+ export const CursorShape = { Default: 0, Hand: 1, Move: 2, Text: 3, ResizeH: 4, ResizeV: 5, Crosshair: 6 } as const;
1039
+
1040
+ export function setCursorShape(shape: number): void {
1041
+ bloom_set_cursor_shape(shape);
1042
+ }
1043
+
1044
+ /**
1045
+ * Copy text to the system clipboard.
1046
+ */
1047
+ export function setClipboardText(text: string): void {
1048
+ bloom_set_clipboard_text(text as any);
1049
+ }
1050
+
1051
+ /**
1052
+ * Read text from the system clipboard. Returns empty string on failure.
1053
+ */
1054
+ export function getClipboardText(): string {
1055
+ return bloom_get_clipboard_text() as any;
1056
+ }
1057
+
1058
+ /**
1059
+ * Open a native file-open dialog. Returns the selected file path, or
1060
+ * empty string if the user cancelled. `filter` is a file extension
1061
+ * (e.g. "world.json") or empty for all files.
1062
+ */
1063
+ export function openFileDialog(filter: string, title: string): string {
1064
+ return bloom_open_file_dialog(filter as any, title as any) as any;
1065
+ }
1066
+
1067
+ /**
1068
+ * Open a native file-save dialog. Returns the chosen save path, or
1069
+ * empty string if cancelled.
1070
+ */
1071
+ export function saveFileDialog(defaultName: string, title: string): string {
1072
+ return bloom_save_file_dialog(defaultName as any, title as any) as any;
1073
+ }
1074
+
1075
+ // File I/O
1076
+
1077
+ export function writeFile(path: string, data: string): boolean {
1078
+ return bloom_write_file(path as any, data as any) !== 0.0;
1079
+ }
1080
+
1081
+ export function fileExists(path: string): boolean {
1082
+ return bloom_file_exists(path as any) !== 0.0;
1083
+ }
1084
+
1085
+ export function readFile(path: string): string {
1086
+ return bloom_read_file(path as any) as any;
1087
+ }
1088
+
1089
+ // Input injection
1090
+
1091
+ export function injectKeyDown(key: number): void { bloom_inject_key_down(key); }
1092
+ export function injectKeyUp(key: number): void { bloom_inject_key_up(key); }
1093
+ export function injectGamepadAxis(axis: number, value: number): void { bloom_inject_gamepad_axis(axis, value); }
1094
+ export function injectGamepadButtonDown(button: number): void { bloom_inject_gamepad_button_down(button); }
1095
+ export function injectGamepadButtonUp(button: number): void { bloom_inject_gamepad_button_up(button); }
1096
+
1097
+ // Platform detection
1098
+
1099
+ export const Platform = { UNKNOWN: 0, MACOS: 1, IOS: 2, WINDOWS: 3, LINUX: 4, ANDROID: 5, TVOS: 6, WEB: 7, WATCHOS: 8, VISIONOS: 9 } as const;
1100
+
1101
+ export function getPlatform(): number { return bloom_get_platform(); }
1102
+
1103
+ /// User's preferred OS language as a packed 2-letter code (`c0 * 256 + c1`,
1104
+ /// ASCII of the lowercased ISO-639 primary subtag, e.g. "en" = 101*256+110).
1105
+ /// Script subtags are dropped (zh-Hans -> "zh"); callers map to their variant.
1106
+ export function getLanguage(): number { return bloom_get_language(); }
1107
+
1108
+ export function isMobile(): boolean {
1109
+ const p = bloom_get_platform();
1110
+ return p === 2 || p === 5;
1111
+ }
1112
+
1113
+ export function isTV(): boolean {
1114
+ return bloom_get_platform() === 6;
1115
+ }
1116
+
1117
+ export function isWatch(): boolean {
1118
+ return bloom_get_platform() === 8;
1119
+ }
1120
+
1121
+ /**
1122
+ * Digital Crown rotation accumulated since the last call, in radians.
1123
+ * Positive values = clockwise (scrolling away from the user).
1124
+ * Returns 0 on platforms without a crown. Reading consumes the accumulator.
1125
+ */
1126
+ export function getCrownRotation(): number {
1127
+ return bloom_get_crown_rotation();
1128
+ }
1129
+
1130
+ export function isAnyInputPressed(): boolean {
1131
+ return bloom_is_any_input_pressed() !== 0;
1132
+ }
1133
+
1134
+ /**
1135
+ * Cross-platform game loop entry point (Emscripten-style).
1136
+ *
1137
+ * On native: blocks in a while loop calling beginDrawing/update/endDrawing each frame.
1138
+ * On web: passes the callback to the JS runtime which drives it via requestAnimationFrame.
1139
+ *
1140
+ * Usage:
1141
+ * initWindow(800, 600, "My Game");
1142
+ * runGame((dt) => {
1143
+ * clearBackground({ r: 0, g: 0, b: 0, a: 255 });
1144
+ * // game logic + draw calls
1145
+ * });
1146
+ */
1147
+ export function runGame(update: (dt: number) => void): void {
1148
+ const platform = bloom_get_platform();
1149
+ if (platform === 7) {
1150
+ // Web: delegate to JS glue layer via FFI.
1151
+ // bloom_glue.js intercepts this call and sets up requestAnimationFrame.
1152
+ bloom_run_game(update as any);
1153
+ } else {
1154
+ // Native: blocking game loop
1155
+ while (!windowShouldClose()) {
1156
+ beginDrawing();
1157
+ update(getDeltaTime());
1158
+ endDrawing();
1159
+ }
1160
+ }
1161
+ }
1162
+
1163
+ // Pure TS camera helpers
1164
+
1165
+ export function getScreenToWorld2D(position: { x: number; y: number }, camera: Camera2D): { x: number; y: number } {
1166
+ const cos = Math.cos(camera.rotation * Math.PI / 180);
1167
+ const sin = Math.sin(camera.rotation * Math.PI / 180);
1168
+ const dx = (position.x - camera.offset.x) / camera.zoom;
1169
+ const dy = (position.y - camera.offset.y) / camera.zoom;
1170
+ return {
1171
+ x: cos * dx + sin * dy + camera.target.x,
1172
+ y: -sin * dx + cos * dy + camera.target.y,
1173
+ };
1174
+ }
1175
+
1176
+ export function getWorldToScreen2D(position: { x: number; y: number }, camera: Camera2D): { x: number; y: number } {
1177
+ const cos = Math.cos(camera.rotation * Math.PI / 180);
1178
+ const sin = Math.sin(camera.rotation * Math.PI / 180);
1179
+ const dx = position.x - camera.target.x;
1180
+ const dy = position.y - camera.target.y;
1181
+ return {
1182
+ x: (cos * dx - sin * dy) * camera.zoom + camera.offset.x,
1183
+ y: (sin * dx + cos * dy) * camera.zoom + camera.offset.y,
1184
+ };
1185
+ }
1186
+
1187
+
1188
+ /// Launch another program, fire and forget. Returns its pid, or 0 on failure.
1189
+ ///
1190
+ /// Perry's `child_process.spawn` COMPILES and then does nothing — undefined pid, no
1191
+ /// process. So this is the only way for a Bloom tool to run another program (the
1192
+ /// editor's play-in-editor: save the level, run the game on it).
1193
+ ///
1194
+ /// The child is fully detached: we never wait on it and its stdio goes nowhere. A
1195
+ /// GUI must not block on, or die with, the thing it launched.
1196
+ ///
1197
+ /// `args` is passed as a real argv, not a command line — there is no shell here,
1198
+ /// which is also why there is nothing to inject into.
1199
+ export function launchProcess(cmd: string, args: string[], cwd: string): number {
1200
+ let joined = '';
1201
+ for (let i = 0; i < args.length; i++) {
1202
+ if (i > 0) joined = joined + '\n';
1203
+ joined = joined + args[i];
1204
+ }
1205
+ return bloom_launch_process(cmd, joined, cwd);
1206
+ }