diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ceb4a6..1a6088d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Bookmark→library save and bookmark Save As now plumb the active site's `CategoryFetcher` through to the filename template, so `%artist%`/`%character%` tokens render correctly instead of silently dropping out when saving a post that wasn't previewed first - Info panel no longer silently drops tags that failed to land in a cached category — any tag from `post.tag_list` not rendered under a known category section now appears in an "Other" bucket, so partial cache coverage can't make individual tags invisible - `BooruClient._request` retries now cover `httpx.RemoteProtocolError` and `httpx.ReadError` in addition to the existing timeout/connect/network set — an overloaded booru that drops the TCP connection mid-response no longer fails the whole search on the first try +- VRAM retained when no video is playing — `stop()` now frees the GL render context (textures + FBOs) instead of just dropping the hwdec surface pool. Context is recreated lazily on next `play_file()` via `ensure_gl_init()` (~5ms, invisible behind network fetch) ### Refactored - `category_fetcher` batch tag-API params are now built by a shared `_build_tag_api_params` helper instead of duplicated across `fetch_via_tag_api` and `_probe_batch_api` diff --git a/booru_viewer/gui/media/mpv_gl.py b/booru_viewer/gui/media/mpv_gl.py index 9927325..c50b2dc 100644 --- a/booru_viewer/gui/media/mpv_gl.py +++ b/booru_viewer/gui/media/mpv_gl.py @@ -111,7 +111,20 @@ class _MpvGLWidget(QWidget): self._gl.makeCurrent() self._init_gl() - def cleanup(self) -> None: + def release_render_context(self) -> None: + """Free the GL render context without terminating mpv. + + Releases all GPU-side textures and FBOs that the render context + holds. The next ``ensure_gl_init()`` call (from ``play_file``) + recreates the context cheaply (~5ms). This is the difference + between "mpv is idle but holding VRAM" and "mpv is idle and + clean." + + Safe to call when mpv has no active file (after + ``mpv.command('stop')``). After this, ``_paint_gl`` is a no-op + (``_ctx is None`` guard) and mpv won't fire frame-ready + callbacks because there's no render context to trigger them. + """ if self._ctx: # GL context must be current so mpv can release its textures # and FBOs on the correct context. Without this, drivers that @@ -123,6 +136,10 @@ class _MpvGLWidget(QWidget): finally: self._gl.doneCurrent() self._ctx = None + self._gl_inited = False + + def cleanup(self) -> None: + self.release_render_context() if self._mpv: self._mpv.terminate() self._mpv = None diff --git a/booru_viewer/gui/media/video_player.py b/booru_viewer/gui/media/video_player.py index d4c0c51..cc7efa4 100644 --- a/booru_viewer/gui/media/video_player.py +++ b/booru_viewer/gui/media/video_player.py @@ -491,6 +491,11 @@ class VideoPlayer(QWidget): # teardown and rejects the write, GL context destruction # still drops the surface pool eventually. pass + # Free the GL render context so its internal textures and FBOs + # release VRAM while no video is playing. The next play_file() + # call recreates the context via ensure_gl_init() (~5ms cost, + # swamped by the network fetch for uncached videos). + self._gl_widget.release_render_context() self._time_label.setText("0:00") self._duration_label.setText("0:00") self._seek_slider.setRange(0, 0)