Mercurial > repos > blastem
comparison render_sdl.c @ 1573:a051d8ee4528
Only save config file if something has changed. Re-initialize audio and video with new settings if config has changed
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Fri, 27 Apr 2018 20:08:47 -0700 |
parents | 66387b1645e4 |
children | ade5b8148caa |
comparison
equal
deleted
inserted
replaced
1572:5efeca06d942 | 1573:a051d8ee4528 |
---|---|
46 | 46 |
47 struct audio_source { | 47 struct audio_source { |
48 SDL_cond *cond; | 48 SDL_cond *cond; |
49 int16_t *front; | 49 int16_t *front; |
50 int16_t *back; | 50 int16_t *back; |
51 double dt; | |
51 uint64_t buffer_fraction; | 52 uint64_t buffer_fraction; |
52 uint64_t buffer_inc; | 53 uint64_t buffer_inc; |
53 uint32_t buffer_pos; | 54 uint32_t buffer_pos; |
54 uint32_t read_start; | 55 uint32_t read_start; |
55 uint32_t read_end; | 56 uint32_t read_end; |
255 fatal_error("Too many audio sources!"); | 256 fatal_error("Too many audio sources!"); |
256 } else { | 257 } else { |
257 render_audio_adjust_clock(ret, master_clock, sample_divider); | 258 render_audio_adjust_clock(ret, master_clock, sample_divider); |
258 double lowpass_cutoff = get_lowpass_cutoff(config); | 259 double lowpass_cutoff = get_lowpass_cutoff(config); |
259 double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); | 260 double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); |
260 double dt = 1.0 / ((double)master_clock / (double)(sample_divider)); | 261 ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider)); |
261 double alpha = dt / (dt + rc); | 262 double alpha = ret->dt / (ret->dt + rc); |
262 ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); | 263 ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); |
263 ret->buffer_pos = 0; | 264 ret->buffer_pos = 0; |
264 ret->buffer_fraction = 0; | 265 ret->buffer_fraction = 0; |
265 ret->last_left = ret->last_right = 0; | 266 ret->last_left = ret->last_right = 0; |
266 ret->read_start = ret->read_end = 0; | 267 ret->read_start = ret->read_end = 0; |
522 un_height = glGetUniformLocation(program, "height"); | 523 un_height = glGetUniformLocation(program, "height"); |
523 at_pos = glGetAttribLocation(program, "pos"); | 524 at_pos = glGetAttribLocation(program, "pos"); |
524 } | 525 } |
525 #endif | 526 #endif |
526 | 527 |
528 static uint8_t texture_init; | |
527 static void render_alloc_surfaces() | 529 static void render_alloc_surfaces() |
528 { | 530 { |
529 static uint8_t texture_init; | |
530 | |
531 if (texture_init) { | 531 if (texture_init) { |
532 return; | 532 return; |
533 } | 533 } |
534 sdl_textures= malloc(sizeof(SDL_Texture *) * 2); | 534 sdl_textures= malloc(sizeof(SDL_Texture *) * 2); |
535 num_textures = 2; | 535 num_textures = 2; |
548 #ifndef DISABLE_OPENGL | 548 #ifndef DISABLE_OPENGL |
549 } | 549 } |
550 #endif | 550 #endif |
551 } | 551 } |
552 | 552 |
553 static char * caption = NULL; | 553 static void free_surfaces(void) |
554 static char * fps_caption = NULL; | 554 { |
555 | |
556 static void render_quit() | |
557 { | |
558 render_close_audio(); | |
559 for (int i = 0; i < num_textures; i++) | 555 for (int i = 0; i < num_textures; i++) |
560 { | 556 { |
561 if (sdl_textures[i]) { | 557 if (sdl_textures[i]) { |
562 SDL_DestroyTexture(sdl_textures[i]); | 558 SDL_DestroyTexture(sdl_textures[i]); |
563 } | 559 } |
564 } | 560 } |
561 free(sdl_textures); | |
562 sdl_textures = NULL; | |
563 texture_init = 0; | |
564 } | |
565 | |
566 static char * caption = NULL; | |
567 static char * fps_caption = NULL; | |
568 | |
569 static void render_quit() | |
570 { | |
571 render_close_audio(); | |
572 free_surfaces(); | |
565 } | 573 } |
566 | 574 |
567 static float config_aspect() | 575 static float config_aspect() |
568 { | 576 { |
569 static float aspect = 0.0f; | 577 static float aspect = 0.0f; |
619 } | 627 } |
620 #endif | 628 #endif |
621 } | 629 } |
622 } | 630 } |
623 | 631 |
624 static uint32_t overscan_top[NUM_VID_STD] = {2, 21}; | 632 static ui_render_fun on_context_destroyed, on_context_created; |
625 static uint32_t overscan_bot[NUM_VID_STD] = {1, 17}; | 633 void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create) |
626 static uint32_t overscan_left[NUM_VID_STD] = {13, 13}; | 634 { |
627 static uint32_t overscan_right[NUM_VID_STD] = {14, 14}; | 635 on_context_destroyed = destroy; |
628 static vid_std video_standard = VID_NTSC; | 636 on_context_created = create; |
629 static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"}; | |
630 static int display_hz; | |
631 static int source_hz; | |
632 static int source_frame; | |
633 static int source_frame_count; | |
634 static int frame_repeat[60]; | |
635 void render_init(int width, int height, char * title, uint8_t fullscreen) | |
636 { | |
637 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { | |
638 fatal_error("Unable to init SDL: %s\n", SDL_GetError()); | |
639 } | |
640 atexit(SDL_Quit); | |
641 if (height <= 0) { | |
642 float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; | |
643 height = ((float)width / aspect) + 0.5f; | |
644 } | |
645 printf("width: %d, height: %d\n", width, height); | |
646 windowed_width = width; | |
647 windowed_height = height; | |
648 | |
649 uint32_t flags = SDL_WINDOW_RESIZABLE; | |
650 | |
651 SDL_DisplayMode mode; | |
652 //TODO: Explicit multiple monitor support | |
653 SDL_GetCurrentDisplayMode(0, &mode); | |
654 display_hz = mode.refresh_rate; | |
655 | |
656 if (fullscreen) { | |
657 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; | |
658 //the SDL2 migration guide suggests setting width and height to 0 when using SDL_WINDOW_FULLSCREEN_DESKTOP | |
659 //but that doesn't seem to work right when using OpenGL, at least on Linux anyway | |
660 width = mode.w; | |
661 height = mode.h; | |
662 } | |
663 main_width = width; | |
664 main_height = height; | |
665 is_fullscreen = fullscreen; | |
666 | |
667 tern_val def = {.ptrval = "video"}; | |
668 char *sync_src = tern_find_path_default(config, "system\0sync_source\0", def, TVAL_PTR).ptrval; | |
669 sync_to_audio = !strcmp(sync_src, "audio"); | |
670 | |
671 render_gl = 0; | |
672 char *vsync; | |
673 if (sync_to_audio) { | |
674 def.ptrval = "off"; | |
675 vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval; | |
676 } else { | |
677 vsync = "on"; | |
678 } | |
679 | |
680 tern_node *video = tern_find_node(config, "video"); | |
681 if (video) | |
682 { | |
683 for (int i = 0; i < NUM_VID_STD; i++) | |
684 { | |
685 tern_node *std_settings = tern_find_node(video, vid_std_names[i]); | |
686 if (std_settings) { | |
687 char *val = tern_find_path_default(std_settings, "overscan\0top\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
688 if (val) { | |
689 overscan_top[i] = atoi(val); | |
690 } | |
691 val = tern_find_path_default(std_settings, "overscan\0bottom\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
692 if (val) { | |
693 overscan_bot[i] = atoi(val); | |
694 } | |
695 val = tern_find_path_default(std_settings, "overscan\0left\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
696 if (val) { | |
697 overscan_left[i] = atoi(val); | |
698 } | |
699 val = tern_find_path_default(std_settings, "overscan\0right\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
700 if (val) { | |
701 overscan_right[i] = atoi(val); | |
702 } | |
703 } | |
704 } | |
705 } | |
706 | |
707 #ifndef DISABLE_OPENGL | |
708 char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval; | |
709 uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0; | |
710 if (gl_enabled) | |
711 { | |
712 flags |= SDL_WINDOW_OPENGL; | |
713 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); | |
714 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); | |
715 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); | |
716 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); | |
717 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |
718 } | |
719 #endif | |
720 main_window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); | |
721 if (!main_window) { | |
722 fatal_error("Unable to create SDL window: %s\n", SDL_GetError()); | |
723 } | |
724 #ifndef DISABLE_OPENGL | |
725 if (gl_enabled) | |
726 { | |
727 main_context = SDL_GL_CreateContext(main_window); | |
728 GLenum res = glewInit(); | |
729 if (res != GLEW_OK) { | |
730 warning("Initialization of GLEW failed with code %d\n", res); | |
731 } | |
732 | |
733 if (res == GLEW_OK && GLEW_VERSION_2_0) { | |
734 render_gl = 1; | |
735 if (!strcmp("tear", vsync)) { | |
736 if (SDL_GL_SetSwapInterval(-1) < 0) { | |
737 warning("late tear is not available (%s), using normal vsync\n", SDL_GetError()); | |
738 vsync = "on"; | |
739 } else { | |
740 vsync = NULL; | |
741 } | |
742 } | |
743 if (vsync) { | |
744 if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) { | |
745 warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); | |
746 } | |
747 } | |
748 } else { | |
749 warning("OpenGL 2.0 is unavailable, falling back to SDL2 renderer\n"); | |
750 } | |
751 } | |
752 if (!render_gl) { | |
753 #endif | |
754 flags = SDL_RENDERER_ACCELERATED; | |
755 if (!strcmp("on", vsync) || !strcmp("tear", vsync)) { | |
756 flags |= SDL_RENDERER_PRESENTVSYNC; | |
757 } | |
758 main_renderer = SDL_CreateRenderer(main_window, -1, flags); | |
759 | |
760 if (!main_renderer) { | |
761 fatal_error("unable to create SDL renderer: %s\n", SDL_GetError()); | |
762 } | |
763 main_clip.x = main_clip.y = 0; | |
764 main_clip.w = width; | |
765 main_clip.h = height; | |
766 #ifndef DISABLE_OPENGL | |
767 } | |
768 #endif | |
769 | |
770 SDL_GetWindowSize(main_window, &main_width, &main_height); | |
771 printf("Window created with size: %d x %d\n", main_width, main_height); | |
772 update_aspect(); | |
773 render_alloc_surfaces(); | |
774 def.ptrval = "off"; | |
775 scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def, TVAL_PTR).ptrval, "on"); | |
776 | |
777 caption = title; | |
778 | |
779 audio_mutex = SDL_CreateMutex(); | |
780 audio_ready = SDL_CreateCond(); | |
781 | |
782 SDL_AudioSpec desired, actual; | |
783 char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval; | |
784 int rate = rate_str ? atoi(rate_str) : 0; | |
785 if (!rate) { | |
786 rate = 48000; | |
787 } | |
788 desired.freq = rate; | |
789 desired.format = AUDIO_S16SYS; | |
790 desired.channels = 2; | |
791 char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval; | |
792 int samples = samples_str ? atoi(samples_str) : 0; | |
793 if (!samples) { | |
794 samples = 512; | |
795 } | |
796 printf("config says: %d\n", samples); | |
797 desired.samples = samples*2; | |
798 desired.callback = sync_to_audio ? audio_callback : audio_callback_drc; | |
799 desired.userdata = NULL; | |
800 | |
801 if (SDL_OpenAudio(&desired, &actual) < 0) { | |
802 fatal_error("Unable to open SDL audio: %s\n", SDL_GetError()); | |
803 } | |
804 buffer_samples = actual.samples; | |
805 sample_rate = actual.freq; | |
806 printf("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples); | |
807 if (actual.format == AUDIO_S16SYS) { | |
808 puts("signed 16-bit int format"); | |
809 mix = mix_s16; | |
810 } else if (actual.format == AUDIO_F32SYS) { | |
811 puts("32-bit float format"); | |
812 mix = mix_f32; | |
813 } else { | |
814 printf("unsupported format %X\n", actual.format); | |
815 warning("Unsupported audio sample format: %X\n", actual.format); | |
816 mix = mix_null; | |
817 } | |
818 | |
819 uint32_t db_size; | |
820 char *db_data = read_bundled_file("gamecontrollerdb.txt", &db_size); | |
821 if (db_data) { | |
822 int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1); | |
823 free(db_data); | |
824 printf("Added %d game controller mappings from gamecontrollerdb.txt\n", added); | |
825 } | |
826 | |
827 SDL_JoystickEventState(SDL_ENABLE); | |
828 | |
829 render_set_video_standard(VID_NTSC); | |
830 | |
831 atexit(render_quit); | |
832 } | |
833 | |
834 SDL_Window *render_get_window(void) | |
835 { | |
836 return main_window; | |
837 } | |
838 | |
839 void render_set_video_standard(vid_std std) | |
840 { | |
841 video_standard = std; | |
842 source_hz = std == VID_PAL ? 50 : 60; | |
843 uint32_t max_repeat = 0; | |
844 if (abs(source_hz - display_hz) < 2) { | |
845 memset(frame_repeat, 0, sizeof(int)*display_hz); | |
846 } else { | |
847 int inc = display_hz * 100000 / source_hz; | |
848 int accum = 0; | |
849 int dst_frames = 0; | |
850 for (int src_frame = 0; src_frame < source_hz; src_frame++) | |
851 { | |
852 frame_repeat[src_frame] = -1; | |
853 accum += inc; | |
854 while (accum > 100000) | |
855 { | |
856 accum -= 100000; | |
857 frame_repeat[src_frame]++; | |
858 max_repeat = frame_repeat[src_frame] > max_repeat ? frame_repeat[src_frame] : max_repeat; | |
859 dst_frames++; | |
860 } | |
861 } | |
862 if (dst_frames != display_hz) { | |
863 frame_repeat[source_hz-1] += display_hz - dst_frames; | |
864 } | |
865 } | |
866 source_frame = 0; | |
867 source_frame_count = frame_repeat[0]; | |
868 //sync samples with audio thread approximately every 8 lines | |
869 sync_samples = 8 * sample_rate / (source_hz * (VID_PAL ? 313 : 262)); | |
870 max_repeat++; | |
871 min_buffered = (((float)max_repeat * (float)sample_rate/(float)source_hz)/* / (float)buffer_samples*/);// + 0.9999; | |
872 //min_buffered *= buffer_samples; | |
873 printf("Min samples buffered before audio start: %d\n", min_buffered); | |
874 max_adjust = BASE_MAX_ADJUST / source_hz; | |
875 } | |
876 | |
877 void render_update_caption(char *title) | |
878 { | |
879 caption = title; | |
880 free(fps_caption); | |
881 fps_caption = NULL; | |
882 } | |
883 | |
884 static char *screenshot_path; | |
885 void render_save_screenshot(char *path) | |
886 { | |
887 if (screenshot_path) { | |
888 free(screenshot_path); | |
889 } | |
890 screenshot_path = path; | |
891 } | |
892 | |
893 uint32_t *locked_pixels; | |
894 uint32_t locked_pitch; | |
895 uint32_t *render_get_framebuffer(uint8_t which, int *pitch) | |
896 { | |
897 #ifndef DISABLE_OPENGL | |
898 if (render_gl && which <= FRAMEBUFFER_EVEN) { | |
899 *pitch = LINEBUF_SIZE * sizeof(uint32_t); | |
900 return texture_buf; | |
901 } else { | |
902 #endif | |
903 if (which >= num_textures) { | |
904 warning("Request for invalid framebuffer number %d\n", which); | |
905 return NULL; | |
906 } | |
907 void *pixels; | |
908 if (SDL_LockTexture(sdl_textures[which], NULL, &pixels, pitch) < 0) { | |
909 warning("Failed to lock texture: %s\n", SDL_GetError()); | |
910 return NULL; | |
911 } | |
912 static uint8_t last; | |
913 if (which <= FRAMEBUFFER_EVEN) { | |
914 locked_pixels = pixels; | |
915 if (which == FRAMEBUFFER_EVEN) { | |
916 pixels += *pitch; | |
917 } | |
918 locked_pitch = *pitch; | |
919 if (which != last) { | |
920 *pitch *= 2; | |
921 } | |
922 last = which; | |
923 } | |
924 return pixels; | |
925 #ifndef DISABLE_OPENGL | |
926 } | |
927 #endif | |
928 } | |
929 | |
930 uint8_t events_processed; | |
931 #ifdef __ANDROID__ | |
932 #define FPS_INTERVAL 10000 | |
933 #else | |
934 #define FPS_INTERVAL 1000 | |
935 #endif | |
936 | |
937 static uint32_t last_width, last_height; | |
938 static uint8_t interlaced; | |
939 void render_framebuffer_updated(uint8_t which, int width) | |
940 { | |
941 static uint8_t last; | |
942 if (!sync_to_audio && which <= FRAMEBUFFER_EVEN && source_frame_count < 0) { | |
943 source_frame++; | |
944 if (source_frame >= source_hz) { | |
945 source_frame = 0; | |
946 } | |
947 source_frame_count = frame_repeat[source_frame]; | |
948 //TODO: Figure out what to do about SDL Render API texture locking | |
949 return; | |
950 } | |
951 | |
952 last_width = width; | |
953 uint32_t height = which <= FRAMEBUFFER_EVEN | |
954 ? (video_standard == VID_NTSC ? 243 : 294) - (overscan_top[video_standard] + overscan_bot[video_standard]) | |
955 : 240; | |
956 FILE *screenshot_file = NULL; | |
957 uint32_t shot_height, shot_width; | |
958 char *ext; | |
959 if (screenshot_path && which == FRAMEBUFFER_ODD) { | |
960 screenshot_file = fopen(screenshot_path, "wb"); | |
961 if (screenshot_file) { | |
962 #ifndef DISABLE_ZLIB | |
963 ext = path_extension(screenshot_path); | |
964 #endif | |
965 info_message("Saving screenshot to %s\n", screenshot_path); | |
966 } else { | |
967 warning("Failed to open screenshot file %s for writing\n", screenshot_path); | |
968 } | |
969 free(screenshot_path); | |
970 screenshot_path = NULL; | |
971 shot_height = video_standard == VID_NTSC ? 243 : 294; | |
972 shot_width = width; | |
973 } | |
974 interlaced = last != which; | |
975 width -= overscan_left[video_standard] + overscan_right[video_standard]; | |
976 #ifndef DISABLE_OPENGL | |
977 if (render_gl && which <= FRAMEBUFFER_EVEN) { | |
978 glBindTexture(GL_TEXTURE_2D, textures[which]); | |
979 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LINEBUF_SIZE, height, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]); | |
980 | |
981 if (screenshot_file) { | |
982 //properly supporting interlaced modes here is non-trivial, so only save the odd field for now | |
983 #ifndef DISABLE_ZLIB | |
984 if (!strcasecmp(ext, "png")) { | |
985 free(ext); | |
986 save_png(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); | |
987 } else { | |
988 free(ext); | |
989 #endif | |
990 save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); | |
991 #ifndef DISABLE_ZLIB | |
992 } | |
993 #endif | |
994 } | |
995 } else { | |
996 #endif | |
997 if (which <= FRAMEBUFFER_EVEN && last != which) { | |
998 uint8_t *cur_dst = (uint8_t *)locked_pixels; | |
999 uint8_t *cur_saved = (uint8_t *)texture_buf; | |
1000 uint32_t dst_off = which == FRAMEBUFFER_EVEN ? 0 : locked_pitch; | |
1001 uint32_t src_off = which == FRAMEBUFFER_EVEN ? locked_pitch : 0; | |
1002 for (int i = 0; i < height; ++i) | |
1003 { | |
1004 //copy saved line from other field | |
1005 memcpy(cur_dst + dst_off, cur_saved, locked_pitch); | |
1006 //save line from this field to buffer for next frame | |
1007 memcpy(cur_saved, cur_dst + src_off, locked_pitch); | |
1008 cur_dst += locked_pitch * 2; | |
1009 cur_saved += locked_pitch; | |
1010 } | |
1011 height = 480; | |
1012 } | |
1013 if (screenshot_file) { | |
1014 uint32_t shot_pitch = locked_pitch; | |
1015 if (which == FRAMEBUFFER_EVEN) { | |
1016 shot_height *= 2; | |
1017 } else { | |
1018 shot_pitch *= 2; | |
1019 } | |
1020 #ifndef DISABLE_ZLIB | |
1021 if (!strcasecmp(ext, "png")) { | |
1022 free(ext); | |
1023 save_png(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch); | |
1024 } else { | |
1025 free(ext); | |
1026 #endif | |
1027 save_ppm(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch); | |
1028 #ifndef DISABLE_ZLIB | |
1029 } | |
1030 #endif | |
1031 } | |
1032 SDL_UnlockTexture(sdl_textures[which]); | |
1033 #ifndef DISABLE_OPENGL | |
1034 } | |
1035 #endif | |
1036 last_height = height; | |
1037 render_update_display(); | |
1038 if (screenshot_file) { | |
1039 fclose(screenshot_file); | |
1040 } | |
1041 if (which <= FRAMEBUFFER_EVEN) { | |
1042 last = which; | |
1043 static uint32_t frame_counter, start; | |
1044 frame_counter++; | |
1045 last_frame= SDL_GetTicks(); | |
1046 if ((last_frame - start) > FPS_INTERVAL) { | |
1047 if (start && (last_frame-start)) { | |
1048 #ifdef __ANDROID__ | |
1049 info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); | |
1050 #else | |
1051 if (!fps_caption) { | |
1052 fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1); | |
1053 } | |
1054 sprintf(fps_caption, "%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); | |
1055 SDL_SetWindowTitle(main_window, fps_caption); | |
1056 #endif | |
1057 } | |
1058 start = last_frame; | |
1059 frame_counter = 0; | |
1060 } | |
1061 } | |
1062 if (!sync_to_audio) { | |
1063 int32_t local_cur_min, local_min_remaining; | |
1064 SDL_LockAudio(); | |
1065 if (last_buffered > NO_LAST_BUFFERED) { | |
1066 average_change *= 0.9f; | |
1067 average_change += (cur_min_buffered - last_buffered) * 0.1f; | |
1068 } | |
1069 local_cur_min = cur_min_buffered; | |
1070 local_min_remaining = min_remaining_buffer; | |
1071 last_buffered = cur_min_buffered; | |
1072 SDL_UnlockAudio(); | |
1073 float frames_to_problem; | |
1074 if (average_change < 0) { | |
1075 frames_to_problem = (float)local_cur_min / -average_change; | |
1076 } else { | |
1077 frames_to_problem = (float)local_min_remaining / average_change; | |
1078 } | |
1079 float adjust_ratio = 0.0f; | |
1080 if ( | |
1081 frames_to_problem < BUFFER_FRAMES_THRESHOLD | |
1082 || (average_change < 0 && local_cur_min < 3*min_buffered / 4) | |
1083 || (average_change >0 && local_cur_min > 5 * min_buffered / 4) | |
1084 ) { | |
1085 | |
1086 if (cur_min_buffered < 0) { | |
1087 adjust_ratio = max_adjust; | |
1088 SDL_PauseAudio(1); | |
1089 last_buffered = NO_LAST_BUFFERED; | |
1090 } else { | |
1091 adjust_ratio = -1.0 * average_change / ((float)sample_rate / (float)source_hz); | |
1092 adjust_ratio /= 2.5 * source_hz; | |
1093 if (fabsf(adjust_ratio) > max_adjust) { | |
1094 adjust_ratio = adjust_ratio > 0 ? max_adjust : -max_adjust; | |
1095 } | |
1096 } | |
1097 } else if (local_cur_min < min_buffered / 2) { | |
1098 adjust_ratio = max_adjust; | |
1099 } | |
1100 if (adjust_ratio != 0.0f) { | |
1101 average_change = 0; | |
1102 for (uint8_t i = 0; i < num_audio_sources; i++) | |
1103 { | |
1104 audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5; | |
1105 } | |
1106 } | |
1107 while (source_frame_count > 0) | |
1108 { | |
1109 render_update_display(); | |
1110 source_frame_count--; | |
1111 } | |
1112 source_frame++; | |
1113 if (source_frame >= source_hz) { | |
1114 source_frame = 0; | |
1115 } | |
1116 source_frame_count = frame_repeat[source_frame]; | |
1117 } | |
1118 } | |
1119 | |
1120 static ui_render_fun render_ui; | |
1121 void render_set_ui_render_fun(ui_render_fun fun) | |
1122 { | |
1123 render_ui = fun; | |
1124 } | |
1125 | |
1126 void render_update_display() | |
1127 { | |
1128 #ifndef DISABLE_OPENGL | |
1129 if (render_gl) { | |
1130 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
1131 glClear(GL_COLOR_BUFFER_BIT); | |
1132 | |
1133 glUseProgram(program); | |
1134 glActiveTexture(GL_TEXTURE0); | |
1135 glBindTexture(GL_TEXTURE_2D, textures[0]); | |
1136 glUniform1i(un_textures[0], 0); | |
1137 | |
1138 glActiveTexture(GL_TEXTURE1); | |
1139 glBindTexture(GL_TEXTURE_2D, textures[interlaced ? 1 : scanlines ? 2 : 0]); | |
1140 glUniform1i(un_textures[1], 1); | |
1141 | |
1142 glUniform1f(un_width, render_emulated_width()); | |
1143 glUniform1f(un_height, last_height); | |
1144 | |
1145 glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); | |
1146 glVertexAttribPointer(at_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat[2]), (void *)0); | |
1147 glEnableVertexAttribArray(at_pos); | |
1148 | |
1149 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); | |
1150 glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void *)0); | |
1151 | |
1152 glDisableVertexAttribArray(at_pos); | |
1153 | |
1154 if (render_ui) { | |
1155 render_ui(); | |
1156 } | |
1157 | |
1158 SDL_GL_SwapWindow(main_window); | |
1159 } else { | |
1160 #endif | |
1161 SDL_Rect src_clip = { | |
1162 .x = overscan_left[video_standard], | |
1163 .y = overscan_top[video_standard], | |
1164 .w = render_emulated_width(), | |
1165 .h = last_height | |
1166 }; | |
1167 SDL_SetRenderDrawColor(main_renderer, 0, 0, 0, 255); | |
1168 SDL_RenderClear(main_renderer); | |
1169 SDL_RenderCopy(main_renderer, sdl_textures[FRAMEBUFFER_ODD], &src_clip, &main_clip); | |
1170 if (render_ui) { | |
1171 render_ui(); | |
1172 } | |
1173 SDL_RenderPresent(main_renderer); | |
1174 #ifndef DISABLE_OPENGL | |
1175 } | |
1176 #endif | |
1177 if (!events_processed) { | |
1178 process_events(); | |
1179 } | |
1180 events_processed = 0; | |
1181 } | |
1182 | |
1183 uint32_t render_emulated_width() | |
1184 { | |
1185 return last_width - overscan_left[video_standard] - overscan_right[video_standard]; | |
1186 } | |
1187 | |
1188 uint32_t render_emulated_height() | |
1189 { | |
1190 return (video_standard == VID_NTSC ? 243 : 294) - overscan_top[video_standard] - overscan_bot[video_standard]; | |
1191 } | |
1192 | |
1193 uint32_t render_overscan_left() | |
1194 { | |
1195 return overscan_left[video_standard]; | |
1196 } | |
1197 | |
1198 uint32_t render_overscan_top() | |
1199 { | |
1200 return overscan_top[video_standard]; | |
1201 } | |
1202 | |
1203 void render_wait_quit(vdp_context * context) | |
1204 { | |
1205 SDL_Event event; | |
1206 while(SDL_WaitEvent(&event)) { | |
1207 switch (event.type) { | |
1208 case SDL_QUIT: | |
1209 return; | |
1210 } | |
1211 } | |
1212 } | |
1213 | |
1214 static int find_joystick_index(SDL_JoystickID instanceID) | |
1215 { | |
1216 for (int i = 0; i < MAX_JOYSTICKS; i++) { | |
1217 if (joysticks[i] && SDL_JoystickInstanceID(joysticks[i]) == instanceID) { | |
1218 return i; | |
1219 } | |
1220 } | |
1221 return -1; | |
1222 } | |
1223 | |
1224 static int lowest_unused_joystick_index() | |
1225 { | |
1226 for (int i = 0; i < MAX_JOYSTICKS; i++) { | |
1227 if (!joysticks[i]) { | |
1228 return i; | |
1229 } | |
1230 } | |
1231 return -1; | |
1232 } | |
1233 | |
1234 int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis) | |
1235 { | |
1236 static tern_node *button_lookup, *axis_lookup; | |
1237 if (controller > MAX_JOYSTICKS || !joysticks[controller]) { | |
1238 return RENDER_NOT_PLUGGED_IN; | |
1239 } | |
1240 | |
1241 if (!SDL_IsGameController(joystick_sdl_index[controller])) { | |
1242 return RENDER_NOT_MAPPED; | |
1243 } | |
1244 SDL_GameController *control = SDL_GameControllerOpen(joystick_sdl_index[controller]); | |
1245 if (!control) { | |
1246 warning("Failed to open game controller %d: %s\n", controller, SDL_GetError()); | |
1247 return RENDER_NOT_PLUGGED_IN; | |
1248 } | |
1249 | |
1250 SDL_GameControllerButtonBind cbind; | |
1251 if (is_axis) { | |
1252 if (!axis_lookup) { | |
1253 for (int i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) | |
1254 { | |
1255 axis_lookup = tern_insert_int(axis_lookup, SDL_GameControllerGetStringForAxis(i), i); | |
1256 } | |
1257 //alternative Playstation-style names | |
1258 axis_lookup = tern_insert_int(axis_lookup, "l2", SDL_CONTROLLER_AXIS_TRIGGERLEFT); | |
1259 axis_lookup = tern_insert_int(axis_lookup, "r2", SDL_CONTROLLER_AXIS_TRIGGERRIGHT); | |
1260 } | |
1261 intptr_t sdl_axis = tern_find_int(axis_lookup, name, SDL_CONTROLLER_AXIS_INVALID); | |
1262 if (sdl_axis == SDL_CONTROLLER_AXIS_INVALID) { | |
1263 SDL_GameControllerClose(control); | |
1264 return RENDER_INVALID_NAME; | |
1265 } | |
1266 cbind = SDL_GameControllerGetBindForAxis(control, sdl_axis); | |
1267 } else { | |
1268 if (!button_lookup) { | |
1269 for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) | |
1270 { | |
1271 button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i); | |
1272 } | |
1273 //alternative Playstation-style names | |
1274 button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A); | |
1275 button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B); | |
1276 button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X); | |
1277 button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y); | |
1278 button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK); | |
1279 button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK); | |
1280 button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START); | |
1281 button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER); | |
1282 button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); | |
1283 button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK); | |
1284 button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK); | |
1285 } | |
1286 intptr_t sdl_button = tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID); | |
1287 if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) { | |
1288 SDL_GameControllerClose(control); | |
1289 return RENDER_INVALID_NAME; | |
1290 } | |
1291 cbind = SDL_GameControllerGetBindForButton(control, sdl_button); | |
1292 } | |
1293 SDL_GameControllerClose(control); | |
1294 switch (cbind.bindType) | |
1295 { | |
1296 case SDL_CONTROLLER_BINDTYPE_BUTTON: | |
1297 return cbind.value.button; | |
1298 case SDL_CONTROLLER_BINDTYPE_AXIS: | |
1299 return RENDER_AXIS_BIT | cbind.value.axis; | |
1300 case SDL_CONTROLLER_BINDTYPE_HAT: | |
1301 return RENDER_DPAD_BIT | (cbind.value.hat.hat << 4) | cbind.value.hat.hat_mask; | |
1302 } | |
1303 return RENDER_NOT_MAPPED; | |
1304 } | |
1305 | |
1306 int32_t render_dpad_part(int32_t input) | |
1307 { | |
1308 return input >> 4 & 0xFFFFFF; | |
1309 } | |
1310 | |
1311 uint8_t render_direction_part(int32_t input) | |
1312 { | |
1313 return input & 0xF; | |
1314 } | |
1315 | |
1316 int32_t render_axis_part(int32_t input) | |
1317 { | |
1318 return input & 0xFFFFFFF; | |
1319 } | 637 } |
1320 | 638 |
1321 static uint8_t scancode_map[SDL_NUM_SCANCODES] = { | 639 static uint8_t scancode_map[SDL_NUM_SCANCODES] = { |
1322 [SDL_SCANCODE_A] = 0x1C, | 640 [SDL_SCANCODE_A] = 0x1C, |
1323 [SDL_SCANCODE_B] = 0x32, | 641 [SDL_SCANCODE_B] = 0x32, |
1426 void render_set_drag_drop_handler(drop_handler handler) | 744 void render_set_drag_drop_handler(drop_handler handler) |
1427 { | 745 { |
1428 drag_drop_handler = handler; | 746 drag_drop_handler = handler; |
1429 } | 747 } |
1430 | 748 |
1431 static ui_render_fun on_context_destroyed, on_context_created; | |
1432 void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create) | |
1433 { | |
1434 on_context_destroyed = destroy; | |
1435 on_context_created = create; | |
1436 } | |
1437 | |
1438 static event_handler custom_event_handler; | 749 static event_handler custom_event_handler; |
1439 void render_set_event_handler(event_handler handler) | 750 void render_set_event_handler(event_handler handler) |
1440 { | 751 { |
1441 custom_event_handler = handler; | 752 custom_event_handler = handler; |
1442 } | 753 } |
754 | |
755 static int find_joystick_index(SDL_JoystickID instanceID) | |
756 { | |
757 for (int i = 0; i < MAX_JOYSTICKS; i++) { | |
758 if (joysticks[i] && SDL_JoystickInstanceID(joysticks[i]) == instanceID) { | |
759 return i; | |
760 } | |
761 } | |
762 return -1; | |
763 } | |
764 | |
765 static int lowest_unused_joystick_index() | |
766 { | |
767 for (int i = 0; i < MAX_JOYSTICKS; i++) { | |
768 if (!joysticks[i]) { | |
769 return i; | |
770 } | |
771 } | |
772 return -1; | |
773 } | |
774 | |
775 static uint32_t overscan_top[NUM_VID_STD] = {2, 21}; | |
776 static uint32_t overscan_bot[NUM_VID_STD] = {1, 17}; | |
777 static uint32_t overscan_left[NUM_VID_STD] = {13, 13}; | |
778 static uint32_t overscan_right[NUM_VID_STD] = {14, 14}; | |
779 static vid_std video_standard = VID_NTSC; | |
1443 | 780 |
1444 static int32_t handle_event(SDL_Event *event) | 781 static int32_t handle_event(SDL_Event *event) |
1445 { | 782 { |
1446 if (custom_event_handler) { | 783 if (custom_event_handler) { |
1447 custom_event_handler(event); | 784 custom_event_handler(event); |
1542 { | 879 { |
1543 handle_event(&event); | 880 handle_event(&event); |
1544 } | 881 } |
1545 } | 882 } |
1546 | 883 |
884 static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"}; | |
885 static int display_hz; | |
886 static int source_hz; | |
887 static int source_frame; | |
888 static int source_frame_count; | |
889 static int frame_repeat[60]; | |
890 | |
891 static void init_audio() | |
892 { | |
893 SDL_AudioSpec desired, actual; | |
894 char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval; | |
895 int rate = rate_str ? atoi(rate_str) : 0; | |
896 if (!rate) { | |
897 rate = 48000; | |
898 } | |
899 desired.freq = rate; | |
900 desired.format = AUDIO_S16SYS; | |
901 desired.channels = 2; | |
902 char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval; | |
903 int samples = samples_str ? atoi(samples_str) : 0; | |
904 if (!samples) { | |
905 samples = 512; | |
906 } | |
907 printf("config says: %d\n", samples); | |
908 desired.samples = samples*2; | |
909 desired.callback = sync_to_audio ? audio_callback : audio_callback_drc; | |
910 desired.userdata = NULL; | |
911 | |
912 if (SDL_OpenAudio(&desired, &actual) < 0) { | |
913 fatal_error("Unable to open SDL audio: %s\n", SDL_GetError()); | |
914 } | |
915 buffer_samples = actual.samples; | |
916 sample_rate = actual.freq; | |
917 printf("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples); | |
918 if (actual.format == AUDIO_S16SYS) { | |
919 puts("signed 16-bit int format"); | |
920 mix = mix_s16; | |
921 } else if (actual.format == AUDIO_F32SYS) { | |
922 puts("32-bit float format"); | |
923 mix = mix_f32; | |
924 } else { | |
925 printf("unsupported format %X\n", actual.format); | |
926 warning("Unsupported audio sample format: %X\n", actual.format); | |
927 mix = mix_null; | |
928 } | |
929 } | |
930 | |
931 void window_setup(void) | |
932 { | |
933 uint32_t flags = SDL_WINDOW_RESIZABLE; | |
934 if (is_fullscreen) { | |
935 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; | |
936 } | |
937 | |
938 tern_val def = {.ptrval = "video"}; | |
939 char *sync_src = tern_find_path_default(config, "system\0sync_source\0", def, TVAL_PTR).ptrval; | |
940 sync_to_audio = !strcmp(sync_src, "audio"); | |
941 | |
942 const char *vsync; | |
943 if (sync_to_audio) { | |
944 def.ptrval = "off"; | |
945 vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval; | |
946 } else { | |
947 vsync = "on"; | |
948 } | |
949 | |
950 tern_node *video = tern_find_node(config, "video"); | |
951 if (video) | |
952 { | |
953 for (int i = 0; i < NUM_VID_STD; i++) | |
954 { | |
955 tern_node *std_settings = tern_find_node(video, vid_std_names[i]); | |
956 if (std_settings) { | |
957 char *val = tern_find_path_default(std_settings, "overscan\0top\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
958 if (val) { | |
959 overscan_top[i] = atoi(val); | |
960 } | |
961 val = tern_find_path_default(std_settings, "overscan\0bottom\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
962 if (val) { | |
963 overscan_bot[i] = atoi(val); | |
964 } | |
965 val = tern_find_path_default(std_settings, "overscan\0left\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
966 if (val) { | |
967 overscan_left[i] = atoi(val); | |
968 } | |
969 val = tern_find_path_default(std_settings, "overscan\0right\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
970 if (val) { | |
971 overscan_right[i] = atoi(val); | |
972 } | |
973 } | |
974 } | |
975 } | |
976 | |
977 #ifndef DISABLE_OPENGL | |
978 char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval; | |
979 uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0; | |
980 if (gl_enabled) | |
981 { | |
982 flags |= SDL_WINDOW_OPENGL; | |
983 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); | |
984 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); | |
985 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); | |
986 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); | |
987 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |
988 } | |
989 #endif | |
990 main_window = SDL_CreateWindow(caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, main_width, main_height, flags); | |
991 if (!main_window) { | |
992 fatal_error("Unable to create SDL window: %s\n", SDL_GetError()); | |
993 } | |
994 #ifndef DISABLE_OPENGL | |
995 if (gl_enabled) | |
996 { | |
997 main_context = SDL_GL_CreateContext(main_window); | |
998 GLenum res = glewInit(); | |
999 if (res != GLEW_OK) { | |
1000 warning("Initialization of GLEW failed with code %d\n", res); | |
1001 } | |
1002 | |
1003 if (res == GLEW_OK && GLEW_VERSION_2_0) { | |
1004 render_gl = 1; | |
1005 if (!strcmp("tear", vsync)) { | |
1006 if (SDL_GL_SetSwapInterval(-1) < 0) { | |
1007 warning("late tear is not available (%s), using normal vsync\n", SDL_GetError()); | |
1008 vsync = "on"; | |
1009 } else { | |
1010 vsync = NULL; | |
1011 } | |
1012 } | |
1013 if (vsync) { | |
1014 if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) { | |
1015 warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); | |
1016 } | |
1017 } | |
1018 } else { | |
1019 warning("OpenGL 2.0 is unavailable, falling back to SDL2 renderer\n"); | |
1020 } | |
1021 } | |
1022 if (!render_gl) { | |
1023 #endif | |
1024 flags = SDL_RENDERER_ACCELERATED; | |
1025 if (!strcmp("on", vsync) || !strcmp("tear", vsync)) { | |
1026 flags |= SDL_RENDERER_PRESENTVSYNC; | |
1027 } | |
1028 main_renderer = SDL_CreateRenderer(main_window, -1, flags); | |
1029 | |
1030 if (!main_renderer) { | |
1031 fatal_error("unable to create SDL renderer: %s\n", SDL_GetError()); | |
1032 } | |
1033 main_clip.x = main_clip.y = 0; | |
1034 main_clip.w = main_width; | |
1035 main_clip.h = main_height; | |
1036 #ifndef DISABLE_OPENGL | |
1037 } | |
1038 #endif | |
1039 | |
1040 SDL_GetWindowSize(main_window, &main_width, &main_height); | |
1041 printf("Window created with size: %d x %d\n", main_width, main_height); | |
1042 update_aspect(); | |
1043 render_alloc_surfaces(); | |
1044 def.ptrval = "off"; | |
1045 scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def, TVAL_PTR).ptrval, "on"); | |
1046 } | |
1047 | |
1048 void render_init(int width, int height, char * title, uint8_t fullscreen) | |
1049 { | |
1050 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { | |
1051 fatal_error("Unable to init SDL: %s\n", SDL_GetError()); | |
1052 } | |
1053 atexit(SDL_Quit); | |
1054 if (height <= 0) { | |
1055 float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; | |
1056 height = ((float)width / aspect) + 0.5f; | |
1057 } | |
1058 printf("width: %d, height: %d\n", width, height); | |
1059 windowed_width = width; | |
1060 windowed_height = height; | |
1061 | |
1062 SDL_DisplayMode mode; | |
1063 //TODO: Explicit multiple monitor support | |
1064 SDL_GetCurrentDisplayMode(0, &mode); | |
1065 display_hz = mode.refresh_rate; | |
1066 | |
1067 if (fullscreen) { | |
1068 //the SDL2 migration guide suggests setting width and height to 0 when using SDL_WINDOW_FULLSCREEN_DESKTOP | |
1069 //but that doesn't seem to work right when using OpenGL, at least on Linux anyway | |
1070 width = mode.w; | |
1071 height = mode.h; | |
1072 } | |
1073 main_width = width; | |
1074 main_height = height; | |
1075 is_fullscreen = fullscreen; | |
1076 | |
1077 caption = title; | |
1078 | |
1079 window_setup(); | |
1080 | |
1081 audio_mutex = SDL_CreateMutex(); | |
1082 audio_ready = SDL_CreateCond(); | |
1083 | |
1084 init_audio(); | |
1085 | |
1086 uint32_t db_size; | |
1087 char *db_data = read_bundled_file("gamecontrollerdb.txt", &db_size); | |
1088 if (db_data) { | |
1089 int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1); | |
1090 free(db_data); | |
1091 printf("Added %d game controller mappings from gamecontrollerdb.txt\n", added); | |
1092 } | |
1093 | |
1094 SDL_JoystickEventState(SDL_ENABLE); | |
1095 | |
1096 render_set_video_standard(VID_NTSC); | |
1097 | |
1098 atexit(render_quit); | |
1099 } | |
1100 #include<unistd.h> | |
1101 static int in_toggle; | |
1102 void render_config_updated(void) | |
1103 { | |
1104 uint8_t old_sync_to_audio = sync_to_audio; | |
1105 | |
1106 free_surfaces(); | |
1107 #ifndef DISABLE_OPENGL | |
1108 if (render_gl) { | |
1109 if (on_context_destroyed) { | |
1110 on_context_destroyed(); | |
1111 } | |
1112 SDL_GL_DeleteContext(main_context); | |
1113 } else { | |
1114 #endif | |
1115 SDL_DestroyRenderer(main_renderer); | |
1116 #ifndef DISABLE_OPENGL | |
1117 } | |
1118 #endif | |
1119 in_toggle = 1; | |
1120 SDL_DestroyWindow(main_window); | |
1121 drain_events(); | |
1122 | |
1123 char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval; | |
1124 if (config_width) { | |
1125 windowed_width = atoi(config_width); | |
1126 } | |
1127 char *config_height = tern_find_path(config, "video\0height\0", TVAL_PTR).ptrval; | |
1128 if (config_height) { | |
1129 windowed_height = atoi(config_height); | |
1130 } else { | |
1131 float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; | |
1132 windowed_height = ((float)windowed_width / aspect) + 0.5f; | |
1133 } | |
1134 char *config_fullscreen = tern_find_path(config, "video\0fullscreen\0", TVAL_PTR).ptrval; | |
1135 is_fullscreen = config_fullscreen && !strcmp("on", config_fullscreen); | |
1136 if (is_fullscreen) { | |
1137 SDL_DisplayMode mode; | |
1138 //TODO: Multiple monitor support | |
1139 SDL_GetCurrentDisplayMode(0, &mode); | |
1140 main_width = mode.w; | |
1141 main_height = mode.h; | |
1142 } else { | |
1143 main_width = windowed_width; | |
1144 main_height = windowed_height; | |
1145 } | |
1146 | |
1147 window_setup(); | |
1148 update_aspect(); | |
1149 #ifndef DISABLE_OPENGL | |
1150 //need to check render_gl again after window_setup as render option could have changed | |
1151 if (render_gl && on_context_created) { | |
1152 on_context_created(); | |
1153 } | |
1154 #endif | |
1155 | |
1156 SDL_CloseAudio(); | |
1157 init_audio(); | |
1158 | |
1159 double lowpass_cutoff = get_lowpass_cutoff(config); | |
1160 double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); | |
1161 lock_audio(); | |
1162 for (uint8_t i = 0; i < num_audio_sources; i++) | |
1163 { | |
1164 double alpha = audio_sources[i]->dt / (audio_sources[i]->dt + rc); | |
1165 int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha); | |
1166 audio_sources[i]->lowpass_alpha = lowpass_alpha; | |
1167 } | |
1168 unlock_audio(); | |
1169 drain_events(); | |
1170 in_toggle = 0; | |
1171 } | |
1172 | |
1173 SDL_Window *render_get_window(void) | |
1174 { | |
1175 return main_window; | |
1176 } | |
1177 | |
1178 void render_set_video_standard(vid_std std) | |
1179 { | |
1180 video_standard = std; | |
1181 source_hz = std == VID_PAL ? 50 : 60; | |
1182 uint32_t max_repeat = 0; | |
1183 if (abs(source_hz - display_hz) < 2) { | |
1184 memset(frame_repeat, 0, sizeof(int)*display_hz); | |
1185 } else { | |
1186 int inc = display_hz * 100000 / source_hz; | |
1187 int accum = 0; | |
1188 int dst_frames = 0; | |
1189 for (int src_frame = 0; src_frame < source_hz; src_frame++) | |
1190 { | |
1191 frame_repeat[src_frame] = -1; | |
1192 accum += inc; | |
1193 while (accum > 100000) | |
1194 { | |
1195 accum -= 100000; | |
1196 frame_repeat[src_frame]++; | |
1197 max_repeat = frame_repeat[src_frame] > max_repeat ? frame_repeat[src_frame] : max_repeat; | |
1198 dst_frames++; | |
1199 } | |
1200 } | |
1201 if (dst_frames != display_hz) { | |
1202 frame_repeat[source_hz-1] += display_hz - dst_frames; | |
1203 } | |
1204 } | |
1205 source_frame = 0; | |
1206 source_frame_count = frame_repeat[0]; | |
1207 //sync samples with audio thread approximately every 8 lines | |
1208 sync_samples = 8 * sample_rate / (source_hz * (VID_PAL ? 313 : 262)); | |
1209 max_repeat++; | |
1210 min_buffered = (((float)max_repeat * (float)sample_rate/(float)source_hz)/* / (float)buffer_samples*/);// + 0.9999; | |
1211 //min_buffered *= buffer_samples; | |
1212 printf("Min samples buffered before audio start: %d\n", min_buffered); | |
1213 max_adjust = BASE_MAX_ADJUST / source_hz; | |
1214 } | |
1215 | |
1216 void render_update_caption(char *title) | |
1217 { | |
1218 caption = title; | |
1219 free(fps_caption); | |
1220 fps_caption = NULL; | |
1221 } | |
1222 | |
1223 static char *screenshot_path; | |
1224 void render_save_screenshot(char *path) | |
1225 { | |
1226 if (screenshot_path) { | |
1227 free(screenshot_path); | |
1228 } | |
1229 screenshot_path = path; | |
1230 } | |
1231 | |
1232 uint32_t *locked_pixels; | |
1233 uint32_t locked_pitch; | |
1234 uint32_t *render_get_framebuffer(uint8_t which, int *pitch) | |
1235 { | |
1236 #ifndef DISABLE_OPENGL | |
1237 if (render_gl && which <= FRAMEBUFFER_EVEN) { | |
1238 *pitch = LINEBUF_SIZE * sizeof(uint32_t); | |
1239 return texture_buf; | |
1240 } else { | |
1241 #endif | |
1242 if (which >= num_textures) { | |
1243 warning("Request for invalid framebuffer number %d\n", which); | |
1244 return NULL; | |
1245 } | |
1246 void *pixels; | |
1247 if (SDL_LockTexture(sdl_textures[which], NULL, &pixels, pitch) < 0) { | |
1248 warning("Failed to lock texture: %s\n", SDL_GetError()); | |
1249 return NULL; | |
1250 } | |
1251 static uint8_t last; | |
1252 if (which <= FRAMEBUFFER_EVEN) { | |
1253 locked_pixels = pixels; | |
1254 if (which == FRAMEBUFFER_EVEN) { | |
1255 pixels += *pitch; | |
1256 } | |
1257 locked_pitch = *pitch; | |
1258 if (which != last) { | |
1259 *pitch *= 2; | |
1260 } | |
1261 last = which; | |
1262 } | |
1263 return pixels; | |
1264 #ifndef DISABLE_OPENGL | |
1265 } | |
1266 #endif | |
1267 } | |
1268 | |
1269 uint8_t events_processed; | |
1270 #ifdef __ANDROID__ | |
1271 #define FPS_INTERVAL 10000 | |
1272 #else | |
1273 #define FPS_INTERVAL 1000 | |
1274 #endif | |
1275 | |
1276 static uint32_t last_width, last_height; | |
1277 static uint8_t interlaced; | |
1278 void render_framebuffer_updated(uint8_t which, int width) | |
1279 { | |
1280 static uint8_t last; | |
1281 if (!sync_to_audio && which <= FRAMEBUFFER_EVEN && source_frame_count < 0) { | |
1282 source_frame++; | |
1283 if (source_frame >= source_hz) { | |
1284 source_frame = 0; | |
1285 } | |
1286 source_frame_count = frame_repeat[source_frame]; | |
1287 //TODO: Figure out what to do about SDL Render API texture locking | |
1288 return; | |
1289 } | |
1290 | |
1291 last_width = width; | |
1292 uint32_t height = which <= FRAMEBUFFER_EVEN | |
1293 ? (video_standard == VID_NTSC ? 243 : 294) - (overscan_top[video_standard] + overscan_bot[video_standard]) | |
1294 : 240; | |
1295 FILE *screenshot_file = NULL; | |
1296 uint32_t shot_height, shot_width; | |
1297 char *ext; | |
1298 if (screenshot_path && which == FRAMEBUFFER_ODD) { | |
1299 screenshot_file = fopen(screenshot_path, "wb"); | |
1300 if (screenshot_file) { | |
1301 #ifndef DISABLE_ZLIB | |
1302 ext = path_extension(screenshot_path); | |
1303 #endif | |
1304 info_message("Saving screenshot to %s\n", screenshot_path); | |
1305 } else { | |
1306 warning("Failed to open screenshot file %s for writing\n", screenshot_path); | |
1307 } | |
1308 free(screenshot_path); | |
1309 screenshot_path = NULL; | |
1310 shot_height = video_standard == VID_NTSC ? 243 : 294; | |
1311 shot_width = width; | |
1312 } | |
1313 interlaced = last != which; | |
1314 width -= overscan_left[video_standard] + overscan_right[video_standard]; | |
1315 #ifndef DISABLE_OPENGL | |
1316 if (render_gl && which <= FRAMEBUFFER_EVEN) { | |
1317 glBindTexture(GL_TEXTURE_2D, textures[which]); | |
1318 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LINEBUF_SIZE, height, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]); | |
1319 | |
1320 if (screenshot_file) { | |
1321 //properly supporting interlaced modes here is non-trivial, so only save the odd field for now | |
1322 #ifndef DISABLE_ZLIB | |
1323 if (!strcasecmp(ext, "png")) { | |
1324 free(ext); | |
1325 save_png(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); | |
1326 } else { | |
1327 free(ext); | |
1328 #endif | |
1329 save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); | |
1330 #ifndef DISABLE_ZLIB | |
1331 } | |
1332 #endif | |
1333 } | |
1334 } else { | |
1335 #endif | |
1336 if (which <= FRAMEBUFFER_EVEN && last != which) { | |
1337 uint8_t *cur_dst = (uint8_t *)locked_pixels; | |
1338 uint8_t *cur_saved = (uint8_t *)texture_buf; | |
1339 uint32_t dst_off = which == FRAMEBUFFER_EVEN ? 0 : locked_pitch; | |
1340 uint32_t src_off = which == FRAMEBUFFER_EVEN ? locked_pitch : 0; | |
1341 for (int i = 0; i < height; ++i) | |
1342 { | |
1343 //copy saved line from other field | |
1344 memcpy(cur_dst + dst_off, cur_saved, locked_pitch); | |
1345 //save line from this field to buffer for next frame | |
1346 memcpy(cur_saved, cur_dst + src_off, locked_pitch); | |
1347 cur_dst += locked_pitch * 2; | |
1348 cur_saved += locked_pitch; | |
1349 } | |
1350 height = 480; | |
1351 } | |
1352 if (screenshot_file) { | |
1353 uint32_t shot_pitch = locked_pitch; | |
1354 if (which == FRAMEBUFFER_EVEN) { | |
1355 shot_height *= 2; | |
1356 } else { | |
1357 shot_pitch *= 2; | |
1358 } | |
1359 #ifndef DISABLE_ZLIB | |
1360 if (!strcasecmp(ext, "png")) { | |
1361 free(ext); | |
1362 save_png(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch); | |
1363 } else { | |
1364 free(ext); | |
1365 #endif | |
1366 save_ppm(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch); | |
1367 #ifndef DISABLE_ZLIB | |
1368 } | |
1369 #endif | |
1370 } | |
1371 SDL_UnlockTexture(sdl_textures[which]); | |
1372 #ifndef DISABLE_OPENGL | |
1373 } | |
1374 #endif | |
1375 last_height = height; | |
1376 render_update_display(); | |
1377 if (screenshot_file) { | |
1378 fclose(screenshot_file); | |
1379 } | |
1380 if (which <= FRAMEBUFFER_EVEN) { | |
1381 last = which; | |
1382 static uint32_t frame_counter, start; | |
1383 frame_counter++; | |
1384 last_frame= SDL_GetTicks(); | |
1385 if ((last_frame - start) > FPS_INTERVAL) { | |
1386 if (start && (last_frame-start)) { | |
1387 #ifdef __ANDROID__ | |
1388 info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); | |
1389 #else | |
1390 if (!fps_caption) { | |
1391 fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1); | |
1392 } | |
1393 sprintf(fps_caption, "%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); | |
1394 SDL_SetWindowTitle(main_window, fps_caption); | |
1395 #endif | |
1396 } | |
1397 start = last_frame; | |
1398 frame_counter = 0; | |
1399 } | |
1400 } | |
1401 if (!sync_to_audio) { | |
1402 int32_t local_cur_min, local_min_remaining; | |
1403 SDL_LockAudio(); | |
1404 if (last_buffered > NO_LAST_BUFFERED) { | |
1405 average_change *= 0.9f; | |
1406 average_change += (cur_min_buffered - last_buffered) * 0.1f; | |
1407 } | |
1408 local_cur_min = cur_min_buffered; | |
1409 local_min_remaining = min_remaining_buffer; | |
1410 last_buffered = cur_min_buffered; | |
1411 SDL_UnlockAudio(); | |
1412 float frames_to_problem; | |
1413 if (average_change < 0) { | |
1414 frames_to_problem = (float)local_cur_min / -average_change; | |
1415 } else { | |
1416 frames_to_problem = (float)local_min_remaining / average_change; | |
1417 } | |
1418 float adjust_ratio = 0.0f; | |
1419 if ( | |
1420 frames_to_problem < BUFFER_FRAMES_THRESHOLD | |
1421 || (average_change < 0 && local_cur_min < 3*min_buffered / 4) | |
1422 || (average_change >0 && local_cur_min > 5 * min_buffered / 4) | |
1423 ) { | |
1424 | |
1425 if (cur_min_buffered < 0) { | |
1426 adjust_ratio = max_adjust; | |
1427 SDL_PauseAudio(1); | |
1428 last_buffered = NO_LAST_BUFFERED; | |
1429 } else { | |
1430 adjust_ratio = -1.0 * average_change / ((float)sample_rate / (float)source_hz); | |
1431 adjust_ratio /= 2.5 * source_hz; | |
1432 if (fabsf(adjust_ratio) > max_adjust) { | |
1433 adjust_ratio = adjust_ratio > 0 ? max_adjust : -max_adjust; | |
1434 } | |
1435 } | |
1436 } else if (local_cur_min < min_buffered / 2) { | |
1437 adjust_ratio = max_adjust; | |
1438 } | |
1439 if (adjust_ratio != 0.0f) { | |
1440 average_change = 0; | |
1441 for (uint8_t i = 0; i < num_audio_sources; i++) | |
1442 { | |
1443 audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5; | |
1444 } | |
1445 } | |
1446 while (source_frame_count > 0) | |
1447 { | |
1448 render_update_display(); | |
1449 source_frame_count--; | |
1450 } | |
1451 source_frame++; | |
1452 if (source_frame >= source_hz) { | |
1453 source_frame = 0; | |
1454 } | |
1455 source_frame_count = frame_repeat[source_frame]; | |
1456 } | |
1457 } | |
1458 | |
1459 static ui_render_fun render_ui; | |
1460 void render_set_ui_render_fun(ui_render_fun fun) | |
1461 { | |
1462 render_ui = fun; | |
1463 } | |
1464 | |
1465 void render_update_display() | |
1466 { | |
1467 #ifndef DISABLE_OPENGL | |
1468 if (render_gl) { | |
1469 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
1470 glClear(GL_COLOR_BUFFER_BIT); | |
1471 | |
1472 glUseProgram(program); | |
1473 glActiveTexture(GL_TEXTURE0); | |
1474 glBindTexture(GL_TEXTURE_2D, textures[0]); | |
1475 glUniform1i(un_textures[0], 0); | |
1476 | |
1477 glActiveTexture(GL_TEXTURE1); | |
1478 glBindTexture(GL_TEXTURE_2D, textures[interlaced ? 1 : scanlines ? 2 : 0]); | |
1479 glUniform1i(un_textures[1], 1); | |
1480 | |
1481 glUniform1f(un_width, render_emulated_width()); | |
1482 glUniform1f(un_height, last_height); | |
1483 | |
1484 glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); | |
1485 glVertexAttribPointer(at_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat[2]), (void *)0); | |
1486 glEnableVertexAttribArray(at_pos); | |
1487 | |
1488 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); | |
1489 glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void *)0); | |
1490 | |
1491 glDisableVertexAttribArray(at_pos); | |
1492 | |
1493 if (render_ui) { | |
1494 render_ui(); | |
1495 } | |
1496 | |
1497 SDL_GL_SwapWindow(main_window); | |
1498 } else { | |
1499 #endif | |
1500 SDL_Rect src_clip = { | |
1501 .x = overscan_left[video_standard], | |
1502 .y = overscan_top[video_standard], | |
1503 .w = render_emulated_width(), | |
1504 .h = last_height | |
1505 }; | |
1506 SDL_SetRenderDrawColor(main_renderer, 0, 0, 0, 255); | |
1507 SDL_RenderClear(main_renderer); | |
1508 SDL_RenderCopy(main_renderer, sdl_textures[FRAMEBUFFER_ODD], &src_clip, &main_clip); | |
1509 if (render_ui) { | |
1510 render_ui(); | |
1511 } | |
1512 SDL_RenderPresent(main_renderer); | |
1513 #ifndef DISABLE_OPENGL | |
1514 } | |
1515 #endif | |
1516 if (!events_processed) { | |
1517 process_events(); | |
1518 } | |
1519 events_processed = 0; | |
1520 } | |
1521 | |
1522 uint32_t render_emulated_width() | |
1523 { | |
1524 return last_width - overscan_left[video_standard] - overscan_right[video_standard]; | |
1525 } | |
1526 | |
1527 uint32_t render_emulated_height() | |
1528 { | |
1529 return (video_standard == VID_NTSC ? 243 : 294) - overscan_top[video_standard] - overscan_bot[video_standard]; | |
1530 } | |
1531 | |
1532 uint32_t render_overscan_left() | |
1533 { | |
1534 return overscan_left[video_standard]; | |
1535 } | |
1536 | |
1537 uint32_t render_overscan_top() | |
1538 { | |
1539 return overscan_top[video_standard]; | |
1540 } | |
1541 | |
1542 void render_wait_quit(vdp_context * context) | |
1543 { | |
1544 SDL_Event event; | |
1545 while(SDL_WaitEvent(&event)) { | |
1546 switch (event.type) { | |
1547 case SDL_QUIT: | |
1548 return; | |
1549 } | |
1550 } | |
1551 } | |
1552 | |
1553 int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis) | |
1554 { | |
1555 static tern_node *button_lookup, *axis_lookup; | |
1556 if (controller > MAX_JOYSTICKS || !joysticks[controller]) { | |
1557 return RENDER_NOT_PLUGGED_IN; | |
1558 } | |
1559 | |
1560 if (!SDL_IsGameController(joystick_sdl_index[controller])) { | |
1561 return RENDER_NOT_MAPPED; | |
1562 } | |
1563 SDL_GameController *control = SDL_GameControllerOpen(joystick_sdl_index[controller]); | |
1564 if (!control) { | |
1565 warning("Failed to open game controller %d: %s\n", controller, SDL_GetError()); | |
1566 return RENDER_NOT_PLUGGED_IN; | |
1567 } | |
1568 | |
1569 SDL_GameControllerButtonBind cbind; | |
1570 if (is_axis) { | |
1571 if (!axis_lookup) { | |
1572 for (int i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) | |
1573 { | |
1574 axis_lookup = tern_insert_int(axis_lookup, SDL_GameControllerGetStringForAxis(i), i); | |
1575 } | |
1576 //alternative Playstation-style names | |
1577 axis_lookup = tern_insert_int(axis_lookup, "l2", SDL_CONTROLLER_AXIS_TRIGGERLEFT); | |
1578 axis_lookup = tern_insert_int(axis_lookup, "r2", SDL_CONTROLLER_AXIS_TRIGGERRIGHT); | |
1579 } | |
1580 intptr_t sdl_axis = tern_find_int(axis_lookup, name, SDL_CONTROLLER_AXIS_INVALID); | |
1581 if (sdl_axis == SDL_CONTROLLER_AXIS_INVALID) { | |
1582 SDL_GameControllerClose(control); | |
1583 return RENDER_INVALID_NAME; | |
1584 } | |
1585 cbind = SDL_GameControllerGetBindForAxis(control, sdl_axis); | |
1586 } else { | |
1587 if (!button_lookup) { | |
1588 for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) | |
1589 { | |
1590 button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i); | |
1591 } | |
1592 //alternative Playstation-style names | |
1593 button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A); | |
1594 button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B); | |
1595 button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X); | |
1596 button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y); | |
1597 button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK); | |
1598 button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK); | |
1599 button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START); | |
1600 button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER); | |
1601 button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); | |
1602 button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK); | |
1603 button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK); | |
1604 } | |
1605 intptr_t sdl_button = tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID); | |
1606 if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) { | |
1607 SDL_GameControllerClose(control); | |
1608 return RENDER_INVALID_NAME; | |
1609 } | |
1610 cbind = SDL_GameControllerGetBindForButton(control, sdl_button); | |
1611 } | |
1612 SDL_GameControllerClose(control); | |
1613 switch (cbind.bindType) | |
1614 { | |
1615 case SDL_CONTROLLER_BINDTYPE_BUTTON: | |
1616 return cbind.value.button; | |
1617 case SDL_CONTROLLER_BINDTYPE_AXIS: | |
1618 return RENDER_AXIS_BIT | cbind.value.axis; | |
1619 case SDL_CONTROLLER_BINDTYPE_HAT: | |
1620 return RENDER_DPAD_BIT | (cbind.value.hat.hat << 4) | cbind.value.hat.hat_mask; | |
1621 } | |
1622 return RENDER_NOT_MAPPED; | |
1623 } | |
1624 | |
1625 int32_t render_dpad_part(int32_t input) | |
1626 { | |
1627 return input >> 4 & 0xFFFFFF; | |
1628 } | |
1629 | |
1630 uint8_t render_direction_part(int32_t input) | |
1631 { | |
1632 return input & 0xF; | |
1633 } | |
1634 | |
1635 int32_t render_axis_part(int32_t input) | |
1636 { | |
1637 return input & 0xFFFFFFF; | |
1638 } | |
1639 | |
1547 void process_events() | 1640 void process_events() |
1548 { | 1641 { |
1549 if (events_processed > MAX_EVENT_POLL_PER_FRAME) { | 1642 if (events_processed > MAX_EVENT_POLL_PER_FRAME) { |
1550 return; | 1643 return; |
1551 } | 1644 } |
1554 } | 1647 } |
1555 | 1648 |
1556 #define TOGGLE_MIN_DELAY 250 | 1649 #define TOGGLE_MIN_DELAY 250 |
1557 void render_toggle_fullscreen() | 1650 void render_toggle_fullscreen() |
1558 { | 1651 { |
1559 static int in_toggle; | |
1560 //protect against event processing causing us to attempt to toggle while still toggling | 1652 //protect against event processing causing us to attempt to toggle while still toggling |
1561 if (in_toggle) { | 1653 if (in_toggle) { |
1562 return; | 1654 return; |
1563 } | 1655 } |
1564 in_toggle = 1; | 1656 in_toggle = 1; |