How to use imgui in an existing win32 dx11 application properly. #5061

Closed
opened 2022-02-27 21:14:37 +00:00 by mistahmikey · 6 comments
mistahmikey commented 2022-02-27 21:14:37 +00:00 (Migrated from github.com)

Version/Branch of Dear ImGui:

Version: 1.88 WIP (18709)
Branch: master

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_dx11_cpp + imgui_impl_win32.cpp
Compiler: Visual C++ 2022
Operating System: Windows 11

My Issue/Question:

I am trying to use imgui with an existing win32 DX11 application. I have done everything you have done in example_win32_directx11 when I hook into the dx11 "present" function to initialize imgui and, upon request, displaying the demo window, and I am able to get the imgui demo window to display and interact with its widgets. However, I am unable to drag the demo window around (it giggles a bit but stays put), and when I resize the platform window containing the imgui demo window, the imgui window scales up or down, becoming unreadable and difficult to interact with (mouse no longer seems to correlate to where widgets visually appear in the resized imgui window.) In the example application, neither of these situations occurs - I can drag the imgui window around freely, and when I resize the platform window, the imgui window retains their original aspect, both of which are the behaviors I want. Here is my WinProc:

LRESULT __stdcall WndProc(const HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

	if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam))
		return true;

        ImGuiIO& io = ImGui::GetIO();
	std::cout << "uMsg: " << GetMessageText(uMsg) << ", io.WantCaptureMouse: " << io.WantCaptureMouse << ", io.WantCaptureKeyboard: " << io.WantCaptureKeyboard << std::endl;

	switch (uMsg)
	{
	case WM_KEYUP:
		switch (wParam)
		{
		case VK_F11:
			g_showExamples = !g_showExamples;
			break;
		case VK_END:
			g_shutdownRequested = true;
		}
               break;
        case WM_SIZE:
               if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED)
               {
                       CleanupRenderTarget();
                       g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0);
                      CreateRenderTarget();
               }
              return 0;
        }

	return CallWindowProc(g_oWndProc, hWnd, uMsg, wParam, lParam);
}

It is almost the same as your example, with the main differences being handling a couple of keystrokes so I can toggle the demo window display or end my imgui usage, and passing the messages if needed to the original WinProc of the application.

So, I would appreciate some help in understanding why by imgui behavior is not the same as your example, and if perhaps there is something specific I must do to filter certain events from the platform window that is not needed in your demo application (since it is only doing default processing of window messages not needed by the example.) I tried to use io.WantCaptureMouse to do that, but it didn't change any behavior.

**Version/Branch of Dear ImGui:** Version: 1.88 WIP (18709) Branch: master **Back-end/Renderer/Compiler/OS** Back-ends: imgui_impl_dx11_cpp + imgui_impl_win32.cpp Compiler: Visual C++ 2022 Operating System: Windows 11 **My Issue/Question:** I am trying to use imgui with an existing win32 DX11 application. I have done everything you have done in example_win32_directx11 when I hook into the dx11 "present" function to initialize imgui and, upon request, displaying the demo window, and I am able to get the imgui demo window to display and interact with its widgets. However, I am unable to drag the demo window around (it giggles a bit but stays put), and when I resize the platform window containing the imgui demo window, the imgui window scales up or down, becoming unreadable and difficult to interact with (mouse no longer seems to correlate to where widgets visually appear in the resized imgui window.) In the example application, neither of these situations occurs - I can drag the imgui window around freely, and when I resize the platform window, the imgui window retains their original aspect, both of which are the behaviors I want. Here is my WinProc: ``` LRESULT __stdcall WndProc(const HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam)) return true; ImGuiIO& io = ImGui::GetIO(); std::cout << "uMsg: " << GetMessageText(uMsg) << ", io.WantCaptureMouse: " << io.WantCaptureMouse << ", io.WantCaptureKeyboard: " << io.WantCaptureKeyboard << std::endl; switch (uMsg) { case WM_KEYUP: switch (wParam) { case VK_F11: g_showExamples = !g_showExamples; break; case VK_END: g_shutdownRequested = true; } break; case WM_SIZE: if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED) { CleanupRenderTarget(); g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0); CreateRenderTarget(); } return 0; } return CallWindowProc(g_oWndProc, hWnd, uMsg, wParam, lParam); } ``` It is almost the same as your example, with the main differences being handling a couple of keystrokes so I can toggle the demo window display or end my imgui usage, and passing the messages if needed to the original WinProc of the application. So, I would appreciate some help in understanding why by imgui behavior is not the same as your example, and if perhaps there is something specific I must do to filter certain events from the platform window that is not needed in your demo application (since it is only doing default processing of window messages not needed by the example.) I tried to use io.WantCaptureMouse to do that, but it didn't change any behavior.
mistahmikey commented 2022-02-27 23:22:37 +00:00 (Migrated from github.com)

