Skip to content

[CEF 144] x-cfx-game-view compat renderer is never destroyed: 'remove' is not a DOM event, WebGL2 rAF loop leaks on unmount #4342

Open

Description

@igorcalabraro

What happened?

Since the CEF 144 update (7bee8d1, mr-972), <object type="application/x-cfx-game-view"> is replaced by the epoxy compatibility script with a WebGL2 canvas driven by CfxGameViewRenderer, which re-renders the game frame on every requestAnimationFrame (EpoxyScript.h#L254-L262).

The only teardown path is (EpoxyScript.h#L346):

canvas.addEventListener('remove', () => {
  renderer.destroy();
  resizeObserver.disconnect();
});

remove is not a DOM event, so this listener never fires. When the canvas is removed from the document (for example a React/Vue component unmounting a panel that uses a game-view background), the rAF loop keeps running and the WebGL2 context (powerPreference: 'high-performance') stays alive. Each mount adds another full-frame renderer, and none of them is ever released. 0ee58ea improved destroy() itself, but nothing calls it.

We found this while looking into reports of 100% GPU usage (mostly AMD) on our server. Our HUD settings panel used a game-view background. We suspect the accumulating renderers are the cause and have removed the game view from the panel as a workaround; we have not yet confirmed the GPU impact in-game.

Expected result

When the canvas leaves the document, the renderer is destroyed: the rAF loop is cancelled and the WebGL context is lost.

Reproduction steps

  1. Create a NUI page that mounts and unmounts <object type="application/x-cfx-game-view"> on a toggle (e.g. a button that adds/removes a container holding it).
  2. Toggle it about 10 times.
  3. In the NUI devtools, the CfxGameViewRenderer rAF callbacks from every previous mount are still running even though their canvases are detached. GPU usage goes up with each mount and never comes back down.

Suggested fix

Detect detachment instead of listening for a non-existent event, e.g. in the render loop:

#render = () => {
  if (!this.#canvas || !this.#canvas.isConnected) { this.destroy(); return; }
  // ...
};

Alternatively, have the existing MutationObserver check removedNodes for canvases with cfx-game-view-compatibility and call destroy() on their renderer.

Importancy

There's a workaround

Area(s)

FiveM

Specific version(s)

Any FiveM client with CEF 144 (since 7bee8d1 / mr-972), up to current master 93a7f0d (v1.0.0.38220), based on reading the code. The exact client build of the affected players is not known yet.

Additional information

Workaround for resource authors: don't use application/x-cfx-game-view in elements that get mounted/unmounted. Or create the canvas yourself with CfxGameViewRenderer and call destroy() on unmount.

Activity

  1. added
    triageNeeds a preliminary assessment to determine the urgency and required action
    on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageNeeds a preliminary assessment to determine the urgency and required action

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions