Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nullptr backend ptr after resources clean #8239

Open
GitJVGuinot opened this issue Dec 18, 2024 · 11 comments
Open

nullptr backend ptr after resources clean #8239

GitJVGuinot opened this issue Dec 18, 2024 · 11 comments

Comments

@GitJVGuinot
Copy link

Version/Branch of Dear ImGui:

Version 1.91.6 Branch: Docking

Back-ends:

imgui_impl_glfw.cpp + imgui_impl_vulkan.cpp

Compiler, OS:

Windows 11 + MSVC 2022, Ubuntu 22.04 + g++ 11

Full config/build information:

Windows: Compiler line: cl /nologo /EHsc /Zi /Od /W4 /WX /MTd /std:c++20 /c /I....\include /I....\deps\include /IC:\VulkanSDK\1.3.283.0\Include /IC:\OpenAL_1.1_SDK\include /DDEBUG /D_DEBUG /D_REENTRANT /D_THREAD_SAFE file.cpp /Fo: obj_folder/file.obj

Linux:
g++ -fdiagnostics-color=always -g3 -O0 -Wall -Wextra -Werror -Wpedantic -Wconversion -std::c++20 -Bstatic -m64 -c ./file_path/file_name.cpp -o ./obj/file_name.o -I../../include -I../../deps/include -DDEBUG -D_DEBUG -D_THREAD_SAFE -D_REENTRANT

Details:

I have multiple imgui contexts to have multiple windows, but when i close one window (Freeing all resources: imgui, glfw, vulkan)
The function make break trying to acces in a nullptr (Because the resources was freed)
This only happens in windows, when i compile the exact same code in linux this doesn't happen.

Screenshots/Video:

2024-12-18-10-21-56.mp4

Minimal, Complete and Verifiable Example code:

/// @author F.c.o Javier guinot Almenar <[email protected]>
#include "jvlke/ui/imgui.hpp"

#include "jvlke/render/swapchain.hpp"
#include "jvlke/gpu/device.hpp"
#include "jvlke/utils/logger.hpp"

#include "imgui/imgui.h"
#include "imgui/imgui_impl_glfw.h"
#include "imgui/imgui_impl_vulkan.h"

namespace jvlke
{
  namespace ui
  {
    struct Imgui::Data
    {
      VkDescriptorPool descriptor_pool_ = VK_NULL_HANDLE;
      VkRenderPass render_pass_ = VK_NULL_HANDLE;

      ImGuiContext *context_ = nullptr;
      ImGuiIO *io_ = nullptr;

      GLFWwindow *window_ = nullptr;

      std::weak_ptr<gpu::Device> device_ = {};
    };

    Imgui::Imgui() {}

