@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,263 @@
1
+ //! Shared host-surface attach path (PerryTS/perry#5519).
2
+ //!
3
+ //! Factors the wgpu bring-up — instance → surface → adapter → device →
4
+ //! swapchain config → [`Renderer`] → [`EngineState`] — that every
5
+ //! platform's `bloom_init_window` duplicates near-verbatim into one
6
+ //! helper, so a host application that already owns a native render
7
+ //! surface (e.g. Perry UI's `BloomView`: an `NSView`/`UIView`/
8
+ //! `GtkWidget`/`ANativeWindow`/`HWND`) can hand it to the engine instead
9
+ //! of letting the engine create its own window.
10
+ //!
11
+ //! Each platform crate exposes a thin `bloom_attach_native(handle, w, h)`
12
+ //! FFI that turns the host pointer into the platform's
13
+ //! [`wgpu::SurfaceTargetUnsafe`] and calls [`attach_engine`]. The only
14
+ //! per-platform deltas — backend bitmask, the raw-handle variant, and the
15
+ //! swapchain format policy — are parameters here; the ~120 lines of
16
+ //! adapter / feature / limit / device negotiation live in one place.
17
+
18
+ use crate::engine::EngineState;
19
+ use crate::renderer::Renderer;
20
+
21
+ /// Minimal blocking executor for wgpu's async adapter/device requests.
22
+ /// The platform crates each carry a private copy of this (`bloom_init_
23
+ /// window` predates this shared module); kept here so the attach path has
24
+ /// no extra dependency on `pollster`.
25
+ fn block_on<F: std::future::Future>(future: F) -> F::Output {
26
+ use std::pin::Pin;
27
+ use std::sync::Arc;
28
+ use std::task::{Context, Poll, Wake, Waker};
29
+
30
+ struct NoopWaker;
31
+ impl Wake for NoopWaker {
32
+ fn wake(self: Arc<Self>) {}
33
+ }
34
+
35
+ let waker = Waker::from(Arc::new(NoopWaker));
36
+ let mut cx = Context::from_waker(&waker);
37
+ let mut future = unsafe { Pin::new_unchecked(Box::new(future)) };
38
+ loop {
39
+ match future.as_mut().poll(&mut cx) {
40
+ Poll::Ready(result) => return result,
41
+ Poll::Pending => std::thread::yield_now(),
42
+ }
43
+ }
44
+ }
45
+
46
+ /// How [`attach_engine`] picks the swapchain texture format.
47
+ #[derive(Clone, Copy, PartialEq, Eq)]
48
+ pub enum FormatPreference {
49
+ /// Prefer an sRGB-capable format (Apple Metal / desktop default —
50
+ /// the renderer writes linear color and relies on the swapchain for
51
+ /// the sRGB encode).
52
+ Srgb,
53
+ /// Prefer a *non*-sRGB format, falling back to the first reported
54
+ /// (tvOS / visionOS: those backends double-encode if handed an sRGB
55
+ /// swapchain, so the renderer does the encode itself).
56
+ NonSrgb,
57
+ /// Take the adapter's first reported format unchanged. GL / some
58
+ /// mobile surfaces don't expose an sRGB variant and fail to
59
+ /// configure if one is forced (Linux / Windows).
60
+ First,
61
+ }
62
+
63
+ /// Inputs to [`attach_engine`]. Sizes are split into *logical* (the
64
+ /// points / DIPs the engine reasons in) and *physical* (the backing
65
+ /// pixels the swapchain allocates) so HiDPI hosts pass both; non-HiDPI
66
+ /// hosts pass equal values.
67
+ pub struct AttachParams {
68
+ /// Backends to instantiate (e.g. `wgpu::Backends::METAL`, or
69
+ /// `VULKAN | GL` on Linux/Android).
70
+ pub backends: wgpu::Backends,
71
+ pub logical_w: u32,
72
+ pub logical_h: u32,
73
+ pub physical_w: u32,
74
+ pub physical_h: u32,
75
+ pub format: FormatPreference,
76
+ }
77
+
78
+ /// Build a fully-configured [`EngineState`] that renders into a
79
+ /// host-owned surface. This is the GPU half of `bloom_init_window` with
80
+ /// the windowing half removed: the caller supplies the surface target,
81
+ /// we own the instance / adapter / device / swapchain and the engine.
82
+ ///
83
+ /// Returns `Err` with a human-readable reason instead of panicking, so a
84
+ /// host that attaches to a not-yet-realized view can surface the failure
85
+ /// rather than abort the process.
86
+ ///
87
+ /// # Safety
88
+ /// `target` must reference a live native view / window / layer / surface
89
+ /// that outlives the returned [`EngineState`]; the host owns it and must
90
+ /// not free it while the engine renders.
91
+ pub unsafe fn attach_engine(
92
+ target: wgpu::SurfaceTargetUnsafe,
93
+ params: AttachParams,
94
+ ) -> Result<EngineState, String> {
95
+ let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
96
+ backends: params.backends,
97
+ ..wgpu::InstanceDescriptor::new_without_display_handle()
98
+ });
99
+
100
+ let surface = instance
101
+ .create_surface_unsafe(target)
102
+ .map_err(|e| format!("create_surface failed: {e}"))?;
103
+
104
+ let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
105
+ compatible_surface: Some(&surface),
106
+ power_preference: wgpu::PowerPreference::HighPerformance,
107
+ ..Default::default()
108
+ }))
109
+ .map_err(|e| format!("no compatible adapter: {e}"))?;
110
+
111
+ // Optional device features, requested only when the adapter offers
112
+ // them (mirrors bloom_init_window): GPU-timestamp profiling, BC
113
+ // texture compression, and HW ray query for the GI probe path.
114
+ let supported = adapter.features();
115
+ let mut required_features = wgpu::Features::empty();
116
+ if supported.contains(wgpu::Features::TIMESTAMP_QUERY) {
117
+ required_features |= wgpu::Features::TIMESTAMP_QUERY;
118
+ }
119
+ if supported.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) {
120
+ required_features |= wgpu::Features::TEXTURE_COMPRESSION_BC;
121
+ }
122
+ let force_sw_gi = std::env::var("BLOOM_FORCE_SW_GI")
123
+ .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
124
+ .unwrap_or(false);
125
+ let rt_mask = wgpu::Features::EXPERIMENTAL_RAY_QUERY;
126
+ if !force_sw_gi && supported.contains(rt_mask) {
127
+ required_features |= rt_mask;
128
+ }
129
+ // PT-2: texture binding array + non-uniform indexing for textured
130
+ // path-trace hit shading (mirrors bloom_init_window).
131
+ let pt_tex_mask = wgpu::Features::TEXTURE_BINDING_ARRAY
132
+ | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING;
133
+ if supported.contains(pt_tex_mask) {
134
+ required_features |= pt_tex_mask;
135
+ }
136
+ let experimental_features = if required_features.intersects(rt_mask) {
137
+ // wgpu 29 requires this explicit opt-in token for EXPERIMENTAL_*
138
+ // features. Apple-Silicon Metal ray query has been stable since
139
+ // wgpu v25, so the documented UB risk is acceptable here.
140
+ unsafe { wgpu::ExperimentalFeatures::enabled() }
141
+ } else {
142
+ wgpu::ExperimentalFeatures::disabled()
143
+ };
144
+
145
+ // The material ABI declares 5 bind groups; wgpu defaults to 4. Every
146
+ // real backend supports >= 7.
147
+ let adapter_limits = adapter.limits();
148
+ let mut required_limits = wgpu::Limits::default();
149
+ required_limits.max_bind_groups = 5;
150
+
151
+ // A user material's fragment stage binds more sampled textures than
152
+ // wgpu's default permits — 19 against a default cap of 16, once the
153
+ // scene/GI inputs sit alongside the material's own maps.
154
+ //
155
+ // Only the fallback below ever picked up the adapter's real limits, and
156
+ // it runs solely when request_device *fails*. On macOS the default
157
+ // request succeeds at 16, so the shortfall surfaced much later, as an
158
+ // abort inside create_pipeline_layout('user_material') — the shooter
159
+ // could not open on macOS at all. (iOS escaped it by luck: its first
160
+ // request fails on an unrelated limit, so it always retried with the
161
+ // adapter's limits and got the headroom as a side effect.)
162
+ //
163
+ // Ask for what the adapter actually offers on the limits the material
164
+ // system leans on, never dropping below wgpu's defaults.
165
+ required_limits.max_sampled_textures_per_shader_stage = required_limits
166
+ .max_sampled_textures_per_shader_stage
167
+ .max(adapter_limits.max_sampled_textures_per_shader_stage);
168
+ required_limits.max_samplers_per_shader_stage = required_limits
169
+ .max_samplers_per_shader_stage
170
+ .max(adapter_limits.max_samplers_per_shader_stage);
171
+ // PT-2: binding arrays have their own element budget, default 0.
172
+ if required_features.contains(pt_tex_mask) {
173
+ required_limits.max_binding_array_elements_per_shader_stage =
174
+ adapter_limits.max_binding_array_elements_per_shader_stage;
175
+ }
176
+ // PT-4: the path-trace kernel binds 9 storage buffers (accum +
177
+ // moments + reservoir ping-pongs on top of instance/geo data);
178
+ // the wgpu default limit is 8.
179
+ required_limits.max_storage_buffers_per_shader_stage = required_limits
180
+ .max_storage_buffers_per_shader_stage
181
+ .max(adapter_limits.max_storage_buffers_per_shader_stage.min(16));
182
+
183
+ if required_features.intersects(rt_mask) {
184
+ required_limits =
185
+ required_limits.using_minimum_supported_acceleration_structure_values();
186
+ }
187
+
188
+ let device_desc = wgpu::DeviceDescriptor {
189
+ label: Some("bloom_device"),
190
+ required_features,
191
+ required_limits: required_limits.clone(),
192
+ experimental_features,
193
+ ..Default::default()
194
+ };
195
+
196
+ // Some constrained mobile GPUs (e.g. A18) report a feature/limit set
197
+ // they then refuse at device-create time. Retry once with the
198
+ // adapter's own reported limits + no optional features before giving
199
+ // up — matches the iOS init path's fallback.
200
+ let (device, queue) = match block_on(adapter.request_device(&device_desc)) {
201
+ Ok(pair) => pair,
202
+ Err(first) => {
203
+ let fallback = wgpu::DeviceDescriptor {
204
+ label: Some("bloom_device_fallback"),
205
+ required_features: wgpu::Features::empty(),
206
+ required_limits: {
207
+ let mut l = adapter.limits();
208
+ l.max_bind_groups = l.max_bind_groups.max(5);
209
+ l
210
+ },
211
+ experimental_features: wgpu::ExperimentalFeatures::disabled(),
212
+ ..Default::default()
213
+ };
214
+ block_on(adapter.request_device(&fallback)).map_err(|second| {
215
+ format!("request_device failed: {first}; fallback: {second}")
216
+ })?
217
+ }
218
+ };
219
+
220
+ let surface_caps = surface.get_capabilities(&adapter);
221
+ if surface_caps.formats.is_empty() {
222
+ return Err("surface reports no supported formats".to_string());
223
+ }
224
+ let format = match params.format {
225
+ FormatPreference::Srgb => surface_caps
226
+ .formats
227
+ .iter()
228
+ .find(|f| f.is_srgb())
229
+ .copied()
230
+ .unwrap_or(surface_caps.formats[0]),
231
+ FormatPreference::NonSrgb => surface_caps
232
+ .formats
233
+ .iter()
234
+ .find(|f| !f.is_srgb())
235
+ .copied()
236
+ .unwrap_or(surface_caps.formats[0]),
237
+ FormatPreference::First => surface_caps.formats[0],
238
+ };
239
+
240
+ let physical_w = params.physical_w.max(1);
241
+ let physical_h = params.physical_h.max(1);
242
+ let surface_config = wgpu::SurfaceConfiguration {
243
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
244
+ format,
245
+ width: physical_w,
246
+ height: physical_h,
247
+ present_mode: wgpu::PresentMode::Fifo,
248
+ alpha_mode: surface_caps.alpha_modes[0],
249
+ view_formats: vec![],
250
+ desired_maximum_frame_latency: 2,
251
+ };
252
+ surface.configure(&device, &surface_config);
253
+
254
+ let renderer = Renderer::new(
255
+ device,
256
+ queue,
257
+ surface,
258
+ surface_config,
259
+ params.logical_w.max(1),
260
+ params.logical_h.max(1),
261
+ );
262
+ Ok(EngineState::new(renderer))
263
+ }
@@ -0,0 +1,123 @@
1
+ //! Audio file decoding: WAV, OGG Vorbis, and (behind the `mp3` feature)
2
+ //! MP3, plus the unified extension-dispatch + sniffing entry point
3
+ //! [`decode_audio`]. Pure functions over byte slices — no engine state.
4
+
5
+ use super::SoundData;
6
+
7
+ /// Parse a WAV file into SoundData.
8
+ pub fn parse_wav(data: &[u8]) -> Option<SoundData> {
9
+ if data.len() < 44 { return None; }
10
+ if &data[0..4] != b"RIFF" || &data[8..12] != b"WAVE" { return None; }
11
+
12
+ let channels = u16::from_le_bytes([data[22], data[23]]);
13
+ let sample_rate = u32::from_le_bytes([data[24], data[25], data[26], data[27]]);
14
+ let bits_per_sample = u16::from_le_bytes([data[34], data[35]]);
15
+
16
+ let mut offset = 36;
17
+ while offset + 8 < data.len() {
18
+ let chunk_id = &data[offset..offset + 4];
19
+ let chunk_size = u32::from_le_bytes([
20
+ data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7],
21
+ ]) as usize;
22
+
23
+ if chunk_id == b"data" {
24
+ let pcm_data = &data[offset + 8..std::cmp::min(offset + 8 + chunk_size, data.len())];
25
+ let samples = match bits_per_sample {
26
+ 16 => pcm_data.chunks_exact(2)
27
+ .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]]) as f32 / 32768.0)
28
+ .collect(),
29
+ 8 => pcm_data.iter()
30
+ .map(|&b| (b as f32 - 128.0) / 128.0)
31
+ .collect(),
32
+ _ => return None,
33
+ };
34
+ return Some(SoundData { samples, sample_rate, channels });
35
+ }
36
+ offset += 8 + chunk_size;
37
+ }
38
+ None
39
+ }
40
+
41
+ /// Parse an MP3 file into SoundData.
42
+ #[cfg(feature = "mp3")]
43
+ pub fn parse_mp3(data: &[u8]) -> Option<SoundData> {
44
+ let mut decoder = minimp3::Decoder::new(std::io::Cursor::new(data));
45
+ let mut samples = Vec::new();
46
+ let mut sample_rate = 0u32;
47
+ let mut channels = 0u16;
48
+
49
+ loop {
50
+ match decoder.next_frame() {
51
+ Ok(frame) => {
52
+ if sample_rate == 0 {
53
+ sample_rate = frame.sample_rate as u32;
54
+ channels = frame.channels as u16;
55
+ }
56
+ for &s in &frame.data {
57
+ samples.push(s as f32 / 32768.0);
58
+ }
59
+ }
60
+ Err(minimp3::Error::Eof) => break,
61
+ Err(_) => return None,
62
+ }
63
+ }
64
+
65
+ if samples.is_empty() { return None; }
66
+ Some(SoundData { samples, sample_rate, channels })
67
+ }
68
+
69
+ /// Parse an OGG Vorbis file into SoundData.
70
+ pub fn parse_ogg(data: &[u8]) -> Option<SoundData> {
71
+ let cursor = std::io::Cursor::new(data);
72
+ let mut reader = lewton::inside_ogg::OggStreamReader::new(cursor).ok()?;
73
+ let sample_rate = reader.ident_hdr.audio_sample_rate;
74
+ let channels = reader.ident_hdr.audio_channels as u16;
75
+ let mut samples = Vec::new();
76
+
77
+ while let Ok(Some(packet)) = reader.read_dec_packet_itl() {
78
+ for &sample in &packet {
79
+ samples.push(sample as f32 / 32768.0);
80
+ }
81
+ }
82
+
83
+ if samples.is_empty() { return None; }
84
+ Some(SoundData { samples, sample_rate, channels })
85
+ }
86
+
87
+ /// Decode an audio file by extension, falling back to format sniffing.
88
+ ///
89
+ /// Unifies the two historical platform behaviors: extension dispatch
90
+ /// (macOS/iOS/tvOS) and try-every-parser (Linux/Windows/Android). The
91
+ /// extension picks the first parser; if it fails — or the extension is
92
+ /// unrecognized — the remaining parsers run in turn, so a mislabelled
93
+ /// file still decodes.
94
+ pub fn decode_audio(path: &str, data: &[u8]) -> Option<SoundData> {
95
+ let lower = path.to_ascii_lowercase();
96
+ if lower.ends_with(".ogg") {
97
+ if let Some(s) = parse_ogg(data) {
98
+ return Some(s);
99
+ }
100
+ } else if lower.ends_with(".mp3") {
101
+ #[cfg(feature = "mp3")]
102
+ if let Some(s) = parse_mp3(data) {
103
+ return Some(s);
104
+ }
105
+ } else if lower.ends_with(".wav") {
106
+ if let Some(s) = parse_wav(data) {
107
+ return Some(s);
108
+ }
109
+ }
110
+ // Sniff: wav first (cheap header check), then ogg, then mp3 (no
111
+ // reliable magic — minimp3 will chew on almost anything, keep it last).
112
+ if let Some(s) = parse_wav(data) {
113
+ return Some(s);
114
+ }
115
+ if let Some(s) = parse_ogg(data) {
116
+ return Some(s);
117
+ }
118
+ #[cfg(feature = "mp3")]
119
+ if let Some(s) = parse_mp3(data) {
120
+ return Some(s);
121
+ }
122
+ None
123
+ }