|
1 | 1 | use bloom_shared::engine::EngineState; |
2 | 2 | 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}; |
4 | 4 | use bloom_shared::audio::{parse_wav, parse_ogg, parse_mp3, SoundData}; |
5 | 5 |
|
6 | 6 | use objc2::encode::{Encode, Encoding, RefEncode}; |
@@ -3328,3 +3328,137 @@ pub extern "C" fn bloom_set_ssgi_enabled(enabled: f64) { |
3328 | 3328 | pub extern "C" fn bloom_set_ssgi_intensity(intensity: f64) { |
3329 | 3329 | engine().renderer.set_ssgi_intensity(intensity as f32); |
3330 | 3330 | } |
| 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 | +} |
0 commit comments