fix(app): free the replaced gradient texture and defer logo frees
reloadThemeImages() replaced gradient_tex_ in each of its three branches (image loaded, load failed, no background) without freeing the old texture, so every skin activation, including the one at startup, leaked one texture. The logo and coin textures were freed immediately instead, although the current frame's draw lists can still reference them: the window backdrop records its images at the start of the frame and skin switches happen mid-frame (Settings, Ctrl+arrow keys), and the screenshot sweep switches skins after RenderDrawData() but before the secondary viewports are rendered. That hands the renderer a freed texture. Textures replaced mid-frame are now retired to a queue (retireTexture() also zeroes the member) that the next preFrame() frees first, after the previous frame's draw data has been rendered. The old gradient is retired at the top of reloadThemeImages(), and the logo and coin where they used to be freed. ensureLogoTexture() still frees directly, since it runs at the top of render(), before this frame draws a logo. Shutdown does not drain the queue. Measured with a temporary live-texture counter over a screenshot sweep of all 10 skins: before, 6, 7, ... 15 live textures (one more per skin); now a constant 5. The first screenshot after each skin switch is pixel-identical to before on all 10 skins, and the full sweep matches within noise. Not verifiable here: DX11 at runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+12
-3
@@ -623,6 +623,10 @@ void App::applyVisualSettings()
|
||||
|
||||
void App::preFrame()
|
||||
{
|
||||
// Free textures retired during the previous frame (its draw data has been rendered by now).
|
||||
for (ImTextureID t : pending_texture_destroy_) util::DestroyTexture(t);
|
||||
pending_texture_destroy_.clear();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// Hot-reload unified UI schema
|
||||
@@ -3300,6 +3304,10 @@ void App::renderStatusBar()
|
||||
|
||||
void App::reloadThemeImages(const std::string& bgPath, const std::string& logoPath)
|
||||
{
|
||||
// Every branch below replaces gradient_tex_; the background draw list may already reference the
|
||||
// old one this frame (skin switches happen mid-frame), so free it at the next preFrame().
|
||||
retireTexture(gradient_tex_);
|
||||
|
||||
// Reload background image from the path resolved by the skin system.
|
||||
// Each theme specifies its image via [theme] images.background_image in its .toml.
|
||||
if (!bgPath.empty()) {
|
||||
@@ -3337,14 +3345,15 @@ void App::reloadThemeImages(const std::string& bgPath, const std::string& logoPa
|
||||
}
|
||||
|
||||
// Reset logo loaded flags — will reload on next render frame (ensureLogoTexture re-rasterizes the
|
||||
// SVG for the new theme). Free the current texture first so a theme switch doesn't leak it.
|
||||
if (logo_tex_) util::DestroyTexture(logo_tex_);
|
||||
// SVG for the new theme). Retire the current texture (freed at the next preFrame(), after this
|
||||
// frame's draw lists are rendered) so a theme switch doesn't leak it.
|
||||
retireTexture(logo_tex_);
|
||||
logo_loaded_ = false;
|
||||
logo_tex_ = 0;
|
||||
logo_w_ = 0;
|
||||
logo_h_ = 0;
|
||||
coin_logo_loaded_ = false;
|
||||
if (coin_logo_tex_) util::DestroyTexture(coin_logo_tex_);
|
||||
retireTexture(coin_logo_tex_);
|
||||
coin_logo_tex_ = 0;
|
||||
coin_logo_w_ = 0;
|
||||
coin_logo_h_ = 0;
|
||||
|
||||
@@ -1619,6 +1619,10 @@ private:
|
||||
// Background gradient overlay
|
||||
ImTextureID gradient_tex_ = 0;
|
||||
|
||||
// Textures retired mid-frame (draw lists may still reference them); freed at the next preFrame().
|
||||
std::vector<ImTextureID> pending_texture_destroy_;
|
||||
void retireTexture(ImTextureID& t) { if (t) { pending_texture_destroy_.push_back(t); t = 0; } }
|
||||
|
||||
// Logo texture (reload on skin change / dark↔light switch)
|
||||
ImTextureID logo_tex_ = 0;
|
||||
int logo_w_ = 0;
|
||||
|
||||
Reference in New Issue
Block a user