官术网_书友最值得收藏!

Opening a window

Next, you need to implement the window entry function, WinMain. This function will be responsible for creating a window class, registering the window class, and opening a new window:

  1. Start the definition of WinMain by creating a new instance of the Application class and storing it in the global pointer:

    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE

                       hPrevInstance, PSTR szCmdLine,

                       int iCmdShow) {

    gApplication = new Application();

  2. Next, an instance of WNDCLASSEX needs to be filled out. There isn't anything special that goes into this; it's just a standard window definition. The only thing to look out for is whether the WndProc function is set correctly:

        WNDCLASSEX wndclass;

        wndclass.cbSize = sizeof(WNDCLASSEX);

        wndclass.style = CS_HREDRAW | CS_VREDRAW;

        wndclass.lpfnWndProc = WndProc;

        wndclass.cbClsExtra = 0;

        wndclass.cbWndExtra = 0;

        wndclass.hInstance = hInstance;

        wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

        wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

        wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);

        wndclass.lpszMenuName = 0;

        wndclass.lpszClassName = "Win32 Game Window";

        RegisterClassEx(&wndclass);

  3. A new application window should launch in the center of the monitor. To do this, find the width and height of the screen using GetSystemMetrics. Then, adjust windowRect to the desired size around the center of the screen:

        int screenWidth = GetSystemMetrics(SM_CXSCREEN);

        int screenHeight = GetSystemMetrics(SM_CYSCREEN);

        int clientWidth = 800;

        int clientHeight = 600;

        RECT windowRect;

        SetRect(&windowRect,

                (screenWidth / 2) - (clientWidth / 2),

                (screenHeight / 2) - (clientHeight / 2),

                (screenWidth / 2) + (clientWidth / 2),

                (screenHeight / 2) + (clientHeight / 2));

  4. To figure out the size of the window, not just the client area, the style of the window needs to be known. The following code sample creates a window that can be minimized or maximized but not resized. To resize the window, use a bitwise OR (|) operator with the WS_THICKFRAME defined:

        DWORD style = (WS_OVERLAPPED | WS_CAPTION |

            WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);

        // | WS_THICKFRAME to resize

  5. Once the desired window style is defined, call the AdjustWindowRectEx function to adjust the size of the client rectangle to include all the window dressing in its size as well. When the final size is known, CreateWindowEx can be used to create the actual window. Once the window is created, store a reference to its device context:

        AdjustWindowRectEx(&windowRect, style, FALSE, 0);

        HWND hwnd = CreateWindowEx(0, wndclass.lpszClassName,

                    "Game Window", style, windowRect.left,

                    windowRect.top, windowRect.right -

                    windowRect.left, windowRect.bottom -

                    windowRect.top, NULL, NULL,

                    hInstance, szCmdLine);

        HDC hdc = GetDC(hwnd);

  6. Now that the window is created, you will next create an OpenGL context. To do this, you first need to find the correct pixel format, and then apply it to the device context of the window. The following code shows you how to do this:

        PIXELFORMATDESCRIPTOR pfd;

        memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));

        pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);

        pfd.nVersion = 1;

        pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW

                      | PFD_DOUBLEBUFFER;

        pfd.iPixelType = PFD_TYPE_RGBA;

        pfd.cColorBits = 24;

        pfd.cDepthBits = 32;

        pfd.cStencilBits = 8;

        pfd.iLayerType = PFD_MAIN_PLANE;

        int pixelFormat = ChoosePixelFormat(hdc, &pfd);

        SetPixelFormat(hdc, pixelFormat, &pfd);

  7. With the pixel format set, create a temporary OpenGL context using wglCreateContext. This temporary context is only needed to get a pointer to wglCreateContextAttribsARB, which will be used to create a modern context:

        HGLRC tempRC = wglCreateContext(hdc);

        wglMakeCurrent(hdc, tempRC);

        PFNWGLCREATECONTEXTATTRIBSARBPROC

           wglCreateContextAttribsARB = NULL;

        wglCreateContextAttribsARB =

           (PFNWGLCREATECONTEXTATTRIBSARBPROC)

           wglGetProcAddress("wglCreateContextAttribsARB");

  8. A temporary OpenGL context exists and is bound, so call the wglCreateContextAttribsARB function next. This function will return an OpenGL 3.3 Core context profile, bind it, and delete the legacy context:

        const int attribList[] = {

            WGL_CONTEXT_MAJOR_VERSION_ARB, 3,

            WGL_CONTEXT_MINOR_VERSION_ARB, 3,

            WGL_CONTEXT_FLAGS_ARB, 0,

            WGL_CONTEXT_PROFILE_MASK_ARB,

            WGL_CONTEXT_CORE_PROFILE_BIT_ARB,

            0, };

        HGLRC hglrc = wglCreateContextAttribsARB(

                           hdc, 0, attribList);

        wglMakeCurrent(NULL, NULL);

        wglDeleteContext(tempRC);

        wglMakeCurrent(hdc, hglrc);

  9. With an OpenGL 3.3 Core context active, glad can be used to load all the OpenGL 3.3 Core functions. Call gladLoadGL to do this:

        if (!gladLoadGL()) {

            std::cout << "Could not initialize GLAD\n";

        }

        else {

            std::cout << "OpenGL Version " <<

            GLVersion.major << "." << GLVersion.minor <<

              "\n";

        }

  10. An OpenGL 3.3 Core context should now be initialized, with all of the core OpenGL functions loaded. Next, you will enable vsynch on the window. vsynch is not a built-in function; it's an extension and, as such, support for it needs to be queried with wglGetExtensionStringEXT. The extension string for vsynch is WGL_EXT_swap_control. Check whether this is in the list of extension strings:

        PFNWGLGETEXTENSIONSSTRINGEXTPROC

           _wglGetExtensionsStringEXT =

           (PFNWGLGETEXTENSIONSSTRINGEXTPROC)

           wglGetProcAddress("wglGetExtensionsStringEXT");

        bool swapControlSupported = strstr(

             _wglGetExtensionsStringEXT(),

             "WGL_EXT_swap_control") != 0;

  11. If the WGL_EXT_swap_control extension is available, it needs to be loaded. The actual function is wglSwapIntervalEXT, which can be found in wgl.h. Passing an argument to wglSwapIntervalEXT turns on vsynch:

        int vsynch = 0;

        if (swapControlSupported) {

            PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT =

                (PFNWGLSWAPINTERVALEXTPROC)

                wglGetProcAddress("wglSwapIntervalEXT");

            PFNWGLGETSWAPINTERVALEXTPROC

                wglGetSwapIntervalEXT =

                (PFNWGLGETSWAPINTERVALEXTPROC)

                wglGetProcAddress("wglGetSwapIntervalEXT");

            if (wglSwapIntervalEXT(1)) {

                std::cout << "Enabled vsynch\n";

                vsynch = wglGetSwapIntervalEXT();

            }

            else {

                std::cout << "Could not enable vsynch\n";

            }

        }

        else { // !swapControlSupported

            cout << "WGL_EXT_swap_control not supported\n";

        }

  12. There is just a little bit more housekeeping to do to finish setting up an OpenGL-enabled window. OpenGL 3.3 Core requires a VAO to be bound for all draw calls. Instead of creating a VAO for each draw call, you will create one global VAO that is bound in WinMain and never unbound until the window is destroyed. The following code creates this VAO and binds it:

        glGenVertexArrays(1, &gVertexArrayObject);

        glBindVertexArray(gVertexArrayObject);

  13. Call the ShowWindow and UpdateWindow functions to display the current window; this is also a good place to initialize the global application. Depending on the amount of work that the application's Initialize function ends up doing, the window may appear frozen for a little bit:

        ShowWindow(hwnd, SW_SHOW);

        UpdateWindow(hwnd);

        gApplication->Initialize();

  14. You're now ready to implement the actual game loop. You will need to keep track of the last frame time to calculate the delta time between frames. In addition to game logic, the loop needs to handle window events by peeking at the current message stack and dispatching messages accordingly:

        DWORD lastTick = GetTickCount();

        MSG msg;

        while (true) {

            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

                if (msg.message == WM_QUIT) {

                    break;

                }

                TranslateMessage(&msg);

                DispatchMessage(&msg);

            }

  15. After the window events are processed, the Application instance needs to update and render. First, find the delta time between the last frame and this one, converting it into seconds. For example, a game that's running at 60 FPS should have a delta time of 16.6 milliseconds, or 0.0166 seconds:

            DWORD thisTick = GetTickCount();

            float dt = float(thisTick - lastTick) * 0.001f;

            lastTick = thisTick;

            if (gApplication != 0) {

                gApplication->Update(dt);

            }

  16. Rendering the currently running application needs just a little bit more housekeeping. Set the OpenGL viewport with glViewport every frame and clear the color, depth, and stencil buffer. In addition to this, make sure all OpenGL states are correct before rendering. This means that the correct VAO is bound, depth test and face culling are enabled, and the appropriate point size is set:

            if (gApplication != 0) {

                RECT clientRect;

                GetClientRect(hwnd, &clientRect);

                clientWidth = clientRect.right -

                              clientRect.left;

                clientHeight = clientRect.bottom -

                               clientRect.top;

                glViewport(0, 0, clientWidth, clientHeight);

                glEnable(GL_DEPTH_TEST);

                glEnable(GL_CULL_FACE);

                glPointSize(5.0f);

                glBindVertexArray(gVertexArrayObject);

                glClearColor(0.5f, 0.6f, 0.7f, 1.0f);

                glClear(GL_COLOR_BUFFER_BIT |

                GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

                float aspect = (float)clientWidth /

                               (float)clientHeight;

                gApplication->Render(aspect);

            }

  17. After the current Application instance has updated and rendered, the back buffer needs to be presented. This is done by calling SwapBuffers. If vsynch is enabled, glFinish needs to be called right after SwapBuffers:

            if (gApplication != 0) {

                SwapBuffers(hdc);

                if (vsynch != 0) {

                    glFinish();

                }

            }

  18. That's it for the window loop. After the window loop exits, it's safe to return from the WinMain function:

        } // End of game loop

        if (gApplication != 0) {

            std::cout << "Expected application to

                          be null on exit\n";

            delete gApplication;

        }

        return (int)msg.wParam;

    }

If you want to use a version of OpenGL other than 3.3, adjust the major and minor values in the attribList variable presented in Step 8. Even though the WinMain function is written, you still can't compile this file; it would fail because WndProc was never defined. The WndProc function handles events such as mouse motion or resizing for a window. In the next section, you will implement the WndProc function.

主站蜘蛛池模板: 甘肃省| 谢通门县| 鄂托克前旗| 高阳县| 当涂县| 乐业县| 韶山市| 高陵县| 邵阳县| 桑植县| 盐津县| 芮城县| 泽州县| 永平县| 佛山市| 西华县| 新丰县| 瑞昌市| 嘉荫县| 句容市| 正蓝旗| 石楼县| 阳西县| 石台县| 维西| 临朐县| 富川| 安阳县| 蛟河市| 三台县| 洪洞县| 筠连县| 报价| 胶南市| 喀喇| 金湖县| 左权县| 蒲江县| 香港 | 汉沽区| 韶关市|