Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: RenderWindow in Win32 app problem with resizing  (Read 917 times)

0 Members and 1 Guest are viewing this topic.

Thez3rt3r

  • Newbie
  • *
  • Posts: 3
    • View Profile
RenderWindow in Win32 app problem with resizing
« on: February 11, 2023, 11:27:54 pm »
Hello everyone.

I started to write an utility application that needs a menu and status bar, so I went with a native win32 main window (as I don't need portability) and I'm trying to use sf::RenderWindow as a renderer.
I also tried using SFML window, but recieving WM_COMMAND from it seemed a bit hacky.
I followed this tutorial: https://www.sfml-dev.org/tutorials/1.6/graphics-win32.php, and succesfuly got myself a RenderWindow, it renders ok, but when I resize my window, strange things happen.

When I don't process WM_SIZE at all, the image is rendered correctly in the window (on the static control that's SFML's RenderWindow's "base"), but it stays the same when resizing the window, as expected.
Here's my onSize function:
Code: [Select]
LRESULT Application::onSize(UINT width, UINT height)
{
main_window.width = width;
main_window.height = height;
SetWindowPos(main_window.renderer_parent, nullptr, 0, 0, width, height, SWP_NOZORDER);
//renderer.setView(sf::View(sf::FloatRect(0.f, 0.f, width, height)));
return 0;
}
UINT width and height are taken from lParam of WM_SIZE message (using LOWORD and HIWORD macros) and are correct. SetWindowPos is used to resize the static control to dimensions of the client area, while renderer.setView sets the view for SFML RenderWindow.
Strange behavoiur goes like this:
When I just resize the static control (HWND renderer_parent), I get a drawing of the default view, as expected, but when changing height of the window, the drawn "picture" follows bottom border and hides behind the top border. When I drag the bottom border, drawing goes up/down accordingly and even goes up under the menu/title bar; whe I drag the top border, I get more/less space between it and the drawing, and I can hide the drawing behind the menu/title bar. Changing width works a s expected: the drawing sticks to the left border, I don't get anything more than drawn at the beggining when I stretch the window, and it hides behind the right border when I shrink the window.
When I uncomment the last line and set the view for the renderer, everything works just like above and I get reverse scaling of the drawing - it shrinks when I stretch the window and vice versa, in both axes. So for example, when I stretch the window in x axis, dragging right border, the drawin get squished in x axis (y axis unaffected). It also doesn't draw anything more than the initial painting (with the default view) that gets transformed in that inverse way.
I tried to create the sf::RenderWindow using my main window's handle (renderer.create(main_window.handle)) and got the exact same results, except I don't get the gray static background when resizing.

I compiled a small code snippet from the SFML and VS tutorial to see if it behaves properly, and it does. I added sf::Event::Resized case to that code as well, and just setting the view of the window makes the green circle behave properly (not get stretched with the window). So SFML library seem to work as intended.

I tried to browse SFML sources to figure out what's going on under the hood, but couldn't find anything fancy that could break when combined with WinAPI code. WindowImplWin32 instance just pushes proper SFML event to the queue and calls my own window procedure (as its pointer is stored in a class member). In theory I should be able to set the view of the RenderWindow as I would do with pure SFML code and it should work just fine.
I found a possible culprit of the wierd behaviour of the y axis resizing in the RenderTarget.cpp file:
Code: [Select]
void RenderTarget::applyCurrentView()
{
    // Set the viewport
    IntRect viewport = getViewport(m_view);
    int top = getSize().y - (viewport.top + viewport.height);
    glCheck(glViewport(viewport.left, top, viewport.width, viewport.height));

    // Set the projection matrix
    glCheck(glMatrixMode(GL_PROJECTION));
    glCheck(glLoadMatrixf(m_view.getTransform().getMatrix()));

    // Go back to model-view mode
    glCheck(glMatrixMode(GL_MODELVIEW));

    m_cache.viewChanged = false;
}
Calculating "top" variable seems off here. It shouldn't matter with the default viewport (0,0,1,1), but why there's no "left" calculated as well?

My actual question is: Is there anything else that goes inside SFML that makes the code produce such odd results?

I may try compiling SFML without that suspicious line in applyCurrentView(), if I feel sane enough for that. This problem bothers me for more than a week now and got really into my head, but I feel like I'm making circles without any progress.
Also I thought a simple workaround could be to add sf::Event::Command triggered on WM_COMMAND and recompile SFML with that, so I can use native Win32 menu with SFML window. I really don't want to drop SFML as any alternative would require implementing lots of stuff "by hand".

My initialization function:
Code: [Select]
bool Application::init(HINSTANCE instance, int cmdShow)
{
instHandle = instance;

//prepare and register window class
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = Application::wndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = instance;
wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(ID_APP_ICON));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wcex.lpszMenuName = MAKEINTRESOURCEW(ID_MAIN_MENU);
wcex.lpszClassName = WNDCLASS_NAME;
wcex.hIconSm = LoadIcon(instance, MAKEINTRESOURCE(ID_APP_ICON_SMALL));

