1

I have read a lot of Stack Overflow over the years when struggling with making sense of Microsoft Windows' strange world of CreateWindowEx() .. etc. This question, when originally asked was "What is the best way to create a fluidly resizable OpenGL window in WinAPI?"

I've been struggling with getting WinAPI to make a window that:

  • Has an OpenGL context
  • Is properly centered on the main monitor (or any monitor determined by command line signal) in both multi-monitor and single-monitor displays when in "Windowed" mode or in "Fullscreen" mode
  • Has a fixed internal client screen size (viewport 2d)
  • Doesn't allow you to click outside causing it to lose focus at the wrong times or in special cases for multi-monitor
  • Can be resized fluidly, but doesn't change internal "client size" (meaning that it stretches the OpenGL content which is a fixed size to the new screen size) ... the idea here is to add a layer of virtualization, so that all pixels are expressed in the same 1920x1080 (1080p) coordinate system. This part is no problem for me.
  • Correctly handles mouse event translation from screen_size -> client_size equivalent via the screen->client ratio

In my homegrown App framework, I have to set the display size, and even then, Windows doesn't give me the right sized window. (Sometimes the title bar is subtracted, sometimes the scrollbars are subtracted, but the context draws under the title bar, for example.)

Also, recently when moving from 2010 EE (Win32 / Windows 7) to 2015 (win32 / Windows 10), I had to change the parameters to recenter the view because it was off-centered on the main display. Now, only sometimes are these values correct or incorrect. If I go "fullscreen" for example, the same values will draw above the top of the screen such that there is an area at the bottom of the screen that shows the "gl clear color" (in my case, orange)

I can play with these things by providing the following command line parameters:

  • -bordered (default, and has no effect really, is the default windowed mode with the title bar and such)
  • -borderless (seems to go into fullscreen mode, with the app off-center where win 0,0 is actually in screen center)
  • -windowed (or -window)

If I don't provide -window, it defaults to "full screen" resolution-adjusted (but only if supported I assume, otherwise it might throw an error).

Anyway, all of this is very bad because a) I have to write a bajillion cases for each resolution I'm working in, rather than write everything for 1080p and have it adjust to display size, which is what i want because it handles most new displays on laptops and desktops (this is Windows remember) (and only slightly squishes things in those corner cases) b) I cannot resize the window fluidly, also i have to trap the mouse at center and recalculate the mouse position, so I record only the deltas -- this is to avoid the mouse leaving the window and clicking the desktop, or floating off the monitor to some other monitor, even when it is hidden. I also have to make the mouse cursor invisible so the user doesn't see this, then show a simulated mouse cursor. c) Users who don't support specifically 1920x1080 won't be able to use full screen mode

Someone pointed this article out in another question (window border width and height in Win32 - how do I get it?): https://web.archive.org/web/20120716062211/http://suite101.com/article/client-area-size-with-movewindow-a17846

And I've read through this, learning that AdjustWindowRectEx() has some issues: AdjustWindowRectEx() and GetWindowRect() give wrong size with WS_OVERLAPPED

I don't use WS_OVERLAPPED, so this was only moderately helpful: AdjustWindowRectEx() and GetWindowRect() give wrong size with WS_OVERLAPPED