    void Imgui::init(std::weak_ptr<render::Viewer> viewer, std::weak_ptr<gpu::Device> device, VkInstance instance)
    {
      if (data_)
        return;

      LOG_INFO("Making imgui context");

      data_ = std::make_shared<Imgui::Data>();
      data_->device_ = device;
      data_->window_ = viewer.lock()->getWindow().lock()->getWindowPtr();

      // Crear contexto ImGui
      /////////////////////////////////////////////////////////////////////////
      IMGUI_CHECKVERSION();
      data_->context_ = ImGui::CreateContext();
      ImGui::SetCurrentContext(data_->context_);
      data_->io_ = &ImGui::GetIO();

      // Inicializar backend de GLFW
      // Todos los callbacks se setean en la clase window
      if (!ImGui_ImplGlfw_InitForVulkan(data_->window_, false))
      {
        LOG_ERROR("Imgui GLFW initialization failed");
        this->free();
        return;
      }
      /////////////////////////////////////////////////////////////////////////

      // Crear descriptor pool para este contexto
      /////////////////////////////////////////////////////////////////////////
      VkDescriptorPoolSize pool_sizes[1] = {
          {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1},
      };
      VkDescriptorPoolCreateInfo pool_info = {};
      pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
      pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
      pool_info.maxSets = 1;
      pool_info.poolSizeCount = (uint32_t)(sizeof(pool_sizes) / sizeof(*pool_sizes));
      pool_info.pPoolSizes = pool_sizes;

      if (vkCreateDescriptorPool(data_->device_.lock()->getDevice(), &pool_info, nullptr, &data_->descriptor_pool_) != VK_SUCCESS)
      {
        LOG_ERROR("Failed to create descriptor pool for Imgui");
        this->free();
        return;
      }
      /////////////////////////////////////////////////////////////////////////

      // Crear render pass para este contexto
      /////////////////////////////////////////////////////////////////////////
      VkAttachmentDescription colorAttachment = {};
      colorAttachment.format = viewer.lock()->getRender().lock()->getSwapChainFormat();
      colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
      colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
      colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
      colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
      colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
      colorAttachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
      colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

      // Configurar referencia para el color attachment
      VkAttachmentReference colorAttachmentRef = {};
      colorAttachmentRef.attachment = 0;
      colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

      // Configurar descripción del depth attachment
      VkAttachmentDescription depthAttachment = {};
      depthAttachment.format = viewer.lock()->getRender().lock()->getSwapChainDepthFormat();
      depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
      depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
      depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
      depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
      depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
      depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
      depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

      // Configurar referencia para el depth attachment
      VkAttachmentReference depthAttachmentRef = {};
      depthAttachmentRef.attachment = 1;
      depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

      // Configurar subpass para usar el depth attachment
      VkSubpassDescription subpass = {};
      subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
      subpass.colorAttachmentCount = 1;
      subpass.pColorAttachments = &colorAttachmentRef;
      subpass.pDepthStencilAttachment = &depthAttachmentRef;

      // Configurar dependencias para garantizar las transiciones de layouts
      VkSubpassDependency dependency = {};
      dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
      dependency.dstSubpass = 0;
      dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
      dependency.srcAccessMask = 0;
      dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
      dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;

      // Crear el render pass con color y depth attachments
      std::vector<VkAttachmentDescription> attachments = {colorAttachment, depthAttachment};

      VkRenderPassCreateInfo renderPassInfo = {};
      renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
      renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
      renderPassInfo.pAttachments = attachments.data();
      renderPassInfo.subpassCount = 1;
      renderPassInfo.pSubpasses = &subpass;
      renderPassInfo.dependencyCount = 1;
      renderPassInfo.pDependencies = &dependency;

      // Crear el render pass
      if (vkCreateRenderPass(device.lock()->getDevice(), &renderPassInfo, nullptr, &data_->render_pass_) != VK_SUCCESS)
      {
        LOG_ERROR("Failed to create ImGui render pass with depth attachment");
        this->free();
        return;
      }
      /////////////////////////////////////////////////////////////////////////

      // Configuración del backend (Vulkan)
      /////////////////////////////////////////////////////////////////////////
      ImGui_ImplVulkan_InitInfo init_info = {};
      init_info.Instance = instance;
      init_info.PhysicalDevice = device.lock()->getPhysicalDevice();
      init_info.Device = device.lock()->getDevice();
      init_info.QueueFamily = device.lock()->getQueueFamilyIndices().graphics_family_;
      init_info.Queue = device.lock()->getGraphicsQueue();
      init_info.DescriptorPool = data_->descriptor_pool_;
      init_info.RenderPass = data_->render_pass_;
      init_info.MinImageCount = render::SwapChain::MAX_FRAMES_IN_FLIGHT;
      init_info.ImageCount = (uint32_t)(viewer.lock()->getRender().lock()->getImageCount());
      init_info.CheckVkResultFn = [](VkResult err)
      {
        if (err != VK_SUCCESS)
          LOG_ERROR(("Vulkan error: " + std::to_string(err)).c_str());
      };

      if (!ImGui_ImplVulkan_Init(&init_info))
      {
        LOG_ERROR("ImGui Vulkan initialization failed");
        this->free();
        return;
      }
      /////////////////////////////////////////////////////////////////////////

      LOG_INFO("Imgui context created");
    }

    void Imgui::free()
    {
      if (!data_)
        return;

      LOG_INFO("Cleaning imgui context");

      ImGui::SetCurrentContext(data_->context_);
      ImGui_ImplGlfw_Shutdown();
      ImGui_ImplVulkan_Shutdown();
      ImGui::DestroyContext(data_->context_);

      if (data_->descriptor_pool_ != VK_NULL_HANDLE)
        vkDestroyDescriptorPool(data_->device_.lock()->getDevice(), data_->descriptor_pool_, nullptr);

      if (data_->render_pass_ != VK_NULL_HANDLE)
        vkDestroyRenderPass(data_->device_.lock()->getDevice(), data_->render_pass_, nullptr);

      data_.reset();
      LOG_INFO("Imgui context cleaned");
    }

