diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 43658071b..628b909aa 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -69,6 +69,9 @@ Other Changes: - Misc: Debuggers: added type formatters for the LLDB debuggers (e.g. Xcode, Android Studio & more) to provide nicer display for ImVec2, ImVec4, ImVector etc. See misc/debuggers/ for details. (#8950) [@mentlerd] +- Textures: fixed a crash if a texture marked as _WantDestroy by a backend after + it had already been destroyed. This would typically happen when calling backend's + ImGui_ImplXXXX_InvalidateDeviceObjects() helpers twice in a row. (#8977, #8811) - Textures: fixed an issue preventing multi-contexts sharing a ImFontAtlas from being possible to destroy in any order. - Textures: fixed not updating ImTextureData's RefCount when destroying a context diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0946d7a46..082af59ac 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2790,21 +2790,29 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures) IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK."); + // Request destroy + // - Keep bool to true in order to differentiate a planned destroy vs a destroy decided by the backend. + // - We don't destroy pixels right away, as backend may have an in-flight copy from RAM. + if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_Destroyed && tex->Status != ImTextureStatus_WantDestroy) + { + IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); + tex->Status = ImTextureStatus_WantDestroy; + } + + // If a texture has never reached the backend, they don't need to know about it. + // (note: backends between 1.92.0 and 1.92.4 could set an already destroyed texture to ImTextureStatus_WantDestroy + // when invalidating graphics objects twice, which would previously remove it from the list and crash.) + if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) + tex->Status = ImTextureStatus_Destroyed; + + // Process texture being destroyed if (tex->Status == ImTextureStatus_Destroyed) { IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!"); if (tex->WantDestroyNextFrame) remove_from_list = true; // Destroy was scheduled by us else - tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend (e.g. freed resources mid-run) - } - else if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_WantDestroy) - { - // Request destroy. - // - Keep bool to true in order to differentiate a planned destroy vs a destroy decided by the backend. - // - We don't destroy pixels right away, as backend may have an in-flight copy from RAM. - IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); - tex->Status = ImTextureStatus_WantDestroy; + tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend: recreate it (e.g. freed resources mid-run) } // The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering. @@ -2812,13 +2820,10 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere if (tex->Status == ImTextureStatus_WantDestroy) tex->UnusedFrames++; - // If a texture has never reached the backend, they don't need to know about it. - if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) - remove_from_list = true; - // Destroy and remove if (remove_from_list) { + IM_ASSERT(atlas->TexData != tex); tex->DestroyPixels(); IM_DELETE(tex); atlas->TexList.erase(atlas->TexList.begin() + tex_n);