@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.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/native/android/Cargo.lock +1848 -0
- package/native/android/Cargo.toml +24 -0
- package/native/android/src/lib.rs +702 -0
- package/native/ios/Cargo.lock +1690 -0
- package/native/ios/Cargo.toml +32 -0
- package/native/ios/src/lib.rs +1267 -0
- package/native/linux/Cargo.lock +3279 -0
- package/native/linux/Cargo.toml +29 -0
- package/native/linux/src/lib.rs +1331 -0
- package/native/macos/Cargo.lock +3310 -0
- package/native/macos/Cargo.toml +46 -0
- package/native/macos/src/lib.rs +1302 -0
- package/native/shared/Cargo.lock +1899 -0
- package/native/shared/Cargo.toml +62 -0
- package/native/shared/assets/default_font.ttf +0 -0
- package/native/shared/build.rs +270 -0
- package/native/shared/shaders/common/clouds.wgsl +122 -0
- package/native/shared/shaders/common/fog.wgsl +16 -0
- package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
- package/native/shared/shaders/common/imposter.wgsl +112 -0
- package/native/shared/shaders/common/pbr.wgsl +186 -0
- package/native/shared/shaders/common/shadows.wgsl +186 -0
- package/native/shared/shaders/common/sky.wgsl +8 -0
- package/native/shared/shaders/common/tonemap.wgsl +25 -0
- package/native/shared/shaders/impulse_field.wgsl +57 -0
- package/native/shared/shaders/material_abi.wgsl +383 -0
- package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
- package/native/shared/src/anim_mixer.rs +61 -0
- package/native/shared/src/attach.rs +263 -0
- package/native/shared/src/audio/decode.rs +123 -0
- package/native/shared/src/audio/mod.rs +863 -0
- package/native/shared/src/audio/render.rs +892 -0
- package/native/shared/src/audio/spsc.rs +156 -0
- package/native/shared/src/audio/stream.rs +226 -0
- package/native/shared/src/custom_shaders.rs +104 -0
- package/native/shared/src/decals.rs +245 -0
- package/native/shared/src/drs.rs +211 -0
- package/native/shared/src/engine.rs +261 -0
- package/native/shared/src/ffi.rs +116 -0
- package/native/shared/src/ffi_core/assets.rs +388 -0
- package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
- package/native/shared/src/ffi_core/draw.rs +334 -0
- package/native/shared/src/ffi_core/game_loop.rs +577 -0
- package/native/shared/src/ffi_core/input.rs +234 -0
- package/native/shared/src/ffi_core/mod.rs +127 -0
- package/native/shared/src/ffi_core/models.rs +1154 -0
- package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
- package/native/shared/src/ffi_core/scene.rs +626 -0
- package/native/shared/src/ffi_core/vfx.rs +212 -0
- package/native/shared/src/ffi_core/visual.rs +691 -0
- package/native/shared/src/frame_callbacks.rs +122 -0
- package/native/shared/src/geometry.rs +236 -0
- package/native/shared/src/handles.rs +182 -0
- package/native/shared/src/input.rs +448 -0
- package/native/shared/src/jolt_sys.rs +822 -0
- package/native/shared/src/lib.rs +55 -0
- package/native/shared/src/models.rs +1093 -0
- package/native/shared/src/models_gltf.rs +1280 -0
- package/native/shared/src/particles.rs +391 -0
- package/native/shared/src/physics_jolt.rs +1908 -0
- package/native/shared/src/picking.rs +298 -0
- package/native/shared/src/postfx.rs +345 -0
- package/native/shared/src/profiler.rs +492 -0
- package/native/shared/src/ragdoll.rs +474 -0
- package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
- package/native/shared/src/renderer/brdf_lut.rs +154 -0
- package/native/shared/src/renderer/draw2d.rs +143 -0
- package/native/shared/src/renderer/formats.rs +822 -0
- package/native/shared/src/renderer/froxel.rs +421 -0
- package/native/shared/src/renderer/gi_bake.rs +653 -0
- package/native/shared/src/renderer/graph.rs +462 -0
- package/native/shared/src/renderer/hiz.rs +269 -0
- package/native/shared/src/renderer/hot_reload.rs +390 -0
- package/native/shared/src/renderer/impulse_field.rs +456 -0
- package/native/shared/src/renderer/lighting.rs +154 -0
- package/native/shared/src/renderer/material_instancing.rs +171 -0
- package/native/shared/src/renderer/material_pipeline.rs +700 -0
- package/native/shared/src/renderer/material_system.rs +1996 -0
- package/native/shared/src/renderer/material_system_tests.rs +601 -0
- package/native/shared/src/renderer/material_system_wasm.rs +41 -0
- package/native/shared/src/renderer/mod.rs +12556 -0
- package/native/shared/src/renderer/model_draw.rs +641 -0
- package/native/shared/src/renderer/occlusion.rs +429 -0
- package/native/shared/src/renderer/planar_pass.rs +593 -0
- package/native/shared/src/renderer/planar_reflection.rs +499 -0
- package/native/shared/src/renderer/post_pass.rs +249 -0
- package/native/shared/src/renderer/postfx_chain.rs +728 -0
- package/native/shared/src/renderer/pt_pass.rs +577 -0
- package/native/shared/src/renderer/scene_pass.rs +607 -0
- package/native/shared/src/renderer/shader_include.rs +205 -0
- package/native/shared/src/renderer/shader_library.rs +135 -0
- package/native/shared/src/renderer/shaders/ao.rs +570 -0
- package/native/shared/src/renderer/shaders/core.rs +1243 -0
- package/native/shared/src/renderer/shaders/env.rs +907 -0
- package/native/shared/src/renderer/shaders/gi.rs +810 -0
- package/native/shared/src/renderer/shaders/mod.rs +19 -0
- package/native/shared/src/renderer/shaders/post.rs +1558 -0
- package/native/shared/src/renderer/shaders/pt.rs +1859 -0
- package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
- package/native/shared/src/renderer/shadow_pass.rs +731 -0
- package/native/shared/src/renderer/ssgi_pass.rs +392 -0
- package/native/shared/src/renderer/ssr_pass.rs +188 -0
- package/native/shared/src/renderer/texture_store.rs +473 -0
- package/native/shared/src/renderer/transient.rs +591 -0
- package/native/shared/src/renderer/types.rs +941 -0
- package/native/shared/src/renderer/util.rs +152 -0
- package/native/shared/src/scene.rs +1362 -0
- package/native/shared/src/sdf_cache.rs +274 -0
- package/native/shared/src/shadows.rs +1036 -0
- package/native/shared/src/staging.rs +102 -0
- package/native/shared/src/string_header.rs +266 -0
- package/native/shared/src/text_renderer.rs +502 -0
- package/native/shared/src/textures.rs +197 -0
- package/native/tvos/Cargo.lock +1693 -0
- package/native/tvos/Cargo.toml +36 -0
- package/native/tvos/metal-patched/Cargo.toml +178 -0
- package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
- package/native/tvos/metal-patched/LICENSE-MIT +25 -0
- package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
- package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
- package/native/tvos/metal-patched/src/argument.rs +366 -0
- package/native/tvos/metal-patched/src/blitpass.rs +102 -0
- package/native/tvos/metal-patched/src/buffer.rs +71 -0
- package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
- package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
- package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
- package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
- package/native/tvos/metal-patched/src/computepass.rs +107 -0
- package/native/tvos/metal-patched/src/constants.rs +152 -0
- package/native/tvos/metal-patched/src/counters.rs +119 -0
- package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
- package/native/tvos/metal-patched/src/device.rs +2134 -0
- package/native/tvos/metal-patched/src/drawable.rs +39 -0
- package/native/tvos/metal-patched/src/encoder.rs +2041 -0
- package/native/tvos/metal-patched/src/heap.rs +281 -0
- package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
- package/native/tvos/metal-patched/src/lib.rs +657 -0
- package/native/tvos/metal-patched/src/library.rs +902 -0
- package/native/tvos/metal-patched/src/mps.rs +575 -0
- package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
- package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
- package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
- package/native/tvos/metal-patched/src/renderpass.rs +443 -0
- package/native/tvos/metal-patched/src/resource.rs +182 -0
- package/native/tvos/metal-patched/src/sampler.rs +165 -0
- package/native/tvos/metal-patched/src/sync.rs +178 -0
- package/native/tvos/metal-patched/src/texture.rs +352 -0
- package/native/tvos/metal-patched/src/types.rs +90 -0
- package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
- package/native/tvos/src/audio_backend.rs +197 -0
- package/native/tvos/src/lib.rs +1891 -0
- package/native/visionos/Cargo.lock +1693 -0
- package/native/visionos/Cargo.toml +40 -0
- package/native/visionos/src/audio_backend.rs +197 -0
- package/native/visionos/src/lib.rs +1887 -0
- package/native/watchos/Cargo.lock +16 -0
- package/native/watchos/Cargo.toml +19 -0
- package/native/watchos/shaders/bloom_postfx.metal +99 -0
- package/native/watchos/src/BloomWatchApp.swift +1267 -0
- package/native/watchos/src/BloomWatchAudio.swift +179 -0
- package/native/watchos/src/audio.rs +55 -0
- package/native/watchos/src/draw_list.rs +229 -0
- package/native/watchos/src/ffi_stubs.rs +915 -0
- package/native/watchos/src/ffi_stubs_manual.rs +35 -0
- package/native/watchos/src/lib.rs +1124 -0
- package/native/watchos/src/models.rs +746 -0
- package/native/watchos/src/postfx.rs +95 -0
- package/native/watchos/src/scene.rs +534 -0
- package/native/watchos/src/textures.rs +184 -0
- package/native/web/Cargo.lock +1657 -0
- package/native/web/Cargo.toml +43 -0
- package/native/web/bloom_glue.js +695 -0
- package/native/web/build.sh +131 -0
- package/native/web/index.html +35 -0
- package/native/web/jolt_bridge.js +1519 -0
- package/native/web/src/input_ffi.rs +286 -0
- package/native/web/src/lib.rs +1796 -0
- package/native/web/src/material_ffi.rs +710 -0
- package/native/web/src/parity_ffi.rs +343 -0
- package/native/web/src/physics_ffi.rs +643 -0
- package/native/web/src/ragdoll_ffi.rs +250 -0
- package/native/web/src/render_settings.rs +98 -0
- package/native/windows/Cargo.lock +1815 -0
- package/native/windows/Cargo.toml +68 -0
- package/native/windows/src/lib.rs +1486 -0
- package/package.json +4279 -0
- package/src/audio/index.ts +315 -0
- package/src/core/colors.ts +63 -0
- package/src/core/index.ts +1206 -0
- package/src/core/keys.ts +63 -0
- package/src/core/types.ts +104 -0
- package/src/index.ts +171 -0
- package/src/math/index.ts +516 -0
- package/src/mobile/index.ts +294 -0
- package/src/models/index.ts +1258 -0
- package/src/physics/index.ts +1134 -0
- package/src/scene/index.ts +698 -0
- package/src/shapes/index.ts +120 -0
- package/src/text/index.ts +48 -0
- package/src/textures/index.ts +187 -0
- package/src/vfx/index.ts +191 -0
- package/src/world/index.ts +24 -0
- package/src/world/loader.ts +423 -0
- package/src/world/prefab.ts +217 -0
- package/src/world/render.ts +172 -0
- package/src/world/saver.ts +108 -0
- package/src/world/serialize.ts +301 -0
- package/src/world/terrain.ts +355 -0
- package/src/world/types.ts +160 -0
- package/src/world/validate.ts +319 -0
- 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
|
+
}
|