    Imgui::~Imgui()
    {
      if (data_)
      {
        LOG_WARNING("Calling ui::Imgui::free in ui::Imgui::destructor, some error could occur");
        this->free();
      }
    }

    void *Imgui::getContext() const { return (void *)(data_->context_); }

    void Imgui::frameStart()
    {
      if (!data_ || !data_->context_)
        return;

      ImGui::SetCurrentContext(data_->context_);
      ImGui_ImplGlfw_NewFrame();
      ImGui_ImplVulkan_NewFrame();
      ImGui::NewFrame();
    }

    void Imgui::render(VkCommandBuffer command_buffer)
    {
      if (!data_ || !data_->context_)
        return;

      ImGui::SetCurrentContext(data_->context_);
      ImGui::Render();

      ImDrawData *draw_data = ImGui::GetDrawData();
      if (draw_data && draw_data->DisplaySize.x > 0 && draw_data->DisplaySize.y > 0)
        ImGui_ImplVulkan_RenderDrawData(draw_data, command_buffer);
    }

  } /* ui */
} /* jvlke */
@ocornut
Copy link
Owner

ocornut commented Dec 18, 2024

Could you share the callstack at the crashing site?

@GitJVGuinot
Copy link
Author

Yes, but doesn't say too much
Captura de pantalla 2024-12-18 140856

@ocornut
Copy link
Owner

ocornut commented Dec 18, 2024

You can right click to load symbols for standard DLL and have better callstack I assume.

@GitJVGuinot
Copy link
Author

Yes i can load the dll. I'm normally use linux but also like to have compiled for windows.
I check from time to time that it can compile, but I'm not very good at using visual community.
Captura de pantalla 2024-12-18 142520

@ocornut
Copy link
Owner

ocornut commented Dec 18, 2024

Did you call ImGui_ImplGlfw_Shutdown() on that window prior to destroying the window?
It should unregister the WndProc handler....

@GitJVGuinot
Copy link
Author

Yes of course.
I send a full video.

2024-12-18-14-32-53.mp4

@ocornut
Copy link
Owner

ocornut commented Dec 18, 2024

Can you also step in ImGui_ImplGlfw_Shutdown() and verify this call:

    ImGuiViewport* main_viewport = ImGui::GetMainViewport();
    ::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)bd->PrevWndProc);
    bd->PrevWndProc = nullptr;

Check that all values are correct, does the HWND value match the one you see at the crash site?

@GitJVGuinot
Copy link
Author

I can see in glfw shutdown but in the crash function, crash because bd is invalid pointer, so i can't see viewport also.

GLFW Shutdown:
Screenshot from 2024-12-18 18-08-00
Screenshot from 2024-12-18 18-08-17

Crash Function:
Screenshot from 2024-12-18 18-09-54
Screenshot from 2024-12-18 18-09-59

@ocornut
Copy link
Owner

ocornut commented Dec 18, 2024

It doesn't seem to be referring to the same HWND. See PlatformHandleRaw value in Shutdown call vs your HWND value.
I think it needs further investigation on your end.

I could easily silently return when bd==null but I would only do that if proven that it is absolutely required, as that sort of silent return tends to be hiding errors.

@GitJVGuinot
Copy link
Author

I see...
I think that maybe i setted bad imgui. But if (I think) i made the things right i'm not ass well programmer to search the problem...

@ocornut
Copy link
Owner

ocornut commented Dec 19, 2024

You can see what ImGui_ImplGlfw_Init() and ImGui_ImplGlfw_Shutdown() are doing:

ImGui_ImplGlfw_Init saves old WndProc value, set ours.

    // Windows: register a WndProc hook so we can intercept some messages.
#ifdef _WIN32
    bd->PrevWndProc = (WNDPROC)::GetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC);
    IM_ASSERT(bd->PrevWndProc != nullptr);
    ::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)ImGui_ImplGlfw_WndProc);
#endif

ImGui_ImplGlfw_Shutdown restores:

ImGuiViewport* main_viewport = ImGui::GetMainViewport();
::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)bd->PrevWndProc);
bd->PrevWndProc = nullptr;

It's normally restoring the WndProc which was installed before so we don't expect ImGui_ImplGlfw_WndProc to be called again after this call.

Knowing that you can use a debugger and try to find out what's going wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants