@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,261 @@
|
|
|
1
|
+
use crate::audio::AudioMixer;
|
|
2
|
+
use crate::input::InputState;
|
|
3
|
+
use crate::renderer::Renderer;
|
|
4
|
+
use crate::text_renderer::TextRenderer;
|
|
5
|
+
use crate::textures::TextureManager;
|
|
6
|
+
#[cfg(feature = "models3d")]
|
|
7
|
+
use crate::models::ModelManager;
|
|
8
|
+
use crate::scene::SceneGraph;
|
|
9
|
+
use crate::frame_callbacks::FrameCallbackSystem;
|
|
10
|
+
use crate::postfx::PostFxPipeline;
|
|
11
|
+
use crate::profiler::Profiler;
|
|
12
|
+
use crate::drs::DrsController;
|
|
13
|
+
#[cfg(all(feature = "jolt", not(target_arch = "wasm32")))]
|
|
14
|
+
use crate::physics_jolt::JoltPhysics;
|
|
15
|
+
|
|
16
|
+
#[cfg(feature = "web")]
|
|
17
|
+
use web_time::Instant;
|
|
18
|
+
#[cfg(not(feature = "web"))]
|
|
19
|
+
use std::time::Instant;
|
|
20
|
+
|
|
21
|
+
pub struct EngineState {
|
|
22
|
+
pub renderer: Renderer,
|
|
23
|
+
pub text: TextRenderer,
|
|
24
|
+
pub input: InputState,
|
|
25
|
+
pub audio: AudioMixer,
|
|
26
|
+
pub textures: TextureManager,
|
|
27
|
+
#[cfg(feature = "models3d")]
|
|
28
|
+
pub models: ModelManager,
|
|
29
|
+
pub scene: SceneGraph,
|
|
30
|
+
pub frame_callbacks: FrameCallbackSystem,
|
|
31
|
+
pub postfx: Option<PostFxPipeline>,
|
|
32
|
+
pub profiler: Profiler,
|
|
33
|
+
pub screenshot_pending: bool,
|
|
34
|
+
pub drs: DrsController,
|
|
35
|
+
/// EN-026 / EN-027 — particle pools and the decal ring. Both are pure CPU
|
|
36
|
+
/// state that feeds dynamic instance buffers; they own no GPU resources of
|
|
37
|
+
/// their own beyond the buffer handles they were given at creation.
|
|
38
|
+
pub particles: crate::particles::ParticleManager,
|
|
39
|
+
pub decals: crate::decals::DecalManager,
|
|
40
|
+
/// EN-025 — ragdoll slots. Bodies live in the Jolt world; this owns the
|
|
41
|
+
/// bone->body mapping that turns them back into a skinned pose.
|
|
42
|
+
#[cfg(all(feature = "models3d", feature = "jolt"))]
|
|
43
|
+
pub ragdolls: crate::ragdoll::RagdollManager,
|
|
44
|
+
|
|
45
|
+
// Timing
|
|
46
|
+
pub target_fps: f64,
|
|
47
|
+
pub delta_time: f64,
|
|
48
|
+
last_frame_time: Instant,
|
|
49
|
+
frame_count: u64,
|
|
50
|
+
fps_timer: Instant,
|
|
51
|
+
fps_frame_count: u64,
|
|
52
|
+
current_fps: f64,
|
|
53
|
+
start_time: Instant,
|
|
54
|
+
|
|
55
|
+
pub should_close: bool,
|
|
56
|
+
|
|
57
|
+
// Read-back state for the split FFI getters: bloom_scene_pick /
|
|
58
|
+
// bloom_scene_pick_all / bloom_project_to_screen store here, and the
|
|
59
|
+
// bloom_pick_hit_* / bloom_pick_all_* / bloom_project_screen_y getters
|
|
60
|
+
// read the components back one f64 at a time (Perry FFI returns are
|
|
61
|
+
// scalar). Lived in per-crate `static mut`s before the FFI
|
|
62
|
+
// unification.
|
|
63
|
+
pub last_pick: Option<crate::picking::PickResult>,
|
|
64
|
+
pub last_pick_all: Vec<crate::picking::PickResult>,
|
|
65
|
+
pub last_project: (f64, f64),
|
|
66
|
+
|
|
67
|
+
// When true, end_frame() takes a direct-to-swapchain path that skips
|
|
68
|
+
// scene graph prep, shadow maps, HDR/post-FX, SDF/WSRC bakes, etc.
|
|
69
|
+
// Intended for pure-2D games that only need the batched 2D pipeline.
|
|
70
|
+
// Off by default to preserve existing behaviour.
|
|
71
|
+
pub direct_2d_mode: bool,
|
|
72
|
+
|
|
73
|
+
#[cfg(all(feature = "jolt", not(target_arch = "wasm32")))]
|
|
74
|
+
pub jolt: JoltPhysics,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
impl EngineState {
|
|
78
|
+
pub fn new(renderer: Renderer) -> Self {
|
|
79
|
+
let now = Instant::now();
|
|
80
|
+
let mut profiler = Profiler::new();
|
|
81
|
+
profiler.init_gpu(&renderer.device, &renderer.queue);
|
|
82
|
+
// Ticket 007b: tell the scene graph whether the device was
|
|
83
|
+
// created with HW ray-query so geometry upload pays the BLAS /
|
|
84
|
+
// BLAS_INPUT cost only when it can be used.
|
|
85
|
+
let mut scene = SceneGraph::new();
|
|
86
|
+
scene.hw_rt_enabled = renderer.hw_rt_enabled;
|
|
87
|
+
Self {
|
|
88
|
+
renderer,
|
|
89
|
+
text: TextRenderer::new(),
|
|
90
|
+
input: InputState::new(),
|
|
91
|
+
audio: AudioMixer::new(),
|
|
92
|
+
textures: TextureManager::new(),
|
|
93
|
+
#[cfg(feature = "models3d")]
|
|
94
|
+
models: ModelManager::new(),
|
|
95
|
+
scene,
|
|
96
|
+
frame_callbacks: FrameCallbackSystem::new(),
|
|
97
|
+
postfx: None,
|
|
98
|
+
profiler,
|
|
99
|
+
screenshot_pending: false,
|
|
100
|
+
drs: DrsController::new(),
|
|
101
|
+
particles: crate::particles::ParticleManager::new(),
|
|
102
|
+
decals: crate::decals::DecalManager::new(),
|
|
103
|
+
#[cfg(all(feature = "models3d", feature = "jolt"))]
|
|
104
|
+
ragdolls: crate::ragdoll::RagdollManager::new(),
|
|
105
|
+
target_fps: 60.0,
|
|
106
|
+
delta_time: 0.0,
|
|
107
|
+
last_frame_time: now,
|
|
108
|
+
frame_count: 0,
|
|
109
|
+
fps_timer: now,
|
|
110
|
+
fps_frame_count: 0,
|
|
111
|
+
current_fps: 0.0,
|
|
112
|
+
start_time: now,
|
|
113
|
+
should_close: false,
|
|
114
|
+
last_pick: None,
|
|
115
|
+
last_pick_all: Vec::new(),
|
|
116
|
+
last_project: (0.0, 0.0),
|
|
117
|
+
direct_2d_mode: false,
|
|
118
|
+
#[cfg(all(feature = "jolt", not(target_arch = "wasm32")))]
|
|
119
|
+
jolt: JoltPhysics::new(),
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Hard ceiling on the delta time games observe. A debugger pause, OS
|
|
124
|
+
/// hitch, or backgrounded tab produces one slowed-down frame instead
|
|
125
|
+
/// of a quarter-hour dt that explodes physics, animation, and any
|
|
126
|
+
/// game-side `pos += vel * dt`. Matches Unity's maximumDeltaTime
|
|
127
|
+
/// default order of magnitude.
|
|
128
|
+
pub const MAX_DELTA_TIME: f64 = 0.25;
|
|
129
|
+
|
|
130
|
+
pub fn begin_frame(&mut self) {
|
|
131
|
+
self.begin_frame_without_callbacks();
|
|
132
|
+
|
|
133
|
+
// Run frame callbacks after begin_frame (matching R3F's useFrame timing)
|
|
134
|
+
self.profiler.begin("frame_callbacks");
|
|
135
|
+
self.frame_callbacks.run_all(self.delta_time);
|
|
136
|
+
self.profiler.end("frame_callbacks");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// Advance the engine to the callback boundary without invoking foreign
|
|
140
|
+
/// callbacks. Linux uses this split because its engine state is protected
|
|
141
|
+
/// by a mutex and callbacks may call back into the FFI.
|
|
142
|
+
pub fn begin_frame_without_callbacks(&mut self) {
|
|
143
|
+
let now = Instant::now();
|
|
144
|
+
self.delta_time = now
|
|
145
|
+
.duration_since(self.last_frame_time)
|
|
146
|
+
.as_secs_f64()
|
|
147
|
+
.min(Self::MAX_DELTA_TIME);
|
|
148
|
+
self.last_frame_time = now;
|
|
149
|
+
|
|
150
|
+
// DRS samples wall-clock frame time and may step render_scale
|
|
151
|
+
// up or down before this frame's renderer work begins. No-op
|
|
152
|
+
// when disabled (default) — and the controller's own cooldown
|
|
153
|
+
// + hysteresis bound how often a step actually happens.
|
|
154
|
+
self.drs.tick(self.delta_time, &mut self.renderer);
|
|
155
|
+
|
|
156
|
+
self.fps_frame_count += 1;
|
|
157
|
+
let fps_elapsed = now.duration_since(self.fps_timer).as_secs_f64();
|
|
158
|
+
if fps_elapsed >= 1.0 {
|
|
159
|
+
self.current_fps = self.fps_frame_count as f64 / fps_elapsed;
|
|
160
|
+
self.fps_frame_count = 0;
|
|
161
|
+
self.fps_timer = now;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
self.input.begin_frame();
|
|
165
|
+
self.renderer.begin_frame();
|
|
166
|
+
self.frame_count += 1;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// Start profiling and snapshot the callbacks for the current frame. The
|
|
170
|
+
/// returned function pointers are owned by the caller, so the engine lock
|
|
171
|
+
/// can be released before they are invoked.
|
|
172
|
+
pub fn begin_frame_callbacks(&mut self) -> (f64, Vec<extern "C" fn(f64)>) {
|
|
173
|
+
self.profiler.begin("frame_callbacks");
|
|
174
|
+
let delta_time = self.delta_time;
|
|
175
|
+
let callbacks = self.frame_callbacks.callbacks_for_frame();
|
|
176
|
+
(delta_time, callbacks)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Finish the profiling span opened by [`Self::begin_frame_callbacks`].
|
|
180
|
+
pub fn finish_frame_callbacks(&mut self) {
|
|
181
|
+
self.profiler.end("frame_callbacks");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
pub fn end_frame(&mut self) {
|
|
185
|
+
if self.direct_2d_mode {
|
|
186
|
+
// Fast path for pure-2D games: render direct to the swapchain,
|
|
187
|
+
// skipping scene prep, shadow maps, HDR/tonemap, SSAO, bloom,
|
|
188
|
+
// SDF/WSRC bakes and mesh-card capture. On mobile GPUs the
|
|
189
|
+
// full deferred pipeline easily costs tens of ms/frame even
|
|
190
|
+
// when the scene graph is empty; this path typically hits
|
|
191
|
+
// 60 fps on the same device.
|
|
192
|
+
self.profiler.begin("render_total");
|
|
193
|
+
self.renderer.end_frame();
|
|
194
|
+
self.profiler.end("render_total");
|
|
195
|
+
} else {
|
|
196
|
+
self.profiler.begin("scene_prepare");
|
|
197
|
+
// Collect last frame's occlusion-grid readback (if it
|
|
198
|
+
// completed) before culling against it.
|
|
199
|
+
self.renderer.occlusion.poll(&self.renderer.device);
|
|
200
|
+
// EN-057 — skip the occlusion reduce + readback entirely when no
|
|
201
|
+
// rasterized node could consume a verdict (gi_only proxies never
|
|
202
|
+
// draw; the shooter's 267 of them paid this every frame for
|
|
203
|
+
// nothing). The Hi-Z pyramid itself stays: SSAO consumes it.
|
|
204
|
+
self.renderer.occlusion.set_has_consumers(
|
|
205
|
+
self.scene.nodes.iter().any(|(_, n)| n.visible && !n.gi_only));
|
|
206
|
+
self.scene.prepare(
|
|
207
|
+
&self.renderer.device,
|
|
208
|
+
&self.renderer.queue,
|
|
209
|
+
&self.renderer.vp_matrix(),
|
|
210
|
+
// EN-022 fix: the velocity reference (prev unjittered VP
|
|
211
|
+
// + current jitter) — NOT the raw jittered prev VP — so
|
|
212
|
+
// static scene nodes get true zero velocity instead of
|
|
213
|
+
// jitter-delta noise that flickers TAA history.
|
|
214
|
+
&self.renderer.velocity_ref_vp,
|
|
215
|
+
self.renderer.uniform_3d_layout(),
|
|
216
|
+
Some(&self.renderer.occlusion),
|
|
217
|
+
);
|
|
218
|
+
self.scene.prepare_materials(&self.renderer);
|
|
219
|
+
self.profiler.end("scene_prepare");
|
|
220
|
+
|
|
221
|
+
// Phase 6 — drain hot-reload events and rebuild any
|
|
222
|
+
// material whose .wgsl file changed on disk. Cheap when
|
|
223
|
+
// nothing has changed (just a try_recv on an empty queue).
|
|
224
|
+
self.renderer.poll_material_hot_reload();
|
|
225
|
+
|
|
226
|
+
// Sync material-system PerFrame + PerView UBOs with the
|
|
227
|
+
// current clock + camera before dispatching any queued
|
|
228
|
+
// material draws. Without this, group 1 (view/proj/camera/
|
|
229
|
+
// lights/shadow) is zero and shaders that read e.g.
|
|
230
|
+
// `view.view_proj` produce offscreen geometry.
|
|
231
|
+
let t = self.get_time() as f32;
|
|
232
|
+
let dt = self.delta_time as f32;
|
|
233
|
+
self.renderer.material_system_begin_frame(t, dt);
|
|
234
|
+
|
|
235
|
+
self.profiler.begin("render_total");
|
|
236
|
+
self.renderer.end_frame_with_scene(&mut self.scene, &mut self.profiler);
|
|
237
|
+
self.profiler.end("render_total");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
self.profiler.frame_end(&self.renderer.device);
|
|
241
|
+
self.input.end_frame();
|
|
242
|
+
|
|
243
|
+
// Vsync (PresentMode::Fifo, the wgpu default) already caps frame rate.
|
|
244
|
+
// Only apply CPU sleep-based cap when vsync is not active.
|
|
245
|
+
// On WASM, frame pacing is handled by requestAnimationFrame.
|
|
246
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
247
|
+
if self.target_fps > 0.0 && !self.renderer.vsync_active() {
|
|
248
|
+
let target_frame_time = 1.0 / self.target_fps;
|
|
249
|
+
let elapsed = self.last_frame_time.elapsed().as_secs_f64();
|
|
250
|
+
if elapsed < target_frame_time {
|
|
251
|
+
let sleep_time = target_frame_time - elapsed;
|
|
252
|
+
std::thread::sleep(std::time::Duration::from_secs_f64(sleep_time));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
pub fn get_fps(&self) -> f64 { self.current_fps }
|
|
258
|
+
pub fn get_time(&self) -> f64 { self.start_time.elapsed().as_secs_f64() }
|
|
259
|
+
pub fn screen_width(&self) -> f64 { self.renderer.width() as f64 }
|
|
260
|
+
pub fn screen_height(&self) -> f64 { self.renderer.height() as f64 }
|
|
261
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//! FFI boundary infrastructure shared by every platform crate.
|
|
2
|
+
//!
|
|
3
|
+
//! All `bloom_*` extern "C" functions generated by [`define_core_ffi!`]
|
|
4
|
+
//! (and, over time, the hand-written platform-specific ones) route their
|
|
5
|
+
//! bodies through [`guard`], which:
|
|
6
|
+
//!
|
|
7
|
+
//! 1. Prevents a Rust panic from unwinding across the `extern "C"`
|
|
8
|
+
//! boundary into Perry-compiled code. Release builds use
|
|
9
|
+
//! `panic = "abort"` where this is moot, but dev/test profiles unwind,
|
|
10
|
+
//! and rustc only aborts at the boundary since 1.81 — logging the
|
|
11
|
+
//! function name first is strictly more debuggable than a bare abort.
|
|
12
|
+
//! 2. Converts a panic into a logged, once-per-symbol error plus a safe
|
|
13
|
+
//! default return value, so a single bad call (stale handle, NaN
|
|
14
|
+
//! argument, asset race) degrades that call instead of killing the
|
|
15
|
+
//! game process.
|
|
16
|
+
//!
|
|
17
|
+
//! Logging goes through [`log_error`], which writes to logcat on Android
|
|
18
|
+
//! (stderr is discarded there) and stderr everywhere else.
|
|
19
|
+
|
|
20
|
+
/// Default return value for an FFI function whose body panicked.
|
|
21
|
+
pub trait FfiDefault {
|
|
22
|
+
fn ffi_default() -> Self;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl FfiDefault for () {
|
|
26
|
+
fn ffi_default() -> Self {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl FfiDefault for f64 {
|
|
30
|
+
fn ffi_default() -> Self {
|
|
31
|
+
0.0
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl FfiDefault for *const u8 {
|
|
36
|
+
fn ffi_default() -> Self {
|
|
37
|
+
std::ptr::null()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Run an FFI function body, catching panics at the boundary.
|
|
42
|
+
///
|
|
43
|
+
/// On panic: logs `name` + the panic payload (once per symbol, so a
|
|
44
|
+
/// panicking per-frame call doesn't flood the log) and returns
|
|
45
|
+
/// `T::ffi_default()`.
|
|
46
|
+
pub fn guard<T: FfiDefault>(name: &'static str, f: impl FnOnce() -> T) -> T {
|
|
47
|
+
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
|
|
48
|
+
Ok(v) => v,
|
|
49
|
+
Err(payload) => {
|
|
50
|
+
warn_once(name, || {
|
|
51
|
+
let msg = if let Some(s) = payload.downcast_ref::<&str>() {
|
|
52
|
+
(*s).to_string()
|
|
53
|
+
} else if let Some(s) = payload.downcast_ref::<String>() {
|
|
54
|
+
s.clone()
|
|
55
|
+
} else {
|
|
56
|
+
"non-string panic payload".to_string()
|
|
57
|
+
};
|
|
58
|
+
format!("bloom: panic in {name}(): {msg} — returning default; further panics in this function are suppressed")
|
|
59
|
+
});
|
|
60
|
+
T::ffi_default()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Log `make_msg()` the first time `key` is seen, silently no-op after.
|
|
66
|
+
fn warn_once(key: &'static str, make_msg: impl FnOnce() -> String) {
|
|
67
|
+
use std::sync::Mutex;
|
|
68
|
+
static SEEN: Mutex<Vec<&'static str>> = Mutex::new(Vec::new());
|
|
69
|
+
let mut seen = match SEEN.lock() {
|
|
70
|
+
Ok(g) => g,
|
|
71
|
+
// Poisoned only if a previous caller panicked while holding the
|
|
72
|
+
// lock; logging is best-effort, keep going with the inner data.
|
|
73
|
+
Err(p) => p.into_inner(),
|
|
74
|
+
};
|
|
75
|
+
if !seen.contains(&key) {
|
|
76
|
+
seen.push(key);
|
|
77
|
+
log_error(&make_msg());
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// One-time "this function is compiled out" notice for feature-gated FFI
|
|
82
|
+
/// stubs (models3d, image-extras). A silent stub here cost weeks of
|
|
83
|
+
/// confusion historically (Windows shipped silent no-ops for the whole
|
|
84
|
+
/// scene-graph surface) — never strip the warning.
|
|
85
|
+
pub fn feature_off_warn_once(name: &'static str, feature: &'static str) {
|
|
86
|
+
warn_once(name, || {
|
|
87
|
+
format!("bloom: {name}() ignored — engine built without the `{feature}` cargo feature")
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Platform-appropriate error log: logcat on Android, stderr elsewhere.
|
|
92
|
+
pub fn log_error(msg: &str) {
|
|
93
|
+
#[cfg(target_os = "android")]
|
|
94
|
+
{
|
|
95
|
+
// liblog is linked by every Android app process.
|
|
96
|
+
extern "C" {
|
|
97
|
+
fn __android_log_print(prio: i32, tag: *const u8, fmt: *const u8, ...) -> i32;
|
|
98
|
+
}
|
|
99
|
+
if let Ok(c) = std::ffi::CString::new(msg) {
|
|
100
|
+
// 6 = ANDROID_LOG_ERROR
|
|
101
|
+
unsafe {
|
|
102
|
+
__android_log_print(6, b"BloomEngine\0".as_ptr(), b"%s\0".as_ptr(), c.as_ptr());
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// EN-063 — a browser has no stderr, so eprintln! drops the message
|
|
108
|
+
// entirely. Every engine diagnostic was invisible on web until this.
|
|
109
|
+
#[cfg(target_arch = "wasm32")]
|
|
110
|
+
{
|
|
111
|
+
web_sys::console::error_1(&msg.into());
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
#[cfg(all(not(target_os = "android"), not(target_arch = "wasm32")))]
|
|
115
|
+
eprintln!("{msg}");
|
|
116
|
+
}
|