After playing around with this a bit more, I was able to get the imgui window dragging and resizing to work by making the following modification to my WinProc:

LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam))
    {
        return 1;
    }

    ImGuiIO& io = ImGui::GetIO();
    if (io.WantCaptureMouse || io.WantCaptureKeyboard)
    {
        std::cout << "uMsg: " << GetMessageText(uMsg) << ", io.WantCaptureMouse: " << io.WantCaptureMouse << ", io.WantCaptureKeyboard: " << io.WantCaptureKeyboard << std::endl;
    }

    switch (uMsg)
    {
	case WM_KEYUP:
		switch (wParam)
		{
		case VK_F11:
			g_showExamples = !g_showExamples;
			break;
		case VK_END:
			g_shutdownRequested = true;
		}
                break;
          case WM_SIZE:
                if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED)
                {
                    CleanupRenderTarget();
                    g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0);
                    CreateRenderTarget();
                }
                return DefWindowProc(hWnd, uMsg, wParam, lParam);
    } 

    if (io.WantCaptureMouse || io.WantCaptureKeyboard)
    {
         return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    return CallWindowProc(g_oWndProc, hWnd, uMsg, wParam, lParam);
}

Interestingly, if instead of calling the DefWindowProc, I simply return 0, I am no longer able to interact with the imgui widgets at all. In any event, I still am still getting the imgui window scaling when I resize the platform window. I will keep digging, but any help with solving that issue would be appreciated, as would an explanation of why returning 0 vice returning DefWindowProc works so differently.

After playing around with this a bit more, I was able to get the imgui window dragging and resizing to work by making the following modification to my WinProc: ``` LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam)) { return 1; } ImGuiIO& io = ImGui::GetIO(); if (io.WantCaptureMouse || io.WantCaptureKeyboard) { std::cout << "uMsg: " << GetMessageText(uMsg) << ", io.WantCaptureMouse: " << io.WantCaptureMouse << ", io.WantCaptureKeyboard: " << io.WantCaptureKeyboard << std::endl; } switch (uMsg) { case WM_KEYUP: switch (wParam) { case VK_F11: g_showExamples = !g_showExamples; break; case VK_END: g_shutdownRequested = true; } break; case WM_SIZE: if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED) { CleanupRenderTarget(); g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0); CreateRenderTarget(); } return DefWindowProc(hWnd, uMsg, wParam, lParam); } if (io.WantCaptureMouse || io.WantCaptureKeyboard) { return DefWindowProc(hWnd, uMsg, wParam, lParam); } return CallWindowProc(g_oWndProc, hWnd, uMsg, wParam, lParam); } ``` Interestingly, if instead of calling the DefWindowProc, I simply return 0, I am no longer able to interact with the imgui widgets at all. In any event, I still am still getting the imgui window scaling when I resize the platform window. I will keep digging, but any help with solving that issue would be appreciated, as would an explanation of why returning 0 vice returning DefWindowProc works so differently.
ocornut commented 2022-02-28 14:53:35 +00:00 (Migrated from github.com)

You don't need to the return value of ImGui_ImplWin32_WndProcHandler() it'll always return 0.

I have done everything you have done in example_win32_directx11

Beware when making statement like that, they tend to perpetuate not seeing a blind spot. 90% of the time it isn't a correct statement and there is a blind spot that you may have missed.

