#ifndef GP_NO_PLATFORM #ifdef WIN32 #include "Base.h" #include "Platform.h" #include "FileSystem.h" #include "Game.h" #include "Form.h" #include "Vector2.h" #include "ScriptController.h" #include #include #include #include #ifdef GP_USE_GAMEPAD #include #endif using gameplay::print; // Window defaults #define DEFAULT_RESOLUTION_X 1024 #define DEFAULT_RESOLUTION_Y 768 #define DEFAULT_COLOR_BUFFER_SIZE 32 #define DEFAULT_DEPTH_BUFFER_SIZE 24 #define DEFAULT_STENCIL_BUFFER_SIZE 8 static double __timeTicksPerMillis; static double __timeStart; static double __timeAbsolute; static bool __vsync = WINDOW_VSYNC; static HINSTANCE __hinstance = 0; static HWND __hwnd = 0; static HDC __hdc = 0; static HGLRC __hrc = 0; static bool __mouseCaptured = false; static POINT __mouseCapturePoint = { 0, 0 }; static bool __multiSampling = false; static bool __cursorVisible = true; static unsigned int __gamepadsConnected = 0; #ifdef GP_USE_GAMEPAD static const unsigned int XINPUT_BUTTON_COUNT = 14; static const unsigned int XINPUT_JOYSTICK_COUNT = 2; static const unsigned int XINPUT_TRIGGER_COUNT = 2; static XINPUT_STATE __xInputState; static bool __connectedXInput[4]; static float normalizeXInputJoystickAxis(int axisValue, int deadZone) { int absAxisValue = abs(axisValue); if (absAxisValue < deadZone) { return 0.0f; } else { int value = axisValue; int maxVal; if (value < 0) { value = -1; maxVal = 32768; } else if (value > 0) { value = 1; maxVal = 32767; } else { return 0; } return value * (absAxisValue - deadZone) / (float)(maxVal - deadZone); } } #endif static gameplay::Keyboard::Key getKey(WPARAM win32KeyCode, bool shiftDown) { switch (win32KeyCode) { case VK_PAUSE: return gameplay::Keyboard::KEY_PAUSE; case VK_SCROLL: return gameplay::Keyboard::KEY_SCROLL_LOCK; case VK_PRINT: return gameplay::Keyboard::KEY_PRINT; case VK_ESCAPE: return gameplay::Keyboard::KEY_ESCAPE; case VK_BACK: case VK_F16: // generated by CTRL + BACKSPACE return gameplay::Keyboard::KEY_BACKSPACE; case VK_TAB: return shiftDown ? gameplay::Keyboard::KEY_BACK_TAB : gameplay::Keyboard::KEY_TAB; case VK_RETURN: return gameplay::Keyboard::KEY_RETURN; case VK_CAPITAL: return gameplay::Keyboard::KEY_CAPS_LOCK; case VK_SHIFT: return gameplay::Keyboard::KEY_SHIFT; case VK_CONTROL: return gameplay::Keyboard::KEY_CTRL; case VK_MENU: return gameplay::Keyboard::KEY_ALT; case VK_APPS: return gameplay::Keyboard::KEY_MENU; case VK_LSHIFT: return gameplay::Keyboard::KEY_SHIFT; case VK_RSHIFT: return gameplay::Keyboard::KEY_SHIFT; case VK_LCONTROL: return gameplay::Keyboard::KEY_CTRL; case VK_RCONTROL: return gameplay::Keyboard::KEY_CTRL; case VK_LMENU: return gameplay::Keyboard::KEY_ALT; case VK_RMENU: return gameplay::Keyboard::KEY_ALT; case VK_LWIN: case VK_RWIN: return gameplay::Keyboard::KEY_HYPER; case VK_BROWSER_SEARCH: return gameplay::Keyboard::KEY_SEARCH; case VK_INSERT: return gameplay::Keyboard::KEY_INSERT; case VK_HOME: return gameplay::Keyboard::KEY_HOME; case VK_PRIOR: return gameplay::Keyboard::KEY_PG_UP; case VK_DELETE: return gameplay::Keyboard::KEY_DELETE; case VK_END: return gameplay::Keyboard::KEY_END; case VK_NEXT: return gameplay::Keyboard::KEY_PG_DOWN; case VK_LEFT: return gameplay::Keyboard::KEY_LEFT_ARROW; case VK_RIGHT: return gameplay::Keyboard::KEY_RIGHT_ARROW; case VK_UP: return gameplay::Keyboard::KEY_UP_ARROW; case VK_DOWN: return gameplay::Keyboard::KEY_DOWN_ARROW; case VK_NUMLOCK: return gameplay::Keyboard::KEY_NUM_LOCK; case VK_ADD: return gameplay::Keyboard::KEY_KP_PLUS; case VK_SUBTRACT: return gameplay::Keyboard::KEY_KP_MINUS; case VK_MULTIPLY: return gameplay::Keyboard::KEY_KP_MULTIPLY; case VK_DIVIDE: return gameplay::Keyboard::KEY_KP_DIVIDE; case VK_NUMPAD7: return gameplay::Keyboard::KEY_KP_HOME; case VK_NUMPAD8: return gameplay::Keyboard::KEY_KP_UP; case VK_NUMPAD9: return gameplay::Keyboard::KEY_KP_PG_UP; case VK_NUMPAD4: return gameplay::Keyboard::KEY_KP_LEFT; case VK_NUMPAD5: return gameplay::Keyboard::KEY_KP_FIVE; case VK_NUMPAD6: return gameplay::Keyboard::KEY_KP_RIGHT; case VK_NUMPAD1: return gameplay::Keyboard::KEY_KP_END; case VK_NUMPAD2: return gameplay::Keyboard::KEY_KP_DOWN; case VK_NUMPAD3: return gameplay::Keyboard::KEY_KP_PG_DOWN; case VK_NUMPAD0: return gameplay::Keyboard::KEY_KP_INSERT; case VK_DECIMAL: return gameplay::Keyboard::KEY_KP_DELETE; case VK_F1: return gameplay::Keyboard::KEY_F1; case VK_F2: return gameplay::Keyboard::KEY_F2; case VK_F3: return gameplay::Keyboard::KEY_F3; case VK_F4: return gameplay::Keyboard::KEY_F4; case VK_F5: return gameplay::Keyboard::KEY_F5; case VK_F6: return gameplay::Keyboard::KEY_F6; case VK_F7: return gameplay::Keyboard::KEY_F7; case VK_F8: return gameplay::Keyboard::KEY_F8; case VK_F9: return gameplay::Keyboard::KEY_F9; case VK_F10: return gameplay::Keyboard::KEY_F10; case VK_F11: return gameplay::Keyboard::KEY_F11; case VK_F12: return gameplay::Keyboard::KEY_F12; case VK_SPACE: return gameplay::Keyboard::KEY_SPACE; case 0x30: return shiftDown ? gameplay::Keyboard::KEY_RIGHT_PARENTHESIS : gameplay::Keyboard::KEY_ZERO; case 0x31: return shiftDown ? gameplay::Keyboard::KEY_EXCLAM : gameplay::Keyboard::KEY_ONE; case 0x32: return shiftDown ? gameplay::Keyboard::KEY_AT : gameplay::Keyboard::KEY_TWO; case 0x33: return shiftDown ? gameplay::Keyboard::KEY_NUMBER : gameplay::Keyboard::KEY_THREE; case 0x34: return shiftDown ? gameplay::Keyboard::KEY_DOLLAR : gameplay::Keyboard::KEY_FOUR; case 0x35: return shiftDown ? gameplay::Keyboard::KEY_PERCENT : gameplay::Keyboard::KEY_FIVE; case 0x36: return shiftDown ? gameplay::Keyboard::KEY_CIRCUMFLEX : gameplay::Keyboard::KEY_SIX; case 0x37: return shiftDown ? gameplay::Keyboard::KEY_AMPERSAND : gameplay::Keyboard::KEY_SEVEN; case 0x38: return shiftDown ? gameplay::Keyboard::KEY_ASTERISK : gameplay::Keyboard::KEY_EIGHT; case 0x39: return shiftDown ? gameplay::Keyboard::KEY_LEFT_PARENTHESIS : gameplay::Keyboard::KEY_NINE; case VK_OEM_PLUS: return shiftDown ? gameplay::Keyboard::KEY_EQUAL : gameplay::Keyboard::KEY_PLUS; case VK_OEM_COMMA: return shiftDown ? gameplay::Keyboard::KEY_LESS_THAN : gameplay::Keyboard::KEY_COMMA; case VK_OEM_MINUS: return shiftDown ? gameplay::Keyboard::KEY_UNDERSCORE : gameplay::Keyboard::KEY_MINUS; case VK_OEM_PERIOD: return shiftDown ? gameplay::Keyboard::KEY_GREATER_THAN : gameplay::Keyboard::KEY_PERIOD; case VK_OEM_1: return shiftDown ? gameplay::Keyboard::KEY_COLON : gameplay::Keyboard::KEY_SEMICOLON; case VK_OEM_2: return shiftDown ? gameplay::Keyboard::KEY_QUESTION : gameplay::Keyboard::KEY_SLASH; case VK_OEM_3: return shiftDown ? gameplay::Keyboard::KEY_TILDE : gameplay::Keyboard::KEY_GRAVE; case VK_OEM_4: return shiftDown ? gameplay::Keyboard::KEY_LEFT_BRACE : gameplay::Keyboard::KEY_LEFT_BRACKET; case VK_OEM_5: return shiftDown ? gameplay::Keyboard::KEY_BAR : gameplay::Keyboard::KEY_BACK_SLASH; case VK_OEM_6: return shiftDown ? gameplay::Keyboard::KEY_RIGHT_BRACE : gameplay::Keyboard::KEY_RIGHT_BRACKET; case VK_OEM_7: return shiftDown ? gameplay::Keyboard::KEY_QUOTE : gameplay::Keyboard::KEY_APOSTROPHE; case 0x41: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_A : gameplay::Keyboard::KEY_A; case 0x42: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_B : gameplay::Keyboard::KEY_B; case 0x43: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_C : gameplay::Keyboard::KEY_C; case 0x44: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_D : gameplay::Keyboard::KEY_D; case 0x45: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_E : gameplay::Keyboard::KEY_E; case 0x46: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_F : gameplay::Keyboard::KEY_F; case 0x47: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_G : gameplay::Keyboard::KEY_G; case 0x48: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_H : gameplay::Keyboard::KEY_H; case 0x49: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_I : gameplay::Keyboard::KEY_I; case 0x4A: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_J : gameplay::Keyboard::KEY_J; case 0x4B: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_K : gameplay::Keyboard::KEY_K; case 0x4C: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_L : gameplay::Keyboard::KEY_L; case 0x4D: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_M : gameplay::Keyboard::KEY_M; case 0x4E: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_N : gameplay::Keyboard::KEY_N; case 0x4F: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_O : gameplay::Keyboard::KEY_O; case 0x50: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_P : gameplay::Keyboard::KEY_P; case 0x51: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_Q : gameplay::Keyboard::KEY_Q; case 0x52: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_R : gameplay::Keyboard::KEY_R; case 0x53: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_S : gameplay::Keyboard::KEY_S; case 0x54: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_T : gameplay::Keyboard::KEY_T; case 0x55: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_U : gameplay::Keyboard::KEY_U; case 0x56: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_V : gameplay::Keyboard::KEY_V; case 0x57: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_W : gameplay::Keyboard::KEY_W; case 0x58: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_X : gameplay::Keyboard::KEY_X; case 0x59: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_Y : gameplay::Keyboard::KEY_Y; case 0x5A: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_Z : gameplay::Keyboard::KEY_Z; default: return gameplay::Keyboard::KEY_NONE; } } static void UpdateCapture(LPARAM lParam) { if ((lParam & MK_LBUTTON) || (lParam & MK_MBUTTON) || (lParam & MK_RBUTTON)) SetCapture(__hwnd); else ReleaseCapture(); } static void WarpMouse(int clientX, int clientY) { POINT p = { clientX, clientY }; ClientToScreen(__hwnd, &p); SetCursorPos(p.x, p.y); } /** * Gets the width and height of the screen in pixels. */ static void getDesktopResolution(int& width, int& height) { RECT desktop; const HWND hDesktop = GetDesktopWindow(); // Get the size of screen to the variable desktop GetWindowRect(hDesktop, &desktop); width = desktop.right; height = desktop.bottom; } LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static gameplay::Game* game = gameplay::Game::getInstance(); if (!game->isInitialized() || hwnd != __hwnd) { // Ignore messages that are not for our game window. return DefWindowProc(hwnd, msg, wParam, lParam); } static bool shiftDown = false; static bool capsOn = false; switch (msg) { case WM_CLOSE: #ifdef GP_USE_MEM_LEAK_DETECTION DestroyWindow(__hwnd); #else exit(0); #endif return 0; case WM_DESTROY: gameplay::Platform::shutdownInternal(); PostQuitMessage(0); return 0; case WM_LBUTTONDOWN: { int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); UpdateCapture(wParam); if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_LEFT_BUTTON, x, y, 0)) { gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_PRESS, x, y, 0, true); } return 0; } case WM_LBUTTONUP: { int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_LEFT_BUTTON, x, y, 0)) { gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_RELEASE, x, y, 0, true); } UpdateCapture(wParam); return 0; } case WM_RBUTTONDOWN: UpdateCapture(wParam); gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_RIGHT_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); break; case WM_RBUTTONUP: gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_RIGHT_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); UpdateCapture(wParam); break; case WM_MBUTTONDOWN: UpdateCapture(wParam); gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_MIDDLE_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); break; case WM_MBUTTONUP: gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); UpdateCapture(wParam); break; case WM_MOUSEMOVE: { int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); if (__mouseCaptured) { // If the incoming position is the mouse capture point, ignore this event // since this is the event that warped the cursor back. if (x == __mouseCapturePoint.x && y == __mouseCapturePoint.y) break; // Convert to deltas x -= __mouseCapturePoint.x; y -= __mouseCapturePoint.y; // Warp mouse back to center of screen. WarpMouse(__mouseCapturePoint.x, __mouseCapturePoint.y); } // Allow Game::mouseEvent a chance to handle (and possibly consume) the event. if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_MOVE, x, y, 0)) { if ((wParam & MK_LBUTTON) == MK_LBUTTON) { // Mouse move events should be interpreted as touch move only if left mouse is held and the game did not consume the mouse event. gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_MOVE, x, y, 0, true); return 0; } } break; } case WM_MOUSEWHEEL: tagPOINT point; point.x = GET_X_LPARAM(lParam); point.y = GET_Y_LPARAM(lParam); ScreenToClient(__hwnd, &point); gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_WHEEL, point.x, point.y, GET_WHEEL_DELTA_WPARAM(wParam) / 120); break; case WM_KEYDOWN: if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT) shiftDown = true; if (wParam == VK_CAPITAL) capsOn = !capsOn; gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_PRESS, getKey(wParam, shiftDown ^ capsOn)); break; case WM_KEYUP: if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT) shiftDown = false; gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_RELEASE, getKey(wParam, shiftDown ^ capsOn)); break; case WM_CHAR: gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam); break; case WM_UNICHAR: gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam); break; case WM_SETFOCUS: break; case WM_KILLFOCUS: break; case WM_SIZE: // Window was resized. gameplay::Platform::resizeEventInternal((unsigned int)(short)LOWORD(lParam), (unsigned int)(short)HIWORD(lParam)); break; } return DefWindowProc(hwnd, msg, wParam, lParam); } namespace gameplay { struct WindowCreationParams { RECT rect; std::wstring windowName; bool fullscreen; bool resizable; int samples; }; extern void print(const char* format, ...) { va_list argptr; va_start(argptr, format); int sz = vfprintf(stderr, format, argptr); if (sz > 0) { char* buf = new char[sz + 1]; vsprintf(buf, format, argptr); buf[sz] = 0; OutputDebugStringA(buf); SAFE_DELETE_ARRAY(buf); } va_end(argptr); } extern int strcmpnocase(const char* s1, const char* s2) { return _strcmpi(s1, s2); } Platform::Platform(Game* game) : _game(game) { } Platform::~Platform() { if (__hwnd) { DestroyWindow(__hwnd); __hwnd = 0; } } bool createWindow(WindowCreationParams* params, HWND* hwnd, HDC* hdc) { bool fullscreen = false; bool resizable = false; RECT rect = { CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT }; std::wstring windowName; if (params) { windowName = params->windowName; memcpy(&rect, ¶ms->rect, sizeof(RECT)); fullscreen = params->fullscreen; resizable = params->resizable; } // Set the window style. DWORD style, styleEx; if (fullscreen) { style = WS_POPUP; styleEx = WS_EX_APPWINDOW; } else { if (resizable) style = WS_OVERLAPPEDWINDOW; else style = WS_POPUP | WS_BORDER | WS_CAPTION | WS_SYSMENU; styleEx = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; } style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; // Adjust the window rectangle so the client size is the requested size. AdjustWindowRectEx(&rect, style, FALSE, styleEx); // Create the native Windows window. *hwnd = CreateWindowEx(styleEx, L"gameplay", windowName.c_str(), style, 0, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, __hinstance, NULL); if (*hwnd == NULL) { GP_ERROR("Failed to create window."); return false; } // Get the drawing context. *hdc = GetDC(*hwnd); if (*hdc == NULL) { GP_ERROR("Failed to get device context."); return false; } // Center the window GetWindowRect(*hwnd, &rect); const int screenX = (GetSystemMetrics(SM_CXSCREEN) - rect.right) / 2; const int screenY = (GetSystemMetrics(SM_CYSCREEN) - rect.bottom) / 2; SetWindowPos(*hwnd, *hwnd, screenX, screenY, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); return true; } bool initializeGL(WindowCreationParams* params) { // Create a temporary window and context to we can initialize GLEW and get access // to additional OpenGL extension functions. This is a neccessary evil since the // function for querying GL extensions is a GL extension itself. HWND hwnd = NULL; HDC hdc = NULL; if (params) { if (!createWindow(params, &hwnd, &hdc)) return false; } else { hwnd = __hwnd; hdc = __hdc; } PIXELFORMATDESCRIPTOR pfd; memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); pfd.nVersion = 1; pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = DEFAULT_COLOR_BUFFER_SIZE; pfd.cDepthBits = DEFAULT_DEPTH_BUFFER_SIZE; pfd.cStencilBits = DEFAULT_STENCIL_BUFFER_SIZE; pfd.iLayerType = PFD_MAIN_PLANE; int pixelFormat = ChoosePixelFormat(hdc, &pfd); if (pixelFormat == 0) { DestroyWindow(hwnd); GP_ERROR("Failed to choose a pixel format."); return false; } if (!SetPixelFormat(hdc, pixelFormat, &pfd)) { DestroyWindow(hwnd); GP_ERROR("Failed to set the pixel format."); return false; } HGLRC tempContext = wglCreateContext(hdc); if (!tempContext) { DestroyWindow(hwnd); GP_ERROR("Failed to create temporary context for initialization."); return false; } wglMakeCurrent(hdc, tempContext); // Initialize GLEW if (GLEW_OK != glewInit()) { wglDeleteContext(tempContext); DestroyWindow(hwnd); GP_ERROR("Failed to initialize GLEW."); return false; } if( wglChoosePixelFormatARB && wglCreateContextAttribsARB ) { // Choose pixel format using wglChoosePixelFormatARB, which allows us to specify // additional attributes such as multisampling. // // Note: Keep multisampling attributes at the start of the attribute lists since code below // assumes they are array elements 0 through 3. int attribList[] = { WGL_SAMPLES_ARB, params ? params->samples : 0, WGL_SAMPLE_BUFFERS_ARB, params ? (params->samples > 0 ? 1 : 0) : 0, WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, WGL_SUPPORT_OPENGL_ARB, GL_TRUE, WGL_DOUBLE_BUFFER_ARB, GL_TRUE, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_COLOR_BITS_ARB, DEFAULT_COLOR_BUFFER_SIZE, WGL_DEPTH_BITS_ARB, DEFAULT_DEPTH_BUFFER_SIZE, WGL_STENCIL_BITS_ARB, DEFAULT_STENCIL_BUFFER_SIZE, 0 }; __multiSampling = params && params->samples > 0; UINT numFormats; if ( !wglChoosePixelFormatARB(hdc, attribList, NULL, 1, &pixelFormat, &numFormats) || numFormats == 0) { bool valid = false; if (params && params->samples > 0) { GP_WARN("Failed to choose pixel format with WGL_SAMPLES_ARB == %d. Attempting to fallback to lower samples setting.", params->samples); while (params->samples > 0) { params->samples /= 2; attribList[1] = params->samples; attribList[3] = params->samples > 0 ? 1 : 0; if (wglChoosePixelFormatARB(hdc, attribList, NULL, 1, &pixelFormat, &numFormats) && numFormats > 0) { valid = true; GP_WARN("Found pixel format with WGL_SAMPLES_ARB == %d.", params->samples); break; } } __multiSampling = params->samples > 0; } if (!valid) { wglDeleteContext(tempContext); DestroyWindow(hwnd); GP_ERROR("Failed to choose a pixel format."); return false; } } // Create new/final window if needed if (params) { DestroyWindow(hwnd); hwnd = NULL; hdc = NULL; if (!createWindow(params, &__hwnd, &__hdc)) { wglDeleteContext(tempContext); return false; } } // Set final pixel format for window if (!SetPixelFormat(__hdc, pixelFormat, &pfd)) { GP_ERROR("Failed to set the pixel format: %d.", (int)GetLastError()); return false; } // Create our new GL context int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 1, 0 }; if (!(__hrc = wglCreateContextAttribsARB(__hdc, 0, attribs) ) ) { wglDeleteContext(tempContext); GP_ERROR("Failed to create OpenGL context."); return false; } // Delete the old/temporary context and window wglDeleteContext(tempContext); // Make the new context current if (!wglMakeCurrent(__hdc, __hrc) || !__hrc) { GP_ERROR("Failed to make the window current."); return false; } } else // fallback to OpenGL 2.0 if wglChoosePixelFormatARB or wglCreateContextAttribsARB is NULL. { // Context is already here, just use it. __hrc = tempContext; __hwnd = hwnd; __hdc = hdc; } // Vertical sync. if (wglSwapIntervalEXT) wglSwapIntervalEXT(__vsync ? 1 : 0); else __vsync = false; // Some old graphics cards support EXT_framebuffer_object instead of ARB_framebuffer_object. // Patch ARB_framebuffer_object functions to EXT_framebuffer_object ones since semantic is same. if( !GLEW_ARB_framebuffer_object && GLEW_EXT_framebuffer_object ) { glBindFramebuffer = glBindFramebufferEXT; glBindRenderbuffer = glBindRenderbufferEXT; glBlitFramebuffer = glBlitFramebufferEXT; glCheckFramebufferStatus = glCheckFramebufferStatusEXT; glDeleteFramebuffers = glDeleteFramebuffersEXT; glDeleteRenderbuffers = glDeleteRenderbuffersEXT; glFramebufferRenderbuffer = glFramebufferRenderbufferEXT; glFramebufferTexture1D = glFramebufferTexture1DEXT; glFramebufferTexture2D = glFramebufferTexture2DEXT; glFramebufferTexture3D = glFramebufferTexture3DEXT; glFramebufferTextureLayer = glFramebufferTextureLayerEXT; glGenFramebuffers = glGenFramebuffersEXT; glGenRenderbuffers = glGenRenderbuffersEXT; glGenerateMipmap = glGenerateMipmapEXT; glGetFramebufferAttachmentParameteriv = glGetFramebufferAttachmentParameterivEXT; glGetRenderbufferParameteriv = glGetRenderbufferParameterivEXT; glIsFramebuffer = glIsFramebufferEXT; glIsRenderbuffer = glIsRenderbufferEXT; glRenderbufferStorage = glRenderbufferStorageEXT; glRenderbufferStorageMultisample = glRenderbufferStorageMultisampleEXT; } return true; } Platform* Platform::create(Game* game) { GP_ASSERT(game); FileSystem::setResourcePath("./"); Platform* platform = new Platform(game); // Get the application module handle. __hinstance = ::GetModuleHandle(NULL); // Read window settings from config. WindowCreationParams params; params.fullscreen = false; params.resizable = false; params.rect.left = 0; params.rect.top = 0; params.rect.right = 0; params.rect.bottom = 0; params.samples = 0; if (game->getConfig()) { Properties* config = game->getConfig()->getNamespace("window", true); if (config) { // Read window title. const char* title = config->getString("title"); if (title) { int len = MultiByteToWideChar(CP_ACP, 0, title, -1, NULL, 0); wchar_t* wtitle = new wchar_t[len]; MultiByteToWideChar(CP_ACP, 0, title, -1, wtitle, len); params.windowName = wtitle; SAFE_DELETE_ARRAY(wtitle); } // Read fullscreen state. params.fullscreen = config->getBool("fullscreen"); // Read resizable state. params.resizable = config->getBool("resizable"); // Read multisampling state. params.samples = config->getInt("samples"); // Read window rect. int x = config->getInt("x"); if (x != 0) params.rect.left = x; int y = config->getInt("y"); if (y != 0) params.rect.top = y; int width = config->getInt("width"); int height = config->getInt("height"); if (width == 0 && height == 0 && params.fullscreen) getDesktopResolution(width, height); if (width != 0) params.rect.right = params.rect.left + width; if (height != 0) params.rect.bottom = params.rect.top + height; } } // If window size was not specified, set it to a default value if (params.rect.right == 0) params.rect.right = params.rect.left + DEFAULT_RESOLUTION_X; if (params.rect.bottom == 0) params.rect.bottom = params.rect.top + DEFAULT_RESOLUTION_Y; int width = params.rect.right - params.rect.left; int height = params.rect.bottom - params.rect.top; if (params.fullscreen) { // Enumerate all supposed display settings bool modeSupported = false; DWORD modeNum = 0; DEVMODE devMode; memset(&devMode, 0, sizeof(DEVMODE)); devMode.dmSize = sizeof(DEVMODE); devMode.dmDriverExtra = 0; while (EnumDisplaySettings(NULL, modeNum++, &devMode) != 0) { // Is mode supported? if (devMode.dmPelsWidth == width && devMode.dmPelsHeight == height && devMode.dmBitsPerPel == DEFAULT_COLOR_BUFFER_SIZE) { modeSupported = true; break; } } // If the requested mode is not supported, fall back to a safe default if (!modeSupported) { width = DEFAULT_RESOLUTION_X; height = DEFAULT_RESOLUTION_Y; params.rect.right = params.rect.left + width; params.rect.bottom = params.rect.top + height; } } // Register our window class. WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = (WNDPROC)__WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = __hinstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hIconSm = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; // No brush - we are going to paint our own background wc.lpszMenuName = NULL; // No default menu wc.lpszClassName = L"gameplay"; if (!::RegisterClassEx(&wc)) { GP_ERROR("Failed to register window class."); goto error; } if (params.fullscreen) { DEVMODE dm; memset(&dm, 0, sizeof(dm)); dm.dmSize= sizeof(dm); dm.dmPelsWidth = width; dm.dmPelsHeight = height; dm.dmBitsPerPel = DEFAULT_COLOR_BUFFER_SIZE; dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; // Try to set selected mode and get results. NOTE: CDS_FULLSCREEN gets rid of start bar. if (ChangeDisplaySettings(&dm, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { params.fullscreen = false; GP_ERROR("Failed to start game in full-screen mode with resolution %dx%d.", width, height); goto error; } } if (!initializeGL(¶ms)) goto error; // Show the window. ShowWindow(__hwnd, SW_SHOW); #ifdef GP_USE_GAMEPAD // Initialize XInputGamepads. for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { if (XInputGetState(i, &__xInputState) == NO_ERROR) { if (!__connectedXInput[i]) { // Gamepad is connected. Platform::gamepadEventConnectedInternal(i, XINPUT_BUTTON_COUNT, XINPUT_JOYSTICK_COUNT, XINPUT_TRIGGER_COUNT, "Microsoft XBox360 Controller"); __connectedXInput[i] = true; } } } #endif return platform; error: exit(0); return NULL; } int Platform::enterMessagePump() { GP_ASSERT(_game); // Get the initial time. LARGE_INTEGER tps; QueryPerformanceFrequency(&tps); __timeTicksPerMillis = (double)(tps.QuadPart / 1000L); LARGE_INTEGER queryTime; QueryPerformanceCounter(&queryTime); GP_ASSERT(__timeTicksPerMillis); __timeStart = queryTime.QuadPart / __timeTicksPerMillis; SwapBuffers(__hdc); if (_game->getState() != Game::RUNNING) _game->run(); // Enter event dispatch loop. MSG msg; while (true) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if (msg.message == WM_QUIT) { gameplay::Platform::shutdownInternal(); return msg.wParam; } } else { #ifdef GP_USE_GAMEPAD // Check for connected XInput gamepads. for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { if (XInputGetState(i, &__xInputState) == NO_ERROR && !__connectedXInput[i]) { // Gamepad was just connected. Platform::gamepadEventConnectedInternal(i, XINPUT_BUTTON_COUNT, XINPUT_JOYSTICK_COUNT, XINPUT_TRIGGER_COUNT, "Microsoft XBox360 Controller"); __connectedXInput[i] = true; } else if (XInputGetState(i, &__xInputState) != NO_ERROR && __connectedXInput[i]) { // Gamepad was just disconnected. __connectedXInput[i] = false; Platform::gamepadEventDisconnectedInternal(i); } } #endif _game->frame(); SwapBuffers(__hdc); } // If we are done, then exit. if (_game->getState() == Game::UNINITIALIZED) break; } return 0; } void Platform::signalShutdown() { // nothing to do } bool Platform::canExit() { return true; } unsigned int Platform::getDisplayWidth() { static RECT rect; GetClientRect(__hwnd, &rect); return rect.right; } unsigned int Platform::getDisplayHeight() { static RECT rect; GetClientRect(__hwnd, &rect); return rect.bottom; } double Platform::getAbsoluteTime() { LARGE_INTEGER queryTime; QueryPerformanceCounter(&queryTime); GP_ASSERT(__timeTicksPerMillis); __timeAbsolute = queryTime.QuadPart / __timeTicksPerMillis; return __timeAbsolute - __timeStart; } void Platform::setAbsoluteTime(double time) { __timeAbsolute = time; } bool Platform::isVsync() { return __vsync; } void Platform::setVsync(bool enable) { __vsync = enable; if (wglSwapIntervalEXT) wglSwapIntervalEXT(__vsync ? 1 : 0); else __vsync = false; } void Platform::swapBuffers() { if (__hdc) SwapBuffers(__hdc); } void Platform::sleep(long ms) { Sleep(ms); } void Platform::setMultiSampling(bool enabled) { if (enabled == __multiSampling) { return; } if (enabled) { glEnable(GL_MULTISAMPLE); } else { glDisable(GL_MULTISAMPLE); } __multiSampling = enabled; } bool Platform::isMultiSampling() { return __multiSampling; } void Platform::setMultiTouch(bool enabled) { // not supported } bool Platform::isMultiTouch() { return false; } bool Platform::hasAccelerometer() { return false; } void Platform::getAccelerometerValues(float* pitch, float* roll) { GP_ASSERT(pitch); GP_ASSERT(roll); *pitch = 0; *roll = 0; } void Platform::getSensorValues(float* accelX, float* accelY, float* accelZ, float* gyroX, float* gyroY, float* gyroZ) { if (accelX) { *accelX = 0; } if (accelY) { *accelY = 0; } if (accelZ) { *accelZ = 0; } if (gyroX) { *gyroX = 0; } if (gyroY) { *gyroY = 0; } if (gyroZ) { *gyroZ = 0; } } void Platform::getArguments(int* argc, char*** argv) { if (argc) *argc = __argc; if (argv) *argv = __argv; } bool Platform::hasMouse() { return true; } void Platform::setMouseCaptured(bool captured) { if (captured != __mouseCaptured) { if (captured) { // Hide the cursor and warp it to the center of the screen __mouseCapturePoint.x = getDisplayWidth() / 2; __mouseCapturePoint.y = getDisplayHeight() / 2; ShowCursor(FALSE); WarpMouse(__mouseCapturePoint.x, __mouseCapturePoint.y); } else { // Restore cursor WarpMouse(__mouseCapturePoint.x, __mouseCapturePoint.y); ShowCursor(TRUE); } __mouseCaptured = captured; } } bool Platform::isMouseCaptured() { return __mouseCaptured; } void Platform::setCursorVisible(bool visible) { if (visible != __cursorVisible) { ShowCursor(visible ? TRUE : FALSE); __cursorVisible = visible; } } bool Platform::isCursorVisible() { return __cursorVisible; } void Platform::displayKeyboard(bool display) { // Do nothing. } bool Platform::isGestureSupported(Gesture::GestureEvent evt) { return false; } void Platform::registerGesture(Gesture::GestureEvent evt) { } void Platform::unregisterGesture(Gesture::GestureEvent evt) { } bool Platform::isGestureRegistered(Gesture::GestureEvent evt) { return false; } #ifdef GP_USE_GAMEPAD void Platform::pollGamepadState(Gamepad* gamepad) { GP_ASSERT(gamepad->_handle < XUSER_MAX_COUNT); if (XInputGetState(gamepad->_handle, &__xInputState) == NO_ERROR) { WORD buttons = __xInputState.Gamepad.wButtons; // Map XInput buttons to Gamepad::ButtonMappings enum. static const unsigned int xInputMapping[16] = { Gamepad::BUTTON_UP, // 0x0001 Gamepad::BUTTON_DOWN, // 0x0002 Gamepad::BUTTON_LEFT, // 0x0004 Gamepad::BUTTON_RIGHT, // 0x0008 Gamepad::BUTTON_MENU2, // 0x0010 Gamepad::BUTTON_MENU1, // 0x0020 Gamepad::BUTTON_L3, // 0x0040 Gamepad::BUTTON_R3, // 0x0080 Gamepad::BUTTON_L1, // 0x0100 Gamepad::BUTTON_R1, // 0x0200 0, 0, Gamepad::BUTTON_A, // 0x1000 Gamepad::BUTTON_B, // 0x2000 Gamepad::BUTTON_X, // 0x4000 Gamepad::BUTTON_Y // 0x8000 }; const unsigned int *mapping = xInputMapping; unsigned int mappedButtons; for (mappedButtons = 0; buttons; buttons >>= 1, mapping++) { if (buttons & 1) { mappedButtons |= (1 << *mapping); } } gamepad->setButtons(mappedButtons); unsigned int i; for (i = 0; i < gamepad->_joystickCount; ++i) { GP_ASSERT(i < 2); float x; float y; switch (i) { case 0: x = normalizeXInputJoystickAxis(__xInputState.Gamepad.sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); y = normalizeXInputJoystickAxis(__xInputState.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); break; case 1: x = normalizeXInputJoystickAxis(__xInputState.Gamepad.sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); y = normalizeXInputJoystickAxis(__xInputState.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); break; } gamepad->setJoystickValue(i, x, y); } for (i = 0; i < gamepad->_triggerCount; ++i) { GP_ASSERT(i < 2); BYTE trigger; switch (i) { case 0: trigger = __xInputState.Gamepad.bLeftTrigger; break; case 1: trigger = __xInputState.Gamepad.bRightTrigger; break; } if (trigger < XINPUT_GAMEPAD_TRIGGER_THRESHOLD) { gamepad->setTriggerValue(i, 0.0f); } else { gamepad->setTriggerValue(i, (float)trigger / 255.0f); } } } } #else void Platform::pollGamepadState(Gamepad* gamepad) { } #endif void Platform::shutdownInternal() { Game::getInstance()->shutdown(); } bool Platform::launchURL(const char* url) { if (url == NULL || *url == '\0') return false; // Success when result code > 32 int len = MultiByteToWideChar(CP_ACP, 0, url, -1, NULL, 0); wchar_t* wurl = new wchar_t[len]; MultiByteToWideChar(CP_ACP, 0, url, -1, wurl, len); int r = (int)ShellExecute(NULL, NULL, wurl, NULL, NULL, SW_SHOWNORMAL); SAFE_DELETE_ARRAY(wurl); return (r > 32); } std::string Platform::displayFileDialog(size_t mode, const char* title, const char* filterDescription, const char* filterExtensions, const char* initialDirectory) { std::string filename; OPENFILENAMEA ofn; memset(&ofn, 0, sizeof(ofn)); char currentDir[1024]; char absPath[1024]; std::string initialDirectoryStr; if (initialDirectory == NULL) { GetCurrentDirectoryA(1024, currentDir); initialDirectoryStr = currentDir; } else { GetFullPathNameA(initialDirectory, 1024, absPath, 0); initialDirectoryStr = absPath; } // Filter on extensions std::istringstream f(filterExtensions); std::string s; unsigned int count = 0; std::string descStr = filterDescription; descStr += " ("; std::string extStr = ""; while (std::getline(f, s, ';')) { if (count > 0) extStr += ";"; extStr += "*."; extStr += s; count++; } descStr += extStr; descStr += ")"; char filter[1024]; memset(filter, 0, 1024); strcpy(filter, descStr.c_str()); strcpy(filter + descStr.length() + 1, extStr.c_str()); char szFileName[1024] = ""; ofn.lpstrFile = szFileName; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = GetForegroundWindow(); ofn.lpstrTitle = title; ofn.lpstrFilter = filter; ofn.lpstrInitialDir = initialDirectoryStr.c_str(); ofn.nMaxFile = 1024; ofn.lpstrDefExt = filter; if (mode == FileSystem::OPEN) { ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST; GetOpenFileNameA(&ofn); } else { ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; GetSaveFileNameA(&ofn); } filename = szFileName; return filename; } } #endif #endif