🎥 Godot Desktop Caster
GD Desktop Caster is a high-performance, cross-platform Godot 4.x GDExtension for real-time desktop screen capture. Written entirely in Rust, it utilizes native OS-level APIs to achieve minimal latency and zero overhead on Godot's main rendering thread.
📖 Table of Contents
- 🎥 Godot Desktop Caster
⚡ Why GD Desktop Caster?
Godot lacks native, high-performance desktop capture capabilities out of the box. Existing solutions often rely on slow read-backs or block the main thread. gd-desktop-caster solves this by:
- True Multithreading: Frame capture runs entirely on a background thread. Your game's FPS will never drop when a frame is being captured.
- Zero-Copy Architecture (Where possible): Optimized memory swapping (Double Buffering) guarantees
O(1)lock time on the main thread. - SIMD Optimization: Color space conversions (like BGRA to RGBA) are vectorized for extreme speeds.
🌍 Platform Support
| OS | Backend API | Status | Notes |
|---|---|---|---|
| Windows | DXGI Desktop Duplication | 🟢 Production Ready | Extremely fast. Utilizes GPU-accelerated staging textures. |
| Linux (Wayland) | PipeWire & XDG Portal | 🟢 Production Ready | The modern Linux standard. Requires user consent dialog. |
| Linux (X11) | X11 XGetImage (MIT-SHM) |
🟡 Fallback Ready | Ultra-compatible, kicks in automatically if PipeWire fails. |
| macOS | ScreenCaptureKit | 🔴 Planned | Coming soon! |
🧠 Architecture & Performance
To prevent the Godot Engine from freezing during heavy screen captures, we implemented a Double-Buffered Lock-Free-ish Handoff system:
graph LR
subgraph OS[Operating System]
API[DXGI / PipeWire]
end
subgraph Worker[Background Capture Thread]
Fetch[Acquire Frame]
Swizzle[SIMD Color Convert]
Fetch --> Swizzle
end
subgraph Godot[Godot Main Thread]
Node[DesktopCaster Node]
Tex[ImageTexture Update]
Node --> Tex
end
API -->|Raw Pixels| Fetch
Swizzle -->|Swap Pointers| Node
- OS API delivers the frame to our Worker Thread.
- The Worker converts the color space (e.g.,
BGRx->RGBA) into a hiddenlocal_buffer. - When Godot updates, the texture is updated in-place without blocking the main render loop.
🛠 Usage in Godot
Using the plugin in your Godot project is as simple as adding a Node.
1. The DesktopCaster Node
Once the plugin is installed, a new custom node called DesktopCaster will be available in Godot. Add it to your scene.
2. Usage Examples (GDScript & C#)
Assign the live texture once during initialization — the texture updates in-place, so there is no need to reassign it in _process().
extends Control
@onready var caster: DesktopCaster = $DesktopCaster
@onready var rect: TextureRect = $TextureRect
func _ready():
# Start capturing
if caster.start():
# Assign the live texture once
rect.texture = caster.get_texture()
else:
print("Failed to start capture: ", caster.get_last_error())
Since custom GDExtension classes are dynamically bound, call methods on the node using Call(...):
using Godot;
public partial class CaptureView : Control
{
private Node _caster;
private TextureRect _textureRect;
public override void _Ready()
{
_caster = GetNode<Node>("DesktopCaster");
_textureRect = GetNode<TextureRect>("TextureRect");
// Call start() via Call()
bool success = (bool)_caster.Call("start");
if (success)
{
// Get texture and assign it to TextureRect
var texture = _caster.Call("get_texture").As<ImageTexture>();
_textureRect.Texture = texture;
}
else
{
string error = (string)_caster.Call("get_last_error");
GD.PrintErr($"Failed to start capture: {error}");
}
}
}
📚 API Reference
Properties
| Name | Type | Default | Description |
|---|---|---|---|
fps_mode |
int (FpsMode) |
0 (MANUAL) |
Controls how the capture rate is determined (MANUAL or VSYNC). |
target_fps |
int |
30 |
Target frame rate when fps_mode is set to MANUAL (clamped between 1..240). |
Methods
| Return Type | Method & Description |
|---|---|
bool |
start()Starts desktop capture on a background thread. Automatically detects primary display size. Returns true on success. |
void |
stop()Stops desktop capture and joins the background thread. Called automatically when exiting scene tree. |
ImageTexture |
get_texture()Returns the live ImageTexture updated each frame. Returns null if capture has not started. |
bool |
is_capturing()Returns true if the capture thread is currently running. |
String |
get_last_error()Returns the last error message reported by the capture thread, or empty string if no error. |
void |
set_fps_mode_runtime(mode: int)Changes the FPS mode while capture is running without restarting the thread. |
void |
set_target_fps_runtime(target_fps: int)Changes target frame rate while capture is running (for MANUAL mode). |
Enumerations & Constants
enum FpsMode:
| Constant | Value | Description |
|---|---|---|
MANUAL |
0 |
Capture at a user-selected rate specified by target_fps. |
VSYNC |
1 |
Capture whenever the OS compositor presents a new frame for maximum smoothness. |
🏗 Building from Source
Prerequisites
- Godot 4.x
- Rust Toolchain (Cargo)
Windows
git clone https://github.com/Nekitori17/gd-desktop-caster.git
cd gd-desktop-caster/rust
cargo build --release
The resulting gd_desktop_caster.dll will be in rust/target/release/.
Linux (Ubuntu/Debian)
You will need PipeWire and X11 development headers:
sudo apt-get install libpipewire-0.3-dev libx11-dev
git clone https://github.com/Nekitori17/gd-desktop-caster.git
cd gd-desktop-caster/rust
cargo build --release
The resulting libgd_desktop_caster.so will be in rust/target/release/.
Deployment
Copy the compiled dynamic library (.dll or .so) into your Godot project's addons/desktop_capture/bin/ folder and ensure your .gdextension file points to it.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details. Copyright (c) 2026 Nekitori17.
Changelog for version v0.6.0
No changelog provided for this version.