I don't know what g_oWndProc is, that your own code. AFAIK you should always call DefWindowProc() if you want standard Windows behavior to function. And Dear ImGui only needs events passwed to ImGui_ImplWin32_WndProcHandler() + xxx_NewFrame() for the Renderer backend + xxx_NewFrame() for the Win32 backend + ImGui::NewFrame().

You don't need to the return value of `ImGui_ImplWin32_WndProcHandler()` it'll always return 0. > I have done everything you have done in example_win32_directx11 Beware when making statement like that, they tend to perpetuate not seeing a blind spot. 90% of the time it isn't a correct statement and there is a blind spot that you may have missed. I don't know what `g_oWndProc` is, that your own code. AFAIK you should always call `DefWindowProc()` if you want standard Windows behavior to function. And Dear ImGui only needs events passwed to ImGui_ImplWin32_WndProcHandler() + xxx_NewFrame() for the Renderer backend + xxx_NewFrame() for the Win32 backend + ImGui::NewFrame().
mistahmikey commented 2022-02-28 18:55:24 +00:00 (Migrated from github.com)

You don't need to the return value of ImGui_ImplWin32_WndProcHandler() it'll always return 0.

There is this one case in there that doesn't return 0. Should I just pass WM_SETCURSOR on to the application regardless?

    case WM_SETCURSOR:
        if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor())
            return 1;
        return 0;

I don't know what g_oWndProc is, that your own code

That is the application's wndproc - I am hooking it to run imgui on top of its main window.

In any event, I believe I figured out how to deal with the resize scaling. As it turns out, the application is resizing the swap chain buffers on window resize, and my imgui backend was unaware of that. So I hooked SwapChain::ResizeBuffers, reset the render target when that happens, and now it is working as expected - with one exception. As I resize the application window, the imgui windows all scale up or down until I stop resizing by releasing the mouse, then they snap back to their original size. I guess it is just a quirk of the windows resize operation, since it appears that as long as you are dragging the window corner, the swap chain buffers are not resized. If you have more information about how this works, I'd appreciate hearing it.

Oh, and just as a sanity check - to ensure I am not forwarding events to the application that imgui "wants", I made the following function to check for that situation - please let me know if it makes sense, or should there be other events (or maybe less?) than what I have grabbed from ImGui_ImplWin32_WndProcHandler.

bool IsCapturedEvent(UINT uMsg)
{
    ImGuiIO& io = ImGui::GetIO();
    if (io.WantCaptureMouse)
    {
        switch (uMsg)
        {
        case WM_MOUSEMOVE:
        case WM_MOUSELEAVE:
        case WM_LBUTTONDOWN: 
        case WM_LBUTTONDBLCLK:
        case WM_RBUTTONDOWN: 
        case WM_RBUTTONDBLCLK:
        case WM_MBUTTONDOWN: 
        case WM_MBUTTONDBLCLK:
        case WM_XBUTTONDOWN: 
        case WM_XBUTTONDBLCLK:
        case WM_LBUTTONUP:
        case WM_RBUTTONUP:
        case WM_MBUTTONUP:
        case WM_XBUTTONUP:
        case WM_MOUSEWHEEL:
        case WM_MOUSEHWHEEL:
            return true;
        }

    }

    if (io.WantCaptureKeyboard)
    {
        switch (uMsg)
        {
        case WM_KEYDOWN:
        case WM_KEYUP:
        case WM_SYSKEYDOWN:
        case WM_SYSKEYUP:
            return true;
        }
    }
    return false;
}
> You don't need to the return value of ImGui_ImplWin32_WndProcHandler() it'll always return 0. There is this one case in there that doesn't return 0. Should I just pass WM_SETCURSOR on to the application regardless? ``` case WM_SETCURSOR: if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor()) return 1; return 0; ``` > I don't know what g_oWndProc is, that your own code That is the application's wndproc - I am hooking it to run imgui on top of its main window. In any event, I believe I figured out how to deal with the resize scaling. As it turns out, the application is resizing the swap chain buffers on window resize, and my imgui backend was unaware of that. So I hooked SwapChain::ResizeBuffers, reset the render target when that happens, and now it is working as expected - with one exception. As I resize the application window, the imgui windows all scale up or down until I stop resizing by releasing the mouse, then they snap back to their original size. I guess it is just a quirk of the windows resize operation, since it appears that as long as you are dragging the window corner, the swap chain buffers are not resized. If you have more information about how this works, I'd appreciate hearing it. Oh, and just as a sanity check - to ensure I am not forwarding events to the application that imgui "wants", I made the following function to check for that situation - please let me know if it makes sense, or should there be other events (or maybe less?) than what I have grabbed from ImGui_ImplWin32_WndProcHandler. ``` bool IsCapturedEvent(UINT uMsg) { ImGuiIO& io = ImGui::GetIO(); if (io.WantCaptureMouse) { switch (uMsg) { case WM_MOUSEMOVE: case WM_MOUSELEAVE: case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: return true; } } if (io.WantCaptureKeyboard) { switch (uMsg) { case WM_KEYDOWN: case WM_KEYUP: case WM_SYSKEYDOWN: case WM_SYSKEYUP: return true; } } return false; } ```
ocornut commented 2022-02-28 19:08:45 +00:00 (Migrated from github.com)

