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