1 /*
2  * screen_win32.c - Win32 port specific code
3  *
4  * Copyright (C) 2000 Krzysztof Nikiel
5  * Copyright (C) 2000-2006 Atari800 development team (see DOC/CREDITS)
6  *
7  * This file is part of the Atari800 emulator project which emulates
8  * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.
9  *
10  * Atari800 is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * Atari800 is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Atari800; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 #include "config.h"
26 
27 #define DIRECTDRAW_VERSION   0x0500
28 #define DIRECTINPUT_VERSION  0x0500
29 #define WIN32_LEAN_AND_MEAN
30 #define DW_WINDOWSTYLE WS_TILEDWINDOW | WS_SIZEBOX | WS_DLGFRAME
31 
32 #define DISP_CHANGE_NOMODE -6
33 
34 #include <windows.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <math.h>
38 
39 #include "atari.h"
40 #include "colours.h"
41 #include "log.h"
42 #include "util.h"
43 #include "keyboard.h"
44 #include "screen.h"
45 #include "sound.h"
46 #include "main_menu.h"
47 #include "ui.h"
48 
49 #include "screen_win32.h"
50 #include "main.h"
51 #include "render_gdi.h"
52 #include "render_gdiplus.h"
53 #include "render_directdraw.h"
54 #include "render_direct3d.h"
55 
56 static RECT currentwindowrect = {0,0,0,0};
57 static WINDOWPLACEMENT currentwindowstate;
58 static HCURSOR cursor;
59 static BOOL refreshframeparams = TRUE;
60 static int scaledWidth = 0;
61 static int scaledHeight = 0;
62 static int bltgfx = 0;
63 static int scrwidth = SCREENWIDTH;
64 static int scrheight = SCREENHEIGHT;
65 
66 RENDERMODE rendermode = GDI;
67 DISPLAYMODE displaymode = GDI_NEARESTNEIGHBOR;
68 FSRESOLUTION fsresolution = DESKTOP;
69 SCREENMODE screenmode = WINDOW;
70 ASPECTMODE scalingmethod = NORMAL;
71 ASPECTRATIO aspectmode = AUTO;
72 FRAMEPARAMS frameparams;
73 CROP crop;
74 OFFSET offset;
75 BOOL hidecursor = FALSE;
76 BOOL lockaspect = FALSE;
77 BOOL usecustomfsresolution = FALSE;
78 BOOL showmenu = TRUE;
79 int cropamount = 0;
80 int windowscale = 200;
81 int currentscale = 200;
82 int fullscreenWidth = 0;
83 int fullscreenHeight = 0;
84 int origScreenWidth = 0;
85 int origScreenHeight = 0;
86 int origScreenDepth = 0;
87 
groff(void)88 void groff(void)
89 {
90 	switch (rendermode)
91 	{
92 		case GDI:
93 			DestroyWindow(hWndMain);
94 		case DIRECTDRAW:
95 			shutdowndirectdraw();
96 			break;
97 		case GDIPLUS:
98 			DestroyWindow(hWndMain);
99 			shutdowngdiplus();
100 			break;
101 		case DIRECT3D:
102 			DestroyWindow(hWndMain);
103 			shutdowndirect3d();
104 			break;
105 	}
106 }
107 
refreshframe()108 void refreshframe()
109 {
110 	refreshframeparams = TRUE;
111 }
112 
setcursor()113 void setcursor()
114 {
115 	if (screenmode == WINDOW)
116 		while (ShowCursor(TRUE) < 0) {}
117 	else if (hidecursor)
118 		while (ShowCursor(FALSE) > -1) {}
119 	else if (UI_is_active)
120 		while (ShowCursor(TRUE) < 0) {}
121 	else
122 		while (ShowCursor(FALSE) > -1) {}
123 }
124 
GetRenderMode()125 RENDERMODE GetRenderMode() {
126 	return rendermode;
127 }
128 
GetDisplayMode()129 DISPLAYMODE GetDisplayMode() {
130 	return displaymode;
131 }
132 
GetScreenMode()133 SCREENMODE GetScreenMode() {
134 	return screenmode;
135 }
136 
GetDisplayModeName(char * name)137 void GetDisplayModeName(char *name) {
138 
139 	switch (displaymode) {
140 		case GDI_NEARESTNEIGHBOR:
141 			sprintf(name, "%s", "GDI");
142 			break;
143 		case GDIPLUS_NEARESTNEIGHBOR:
144 			sprintf(name, "%s", "GDI+");
145 			break;
146 		case GDIPLUS_BILINEAR:
147 			sprintf(name, "%s", "GDI+/Bilinear");
148 			break;
149 		case GDIPLUS_HQBICUBIC:
150 			sprintf(name, "%s", "GDI+/Bicubic(HQ)");
151 			break;
152 		case DIRECT3D_NEARESTNEIGHBOR:
153 			sprintf(name, "%s", "Direct3D");
154 			break;
155 		case DIRECT3D_BILINEAR:
156 			sprintf(name, "%s", "Direct3D/Bilinear");
157 			break;
158 		case GDIPLUS_HQBILINEAR:
159 			sprintf(name, "%s", "GDI+/Bilinear(HQ)");
160 			break;
161 		case GDIPLUS_BICUBIC:
162 			sprintf(name, "%s", "GDI+/Bicubic");
163 			break;
164 	}
165 }
166 
GetActiveDisplayMode()167 DISPLAYMODE GetActiveDisplayMode() {
168 
169 	DISPLAYMODE retval;
170 
171 	if (rendermode == GDI && frameparams.filter == NEARESTNEIGHBOR)
172 		retval = GDI_NEARESTNEIGHBOR;
173 	else if (rendermode == GDIPLUS && frameparams.filter == NEARESTNEIGHBOR)
174 		retval = GDIPLUS_NEARESTNEIGHBOR;
175 	else if (rendermode == GDIPLUS && frameparams.filter == BILINEAR)
176 		retval = GDIPLUS_BILINEAR;
177 	else if (rendermode == GDIPLUS && frameparams.filter == HQBICUBIC)
178 		retval = GDIPLUS_HQBICUBIC;
179 	else if (rendermode == DIRECT3D && frameparams.filter == NEARESTNEIGHBOR)
180 		retval = DIRECT3D_NEARESTNEIGHBOR;
181 	else if (rendermode == DIRECT3D && frameparams.filter == BILINEAR)
182 		retval = DIRECT3D_BILINEAR;
183 	else if (rendermode == GDIPLUS && frameparams.filter == HQBILINEAR)
184 		retval = GDIPLUS_HQBILINEAR;
185 	else if (rendermode == GDIPLUS && frameparams.filter == BICUBIC)
186 		retval = GDIPLUS_BICUBIC;
187 
188 	return retval;
189 }
190 
SetDisplayMode(DISPLAYMODE dm)191 void SetDisplayMode(DISPLAYMODE dm) {
192 
193 	displaymode = dm;
194 
195 	switch (dm) {
196 		case GDI_NEARESTNEIGHBOR:
197 			rendermode = GDI;
198 			frameparams.filter = NEARESTNEIGHBOR;
199 			break;
200 		case GDIPLUS_NEARESTNEIGHBOR:
201 			rendermode = GDIPLUS;
202 			frameparams.filter = NEARESTNEIGHBOR;
203 			break;
204 		case GDIPLUS_BILINEAR:
205 			rendermode = GDIPLUS;
206 			frameparams.filter = BILINEAR;
207 			break;
208 		case GDIPLUS_HQBICUBIC:
209 			rendermode = GDIPLUS;
210 			frameparams.filter = HQBICUBIC;
211 			break;
212 		case DIRECT3D_NEARESTNEIGHBOR:
213 			rendermode = DIRECT3D;
214 			frameparams.filter = NEARESTNEIGHBOR;
215 			break;
216 		case DIRECT3D_BILINEAR:
217 			rendermode = DIRECT3D;
218 			frameparams.filter = BILINEAR;
219 			break;
220 		case GDIPLUS_HQBILINEAR:
221 			rendermode = GDIPLUS;
222 			frameparams.filter = HQBILINEAR;
223 			break;
224 		case GDIPLUS_BICUBIC:
225 			rendermode = GDIPLUS;
226 			frameparams.filter = BICUBIC;
227 			break;
228 	}
229 }
230 
initwin(void)231 static BOOL initwin(void)
232 {
233 	LONG retval;
234 	RECT windowRect = {0,0,0,0};
235 	int xpos, ypos;
236 	static DEVMODE dmDevMode;
237 	char msgbuffer[200];
238 	WNDCLASS wc;
239 
240 	wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
241 	wc.lpfnWndProc = Atari_WindowProc;
242 	wc.cbClsExtra = 0;
243 	wc.cbWndExtra = 0;
244 	wc.hInstance = myInstance;
245 	wc.hIcon = LoadIcon(myInstance, IDI_APPLICATION);
246 	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
247 	wc.hbrBackground = GetStockObject(BLACK_BRUSH);
248 	wc.lpszMenuName = myname;
249 	wc.lpszClassName = myname;
250 
251 	RegisterClass(&wc);
252 	setmenu();
253 
254 	// save the original screen resolution settings
255 	if (!origScreenWidth && !origScreenHeight)
256 	{
257 		// Get current display settings
258 		EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dmDevMode);
259 		origScreenWidth = dmDevMode.dmPelsWidth;
260 		origScreenHeight = dmDevMode.dmPelsHeight;
261 		origScreenDepth = dmDevMode.dmBitsPerPel;
262 	}
263 
264 	// display to a window
265 	if (rendermode != DIRECTDRAW && screenmode == WINDOW)
266 	{
267 		// If the screen resolution has changed (due, perhaps, to switching into
268 		// fullscreen mode), return to the original screen resolution.
269 		retval = ChangeDisplaySettings(NULL, 0);
270 
271 		// compute a properly scaled client rect for our new window
272 		getscaledrect(&windowRect);
273 		scaledWidth = windowRect.right - windowRect.left;
274 		scaledHeight = windowRect.bottom - windowRect.top;
275 
276 		// get the coordinates necessary to center the rectangle
277 		getcenteredcoords(windowRect, &xpos, &ypos);
278 
279 		hWndMain = CreateWindowEx(0, myname, myname, DW_WINDOWSTYLE, xpos, ypos,
280 			                  scaledWidth, scaledHeight, NULL, hMainMenu, myInstance, NULL );
281 	}
282 	else  // display as fullscreen
283 	{
284 		// User has specified a custom fullscreen screen resolution.
285 		// Scan all supported graphics mode for the one that best
286 		// matches the requested resolution.
287 		if (usecustomfsresolution) {
288 			int  i, nModeExists;
289 
290 			for (i=0; ;i++) {
291 				nModeExists = EnumDisplaySettings(NULL, i, &dmDevMode);
292 
293 				if (!nModeExists) {
294 					// we've reached the end of the list
295 					retval = DISP_CHANGE_NOMODE;
296 					break;
297 				}
298 				else {
299 					// look for a graphics mode that matches our requested resolution
300 					// and has a pixel depth equal to the original screen mode.
301 				    if (dmDevMode.dmPelsWidth == fullscreenWidth &&
302 					    dmDevMode.dmPelsHeight == fullscreenHeight &&
303 					    dmDevMode.dmBitsPerPel == origScreenDepth)	{
304 
305 						// Change the resolution now.
306 						// Using CDS_FULLSCREEN means that this resolution is temporary, so Windows
307 						// rather than us, will take care of resetting this back to the default when we
308 						// exit the application.
309 						retval = ChangeDisplaySettings(&dmDevMode, CDS_FULLSCREEN);
310 						break;
311 					}
312 				}
313 			}
314 		}
315 		else  // no custom screen resolution was specified.
316 		{
317 			// Check current resolution and change it back to the default
318 			// if it is different from the current fullscreen settings.
319 			EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dmDevMode);
320 			if (dmDevMode.dmPelsWidth != fullscreenWidth ||
321 				dmDevMode.dmPelsHeight != fullscreenHeight ||
322 				dmDevMode.dmBitsPerPel != origScreenDepth)	{
323 				retval = ChangeDisplaySettings(NULL, 0);
324 				fullscreenWidth = dmDevMode.dmPelsWidth;
325 				fullscreenHeight = dmDevMode.dmPelsHeight;
326 			}
327 			else
328 				retval = DISP_CHANGE_SUCCESSFUL;
329 		}
330 
331 		// create an empty fullscreen window
332 		hWndMain = CreateWindowEx(
333 			WS_EX_TOPMOST, myname, myname, WS_POPUP, 0, 0,
334 			GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
335 			NULL, NULL, myInstance, NULL);
336 
337 		// If we were unsuccessful in changing the resolution, report this to the user,
338 		// Then attempt to recover by returning to window mode.
339 		if (retval != DISP_CHANGE_SUCCESSFUL) {
340 			sprintf(msgbuffer, "Error %d.\n\nCould not change resolution\nto requested size: %dx%d.\n\nReturning to window mode.\nPlease check your settings.",
341 					retval, fullscreenWidth, fullscreenHeight);
342 			MessageBox(hWndMain, msgbuffer, myname, MB_OK);
343 
344 			// Restore screen to window state
345 			screenmode = WINDOW;
346 			retval = ChangeDisplaySettings(NULL, 0);
347 
348 			getcenteredcoords(windowRect, &xpos, &ypos);
349 			hWndMain = CreateWindowEx(0, myname, myname, DW_WINDOWSTYLE, xpos, ypos,
350 			                  scaledWidth, scaledHeight, NULL, hMainMenu, myInstance, NULL );
351 		}
352 
353 		setcursor();
354 	}
355 
356 	if (!hWndMain)
357 		return 1;
358 
359 	return 0;
360 }
361 
362 // initialize menu the first time for a given window
initmenu(void)363 void initmenu(void)
364 {
365 	hMainMenu = CreateMenu();
366 	hFileMenu = CreateMenu();
367 	hManageMenu = CreateMenu();
368 	hSystemMenu = CreateMenu();
369 	hDisplayMenu = CreateMenu();
370 	hHelpMenu = CreateMenu();
371 	SetMenu(hWndMain, hMainMenu);
372 
373 	InsertMenu(hMainMenu, ID_FILE, MF_POPUP, (UINT)hFileMenu, "File");
374 	AppendMenu(hFileMenu, MF_STRING, ID_RUN_ATARI_PROGRAM, "&Run Atari Program...\tAlt+R");
375 	AppendMenu(hFileMenu, MF_SEPARATOR, 0, "");
376 	AppendMenu(hFileMenu, MF_STRING, ID_RESET, "Reset (Warm Start)\tF5");
377 	AppendMenu(hFileMenu, MF_STRING, ID_REBOOT, "Reboot (Cold Start)\tShift+F5");
378 	AppendMenu(hFileMenu, MF_SEPARATOR, 0, "");
379 	AppendMenu(hFileMenu, MF_STRING, ID_CONFIGURATION, "Configuration...\tF1");
380 	AppendMenu(hFileMenu, MF_STRING, ID_SAVE_CONFIG, "Save Configuration");
381 	AppendMenu(hFileMenu, MF_SEPARATOR, 0, "");
382 	AppendMenu(hFileMenu, MF_STRING, ID_BACK, "Back\tEsc");
383 	AppendMenu(hFileMenu, MF_SEPARATOR, 0, "");
384 	AppendMenu(hFileMenu, MF_STRING, ID_EXIT, "Exit\tF9");
385 
386 	InsertMenu(hMainMenu, ID_MANAGE, MF_POPUP, (UINT)hManageMenu, "Manage");
387 	AppendMenu(hManageMenu, MF_STRING, ID_DISK_MANAGEMENT, "&Disks...\tAlt+D");
388 	AppendMenu(hManageMenu, MF_STRING, ID_CART_MANAGEMENT, "&Cartridges...\tAlt+C");
389 	AppendMenu(hManageMenu, MF_STRING, ID_TAPE_MANAGEMENT, "Tapes...");
390 	AppendMenu(hManageMenu, MF_SEPARATOR, 0, "");
391 	AppendMenu(hManageMenu, MF_STRING, ID_EMULATOR_BIOS, "Emulator/BIOS...");
392 
393 	InsertMenu(hMainMenu, ID_SYSTEM, MF_POPUP, (UINT)hSystemMenu, "System");
394 	AppendMenu(hSystemMenu, MF_STRING, ID_NTSC, "NTSC");
395 	AppendMenu(hSystemMenu, MF_STRING, ID_PAL, "PAL");
396 	AppendMenu(hSystemMenu, MF_SEPARATOR, 0, "");
397 	AppendMenu(hSystemMenu, MF_STRING, ID_SELECT_MACHINE, "Select Machine...\tAlt+Y");
398 	AppendMenu(hSystemMenu, MF_SEPARATOR, 0, "");
399 	AppendMenu(hSystemMenu, MF_STRING, ID_SOUND, "Sound...\tAlt+O");
400 	AppendMenu(hSystemMenu, MF_STRING, ID_CONTROLLERS, "Controllers...");
401 	if (Atari800_tv_mode == Atari800_TV_NTSC) {
402 		CheckMenuItem(hSystemMenu, ID_NTSC, MF_CHECKED);
403 		CheckMenuItem(hSystemMenu, ID_PAL, MF_UNCHECKED);
404 	}
405 	else {
406 		CheckMenuItem(hSystemMenu, ID_NTSC, MF_UNCHECKED);
407 		CheckMenuItem(hSystemMenu, ID_PAL, MF_CHECKED);
408 	}
409 
410 	InsertMenu(hMainMenu, ID_DISPLAY, MF_POPUP, (UINT)hDisplayMenu, "Display");
411 	AppendMenu(hDisplayMenu, MF_STRING, ID_FULLSCREEN, "Toggle Fullscreen\tAlt+Enter");
412 	AppendMenu(hDisplayMenu, MF_SEPARATOR, 0, "");
413 	AppendMenu(hDisplayMenu, MF_STRING, ID_WINDOW_SIZE_UP, "Increase Window Size\tAlt+PgUp");
414 	AppendMenu(hDisplayMenu, MF_STRING, ID_WINDOW_SIZE_DOWN, "Decrease Window Size\tAlt+PgDn");
415 	AppendMenu(hDisplayMenu, MF_SEPARATOR, 0, "");
416 	AppendMenu(hDisplayMenu, MF_STRING, ID_ATARI_DISPLAY, "General Display Settings...");
417 	AppendMenu(hDisplayMenu, MF_STRING, ID_WINDOWS_DISPLAY, "Windows Display Options...");
418 
419 	InsertMenu(hMainMenu, ID_HELP, MF_POPUP, (UINT)hHelpMenu, "Help");
420 	AppendMenu(hHelpMenu, MF_STRING, ID_FUNCTION_KEY_HELP, "Function Key List...");
421 	AppendMenu(hHelpMenu, MF_STRING, ID_HOT_KEY_HELP, "Hot Key List...");
422 	AppendMenu(hHelpMenu, MF_SEPARATOR, 0, "");
423 	AppendMenu(hHelpMenu, MF_STRING, ID_ABOUT_ATARI800, "About Atari800...\tAlt+A");
424 }
425 
426 // restore a previously initialized menu to the window
restoremenu(void)427 void restoremenu(void)
428 {
429 	// menus are never shown in fullscreen mode
430 	if (screenmode == FULLSCREEN)
431 		return;
432 
433 	LoadMenu((HINSTANCE)hWndMain, (LPCSTR)hMainMenu);
434 	SetMenu(hWndMain, hMainMenu);
435 }
436 
437 // remove menu from the window
destroymenu(void)438 void destroymenu(void)
439 {
440 	SetMenu(hWndMain, NULL);
441 	DestroyMenu((HMENU)hWndMain);
442 }
443 
444 // set the menu based on the current state
setmenu(void)445 void setmenu(void)
446 {
447 	if (showmenu && !hMainMenu)
448 		initmenu();
449 	else if (showmenu)
450 		restoremenu();
451 	else if (!showmenu)
452 		destroymenu();
453 
454 	changewindowsize(RESET, 0);
455 }
456 
457 // switch menu on or off
togglemenustate(void)458 void togglemenustate(void)
459 {
460 	showmenu = !showmenu;
461 	setmenu();
462 }
463 
464 // helper function that creates a scaled rectangle with the exact client area that we want.
getscaledrect(RECT * rect)465 void getscaledrect(RECT *rect)
466 {
467 	// This function is intended for windowed mode only
468 	if (screenmode == FULLSCREEN)
469 		return;
470 
471 	// Set base window size based on selected aspect mode
472 	if (aspectmode == WIDE || aspectmode == AUTO) {
473 
474 		// Enforce strict 7:5 aspect dimensions for the window
475 		// if the aspect ratio is locked.
476 		if (lockaspect) {
477 		    scrwidth = SCREENWIDTH;
478 			scrheight = SCREENHEIGHT;
479 		}
480 		else
481 		{
482 			// Otherwise allow dimensions of the window to dynamically
483 			// vary based on the crop.horizontal and crop.vertical values.
484 			scrwidth = SCREENWIDTH - crop.horizontal * 2;
485 			scrheight = SCREENHEIGHT - crop.vertical * 2;
486 		}
487 	}
488 	else {  // a cropped mode was selected
489 	    if (lockaspect) {
490 			// Enforce strict 4:3 aspect dimensions for the window
491 			scrwidth = CROPPEDWIDTH;
492 			scrheight = SCREENHEIGHT;
493 		}
494 		else {
495 			// Allow dimensions of the window to dynamically vary
496 			// based on the crop.horizontal and crop.vertical values.
497 			scrwidth = CROPPEDWIDTH - crop.horizontal * 2;
498 			scrheight = SCREENHEIGHT - crop.vertical * 2;
499 		}
500 	}
501 
502 	// now scale the window dimensions based on the windowscale
503 	scaledWidth = (int)(scrwidth * windowscale / 100.0f);
504 	scaledHeight = (int)(scrheight * windowscale / 100.0f);
505 
506 	// prevent the scaled size from exceeding the screen resolution
507 	if (scaledWidth > GetSystemMetrics(SM_CXSCREEN) || scaledHeight > GetSystemMetrics(SM_CYSCREEN))
508 	{
509 		scaledWidth = GetSystemMetrics(SM_CXSCREEN);
510 		scaledHeight = GetSystemMetrics(SM_CYSCREEN);
511 	}
512 
513 	// set a preliminary scaled rectangle
514 	// to the calculated size...
515 	rect->right = scaledWidth;
516 	rect->bottom = scaledHeight;
517 
518 	// To avoid screen artifacts in windowed mode, especially when using scanlines, it is important
519 	// that the client area of the window is of the exact scaled dimensions.  CreateWindow/MoveWindow by itself
520 	// does not do this, therefore, we need the AdjustWindowRectEx() method to help enforce the dimensions.
521 	AdjustWindowRectEx(rect, DW_WINDOWSTYLE, showmenu, 0);
522 }
523 
cmdlineFail(char msg[])524 static void cmdlineFail(char msg[])
525 {
526 	char txt[256];
527 	sprintf(txt, "There was an invalid or missing argument\nfor commandline parameter: %s\nThe emulator may not run as expected.", msg);
528 	MessageBox(NULL, txt, "Commandline Error", MB_OK | MB_ICONWARNING);
529 }
530 
531 // switches to the next scanline mode
changescanlinemode()532 void changescanlinemode()
533 {
534 	frameparams.scanlinemode++;
535 	if (frameparams.scanlinemode == HIGH + 1)
536 		frameparams.scanlinemode = NONE;
537 
538 	// Force rewrite frame parameters
539 	refreshframe();
540 
541 	return;
542 }
543 
544 // Change the 3D tilt effect to the next mode
545 // This only has an effect in Direct3D mode
changetiltlevel()546 void changetiltlevel()
547 {
548 	frameparams.tiltlevel++;
549 	if (frameparams.tiltlevel > TILTLEVEL3)
550 		frameparams.tiltlevel = TILTLEVEL0;
551 }
552 
553 // Turns the 3D screensaver on or off.
554 // This only has an effect in Direct3D mode
togglescreensaver()555 void togglescreensaver()
556 {
557 	if (rendermode == DIRECT3D)
558 	{
559 		frameparams.screensaver = !frameparams.screensaver;
560 		frameparams.d3dRefresh = TRUE;
561 	}
562 }
563 
564 // toggle between fullscreen and windowed modes
togglewindowstate()565 void togglewindowstate()
566 {
567 	int showCmd = SW_RESTORE;
568 
569 	// not supported in DirectDraw mode
570 	if (rendermode == DIRECTDRAW)
571 		return;
572 
573 	// when the window is destroyed, we lose its keyboard handler
574 	// so let's uninit it here and reinit after we create the new window.
575 	uninitinput();
576 
577 	if (screenmode == FULLSCREEN)	{
578 		screenmode = WINDOW; /* switch to windowed mode */
579 	}
580 	else {  // switch to fullscreen
581 
582 		// ...also save the current windowed state
583 		// in case we need to return to it from fullscreen.
584 		GetWindowRect(hWndMain, &currentwindowrect);
585 		currentwindowstate.length = sizeof(WINDOWPLACEMENT);
586 		GetWindowPlacement(hWndMain, &currentwindowstate);
587 
588 		screenmode = FULLSCREEN;  /* keep screenmode sync'ed with mode */
589 	}
590 
591 	SuppressNextQuitMessage(); // prevents DestroyWindow from closing the application
592 	destroymenu();
593 	DestroyWindow(hWndMain);
594 	initwin();
595 
596 	/* if we're in Direct3D rendering mode, we need to do a full
597 	   shutdown and restart of the Direct3D engine. */
598 	if (rendermode == DIRECT3D) {
599 		shutdowndirect3d();
600 
601 		if (screenmode == FULLSCREEN)
602 			startupdirect3d(fullscreenWidth, fullscreenHeight, FALSE, &frameparams);
603 		else
604 			startupdirect3d(currentwindowrect.right - currentwindowrect.left,
605 					currentwindowrect.bottom - currentwindowrect.top, TRUE, &frameparams);
606 	}
607 
608 	// if we're returning to windowed mode, restore it to its previous size and location.
609     // If not, use the default metrics.
610 	if (screenmode == WINDOW && currentwindowrect.bottom && currentwindowrect.right)
611 	{
612 		MoveWindow(hWndMain, currentwindowrect.left, currentwindowrect.top,
613 				   currentwindowrect.right - currentwindowrect.left,
614 		    	   currentwindowrect.bottom - currentwindowrect.top, TRUE);
615 
616 		if (currentwindowstate.showCmd == SW_SHOWMAXIMIZED)
617 			   showCmd = SW_MAXIMIZE;
618 
619 		changewindowsize(RESET, 0);
620 	}
621 
622 	ShowWindow(hWndMain, showCmd);
623 	setmenu();
624 
625 	// keep sound muted in UI
626 	if (UI_is_active)
627 		Sound_Pause();
628 
629 	// reinit the keyboard input and clear it
630 	initinput();
631 	clearkb();
632 
633 	setcursor();
634 
635 	return;
636 }
637 
638 /* 'scale' is expressed as a percentage (100 = 100%)     */
639 /* CHANGEWINDOWSIZE indicates whether to STEPUP or STEPDOWN */
640 /* by the scale percentage, or to just simply SET or RESET it. */
641 /* Note that when the RESET parameter is used, the scale parameter */
642 /* is ignored. */
changewindowsize(CHANGEWINDOWSIZE cws,int scale)643 void changewindowsize(CHANGEWINDOWSIZE cws, int scale)
644 {
645 	RECT rect = {0,0,0,0};
646 	WINDOWPLACEMENT wndpl;
647 	int xpos, ypos;
648 	int originalwindowscale = windowscale;
649 
650 	// not supported in DirectDraw mode
651 	if (rendermode == DIRECTDRAW)
652 		return;
653 
654 	// in simple mode, force scale stepping to 100% jumps
655 	if (scalingmethod == SIMPLE && (cws == STEPUP || cws == STEPDOWN))
656 		scale = 100;
657 
658 	// don't change window size if window is maximized, minimized, or fullscreen
659 	wndpl.length = sizeof(WINDOWPLACEMENT);
660 	GetWindowPlacement(hWndMain, &wndpl);
661 	if (wndpl.showCmd == SW_SHOWMAXIMIZED || wndpl.showCmd == SW_SHOWMINIMIZED || screenmode == FULLSCREEN)
662 		return;
663 
664 	switch (cws)
665 	{
666 		case STEPUP:
667 			// adjust current windowscale as an even multiple of the scale parameter
668 			windowscale = currentscale / scale * scale;
669 			windowscale += scale;
670 			break;
671 
672 		case STEPDOWN:
673 			windowscale = currentscale / scale * scale;
674 			if (windowscale == currentscale)
675 				windowscale -= scale;
676 			if (windowscale < 100)
677 			{
678 				windowscale = 100;
679 				return;
680 			}
681 			break;
682 
683 		case SET:
684 			windowscale = scale;
685 			break;
686 
687 		case RESET:
688 			windowscale = currentscale;
689 	}
690 
691 	getscaledrect(&rect);
692 
693 	// we've oversized, so back off and get out
694 	if ((rect.bottom - rect.top) > GetSystemMetrics(SM_CYSCREEN))
695 	{
696 		windowscale = originalwindowscale;
697 		return;
698 	}
699 
700 	// Position the new resized window relative to previous window position
701 	xpos = wndpl.rcNormalPosition.left+
702 	       (((wndpl.rcNormalPosition.right-wndpl.rcNormalPosition.left)-
703 		   (rect.right-rect.left)) / 2);
704 
705 	ypos = wndpl.rcNormalPosition.top+
706 	       (((wndpl.rcNormalPosition.bottom-wndpl.rcNormalPosition.top)-
707 		   (rect.bottom-rect.top)) / 2);
708 
709 	MoveWindow(hWndMain, xpos, ypos,
710 	           rect.right - rect.left, rect.bottom - rect.top, TRUE);
711 
712 	return;
713 }
714 
715 // Short helper function that computes an x,y position that can be used
716 // by MoveWindow to center a window having the given rectangle.
getcenteredcoords(RECT rect,int * x,int * y)717 void getcenteredcoords(RECT rect, int* x, int* y)
718 {
719 	*x = (GetSystemMetrics(SM_CXSCREEN) / 2) - ((rect.right - rect.left) / 2);
720 	*y = (GetSystemMetrics(SM_CYSCREEN) / 2) - ((rect.bottom - rect.top) / 2);
721 }
722 
723 // Returns the native atari coordinates given a set of windows coordinates.
724 // These coordinates are relative to the full 384x240 screen buffer.
725 // a coordinate of -1,-1 indicates a click outside the screen buffer.
getnativecoords(int mx,int my,int * nx,int * ny)726 void getnativecoords(int mx, int my, int* nx, int* ny)
727 {
728 	RECT rect;
729 	float fx;
730 	float fy;
731 
732 	GetClientRect(hWndMain, &rect);
733 
734 	// adjust input mouse coordinates based on client rectangle size
735 	my = my - ((rect.bottom - rect.top) - frameparams.height) / 2;
736 	mx = mx - ((rect.right - rect.left) - frameparams.width) / 2;
737 
738 	// transform absolute mouse coordinates into relative coordinates
739 	fx = (float)mx/frameparams.width;
740 	fy = (float)my/frameparams.height;
741 
742 	// compute the absolute atari coordinates relative to the
743 	// 384x240 screen buffer accounting for offsets and cropping
744 	*nx = (int)((frameparams.view.right - frameparams.view.left) * fx
745 	            + frameparams.view.left);
746 
747 	*ny = (int)((frameparams.view.bottom - frameparams.view.top) * fy
748 	            + frameparams.view.top);
749 
750 	// force any points determined to be outside the screen buffer to -1,-1
751 	*nx = (*nx < frameparams.view.left || *nx >= frameparams.view.right) ? -1 : *nx;
752 	*ny = (*ny < frameparams.view.top || *ny >= frameparams.view.bottom) ? -1 : *ny;
753 
754 	if (*nx <= -1 || *ny <= -1 || *nx >= Screen_WIDTH || *ny >= Screen_HEIGHT)
755 		*nx = *ny = -1;
756 }
757 
758 // Parse the width and height from a string formated as
759 // widthxheight (such as 800x600, 1280x720, etc.)
getres(char res[],int * width,int * height)760 BOOL getres(char res[], int *width, int *height)
761 {
762 	size_t pos;
763 	char sep[] = "x";
764 
765 	pos = strcspn(res, sep);
766 	if (pos == strlen(res))
767 		return FALSE;	  // missing x delimiter
768 
769 	*width = atoi(strtok(res, sep));
770 	*height = atoi(strtok(NULL, sep));
771 
772 	if (*width == 0 || *height == 0)
773 		return FALSE;     // value could not be converted
774 
775 	return TRUE;
776 }
777 
778 // Process the commandline, then initialize the selected renderer
gron(int * argc,char * argv[])779 int gron(int *argc, char *argv[])
780 {
781 	int i, j;
782 	int retvalue = 0;
783 	RECT rect = {0,0,0,0};
784 	int xpos, ypos;
785 
786 	// set some defaults
787 	int width = 0;
788 	int help_only = FALSE;
789 
790 	// process commandline parameters and arguments
791 	for (i = j = 1; i < *argc; i++) {
792 		if (strcmp(argv[i], "-windowed") == 0)
793 			screenmode = WINDOW;
794 		else if (strcmp(argv[i], "-width") == 0) {
795 			rendermode = DIRECTDRAW;
796 			width = Util_sscandec(argv[++i]);
797 		}
798 		else if (strcmp(argv[i], "-blt") == 0) {
799 			rendermode = DIRECTDRAW;
800 			bltgfx = TRUE;
801 		}
802 		else if (strcmp(argv[i], "-hidecursor") == 0)
803 			hidecursor = TRUE;
804 		else if (strcmp(argv[i], "-lockaspect") == 0)
805 			lockaspect = TRUE;
806 		else if (strcmp(argv[i], "-fullscreen") == 0)
807 			screenmode = FULLSCREEN;
808 		else if (strcmp(argv[i], "-hidemenu") == 0)
809 			showmenu = FALSE;
810 		else if (strcmp(argv[i], "-winscale") == 0)
811 			windowscale = Util_sscandec(argv[++i]);
812 		else if (strcmp(argv[i], "-hcrop") == 0)
813 			crop.horizontal = atoi(argv[++i]);
814 		else if (strcmp(argv[i], "-vcrop") == 0)
815 			crop.vertical = atoi(argv[++i]);
816 		else if (strcmp(argv[i], "-crop") == 0) {
817 			crop.horizontal = atoi(argv[++i]);
818 			crop.vertical = atoi(argv[++i]);
819 		}
820 		else if (strcmp(argv[i], "-hoffset") == 0)
821 			offset.horizontal = atoi(argv[++i]);
822 		else if (strcmp(argv[i], "-voffset") == 0)
823 			offset.vertical = atoi(argv[++i]);
824 		else if (strcmp(argv[i], "-scanlines") == 0)
825 		{
826 			if (!checkparamarg(argv[++i]))
827 				cmdlineFail(argv[i-1]);
828 			else if (strcmp(argv[i], "low") == 0)
829 				frameparams.scanlinemode = LOW;
830 			else if (strcmp(argv[i], "medium") == 0)
831 				frameparams.scanlinemode = MEDIUM;
832 			else if (strcmp(argv[i], "high") == 0)
833 				frameparams.scanlinemode = HIGH;
834 		}
835 		else if (strcmp(argv[i], "-render") == 0)
836 		{
837 			if (!checkparamarg(argv[++i]))
838 				cmdlineFail(argv[i-1]);
839 			else if (strcmp(argv[i], "ddraw") == 0)
840 			{
841 				rendermode = DIRECTDRAW;
842 				bltgfx = TRUE;
843 			}
844 			else if (strcmp(argv[i], "gdi") == 0) {
845 				frameparams.filter = NEARESTNEIGHBOR;
846 				rendermode = GDI;
847 			}
848 			else if (strcmp(argv[i], "gdiplus") == 0) {
849 				frameparams.filter = NEARESTNEIGHBOR;
850 				rendermode = GDIPLUS;
851 			}
852 			else if (strcmp(argv[i], "direct3d") == 0) {
853 				frameparams.filter = NEARESTNEIGHBOR;
854 				rendermode = DIRECT3D;
855 			}
856 		}
857 		else if (strcmp(argv[i], "-filter") == 0)
858 		{
859 			if (!checkparamarg(argv[++i]))
860 				cmdlineFail(argv[i-1]);
861 			else if (strcmp(argv[i], "pixel") == 0)
862 				frameparams.filter = NEARESTNEIGHBOR;
863 			else if (strcmp(argv[i], "bilinear") == 0)
864 				frameparams.filter = BILINEAR;
865 			else if (strcmp(argv[i], "bicubic") == 0)
866 				frameparams.filter = BICUBIC;
867 			else if (strcmp(argv[i], "hqbilinear") == 0)
868 				frameparams.filter = HQBILINEAR;
869 			else if (strcmp(argv[i], "hqbicubic") == 0)
870 				frameparams.filter = HQBICUBIC;
871 		}
872 		else if (strcmp(argv[i], "-scaling") == 0)
873 		{
874 			if (!checkparamarg(argv[++i]))
875 				cmdlineFail(argv[i-1]);
876 			else if (strcmp(argv[i], "off") == 0)
877 				scalingmethod = OFF;
878 			else if (strcmp(argv[i], "normal") == 0)
879 				scalingmethod = NORMAL;
880 			else if (strcmp(argv[i], "simple") == 0)
881 				scalingmethod = SIMPLE;
882 			else if (strcmp(argv[i], "adaptive") == 0)
883 				scalingmethod = ADAPTIVE;
884 		}
885 		else if (strcmp(argv[i], "-aspect") == 0)
886 		{
887 			if (!checkparamarg(argv[++i]))
888 				cmdlineFail(argv[i-1]);
889 			else if (strcmp(argv[i], "auto") == 0)
890 				aspectmode = AUTO;
891 			else if (strcmp(argv[i], "wide") == 0)
892 				aspectmode = WIDE;
893 			else if (strcmp(argv[i], "cropped") == 0)
894 				aspectmode = CROPPED;
895 			else if (strcmp(argv[i], "compressed") == 0)
896 				aspectmode = COMPRESSED;
897 		}
898 		else if (strcmp(argv[i], "-fsres") == 0) {
899 			getres(argv[++i], &fullscreenWidth, &fullscreenHeight);
900 			usecustomfsresolution = TRUE;
901 		}
902 		else {
903 			if (strcmp(argv[i], "-help") == 0) {
904 				Log_print("\t-windowed        Run in a window");
905 				Log_print("\t-width <num>     Set display mode width (ddraw mode only)");
906 				Log_print("\t-blt             Use blitting to draw graphics (ddraw mode only)");
907 				Log_print("\t-hidecursor      Always hide mouse cursor in fullscreen");
908 				Log_print("\t-fullscreen      Run in fullscreen");
909 				Log_print("\t-winscale <size> Default window size <100> <200> <300>... etc.");
910 				Log_print("\t-scanlines <%%>   Scanline mode <low> <medium> <high>");
911 				Log_print("\t-render <mode>   Render mode <ddraw> <gdi> <gdiplus> <direct3d>");
912 				Log_print("\t-filter <mode>   Filter <bilinear> <bicubic> <hqbilinear> <hqbicubic>");
913 				Log_print("\t-scaling <mode>  Scaling method <off> <normal> <simple> <adaptive>");
914 				Log_print("\t-aspect <mode>   Aspect mode <auto> <wide> <cropped> <compressed>");
915 				Log_print("\t-fsres <res>     Fullscale resolution <widthxheight> viz. <640x480>");
916 				Log_print("\t-vcrop <amt>     Crop screen vertically <0..108>");
917 				Log_print("\t-hcrop <amt>     Crop screen horizontally <-24..150>");
918 				Log_print("\t-voffset <amt>   Offset screen vertically <-50..50>");
919 				Log_print("\t-hoffset <amt>   Offset screen horizontally <-24..24>");
920 				Log_print("\t-lockaspect      Lock aspect mode when trimming");
921 				Log_print("\t-hidemenu        Hide Windows menu");
922 				help_only = TRUE;
923 			}
924 			argv[j++] = argv[i];
925 		}
926 	}
927 
928 	if (help_only)
929 		return FALSE;
930 
931 	*argc = j;
932 
933 	displaymode = GetActiveDisplayMode();
934 
935 	initwin();  // initialize a window
936 
937 	// Do render mode-specific initialization
938 	switch (rendermode)
939 	{
940 		case DIRECTDRAW:
941 			retvalue = startupdirectdraw(bltgfx, width);
942 			break;
943 		case GDIPLUS:
944 			startupgdiplus();
945 			break;
946 		case DIRECT3D:
947 			if (screenmode == WINDOW)
948 			{
949 				startupdirect3d(scaledWidth, scaledHeight, TRUE, &frameparams);
950 
951 				// Direct3D overrides the creation of the Window, so we must
952 				// manually rescale it to ensure it is the exact size we want.
953 				getscaledrect(&rect);
954 				getcenteredcoords(rect, &xpos, &ypos);
955 				MoveWindow(hWndMain, xpos, ypos, rect.right - rect.left,
956 		   			       rect.bottom - rect.top, TRUE);
957 			}
958 			else if (screenmode == FULLSCREEN)
959 			{
960 				startupdirect3d(fullscreenWidth, fullscreenHeight, FALSE, &frameparams);
961 			}
962 			break;
963 	}
964 
965 	return retvalue;
966 }
967 
968 // Refresh the frame -- called on every frame
refreshv(UBYTE * scr_ptr)969 void refreshv(UBYTE *scr_ptr)
970 {
971 	float fHorizAspect, fVertAspect;
972 	PAINTSTRUCT ps;
973 	INT nRectWidth, nRectHeight;
974 	INT nBaseWidth, nBaseHeight;
975 	INT i;
976 	RECT rt;
977 
978 	/* DirectDraw mode does not support windowed modes, or aspect ratio processing
979 	   (nor much else for that matter), so redirect immediately to the DirectDraw renderer. */
980 	if (rendermode == DIRECTDRAW)
981 	{
982 		refreshv_directdraw(scr_ptr, bltgfx);
983 		return;
984 	}
985 
986 	GetClientRect(hWndMain, &rt);
987 
988 	if (refreshframeparams)
989 		InvalidateRect(hWndMain, &rt, TRUE);   // blank screen to avoid artifacts
990 	else
991 		InvalidateRect(hWndMain, &rt, FALSE);  // don't blank screen
992 
993 	// open the device context for painting
994 	frameparams.hdc = BeginPaint(hWndMain, &ps);
995 
996 	// Generic frame and window operations are performed inside this section.
997 	// In order to avoid performing all of this work on every frame, we only process
998 	// when a function has set the refreshframeparams flag to true.
999 	if (refreshframeparams)
1000 	{
1001 		nRectWidth = rt.right - rt.left;
1002 		nRectHeight = rt.bottom - rt.top;
1003 		frameparams.width = nRectWidth;
1004 		frameparams.height = nRectHeight;
1005 		frameparams.y_origin = 0;
1006 		frameparams.x_origin = 0;
1007 		frameparams.d3dRefresh = TRUE;
1008 		cropamount = 0;
1009 
1010 		// Scale and enforce aspect ratio
1011 		if (scalingmethod)
1012 		{
1013 			// Set standard 4:3 crop if we're running in crop mode.
1014 	        if (aspectmode == CROPPED)
1015 	        {
1016 				// 4:3 aspect ratio in CROPPED and AUTO/FULLSCREEN modes
1017 				// are created by specifying an 8 pixel horizontal crop.
1018 		        cropamount = STD_CROP;
1019 			}
1020 			else if (aspectmode == AUTO && screenmode == FULLSCREEN)
1021 			{
1022 				// if we're in AUTO mode and fullscreen, then test the fullscreen
1023 				// aspect ratio to see whether we need to crop.
1024 				if ((float)fullscreenWidth / (float)fullscreenHeight < 7.0f/5.0f)
1025 					cropamount = STD_CROP;
1026 			}
1027 
1028 			// Set the *display* aspect ratio.
1029 			// Wide modes are set nominally to an aspect ratio of 7:5
1030 			if (aspectmode == WIDE || (aspectmode == AUTO && screenmode == WINDOW) ||
1031 			   (aspectmode == AUTO && screenmode == FULLSCREEN && cropamount == 0))
1032 			{
1033 				// Enforce a strict WIDE aspect ratio of 7:5
1034 				// if the aspect ratio is locked.
1035 				if (lockaspect) {
1036 					fHorizAspect = 7.0f;
1037 					fVertAspect = 5.0f;
1038 				}
1039 				else {
1040 					// Otherwise, allow aspect ratio to vary dynamically based on the
1041 					// crop.horizontal and crop.vertical values.
1042 					fHorizAspect = (float)SCREENWIDTH - crop.horizontal * 2;
1043 					fVertAspect = (float)SCREENHEIGHT - crop.vertical * 2;
1044 				}
1045 			}
1046 			else // aspect must be COMPRESSED, CROPPED, or AUTO/FULLSCREEN/CROP
1047 			{
1048 				// Enforce a strict 4:3 aspect ratio if aspect is locked.
1049 				if (lockaspect) {
1050 					fHorizAspect = 4.0f;
1051 					fVertAspect = 3.0f;
1052 				}
1053 				else {
1054 					// Allow aspect ratio to vary dynamically based on the
1055 					// crop.horizontal and crop.vertical values if lockaspect is set to false.
1056 					fHorizAspect = (float)CROPPEDWIDTH - crop.horizontal * 2;
1057 					fVertAspect = (float)SCREENHEIGHT - crop.vertical * 2;
1058 				}
1059 			}
1060 
1061 			if (screenmode == FULLSCREEN && scalingmethod == ADAPTIVE)
1062 			{
1063 				// ADAPTIVE scaling -
1064 				// ** Intended for widescreen monitors that stretch non-native resolutions **:
1065 				// In fullscreen legacy (4:3) resolutions like VGA running on a
1066 				// widescreen monitor, some users may want to squeeze the screen back into a
1067 				// more normalized aspect if (and only if) the monitor stretches these resolutions.
1068 				// To do this, we create a compensation aspect ratio based on the assumption
1069 				// that the original screen resolution is the native resolution --
1070 				// or at least one that has square (or nearly square) pixels.
1071 
1072 				float fOrigAspect = (float)origScreenWidth / (float)origScreenHeight;
1073 				float fFullAspect = (float)fullscreenWidth / (float)fullscreenHeight;
1074 				float fAdjustedAspect = fFullAspect * fHorizAspect/fVertAspect;
1075 				float fCompensationAspect = fAdjustedAspect / fOrigAspect;
1076 				float fCompensatedWidth = (float)fullscreenHeight * fCompensationAspect;
1077 
1078 				frameparams.width = (int)fCompensatedWidth;
1079 				frameparams.height = fullscreenHeight;
1080 				frameparams.x_origin = (int)((nRectWidth - frameparams.width) / 2.0f);
1081 			}
1082 			else if (scalingmethod == SIMPLE)
1083 			{
1084 				// SIMPLE scaling
1085 				// This fullscreen mode sizes the Atari screen by a simple even multiple
1086 				// of its native size, and finds the largest that will fit within the
1087 				// currently set client rectangle size.
1088 
1089                 // Set the base height/width of Atari screen
1090 				// Account for possible cropping if the user has specified it.
1091 				if (lockaspect) {
1092 					if (aspectmode == WIDE || (aspectmode == AUTO && cropamount == 0))
1093 						nBaseWidth = SCREENWIDTH;
1094 					else
1095 						nBaseWidth = CROPPEDWIDTH;
1096 
1097 					nBaseHeight = SCREENHEIGHT;
1098 				}
1099 				else {
1100 					if (aspectmode == WIDE || (aspectmode == AUTO && cropamount == 0))
1101 						nBaseWidth = SCREENWIDTH - crop.horizontal * 2;
1102 					else
1103 						nBaseWidth = CROPPEDWIDTH - crop.horizontal * 2;
1104 
1105 					nBaseHeight = SCREENHEIGHT - crop.vertical * 2;
1106 				}
1107 
1108                 // Find the largest even multiple of the Atari screen
1109                 // that will fit inside the client rectangle.
1110                 for (i = 1; ;i++)
1111 					if (nBaseWidth * i > nRectWidth || nBaseHeight * i > nRectHeight)
1112 						break;
1113 
1114                 // Finally, set the frame size and location
1115                 frameparams.height = nBaseHeight * (i-1);
1116                 frameparams.width = nBaseWidth * (i-1);
1117                 frameparams.y_origin = (int)((nRectHeight - frameparams.height) / 2.0f);
1118                 frameparams.x_origin = (int)((nRectWidth - frameparams.width) / 2.0f);
1119 			}
1120 			else  // we're in either a window or fullscreen with NORMAL aspect processing
1121 			{
1122 				// we have a tall narrow window, so pin to the width and
1123 				// adjust the height to force the aspect ratio.
1124 				if (nRectHeight >= nRectWidth * fVertAspect/fHorizAspect)
1125 				{
1126 					frameparams.height = (int)(nRectWidth * fVertAspect/fHorizAspect);
1127 					frameparams.width = nRectWidth;
1128 					frameparams.y_origin = (int)((nRectHeight - frameparams.height) / 2.0f);
1129 				}
1130 				// we have a wide, fat window, so pin to the height and
1131 				// adjust the width to force the aspect ratio.
1132 				else
1133 				{
1134 					frameparams.width = (int)(nRectHeight * fHorizAspect/fVertAspect);
1135 					frameparams.height = nRectHeight;
1136 					frameparams.x_origin = (int)((nRectWidth - frameparams.width) / 2.0f);
1137 				}
1138 			}
1139 		}
1140 
1141 		// Specify the viewport rectangle that will be sent to the renderer.  This is a
1142 		// rectangle that encloses the portion of the screen buffer that we want to display.
1143 		frameparams.view.left = STD_INDENT + cropamount + crop.horizontal;
1144 		frameparams.view.top = crop.vertical;
1145 		frameparams.view.right = Screen_WIDTH - frameparams.view.left;
1146 		frameparams.view.bottom = Screen_HEIGHT - frameparams.view.top;
1147 
1148     	// If an offset has been set by the user, adjust the viewport by that amount.
1149 		// The negative signs reverse the shift direction to make it more natural.
1150 		OffsetRect(&frameparams.view, -offset.horizontal, -offset.vertical);
1151 
1152 		/* When doing a refreshframeparams refresh, perform these direct3d specific  */
1153 		/* operations.  This should be done regardless of aspect ratio settings.     */
1154 		if (rendermode == DIRECT3D)
1155 		{
1156 			// Update window dimensions
1157 			frameparams.d3dWidth = (float)frameparams.width / nRectWidth;
1158 			frameparams.d3dHeight = (float)frameparams.height / nRectHeight;
1159 
1160 			// Although Direct3D can handle window resizing with no intervention, it is not
1161 			// optimal since it does not automatically change the backbuffer size to match the new
1162 			// window size.  This can result in lower quality after a resize.  However, if we take
1163 			// the time to manually reset the device and change the backbuffer size, we can maintain
1164 			// a high quality image.
1165 			resetdevice(nRectWidth, nRectHeight, screenmode, &frameparams);
1166 		}
1167 
1168 		// Update the title bar with resolution, scale, scanline information
1169 		if (screenmode == WINDOW)
1170 		{
1171 			char buffer[100];
1172 			char scanlinetxt[25];
1173 
1174             // The displayed window scale is actually pixel based rather than screen size
1175 			// based to make it more useful.  To compute this we use the vertical screen
1176 			// dimension, since its relatively constant, and factor out any vertical trim.
1177 
1178 			currentscale = (int)Util_round(((float)frameparams.height +
1179 			                                (crop.vertical * 2 * ((float)windowscale/100)))
1180 			                               / 240.0f * 100);
1181 
1182 			switch (frameparams.scanlinemode)
1183 			{
1184 				case NONE:
1185 					strcpy(scanlinetxt, "");
1186 					break;
1187 				case LOW:
1188 					strcpy(scanlinetxt, "; Scanlines: Low (1x)");
1189 					break;
1190 				case MEDIUM:
1191 					strcpy(scanlinetxt, "; Scanlines: Medium (2x)");
1192 					break;
1193 				case HIGH:
1194 					strcpy(scanlinetxt, "; Scanlines: High (3x)");
1195 					break;
1196 			}
1197 
1198 			sprintf(buffer, "%s - %dx%d (%d%%)%s",myname, frameparams.width, frameparams.height, currentscale, scanlinetxt);
1199 			SetWindowText(hWndMain, buffer);
1200 		}
1201 
1202 		refreshframeparams = FALSE;  // we're done refreshing so set the fp_refresh global to FALSE
1203 	}
1204 
1205 	// sanity check to make sure the window has some real estate
1206 	if ((!frameparams.width) || (!frameparams.height))
1207 		return;
1208 
1209 	// Now that we've completed our generic screen processing, pass the screen buffer along
1210 	// with the calculated frame parameters over to the specific renderer the user has specified
1211 	// in order to actually render the frame.
1212 	switch (rendermode)
1213 	{
1214 		case GDI:
1215 			refreshv_gdi(scr_ptr, &frameparams);
1216 			break;
1217 		case GDIPLUS:
1218 			refreshv_gdiplus(scr_ptr, &frameparams);
1219 			break;
1220 		case DIRECT3D:
1221 			refreshv_direct3d(scr_ptr, &frameparams);
1222 			break;
1223 	}
1224 
1225 	// We are finished writing to the DC
1226 	EndPaint(hWndMain, &ps);
1227 
1228 	ValidateRect(hWndMain, &rt);
1229 }
1230 
1231