There is this one case in there that doesn't return 0. Should I just pass WM_SETCURSOR on to the application regardless?

Good point, let me investigate before I can give you an answer.

and my imgui backend was unaware of that

The win32 backend updates io.DisplaySize in _NewFrame() based on the client area size so normally this would be automatic.

as long as you are dragging the window corner

In Win32 by default when using Windows resize/move is goes into a modal loop and doesn't return control to your application. So this is the "default" legacy behavior of Win32. The common workaround is to hook WM_PAINT and/or set a WM_TIMER and refresh inside that. The exact details of how it looks depends on details of how the swapchain is configured (e.g. on default examples it typically looks different in DX11 vs Vulkan).

Oh, and just as a sanity check - to ensure I am not forwarding events to the application that imgui "wants", I made the following function to check for that situation

I suppose that would work. I guess what people often do is that the use io.WantCaptureMouse to alter higher-level data (e.g. clear/overwrite mouse data in your engine, or make high-level functions in your engine not return them).

> There is this one case in there that doesn't return 0. Should I just pass WM_SETCURSOR on to the application regardless? Good point, let me investigate before I can give you an answer. > and my imgui backend was unaware of that The win32 backend updates `io.DisplaySize` in _NewFrame() based on the client area size so normally this would be automatic. > as long as you are dragging the window corner In Win32 by default when using Windows resize/move is goes into a modal loop and doesn't return control to your application. So this is the "default" legacy behavior of Win32. The common workaround is to hook WM_PAINT and/or set a WM_TIMER and refresh inside that. The exact details of how it looks depends on details of how the swapchain is configured (e.g. on default examples it typically looks different in DX11 vs Vulkan). > Oh, and just as a sanity check - to ensure I am not forwarding events to the application that imgui "wants", I made the following function to check for that situation I suppose that would work. I guess what people often do is that the use `io.WantCaptureMouse` to alter higher-level data (e.g. clear/overwrite mouse data in your engine, or make high-level functions in your engine not return them).
ocornut commented 2022-02-28 19:31:18 +00:00 (Migrated from github.com)

There is this one case in there that doesn't return 0. Should I just pass WM_SETCURSOR on to the application regardless?
Good point, let me investigate before I can give you an answer.

This is correct, ignore my previous comment about not testing return value. In fact I am going to add additional commentary around that function now.

>> There is this one case in there that doesn't return 0. Should I just pass WM_SETCURSOR on to the application regardless? > Good point, let me investigate before I can give you an answer. This is correct, ignore my previous comment about not testing return value. In fact I am going to add additional commentary around that function now.
mistahmikey commented 2022-02-28 21:24:19 +00:00 (Migrated from github.com)

Allrighty, thanks for your time. Got it working well now.

Allrighty, thanks for your time. Got it working well now.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ocornut/imgui#5061
No description provided.