@bloomengine/engine 0.3.1 → 0.3.2

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.
@@ -1,6 +1,6 @@
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::{alloc_perry_string, str_from_header};
4
4
  use bloom_shared::audio::{parse_wav, parse_ogg, parse_mp3};
5
5
 
6
6
  use std::sync::OnceLock;
@@ -344,6 +344,11 @@ pub extern "C" fn bloom_init_window(width: f64, height: f64, title_ptr: *const u
344
344
  wgpu::ExperimentalFeatures::disabled()
345
345
  };
346
346
  let mut required_limits = wgpu::Limits::default();
347
+ // Phase 1c: the material ABI declares 5 bind groups (PerFrame,
348
+ // PerView, PerMaterial, PerDraw, SceneInputs). wgpu's default
349
+ // limit is 4. Metal / Vulkan / D3D12 support at least 7, so 5 is
350
+ // safely within every real backend's capabilities.
351
+ required_limits.max_bind_groups = 5;
347
352
  if required_features.intersects(rt_mask) {
348
353
  required_limits = required_limits
349
354
  .using_minimum_supported_acceleration_structure_values();
@@ -1587,24 +1592,14 @@ pub extern "C" fn bloom_file_exists(path_ptr: *const u8) -> f64 {
1587
1592
  #[no_mangle]
1588
1593
  pub extern "C" fn bloom_read_file(path_ptr: *const u8) -> *const u8 {
1589
1594
  let path = str_from_header(path_ptr);
1595
+ // Always return a valid Perry string. A null pointer would NaN-box into a
1596
+ // string-typed JS value pointing at address 0; subsequent `.length` /
1597
+ // `.charCodeAt` reads dereference the bogus StringHeader and segfault.
1598
+ // Callers detect "missing file" via `data.length === 0` (e.g. the
1599
+ // jump game's discoverLevels probe across level1..level10 / custom_*).
1590
1600
  match std::fs::read_to_string(path) {
1591
- Ok(contents) => {
1592
- // Return Perry-format string: StringHeader (length u32 + capacity u32 + refcount u32) followed by UTF-8 data
1593
- let bytes = contents.as_bytes();
1594
- let len = bytes.len();
1595
- let total = 12 + len; // 12 bytes header (3 × u32) + data
1596
- let layout = std::alloc::Layout::from_size_align(total, 4).unwrap();
1597
- unsafe {
1598
- let ptr = std::alloc::alloc(layout);
1599
- if ptr.is_null() { return std::ptr::null(); }
1600
- *(ptr as *mut u32) = len as u32; // length
1601
- *(ptr.add(4) as *mut u32) = len as u32; // capacity
1602
- *(ptr.add(8) as *mut u32) = 1; // refcount (unique)
1603
- std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(12), len);
1604
- ptr
1605
- }
1606
- }
1607
- Err(_) => std::ptr::null(),
1601
+ Ok(contents) => alloc_perry_string(&contents),
1602
+ Err(_) => alloc_perry_string(""),
1608
1603
  }
1609
1604
  }
1610
1605
 
@@ -1897,6 +1892,219 @@ pub extern "C" fn bloom_set_sss_enabled(on: f64) {
1897
1892
  engine().renderer.set_sss_enabled(on != 0.0);
1898
1893
  }
1899
1894
 
1895
+ // ============================================================
1896
+ // Render scale / upscale / DRS / post-FX / screenshots / impulse
1897
+ // Ports of the macOS / Linux FFI surface so the bloom/core TS layer
1898
+ // links cleanly on Windows. EngineState in bloom-shared already
1899
+ // exposes the underlying renderer methods, so these wrappers are
1900
+ // platform-agnostic.
1901
+ // ============================================================
1902
+
1903
+ #[no_mangle]
1904
+ pub extern "C" fn bloom_take_screenshot(path_ptr: *const u8) {
1905
+ let path = str_from_header(path_ptr).to_string();
1906
+ let eng = engine();
1907
+ eng.renderer.screenshot_requested = true;
1908
+ eng.renderer.pending_screenshot_path = Some(path);
1909
+ }
1910
+
1911
+ #[no_mangle]
1912
+ pub extern "C" fn bloom_set_env_clear_from_hdr(path_ptr: *const u8) {
1913
+ use image::ImageDecoder;
1914
+ let path = str_from_header(path_ptr).to_string();
1915
+ let file = match std::fs::File::open(&path) {
1916
+ Ok(f) => f,
1917
+ Err(_) => return,
1918
+ };
1919
+ let decoder = match image::codecs::hdr::HdrDecoder::new(std::io::BufReader::new(file)) {
1920
+ Ok(d) => d,
1921
+ Err(_) => return,
1922
+ };
1923
+ let (w, h) = decoder.dimensions();
1924
+ let byte_len = (w as usize) * (h as usize) * 3 * 4;
1925
+ let mut buf = vec![0u8; byte_len];
1926
+ if decoder.read_image(&mut buf).is_err() {
1927
+ return;
1928
+ }
1929
+ let rgb_f32: Vec<f32> = buf
1930
+ .chunks_exact(4)
1931
+ .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
1932
+ .collect();
1933
+ engine().renderer.load_env_from_hdr(w, h, &rgb_f32);
1934
+ }
1935
+
1936
+ #[no_mangle]
1937
+ pub extern "C" fn bloom_set_fog(r: f64, g: f64, b: f64, density: f64, height_ref: f64, height_falloff: f64) {
1938
+ let r_ = engine();
1939
+ r_.renderer.set_fog_color(r as f32, g as f32, b as f32);
1940
+ r_.renderer.set_fog_density(density as f32);
1941
+ r_.renderer.set_fog_height_falloff(height_ref as f32, height_falloff as f32);
1942
+ }
1943
+
1944
+ #[no_mangle]
1945
+ pub extern "C" fn bloom_set_chromatic_aberration(strength: f64) {
1946
+ engine().renderer.set_chromatic_aberration(strength as f32);
1947
+ }
1948
+
1949
+ #[no_mangle]
1950
+ pub extern "C" fn bloom_set_vignette(strength: f64, softness: f64) {
1951
+ engine().renderer.set_vignette(strength as f32, softness as f32);
1952
+ }
1953
+
1954
+ #[no_mangle]
1955
+ pub extern "C" fn bloom_set_film_grain(strength: f64) {
1956
+ engine().renderer.set_film_grain(strength as f32);
1957
+ }
1958
+
1959
+ #[no_mangle]
1960
+ pub extern "C" fn bloom_set_sun_shafts(strength: f64, decay: f64, r: f64, g: f64, b: f64) {
1961
+ let eng = engine();
1962
+ eng.renderer.set_sun_shaft_strength(strength as f32);
1963
+ eng.renderer.set_sun_shaft_decay(decay as f32);
1964
+ eng.renderer.set_sun_shaft_color(r as f32, g as f32, b as f32);
1965
+ }
1966
+
1967
+ #[no_mangle]
1968
+ pub extern "C" fn bloom_set_auto_exposure(on: f64) {
1969
+ engine().renderer.set_auto_exposure(on != 0.0);
1970
+ }
1971
+
1972
+ #[no_mangle]
1973
+ pub extern "C" fn bloom_set_taa_enabled(on: f64) {
1974
+ engine().renderer.set_taa_enabled(on != 0.0);
1975
+ }
1976
+
1977
+ #[no_mangle]
1978
+ pub extern "C" fn bloom_set_render_scale(scale: f64) {
1979
+ engine().renderer.set_render_scale(scale as f32);
1980
+ }
1981
+
1982
+ #[no_mangle]
1983
+ pub extern "C" fn bloom_get_render_scale() -> f64 {
1984
+ engine().renderer.render_scale() as f64
1985
+ }
1986
+
1987
+ #[no_mangle]
1988
+ pub extern "C" fn bloom_set_upscale_mode(mode: f64) {
1989
+ engine().renderer.set_upscale_mode(mode as u32);
1990
+ }
1991
+
1992
+ #[no_mangle]
1993
+ pub extern "C" fn bloom_set_cas_strength(strength: f64) {
1994
+ engine().renderer.set_cas_strength(strength as f32);
1995
+ }
1996
+
1997
+ #[no_mangle]
1998
+ pub extern "C" fn bloom_get_physical_width() -> f64 {
1999
+ engine().renderer.physical_width() as f64
2000
+ }
2001
+
2002
+ #[no_mangle]
2003
+ pub extern "C" fn bloom_get_physical_height() -> f64 {
2004
+ engine().renderer.physical_height() as f64
2005
+ }
2006
+
2007
+ #[no_mangle]
2008
+ pub extern "C" fn bloom_set_auto_resolution(target_hz: f64, enabled: f64) {
2009
+ let eng = engine();
2010
+ if enabled != 0.0 {
2011
+ let current = eng.renderer.render_scale();
2012
+ eng.drs.enable(target_hz as f32, current);
2013
+ } else {
2014
+ eng.drs.disable();
2015
+ }
2016
+ }
2017
+
2018
+ #[no_mangle]
2019
+ pub extern "C" fn bloom_set_manual_exposure(value: f64) {
2020
+ engine().renderer.set_manual_exposure(value as f32);
2021
+ }
2022
+
2023
+ #[no_mangle]
2024
+ pub extern "C" fn bloom_set_env_intensity(intensity: f64) {
2025
+ engine().renderer.set_env_intensity(intensity as f32);
2026
+ }
2027
+
2028
+ #[no_mangle]
2029
+ pub extern "C" fn bloom_set_ssgi_enabled(enabled: f64) {
2030
+ engine().renderer.set_ssgi_enabled(enabled != 0.0);
2031
+ }
2032
+
2033
+ #[no_mangle]
2034
+ pub extern "C" fn bloom_set_ssgi_intensity(intensity: f64) {
2035
+ engine().renderer.set_ssgi_intensity(intensity as f32);
2036
+ }
2037
+
2038
+ #[no_mangle]
2039
+ pub extern "C" fn bloom_set_ssgi_radius(radius: f64) {
2040
+ engine().renderer.set_ssgi_radius(radius as f32);
2041
+ }
2042
+
2043
+ #[no_mangle]
2044
+ pub extern "C" fn bloom_set_dof(enabled: f64, focus_distance: f64, aperture: f64) {
2045
+ let r = &mut engine().renderer;
2046
+ r.set_dof_enabled(enabled != 0.0);
2047
+ r.set_dof_focus_distance(focus_distance as f32);
2048
+ r.set_dof_aperture(aperture as f32);
2049
+ }
2050
+
2051
+ #[no_mangle]
2052
+ pub extern "C" fn bloom_splat_impulse(x: f64, z: f64, radius: f64, strength: f64) {
2053
+ engine().renderer.impulse_field.submit_splat(
2054
+ x as f32, z as f32, radius as f32, strength as f32,
2055
+ );
2056
+ }
2057
+
2058
+ // Render texture FFI (stub — GPU implementation deferred).
2059
+ #[no_mangle]
2060
+ pub extern "C" fn bloom_load_render_texture(width: f64, height: f64) -> f64 {
2061
+ engine().textures.load_render_texture(width as u32, height as u32)
2062
+ }
2063
+ #[no_mangle]
2064
+ pub extern "C" fn bloom_unload_render_texture(handle: f64) {
2065
+ engine().textures.unload_render_texture(handle);
2066
+ }
2067
+ #[no_mangle]
2068
+ pub extern "C" fn bloom_begin_texture_mode(_handle: f64) {
2069
+ // Stub: no-op until GPU render-to-texture is wired.
2070
+ }
2071
+ #[no_mangle]
2072
+ pub extern "C" fn bloom_end_texture_mode() {
2073
+ // Stub: no-op.
2074
+ }
2075
+ #[no_mangle]
2076
+ pub extern "C" fn bloom_get_render_texture_texture(handle: f64) -> f64 {
2077
+ engine().textures.get_render_texture_texture(handle)
2078
+ }
2079
+
2080
+ #[no_mangle]
2081
+ pub extern "C" fn bloom_profiler_frame_history() -> *const u8 {
2082
+ let hist = engine().profiler.frame_history();
2083
+ let mut s = String::with_capacity(hist.len() * 24);
2084
+ for (cpu, gpu) in &hist {
2085
+ s.push_str(&format!("{:.2}|{:.2}\n", cpu, gpu));
2086
+ }
2087
+ alloc_perry_string(&s)
2088
+ }
2089
+
2090
+ #[no_mangle]
2091
+ pub extern "C" fn bloom_profiler_overlay_text() -> *const u8 {
2092
+ let snap = engine().profiler.snapshot();
2093
+ let mut s = String::with_capacity(snap.len() * 48);
2094
+ for (label, cpu, gpu) in &snap {
2095
+ s.push_str(label);
2096
+ s.push('|');
2097
+ s.push_str(&format!("{:.2}", cpu));
2098
+ s.push('|');
2099
+ match gpu {
2100
+ Some(g) => s.push_str(&format!("{:.2}", g)),
2101
+ None => s.push_str("-1"),
2102
+ }
2103
+ s.push('\n');
2104
+ }
2105
+ alloc_perry_string(&s)
2106
+ }
2107
+
1900
2108
  // ============================================================
1901
2109
  // Profiler — CPU phase timings (always available) + GPU timestamps
1902
2110
  // (when the adapter supports TIMESTAMP_QUERY). Disabled by default.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloomengine/engine",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Bloom Engine: native TypeScript game engine compiled by Perry",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -203,7 +203,7 @@
203
203
  { "name": "bloom_clear_all_post_passes", "params": [], "returns": "void" },
204
204
  { "name": "bloom_draw_material", "params": ["f64", "f64", "f64", "f64", "f64", "f64", "f64", "f64", "f64", "f64", "f64"], "returns": "void" },
205
205
  { "name": "bloom_load_model_animation", "params": ["i64"], "returns": "f64" },
206
- { "name": "bloom_update_model_animation", "params": ["f64", "f64", "f64", "f64", "f64", "f64", "f64"], "returns": "void" },
206
+ { "name": "bloom_update_model_animation", "params": ["f64", "f64", "f64", "f64", "f64", "f64", "f64", "f64"], "returns": "void" },
207
207
  { "name": "bloom_create_mesh", "params": ["i64", "f64", "i64", "f64"], "returns": "f64" },
208
208
  { "name": "bloom_set_joint_test", "params": ["f64", "f64"], "returns": "void" },
209
209
  { "name": "bloom_set_ambient_light", "params": ["f64", "f64", "f64", "f64"], "returns": "void" },
@@ -287,7 +287,7 @@
287
287
  { "name": "bloom_get_model_bounds_max_z", "params": ["f64"], "returns": "f64" },
288
288
  { "name": "bloom_write_file", "params": ["i64", "i64"], "returns": "f64" },
289
289
  { "name": "bloom_file_exists", "params": ["i64"], "returns": "f64" },
290
- { "name": "bloom_read_file", "params": ["i64"], "returns": "i64" },
290
+ { "name": "bloom_read_file", "params": ["i64"], "returns": "string" },
291
291
  { "name": "bloom_get_touch_x", "params": ["f64"], "returns": "f64" },
292
292
  { "name": "bloom_get_touch_y", "params": ["f64"], "returns": "f64" },
293
293
  { "name": "bloom_get_touch_count", "params": [], "returns": "f64" },
@@ -315,9 +315,9 @@
315
315
  { "name": "bloom_scene_node_index_count", "params": ["f64"], "returns": "f64" },
316
316
  { "name": "bloom_set_cursor_shape", "params": ["f64"], "returns": "void" },
317
317
  { "name": "bloom_set_clipboard_text", "params": ["i64"], "returns": "void" },
318
- { "name": "bloom_get_clipboard_text", "params": [], "returns": "i64" },
319
- { "name": "bloom_open_file_dialog", "params": ["i64", "i64"], "returns": "i64" },
320
- { "name": "bloom_save_file_dialog", "params": ["i64", "i64"], "returns": "i64" },
318
+ { "name": "bloom_get_clipboard_text", "params": [], "returns": "string" },
319
+ { "name": "bloom_open_file_dialog", "params": ["i64", "i64"], "returns": "string" },
320
+ { "name": "bloom_save_file_dialog", "params": ["i64", "i64"], "returns": "string" },
321
321
  { "name": "bloom_scene_pick_all", "params": ["f64", "f64", "f64"], "returns": "f64" },
322
322
  { "name": "bloom_pick_all_handle", "params": ["f64"], "returns": "f64" },
323
323
  { "name": "bloom_pick_all_distance", "params": ["f64"], "returns": "f64" },
@@ -538,7 +538,8 @@
538
538
  },