Here's how I do it now:

    display.Resized(display.w,display.h);

    // Fill in the window class structure for testing display type.

    winclass.cbSize = sizeof(WNDCLASSEX);
    winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    winclass.lpfnWndProc = WinProc;
    winclass.cbClsExtra = 0;
    winclass.cbWndExtra = 0;
    winclass.hInstance = hinstance;
    winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    winclass.lpszMenuName = NULL;
    winclass.lpszClassName = WINDOW_CLASS_NAME;

    // Save the game instance handle
    display.hinstance = game_instance = hinstance;

    // Register the window class
    if (!RegisterClassEx(&winclass)) return(0);

    if (!gl.Init(hinstance, display.bits)) {
     return(0);
    }

    // Detect the display size and create the final display profile 

    DWORD winStyle=
     WS_EX_APPWINDOW |
     WS_EX_TOPMOST /*|
     WS_EX_ACCEPTFILES*/ ;

    // Adjust Window, Account For Window Borders
    int xPos = GetSystemMetrics(SM_CXSCREEN) - display.w;
    int yPos = GetSystemMetrics(SM_CYSCREEN) - display.h;
    RECT windowRect = {0, 0, display.w, display.h}; // Define Our Window Coordinates
    AdjustWindowRectEx (&windowRect, WS_POPUP, 0, winStyle );
    // Create the window
    if (!(hwnd = CreateWindowEx(
     winStyle,                    // extended style
     WINDOW_CLASS_NAME, // class
     gl.winTitle.c_str(),          // title
     ( gl.borderless || CmdLine.Option("-borderless") ) ? (WS_POPUPWINDOW | WS_VISIBLE)
     : (gl.noFullscreen ? ((CmdLine.Option("-bordered") ? WS_BORDER : 0) | WS_VISIBLE)
                        : (WS_POPUP | WS_VISIBLE)),  // use POPUP for full screen
       gl.noFullscreen && !CmdLine.Option("-recenter") ? xPos / 2 : 0,
       gl.noFullscreen && !CmdLine.Option("-recenter") ? yPos / 2 : 0,     // initial game window x,y
       display.w,         // initial game width
       display.h,         // initial game height
       HWND_DESKTOP,      // handle to parent
       NULL,              // handle to menu
       hinstance,         // instance of this application
       NULL)
      )         // extra creation parms
     ) {
     OUTPUT("WinAPI ERROR: Could not open window.\n");
     return(0);
    }

    if (gl.borderless || CmdLine.Option("-borderless") ) {
      LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
      lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU);
      SetWindowLong(hwnd, GWL_STYLE, lStyle);
      LONG lExStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
      lExStyle &= ~(WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE);
      SetWindowLong(hwnd, GWL_EXSTYLE, lExStyle);
      SetWindowPos(hwnd, NULL, 0, 0, display.w, display.h, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);
      }

      // Temporary change to full screen mode
      ZeroMemory(&game_screen, sizeof(game_screen)); // clear out size of DEVMODE    struct
    game_screen.dmSize = sizeof(game_screen);
    game_screen.dmPelsWidth = display.w;
    game_screen.dmPelsHeight = display.h;
    game_screen.dmBitsPerPel = display.bits;
    game_screen.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
    ChangeDisplaySettings(&game_screen, CDS_FULLSCREEN);

    // save the game window handle
    display.hwnd = game_window = hwnd;
    display.hdc = game_dc = GetDC(display.hwnd = game_window); // get the GDI device context
                                                             // set up the pixel format desc struct

    pfd = {
     sizeof(PIXELFORMATDESCRIPTOR),   // size of this PFD
     1,                               // version number
     PFD_DRAW_TO_WINDOW |             // supports window
     PFD_SUPPORT_OPENGL |             // supports OpenGL
     PFD_DOUBLEBUFFER,                // support double buff
     PFD_TYPE_RGBA,                   // request RGBA format
     (BYTE)display.bits,              // select color depth
     0, 0, 0, 0, 0, 0,                // color bits ignored
     0,                               // no alpha buff
     0,                               // shift bit ignored
     0,                               // no accum buff
     0, 0, 0, 0,                      // accum bits ignored
     16,                              // 16-bit Z-buff (depth buff)
     0,                               // no stencil buff
     0,                               // no aux buff
     PFD_MAIN_PLANE,                  // main drawing layer
     0,                               // reserved
     0, 0, 0                          // layer masks ignored
    };
    int pf;  // pixel format

    if (!gl.arbMultisampleSupported) {
     if (!(pf = ChoosePixelFormat(game_dc, &pfd)))  // match the pixel format
     {
      MessageBox(game_window, "OpenGL could not be initialized -- ChoosePixelFormat Error ; report this to program authors for help!", "OpenGL Error", MB_OK);
      return FALSE; // error returned
     }
    } else {
     pf = gl.arbMultisampleFormat;
    }
    if (!SetPixelFormat(game_dc, pf, &pfd))        // set the pixel format
    {
     MessageBox(game_window, "OpenGL could not be initialized -- SetPixelFormat Error ; report this to program authors for help!", "OpenGL Error", MB_OK);
     return FALSE; // error returned
    }

    if (!(game_rc = wglCreateContext(game_dc)))    // create the rendering context
    {
     MessageBox(game_window, "OpenGL could not be initialized -- CreateContext Error ; report this to program authors for help!", "OpenGL Error", MB_OK);
     return FALSE; // error returned
    }

    if (!(upload_rc = wglCreateContext(game_dc)))    // create the rendering context
    {
     MessageBox(game_window, "Multiple OpenGL contexts could not be initialized -- CreateContext Error ; report this to program authors for help!", "OpenGL Error", MB_OK);
     return FALSE; // error returned
    } else { // Share as much as you can between two contexts
     if (!wglShareLists(game_rc, upload_rc)) {
      // could use GetLastError here
      MessageBox(game_window, "wglShareLists -- Error ; report this to program authors for help!", "OpenGL Error", MB_OK);
      return FALSE; // error returned
     }
    }

    if (!wglMakeCurrent(game_dc, display.hglrc = game_rc))         // make it  current
    {
     MessageBox(game_window, "OpenGL could not be initialized -- MakeCurrent Error ; report this to program authors for help!", "OpenGL Error", MB_OK);
     return FALSE; // error returned
    }

    ShowCursor(false);
    ShowWindow(game_window, SW_SHOWNORMAL);
    SetForegroundWindow(game_window);

