Skip to content

[FEATURE]: limit the FPS (frames per second) target rate, useful for remote work #13817

Closed

Description

@CoolCold

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Gist

When working on powerful remote machine over RDP (XRDP) I could see that it become laggy and xrdp consumes CPU. After some back and forth, I've assumed it may be to high refresh rate and it seems to be so - once I've set rps to 5, xrdp CPU consumption became much lower.

As well, when working over ssh - which is a quite standard thing too, having high FPS doesn't make much sense, i.e. over 250-300 ms link, what kind of FPS/RPS you may expect :)

I've build local package, enabled debug information ( Ctrl+P -> Toggle Debug ) , watched FPS is limited while producing some output. In the other terminal, I was watching top for xrdp.

"classical" situation:

Image

Activity

  1. added
    discussionUsed for feature requests, proposals, ideas, etc. Open discussion
    on Feb 16, 2026
  2. added
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    perfIndicates a performance issue or need for optimization
    on Feb 16, 2026
  3. jmagder commented on Feb 16, 2026

    @jmagder

    Its your lucky day! I just opened a PR to do just this because I was annoyed my CPU fan was too loud. :D You can check it out here:

    #13901

    Please let me know if it helps you! Hopefully it'll be merged before you see this. :D

  4. github-actions commented on Apr 22, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

  5. jmagder commented on Apr 22, 2026

    @jmagder

    I guess this never got looked at? I can't reopen the issue, and I have a PR solution!

  6. dkamins commented on Jul 22, 2026

    @dkamins

    This problem still exists, and the feature suggestion is good.
    (The suggested PR seems orthogonal and related to the web interface. I believe the original feature request is about the TUI, as is my interest.)

    More Detailed Proposal

    OpenCode currently initializes its TUI renderer with hard-coded targetFps: 60, while leaving OpenTUI's separate maxFps cap at its default.
    See: https://github.com/anomalyco/opencode/blob/dev/packages/tui/src/app.tsx

    On systems where an animation or spinner keeps the renderer live, lowering both values substantially reduces CPU usage (the app is internally walking a huge scene graph 60 times per second) as well as remote-terminal traffic.

    Something as simple as an environment variable would suffice, e.g.:

    OPENCODE_TUI_FPS=10 opencode

  7. dkamins commented on Jul 22, 2026

    @dkamins

    Unsupported workaround: Binary patch OpenCode TUI to throttle FPS

    (targets binary in official Docker image ghcr.io/anomalyco/opencode)

    While native FPS configuration is being considered...
    I developed a binary-patch workaround that makes a copy of the opencode binary with both targetFps and maxFps set as specified. You can easily make separate binaries for different uses, e.g. opencode-fps-30, opencode-fps-10, opencode-fps-1, etc.

    At some point in the future when OpenCode changes the relevant code, this patch will no longer work (but it should fail gracefully as opposed to created a corrupt binary).

    This patch is provided for experimentation, with no warranties. Review the script before running it.

    #!/usr/bin/env node
    
    /**
     * opencode-fps-patch.js -
     * This script creates a modified copy of the `opencode` binary,
     * but with a different target and max FPS "patched" (a.k.a. hacked) in.
     * 
     * 
     * ## Background
     * 
     *  - OpenCode is a great open-source TUI coding agent.
     *  - OpenCode TUI, by default, runs and renders at 60 FPS, hard-coded.
     *  - Even a basic animation or spinner that keeps the renderer live wastes
     *    _immense_ amounts of CPU cycles, energy, and network bandwidth.
     *  - There is no option to change FPS in the application itself.
     *  - There are multiple open and closed issues about this, but no resolutions.
     * 
     * 
     * Main related issues:
     *  - https://github.com/anomalyco/opencode/issues/13817
     *  - https://github.com/anomalyco/opencode/issues/19466
     * 
     * Possibly related issues:
     *  - https://github.com/anomalyco/opencode/issues/811
     *  - https://github.com/anomalyco/opencode/issues/6172
     *  - https://github.com/anomalyco/opencode/issues/11119
     *  - https://github.com/anomalyco/opencode/issues/13673
     *  - https://github.com/anomalyco/opencode/issues/22017
     * 
     * 
     * ## Should You Use This Patch?
     * 
     * Decision tree:
     *  - Could you fork OpenCode, patch the source, and recompile it? Yes.
     *  - Should you do that? Yes.
     *  - Are you lazy and want a quick hack? Also yes.
     *  - Does this come with any warranties? Absolutely not.
     *  - Is it safe? Sure. :homer-bush:
     *  - Should you use this? No. Compile from source.
     *  - Can you use it? Yes. Read what it does, and decide for yourself.
     *
     * 
     * ## How This Works
     * 
     * First, see:
     * https://github.com/anomalyco/opencode/blob/dev/packages/tui/src/app.tsx
     * 
     * That file does a few things, including (abbreviated) setting up OpenTUI:
     *  - `import { createCliRenderer, MouseButton } from "@opentui/core"`
     *  - `createCliRenderer({ ... targetFps: 60, gatherStats: false, ... })`
     * 
     * In the compiled `opencode` binary, that ends up minified to a single string:
     *  - `targetFps:60,gatherStats:!1`  (27 characters / bytes)
     * 
     * While `targetFps` limits continuous/live rendering,
     * `maxFps` (notably missing) limits immediate `requestRender()` frames.
     * Setting both is required and applies the same cap to both rendering paths.
     * 
     * But `gatherStats` defaults to `false` in OpenTUI. So we don't need that.
     * We overwrite this 27 byte string with a new string of the **same length**,
     * but with our desired FPS and `maxFps` set to the same value.
     * 
     * That is all this patch does. It makes a copy of the `opencode` binary,
     * and overwrites that string with a new one, containing the desired FPS.
     * 
     * 
     * ## How to Use This
     * 
     *  1. Make sure you have Node.js installed.
     *  2. Save this script to a file, e.g. `opencode-fps-patch.js`.
     *  3. Run `node opencode-fps-patch.js <1-99>`,
     *     e.g. `node opencode-fps-patch.js 10`.
     *  4. The new binary is written to,
     *     e.g. `/usr/local/bin/opencode-fps-10`.
     * 
     *
     * ## Which OpenCode Versions is this Compatible With?
     *
     *  - This is a binary patch. 
     *  - This is Linux/Docker oriented; it will not work with signed executables.
     *  - It was tested against the compiled `opencode` in official Docker image:
     *    `ghcr.io/anomalyco/opencode` (originally developed on v1.17.15)
     *  - It may work with other versions, but it is not guaranteed.
     * 
     * 
     * ## Disclaimer / License
     * 
     *  - Read the code below. It is a simple binary patch, but it is still a hack.
     *  - Use at your own risk. 
     *  - Absolutely no warranties, express or implied.
     *  - This code is CC0-1.0 public domain. No rights reserved.
     *
     * @typedef {string} _ABOUT_THIS_PATCH
     */
    
    
    const fs = require("fs");
    
    // Source and destination executable paths (adjust as needed):
    const CONFIG = {
        SRC:    () => `/usr/local/bin/opencode`,            // (existing binary)
        DST: (fps) => `/usr/local/bin/opencode-fps-${fps}`,  // (created binary)
    }
    
    // Parse FPS arg:
    const {fps} = (() => { 
        const args = process.argv.slice(2);
        const rawFps = args[0];
        if ( args.length !== 1 || !/^(?:0?[1-9]|[1-9]\d)$/.test(rawFps ?? "") ) {
            console.error("Usage: opencode-fps-patch <1-99>");
            process.exit(1);
        }
        return {fps: Number(rawFps)};
    })();
    
    // Prepare and double-check patch plan:
    const {oldBytes, newBytes} = (() => {
        const oldBytes = Buffer.from("targetFps:60,gatherStats:!1", 'ascii');
        const newBytes = Buffer.from(`targetFps:${fps},maxFps:${fps}`.padEnd(oldBytes.length, " "), 'ascii');
        if (oldBytes.length !== newBytes.length) {
            throw new Error("Replacement must be same length as original");
        }
        return {oldBytes, newBytes};
    })();
    
    // Read source binary and patch bytes in buffer:
    const {src, patchedSrc, mode} = (() => {
        const src = CONFIG.SRC();
        const mode = fs.statSync(src).mode & 0o777;
        const data = fs.readFileSync(src);
        const offset = data.indexOf(oldBytes);
        if (offset < 0 || data.indexOf(oldBytes, offset + 1) >= 0) {
            throw new Error("Expected exactly one matching byte sequence");
        }
        newBytes.copy(data, offset);
        return {src, patchedSrc: data, mode};
    })();
    
    // Write new binary with replacement:
    const {dst} = (() => {
        const dst = CONFIG.DST(fps);
        const tmp = `${dst}.tmp-${process.pid}`;
        try {
            fs.writeFileSync(tmp, patchedSrc, { flag: 'wx', mode });
            fs.renameSync(tmp, dst);
        } finally {
            if (fs.existsSync(tmp)) { fs.unlinkSync(tmp); }
        }
        return {dst};
    })();
    
    // Happy:
    console.log(`Patched: ${src} -> ${dst}`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

discussionUsed for feature requests, proposals, ideas, etc. Open discussionopentuiThis relates to changes in v1.0, now that opencode uses opentuiperfIndicates a performance issue or need for optimization

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions