comparison render_sdl.c @ 1202:a6ae693974e0

Allow toggling full screen mode at runtime. Allow resizing the window in windowed mode. Allow specifying the aspect ratio in the config file.
author Michael Pavone <pavone@retrodev.com>
date Thu, 26 Jan 2017 00:55:02 -0800
parents 8715174e9366
children 3d3bad51183d
comparison
equal deleted inserted replaced
1201:aee2177a1630 1202:a6ae693974e0
24 static SDL_Texture **sdl_textures; 24 static SDL_Texture **sdl_textures;
25 static uint8_t num_textures; 25 static uint8_t num_textures;
26 static SDL_Rect main_clip; 26 static SDL_Rect main_clip;
27 static SDL_GLContext *main_context; 27 static SDL_GLContext *main_context;
28 28
29 static int main_width, main_height, is_fullscreen; 29 static int main_width, main_height, windowed_width, windowed_height, is_fullscreen;
30 30
31 static uint8_t render_gl = 1; 31 static uint8_t render_gl = 1;
32 static uint8_t scanlines = 0; 32 static uint8_t scanlines = 0;
33 33
34 static uint32_t last_frame = 0; 34 static uint32_t last_frame = 0;
134 } 134 }
135 135
136 #ifndef DISABLE_OPENGL 136 #ifndef DISABLE_OPENGL
137 static GLuint textures[3], buffers[2], vshader, fshader, program, un_textures[2], un_width, un_height, at_pos; 137 static GLuint textures[3], buffers[2], vshader, fshader, program, un_textures[2], un_width, un_height, at_pos;
138 138
139 static GLfloat vertex_data[] = { 139 static GLfloat vertex_data_default[] = {
140 -1.0f, -1.0f, 140 -1.0f, -1.0f,
141 1.0f, -1.0f, 141 1.0f, -1.0f,
142 -1.0f, 1.0f, 142 -1.0f, 1.0f,
143 1.0f, 1.0f 143 1.0f, 1.0f
144 }; 144 };
145
146 static GLfloat vertex_data[8];
145 147
146 static const GLushort element_data[] = {0, 1, 2, 3}; 148 static const GLushort element_data[] = {0, 1, 2, 3};
147 149
148 static GLuint load_shader(char * fname, GLenum shader_type) 150 static GLuint load_shader(char * fname, GLenum shader_type)
149 { 151 {
187 return ret; 189 return ret;
188 } 190 }
189 #endif 191 #endif
190 192
191 static uint32_t texture_buf[512 * 513]; 193 static uint32_t texture_buf[512 * 513];
194 #ifndef DISABLE_OPENGL
195 static void gl_setup()
196 {
197 glGenTextures(3, textures);
198 for (int i = 0; i < 3; i++)
199 {
200 glBindTexture(GL_TEXTURE_2D, textures[i]);
201 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
202 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
203 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
204 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
205 if (i < 2) {
206 //TODO: Fixme for PAL + invalid display mode
207 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf);
208 } else {
209 uint32_t blank = 255 << 24;
210 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_BGRA, GL_UNSIGNED_BYTE, &blank);
211 }
212 }
213 glGenBuffers(2, buffers);
214 glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
215 glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW);
216 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
217 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(element_data), element_data, GL_STATIC_DRAW);
218 tern_val def = {.ptrval = "default.v.glsl"};
219 vshader = load_shader(tern_find_path_default(config, "video\0vertex_shader\0", def).ptrval, GL_VERTEX_SHADER);
220 def.ptrval = "default.f.glsl";
221 fshader = load_shader(tern_find_path_default(config, "video\0fragment_shader\0", def).ptrval, GL_FRAGMENT_SHADER);
222 program = glCreateProgram();
223 glAttachShader(program, vshader);
224 glAttachShader(program, fshader);
225 glLinkProgram(program);
226 GLint link_status;
227 glGetProgramiv(program, GL_LINK_STATUS, &link_status);
228 if (!link_status) {
229 fputs("Failed to link shader program\n", stderr);
230 exit(1);
231 }
232 un_textures[0] = glGetUniformLocation(program, "textures[0]");
233 un_textures[1] = glGetUniformLocation(program, "textures[1]");
234 un_width = glGetUniformLocation(program, "width");
235 un_height = glGetUniformLocation(program, "height");
236 at_pos = glGetAttribLocation(program, "pos");
237 }
238 #endif
239
192 static void render_alloc_surfaces() 240 static void render_alloc_surfaces()
193 { 241 {
194 static uint8_t texture_init; 242 static uint8_t texture_init;
195 243
196 if (texture_init) { 244 if (texture_init) {
200 num_textures = 2; 248 num_textures = 2;
201 texture_init = 1; 249 texture_init = 1;
202 #ifndef DISABLE_OPENGL 250 #ifndef DISABLE_OPENGL
203 if (render_gl) { 251 if (render_gl) {
204 sdl_textures[0] = sdl_textures[1] = NULL; 252 sdl_textures[0] = sdl_textures[1] = NULL;
205 glGenTextures(3, textures); 253 gl_setup();
206 for (int i = 0; i < 3; i++)
207 {
208 glBindTexture(GL_TEXTURE_2D, textures[i]);
209 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
210 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
211 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
212 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
213 if (i < 2) {
214 //TODO: Fixme for PAL + invalid display mode
215 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf);
216 } else {
217 uint32_t blank = 255 << 24;
218 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_BGRA, GL_UNSIGNED_BYTE, &blank);
219 }
220 }
221 glGenBuffers(2, buffers);
222 glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
223 glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW);
224 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
225 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(element_data), element_data, GL_STATIC_DRAW);
226 tern_val def = {.ptrval = "default.v.glsl"};
227 vshader = load_shader(tern_find_path_default(config, "video\0vertex_shader\0", def).ptrval, GL_VERTEX_SHADER);
228 def.ptrval = "default.f.glsl";
229 fshader = load_shader(tern_find_path_default(config, "video\0fragment_shader\0", def).ptrval, GL_FRAGMENT_SHADER);
230 program = glCreateProgram();
231 glAttachShader(program, vshader);
232 glAttachShader(program, fshader);
233 glLinkProgram(program);
234 GLint link_status;
235 glGetProgramiv(program, GL_LINK_STATUS, &link_status);
236 if (!link_status) {
237 fputs("Failed to link shader program\n", stderr);
238 exit(1);
239 }
240 un_textures[0] = glGetUniformLocation(program, "textures[0]");
241 un_textures[1] = glGetUniformLocation(program, "textures[1]");
242 un_width = glGetUniformLocation(program, "width");
243 un_height = glGetUniformLocation(program, "height");
244 at_pos = glGetAttribLocation(program, "pos");
245 } else { 254 } else {
246 #endif 255 #endif
247 256
248 //TODO: Fixme for invalid display mode 257 //TODO: Fixme for invalid display mode
249 sdl_textures[0] = sdl_textures[1] = SDL_CreateTexture(main_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 320, 588); 258 sdl_textures[0] = sdl_textures[1] = SDL_CreateTexture(main_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 320, 588);
264 SDL_DestroyTexture(sdl_textures[i]); 273 SDL_DestroyTexture(sdl_textures[i]);
265 } 274 }
266 } 275 }
267 } 276 }
268 277
278 static void update_aspect()
279 {
280 //reset default values
281 memcpy(vertex_data, vertex_data_default, sizeof(vertex_data));
282 main_clip.w = main_width;
283 main_clip.h = main_height;
284 main_clip.x = main_clip.y = 0;
285 //calculate configured aspect ratio
286 char *config_aspect = tern_find_path_default(config, "video\0aspect\0", (tern_val){.ptrval = "4:3"}).ptrval;
287 if (strcmp("stretch", config_aspect)) {
288 float src_aspect = 4.0f/3.0f;
289 char *end;
290 float aspect_numerator = strtof(config_aspect, &end);
291 if (aspect_numerator > 0.0f && *end == ':') {
292 float aspect_denominator = strtof(end+1, &end);
293 if (aspect_denominator > 0.0f && !*end) {
294 src_aspect = aspect_numerator / aspect_denominator;
295 }
296 }
297 float aspect = (float)main_width / main_height;
298 if (fabs(aspect - src_aspect) < 0.01f) {
299 //close enough for government work
300 return;
301 }
302 #ifndef DISABLE_OPENGL
303 if (render_gl) {
304 for (int i = 0; i < 4; i++)
305 {
306 if (aspect > src_aspect) {
307 vertex_data[i*2] *= src_aspect/aspect;
308 } else {
309 vertex_data[i*2+1] *= aspect/src_aspect;
310 }
311 }
312 } else {
313 #endif
314 main_clip.w = aspect > src_aspect ? src_aspect * (float)main_height : main_width;
315 main_clip.h = aspect > src_aspect ? main_height : main_width / src_aspect;
316 main_clip.x = (main_width - main_clip.w) / 2;
317 main_clip.y = (main_height - main_clip.h) / 2;
318 #ifndef DISABLE_OPENGL
319 }
320 #endif
321 }
322 }
323
269 static uint32_t overscan_top[NUM_VID_STD] = {2, 21}; 324 static uint32_t overscan_top[NUM_VID_STD] = {2, 21};
270 static uint32_t overscan_bot[NUM_VID_STD] = {1, 17}; 325 static uint32_t overscan_bot[NUM_VID_STD] = {1, 17};
271 static vid_std video_standard = VID_NTSC; 326 static vid_std video_standard = VID_NTSC;
272 static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"}; 327 static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"};
273 void render_init(int width, int height, char * title, uint8_t fullscreen) 328 void render_init(int width, int height, char * title, uint8_t fullscreen)
275 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { 330 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
276 fatal_error("Unable to init SDL: %s\n", SDL_GetError()); 331 fatal_error("Unable to init SDL: %s\n", SDL_GetError());
277 } 332 }
278 atexit(SDL_Quit); 333 atexit(SDL_Quit);
279 printf("width: %d, height: %d\n", width, height); 334 printf("width: %d, height: %d\n", width, height);
280 335 windowed_width = width;
281 uint32_t flags = 0; 336 windowed_height = height;
337
338 uint32_t flags = SDL_WINDOW_RESIZABLE;
282 339
283 if (fullscreen) { 340 if (fullscreen) {
284 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; 341 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
285 SDL_DisplayMode mode; 342 SDL_DisplayMode mode;
286 //TODO: Multiple monitor support 343 //TODO: Multiple monitor support
372 main_clip.h = height; 429 main_clip.h = height;
373 #ifndef DISABLE_OPENGL 430 #ifndef DISABLE_OPENGL
374 } 431 }
375 #endif 432 #endif
376 433
377 SDL_GetWindowSize(main_window, &width, &height); 434 SDL_GetWindowSize(main_window, &main_width, &main_height);
378 printf("Window created with size: %d x %d\n", width, height); 435 printf("Window created with size: %d x %d\n", main_width, main_height);
379 float src_aspect = 4.0/3.0; 436 update_aspect();
380 float aspect = (float)width / height;
381 def.ptrval = "normal";
382 int stretch = fabs(aspect - src_aspect) > 0.01 && !strcmp(tern_find_path_default(config, "video\0aspect\0", def).ptrval, "stretch");
383
384 if (!stretch) {
385 #ifndef DISABLE_OPENGL
386 if (render_gl) {
387 for (int i = 0; i < 4; i++)
388 {
389 if (aspect > src_aspect) {
390 vertex_data[i*2] *= src_aspect/aspect;
391 } else {
392 vertex_data[i*2+1] *= aspect/src_aspect;
393 }
394 }
395 } else {
396 #endif
397 float scale_x = (float)width / 320.0;
398 float scale_y = (float)height / 240.0;
399 float scale = scale_x > scale_y ? scale_y : scale_x;
400 main_clip.w = 320.0 * scale;
401 main_clip.h = 240.0 * scale;
402 main_clip.x = (width - main_clip.w) / 2;
403 main_clip.y = (height - main_clip.h) / 2;
404 #ifndef DISABLE_OPENGL
405 }
406 #endif
407 }
408 render_alloc_surfaces(); 437 render_alloc_surfaces();
409 def.ptrval = "off"; 438 def.ptrval = "off";
410 scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def).ptrval, "on"); 439 scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def).ptrval, "on");
411 440
412 caption = title; 441 caption = title;
841 handle_mousedown(event->button.which, event->button.button); 870 handle_mousedown(event->button.which, event->button.button);
842 break; 871 break;
843 case SDL_MOUSEBUTTONUP: 872 case SDL_MOUSEBUTTONUP:
844 handle_mouseup(event->button.which, event->button.button); 873 handle_mouseup(event->button.which, event->button.button);
845 break; 874 break;
875 case SDL_WINDOWEVENT:
876 switch (event->window.event)
877 {
878 case SDL_WINDOWEVENT_SIZE_CHANGED:
879 main_width = event->window.data1;
880 main_height = event->window.data2;
881 update_aspect();
882 #ifndef DISABLE_OPENGL
883 if (render_gl) {
884 SDL_GL_DeleteContext(main_context);
885 main_context = SDL_GL_CreateContext(main_window);
886 gl_setup();
887 }
888 #endif
889 break;
890 }
891 break;
846 case SDL_QUIT: 892 case SDL_QUIT:
847 puts(""); 893 puts("");
848 exit(0); 894 exit(0);
849 } 895 }
850 return 0; 896 return 0;
851 } 897 }
852 898
899 static void drain_events()
900 {
901 SDL_Event event;
902 while(SDL_PollEvent(&event))
903 {
904 handle_event(&event);
905 }
906 }
907
853 void process_events() 908 void process_events()
854 { 909 {
855 if (events_processed > MAX_EVENT_POLL_PER_FRAME) { 910 if (events_processed > MAX_EVENT_POLL_PER_FRAME) {
856 return; 911 return;
857 } 912 }
858 SDL_Event event; 913 drain_events();
859 while(SDL_PollEvent(&event)) {
860 handle_event(&event);
861 }
862 events_processed++; 914 events_processed++;
915 }
916
917 #define TOGGLE_MIN_DELAY 250
918 void render_toggle_fullscreen()
919 {
920 static int in_toggle;
921 //protect against event processing causing us to attempt to toggle while still toggling
922 if (in_toggle) {
923 return;
924 }
925 in_toggle = 1;
926
927 //toggling too fast seems to cause a deadlock
928 static uint32_t last_toggle;
929 uint32_t cur = SDL_GetTicks();
930 if (last_toggle && cur - last_toggle < TOGGLE_MIN_DELAY) {
931 in_toggle = 0;
932 return;
933 }
934 last_toggle = cur;
935
936 drain_events();
937 is_fullscreen = !is_fullscreen;
938 if (is_fullscreen) {
939 SDL_DisplayMode mode;
940 //TODO: Multiple monitor support
941 SDL_GetCurrentDisplayMode(0, &mode);
942 //In theory, the SDL2 docs suggest this is unnecessary
943 //but without it the OpenGL context remains the original size
944 //This needs to happen before the fullscreen transition to have any effect
945 //because SDL does not apply window size changes in fullscreen
946 SDL_SetWindowSize(main_window, mode.w, mode.h);
947 }
948 SDL_SetWindowFullscreen(main_window, is_fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
949 //Since we change the window size on transition to full screen
950 //we need to set it back to normal so we can also go back to windowed mode
951 //normally you would think that this should only be done when actually transitioning
952 //but something is screwy in the guts of SDL (at least on Linux) and setting it each time
953 //is the only thing that seems to work reliably
954 //when we've just switched to fullscreen mode this should be harmless though
955 SDL_SetWindowSize(main_window, windowed_width, windowed_height);
956 drain_events();
957 in_toggle = 0;
863 } 958 }
864 959
865 void render_wait_psg(psg_context * context) 960 void render_wait_psg(psg_context * context)
866 { 961 {
867 SDL_LockMutex(audio_mutex); 962 SDL_LockMutex(audio_mutex);