In the above code, what I get is a window that has no resize functionality, hides the OS mouse cursor, and can only be exitted with ALT-TAB (or ALT-F4), and when it is exitted appears at the back of the windows Z-order. I always open my window using a parameter that sets display.w to 1920 and display.h to 1080, either in full screen or in Windowed mode. WM_SIZE is then called to adjust it to the client area.

Please note that the following WM_SIZE is called during the WinProc right after the initial time I set display.Resized(w,h):

    case WM_SIZE:
    {
     display.Resized(LOWORD(lparam), HIWORD(lparam));
     return (0);
    }
    break;

This is executed exactly once during app load, and in the first case it looks like the values are: 1918,1078


UPDATE: If I use the result of GetWindowRect() here, or GetClientRect() as shown below, the window mysteriously moves to Center-X,Center-Y of screen! What gives??

    //  RECT rect;
    //  if ( GetClientRect(hwnd,&rect) ) {
    //   display.Resized((int)rect.right,(int)rect.bottom);
    //  }
      //if ( GetWindowRect( hwnd, &rect ) ) {
      // display.Resized((int)ADIFF(rect.left,rect.right),(int)ADIFF(rect.top,rect.bottom));
      //}
      display.Resized(LOWORD(lparam), HIWORD(lparam));
      return (0);

What steps do I need to take to make the window stretchable such that the context is resized to the view, and the mouse is properly adjusted based on the screen ratio?

Basically, there are too many edge cases to make sense of all of this. As time has gone on since the 2 years ago that I asked this question, I've had other inconsistencies between full screen and window emerge.

From what I understand there are basically 3 types of windows:

  • Your normal on-screen moveable/resizable window for windowing GUIs, like this browser window (if you are not on mobile)
  • One matched to a display's resolution support (including resolutions smaller than its native) -- we call this "Full screen" (or Fullscreen, which isn't even a word)
  • One that is a normal on-screen window, but lacks a title bar, borders and scroll bars, and appears as large as the screen. Referred to "on the street" as a "Borderless Window"

I want to master all of these but in a way that makes them all accessible and doesn't require special cases. I've basically given up on doing so with WinAPI, but obviously multiple companies do this. Following Microsoft's documentation isn't very helpful, and I've experimented with a lot of different CreateWindow CreateWindowEx -- many of these features are deprecated by the way, or don't work at all.

(Maybe the best question is, WHEN WILL MICROSOFT CLEAN UP THIS CRAP? But I think we all know the answer.) .. any help to get it working would be appreciated.

I'm now working in: C++, Windows API, OpenGL 3.x / 4.x, Windows 10.

