Skip to content

Release the filter FBO when a filter is removed - #2210

Merged
pedroSG94 merged 1 commit into
pedroSG94:masterfrom
x270880x:fix/release-filter-fbo
Oct 9, 2026
Merged

pedroSG94 merged 1 commit into
pedroSG94:masterfrom
x270880x:fix/release-filter-fbo

Conversation

@x270880x

@x270880x x270880x commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

initFBOLink() gives every filter added to MainRender its own framebuffer, a DEPTH_COMPONENT16 renderbuffer and an RGBA texture of the stream size (BaseRenderOffScreen.initFBO). Nothing deletes them. Each filter's release() deletes only its program (object filters also their content texture), and there is no glDeleteFramebuffers or glDeleteRenderbuffers anywhere in the library. So every removeFilter and clearFilters leaks one frame-size FBO until the EGL context is destroyed.

Nobody notices when filters are set once. An app that changes overlays during a stream keeps growing until the GPU runs out of memory. This may be what is behind #744 and #910.

Change

  • MainRender frees the framebuffer, the renderbuffer and the texture whenever it drops a filter (removeFilter, clearFilters, release). It does this after the filter's own release(), on the GL thread that created them. The ids are reset to 0, so a second release does nothing and a filter added again gets new ones.
  • setFilter still reuses the old filter's FBO for the new filter. The old filter now gets a new RenderHandler before its release(), so the two filters no longer share the id arrays.
  • removeFilter(filter) releases the filter only if it was in the list.

Measured

Samsung Galaxy A06 (SM-A065F), our app on RootEncoder 2.8.0. MainRender, RenderHandler, BaseRenderOffScreen and BaseFilterRender are the same in 2.8.0, 2.8.1 and master. The test was a 30-minute stream to a local RTMP server, switching every 60 s between a scene with text, image and GIF overlays and an empty scene:

  • As is: PSS went from 513 to 927 MB, with a peak of 1054 MB. The Graphics line of dumpsys meminfo went from 146 to 564 MB in 16 switches, about 8 MB per overlay per switch.
  • With the same deletion done in our filter subclasses' release(): PSS went from 381 to 358 MB, with a peak of 403 MB. Graphics went from 138 to 112 MB.

These numbers come from that app-side equivalent. This patch itself compiles against master, but I haven't run it on a device yet.

🤖 Generated with Claude Code

initFBOLink() gives every added filter a framebuffer, a depth renderbuffer
and an RGBA texture the size of the stream. Nothing ever deletes them:
release() of each filter only deletes its program (object filters also
their content texture), and there is no glDeleteFramebuffers or
glDeleteRenderbuffers in the library. So every removeFilter and
clearFilters leaks one frame-size FBO until the EGL context is destroyed. Apps that change overlays during a stream run the
GPU out of memory.

MainRender now frees the FBO, the renderbuffer and the texture whenever it
drops a filter, after the filter's own release(), on the GL thread that
created them. The ids are reset to 0, so a second release does nothing and
a filter added again gets new ones.

setFilter keeps reusing the old filter's FBO for the new filter. The old
filter gets a new RenderHandler before release(), so it no longer shares
the ids with the filter that took its place.

removeFilter(filter) now releases the filter only if it was in the list,
so removing a filter twice can't delete ids that belong to something else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pedroSG94
pedroSG94 merged commit 023f436 into pedroSG94:master Oct 9, 2026
1 check passed
@pedroSG94

Copy link
Copy Markdown
Owner

Merged, thank you for the PR.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants