Skip to content

Commit 4af46ac

Browse files
committed
fix(tvos): port 10 missing bloom_* FFI exports for parity with macOS
The tvOS native crate was missing render-texture (begin/end_texture_mode, load/unload/get_render_texture_texture), profiler (frame_history, overlay_text), and post-FX (set_dof, set_ssgi_radius, splat_impulse) functions that the app references → undefined symbols at link. Ported the macOS implementations verbatim (same shared renderer/EngineState) and added alloc_perry_string to the string_header import. Verified: tvOS crate compiles for aarch64-apple-tvos and a full app link leaves 0 undefined bloom_* symbols (only libc++, which the builder's cross-tvOS link already supplies).
1 parent 3b751ad commit 4af46ac

2 files changed

Lines changed: 136 additions & 2 deletions

File tree

native/tvos/src/lib.rs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use bloom_shared::engine::EngineState;
22
use bloom_shared::renderer::Renderer;
3-
use bloom_shared::string_header::str_from_header;
3+
use bloom_shared::string_header::{str_from_header, alloc_perry_string};
44
use bloom_shared::audio::{parse_wav, parse_ogg, parse_mp3, SoundData};
55

66
use objc2::encode::{Encode, Encoding, RefEncode};
@@ -3328,3 +3328,137 @@ pub extern "C" fn bloom_set_ssgi_enabled(enabled: f64) {
33283328
pub extern "C" fn bloom_set_ssgi_intensity(intensity: f64) {
33293329
engine().renderer.set_ssgi_intensity(intensity as f32);
33303330
}
3331+
3332+
// ── Ported from native/macos for tvOS FFI parity (render textures, profiler, DOF/SSGI, splat) ──
3333+
#[no_mangle]
3334+
pub extern "C" fn bloom_begin_texture_mode(handle: f64) {
3335+
let eng = engine();
3336+
let (w, h, bg_idx) = match eng.textures.render_textures.get(handle) {
3337+
Some(rt) => {
3338+
let tex_handle = rt.texture_handle;
3339+
match eng.textures.textures.get(tex_handle) {
3340+
Some(td) => (rt.width, rt.height, td.bind_group_idx as usize),
3341+
None => return,
3342+
}
3343+
}
3344+
None => return,
3345+
};
3346+
if let Some(texture) = eng.renderer.get_texture_ref(bg_idx) {
3347+
// We need to call begin_texture_mode with a reference to the texture,
3348+
// but get_texture_ref borrows renderer immutably. Clone the texture view
3349+
// data we need first, then call the mutable method.
3350+
let color_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
3351+
// Create depth texture for this RT.
3352+
let depth_tex = eng.renderer.device.create_texture(&wgpu::TextureDescriptor {
3353+
label: Some("rt_depth"), size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
3354+
mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2,
3355+
format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3356+
view_formats: &[],
3357+
});
3358+
let depth_view = depth_tex.create_view(&wgpu::TextureViewDescriptor::default());
3359+
eng.renderer.rt_color_view = Some(color_view);
3360+
eng.renderer.rt_depth_view = Some(depth_view);
3361+
eng.renderer.rt_depth_texture = Some(depth_tex);
3362+
eng.renderer.rt_width = w;
3363+
eng.renderer.rt_height = h;
3364+
}
3365+
}
3366+
3367+
#[no_mangle]
3368+
pub extern "C" fn bloom_end_texture_mode() {
3369+
engine().renderer.end_texture_mode();
3370+
}
3371+
3372+
#[no_mangle]
3373+
pub extern "C" fn bloom_get_render_texture_texture(handle: f64) -> f64 {
3374+
engine().textures.get_render_texture_texture(handle)
3375+
}
3376+
3377+
// Q1: Render texture FFI — create GPU textures for render-to-texture.
3378+
#[no_mangle]
3379+
pub extern "C" fn bloom_load_render_texture(width: f64, height: f64) -> f64 {
3380+
let w = width as u32;
3381+
let h = height as u32;
3382+
let eng = engine();
3383+
let rt_handle = eng.textures.load_render_texture(w, h);
3384+
3385+
// Create the GPU texture via the renderer's public method.
3386+
let (bind_group_idx, _tex_vec_idx) = eng.renderer.create_render_texture(w, h);
3387+
3388+
// Register as a texture handle so drawTexture can sample it.
3389+
let tex_handle = eng.textures.textures.alloc(bloom_shared::textures::TextureData {
3390+
bind_group_idx, width: w, height: h,
3391+
});
3392+
eng.textures.set_render_texture_handle(rt_handle, tex_handle);
3393+
3394+
rt_handle
3395+
}
3396+
3397+
#[no_mangle]
3398+
pub extern "C" fn bloom_unload_render_texture(handle: f64) {
3399+
engine().textures.unload_render_texture(handle);
3400+
}
3401+
3402+
/// Phase 8 — frame history for the histogram. Header-prefixed string,
3403+
/// one line per frame "<cpu_us>|<gpu_us>", oldest first, up to 120
3404+
/// lines.
3405+
#[no_mangle]
3406+
pub extern "C" fn bloom_profiler_frame_history() -> *const u8 {
3407+
let hist = engine().profiler.frame_history();
3408+
let mut s = String::with_capacity(hist.len() * 24);
3409+
for (cpu, gpu) in &hist {
3410+
s.push_str(&format!("{:.2}|{:.2}\n", cpu, gpu));
3411+
}
3412+
alloc_perry_string(&s)
3413+
}
3414+
3415+
#[no_mangle]
3416+
pub extern "C" fn bloom_profiler_overlay_text() -> *const u8 {
3417+
let snap = engine().profiler.snapshot();
3418+
let mut s = String::with_capacity(snap.len() * 48);
3419+
for (label, cpu, gpu) in &snap {
3420+
s.push_str(label);
3421+
s.push('|');
3422+
s.push_str(&format!("{:.2}", cpu));
3423+
s.push('|');
3424+
match gpu {
3425+
Some(g) => s.push_str(&format!("{:.2}", g)),
3426+
None => s.push_str("-1"),
3427+
}
3428+
s.push('\n');
3429+
}
3430+
alloc_perry_string(&s)
3431+
}
3432+
3433+
#[no_mangle]
3434+
pub extern "C" fn bloom_set_dof(enabled: f64, focus_distance: f64, aperture: f64) {
3435+
let r = &mut engine().renderer;
3436+
r.set_dof_enabled(enabled != 0.0);
3437+
r.set_dof_focus_distance(focus_distance as f32);
3438+
r.set_dof_aperture(aperture as f32);
3439+
}
3440+
3441+
#[no_mangle]
3442+
pub extern "C" fn bloom_set_ssgi_radius(radius: f64) {
3443+
engine().renderer.set_ssgi_radius(radius as f32);
3444+
}
3445+
3446+
/// Phase 8 — formatted per-pass overlay text. Returns a Perry-style
3447+
/// header-prefixed string (12-byte header: len, cap, flags, then
3448+
/// UTF-8 bytes) with one line per pass sorted by CPU time descending:
3449+
///
3450+
/// "<label>|<cpu_us>|<gpu_us_or_-1>\n..."
3451+
///
3452+
/// The TS overlay splits on \n then | per line. Games call this once
3453+
/// per overlay-draw frame.
3454+
/// Phase 7 — submit a world-space impulse. The renderer's per-frame
3455+
/// compute pass decays + accumulates submissions into a 256×256
3456+
/// R32Float texture covering a 128 m centred square; materials that
3457+
/// sample `impulse_tex` (group 4 binding 4) read the result. Up to 16
3458+
/// splats per frame are accepted — overflow is dropped silently.
3459+
#[no_mangle]
3460+
pub extern "C" fn bloom_splat_impulse(x: f64, z: f64, radius: f64, strength: f64) {
3461+
engine().renderer.impulse_field.submit_splat(
3462+
x as f32, z as f32, radius as f32, strength as f32,
3463+
);
3464+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@bloomengine/engine",
3-
"version": "0.4.9",
3+
"version": "0.4.10",
44
"description": "Bloom Engine: native TypeScript game engine compiled by Perry",
55
"main": "src/index.ts",
66
"types": "src/index.ts",

0 commit comments

Comments
 (0)