What is GPUI?
GPUI is Glass’s custom GPU-accelerated UI framework written in Rust. It provides:
- Cross-platform rendering using Metal, DirectX, or Vulkan
- Reactive state management with entity-based architecture
- High performance with minimal allocations and efficient updates
- Type-safe UI construction at compile time
GPUI is maintained as a separate project: https://github.com/Obsydian-HQ/gpui
Core GPUI Crates
Glass depends on several GPUI crates:
gpui = { git = "https://github.com/Obsydian-HQ/gpui.git" }
gpui_macros = { git = "https://github.com/Obsydian-HQ/gpui.git" }
gpui_tokio = { git = "https://github.com/Obsydian-HQ/gpui.git" }
collections = { git = "https://github.com/Obsydian-HQ/gpui.git" }
util = { git = "https://github.com/Obsydian-HQ/gpui.git" }
sum_tree = { git = "https://github.com/Obsydian-HQ/gpui.git" }
scheduler = { git = "https://github.com/Obsydian-HQ/gpui.git" }
refineable = { git = "https://github.com/Obsydian-HQ/gpui.git" }
media = { git = "https://github.com/Obsydian-HQ/gpui.git" }
http_client = { git = "https://github.com/Obsydian-HQ/gpui.git" }
Context Types
GPUI uses context types to manage state and provide capabilities:
App Context
App is the root context providing access to global state:
fn do_something(cx: &mut App) {
// Access entities
let entity = cx.new(|cx| MyState::new());
// Spawn async work
cx.spawn(|cx| async move {
// Background work
});
}
Window Context
Window provides access to window state and rendering:
fn render_ui(window: &mut Window, cx: &mut App) {
// Dispatch actions
window.dispatch_action(action, cx);
// Manage focus
window.focus(&focus_handle);
}
Entity Context
Context<T> is provided when updating an entity:
impl Editor {
fn update_text(&mut self, cx: &mut Context<Self>) {
// Mutate self
self.buffer.insert(...);
// Trigger re-render
cx.notify();
}
}
Async Contexts
AsyncApp and AsyncWindowContext can be held across await points:
cx.spawn(|mut cx| async move {
let result = some_async_fn().await;
cx.update(|cx| {
// Update state with result
})?;
});
Entity System
Creating Entities
Reading Entities
Updating Entities
Weak References
Entities are created via the context:let editor = cx.new(|cx| Editor::new(cx));
This returns an Entity<Editor> handle. Read entity state immutably:editor.read(cx); // Returns &Editor
editor.read_with(cx, |editor, cx| {
editor.buffer.text()
});
Mutate entity state:editor.update(cx, |editor, cx| {
editor.insert_text("hello", cx);
cx.notify(); // Trigger re-render
});
Prevent reference cycles:let weak = editor.downgrade();
// Later, upgrade when needed
if let Some(editor) = weak.upgrade() {
editor.update(cx, |editor, cx| ...);
}
Element System
UI is built by implementing the Render trait:
struct MyView {
message: SharedString,
}
impl Render for MyView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div()
.flex()
.items_center()
.p_4()
.border_1()
.border_color(rgb(0xcccccc))
.child(self.message.clone())
}
}
Element Composition
Elements are composed using a builder pattern:
div()
.w_full()
.h(px(400.))
.flex()
.flex_col()
.gap_2()
.child(
div()
.text_lg()
.font_bold()
.child("Title")
)
.child(
div()
.text_sm()
.text_color(rgb(0x666666))
.child("Description")
)
Styling
GPUI uses a Tailwind-like API for styling:
w_full(), h_full() - Full width/height
flex(), flex_col() - Flexbox layout
p_4(), px_2(), py_1() - Padding
m_4(), mx_2() - Margin
gap_2() - Gap between flex items
border_1(), border_color() - Borders
bg(), text_color() - Colors
rounded_lg() - Border radius
Conditional Rendering
div()
.when(condition, |this| {
this.bg(red())
})
.when_some(optional_value, |this, value| {
this.child(format!("Value: {}", value))
})
Event Handling
div()
.on_click(|event, window, cx| {
println!("Clicked at {:?}", event.position);
})
.on_mouse_down(|event, window, cx| {
// Handle mouse down
})
Actions
Actions are typed commands dispatched via keybindings:
// Define an action
#[derive(Clone, PartialEq, Debug)]
struct Save;
impl_actions!(editor, [Save]);
// Register handler
div()
.on_action(cx.listener(|this: &mut Editor, _: &Save, window, cx| {
this.save(window, cx);
}))
State Management
Notifications
Trigger UI updates when state changes:
impl Editor {
fn update_buffer(&mut self, cx: &mut Context<Self>) {
self.buffer.insert(...);
cx.notify(); // Re-render this view
}
}
Observations
Observe changes to another entity:
cx.observe(&buffer, |this: &mut Editor, buffer, cx| {
// Called when buffer emits notify
this.handle_buffer_change(buffer, cx);
}).detach();
Entity Events
Entities can emit custom events that other entities subscribe to.
// Emit event
impl EventEmitter<BufferEvent> for Buffer {}
impl Buffer {
fn edit(&mut self, cx: &mut Context<Self>) {
// ... make changes
cx.emit(BufferEvent::Edited);
}
}
// Subscribe to events
cx.subscribe(&buffer, |this: &mut Editor, buffer, event, cx| {
match event {
BufferEvent::Edited => this.handle_edit(cx),
BufferEvent::Saved => this.handle_save(cx),
}
}).detach();
Concurrency
Spawning Tasks
// Foreground task (main thread)
let task = cx.spawn(|cx| async move {
// Async work on main thread
});
// Background task (thread pool)
let task = cx.background_spawn(async move {
// CPU-intensive or blocking work
});
// Keep task alive or it will be cancelled
task.detach();
Task Lifecycle
Awaiting
Detaching
Storing
Waits for the task to complete. Allows task to run even if handle is dropped. struct MyView {
_task: Task<()>,
}
Task is cancelled when struct is dropped.
Graphics Backend
GPUI uses wgpu for cross-platform GPU access:
- macOS: Metal backend
- Windows: DirectX 12 backend
- Linux: Vulkan backend
Rendering Pipeline
- Element tree construction (
Render::render())
- Layout calculation (Taffy flexbox engine)
- Paint phase (GPU commands)
- Frame submission to GPU
Window Management
cx.open_window(WindowOptions::default(), |window, cx| {
// Initialize window content
MyView::new(cx)
});
- Native menus (macOS)
- File dialogs
- Clipboard access
- System notifications
- Screen capture
Efficient Updates
- Only changed elements are re-rendered
- Layout is incremental
- GPU commands are batched
Memory Management
SharedString for string deduplication
- Copy-on-write semantics
- Automatic cleanup via Rust’s ownership
Frame Timing
- 60 FPS target
- VSync synchronization
- Input latency minimization
Related Pages