1use miniquad::*;
40
41use std::collections::{HashMap, HashSet};
42use std::future::Future;
43use std::panic::AssertUnwindSafe;
44use std::pin::Pin;
45
46mod exec;
47mod quad_gl;
48mod tobytes;
49
50pub mod audio;
51pub mod camera;
52pub mod color;
53pub mod file;
54pub mod input;
55pub mod material;
56pub mod math;
57pub mod models;
58pub mod shapes;
59pub mod text;
60pub mod texture;
61pub mod time;
62pub mod ui;
63pub mod window;
64
65pub mod experimental;
66
67pub mod prelude;
68
69pub mod telemetry;
70
71mod error;
72
73pub use error::Error;
74
75pub use macroquad_macro::main;
125
126#[doc(hidden)]
132pub use macroquad_macro::test;
133
134pub mod rand {
136 pub use quad_rand::*;
137}
138
139#[cfg(not(feature = "log-rs"))]
140pub mod logging {
142 pub use miniquad::{debug, error, info, trace, warn};
143}
144#[cfg(feature = "log-rs")]
145pub use ::log as logging;
147pub use miniquad;
148
149use crate::{
150 color::{colors::*, Color},
151 quad_gl::QuadGl,
152 texture::TextureHandle,
153 ui::ui_context::UiContext,
154};
155
156use glam::{vec2, Mat4, Vec2};
157
158pub(crate) mod thread_assert {
159 static mut THREAD_ID: Option<std::thread::ThreadId> = None;
160
161 pub fn set_thread_id() {
162 unsafe {
163 THREAD_ID = Some(std::thread::current().id());
164 }
165 }
166
167 pub fn same_thread() {
168 unsafe {
169 thread_local! {
170 static CURRENT_THREAD_ID: std::thread::ThreadId = std::thread::current().id();
171 }
172 assert!(THREAD_ID.is_some());
173 assert!(THREAD_ID.unwrap() == CURRENT_THREAD_ID.with(|id| *id));
174 }
175 }
176}
177struct Context {
178 audio_context: audio::AudioContext,
179
180 screen_width: f32,
181 screen_height: f32,
182
183 simulate_mouse_with_touch: bool,
184
185 keys_down: HashSet<KeyCode>,
186 keys_pressed: HashSet<KeyCode>,
187 keys_released: HashSet<KeyCode>,
188 mouse_down: HashSet<MouseButton>,
189 mouse_pressed: HashSet<MouseButton>,
190 mouse_released: HashSet<MouseButton>,
191 touches: HashMap<u64, input::Touch>,
192 chars_pressed_queue: Vec<char>,
193 chars_pressed_ui_queue: Vec<char>,
194 mouse_position: Vec2,
195 last_mouse_position: Option<Vec2>,
196 mouse_wheel: Vec2,
197
198 prevent_quit_event: bool,
199 quit_requested: bool,
200
201 cursor_grabbed: bool,
202 previous_cursor_grabbed: bool,
203
204 input_events: Vec<Vec<MiniquadInputEvent>>,
205
206 gl: QuadGl,
207 camera_matrix: Option<Mat4>,
208
209 ui_context: UiContext,
210 coroutines_context: experimental::coroutines::CoroutinesContext,
211 fonts_storage: text::FontsStorage,
212
213 pc_assets_folder: Option<String>,
214
215 start_time: f64,
216 last_frame_time: f64,
217 frame_time: f64,
218
219 #[cfg(one_screenshot)]
220 counter: usize,
221
222 camera_stack: Vec<camera::CameraState>,
223 texture_batcher: texture::Batcher,
224 unwind: bool,
225 recovery_future: Option<Pin<Box<dyn Future<Output = ()>>>>,
226
227 quad_context: Box<dyn miniquad::RenderingBackend>,
228
229 default_filter_mode: crate::quad_gl::FilterMode,
230 textures: crate::texture::TexturesContext,
231
232 update_on: conf::UpdateTrigger,
233
234 dropped_files: Vec<DroppedFile>,
235}
236
237#[derive(Clone)]
238enum MiniquadInputEvent {
239 MouseMotion {
240 x: f32,
241 y: f32,
242 },
243 MouseWheel {
244 x: f32,
245 y: f32,
246 },
247 MouseButtonDown {
248 x: f32,
249 y: f32,
250 btn: MouseButton,
251 },
252 MouseButtonUp {
253 x: f32,
254 y: f32,
255 btn: MouseButton,
256 },
257 Char {
258 character: char,
259 modifiers: KeyMods,
260 repeat: bool,
261 },
262 KeyDown {
263 keycode: KeyCode,
264 modifiers: KeyMods,
265 repeat: bool,
266 },
267 KeyUp {
268 keycode: KeyCode,
269 modifiers: KeyMods,
270 },
271 Touch {
272 phase: TouchPhase,
273 id: u64,
274 x: f32,
275 y: f32,
276 },
277 WindowMinimized,
278 WindowRestored,
279}
280
281impl MiniquadInputEvent {
282 fn repeat<T: miniquad::EventHandler>(&self, t: &mut T) {
283 use crate::MiniquadInputEvent::*;
284 match self {
285 MouseMotion { x, y } => t.mouse_motion_event(*x, *y),
286 MouseWheel { x, y } => t.mouse_wheel_event(*x, *y),
287 MouseButtonDown { x, y, btn } => t.mouse_button_down_event(*btn, *x, *y),
288 MouseButtonUp { x, y, btn } => t.mouse_button_up_event(*btn, *x, *y),
289 Char {
290 character,
291 modifiers,
292 repeat,
293 } => t.char_event(*character, *modifiers, *repeat),
294 KeyDown {
295 keycode,
296 modifiers,
297 repeat,
298 } => t.key_down_event(*keycode, *modifiers, *repeat),
299 KeyUp { keycode, modifiers } => t.key_up_event(*keycode, *modifiers),
300 Touch { phase, id, x, y } => t.touch_event(*phase, *id, *x, *y),
301 WindowMinimized => t.window_minimized_event(),
302 WindowRestored => t.window_restored_event(),
303 }
304 }
305}
306
307impl Context {
308 const DEFAULT_BG_COLOR: Color = BLACK;
309
310 fn new(
311 update_on: conf::UpdateTrigger,
312 default_filter_mode: crate::FilterMode,
313 draw_call_vertex_capacity: usize,
314 draw_call_index_capacity: usize,
315 ) -> Context {
316 let mut ctx: Box<dyn miniquad::RenderingBackend> =
317 miniquad::window::new_rendering_backend();
318 let (screen_width, screen_height) = miniquad::window::screen_size();
319
320 Context {
321 screen_width,
322 screen_height,
323
324 simulate_mouse_with_touch: true,
325
326 keys_down: HashSet::new(),
327 keys_pressed: HashSet::new(),
328 keys_released: HashSet::new(),
329 chars_pressed_queue: Vec::new(),
330 chars_pressed_ui_queue: Vec::new(),
331 mouse_down: HashSet::new(),
332 mouse_pressed: HashSet::new(),
333 mouse_released: HashSet::new(),
334 touches: HashMap::new(),
335 mouse_position: vec2(0., 0.),
336 last_mouse_position: None,
337 mouse_wheel: vec2(0., 0.),
338
339 prevent_quit_event: false,
340 quit_requested: false,
341
342 cursor_grabbed: false,
343 previous_cursor_grabbed: false,
344
345 input_events: Vec::new(),
346
347 camera_matrix: None,
348 gl: QuadGl::new(
349 &mut *ctx,
350 draw_call_vertex_capacity,
351 draw_call_index_capacity,
352 ),
353
354 ui_context: UiContext::new(&mut *ctx, screen_width, screen_height),
355 fonts_storage: text::FontsStorage::new(&mut *ctx),
356 texture_batcher: texture::Batcher::new(&mut *ctx),
357 camera_stack: vec![],
358
359 audio_context: audio::AudioContext::new(),
360 coroutines_context: experimental::coroutines::CoroutinesContext::new(),
361
362 pc_assets_folder: None,
363
364 start_time: miniquad::date::now(),
365 last_frame_time: miniquad::date::now(),
366 frame_time: 1. / 60.,
367
368 #[cfg(one_screenshot)]
369 counter: 0,
370 unwind: false,
371 recovery_future: None,
372
373 quad_context: ctx,
374
375 default_filter_mode,
376 textures: crate::texture::TexturesContext::new(),
377 update_on,
378
379 dropped_files: Vec::new(),
380 }
381 }
382
383 pub fn raw_miniquad_id(&self, handle: &TextureHandle) -> miniquad::TextureId {
385 match handle {
386 TextureHandle::Unmanaged(texture) => *texture,
387 TextureHandle::Managed(texture) => self
388 .textures
389 .texture(texture.0)
390 .unwrap_or(self.gl.white_texture),
391 TextureHandle::ManagedWeak(texture) => self
392 .textures
393 .texture(*texture)
394 .unwrap_or(self.gl.white_texture),
395 }
396 }
397
398 pub fn dropped_files(&mut self) -> Vec<DroppedFile> {
400 std::mem::take(&mut self.dropped_files)
401 }
402
403 fn begin_frame(&mut self) {
404 telemetry::begin_gpu_query("GPU");
405
406 self.ui_context.process_input();
407
408 let color = Self::DEFAULT_BG_COLOR;
409
410 get_quad_context().clear(Some((color.r, color.g, color.b, color.a)), None, None);
411 self.gl.reset();
412 }
413
414 fn end_frame(&mut self) {
415 crate::experimental::scene::update();
416
417 self.perform_render_passes();
418
419 self.ui_context.draw(get_quad_context(), &mut self.gl);
420 let screen_mat = self.pixel_perfect_projection_matrix();
421 self.gl.draw(get_quad_context(), screen_mat);
422
423 get_quad_context().commit_frame();
424
425 #[cfg(one_screenshot)]
426 {
427 get_context().counter += 1;
428 if get_context().counter == 3 {
429 crate::prelude::get_screen_data().export_png("screenshot.png");
430 panic!("screenshot successfully saved to `screenshot.png`");
431 }
432 }
433
434 telemetry::end_gpu_query();
435
436 self.mouse_wheel = Vec2::new(0., 0.);
437 self.keys_pressed.clear();
438 self.keys_released.clear();
439 self.mouse_pressed.clear();
440 self.mouse_released.clear();
441 self.last_mouse_position = Some(crate::prelude::mouse_position_local());
442
443 self.quit_requested = false;
444
445 self.textures.garbage_collect(get_quad_context());
446
447 self.touches.retain(|_, touch| {
449 touch.phase != input::TouchPhase::Ended && touch.phase != input::TouchPhase::Cancelled
450 });
451
452 for touch in self.touches.values_mut() {
454 if touch.phase == input::TouchPhase::Started || touch.phase == input::TouchPhase::Moved
455 {
456 touch.phase = input::TouchPhase::Stationary;
457 }
458 }
459
460 self.dropped_files.clear();
461 }
462
463 pub(crate) fn pixel_perfect_projection_matrix(&self) -> glam::Mat4 {
464 let (width, height) = miniquad::window::screen_size();
465
466 let dpi = miniquad::window::dpi_scale();
467
468 glam::Mat4::orthographic_rh_gl(0., width / dpi, height / dpi, 0., -1., 1.)
469 }
470
471 pub(crate) fn projection_matrix(&self) -> glam::Mat4 {
472 if let Some(matrix) = self.camera_matrix {
473 matrix
474 } else {
475 self.pixel_perfect_projection_matrix()
476 }
477 }
478
479 pub(crate) fn perform_render_passes(&mut self) {
480 let matrix = self.projection_matrix();
481
482 self.gl.draw(get_quad_context(), matrix);
483 }
484}
485
486#[no_mangle]
487static mut CONTEXT: Option<Context> = None;
488
489#[doc(hidden)]
494pub mod test {
495 pub static mut MUTEX: Option<std::sync::Mutex<()>> = None;
496 pub static ONCE: std::sync::Once = std::sync::Once::new();
497}
498
499fn get_context() -> &'static mut Context {
500 thread_assert::same_thread();
501
502 unsafe { CONTEXT.as_mut().unwrap_or_else(|| panic!()) }
503}
504
505fn get_quad_context() -> &'static mut dyn miniquad::RenderingBackend {
506 thread_assert::same_thread();
507
508 unsafe {
509 assert!(CONTEXT.is_some());
510 }
511
512 unsafe { &mut *CONTEXT.as_mut().unwrap().quad_context }
513}
514
515struct Stage {
516 main_future: Pin<Box<dyn Future<Output = ()>>>,
517}
518
519impl EventHandler for Stage {
520 fn resize_event(&mut self, width: f32, height: f32) {
521 let _z = telemetry::ZoneGuard::new("Event::resize_event");
522 get_context().screen_width = width;
523 get_context().screen_height = height;
524
525 if miniquad::window::blocking_event_loop() {
526 miniquad::window::schedule_update();
527 }
528 }
529
530 fn raw_mouse_motion(&mut self, x: f32, y: f32) {
531 let context = get_context();
532
533 if context.cursor_grabbed {
534 context.mouse_position += Vec2::new(x, y);
535
536 let event = MiniquadInputEvent::MouseMotion {
537 x: context.mouse_position.x,
538 y: context.mouse_position.y,
539 };
540 context
541 .input_events
542 .iter_mut()
543 .for_each(|arr| arr.push(event.clone()));
544 }
545 }
546
547 fn mouse_motion_event(&mut self, x: f32, y: f32) {
548 let context = get_context();
549
550 if !context.cursor_grabbed {
551 context.mouse_position = Vec2::new(x, y);
552
553 context
554 .input_events
555 .iter_mut()
556 .for_each(|arr| arr.push(MiniquadInputEvent::MouseMotion { x, y }));
557 }
558
559 if context.update_on.mouse_motion {
560 miniquad::window::schedule_update();
561 }
562 }
563
564 fn mouse_wheel_event(&mut self, x: f32, y: f32) {
565 let context = get_context();
566
567 context.mouse_wheel.x = x;
568 context.mouse_wheel.y = y;
569
570 context
571 .input_events
572 .iter_mut()
573 .for_each(|arr| arr.push(MiniquadInputEvent::MouseWheel { x, y }));
574
575 if context.update_on.mouse_wheel {
576 miniquad::window::schedule_update();
577 }
578 }
579
580 fn mouse_button_down_event(&mut self, btn: MouseButton, x: f32, y: f32) {
581 let context = get_context();
582
583 context.mouse_down.insert(btn);
584 context.mouse_pressed.insert(btn);
585
586 context
587 .input_events
588 .iter_mut()
589 .for_each(|arr| arr.push(MiniquadInputEvent::MouseButtonDown { x, y, btn }));
590
591 if !context.cursor_grabbed {
592 context.mouse_position = Vec2::new(x, y);
593 }
594
595 if context.update_on.mouse_down {
596 miniquad::window::schedule_update();
597 }
598 }
599
600 fn mouse_button_up_event(&mut self, btn: MouseButton, x: f32, y: f32) {
601 let context = get_context();
602
603 context.mouse_down.remove(&btn);
604 context.mouse_released.insert(btn);
605
606 context
607 .input_events
608 .iter_mut()
609 .for_each(|arr| arr.push(MiniquadInputEvent::MouseButtonUp { x, y, btn }));
610
611 if !context.cursor_grabbed {
612 context.mouse_position = Vec2::new(x, y);
613 }
614 if context.update_on.mouse_up {
615 miniquad::window::schedule_update();
616 }
617 }
618
619 fn touch_event(&mut self, phase: TouchPhase, id: u64, x: f32, y: f32) {
620 let context = get_context();
621
622 context.touches.insert(
623 id,
624 input::Touch {
625 id,
626 phase: phase.into(),
627 position: Vec2::new(x, y),
628 },
629 );
630
631 if context.simulate_mouse_with_touch {
632 if phase == TouchPhase::Started {
633 self.mouse_button_down_event(MouseButton::Left, x, y);
634 }
635
636 if phase == TouchPhase::Ended {
637 self.mouse_button_up_event(MouseButton::Left, x, y);
638 }
639
640 if phase == TouchPhase::Moved {
641 self.mouse_motion_event(x, y);
642 }
643 } else if context.update_on.touch {
644 miniquad::window::schedule_update();
645 };
646
647 context
648 .input_events
649 .iter_mut()
650 .for_each(|arr| arr.push(MiniquadInputEvent::Touch { phase, id, x, y }));
651 }
652
653 fn char_event(&mut self, character: char, modifiers: KeyMods, repeat: bool) {
654 let context = get_context();
655
656 context.chars_pressed_queue.push(character);
657 context.chars_pressed_ui_queue.push(character);
658
659 context.input_events.iter_mut().for_each(|arr| {
660 arr.push(MiniquadInputEvent::Char {
661 character,
662 modifiers,
663 repeat,
664 });
665 });
666 }
667
668 fn key_down_event(&mut self, keycode: KeyCode, modifiers: KeyMods, repeat: bool) {
669 let context = get_context();
670 context.keys_down.insert(keycode);
671 if repeat == false {
672 context.keys_pressed.insert(keycode);
673 }
674
675 context.input_events.iter_mut().for_each(|arr| {
676 arr.push(MiniquadInputEvent::KeyDown {
677 keycode,
678 modifiers,
679 repeat,
680 });
681 });
682 if context
683 .update_on
684 .specific_key
685 .as_ref()
686 .map_or(context.update_on.key_down, |keys| keys.contains(&keycode))
687 {
688 miniquad::window::schedule_update();
689 }
690 }
691
692 fn key_up_event(&mut self, keycode: KeyCode, modifiers: KeyMods) {
693 let context = get_context();
694 context.keys_down.remove(&keycode);
695 context.keys_released.insert(keycode);
696
697 context
698 .input_events
699 .iter_mut()
700 .for_each(|arr| arr.push(MiniquadInputEvent::KeyUp { keycode, modifiers }));
701
702 if miniquad::window::blocking_event_loop() {
703 }
705 }
706
707 fn update(&mut self) {
708 let _z = telemetry::ZoneGuard::new("Event::update");
709
710 let context = get_context();
713 if context.cursor_grabbed || context.cursor_grabbed != context.previous_cursor_grabbed {
714 miniquad::window::set_cursor_grab(context.cursor_grabbed);
715 }
716 context.previous_cursor_grabbed = context.cursor_grabbed;
717
718 #[cfg(not(target_arch = "wasm32"))]
719 {
720 std::thread::yield_now();
722 }
723 }
724
725 fn files_dropped_event(&mut self) {
726 let context = get_context();
727 for i in 0..miniquad::window::dropped_file_count() {
728 context.dropped_files.push(DroppedFile {
729 path: miniquad::window::dropped_file_path(i),
730 bytes: miniquad::window::dropped_file_bytes(i),
731 });
732 }
733 }
734
735 fn draw(&mut self) {
736 {
737 let _z = telemetry::ZoneGuard::new("Event::draw");
738
739 use std::panic;
740
741 {
742 let _z = telemetry::ZoneGuard::new("Event::draw begin_frame");
743 get_context().begin_frame();
744 }
745
746 fn maybe_unwind(unwind: bool, f: impl FnOnce() + Sized + panic::UnwindSafe) -> bool {
747 if unwind {
748 panic::catch_unwind(f).is_ok()
749 } else {
750 f();
751 true
752 }
753 }
754
755 let result = maybe_unwind(
756 get_context().unwind,
757 AssertUnwindSafe(|| {
758 let _z = telemetry::ZoneGuard::new("Event::draw user code");
759
760 if exec::resume(&mut self.main_future).is_some() {
761 self.main_future = Box::pin(async move {});
762 miniquad::window::quit();
763 return;
764 }
765 get_context().coroutines_context.update();
766 }),
767 );
768
769 if result == false {
770 if let Some(recovery_future) = get_context().recovery_future.take() {
771 self.main_future = recovery_future;
772 }
773 }
774
775 {
776 let _z = telemetry::ZoneGuard::new("Event::draw end_frame");
777 get_context().end_frame();
778 }
779 get_context().frame_time = date::now() - get_context().last_frame_time;
780 get_context().last_frame_time = date::now();
781
782 #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
783 {
784 let _z = telemetry::ZoneGuard::new("glFinish/glFLush");
785
786 unsafe {
787 miniquad::gl::glFlush();
788 miniquad::gl::glFinish();
789 }
790 }
791 }
792
793 telemetry::reset();
794 }
795
796 fn window_restored_event(&mut self) {
797 let context = get_context();
798
799 #[cfg(target_os = "android")]
800 context.audio_context.resume();
801 #[cfg(target_os = "android")]
802 if miniquad::window::blocking_event_loop() {
803 miniquad::window::schedule_update();
804 }
805
806 context
807 .input_events
808 .iter_mut()
809 .for_each(|arr| arr.push(MiniquadInputEvent::WindowRestored));
810 }
811
812 fn window_minimized_event(&mut self) {
813 let context = get_context();
814
815 #[cfg(target_os = "android")]
816 context.audio_context.pause();
817
818 context.mouse_released.extend(context.mouse_down.drain());
820 context.keys_released.extend(context.keys_down.drain());
821
822 for (_, touch) in context.touches.iter_mut() {
824 touch.phase = input::TouchPhase::Ended;
825 }
826
827 context
828 .input_events
829 .iter_mut()
830 .for_each(|arr| arr.push(MiniquadInputEvent::WindowMinimized));
831 }
832
833 fn quit_requested_event(&mut self) {
834 let context = get_context();
835 if context.prevent_quit_event {
836 miniquad::window::cancel_quit();
837 context.quit_requested = true;
838 }
839 }
840}
841
842pub mod conf {
843 #[derive(Default, Debug)]
844 pub struct UpdateTrigger {
845 pub key_down: bool,
846 pub mouse_down: bool,
847 pub mouse_up: bool,
848 pub mouse_motion: bool,
849 pub mouse_wheel: bool,
850 pub specific_key: Option<Vec<crate::KeyCode>>,
851 pub touch: bool,
852 }
853
854 #[derive(Debug)]
855 pub struct Conf {
856 pub miniquad_conf: miniquad::conf::Conf,
857 pub update_on: Option<UpdateTrigger>,
862 pub default_filter_mode: crate::FilterMode,
863 pub draw_call_vertex_capacity: usize,
874 pub draw_call_index_capacity: usize,
875 }
876
877 impl Default for Conf {
878 fn default() -> Self {
879 Self {
880 miniquad_conf: miniquad::conf::Conf::default(),
881 update_on: Some(UpdateTrigger::default()),
882 default_filter_mode: crate::FilterMode::Linear,
883 draw_call_vertex_capacity: 10000,
884 draw_call_index_capacity: 5000,
885 }
886 }
887 }
888}
889
890impl From<miniquad::conf::Conf> for conf::Conf {
891 fn from(conf: miniquad::conf::Conf) -> conf::Conf {
892 conf::Conf {
893 miniquad_conf: conf,
894 update_on: None,
895 default_filter_mode: crate::FilterMode::Linear,
896 draw_call_vertex_capacity: 10000,
897 draw_call_index_capacity: 5000,
898 }
899 }
900}
901
902#[doc(hidden)]
904pub struct Window {}
905
906impl Window {
907 pub fn new(label: &str, future: impl Future<Output = ()> + 'static) {
908 Window::from_config(
909 conf::Conf {
910 miniquad_conf: miniquad::conf::Conf {
911 window_title: label.to_string(),
912 ..Default::default()
913 },
914 ..Default::default()
915 },
916 future,
917 );
918 }
919
920 pub fn from_config(config: impl Into<conf::Conf>, future: impl Future<Output = ()> + 'static) {
921 let conf::Conf {
922 miniquad_conf,
923 update_on,
924 default_filter_mode,
925 draw_call_vertex_capacity,
926 draw_call_index_capacity,
927 } = config.into();
928 miniquad::start(miniquad_conf, move || {
929 thread_assert::set_thread_id();
930 let context = Context::new(
931 update_on.unwrap_or_default(),
932 default_filter_mode,
933 draw_call_vertex_capacity,
934 draw_call_index_capacity,
935 );
936 unsafe { CONTEXT = Some(context) };
937
938 Box::new(Stage {
939 main_future: Box::pin(async {
940 future.await;
941 unsafe {
942 if let Some(ctx) = CONTEXT.as_mut() {
943 ctx.gl.reset();
944 }
945 }
946 }),
947 })
948 });
949 }
950}
951
952pub struct DroppedFile {
954 pub path: Option<std::path::PathBuf>,
955 pub bytes: Option<Vec<u8>>,
956}