1use std::cell::RefCell;
2use std::path::PathBuf;
3use std::rc::Rc;
4
5use deno_core::OpState;
6
7use crate::renderer::SpriteCommand;
8use crate::renderer::TilemapStore;
9use crate::renderer::PointLight;
10use crate::renderer::camera::CameraBounds;
11use crate::renderer::msdf::MsdfFontStore;
12
13#[derive(Clone, Debug)]
15pub enum BridgeAudioCommand {
16 LoadSound { id: u32, path: String },
17 StopAll,
18 SetMasterVolume { volume: f32 },
19
20 PlaySoundEx {
22 sound_id: u32,
23 instance_id: u64,
24 volume: f32,
25 looping: bool,
26 bus: u32,
27 pan: f32,
28 pitch: f32,
29 low_pass_freq: u32,
30 reverb_mix: f32,
31 reverb_delay_ms: u32,
32 },
33 PlaySoundSpatial {
34 sound_id: u32,
35 instance_id: u64,
36 volume: f32,
37 looping: bool,
38 bus: u32,
39 pitch: f32,
40 source_x: f32,
41 source_y: f32,
42 listener_x: f32,
43 listener_y: f32,
44 },
45 StopInstance { instance_id: u64 },
46 SetInstanceVolume { instance_id: u64, volume: f32 },
47 SetInstancePitch { instance_id: u64, pitch: f32 },
48 UpdateSpatialPositions {
49 updates: Vec<(u64, f32, f32)>, listener_x: f32,
51 listener_y: f32,
52 },
53 SetBusVolume { bus: u32, volume: f32 },
54}
55
56#[derive(Clone)]
59pub struct RenderBridgeState {
60 pub sprite_commands: Vec<SpriteCommand>,
61 pub camera_x: f32,
62 pub camera_y: f32,
63 pub camera_zoom: f32,
64 pub camera_dirty: bool,
66 pub delta_time: f64,
67 pub elapsed_time: f64,
69 pub keys_down: std::collections::HashSet<String>,
71 pub keys_pressed: std::collections::HashSet<String>,
72 pub mouse_x: f32,
73 pub mouse_y: f32,
74 pub mouse_buttons_down: std::collections::HashSet<u8>,
75 pub mouse_buttons_pressed: std::collections::HashSet<u8>,
76 pub gamepad_buttons_down: std::collections::HashSet<String>,
78 pub gamepad_buttons_pressed: std::collections::HashSet<String>,
80 pub gamepad_axes: std::collections::HashMap<String, f32>,
82 pub gamepad_count: u32,
84 pub gamepad_name: String,
86 pub touch_points: Vec<(u64, f32, f32)>,
88 pub touch_count: u32,
90 pub texture_load_queue: Vec<(String, u32)>,
92 pub texture_load_queue_linear: Vec<(String, u32)>,
94 pub base_dir: PathBuf,
96 pub next_texture_id: u32,
98 pub texture_path_to_id: std::collections::HashMap<String, u32>,
100 pub tilemaps: TilemapStore,
102 pub ambient_light: [f32; 3],
104 pub point_lights: Vec<PointLight>,
106 pub audio_commands: Vec<BridgeAudioCommand>,
108 pub next_sound_id: u32,
110 pub sound_path_to_id: std::collections::HashMap<String, u32>,
112 pub font_texture_queue: Vec<u32>,
114 pub viewport_width: f32,
116 pub viewport_height: f32,
117 pub scale_factor: f32,
119 pub clear_color: [f32; 4],
121 pub save_dir: PathBuf,
123 pub shader_create_queue: Vec<(u32, String, String)>,
125 pub shader_param_queue: Vec<(u32, u32, [f32; 4])>,
127 pub next_shader_id: u32,
129 pub effect_create_queue: Vec<(u32, String)>,
131 pub effect_param_queue: Vec<(u32, u32, [f32; 4])>,
133 pub effect_remove_queue: Vec<u32>,
135 pub effect_clear: bool,
137 pub next_effect_id: u32,
139 pub camera_bounds: Option<CameraBounds>,
141 pub gi_enabled: bool,
143 pub gi_intensity: f32,
145 pub gi_probe_spacing: Option<f32>,
147 pub gi_interval: Option<f32>,
149 pub gi_cascade_count: Option<u32>,
151 pub emissives: Vec<[f32; 8]>,
153 pub occluders: Vec<[f32; 4]>,
155 pub directional_lights: Vec<[f32; 5]>,
157 pub spot_lights: Vec<[f32; 9]>,
159 pub msdf_fonts: MsdfFontStore,
161 pub msdf_builtin_queue: Vec<(u32, u32)>,
163 pub msdf_shader_queue: Vec<(u32, String)>,
165 pub msdf_shader_pool: Vec<u32>,
167 pub msdf_texture_load_queue: Vec<(String, u32)>,
169 pub raw_texture_upload_queue: Vec<(u32, u32, u32, Vec<u8>)>,
171 pub frame_time_ms: f64,
173 pub draw_call_count: usize,
175}
176
177impl RenderBridgeState {
178 pub fn new(base_dir: PathBuf) -> Self {
179 let save_dir = base_dir.join(".arcane").join("saves");
180 Self {
181 sprite_commands: Vec::new(),
182 camera_x: 0.0,
183 camera_y: 0.0,
184 camera_zoom: 1.0,
185 camera_dirty: false,
186 delta_time: 0.0,
187 elapsed_time: 0.0,
188 keys_down: std::collections::HashSet::new(),
189 keys_pressed: std::collections::HashSet::new(),
190 mouse_x: 0.0,
191 mouse_y: 0.0,
192 mouse_buttons_down: std::collections::HashSet::new(),
193 mouse_buttons_pressed: std::collections::HashSet::new(),
194 gamepad_buttons_down: std::collections::HashSet::new(),
195 gamepad_buttons_pressed: std::collections::HashSet::new(),
196 gamepad_axes: std::collections::HashMap::new(),
197 gamepad_count: 0,
198 gamepad_name: String::new(),
199 touch_points: Vec::new(),
200 touch_count: 0,
201 texture_load_queue: Vec::new(),
202 texture_load_queue_linear: Vec::new(),
203 base_dir,
204 next_texture_id: 1,
205 texture_path_to_id: std::collections::HashMap::new(),
206 tilemaps: TilemapStore::new(),
207 ambient_light: [1.0, 1.0, 1.0],
208 point_lights: Vec::new(),
209 audio_commands: Vec::new(),
210 next_sound_id: 1,
211 sound_path_to_id: std::collections::HashMap::new(),
212 font_texture_queue: Vec::new(),
213 viewport_width: 800.0,
214 viewport_height: 600.0,
215 scale_factor: 1.0,
216 clear_color: [0.1, 0.1, 0.15, 1.0],
217 save_dir,
218 shader_create_queue: Vec::new(),
219 shader_param_queue: Vec::new(),
220 next_shader_id: 1,
221 effect_create_queue: Vec::new(),
222 effect_param_queue: Vec::new(),
223 effect_remove_queue: Vec::new(),
224 effect_clear: false,
225 next_effect_id: 1,
226 camera_bounds: None,
227 gi_enabled: false,
228 gi_intensity: 1.0,
229 gi_probe_spacing: None,
230 gi_interval: None,
231 gi_cascade_count: None,
232 emissives: Vec::new(),
233 occluders: Vec::new(),
234 directional_lights: Vec::new(),
235 spot_lights: Vec::new(),
236 msdf_fonts: MsdfFontStore::new(),
237 msdf_builtin_queue: Vec::new(),
238 msdf_shader_queue: Vec::new(),
239 msdf_shader_pool: Vec::new(),
240 msdf_texture_load_queue: Vec::new(),
241 raw_texture_upload_queue: Vec::new(),
242 frame_time_ms: 0.0,
243 draw_call_count: 0,
244 }
245 }
246}
247
248#[deno_core::op2(fast)]
250pub fn op_clear_sprites(state: &mut OpState) {
251 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
252 bridge.borrow_mut().sprite_commands.clear();
253}
254
255pub const SPRITE_STRIDE: usize = 22;
260
261#[deno_core::op2(fast)]
265pub fn op_submit_sprite_batch(state: &mut OpState, #[buffer] data: &[u8]) {
266 let floats: &[f32] = bytemuck::cast_slice(data);
267 let sprite_count = floats.len() / SPRITE_STRIDE;
268
269 let active_target = {
271 use super::target_ops::TargetState;
272 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
273 ts.borrow().active_target
274 };
275
276 let parse_cmd = |s: &[f32]| SpriteCommand {
277 texture_id: s[0].to_bits(),
278 x: s[1],
279 y: s[2],
280 w: s[3],
281 h: s[4],
282 layer: s[5].to_bits() as i32,
283 uv_x: s[6],
284 uv_y: s[7],
285 uv_w: s[8],
286 uv_h: s[9],
287 tint_r: s[10],
288 tint_g: s[11],
289 tint_b: s[12],
290 tint_a: s[13],
291 rotation: s[14],
292 origin_x: s[15],
293 origin_y: s[16],
294 flip_x: s[17] != 0.0,
295 flip_y: s[18] != 0.0,
296 opacity: s[19],
297 blend_mode: (s[20] as u8).min(3),
298 shader_id: s[21].to_bits(),
299 };
300
301 if let Some(target_id) = active_target {
302 use super::target_ops::TargetState;
303 let ts = state.borrow::<Rc<RefCell<TargetState>>>();
304 let mut ts = ts.borrow_mut();
305 let queue = ts.target_sprite_queues.entry(target_id).or_default();
306 queue.reserve(sprite_count);
307 for i in 0..sprite_count {
308 let base = i * SPRITE_STRIDE;
309 queue.push(parse_cmd(&floats[base..base + SPRITE_STRIDE]));
310 }
311 } else {
312 let bridge = state.borrow::<Rc<RefCell<RenderBridgeState>>>();
313 let mut b = bridge.borrow_mut();
314 b.sprite_commands.reserve(sprite_count);
315 for i in 0..sprite_count {
316 let base = i * SPRITE_STRIDE;
317 b.sprite_commands.push(parse_cmd(&floats[base..base + SPRITE_STRIDE]));
318 }
319 }
320}
321
322#[deno_core::op2(fast)]
325pub fn op_set_camera(state: &mut OpState, x: f64, y: f64, zoom: f64) {
326 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
327 let mut b = bridge.borrow_mut();
328 b.camera_x = x as f32;
329 b.camera_y = y as f32;
330 b.camera_zoom = zoom as f32;
331 b.camera_dirty = true;
332}
333
334#[deno_core::op2]
336#[serde]
337pub fn op_get_camera(state: &mut OpState) -> Vec<f64> {
338 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
339 let b = bridge.borrow();
340 vec![b.camera_x as f64, b.camera_y as f64, b.camera_zoom as f64]
341}
342
343#[deno_core::op2(fast)]
346pub fn op_load_texture(state: &mut OpState, #[string] path: &str) -> u32 {
347 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
348 let mut b = bridge.borrow_mut();
349
350 let resolved = if std::path::Path::new(path).is_absolute() {
352 path.to_string()
353 } else {
354 b.base_dir.join(path).to_string_lossy().to_string()
355 };
356
357 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
359 return id;
360 }
361
362 let id = b.next_texture_id;
363 b.next_texture_id += 1;
364 b.texture_path_to_id.insert(resolved.clone(), id);
365 b.texture_load_queue.push((resolved, id));
366 id
367}
368
369#[deno_core::op2(fast)]
373pub fn op_load_texture_linear(state: &mut OpState, #[string] path: &str) -> u32 {
374 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
375 let mut b = bridge.borrow_mut();
376
377 let resolved = if std::path::Path::new(path).is_absolute() {
379 path.to_string()
380 } else {
381 b.base_dir.join(path).to_string_lossy().to_string()
382 };
383
384 if let Some(&id) = b.texture_path_to_id.get(&resolved) {
386 return id;
387 }
388
389 let id = b.next_texture_id;
390 b.next_texture_id += 1;
391 b.texture_path_to_id.insert(resolved.clone(), id);
392 b.texture_load_queue_linear.push((resolved, id));
393 id
394}
395
396#[deno_core::op2(fast)]
398pub fn op_is_key_down(state: &mut OpState, #[string] key: &str) -> bool {
399 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
400 bridge.borrow().keys_down.contains(key)
401}
402
403#[deno_core::op2(fast)]
405pub fn op_is_key_pressed(state: &mut OpState, #[string] key: &str) -> bool {
406 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
407 bridge.borrow().keys_pressed.contains(key)
408}
409
410#[deno_core::op2]
412#[serde]
413pub fn op_get_mouse_position(state: &mut OpState) -> Vec<f64> {
414 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
415 let b = bridge.borrow();
416 vec![b.mouse_x as f64, b.mouse_y as f64]
417}
418
419#[deno_core::op2(fast)]
422pub fn op_is_mouse_button_down(state: &mut OpState, button: u8) -> bool {
423 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
424 bridge.borrow().mouse_buttons_down.contains(&button)
425}
426
427#[deno_core::op2(fast)]
430pub fn op_is_mouse_button_pressed(state: &mut OpState, button: u8) -> bool {
431 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
432 bridge.borrow().mouse_buttons_pressed.contains(&button)
433}
434
435#[deno_core::op2(fast)]
437pub fn op_get_delta_time(state: &mut OpState) -> f64 {
438 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
439 bridge.borrow().delta_time
440}
441
442#[deno_core::op2(fast)]
445pub fn op_create_solid_texture(
446 state: &mut OpState,
447 #[string] name: &str,
448 r: u32,
449 g: u32,
450 b: u32,
451 a: u32,
452) -> u32 {
453 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
454 let mut br = bridge.borrow_mut();
455
456 let key = format!("__solid__{name}");
457 if let Some(&id) = br.texture_path_to_id.get(&key) {
458 return id;
459 }
460
461 let id = br.next_texture_id;
462 br.next_texture_id += 1;
463 br.texture_path_to_id.insert(key.clone(), id);
464 br.texture_load_queue
466 .push((format!("__solid__:{name}:{r}:{g}:{b}:{a}"), id));
467 id
468}
469
470#[deno_core::op2(fast)]
473pub fn op_upload_rgba_texture(
474 state: &mut OpState,
475 #[string] name: &str,
476 width: f64,
477 height: f64,
478 #[buffer] pixels: &[u8],
479) -> u32 {
480 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
481 let mut b = bridge.borrow_mut();
482
483 let key = format!("__raw__:{name}");
484 if let Some(&id) = b.texture_path_to_id.get(&key) {
485 return id;
486 }
487
488 let id = b.next_texture_id;
489 b.next_texture_id += 1;
490 b.texture_path_to_id.insert(key, id);
491 b.raw_texture_upload_queue.push((
492 id,
493 width as u32,
494 height as u32,
495 pixels.to_vec(),
496 ));
497 id
498}
499
500#[deno_core::op2(fast)]
502pub fn op_create_tilemap(
503 state: &mut OpState,
504 texture_id: u32,
505 width: u32,
506 height: u32,
507 tile_size: f64,
508 atlas_columns: u32,
509 atlas_rows: u32,
510) -> u32 {
511 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
512 bridge
513 .borrow_mut()
514 .tilemaps
515 .create(texture_id, width, height, tile_size as f32, atlas_columns, atlas_rows)
516}
517
518#[deno_core::op2(fast)]
520pub fn op_set_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32, tile_id: u32) {
521 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
522 if let Some(tm) = bridge.borrow_mut().tilemaps.get_mut(tilemap_id) {
523 tm.set_tile(gx, gy, tile_id as u16);
524 }
525}
526
527#[deno_core::op2(fast)]
529pub fn op_get_tile(state: &mut OpState, tilemap_id: u32, gx: u32, gy: u32) -> u32 {
530 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
531 bridge
532 .borrow()
533 .tilemaps
534 .get(tilemap_id)
535 .map(|tm| tm.get_tile(gx, gy) as u32)
536 .unwrap_or(0)
537}
538
539#[deno_core::op2(fast)]
542pub fn op_draw_tilemap(state: &mut OpState, tilemap_id: u32, world_x: f64, world_y: f64, layer: i32) {
543 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
544 let mut b = bridge.borrow_mut();
545 let cam_x = b.camera_x;
546 let cam_y = b.camera_y;
547 let cam_zoom = b.camera_zoom;
548 let vp_w = 800.0;
550 let vp_h = 600.0;
551
552 if let Some(tm) = b.tilemaps.get(tilemap_id) {
553 let cmds = tm.bake_visible(world_x as f32, world_y as f32, layer, cam_x, cam_y, cam_zoom, vp_w, vp_h);
554 b.sprite_commands.extend(cmds);
555 }
556}
557
558#[deno_core::op2(fast)]
563pub fn op_set_ambient_light(state: &mut OpState, r: f64, g: f64, b: f64) {
564 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
565 bridge.borrow_mut().ambient_light = [r as f32, g as f32, b as f32];
566}
567
568#[deno_core::op2(fast)]
571pub fn op_add_point_light(
572 state: &mut OpState,
573 x: f64,
574 y: f64,
575 radius: f64,
576 r: f64,
577 g: f64,
578 b: f64,
579 intensity: f64,
580) {
581 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
582 bridge.borrow_mut().point_lights.push(PointLight {
583 x: x as f32,
584 y: y as f32,
585 radius: radius as f32,
586 r: r as f32,
587 g: g as f32,
588 b: b as f32,
589 intensity: intensity as f32,
590 });
591}
592
593#[deno_core::op2(fast)]
595pub fn op_clear_lights(state: &mut OpState) {
596 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
597 bridge.borrow_mut().point_lights.clear();
598}
599
600#[deno_core::op2(fast)]
604pub fn op_load_sound(state: &mut OpState, #[string] path: &str) -> u32 {
605 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
606 let mut b = bridge.borrow_mut();
607
608 let resolved = if std::path::Path::new(path).is_absolute() {
609 path.to_string()
610 } else {
611 b.base_dir.join(path).to_string_lossy().to_string()
612 };
613
614 if let Some(&id) = b.sound_path_to_id.get(&resolved) {
615 return id;
616 }
617
618 let id = b.next_sound_id;
619 b.next_sound_id += 1;
620 b.sound_path_to_id.insert(resolved.clone(), id);
621 b.audio_commands.push(BridgeAudioCommand::LoadSound { id, path: resolved });
622 id
623}
624
625#[deno_core::op2(fast)]
627pub fn op_stop_all_sounds(state: &mut OpState) {
628 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
629 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopAll);
630}
631
632#[deno_core::op2(fast)]
635pub fn op_set_master_volume(state: &mut OpState, volume: f64) {
636 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
637 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetMasterVolume { volume: volume as f32 });
638}
639
640#[deno_core::op2(fast)]
644pub fn op_create_font_texture(state: &mut OpState) -> u32 {
645 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
646 let mut b = bridge.borrow_mut();
647
648 let key = "__builtin_font__".to_string();
649 if let Some(&id) = b.texture_path_to_id.get(&key) {
650 return id;
651 }
652
653 let id = b.next_texture_id;
654 b.next_texture_id += 1;
655 b.texture_path_to_id.insert(key, id);
656 b.font_texture_queue.push(id);
657 id
658}
659
660#[deno_core::op2]
664#[serde]
665pub fn op_get_viewport_size(state: &mut OpState) -> Vec<f64> {
666 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
667 let b = bridge.borrow();
668 vec![b.viewport_width as f64, b.viewport_height as f64]
669}
670
671#[deno_core::op2(fast)]
673pub fn op_get_scale_factor(state: &mut OpState) -> f64 {
674 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
675 bridge.borrow().scale_factor as f64
676}
677
678#[deno_core::op2(fast)]
680pub fn op_set_background_color(state: &mut OpState, r: f64, g: f64, b: f64) {
681 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
682 let mut br = bridge.borrow_mut();
683 br.clear_color = [r as f32, g as f32, b as f32, 1.0];
684}
685
686#[deno_core::op2(fast)]
690pub fn op_save_file(state: &mut OpState, #[string] key: &str, #[string] value: &str) -> bool {
691 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
692 let save_dir = bridge.borrow().save_dir.clone();
693
694 if !key.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
696 return false;
697 }
698
699 if std::fs::create_dir_all(&save_dir).is_err() {
701 return false;
702 }
703
704 let path = save_dir.join(format!("{key}.json"));
705 std::fs::write(path, value).is_ok()
706}
707
708#[deno_core::op2]
710#[string]
711pub fn op_load_file(state: &mut OpState, #[string] key: &str) -> String {
712 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
713 let save_dir = bridge.borrow().save_dir.clone();
714
715 let path = save_dir.join(format!("{key}.json"));
716 std::fs::read_to_string(path).unwrap_or_default()
717}
718
719#[deno_core::op2(fast)]
721pub fn op_delete_file(state: &mut OpState, #[string] key: &str) -> bool {
722 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
723 let save_dir = bridge.borrow().save_dir.clone();
724
725 let path = save_dir.join(format!("{key}.json"));
726 std::fs::remove_file(path).is_ok()
727}
728
729#[deno_core::op2]
731#[serde]
732pub fn op_list_save_files(state: &mut OpState) -> Vec<String> {
733 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
734 let save_dir = bridge.borrow().save_dir.clone();
735
736 let mut keys = Vec::new();
737 if let Ok(entries) = std::fs::read_dir(&save_dir) {
738 for entry in entries.flatten() {
739 let path = entry.path();
740 if path.extension().map_or(false, |ext| ext == "json") {
741 if let Some(stem) = path.file_stem() {
742 keys.push(stem.to_string_lossy().to_string());
743 }
744 }
745 }
746 }
747 keys.sort();
748 keys
749}
750
751#[deno_core::op2(fast)]
755pub fn op_create_shader(state: &mut OpState, #[string] name: &str, #[string] source: &str) -> u32 {
756 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
757 let mut b = bridge.borrow_mut();
758 let id = b.next_shader_id;
759 b.next_shader_id += 1;
760 b.shader_create_queue
761 .push((id, name.to_string(), source.to_string()));
762 id
763}
764
765#[deno_core::op2(fast)]
767pub fn op_set_shader_param(
768 state: &mut OpState,
769 shader_id: u32,
770 index: u32,
771 x: f64,
772 y: f64,
773 z: f64,
774 w: f64,
775) {
776 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
777 bridge.borrow_mut().shader_param_queue.push((
778 shader_id,
779 index,
780 [x as f32, y as f32, z as f32, w as f32],
781 ));
782}
783
784#[deno_core::op2(fast)]
788pub fn op_add_effect(state: &mut OpState, #[string] effect_type: &str) -> u32 {
789 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
790 let mut b = bridge.borrow_mut();
791 let id = b.next_effect_id;
792 b.next_effect_id += 1;
793 b.effect_create_queue
794 .push((id, effect_type.to_string()));
795 id
796}
797
798#[deno_core::op2(fast)]
800pub fn op_set_effect_param(
801 state: &mut OpState,
802 effect_id: u32,
803 index: u32,
804 x: f64,
805 y: f64,
806 z: f64,
807 w: f64,
808) {
809 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
810 bridge.borrow_mut().effect_param_queue.push((
811 effect_id,
812 index,
813 [x as f32, y as f32, z as f32, w as f32],
814 ));
815}
816
817#[deno_core::op2(fast)]
819pub fn op_remove_effect(state: &mut OpState, effect_id: u32) {
820 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
821 bridge.borrow_mut().effect_remove_queue.push(effect_id);
822}
823
824#[deno_core::op2(fast)]
826pub fn op_clear_effects(state: &mut OpState) {
827 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
828 bridge.borrow_mut().effect_clear = true;
829}
830
831#[deno_core::op2(fast)]
835pub fn op_set_camera_bounds(state: &mut OpState, min_x: f64, min_y: f64, max_x: f64, max_y: f64) {
836 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
837 bridge.borrow_mut().camera_bounds = Some(CameraBounds {
838 min_x: min_x as f32,
839 min_y: min_y as f32,
840 max_x: max_x as f32,
841 max_y: max_y as f32,
842 });
843}
844
845#[deno_core::op2(fast)]
847pub fn op_clear_camera_bounds(state: &mut OpState) {
848 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
849 bridge.borrow_mut().camera_bounds = None;
850}
851
852#[deno_core::op2]
854#[serde]
855pub fn op_get_camera_bounds(state: &mut OpState) -> Vec<f64> {
856 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
857 let b = bridge.borrow();
858 match b.camera_bounds {
859 Some(bounds) => vec![
860 bounds.min_x as f64,
861 bounds.min_y as f64,
862 bounds.max_x as f64,
863 bounds.max_y as f64,
864 ],
865 None => vec![],
866 }
867}
868
869#[deno_core::op2(fast)]
873pub fn op_enable_gi(state: &mut OpState) {
874 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
875 bridge.borrow_mut().gi_enabled = true;
876}
877
878#[deno_core::op2(fast)]
880pub fn op_disable_gi(state: &mut OpState) {
881 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
882 bridge.borrow_mut().gi_enabled = false;
883}
884
885#[deno_core::op2(fast)]
887pub fn op_set_gi_intensity(state: &mut OpState, intensity: f64) {
888 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
889 bridge.borrow_mut().gi_intensity = intensity as f32;
890}
891
892#[deno_core::op2(fast)]
895pub fn op_set_gi_quality(state: &mut OpState, probe_spacing: f64, interval: f64, cascade_count: f64) {
896 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
897 let mut b = bridge.borrow_mut();
898 if probe_spacing > 0.0 {
899 b.gi_probe_spacing = Some(probe_spacing as f32);
900 }
901 if interval > 0.0 {
902 b.gi_interval = Some(interval as f32);
903 }
904 if cascade_count > 0.0 {
905 b.gi_cascade_count = Some(cascade_count as u32);
906 }
907}
908
909#[deno_core::op2(fast)]
911pub fn op_add_emissive(
912 state: &mut OpState,
913 x: f64,
914 y: f64,
915 w: f64,
916 h: f64,
917 r: f64,
918 g: f64,
919 b: f64,
920 intensity: f64,
921) {
922 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
923 bridge.borrow_mut().emissives.push([
924 x as f32,
925 y as f32,
926 w as f32,
927 h as f32,
928 r as f32,
929 g as f32,
930 b as f32,
931 intensity as f32,
932 ]);
933}
934
935#[deno_core::op2(fast)]
937pub fn op_clear_emissives(state: &mut OpState) {
938 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
939 bridge.borrow_mut().emissives.clear();
940}
941
942#[deno_core::op2(fast)]
944pub fn op_add_occluder(state: &mut OpState, x: f64, y: f64, w: f64, h: f64) {
945 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
946 bridge.borrow_mut().occluders.push([x as f32, y as f32, w as f32, h as f32]);
947}
948
949#[deno_core::op2(fast)]
951pub fn op_clear_occluders(state: &mut OpState) {
952 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
953 bridge.borrow_mut().occluders.clear();
954}
955
956#[deno_core::op2(fast)]
958pub fn op_add_directional_light(
959 state: &mut OpState,
960 angle: f64,
961 r: f64,
962 g: f64,
963 b: f64,
964 intensity: f64,
965) {
966 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
967 bridge.borrow_mut().directional_lights.push([
968 angle as f32,
969 r as f32,
970 g as f32,
971 b as f32,
972 intensity as f32,
973 ]);
974}
975
976#[deno_core::op2(fast)]
978pub fn op_add_spot_light(
979 state: &mut OpState,
980 x: f64,
981 y: f64,
982 angle: f64,
983 spread: f64,
984 range: f64,
985 r: f64,
986 g: f64,
987 b: f64,
988 intensity: f64,
989) {
990 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
991 bridge.borrow_mut().spot_lights.push([
992 x as f32,
993 y as f32,
994 angle as f32,
995 spread as f32,
996 range as f32,
997 r as f32,
998 g as f32,
999 b as f32,
1000 intensity as f32,
1001 ]);
1002}
1003
1004#[deno_core::op2(fast)]
1009pub fn op_play_sound_ex(
1010 state: &mut OpState,
1011 sound_id: u32,
1012 instance_id: f64,
1013 volume: f64,
1014 looping: bool,
1015 bus: u32,
1016 pan: f64,
1017 pitch: f64,
1018 low_pass_freq: u32,
1019 reverb_mix: f64,
1020 reverb_delay_ms: u32,
1021) {
1022 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1023 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundEx {
1024 sound_id,
1025 instance_id: instance_id as u64,
1026 volume: volume as f32,
1027 looping,
1028 bus,
1029 pan: pan as f32,
1030 pitch: pitch as f32,
1031 low_pass_freq,
1032 reverb_mix: reverb_mix as f32,
1033 reverb_delay_ms,
1034 });
1035}
1036
1037#[deno_core::op2(fast)]
1040pub fn op_play_sound_spatial(
1041 state: &mut OpState,
1042 sound_id: u32,
1043 instance_id: f64,
1044 volume: f64,
1045 looping: bool,
1046 bus: u32,
1047 pitch: f64,
1048 source_x: f64,
1049 source_y: f64,
1050 listener_x: f64,
1051 listener_y: f64,
1052) {
1053 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1054 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::PlaySoundSpatial {
1055 sound_id,
1056 instance_id: instance_id as u64,
1057 volume: volume as f32,
1058 looping,
1059 bus,
1060 pitch: pitch as f32,
1061 source_x: source_x as f32,
1062 source_y: source_y as f32,
1063 listener_x: listener_x as f32,
1064 listener_y: listener_y as f32,
1065 });
1066}
1067
1068#[deno_core::op2(fast)]
1071pub fn op_stop_instance(state: &mut OpState, instance_id: f64) {
1072 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1073 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::StopInstance {
1074 instance_id: instance_id as u64,
1075 });
1076}
1077
1078#[deno_core::op2(fast)]
1081pub fn op_set_instance_volume(state: &mut OpState, instance_id: f64, volume: f64) {
1082 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1083 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstanceVolume {
1084 instance_id: instance_id as u64,
1085 volume: volume as f32,
1086 });
1087}
1088
1089#[deno_core::op2(fast)]
1092pub fn op_set_instance_pitch(state: &mut OpState, instance_id: f64, pitch: f64) {
1093 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1094 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetInstancePitch {
1095 instance_id: instance_id as u64,
1096 pitch: pitch as f32,
1097 });
1098}
1099
1100#[deno_core::op2(fast)]
1104pub fn op_update_spatial_positions(state: &mut OpState, #[string] data_json: &str, listener_x: f64, listener_y: f64) {
1105 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1106
1107 let mut updates = Vec::new();
1110
1111 if let Some(ids_start) = data_json.find("\"instanceIds\":[") {
1113 if let Some(xs_start) = data_json.find("\"sourceXs\":[") {
1114 if let Some(ys_start) = data_json.find("\"sourceYs\":[") {
1115 let ids_str = &data_json[ids_start + 15..];
1116 let xs_str = &data_json[xs_start + 12..];
1117 let ys_str = &data_json[ys_start + 12..];
1118
1119 let ids_end = ids_str.find(']').unwrap_or(0);
1120 let xs_end = xs_str.find(']').unwrap_or(0);
1121 let ys_end = ys_str.find(']').unwrap_or(0);
1122
1123 let ids: Vec<u64> = ids_str[..ids_end]
1124 .split(',')
1125 .filter_map(|s| s.trim().parse().ok())
1126 .collect();
1127 let xs: Vec<f32> = xs_str[..xs_end]
1128 .split(',')
1129 .filter_map(|s| s.trim().parse().ok())
1130 .collect();
1131 let ys: Vec<f32> = ys_str[..ys_end]
1132 .split(',')
1133 .filter_map(|s| s.trim().parse().ok())
1134 .collect();
1135
1136 for i in 0..ids.len().min(xs.len()).min(ys.len()) {
1137 updates.push((ids[i], xs[i], ys[i]));
1138 }
1139 }
1140 }
1141 }
1142
1143 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::UpdateSpatialPositions {
1144 updates,
1145 listener_x: listener_x as f32,
1146 listener_y: listener_y as f32,
1147 });
1148}
1149
1150#[deno_core::op2(fast)]
1153pub fn op_set_bus_volume(state: &mut OpState, bus: u32, volume: f64) {
1154 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1155 bridge.borrow_mut().audio_commands.push(BridgeAudioCommand::SetBusVolume {
1156 bus,
1157 volume: volume as f32,
1158 });
1159}
1160
1161#[deno_core::op2]
1166#[string]
1167pub fn op_create_msdf_builtin_font(state: &mut OpState) -> String {
1168 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1169 let mut b = bridge.borrow_mut();
1170
1171 let key = "__msdf_builtin__".to_string();
1173 if let Some(&tex_id) = b.texture_path_to_id.get(&key) {
1174 let font_id_key = format!("__msdf_font_{tex_id}__");
1177 if let Some(&font_id) = b.texture_path_to_id.get(&font_id_key) {
1178 let pool = &b.msdf_shader_pool;
1179 let shader_id = pool.first().copied().unwrap_or(0);
1180 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1181 return format!(
1182 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1183 font_id, tex_id, shader_id, pool_json.join(",")
1184 );
1185 }
1186 }
1187
1188 let tex_id = b.next_texture_id;
1190 b.next_texture_id += 1;
1191 b.texture_path_to_id.insert(key, tex_id);
1192
1193 let (_pixels, _width, _height, mut font) =
1195 crate::renderer::msdf::generate_builtin_msdf_font();
1196 font.texture_id = tex_id;
1197
1198 let font_id = b.msdf_fonts.register(font);
1200 b.texture_path_to_id
1201 .insert(format!("__msdf_font_{tex_id}__"), font_id);
1202
1203 b.msdf_builtin_queue.push((font_id, tex_id));
1206
1207 let pool = ensure_msdf_shader_pool(&mut b);
1209 let shader_id = pool.first().copied().unwrap_or(0);
1210 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1211
1212 format!(
1213 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1214 font_id, tex_id, shader_id, pool_json.join(",")
1215 )
1216}
1217
1218#[deno_core::op2]
1221#[string]
1222pub fn op_get_msdf_glyphs(
1223 state: &mut OpState,
1224 font_id: u32,
1225 #[string] text: &str,
1226) -> String {
1227 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1228 let b = bridge.borrow();
1229
1230 let font = match b.msdf_fonts.get(font_id) {
1231 Some(f) => f,
1232 None => return "[]".to_string(),
1233 };
1234
1235 let mut entries = Vec::new();
1236 for ch in text.chars() {
1237 if let Some(glyph) = font.get_glyph(ch) {
1238 entries.push(format!(
1239 "{{\"char\":{},\"uv\":[{},{},{},{}],\"advance\":{},\"width\":{},\"height\":{},\"offsetX\":{},\"offsetY\":{}}}",
1240 ch as u32,
1241 glyph.uv_x, glyph.uv_y, glyph.uv_w, glyph.uv_h,
1242 glyph.advance, glyph.width, glyph.height,
1243 glyph.offset_x, glyph.offset_y,
1244 ));
1245 }
1246 }
1247
1248 format!("[{}]", entries.join(","))
1249}
1250
1251#[deno_core::op2]
1253#[string]
1254pub fn op_get_msdf_font_info(state: &mut OpState, font_id: u32) -> String {
1255 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1256 let b = bridge.borrow();
1257
1258 match b.msdf_fonts.get(font_id) {
1259 Some(font) => format!(
1260 "{{\"fontSize\":{},\"lineHeight\":{},\"distanceRange\":{},\"textureId\":{}}}",
1261 font.font_size, font.line_height, font.distance_range, font.texture_id,
1262 ),
1263 None => "null".to_string(),
1264 }
1265}
1266
1267#[deno_core::op2]
1270#[string]
1271pub fn op_load_msdf_font(
1272 state: &mut OpState,
1273 #[string] atlas_path: &str,
1274 #[string] metrics_json_or_path: &str,
1275) -> String {
1276 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1277 let mut b = bridge.borrow_mut();
1278
1279 let resolved = if std::path::Path::new(atlas_path).is_absolute() {
1281 atlas_path.to_string()
1282 } else {
1283 b.base_dir.join(atlas_path).to_string_lossy().to_string()
1284 };
1285
1286 let tex_id = if let Some(&id) = b.texture_path_to_id.get(&resolved) {
1288 id
1289 } else {
1290 let id = b.next_texture_id;
1291 b.next_texture_id += 1;
1292 b.texture_path_to_id.insert(resolved.clone(), id);
1293 b.msdf_texture_load_queue.push((resolved, id));
1294 id
1295 };
1296
1297 let metrics_json: String = if metrics_json_or_path.trim_start().starts_with('{') {
1300 metrics_json_or_path.to_string()
1301 } else {
1302 let json_path = if std::path::Path::new(metrics_json_or_path).is_absolute() {
1304 metrics_json_or_path.to_string()
1305 } else {
1306 b.base_dir
1307 .join(metrics_json_or_path)
1308 .to_string_lossy()
1309 .to_string()
1310 };
1311 match std::fs::read_to_string(&json_path) {
1312 Ok(content) => content,
1313 Err(e) => {
1314 return format!("{{\"error\":\"Failed to read metrics file {}: {}\"}}", json_path, e);
1315 }
1316 }
1317 };
1318
1319 let font = match crate::renderer::msdf::parse_msdf_metrics(&metrics_json, tex_id) {
1321 Ok(f) => f,
1322 Err(e) => {
1323 return format!("{{\"error\":\"{}\"}}", e);
1324 }
1325 };
1326
1327 let font_id = b.msdf_fonts.register(font);
1328 let pool = ensure_msdf_shader_pool(&mut b);
1329 let shader_id = pool.first().copied().unwrap_or(0);
1330 let pool_json: Vec<String> = pool.iter().map(|id| id.to_string()).collect();
1331
1332 format!(
1333 "{{\"fontId\":{},\"textureId\":{},\"shaderId\":{},\"shaderPool\":[{}]}}",
1334 font_id, tex_id, shader_id, pool_json.join(",")
1335 )
1336}
1337
1338const MSDF_SHADER_POOL_SIZE: usize = 8;
1340
1341fn ensure_msdf_shader_pool(b: &mut RenderBridgeState) -> Vec<u32> {
1343 if !b.msdf_shader_pool.is_empty() {
1344 return b.msdf_shader_pool.clone();
1345 }
1346
1347 let source = crate::renderer::msdf::MSDF_FRAGMENT_SOURCE.to_string();
1348 let mut pool = Vec::with_capacity(MSDF_SHADER_POOL_SIZE);
1349
1350 for _ in 0..MSDF_SHADER_POOL_SIZE {
1351 let id = b.next_shader_id;
1352 b.next_shader_id += 1;
1353 b.msdf_shader_queue.push((id, source.clone()));
1354 pool.push(id);
1355 }
1356
1357 b.msdf_shader_pool = pool.clone();
1358 pool
1359}
1360
1361#[deno_core::op2(fast)]
1365pub fn op_get_gamepad_count(state: &mut OpState) -> u32 {
1366 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1367 bridge.borrow().gamepad_count
1368}
1369
1370#[deno_core::op2]
1372#[string]
1373pub fn op_get_gamepad_name(state: &mut OpState) -> String {
1374 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1375 bridge.borrow().gamepad_name.clone()
1376}
1377
1378#[deno_core::op2(fast)]
1381pub fn op_is_gamepad_button_down(state: &mut OpState, #[string] button: &str) -> bool {
1382 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1383 bridge.borrow().gamepad_buttons_down.contains(button)
1384}
1385
1386#[deno_core::op2(fast)]
1388pub fn op_is_gamepad_button_pressed(state: &mut OpState, #[string] button: &str) -> bool {
1389 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1390 bridge.borrow().gamepad_buttons_pressed.contains(button)
1391}
1392
1393#[deno_core::op2(fast)]
1396pub fn op_get_gamepad_axis(state: &mut OpState, #[string] axis: &str) -> f64 {
1397 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1398 bridge.borrow().gamepad_axes.get(axis).copied().unwrap_or(0.0) as f64
1399}
1400
1401#[deno_core::op2(fast)]
1405pub fn op_get_touch_count(state: &mut OpState) -> u32 {
1406 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1407 bridge.borrow().touch_count
1408}
1409
1410#[deno_core::op2]
1412#[serde]
1413pub fn op_get_touch_position(state: &mut OpState, index: u32) -> Vec<f64> {
1414 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1415 let b = bridge.borrow();
1416 if let Some(&(_, x, y)) = b.touch_points.get(index as usize) {
1417 vec![x as f64, y as f64]
1418 } else {
1419 vec![]
1420 }
1421}
1422
1423#[deno_core::op2(fast)]
1425pub fn op_is_touch_active(state: &mut OpState) -> bool {
1426 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
1427 bridge.borrow().touch_count > 0
1428}
1429
1430deno_core::extension!(
1431 render_ext,
1432 ops = [
1433 op_clear_sprites,
1434 op_submit_sprite_batch,
1435 op_set_camera,
1436 op_get_camera,
1437 op_load_texture,
1438 op_load_texture_linear,
1439 op_upload_rgba_texture,
1440 op_is_key_down,
1441 op_is_key_pressed,
1442 op_get_mouse_position,
1443 op_is_mouse_button_down,
1444 op_is_mouse_button_pressed,
1445 op_get_delta_time,
1446 op_create_solid_texture,
1447 op_create_tilemap,
1448 op_set_tile,
1449 op_get_tile,
1450 op_draw_tilemap,
1451 op_set_ambient_light,
1452 op_add_point_light,
1453 op_clear_lights,
1454 op_load_sound,
1455 op_stop_all_sounds,
1456 op_set_master_volume,
1457 op_play_sound_ex,
1458 op_play_sound_spatial,
1459 op_stop_instance,
1460 op_set_instance_volume,
1461 op_set_instance_pitch,
1462 op_update_spatial_positions,
1463 op_set_bus_volume,
1464 op_create_font_texture,
1465 op_get_viewport_size,
1466 op_get_scale_factor,
1467 op_set_background_color,
1468 op_save_file,
1469 op_load_file,
1470 op_delete_file,
1471 op_list_save_files,
1472 op_create_shader,
1473 op_set_shader_param,
1474 op_add_effect,
1475 op_set_effect_param,
1476 op_remove_effect,
1477 op_clear_effects,
1478 op_set_camera_bounds,
1479 op_clear_camera_bounds,
1480 op_get_camera_bounds,
1481 op_enable_gi,
1482 op_disable_gi,
1483 op_set_gi_intensity,
1484 op_set_gi_quality,
1485 op_add_emissive,
1486 op_clear_emissives,
1487 op_add_occluder,
1488 op_clear_occluders,
1489 op_add_directional_light,
1490 op_add_spot_light,
1491 op_create_msdf_builtin_font,
1492 op_get_msdf_glyphs,
1493 op_get_msdf_font_info,
1494 op_load_msdf_font,
1495 op_get_gamepad_count,
1496 op_get_gamepad_name,
1497 op_is_gamepad_button_down,
1498 op_is_gamepad_button_pressed,
1499 op_get_gamepad_axis,
1500 op_get_touch_count,
1501 op_get_touch_position,
1502 op_is_touch_active,
1503 ],
1504);