539
539
  "linux": {
540
540
  "crate": "native/linux/",
541
- "lib": "libbloom_linux.a",
541
+ "lib": "libbloom_linux_bundled.a",
542
+ "libs": ["stdc++"],
542
543
  "pkgConfig": ["x11", "xi", "alsa"]
543
544
  },
544
545
  "android": {
@@ -1,6 +1,10 @@
1
- import { Color } from './types';
1
+ import { Color as ColorType } from './types';
2
2
 
3
- export const ColorConstants: Record<string, Color> = {
3
+ // Canonical color palette. Re-exported as `Color` from `bloom/core` and
4
+ // declared as a real top-level binding (not an alias re-export) so Perry
5
+ // emits a `_perry_fn_src_core_colors_ts__Color` symbol that examples
6
+ // importing `Color` from `bloom/core` can link against.
7
+ export const Color: Record<string, ColorType> = {
4
8
  RayWhite: { r: 245, g: 245, b: 245, a: 255 },
5
9
  White: { r: 255, g: 255, b: 255, a: 255 },
6
10
  Black: { r: 0, g: 0, b: 0, a: 255 },
@@ -27,30 +31,33 @@ export const ColorConstants: Record<string, Color> = {
27
31
  Blank: { r: 0, g: 0, b: 0, a: 0 },
28
32
  };
29
33
 
34
+ // Backward-compatible alias — same object, kept for older imports.
35
+ export const ColorConstants = Color;
36
+
30
37
  // Backward-compatible alias with SCREAMING_SNAKE keys
31
- export const Colors: Record<string, Color> = {
32
- WHITE: ColorConstants.White,
33
- BLACK: ColorConstants.Black,
34
- RED: ColorConstants.Red,
35
- GREEN: ColorConstants.Green,
36
- BLUE: ColorConstants.Blue,
37
- YELLOW: ColorConstants.Yellow,
38
- ORANGE: ColorConstants.Orange,
39
- PINK: ColorConstants.Pink,
40
- PURPLE: ColorConstants.Purple,
41
- DARKGRAY: ColorConstants.DarkGray,
42
- LIGHTGRAY: ColorConstants.LightGray,
43
- GRAY: ColorConstants.Gray,
44
- DARKBLUE: ColorConstants.DarkBlue,
45
- SKYBLUE: ColorConstants.SkyBlue,
46
- LIME: ColorConstants.Lime,
47
- DARKGREEN: ColorConstants.DarkGreen,
48
- GOLD: ColorConstants.Gold,
49
- MAROON: ColorConstants.Maroon,
50
- BROWN: ColorConstants.Brown,
51
- BEIGE: ColorConstants.Beige,
52
- MAGENTA: ColorConstants.Magenta,
53
- VIOLET: ColorConstants.Violet,
54
- RAYWHITE: ColorConstants.RayWhite,
55
- BLANK: ColorConstants.Blank,
38
+ export const Colors: Record<string, ColorType> = {
39
+ WHITE: Color.White,
40
+ BLACK: Color.Black,
41
+ RED: Color.Red,
42
+ GREEN: Color.Green,
43
+ BLUE: Color.Blue,
44
+ YELLOW: Color.Yellow,
45
+ ORANGE: Color.Orange,
46
+ PINK: Color.Pink,
47
+ PURPLE: Color.Purple,
48
+ DARKGRAY: Color.DarkGray,
49
+ LIGHTGRAY: Color.LightGray,
50
+ GRAY: Color.Gray,
51
+ DARKBLUE: Color.DarkBlue,
52
+ SKYBLUE: Color.SkyBlue,
53
+ LIME: Color.Lime,
54
+ DARKGREEN: Color.DarkGreen,
55
+ GOLD: Color.Gold,
56
+ MAROON: Color.Maroon,
57
+ BROWN: Color.Brown,
58
+ BEIGE: Color.Beige,
59
+ MAGENTA: Color.Magenta,
60
+ VIOLET: Color.Violet,
61
+ RAYWHITE: Color.RayWhite,
62
+ BLANK: Color.Blank,
56
63
  };
package/src/core/index.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { Color, Camera2D, Camera3D } from './types';
2
2
 
3
3
  export type { Color, Vec2, Vec3, Vec4, Rect, Camera2D, Camera3D, Texture, Font, Sound, Music, Quat, Ray, BoundingBox, Model, Mat4, RayHit, FrustumPlanes } from './types';
4
- export { ColorConstants, Colors } from './colors';
5
- export { ColorConstants as Color } from './colors';
4
+ export { Color, ColorConstants, Colors } from './colors';
6
5
  export { Key, MouseButton } from './keys';
7
6
 
8
7
  // FFI declarations