@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,702 @@
|
|
|
1
|
+
use bloom_shared::engine::EngineState;
|
|
2
|
+
use bloom_shared::renderer::Renderer;
|
|
3
|
+
use bloom_shared::string_header::{str_from_header, alloc_perry_string};
|
|
4
|
+
use bloom_shared::audio::{parse_wav, parse_ogg, parse_mp3};
|
|
5
|
+
|
|
6
|
+
use std::sync::OnceLock;
|
|
7
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
8
|
+
|
|
9
|
+
static mut ENGINE: OnceLock<EngineState> = OnceLock::new();
|
|
10
|
+
static mut NATIVE_WINDOW: *mut libc::c_void = std::ptr::null_mut();
|
|
11
|
+
static AUDIO_RUNNING: AtomicBool = AtomicBool::new(false);
|
|
12
|
+
static mut ASSET_BASE_PATH: Option<String> = None;
|
|
13
|
+
|
|
14
|
+
fn engine() -> &'static mut EngineState {
|
|
15
|
+
unsafe { ENGINE.get_mut().expect("Engine not initialized") }
|
|
16
|
+
}
|
|
17
|
+
/// Asset-path hook for define_core_ffi! — routes through this platform's
|
|
18
|
+
/// resolve_path (relative paths don't resolve from the app working dir here).
|
|
19
|
+
fn bloom_resolve_asset_path(path: &str) -> std::borrow::Cow<'_, str> {
|
|
20
|
+
std::borrow::Cow::Owned(resolve_path(path))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The full shared (non-physics) FFI surface. See bloom_shared::ffi_core
|
|
24
|
+
// docs for the contract; tools/validate-ffi.js checks parity in CI.
|
|
25
|
+
bloom_shared::define_core_ffi!();
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/// Resolve relative asset paths to the app's base asset directory.
|
|
29
|
+
/// On Android, relative paths like "assets/models/tree.glb" won't resolve
|
|
30
|
+
/// because the working directory isn't the app's data directory.
|
|
31
|
+
fn resolve_path(path: &str) -> String {
|
|
32
|
+
if path.starts_with('/') {
|
|
33
|
+
return path.to_string();
|
|
34
|
+
}
|
|
35
|
+
unsafe {
|
|
36
|
+
if let Some(ref base) = ASSET_BASE_PATH {
|
|
37
|
+
format!("{}/{}", base, path)
|
|
38
|
+
} else {
|
|
39
|
+
path.to_string()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Called by the Android Activity to set the base path for asset resolution.
|
|
45
|
+
/// Should be set to the app's files directory where assets are extracted.
|
|
46
|
+
#[no_mangle]
|
|
47
|
+
pub extern "C" fn bloom_android_set_asset_path(path_ptr: *const u8) {
|
|
48
|
+
let path = str_from_header(path_ptr);
|
|
49
|
+
unsafe {
|
|
50
|
+
ASSET_BASE_PATH = Some(path.to_string());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================
|
|
55
|
+
// ANativeWindow FFI
|
|
56
|
+
// ============================================================
|
|
57
|
+
|
|
58
|
+
extern "C" {
|
|
59
|
+
fn ANativeWindow_getWidth(window: *mut libc::c_void) -> i32;
|
|
60
|
+
fn ANativeWindow_getHeight(window: *mut libc::c_void) -> i32;
|
|
61
|
+
fn ANativeWindow_acquire(window: *mut libc::c_void);
|
|
62
|
+
fn ANativeWindow_release(window: *mut libc::c_void);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Called by Perry's Android runtime to set the ANativeWindow pointer
|
|
66
|
+
/// before bloom_init_window is invoked.
|
|
67
|
+
#[no_mangle]
|
|
68
|
+
pub extern "C" fn bloom_android_set_native_window(window: *mut libc::c_void) {
|
|
69
|
+
unsafe {
|
|
70
|
+
if !window.is_null() {
|
|
71
|
+
ANativeWindow_acquire(window);
|
|
72
|
+
}
|
|
73
|
+
NATIVE_WINDOW = window;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn pollster_block_on<F: std::future::Future>(future: F) -> F::Output {
|
|
78
|
+
use std::task::{Context, Poll, Wake, Waker};
|
|
79
|
+
use std::pin::Pin;
|
|
80
|
+
use std::sync::Arc;
|
|
81
|
+
struct NoopWaker;
|
|
82
|
+
impl Wake for NoopWaker { fn wake(self: Arc<Self>) {} }
|
|
83
|
+
let waker = Waker::from(Arc::new(NoopWaker));
|
|
84
|
+
let mut cx = Context::from_waker(&waker);
|
|
85
|
+
let mut future = unsafe { Pin::new_unchecked(Box::new(future)) };
|
|
86
|
+
loop {
|
|
87
|
+
match future.as_mut().poll(&mut cx) {
|
|
88
|
+
Poll::Ready(result) => return result,
|
|
89
|
+
Poll::Pending => std::thread::yield_now(),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================
|
|
95
|
+
// Window + Renderer init
|
|
96
|
+
// ============================================================
|
|
97
|
+
|
|
98
|
+
#[no_mangle]
|
|
99
|
+
pub extern "C" fn bloom_init_window(width: f64, height: f64, title_ptr: *const u8, _fullscreen: f64) {
|
|
100
|
+
let _title = str_from_header(title_ptr);
|
|
101
|
+
|
|
102
|
+
unsafe {
|
|
103
|
+
__android_log_print(3, b"BloomEngine\0".as_ptr(), b"bloom_init_window: starting\0".as_ptr());
|
|
104
|
+
let window = NATIVE_WINDOW;
|
|
105
|
+
// If no native window was set, use requested dimensions with a headless surface
|
|
106
|
+
let (pixel_w, pixel_h) = if !window.is_null() {
|
|
107
|
+
(ANativeWindow_getWidth(window) as u32, ANativeWindow_getHeight(window) as u32)
|
|
108
|
+
} else {
|
|
109
|
+
(width as u32, height as u32)
|
|
110
|
+
};
|
|
111
|
+
// Logical size is half of physical: the game UI (fonts, layout constants)
|
|
112
|
+
// was sized for ~1170×540 landscape (iPhone-sized); on Android's 2340×1080
|
|
113
|
+
// panel we'd otherwise render at native pixel size and everything looks
|
|
114
|
+
// half-scale. wgpu still renders to the full physical surface; only the
|
|
115
|
+
// `screen_width`/`screen_height` the game sees are halved.
|
|
116
|
+
let logical_w = (pixel_w / 2).max(1);
|
|
117
|
+
let logical_h = (pixel_h / 2).max(1);
|
|
118
|
+
|
|
119
|
+
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
120
|
+
backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
|
|
121
|
+
flags: wgpu::InstanceFlags::default(),
|
|
122
|
+
..wgpu::InstanceDescriptor::new_without_display_handle()
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Create surface from ANativeWindow
|
|
126
|
+
if window.is_null() {
|
|
127
|
+
// Fallback: can't render without a window, but don't panic
|
|
128
|
+
// Create a minimal engine state (game logic will work, rendering will no-op)
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let handle = raw_window_handle::AndroidNdkWindowHandle::new(
|
|
133
|
+
std::ptr::NonNull::new(window).unwrap()
|
|
134
|
+
);
|
|
135
|
+
let raw = raw_window_handle::RawWindowHandle::AndroidNdk(handle);
|
|
136
|
+
let surface = instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
|
|
137
|
+
raw_display_handle: Some(raw_window_handle::RawDisplayHandle::Android(
|
|
138
|
+
raw_window_handle::AndroidDisplayHandle::new()
|
|
139
|
+
)),
|
|
140
|
+
raw_window_handle: raw,
|
|
141
|
+
}).expect("Failed to create surface");
|
|
142
|
+
__android_log_print(3, b"BloomEngine\0".as_ptr(), b"bloom_init_window: surface created\0".as_ptr());
|
|
143
|
+
|
|
144
|
+
let adapter = pollster_block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
|
145
|
+
compatible_surface: Some(&surface),
|
|
146
|
+
power_preference: wgpu::PowerPreference::HighPerformance,
|
|
147
|
+
..Default::default()
|
|
148
|
+
}));
|
|
149
|
+
let adapter = match adapter {
|
|
150
|
+
Ok(a) => a,
|
|
151
|
+
Err(_) => {
|
|
152
|
+
// Try again without surface compatibility requirement
|
|
153
|
+
match pollster_block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
|
154
|
+
power_preference: wgpu::PowerPreference::HighPerformance,
|
|
155
|
+
..Default::default()
|
|
156
|
+
})) {
|
|
157
|
+
Ok(a) => a,
|
|
158
|
+
Err(_) => panic!("No GPU adapter found"),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
__android_log_print(3, b"BloomEngine\0".as_ptr(), b"bloom_init_window: adapter found\0".as_ptr());
|
|
163
|
+
{
|
|
164
|
+
let info = adapter.get_info();
|
|
165
|
+
let msg = std::ffi::CString::new(format!(
|
|
166
|
+
"adapter: name='{}' backend={:?} device_type={:?} driver='{}' driver_info='{}'",
|
|
167
|
+
info.name, info.backend, info.device_type, info.driver, info.driver_info
|
|
168
|
+
)).unwrap();
|
|
169
|
+
__android_log_print(3, b"BloomEngine\0".as_ptr(), b"%s\0".as_ptr(), msg.as_ptr());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Ticket 007b: most Android GPUs lack RT, but recent Adreno /
|
|
173
|
+
// Mali-Immortalis devices do — request the feature if advertised.
|
|
174
|
+
// Limits merge: start from downlevel (required for older Android
|
|
175
|
+
// adapters), then layer acceleration-structure minimums on top
|
|
176
|
+
// when RT was granted.
|
|
177
|
+
let supported = adapter.features();
|
|
178
|
+
let force_sw_gi = std::env::var("BLOOM_FORCE_SW_GI")
|
|
179
|
+
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
|
|
180
|
+
.unwrap_or(false);
|
|
181
|
+
let rt_mask = wgpu::Features::EXPERIMENTAL_RAY_QUERY;
|
|
182
|
+
let mut required_features = wgpu::Features::empty();
|
|
183
|
+
// Ticket 011: request TIMESTAMP_QUERY when supported so the profiler
|
|
184
|
+
// can record GPU timings. Optional — profiler falls back to CPU-only
|
|
185
|
+
// when the adapter doesn't grant it (many Android GPUs won't).
|
|
186
|
+
if supported.contains(wgpu::Features::TIMESTAMP_QUERY) {
|
|
187
|
+
required_features |= wgpu::Features::TIMESTAMP_QUERY;
|
|
188
|
+
}
|
|
189
|
+
// Cooked BC7 textures (bloom-cook) upload compressed when the
|
|
190
|
+
// adapter has BC support; without it they CPU-decode at load.
|
|
191
|
+
if supported.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) {
|
|
192
|
+
required_features |= wgpu::Features::TEXTURE_COMPRESSION_BC;
|
|
193
|
+
}
|
|
194
|
+
if !force_sw_gi && supported.contains(rt_mask) {
|
|
195
|
+
required_features |= rt_mask;
|
|
196
|
+
}
|
|
197
|
+
let experimental_features = if required_features.intersects(rt_mask) {
|
|
198
|
+
unsafe { wgpu::ExperimentalFeatures::enabled() }
|
|
199
|
+
} else {
|
|
200
|
+
wgpu::ExperimentalFeatures::disabled()
|
|
201
|
+
};
|
|
202
|
+
let adapter_limits = adapter.limits();
|
|
203
|
+
let mut required_limits = wgpu::Limits::downlevel_defaults()
|
|
204
|
+
.using_resolution(adapter_limits.clone());
|
|
205
|
+
// The renderer's `joint_bg` binds a 64KB uniform buffer, but
|
|
206
|
+
// downlevel_defaults caps uniform-buffer bindings at 16KB, so
|
|
207
|
+
// create_bind_group panics on mobile GPUs (e.g. Adreno) with
|
|
208
|
+
// "range 65536 exceeds max_*_buffer_binding_size limit 16384". Raise the
|
|
209
|
+
// buffer-binding sizes (and bind-group count) to the adapter's maximum;
|
|
210
|
+
// these are guaranteed-supported and match the desktop limits.
|
|
211
|
+
required_limits.max_uniform_buffer_binding_size =
|
|
212
|
+
adapter_limits.max_uniform_buffer_binding_size;
|
|
213
|
+
required_limits.max_storage_buffer_binding_size =
|
|
214
|
+
adapter_limits.max_storage_buffer_binding_size;
|
|
215
|
+
required_limits.max_bind_groups =
|
|
216
|
+
required_limits.max_bind_groups.max(5).min(adapter_limits.max_bind_groups);
|
|
217
|
+
if required_features.intersects(rt_mask) {
|
|
218
|
+
required_limits = required_limits
|
|
219
|
+
.using_minimum_supported_acceleration_structure_values();
|
|
220
|
+
}
|
|
221
|
+
let (device, queue) = pollster_block_on(adapter.request_device(
|
|
222
|
+
&wgpu::DeviceDescriptor {
|
|
223
|
+
label: Some("bloom_device"),
|
|
224
|
+
required_features,
|
|
225
|
+
required_limits,
|
|
226
|
+
experimental_features,
|
|
227
|
+
..Default::default()
|
|
228
|
+
},
|
|
229
|
+
)).expect("Failed to create device");
|
|
230
|
+
__android_log_print(3, b"BloomEngine\0".as_ptr(), b"bloom_init_window: device created\0".as_ptr());
|
|
231
|
+
|
|
232
|
+
let surface_caps = surface.get_capabilities(&adapter);
|
|
233
|
+
if surface_caps.formats.is_empty() {
|
|
234
|
+
panic!("Surface reports no supported formats (emulator Vulkan limitation)");
|
|
235
|
+
}
|
|
236
|
+
let format = surface_caps.formats.iter()
|
|
237
|
+
.find(|f| f.is_srgb()).copied()
|
|
238
|
+
.unwrap_or(surface_caps.formats[0]);
|
|
239
|
+
|
|
240
|
+
let alpha_mode = if surface_caps.alpha_modes.is_empty() {
|
|
241
|
+
wgpu::CompositeAlphaMode::Auto
|
|
242
|
+
} else {
|
|
243
|
+
surface_caps.alpha_modes[0]
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
let surface_config = wgpu::SurfaceConfiguration {
|
|
247
|
+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
248
|
+
format,
|
|
249
|
+
width: pixel_w,
|
|
250
|
+
height: pixel_h,
|
|
251
|
+
present_mode: wgpu::PresentMode::Fifo,
|
|
252
|
+
alpha_mode,
|
|
253
|
+
view_formats: vec![],
|
|
254
|
+
desired_maximum_frame_latency: 2,
|
|
255
|
+
};
|
|
256
|
+
surface.configure(&device, &surface_config);
|
|
257
|
+
|
|
258
|
+
__android_log_print(3, b"BloomEngine\0".as_ptr(), b"bloom_init_window: surface configured\0".as_ptr());
|
|
259
|
+
let renderer = Renderer::new(device, queue, surface, surface_config, logical_w, logical_h);
|
|
260
|
+
let _ = ENGINE.set(EngineState::new(renderer));
|
|
261
|
+
__android_log_print(3, b"BloomEngine\0".as_ptr(), b"bloom_init_window: engine initialized\0".as_ptr());
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/// Attach the engine to a host-owned `ANativeWindow*` instead of pulling
|
|
266
|
+
/// it from the global set by `bloom_android_set_native_window`
|
|
267
|
+
/// (PerryTS/perry#5519). `handle` is the `ANativeWindow*` the host
|
|
268
|
+
/// (Perry UI's `BloomView`, backed by a `SurfaceView`/`TextureView`)
|
|
269
|
+
/// owns; `width`/`height` are the surface size in physical pixels.
|
|
270
|
+
/// Returns 1.0 on success, 0.0 on a null/invalid handle or surface
|
|
271
|
+
/// bring-up failure. Idempotent once attached.
|
|
272
|
+
#[no_mangle]
|
|
273
|
+
pub extern "C" fn bloom_attach_native(handle: i64, width: f64, height: f64) -> f64 {
|
|
274
|
+
if handle == 0 {
|
|
275
|
+
return 0.0;
|
|
276
|
+
}
|
|
277
|
+
if unsafe { ENGINE.get() }.is_some() {
|
|
278
|
+
return 1.0;
|
|
279
|
+
}
|
|
280
|
+
let window = handle as *mut libc::c_void;
|
|
281
|
+
let Some(win_nn) = std::ptr::NonNull::new(window) else {
|
|
282
|
+
return 0.0;
|
|
283
|
+
};
|
|
284
|
+
// Hold a reference for as long as the engine renders into it.
|
|
285
|
+
unsafe {
|
|
286
|
+
ANativeWindow_acquire(window);
|
|
287
|
+
NATIVE_WINDOW = window;
|
|
288
|
+
}
|
|
289
|
+
let target = {
|
|
290
|
+
let h = raw_window_handle::AndroidNdkWindowHandle::new(win_nn);
|
|
291
|
+
wgpu::SurfaceTargetUnsafe::RawHandle {
|
|
292
|
+
raw_display_handle: Some(raw_window_handle::RawDisplayHandle::Android(
|
|
293
|
+
raw_window_handle::AndroidDisplayHandle::new(),
|
|
294
|
+
)),
|
|
295
|
+
raw_window_handle: raw_window_handle::RawWindowHandle::AndroidNdk(h),
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
match unsafe {
|
|
299
|
+
bloom_shared::attach::attach_engine(
|
|
300
|
+
target,
|
|
301
|
+
bloom_shared::attach::AttachParams {
|
|
302
|
+
backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
|
|
303
|
+
logical_w: (width as u32).max(1),
|
|
304
|
+
logical_h: (height as u32).max(1),
|
|
305
|
+
physical_w: (width as u32).max(1),
|
|
306
|
+
physical_h: (height as u32).max(1),
|
|
307
|
+
format: bloom_shared::attach::FormatPreference::Srgb,
|
|
308
|
+
},
|
|
309
|
+
)
|
|
310
|
+
} {
|
|
311
|
+
Ok(es) => {
|
|
312
|
+
unsafe {
|
|
313
|
+
let _ = ENGINE.set(es);
|
|
314
|
+
}
|
|
315
|
+
1.0
|
|
316
|
+
}
|
|
317
|
+
Err(_) => 0.0,
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// Resize the engine's surface (#70 parity; used by host-driven
|
|
322
|
+
/// BloomViews on layout changes). `phys_*` physical px, `log_*` logical.
|
|
323
|
+
#[no_mangle]
|
|
324
|
+
pub extern "C" fn bloom_resize(phys_w: f64, phys_h: f64, log_w: f64, log_h: f64) {
|
|
325
|
+
if let Some(eng) = unsafe { ENGINE.get_mut() } {
|
|
326
|
+
eng.renderer.resize(phys_w as u32, phys_h as u32, log_w as u32, log_h as u32);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/// HWND host-embed (#70) — Windows only; a no-op here for FFI-manifest
|
|
331
|
+
/// parity. Non-Windows hosts attach via `bloom_attach_native`.
|
|
332
|
+
#[no_mangle]
|
|
333
|
+
pub extern "C" fn bloom_attach_hwnd(_hwnd_bits: f64, _width: f64, _height: f64) {}
|
|
334
|
+
|
|
335
|
+
#[no_mangle]
|
|
336
|
+
pub extern "C" fn bloom_close_window() {
|
|
337
|
+
unsafe {
|
|
338
|
+
if !NATIVE_WINDOW.is_null() {
|
|
339
|
+
ANativeWindow_release(NATIVE_WINDOW);
|
|
340
|
+
NATIVE_WINDOW = std::ptr::null_mut();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#[no_mangle]
|
|
346
|
+
pub extern "C" fn bloom_window_should_close() -> f64 {
|
|
347
|
+
if engine().should_close { 1.0 } else { 0.0 }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ============================================================
|
|
351
|
+
// Touch input polling (called from Perry's Android event pump)
|
|
352
|
+
// ============================================================
|
|
353
|
+
|
|
354
|
+
/// Called by Perry's Android layer when a touch event occurs.
|
|
355
|
+
#[no_mangle]
|
|
356
|
+
pub extern "C" fn bloom_android_on_touch(action: i32, x: f64, y: f64, pointer_index: i32) {
|
|
357
|
+
unsafe {
|
|
358
|
+
if let Some(eng) = ENGINE.get_mut() {
|
|
359
|
+
let sw = eng.screen_width();
|
|
360
|
+
let sh = eng.screen_height();
|
|
361
|
+
let lx = x * 0.5;
|
|
362
|
+
let ly = y * 0.5;
|
|
363
|
+
let msg = std::ffi::CString::new(format!("touch a={} raw=({},{}) scaled=({},{}) sw={} sh={}", action, x, y, lx, ly, sw, sh)).unwrap();
|
|
364
|
+
__android_log_print(3, b"BloomTouch\0".as_ptr(), b"%s\0".as_ptr(), msg.as_ptr());
|
|
365
|
+
eng.input.set_mouse_position(lx, ly);
|
|
366
|
+
if action == 1 || action == 3 {
|
|
367
|
+
eng.input.release_touch(pointer_index as usize, lx, ly); // UP / CANCEL
|
|
368
|
+
} else {
|
|
369
|
+
eng.input.set_touch(pointer_index as usize, lx, ly, true); // DOWN / MOVE
|
|
370
|
+
}
|
|
371
|
+
match action {
|
|
372
|
+
0 => eng.input.set_mouse_button_down(0), // ACTION_DOWN
|
|
373
|
+
1 => eng.input.set_mouse_button_up(0), // ACTION_UP
|
|
374
|
+
2 => {} // ACTION_MOVE
|
|
375
|
+
_ => {}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#[no_mangle]
|
|
382
|
+
pub extern "C" fn bloom_begin_drawing() {
|
|
383
|
+
engine().begin_frame();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#[no_mangle]
|
|
387
|
+
pub extern "C" fn bloom_end_drawing() {
|
|
388
|
+
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
389
|
+
engine().end_frame();
|
|
390
|
+
}));
|
|
391
|
+
if let Err(e) = result {
|
|
392
|
+
let msg = if let Some(s) = e.downcast_ref::<&str>() {
|
|
393
|
+
s.to_string()
|
|
394
|
+
} else if let Some(s) = e.downcast_ref::<String>() {
|
|
395
|
+
s.clone()
|
|
396
|
+
} else {
|
|
397
|
+
format!("unknown panic: {:?}", e.type_id())
|
|
398
|
+
};
|
|
399
|
+
// Write to file since eprintln doesn't reach logcat on Android
|
|
400
|
+
static mut LOGGED: bool = false;
|
|
401
|
+
unsafe {
|
|
402
|
+
if !LOGGED {
|
|
403
|
+
LOGGED = true;
|
|
404
|
+
let path = resolve_path("bloom_panic.txt");
|
|
405
|
+
let _ = std::fs::write(&path, format!("end_drawing panic: {}\n", msg));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ============================================================
|
|
412
|
+
// Audio (Oboe / AAudio)
|
|
413
|
+
// ============================================================
|
|
414
|
+
|
|
415
|
+
#[no_mangle]
|
|
416
|
+
pub extern "C" fn bloom_init_audio() {
|
|
417
|
+
AUDIO_RUNNING.store(true, Ordering::SeqCst);
|
|
418
|
+
// Move the render half into the audio thread; the engine keeps only
|
|
419
|
+
// the command-producing control half.
|
|
420
|
+
let renderer = engine().audio.take_renderer();
|
|
421
|
+
std::thread::spawn(move || {
|
|
422
|
+
android_audio_thread(renderer);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
#[no_mangle]
|
|
427
|
+
pub extern "C" fn bloom_close_audio() {
|
|
428
|
+
AUDIO_RUNNING.store(false, Ordering::SeqCst);
|
|
429
|
+
std::thread::sleep(std::time::Duration::from_millis(50));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
fn android_audio_thread(renderer: Option<bloom_shared::audio::AudioRenderer>) {
|
|
433
|
+
// Use oboe (AAudio/OpenSL ES wrapper) for audio output
|
|
434
|
+
use oboe::*;
|
|
435
|
+
|
|
436
|
+
// The render half is owned by the oboe callback — AAudio invokes
|
|
437
|
+
// on_audio_ready from its own realtime thread, and the callback never
|
|
438
|
+
// touches shared engine state (see audio/mod.rs contract).
|
|
439
|
+
struct BloomAudioCallback {
|
|
440
|
+
renderer: Option<bloom_shared::audio::AudioRenderer>,
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
impl AudioOutputCallback for BloomAudioCallback {
|
|
444
|
+
type FrameType = (f32, Stereo);
|
|
445
|
+
|
|
446
|
+
fn on_audio_ready(&mut self, _stream: &mut dyn AudioOutputStreamSafe, frames: &mut [(f32, f32)]) -> DataCallbackResult {
|
|
447
|
+
// Convert frame tuples to interleaved slice
|
|
448
|
+
let len = frames.len() * 2;
|
|
449
|
+
let ptr = frames.as_mut_ptr() as *mut f32;
|
|
450
|
+
let interleaved = unsafe { std::slice::from_raw_parts_mut(ptr, len) };
|
|
451
|
+
for s in interleaved.iter_mut() { *s = 0.0; }
|
|
452
|
+
if let Some(r) = self.renderer.as_mut() {
|
|
453
|
+
r.mix(interleaved);
|
|
454
|
+
}
|
|
455
|
+
if AUDIO_RUNNING.load(Ordering::SeqCst) {
|
|
456
|
+
DataCallbackResult::Continue
|
|
457
|
+
} else {
|
|
458
|
+
DataCallbackResult::Stop
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
let mut stream = AudioStreamBuilder::default()
|
|
464
|
+
.set_performance_mode(PerformanceMode::LowLatency)
|
|
465
|
+
.set_sharing_mode(SharingMode::Shared)
|
|
466
|
+
.set_format::<f32>()
|
|
467
|
+
.set_channel_count::<Stereo>()
|
|
468
|
+
.set_sample_rate(44100)
|
|
469
|
+
.set_callback(BloomAudioCallback { renderer })
|
|
470
|
+
.open_stream();
|
|
471
|
+
|
|
472
|
+
match stream {
|
|
473
|
+
Ok(ref mut s) => {
|
|
474
|
+
let _ = s.start();
|
|
475
|
+
// Keep thread alive while audio is running
|
|
476
|
+
while AUDIO_RUNNING.load(Ordering::SeqCst) {
|
|
477
|
+
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
478
|
+
}
|
|
479
|
+
let _ = s.stop();
|
|
480
|
+
}
|
|
481
|
+
Err(_) => {}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// --- Texture FFI ---
|
|
486
|
+
|
|
487
|
+
// --- Camera FFI ---
|
|
488
|
+
|
|
489
|
+
// --- 3D Drawing FFI ---
|
|
490
|
+
|
|
491
|
+
// --- Model FFI ---
|
|
492
|
+
|
|
493
|
+
// ============================================================
|
|
494
|
+
// Phase 1c — material system FFI
|
|
495
|
+
// ============================================================
|
|
496
|
+
|
|
497
|
+
// --- Music FFI ---
|
|
498
|
+
|
|
499
|
+
// --- Gamepad FFI ---
|
|
500
|
+
|
|
501
|
+
// --- Skeletal Animation Debug ---
|
|
502
|
+
|
|
503
|
+
// --- Lighting ---
|
|
504
|
+
|
|
505
|
+
// --- Utility FFI ---
|
|
506
|
+
|
|
507
|
+
#[no_mangle]
|
|
508
|
+
pub extern "C" fn bloom_toggle_fullscreen() {}
|
|
509
|
+
#[no_mangle]
|
|
510
|
+
pub extern "C" fn bloom_set_window_title(title_ptr: *const u8) { let _ = str_from_header(title_ptr); }
|
|
511
|
+
#[no_mangle]
|
|
512
|
+
pub extern "C" fn bloom_set_window_icon(path_ptr: *const u8) { let _ = str_from_header(path_ptr); }
|
|
513
|
+
|
|
514
|
+
#[no_mangle]
|
|
515
|
+
pub extern "C" fn bloom_disable_cursor() {
|
|
516
|
+
engine().input.cursor_disabled = true;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#[no_mangle]
|
|
520
|
+
pub extern "C" fn bloom_enable_cursor() {
|
|
521
|
+
engine().input.cursor_disabled = false;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// E4: Clipboard (stub on this platform)
|
|
525
|
+
#[no_mangle]
|
|
526
|
+
pub extern "C" fn bloom_set_clipboard_text(_text_ptr: *const u8) {}
|
|
527
|
+
#[no_mangle]
|
|
528
|
+
pub extern "C" fn bloom_get_clipboard_text() -> *const u8 { std::ptr::null() }
|
|
529
|
+
|
|
530
|
+
// E5b: File dialogs (stub on this platform)
|
|
531
|
+
#[no_mangle]
|
|
532
|
+
pub extern "C" fn bloom_open_file_dialog(_filter_ptr: *const u8, _title_ptr: *const u8) -> *const u8 { std::ptr::null() }
|
|
533
|
+
#[no_mangle]
|
|
534
|
+
pub extern "C" fn bloom_save_file_dialog(_default_name_ptr: *const u8, _title_ptr: *const u8) -> *const u8 { std::ptr::null() }
|
|
535
|
+
#[no_mangle]
|
|
536
|
+
pub extern "C" fn bloom_get_platform() -> f64 { 5.0 }
|
|
537
|
+
|
|
538
|
+
/// Preferred OS language packed as `c0*256+c1` (ISO-639 primary subtag), read
|
|
539
|
+
/// from the device locale system property via the NDK (no JNI). Tries the
|
|
540
|
+
/// user-set locale first, then the factory defaults. Falls back to "en".
|
|
541
|
+
#[no_mangle]
|
|
542
|
+
pub extern "C" fn bloom_get_language() -> f64 {
|
|
543
|
+
fn parse(buf: &[u8], n: i32) -> Option<f64> {
|
|
544
|
+
if n < 2 { return None; }
|
|
545
|
+
let lc = |b: u8| if b.is_ascii_uppercase() { b + 32 } else { b };
|
|
546
|
+
let (c0, c1) = (lc(buf[0]), lc(buf[1]));
|
|
547
|
+
if c0.is_ascii_alphabetic() && c1.is_ascii_alphabetic() {
|
|
548
|
+
Some((c0 as f64) * 256.0 + (c1 as f64))
|
|
549
|
+
} else {
|
|
550
|
+
None
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
let props: [&[u8]; 3] = [
|
|
554
|
+
b"persist.sys.locale\0",
|
|
555
|
+
b"ro.product.locale\0",
|
|
556
|
+
b"ro.product.locale.language\0",
|
|
557
|
+
];
|
|
558
|
+
for prop in props {
|
|
559
|
+
let mut buf = [0u8; 92]; // PROP_VALUE_MAX
|
|
560
|
+
let n = unsafe {
|
|
561
|
+
libc::__system_property_get(
|
|
562
|
+
prop.as_ptr() as *const libc::c_char,
|
|
563
|
+
buf.as_mut_ptr() as *mut libc::c_char,
|
|
564
|
+
)
|
|
565
|
+
};
|
|
566
|
+
if let Some(v) = parse(&buf, n) { return v; }
|
|
567
|
+
}
|
|
568
|
+
25966.0
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ============================================================
|
|
572
|
+
// JNI Bridge for Bloom game applications
|
|
573
|
+
// ============================================================
|
|
574
|
+
//
|
|
575
|
+
// These functions bridge the Android Java/Kotlin layer to the
|
|
576
|
+
// Bloom engine. Any Bloom game on Android should use the
|
|
577
|
+
// com.bloomengine.game.BloomGameBridge Kotlin class.
|
|
578
|
+
|
|
579
|
+
extern "C" {
|
|
580
|
+
fn ANativeWindow_fromSurface(env: *mut libc::c_void, surface: *mut libc::c_void) -> *mut libc::c_void;
|
|
581
|
+
fn mallopt(param: i32, value: i32) -> i32;
|
|
582
|
+
fn __android_log_print(prio: i32, tag: *const u8, fmt: *const u8, ...) -> i32;
|
|
583
|
+
fn main() -> i32;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/// JNI_OnLoad: called when System.loadLibrary() loads this .so.
|
|
587
|
+
/// Disables MTE heap tagging (required for Perry NaN-boxing) and
|
|
588
|
+
/// reads the asset base path from BLOOM_ASSET_PATH env var.
|
|
589
|
+
#[no_mangle]
|
|
590
|
+
pub extern "C" fn JNI_OnLoad(_vm: *mut libc::c_void, _reserved: *mut libc::c_void) -> i32 {
|
|
591
|
+
unsafe {
|
|
592
|
+
// Disable MTE heap tagging for Perry NaN-boxing compatibility.
|
|
593
|
+
// Perry uses 48-bit pointers; Android's scudo allocator may tag
|
|
594
|
+
// the top byte, corrupting NaN-boxed pointer values.
|
|
595
|
+
mallopt(-204, 0);
|
|
596
|
+
|
|
597
|
+
__android_log_print(
|
|
598
|
+
3, b"BloomEngine\0".as_ptr(),
|
|
599
|
+
b"JNI_OnLoad: MTE disabled\0".as_ptr(),
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Read asset base path from environment (set by Activity before loadLibrary)
|
|
604
|
+
if let Ok(path) = std::env::var("BLOOM_ASSET_PATH") {
|
|
605
|
+
unsafe {
|
|
606
|
+
__android_log_print(
|
|
607
|
+
3, b"BloomEngine\0".as_ptr(),
|
|
608
|
+
b"JNI_OnLoad: asset path set\0".as_ptr(),
|
|
609
|
+
);
|
|
610
|
+
ASSET_BASE_PATH = Some(path);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
0x00010006 // JNI_VERSION_1_6
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/// Pass the Android Surface to the engine so it can create a wgpu rendering surface.
|
|
618
|
+
/// Called from BloomGameBridge.nativeSetSurface(surface).
|
|
619
|
+
#[no_mangle]
|
|
620
|
+
pub unsafe extern "C" fn Java_com_bloomengine_game_BloomGameBridge_nativeSetSurface(
|
|
621
|
+
env: *mut libc::c_void,
|
|
622
|
+
_class: *mut libc::c_void,
|
|
623
|
+
surface: *mut libc::c_void,
|
|
624
|
+
) {
|
|
625
|
+
let window = ANativeWindow_fromSurface(env, surface);
|
|
626
|
+
__android_log_print(
|
|
627
|
+
3, b"BloomEngine\0".as_ptr(),
|
|
628
|
+
b"nativeSetSurface: ANativeWindow acquired\0".as_ptr(),
|
|
629
|
+
);
|
|
630
|
+
bloom_android_set_native_window(window);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/// Run the compiled game's main() function on the game thread.
|
|
634
|
+
/// Called from BloomGameBridge.nativeMain().
|
|
635
|
+
#[no_mangle]
|
|
636
|
+
pub unsafe extern "C" fn Java_com_bloomengine_game_BloomGameBridge_nativeMain(
|
|
637
|
+
_env: *mut libc::c_void,
|
|
638
|
+
_class: *mut libc::c_void,
|
|
639
|
+
) {
|
|
640
|
+
__android_log_print(
|
|
641
|
+
3, b"BloomEngine\0".as_ptr(),
|
|
642
|
+
b"nativeMain: calling main()\0".as_ptr(),
|
|
643
|
+
);
|
|
644
|
+
main();
|
|
645
|
+
__android_log_print(
|
|
646
|
+
3, b"BloomEngine\0".as_ptr(),
|
|
647
|
+
b"nativeMain: main() returned\0".as_ptr(),
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/// Forward touch events from the Android UI thread to the engine's input system.
|
|
652
|
+
/// Called from BloomGameBridge.nativeOnTouch(action, x, y, pointerIndex).
|
|
653
|
+
#[no_mangle]
|
|
654
|
+
pub extern "C" fn Java_com_bloomengine_game_BloomGameBridge_nativeOnTouch(
|
|
655
|
+
_env: *mut libc::c_void,
|
|
656
|
+
_class: *mut libc::c_void,
|
|
657
|
+
action: i32,
|
|
658
|
+
x: f64,
|
|
659
|
+
y: f64,
|
|
660
|
+
pointer_index: i32,
|
|
661
|
+
) {
|
|
662
|
+
bloom_android_on_touch(action, x, y, pointer_index);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/// Signal the engine to close when the Activity is destroyed.
|
|
666
|
+
/// Called from BloomGameBridge.nativeOnDestroy().
|
|
667
|
+
#[no_mangle]
|
|
668
|
+
pub extern "C" fn Java_com_bloomengine_game_BloomGameBridge_nativeOnDestroy(
|
|
669
|
+
_env: *mut libc::c_void,
|
|
670
|
+
_class: *mut libc::c_void,
|
|
671
|
+
) {
|
|
672
|
+
unsafe {
|
|
673
|
+
if let Some(eng) = ENGINE.get_mut() {
|
|
674
|
+
eng.should_close = true;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// ============================================================
|
|
680
|
+
// Thread-safe staging (for async asset loading via Perry threads)
|
|
681
|
+
// ============================================================
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
// Q6: Multi-hit picking
|
|
686
|
+
// ============================================================
|
|
687
|
+
|
|
688
|
+
// ============================================================
|
|
689
|
+
// Physics (Jolt 5.x) — FFI surface generated from shared macro
|
|
690
|
+
// ============================================================
|
|
691
|
+
|
|
692
|
+
#[cfg(feature = "jolt")]
|
|
693
|
+
#[inline]
|
|
694
|
+
fn bloom_jolt_ffi_physics() -> &'static mut bloom_shared::physics_jolt::JoltPhysics {
|
|
695
|
+
&mut engine().jolt
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
#[cfg(feature = "jolt")]
|
|
699
|
+
bloom_shared::define_physics_ffi!();
|
|
700
|
+
|
|
701
|
+
// === Android FFI parity: ported from native/linux/src/lib.rs (shared renderer/scene) ===
|
|
702
|
+
// Backing statics for the ported pick/project FFI (mirror native/linux).
|