RegisterClassExW(&wcex);

//create the main window
main_window.handle = CreateWindow(WNDCLASS_NAME, CFG::APP_NAME, WS_OVERLAPPEDWINDOW,
  40, 40, 800, 450,
  nullptr, nullptr, instance, nullptr);
if (!main_window.handle) {
MessageBox(nullptr, L"Creation of the main window failed.", nullptr, MB_OK);
return false;
}
//cache size of the client area
{
RECT rc;
GetClientRect(main_window.handle, &rc);
main_window.width = rc.right - rc.left;
main_window.height = rc.bottom - rc.top;
}

//create a static control as a base for sfml renderer
main_window.renderer_parent = CreateWindow(L"STATIC", nullptr, WS_CHILD | WS_CLIPSIBLINGS,
   0, 0, main_window.width, main_window.height,
   main_window.handle, nullptr, instance, nullptr);
if (!main_window.renderer_parent) {
MessageBox(nullptr, L"Creation of the view parent failed.", nullptr, MB_OK);
return false;
}
renderer.create(main_window.renderer_parent);

//show and update the window
ShowWindow(main_window.handle, cmdShow);
UpdateWindow(main_window.handle);

//load accelerators
accelerators = LoadAccelerators(instance, MAKEINTRESOURCE(ID_ACCELERATORS));

//load resources
ResourceManager::get().loadFont(instance, MAKEINTRESOURCE(ID_APP_FONT));

return true;
}

My message loop:
Code: [Select]
int Application::run()
{
MSG msg;
msg.message = ~(WM_QUIT);
while (msg.message != WM_QUIT){
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateAccelerator(main_window.handle, accelerators, &msg);

TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
renderer.clear();
renderer.draw(data_bank);
renderer.display();
}
}

return (int)msg.wParam;
}

Application class just stores thinkgs like HINSTANCE/HACCEL/HWND stuff, proveides static window procedure function and non-static function for handling messages (onSize, onClose etc). Instance of that class is a singleton static object, and I get it like this:
Code: [Select]
Application& Application::get()
{
static Application app_instance;
return app_instance;
}
So in my window proc I can do this:
Code: [Select]
LRESULT Application::wndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT ret = 0;
switch (message) {
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_SIZE:
ret = Application::get().onSize(LOWORD(lParam), HIWORD(lParam));
break;
case WM_CLOSE:
ret = Application::get().onClose();
break;
case WM_GETMINMAXINFO:
ret = Application::get().onMinmaxinfo((MINMAXINFO*)lParam);
break;
case WM_COMMAND:
ret = Application::get().onCommand(wParam, lParam);
break;

default:
ret = DefWindowProc(window, message, wParam, lParam);
}

return ret;
}

Any hint I could research would be very appreciated.

Stay safe!
« Last Edit: February 15, 2023, 10:10:02 pm by Thez3rt3r »

Thez3rt3r

  • Newbie
  • *
  • Posts: 3
    • View Profile
Re: RenderWindow in Win32 app problem with resizing
« Reply #1 on: February 15, 2023, 10:08:30 pm »
After few more days of playing around I think I've found a solution.
Code: [Select]
LRESULT Application::onSize(UINT width, UINT height)
{
main_window.width = width;
main_window.height = height;

renderer.setSize(sf::Vector2u(width, height));
renderer.setView(sf::View(sf::FloatRect(0.f, 0.f, width, height)));
return 0;
}
The "trick" is to set the size of sf::RenderWindow explicitly with setSize() method. You should pass it the size of client area, which is convieniently available from the WM_SIZE message, as the setSize function calculates the dimensions of the window (including borders and title bar) and calls SetWindowPos. And for that to work one doesn't need to recompile SFML (I did that to test things and find the solution).

I'll present some more explanation of what I found/tested in hope it trun out useful to anyone.
Using SetWindowPos or MoveWindow function works well for resizing the static control that's the "base" of sf::RenderWindow, but for some reason that change isn't propagated to SFML internals, so the RenderWindow doesn't change it's size. That cuases symptomps I described before, as any function calling getSize() on the RenderWindow gets the starting dimensions. When you change the view to adjust the drawing after resizing, you set it properly, but since the size of the RenderWindow isn't updated, you end up drawing to a rectangle of starting dimensions, so the scaling is reversed. And when you don't change the view, scaling doesn't occur, but nothing is drawn outside that starting rectangle.
The reason of unusual behaviour along the vertical axis - that the drawing follows to the bottom border, is caused by glViewport taking the bottom left pixel as the viewport's origin. That's also the reason why applyCurrentView function of RenderTarget (called internally when drawing) calculates the top variable. Connecting this with the fact that the RenderWindow's size isn't updated explains that phenomenon we observe.

And I think that'd be it for now. I'll be playing more with that app so maybe I'll find out more in the future. Hopefully that "more" won't be "more issues".