Acorn
  • 24,970
  • 5
  • 40
  • 69
  • 1
    "*rather than write everything for 1080p and have it adjust to display size, which is what i want*" Your ***users*** don't want that. Your users will want text that doesn't become illegible just because they used your program in a window. So while it makes things easy for you, it makes things painful for your users. – Nicol Bolas Dec 20 '16 at 20:32
  • If they are running it at < 1080p the text won't become illegible unless they are at less than 50% of the target size... point is if they can only support 1600x1200 it will work .. this is a game we're talking about here.. for PC desktops .. that run Windows 10 ... but might have an older monitor with off-standard sizes ... sheesh – Head Wizard Locke Dec 20 '16 at 20:38
  • It's also not clear if your problem is, "I can't accurately set and/or get the Win32 window's client area" or "Given the current window client area, how do I make OpenGL render to it so that most of my code thinks its rendering at a specific resolution, with the final rendering being scaled up/down to match the client area?" Because these are two separate issues. – Nicol Bolas Dec 20 '16 at 20:43
  • I'm asking for the best solution given the first bullet points in the question at the top. And I'm asking for clarification on the various methods I linked to, and which ones I should use, how they should be used, to produce the bullet points at the top of the question. – Head Wizard Locke Dec 20 '16 at 20:53
  • 3
    My point is that those are *separate* questions. How to set the position and size of a Win32 window has nothing to do with OpenGL. How to have the canvas area change while the internal rendering code remains the same by scaling the rendered image has nothing to do with Win32; it's a function of how you do your rendering. I have no idea how to deal with the positioning of a Win32 window, but I know several techniques (with varying properties) for accomplishing the latter. But since that wouldn't be a *complete* answer to your multi-part question, I can't post it. – Nicol Bolas Dec 20 '16 at 20:57
  • The overall question here is "How do I make the window meeting the criteria" .. in WinAPI, using an OpenGL context . . . that's fine if you only meet the criteria of the one side of the Venn Diagram and therefore cannot answer the question....I'll wait for someone who can answer it in this context, for these reasons, I guess. It has mainly to do with WinAPI. I understand the whole render-to-texture pipeline which I'm using, but when I stretch it, it doesn't match the window, so this question is mainly about dealing with WinAPI windows, and not so much the OpenGL side – Head Wizard Locke Dec 20 '16 at 23:33
  • but there could be an OpenGL ramification – Head Wizard Locke Dec 20 '16 at 23:35
  • 2
    There is nothing truly special to Win32 here. About the only thing unique would be the default framebuffer's behavior where its resolution is in parity with your client rectangle. That is not unique among OpenGL windowing systems, just unique if you compare it with Direct3D (where the swapchain resolution remains constant and is upscaled). You do not want to go the "let the driver upscale it" route, because that can add a full frame of latency in addition to moving text off-center so that it becomes illegible. – Andon M. Coleman Dec 21 '16 at 00:08
  • 3
    The question is on a topic I love and deal with daily writing game overlay software for D3D/GL/Vulkan, but it is much too broad to answer right now. In fact, when I read the title, I thought you were looking for a way to continue rendering while Windows is in the resize modal loop. – Andon M. Coleman Dec 21 '16 at 00:08
  • I'm not sure why it is so "broad", especially since it deals with something very specific: a resizable OpenGL context with a fixed viewport size in a WinAPI window. – Head Wizard Locke Dec 21 '16 at 01:22
  • Ok I figured out why the gap is appearing, so the question has been simplified to not include that bit. Now it's simply a question of how to setup a window such that the context is displayed at a fixed size, and the window shows the output at the window's size. – Head Wizard Locke Dec 21 '16 at 01:27
  • 1
    @Andon M. Coleman Is the main idea to create very specific transforms for text rendering to allow it being displayed without scaling by driver? Locke, your projection matrix sets transform between NCS and your model coordinate system, glViewport sets transform from NCS to window (screen) coordinates. If you have different resolution, you have different transform, if you want a uniform result, you have to change model-NCS transform too, because aspect ratio or scale changes – Swift - Friday Pie Dec 22 '16 at 07:32
  • @Swift I understand there is that method, but I have FBOs of a certain size etc. Don't I have to modify the context during WM_SIZE or WM_SIZING? Anyway the point is not only about the stretchiness, its about how to navigate everything to support not only stretchiness, but also how to make it so I can get the cursor to leave the window, how to get the right #s, I'm sort of looking for the definitive answer to the question. I've followed a bunch of different tutorials / q&a posts, and I'm still not entirely sure why it shows up with Top Left at CenterX,Y, and some other oddities across versns – Head Wizard Locke Dec 27 '16 at 22:08
  • Guys, I asked, and what I asked was: "What steps do I need to take to make the window stretchable such that the context is resized to the view, and the mouse is properly adjusted based on the screen ratio?" – Head Wizard Locke Oct 30 '19 at 18:18
  • @NicolBolas "OpenGL and Win32 window = nothing to do with each other" -- I disagree entirely. That's like saying wheels have nothing to do with axles. – Head Wizard Locke Oct 30 '19 at 18:43
  • I get that there are glViewport calls to make to set up your coordinate system. But the context has to be lied to about this viewport on Windows in some contexts because I have to set the viewport's Y value as negative to compensate for dumb old windows arbitrary title bars and the display resolution scaling, and lately none of that has been adding up. – Head Wizard Locke Oct 30 '19 at 18:45
  • The Win32 API has some quirks, no doubt, but how it behaves is pretty well known for everything but the obscure stuff or edge cases. To the point that there are alternative implementations (WINE, ReactOS...). I suggest reading MS documentation thoroughly. – Acorn Oct 30 '19 at 18:46
  • By the way, why do you mention .NET or Visual C++? I am confused. Both of those have nothing to do with the question. – Acorn Oct 30 '19 at 18:49
  • @Acorn to differentiate between C# .NET or C++ g++ mingw – Head Wizard Locke Nov 01 '19 at 02:24

0 Answers0