@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,577 @@
1
+ //! Game loop, timing, frame callbacks, profiler, window-state getters.
2
+ //!
3
+ //! Section of [`define_core_ffi!`](crate::define_core_ffi) — see
4
+ //! `ffi_core/mod.rs` for the architecture and the invoking-crate contract.
5
+ //! Internal: platform crates must invoke `define_core_ffi!()`, never the
6
+ //! section macros directly.
7
+
8
+ #[doc(hidden)]
9
+ #[macro_export]
10
+ macro_rules! __bloom_ffi_game_loop {
11
+ () => {
12
+
13
+ // --- game loop hooks --------------------------------------------
14
+
15
+ // No-op on native: the TypeScript runGame() helper drives the
16
+ // loop. (On web, the JS glue hooks requestAnimationFrame.)
17
+ #[no_mangle]
18
+ pub extern "C" fn bloom_run_game(_callback: extern "C" fn(f64)) {}
19
+
20
+ #[no_mangle]
21
+ pub extern "C" fn bloom_register_frame_callback(priority: f64, callback: extern "C" fn(f64)) -> f64 {
22
+ $crate::ffi::guard("bloom_register_frame_callback", move || {
23
+ engine().frame_callbacks.register(priority as i32, callback) as f64
24
+ })
25
+ }
26
+
27
+ // bloom_get_delta_time [source: macos]
28
+ #[no_mangle]
29
+ pub extern "C" fn bloom_get_delta_time() -> f64 {
30
+ $crate::ffi::guard("bloom_get_delta_time", move || {
31
+ engine().delta_time
32
+ })
33
+ }
34
+
35
+ // bloom_get_fps [source: macos]
36
+ #[no_mangle]
37
+ pub extern "C" fn bloom_get_fps() -> f64 {
38
+ $crate::ffi::guard("bloom_get_fps", move || {
39
+ engine().get_fps()
40
+ })
41
+ }
42
+
43
+ // bloom_get_screen_width [source: macos]
44
+ #[no_mangle]
45
+ pub extern "C" fn bloom_get_screen_width() -> f64 {
46
+ $crate::ffi::guard("bloom_get_screen_width", move || {
47
+ engine().screen_width()
48
+ })
49
+ }
50
+
51
+ // bloom_get_screen_height [source: macos]
52
+ #[no_mangle]
53
+ pub extern "C" fn bloom_get_screen_height() -> f64 {
54
+ $crate::ffi::guard("bloom_get_screen_height", move || {
55
+ engine().screen_height()
56
+ })
57
+ }
58
+
59
+ // bloom_create_instance_buffer [source: macos]
60
+ #[no_mangle]
61
+ pub extern "C" fn bloom_create_instance_buffer(
62
+ data_ptr: *const f64, instance_count: f64,
63
+ ) -> f64 {
64
+ $crate::ffi::guard("bloom_create_instance_buffer", move || {
65
+ if data_ptr.is_null() || instance_count <= 0.0 { return 0.0; }
66
+ let count = instance_count as u32;
67
+ let slot_count = (count as usize) * 9;
68
+ let raw_f64 = unsafe { std::slice::from_raw_parts(data_ptr, slot_count) };
69
+ let raw_f32: Vec<f32> = raw_f64.iter().map(|&v| v as f32).collect();
70
+ engine().renderer.create_instance_buffer(&raw_f32, count) as f64
71
+ })
72
+ }
73
+
74
+ // bloom_create_instance_buffer_scratch — instance data arrives via
75
+ // the mesh scratch (bloom_mesh_scratch_reset + push_f32 × 9·N)
76
+ // instead of the i64-array pointer above, which Perry 0.5.x rejects
77
+ // ("Expected safe integer for native i64 parameter"). Same idiom as
78
+ // bloom_create_mesh_scratch. 9 f32 slots per instance.
79
+ #[no_mangle]
80
+ pub extern "C" fn bloom_create_instance_buffer_scratch(instance_count: f64) -> f64 {
81
+ $crate::ffi::guard("bloom_create_instance_buffer_scratch", move || {
82
+ let mut eng = engine();
83
+ let count = instance_count as u32;
84
+ let need = (count as usize) * 9;
85
+ if count == 0 || eng.models.scratch_f32.len() < need { return 0.0; }
86
+ let data: Vec<f32> = eng.models.scratch_f32[..need].to_vec();
87
+ eng.models.mesh_scratch_reset();
88
+ eng.renderer.create_instance_buffer(&data, count) as f64
89
+ })
90
+ }
91
+
92
+ // bloom_destroy_instance_buffer [source: macos]
93
+ #[no_mangle]
94
+ pub extern "C" fn bloom_destroy_instance_buffer(handle: f64) {
95
+ $crate::ffi::guard("bloom_destroy_instance_buffer", move || {
96
+ engine().renderer.destroy_instance_buffer(handle as u32);
97
+ })
98
+ }
99
+
100
+ // bloom_create_planar_reflection [source: macos]
101
+ #[no_mangle]
102
+ pub extern "C" fn bloom_create_planar_reflection(
103
+ plane_y: f64, nx: f64, ny: f64, nz: f64, resolution: f64,
104
+ ) -> f64 {
105
+ $crate::ffi::guard("bloom_create_planar_reflection", move || {
106
+ engine().renderer.create_planar_reflection(
107
+ plane_y as f32,
108
+ [nx as f32, ny as f32, nz as f32],
109
+ resolution as u32,
110
+ ) as f64
111
+ })
112
+ }
113
+
114
+ // bloom_create_texture_array [source: macos]
115
+ #[no_mangle]
116
+ pub extern "C" fn bloom_create_texture_array(
117
+ data_ptr: *const u8,
118
+ data_len: f64,
119
+ width: f64,
120
+ height: f64,
121
+ layer_count: f64,
122
+ ) -> f64 {
123
+ $crate::ffi::guard("bloom_create_texture_array", move || {
124
+ // EN-014 V2 — V1 stays callable; forwards to _ex with default
125
+ // format = sRGB (0) and mip_levels = 1 (no mips).
126
+ bloom_create_texture_array_ex(data_ptr, data_len, width, height, layer_count, 0.0, 1.0)
127
+ })
128
+ }
129
+
130
+ // bloom_create_texture_array_ex [source: macos]
131
+ #[no_mangle]
132
+ pub extern "C" fn bloom_create_texture_array_ex(
133
+ data_ptr: *const u8,
134
+ data_len: f64,
135
+ width: f64,
136
+ height: f64,
137
+ layer_count: f64,
138
+ format: f64,
139
+ mip_levels: f64,
140
+ ) -> f64 {
141
+ $crate::ffi::guard("bloom_create_texture_array_ex", move || {
142
+ if data_ptr.is_null() || data_len <= 0.0 { return 0.0; }
143
+ let w = width as u32;
144
+ let h = height as u32;
145
+ if w == 0 || h == 0 { return 0.0; }
146
+ let layers_count = (layer_count as u32)
147
+ .min($crate::renderer::material_system::MAX_TEXTURE_ARRAY_LAYERS);
148
+ if layers_count == 0 { return 0.0; }
149
+ let layer_size = (w as usize) * (h as usize) * 4;
150
+ let total_bytes = (data_len as usize)
151
+ .min(layers_count as usize * layer_size);
152
+ let bytes = unsafe { std::slice::from_raw_parts(data_ptr, total_bytes) };
153
+ let mut layers: Vec<(&[u8], u32, u32)> = Vec::with_capacity(layers_count as usize);
154
+ for i in 0..(layers_count as usize) {
155
+ let start = i * layer_size;
156
+ let end = start + layer_size;
157
+ if end > bytes.len() { break; }
158
+ layers.push((&bytes[start..end], w, h));
159
+ }
160
+ engine().renderer.create_texture_array_ex(&layers, format as u32, mip_levels as u32) as f64
161
+ })
162
+ }
163
+
164
+ // bloom_create_texture_array_scratch [EN-049]
165
+ //
166
+ // The byte-array path above takes a `*const u8`, which the manifest has
167
+ // to declare as `i64` — and Perry cannot put a `number[]` in an i64
168
+ // param (it raises "Expected safe integer for native i64 parameter").
169
+ // So from Perry, `bloom_create_texture_array_ex` is only callable with a
170
+ // pointer nobody has. Every real caller ended up on the from_files path,
171
+ // which is fine for ART and useless for DATA: a splat map is computed at
172
+ // load from the world file, and there is no file to name.
173
+ //
174
+ // Same fix the mesh path already uses (`bloom_create_mesh_scratch`):
175
+ // push the payload through the scratch buffer, then call this with the
176
+ // dimensions. Texels are pushed as PACKED u32 (one per texel, RGBA in
177
+ // little-endian byte order = R | G<<8 | B<<16 | A<<24), so a 128² splat
178
+ // is 16,384 pushes rather than 65,536.
179
+ #[no_mangle]
180
+ pub extern "C" fn bloom_create_texture_array_scratch(
181
+ width: f64,
182
+ height: f64,
183
+ layer_count: f64,
184
+ format: f64,
185
+ mip_levels: f64,
186
+ ) -> f64 {
187
+ $crate::ffi::guard("bloom_create_texture_array_scratch", move || {
188
+ let w = width as u32;
189
+ let h = height as u32;
190
+ if w == 0 || h == 0 { return 0.0; }
191
+ let layers_count = (layer_count as u32)
192
+ .min($crate::renderer::material_system::MAX_TEXTURE_ARRAY_LAYERS);
193
+ if layers_count == 0 { return 0.0; }
194
+
195
+ let texels = (w as usize) * (h as usize) * (layers_count as usize);
196
+ // Scoped so the scratch borrow ends before the renderer borrow.
197
+ let bytes: Vec<u8> = {
198
+ let eng = engine();
199
+ if eng.models.scratch_u32.len() < texels {
200
+ // Short buffer: refuse rather than upload uninitialised
201
+ // memory as a texture. Silent garbage here surfaces as a
202
+ // terrain painted in noise, three layers from the cause.
203
+ return 0.0;
204
+ }
205
+ eng.models.scratch_u32[..texels]
206
+ .iter()
207
+ .flat_map(|p| p.to_le_bytes())
208
+ .collect()
209
+ };
210
+ let layer_size = (w as usize) * (h as usize) * 4;
211
+ let mut layers: Vec<(&[u8], u32, u32)> = Vec::with_capacity(layers_count as usize);
212
+ for i in 0..(layers_count as usize) {
213
+ let start = i * layer_size;
214
+ let end = start + layer_size;
215
+ if end > bytes.len() { break; }
216
+ layers.push((&bytes[start..end], w, h));
217
+ }
218
+ engine().renderer.create_texture_array_ex(&layers, format as u32, mip_levels as u32) as f64
219
+ })
220
+ }
221
+
222
+ // bloom_create_texture_array_from_files [EN-014 V3]
223
+ //
224
+ // The byte-array path above asks the game to marshal every texel across
225
+ // the FFI — a 6-layer 256² array is 1.5 M numbers, which is exactly the
226
+ // shape of call Perry's array bridge is worst at. Decoding on this side
227
+ // from a path list is both faster and the API every actual consumer
228
+ // wants (VFX atlas layers, splat-terrain layers).
229
+ //
230
+ // `paths` is comma-separated, and is parsed HERE, at load time — never
231
+ // on a per-frame path (perry-quirks #5). Layers must share dimensions;
232
+ // the first file's size wins and any mismatch is skipped with a warning.
233
+ #[no_mangle]
234
+ pub extern "C" fn bloom_create_texture_array_from_files(
235
+ paths_ptr: *const u8, format: f64, mip_levels: f64,
236
+ ) -> f64 {
237
+ $crate::ffi::guard("bloom_create_texture_array_from_files", move || {
238
+ let list = $crate::string_header::str_from_header(paths_ptr);
239
+ let mut decoded: Vec<(Vec<u8>, u32, u32)> = Vec::new();
240
+ for p in list.split(',') {
241
+ let p = p.trim();
242
+ if p.is_empty() { continue; }
243
+ let resolved = bloom_resolve_asset_path(p);
244
+ match image::open(resolved.as_ref()) {
245
+ Ok(img) => {
246
+ let rgba = img.to_rgba8();
247
+ let (w, h) = rgba.dimensions();
248
+ decoded.push((rgba.into_raw(), w, h));
249
+ }
250
+ Err(e) => {
251
+ eprintln!("[texarray] failed to load {}: {}", p, e);
252
+ }
253
+ }
254
+ }
255
+ if decoded.is_empty() { return 0.0; }
256
+ let (w, h) = (decoded[0].1, decoded[0].2);
257
+ let mut layers: Vec<(&[u8], u32, u32)> = Vec::with_capacity(decoded.len());
258
+ for (bytes, lw, lh) in decoded.iter() {
259
+ if *lw != w || *lh != h {
260
+ eprintln!("[texarray] layer size {}x{} != {}x{}; skipped", lw, lh, w, h);
261
+ continue;
262
+ }
263
+ layers.push((bytes.as_slice(), w, h));
264
+ }
265
+ if layers.is_empty() { return 0.0; }
266
+ engine().renderer.create_texture_array_ex(
267
+ &layers, format as u32, mip_levels as u32) as f64
268
+ })
269
+ }
270
+
271
+ // bloom_clear_post_pass [source: macos]
272
+ #[no_mangle]
273
+ pub extern "C" fn bloom_clear_post_pass() {
274
+ $crate::ffi::guard("bloom_clear_post_pass", move || {
275
+ engine().renderer.clear_post_pass();
276
+ })
277
+ }
278
+
279
+ // bloom_add_post_pass [source: macos]
280
+ #[no_mangle]
281
+ pub extern "C" fn bloom_add_post_pass(source_ptr: *const u8) -> f64 {
282
+ $crate::ffi::guard("bloom_add_post_pass", move || {
283
+ let source = $crate::string_header::str_from_header(source_ptr);
284
+ match engine().renderer.add_post_pass(source) {
285
+ Ok(h) => h as f64,
286
+ Err(e) => { eprintln!("[post_pass] compile failed: {:?}", e); 0.0 }
287
+ }
288
+ })
289
+ }
290
+
291
+ // bloom_clear_all_post_passes [source: macos]
292
+ #[no_mangle]
293
+ pub extern "C" fn bloom_clear_all_post_passes() {
294
+ $crate::ffi::guard("bloom_clear_all_post_passes", move || {
295
+ engine().renderer.clear_all_post_passes();
296
+ })
297
+ }
298
+
299
+ // bloom_get_render_scale [source: macos]
300
+ #[no_mangle]
301
+ pub extern "C" fn bloom_get_render_scale() -> f64 {
302
+ $crate::ffi::guard("bloom_get_render_scale", move || {
303
+ engine().renderer.render_scale() as f64
304
+ })
305
+ }
306
+
307
+ // bloom_get_physical_width [source: macos]
308
+ #[no_mangle]
309
+ pub extern "C" fn bloom_get_physical_width() -> f64 {
310
+ $crate::ffi::guard("bloom_get_physical_width", move || {
311
+ engine().renderer.physical_width() as f64
312
+ })
313
+ }
314
+
315
+ // bloom_get_physical_height [source: macos]
316
+ #[no_mangle]
317
+ pub extern "C" fn bloom_get_physical_height() -> f64 {
318
+ $crate::ffi::guard("bloom_get_physical_height", move || {
319
+ engine().renderer.physical_height() as f64
320
+ })
321
+ }
322
+
323
+ // bloom_get_profiler_frame_cpu_us [source: macos]
324
+ #[no_mangle]
325
+ pub extern "C" fn bloom_get_profiler_frame_cpu_us() -> f64 {
326
+ $crate::ffi::guard("bloom_get_profiler_frame_cpu_us", move || {
327
+ engine().profiler.avg_frame_cpu_us()
328
+ })
329
+ }
330
+
331
+ // bloom_get_profiler_frame_gpu_us [source: macos]
332
+ #[no_mangle]
333
+ pub extern "C" fn bloom_get_profiler_frame_gpu_us() -> f64 {
334
+ $crate::ffi::guard("bloom_get_profiler_frame_gpu_us", move || {
335
+ engine().profiler.avg_frame_gpu_us()
336
+ })
337
+ }
338
+
339
+ // bloom_print_profiler_summary [source: macos]
340
+ #[no_mangle]
341
+ pub extern "C" fn bloom_print_profiler_summary() {
342
+ $crate::ffi::guard("bloom_print_profiler_summary", move || {
343
+ print!("{}", engine().profiler.summary());
344
+ })
345
+ }
346
+
347
+ // bloom_inject_key_down [source: macos]
348
+ #[no_mangle]
349
+ pub extern "C" fn bloom_inject_key_down(key: f64) {
350
+ $crate::ffi::guard("bloom_inject_key_down", move || {
351
+ engine().input.inject_key_down(key as usize);
352
+ })
353
+ }
354
+
355
+ // bloom_inject_key_up [source: macos]
356
+ #[no_mangle]
357
+ pub extern "C" fn bloom_inject_key_up(key: f64) {
358
+ $crate::ffi::guard("bloom_inject_key_up", move || {
359
+ engine().input.inject_key_up(key as usize);
360
+ })
361
+ }
362
+
363
+ // bloom_inject_gamepad_axis [source: macos]
364
+ #[no_mangle]
365
+ pub extern "C" fn bloom_inject_gamepad_axis(axis: f64, value: f64) {
366
+ $crate::ffi::guard("bloom_inject_gamepad_axis", move || {
367
+ engine().input.set_gamepad_axis(axis as usize, value as f32);
368
+ })
369
+ }
370
+
371
+ // bloom_inject_gamepad_button_down [source: macos]
372
+ #[no_mangle]
373
+ pub extern "C" fn bloom_inject_gamepad_button_down(button: f64) {
374
+ $crate::ffi::guard("bloom_inject_gamepad_button_down", move || {
375
+ engine().input.set_gamepad_button_down(button as usize);
376
+ })
377
+ }
378
+
379
+ // bloom_inject_gamepad_button_up [source: macos]
380
+ #[no_mangle]
381
+ pub extern "C" fn bloom_inject_gamepad_button_up(button: f64) {
382
+ $crate::ffi::guard("bloom_inject_gamepad_button_up", move || {
383
+ engine().input.set_gamepad_button_up(button as usize);
384
+ })
385
+ }
386
+
387
+ // bloom_is_any_input_pressed [source: macos]
388
+ #[no_mangle]
389
+ pub extern "C" fn bloom_is_any_input_pressed() -> f64 {
390
+ $crate::ffi::guard("bloom_is_any_input_pressed", move || {
391
+ if engine().input.is_any_input_pressed() { 1.0 } else { 0.0 }
392
+ })
393
+ }
394
+
395
+ // bloom_update_music_stream [source: macos]
396
+ #[no_mangle]
397
+ pub extern "C" fn bloom_update_music_stream(handle: f64) {
398
+ $crate::ffi::guard("bloom_update_music_stream", move || {
399
+ engine().audio.update_music_stream(handle);
400
+ })
401
+ }
402
+
403
+ // bloom_get_time [source: macos]
404
+ #[no_mangle]
405
+ pub extern "C" fn bloom_get_time() -> f64 {
406
+ $crate::ffi::guard("bloom_get_time", move || {
407
+ engine().get_time()
408
+ })
409
+ }
410
+
411
+ // bloom_unregister_frame_callback [source: macos]
412
+ #[no_mangle]
413
+ pub extern "C" fn bloom_unregister_frame_callback(id: f64) {
414
+ $crate::ffi::guard("bloom_unregister_frame_callback", move || {
415
+ engine().frame_callbacks.unregister(id as u64);
416
+ })
417
+ }
418
+
419
+ // bloom_get_render_texture_texture [source: macos]
420
+ #[no_mangle]
421
+ pub extern "C" fn bloom_get_render_texture_texture(handle: f64) -> f64 {
422
+ $crate::ffi::guard("bloom_get_render_texture_texture", move || {
423
+ engine().textures.get_render_texture_texture(handle)
424
+ })
425
+ }
426
+
427
+ // bloom_postfx_set_selected [source: macos]
428
+ #[no_mangle]
429
+ pub extern "C" fn bloom_postfx_set_selected(handle: f64) {
430
+ $crate::ffi::guard("bloom_postfx_set_selected", move || {
431
+ if let Some(pfx) = &mut engine().postfx {
432
+ if handle == 0.0 {
433
+ pfx.set_selected(Vec::new());
434
+ } else {
435
+ pfx.set_selected(vec![handle]);
436
+ }
437
+ }
438
+ })
439
+ }
440
+
441
+ // bloom_postfx_set_hovered [source: macos]
442
+ #[no_mangle]
443
+ pub extern "C" fn bloom_postfx_set_hovered(handle: f64) {
444
+ $crate::ffi::guard("bloom_postfx_set_hovered", move || {
445
+ if let Some(pfx) = &mut engine().postfx {
446
+ pfx.set_hovered(handle);
447
+ }
448
+ })
449
+ }
450
+
451
+ // bloom_postfx_set_outline_color [source: macos]
452
+ #[no_mangle]
453
+ pub extern "C" fn bloom_postfx_set_outline_color(r: f64, g: f64, b: f64, a: f64) {
454
+ $crate::ffi::guard("bloom_postfx_set_outline_color", move || {
455
+ if let Some(pfx) = &mut engine().postfx {
456
+ pfx.outline_params.color_selected = [r as f32, g as f32, b as f32, a as f32];
457
+ }
458
+ })
459
+ }
460
+
461
+ // bloom_postfx_set_outline_thickness [source: macos]
462
+ #[no_mangle]
463
+ pub extern "C" fn bloom_postfx_set_outline_thickness(thickness: f64) {
464
+ $crate::ffi::guard("bloom_postfx_set_outline_thickness", move || {
465
+ if let Some(pfx) = &mut engine().postfx {
466
+ pfx.outline_params.thickness[0] = thickness as f32;
467
+ }
468
+ })
469
+ }
470
+
471
+ // bloom_profiler_frame_history [source: macos]
472
+ #[no_mangle]
473
+ pub extern "C" fn bloom_profiler_frame_history() -> *const u8 {
474
+ $crate::ffi::guard("bloom_profiler_frame_history", move || {
475
+ let hist = engine().profiler.frame_history();
476
+ let mut s = String::with_capacity(hist.len() * 24);
477
+ for (cpu, gpu) in &hist {
478
+ s.push_str(&format!("{:.2}|{:.2}\n", cpu, gpu));
479
+ }
480
+ $crate::string_header::alloc_perry_string(&s)
481
+ })
482
+ }
483
+
484
+ // bloom_profiler_overlay_text [source: macos]
485
+ #[no_mangle]
486
+ pub extern "C" fn bloom_profiler_overlay_text() -> *const u8 {
487
+ $crate::ffi::guard("bloom_profiler_overlay_text", move || {
488
+ let snap = engine().profiler.snapshot();
489
+ let mut s = String::with_capacity(snap.len() * 48);
490
+ for (label, cpu, gpu) in &snap {
491
+ s.push_str(label);
492
+ s.push('|');
493
+ s.push_str(&format!("{:.2}", cpu));
494
+ s.push('|');
495
+ match gpu {
496
+ Some(g) => s.push_str(&format!("{:.2}", g)),
497
+ None => s.push_str("-1"),
498
+ }
499
+ s.push('\n');
500
+ }
501
+ $crate::string_header::alloc_perry_string(&s)
502
+ })
503
+ }
504
+
505
+ // EN-020 — numeric profiler ABI. The packed-text FFIs above stay
506
+ // for back-compat, but Perry 0.5.x's split()/parseFloat() overread
507
+ // their own slice allocations, so per-frame consumers must use
508
+ // these instead: f64s cross the FFI clean and the label is only
509
+ // ever drawn, never parsed. Row/hist indices address the same
510
+ // snapshot the text FFIs serialize; rows are few (≤ MAX_GPU_PAIRS)
511
+ // so the per-call snapshot() is negligible overlay-only cost.
512
+
513
+ // bloom_profiler_row_count [source: windows]
514
+ #[no_mangle]
515
+ pub extern "C" fn bloom_profiler_row_count() -> f64 {
516
+ $crate::ffi::guard("bloom_profiler_row_count", move || {
517
+ engine().profiler.snapshot().len() as f64
518
+ })
519
+ }
520
+
521
+ // bloom_profiler_row_label [source: windows]
522
+ #[no_mangle]
523
+ pub extern "C" fn bloom_profiler_row_label(i: f64) -> *const u8 {
524
+ $crate::ffi::guard("bloom_profiler_row_label", move || {
525
+ let snap = engine().profiler.snapshot();
526
+ match snap.get(i as usize) {
527
+ Some((label, _, _)) => $crate::string_header::alloc_perry_string(label),
528
+ None => $crate::string_header::alloc_perry_string(""),
529
+ }
530
+ })
531
+ }
532
+
533
+ // bloom_profiler_row_cpu_us [source: windows]
534
+ #[no_mangle]
535
+ pub extern "C" fn bloom_profiler_row_cpu_us(i: f64) -> f64 {
536
+ $crate::ffi::guard("bloom_profiler_row_cpu_us", move || {
537
+ engine().profiler.snapshot().get(i as usize).map(|r| r.1).unwrap_or(0.0)
538
+ })
539
+ }
540
+
541
+ // bloom_profiler_row_gpu_us [source: windows]
542
+ #[no_mangle]
543
+ pub extern "C" fn bloom_profiler_row_gpu_us(i: f64) -> f64 {
544
+ $crate::ffi::guard("bloom_profiler_row_gpu_us", move || {
545
+ match engine().profiler.snapshot().get(i as usize) {
546
+ Some((_, _, Some(g))) => *g,
547
+ _ => -1.0,
548
+ }
549
+ })
550
+ }
551
+
552
+ // bloom_profiler_hist_count [source: windows]
553
+ #[no_mangle]
554
+ pub extern "C" fn bloom_profiler_hist_count() -> f64 {
555
+ $crate::ffi::guard("bloom_profiler_hist_count", move || {
556
+ engine().profiler.frame_history().len() as f64
557
+ })
558
+ }
559
+
560
+ // bloom_profiler_hist_cpu_us [source: windows]
561
+ #[no_mangle]
562
+ pub extern "C" fn bloom_profiler_hist_cpu_us(i: f64) -> f64 {
563
+ $crate::ffi::guard("bloom_profiler_hist_cpu_us", move || {
564
+ engine().profiler.frame_history().get(i as usize).map(|h| h.0).unwrap_or(0.0)
565
+ })
566
+ }
567
+
568
+ // bloom_profiler_hist_gpu_us [source: windows]
569
+ #[no_mangle]
570
+ pub extern "C" fn bloom_profiler_hist_gpu_us(i: f64) -> f64 {
571
+ $crate::ffi::guard("bloom_profiler_hist_gpu_us", move || {
572
+ engine().profiler.frame_history().get(i as usize).map(|h| h.1).unwrap_or(0.0)
573
+ })
574
+ }
575
+
576
+ };
577
+ }