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;