Skip to content

Commit 12951ab

Browse files
committed
perf: eliminate 8MB-per-call allocation in text rendering swap
TextRenderer::new() was used as a throwaway placeholder in the std::mem::replace borrow-split pattern for draw_text/draw_text_ex. Each call allocated two 1024x1024x4 atlas buffers (~8MB) plus parsed the default font, only to be immediately discarded. Add TextRenderer::empty() that allocates nothing, and use it as the swap placeholder across all platforms (macOS, iOS, tvOS, Android, Linux, Windows). This drops draw time from ~150ms to <1ms on Android. Also includes tvOS platform support and misc Cargo.lock updates.
1 parent f3a7f9f commit 12951ab

15 files changed

Lines changed: 1119 additions & 183 deletions

File tree

native/android/Cargo.lock

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

native/android/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ pub extern "C" fn bloom_draw_poly(cx: f64, cy: f64, sides: f64, radius: f64, rot
353353
pub extern "C" fn bloom_draw_text(text_ptr: *const u8, x: f64, y: f64, size: f64, r: f64, g: f64, b: f64, a: f64) {
354354
let text = str_from_header(text_ptr);
355355
let eng = engine();
356-
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::new());
356+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
357357
text_renderer.draw_text(&mut eng.renderer, text, x, y, size as u32, r, g, b, a);
358358
eng.text = text_renderer;
359359
}
@@ -379,7 +379,7 @@ pub extern "C" fn bloom_unload_font(font_handle: f64) {
379379
pub extern "C" fn bloom_draw_text_ex(font_handle: f64, text_ptr: *const u8, x: f64, y: f64, size: f64, spacing: f64, r: f64, g: f64, b: f64, a: f64) {
380380
let text = str_from_header(text_ptr);
381381
let eng = engine();
382-
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::new());
382+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
383383
text_renderer.draw_text_ex(&mut eng.renderer, font_handle as usize, text, x, y, size as u32, spacing as f32, r, g, b, a);
384384
eng.text = text_renderer;
385385
}
@@ -1028,7 +1028,7 @@ pub extern "C" fn Java_com_bloomengine_game_BloomGameBridge_nativeOnDestroy(
10281028
#[no_mangle]
10291029
pub extern "C" fn bloom_stage_texture(path_ptr: *const u8) -> f64 {
10301030
let path = str_from_header(path_ptr);
1031-
match std::fs::read(path) {
1031+
match std::fs::read(resolve_path(path)) {
10321032
Ok(data) => bloom_shared::staging::decode_and_stage_texture(&data),
10331033
Err(_) => 0.0,
10341034
}
@@ -1037,7 +1037,7 @@ pub extern "C" fn bloom_stage_texture(path_ptr: *const u8) -> f64 {
10371037
#[no_mangle]
10381038
pub extern "C" fn bloom_stage_model(path_ptr: *const u8) -> f64 {
10391039
let path = str_from_header(path_ptr);
1040-
let data = match std::fs::read(path) {
1040+
let data = match std::fs::read(resolve_path(path)) {
10411041
Ok(d) => d,
10421042
Err(_) => return 0.0,
10431043
};
@@ -1050,7 +1050,7 @@ pub extern "C" fn bloom_stage_model(path_ptr: *const u8) -> f64 {
10501050
#[no_mangle]
10511051
pub extern "C" fn bloom_stage_sound(path_ptr: *const u8) -> f64 {
10521052
let path = str_from_header(path_ptr);
1053-
let data = match std::fs::read(path) {
1053+
let data = match std::fs::read(resolve_path(path)) {
10541054
Ok(d) => d,
10551055
Err(_) => return 0.0,
10561056
};

native/ios/Cargo.lock

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

native/ios/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ pub extern "C" fn bloom_draw_poly(cx: f64, cy: f64, sides: f64, radius: f64, rot
690690
pub extern "C" fn bloom_draw_text(text_ptr: *const u8, x: f64, y: f64, size: f64, r: f64, g: f64, b: f64, a: f64) {
691691
let text = str_from_header(text_ptr);
692692
let eng = engine();
693-
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::new());
693+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
694694
text_renderer.draw_text(&mut eng.renderer, text, x, y, size as u32, r, g, b, a);
695695
eng.text = text_renderer;
696696
}
@@ -716,7 +716,7 @@ pub extern "C" fn bloom_unload_font(font_handle: f64) {
716716
pub extern "C" fn bloom_draw_text_ex(font_handle: f64, text_ptr: *const u8, x: f64, y: f64, size: f64, spacing: f64, r: f64, g: f64, b: f64, a: f64) {
717717
let text = str_from_header(text_ptr);
718718
let eng = engine();
719-
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::new());
719+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
720720
text_renderer.draw_text_ex(&mut eng.renderer, font_handle as usize, text, x, y, size as u32, spacing as f32, r, g, b, a);
721721
eng.text = text_renderer;
722722
}
@@ -1484,7 +1484,7 @@ pub extern "C" fn bloom_is_any_input_pressed() -> f64 {
14841484
#[no_mangle]
14851485
pub extern "C" fn bloom_stage_texture(path_ptr: *const u8) -> f64 {
14861486
let path = str_from_header(path_ptr);
1487-
match std::fs::read(path) {
1487+
match std::fs::read(resolve_path(path)) {
14881488
Ok(data) => bloom_shared::staging::decode_and_stage_texture(&data),
14891489
Err(_) => 0.0,
14901490
}
@@ -1493,7 +1493,7 @@ pub extern "C" fn bloom_stage_texture(path_ptr: *const u8) -> f64 {
14931493
#[no_mangle]
14941494
pub extern "C" fn bloom_stage_model(path_ptr: *const u8) -> f64 {
14951495
let path = str_from_header(path_ptr);
1496-
let data = match std::fs::read(path) {
1496+
let data = match std::fs::read(resolve_path(path)) {
14971497
Ok(d) => d,
14981498
Err(_) => return 0.0,
14991499
};
@@ -1506,7 +1506,7 @@ pub extern "C" fn bloom_stage_model(path_ptr: *const u8) -> f64 {
15061506
#[no_mangle]
15071507
pub extern "C" fn bloom_stage_sound(path_ptr: *const u8) -> f64 {
15081508
let path = str_from_header(path_ptr);
1509-
let data = match std::fs::read(path) {
1509+
let data = match std::fs::read(resolve_path(path)) {
15101510
Ok(d) => d,
15111511
Err(_) => return 0.0,
15121512
};

native/linux/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ pub extern "C" fn bloom_draw_poly(cx: f64, cy: f64, sides: f64, radius: f64, rot
398398
pub extern "C" fn bloom_draw_text(text_ptr: *const u8, x: f64, y: f64, size: f64, r: f64, g: f64, b: f64, a: f64) {
399399
let text = str_from_header(text_ptr);
400400
let eng = engine();
401-
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::new());
401+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
402402
text_renderer.draw_text(&mut eng.renderer, text, x, y, size as u32, r, g, b, a);
403403
eng.text = text_renderer;
404404
}
@@ -424,7 +424,7 @@ pub extern "C" fn bloom_unload_font(font_handle: f64) {
424424
pub extern "C" fn bloom_draw_text_ex(font_handle: f64, text_ptr: *const u8, x: f64, y: f64, size: f64, spacing: f64, r: f64, g: f64, b: f64, a: f64) {
425425
let text = str_from_header(text_ptr);
426426
let eng = engine();
427-
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::new());
427+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
428428
text_renderer.draw_text_ex(&mut eng.renderer, font_handle as usize, text, x, y, size as u32, spacing as f32, r, g, b, a);
429429
eng.text = text_renderer;
430430
}

native/macos/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

native/macos/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ objc2-app-kit = { version = "0.3", features = ["NSWindow", "NSView", "NSEvent",
1515
objc2-quartz-core = { version = "0.3", features = ["CAMetalLayer"] }
1616
raw-window-handle = "0.6"
1717
wgpu = "24"
18+
libc = "0.2"

native/macos/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ pub extern "C" fn bloom_draw_text(text_ptr: *const u8, x: f64, y: f64, size: f64
631631
let text = str_from_header(text_ptr);
632632
let eng = engine();
633633
// Need to split borrow: take text out temporarily
634-
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::new());
634+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
635635
text_renderer.draw_text(&mut eng.renderer, text, x, y, size as u32, r, g, b, a);
636636
eng.text = text_renderer;
637637
}
@@ -660,7 +660,7 @@ pub extern "C" fn bloom_unload_font(font_handle: f64) {
660660
pub extern "C" fn bloom_draw_text_ex(font_handle: f64, text_ptr: *const u8, x: f64, y: f64, size: f64, spacing: f64, r: f64, g: f64, b: f64, a: f64) {
661661
let text = str_from_header(text_ptr);
662662
let eng = engine();
663-
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::new());
663+
let mut text_renderer = std::mem::replace(&mut eng.text, bloom_shared::text_renderer::TextRenderer::empty());
664664
text_renderer.draw_text_ex(&mut eng.renderer, font_handle as usize, text, x, y, size as u32, spacing as f32, r, g, b, a);
665665
eng.text = text_renderer;
666666
}

native/shared/src/engine.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub struct EngineState {
1818
pub scene: SceneGraph,
1919
pub frame_callbacks: FrameCallbackSystem,
2020
pub postfx: Option<PostFxPipeline>,
21+
pub screenshot_pending: bool,
2122

2223
// Timing
2324
pub target_fps: f64,
@@ -45,6 +46,7 @@ impl EngineState {
4546
scene: SceneGraph::new(),
4647
frame_callbacks: FrameCallbackSystem::new(),
4748
postfx: None,
49+
screenshot_pending: false,
4850
target_fps: 60.0,
4951
delta_time: 0.0,
5052
last_frame_time: now,
@@ -79,7 +81,7 @@ impl EngineState {
7981
}
8082

8183
pub fn end_frame(&mut self) {
82-
// Prepare scene graph GPU resources before rendering
84+
// Prepare and render with scene graph
8385
self.scene.prepare(
8486
&self.renderer.device,
8587
&self.renderer.queue,

native/shared/src/text_renderer.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,30 @@ pub struct TextRenderer {
8585
}
8686

8787
impl TextRenderer {
88+
/// Cheap placeholder that allocates nothing. Used as a temporary swap target
89+
/// when we need to split borrows on EngineState (text + renderer).
90+
pub fn empty() -> Self {
91+
Self {
92+
fonts: Vec::new(),
93+
glyph_cache: HashMap::new(),
94+
atlas_data: Vec::new(),
95+
atlas_width: 0,
96+
atlas_height: 0,
97+
atlas_cursor_x: 0,
98+
atlas_cursor_y: 0,
99+
atlas_row_height: 0,
100+
atlas_bind_group_idx: None,
101+
atlas_dirty: false,
102+
sdf_glyph_cache: HashMap::new(),
103+
sdf_atlas_data: Vec::new(),
104+
sdf_atlas_cursor_x: 0,
105+
sdf_atlas_cursor_y: 0,
106+
sdf_atlas_row_height: 0,
107+
sdf_atlas_bind_group_idx: None,
108+
sdf_atlas_dirty: false,
109+
}
110+
}
111+
88112
pub fn new() -> Self {
89113
let default_font = Font::from_bytes(DEFAULT_FONT_DATA, FontSettings::default())
90114
.expect("Failed to load default font");

0 commit comments

Comments
 (0)