You can embed Ultralight directly into an existing game engine while keeping the engine's own render loop, window management, and asset pipeline.

A complete integration connects the renderer to the graphics API, forwards input events, listens for page events, and synchronizes game state with the UI.

> 👍 First-time setup
>
> If you haven't displayed a View yet, start with [Your First Game UI](/docs/2.0/your-first-game-ui) to set up the CPU renderer and the frame loop (`Update()`, `RefreshDisplay()`, and `Render()`). For frame timing details, see [Updating and Rendering](/docs/2.0/updating-and-rendering).

## Choose a Render Path

You select a render path for each View at creation time using `ViewConfig::is_accelerated`.

| Path | `is_accelerated` | What Native Code Reads | Destination |
| --- | --- | --- | --- |
| CPU (default) | `false` | `View::surface()` | Pixel buffer in system memory |
| GPU | `true` | `View::render_target()` | GPU texture created by the driver |

### CPU Renderer

The CPU renderer requires no graphics integration code, making it a straightforward choice for menus and static HUDs. The engine uploads the View's pixels to an engine texture whenever they change— see [Your First Game UI](/docs/2.0/your-first-game-ui).

To render directly into memory the engine already owns and avoid a copy, provide a custom `Surface` factory— see [Render Surfaces](/docs/2.0/using-a-custom-surface).

### GPU Renderer

The GPU renderer draws everything on the GPU, including paths, text, gradients, and video. It runs faster for animated or complex interfaces, and it allows the page to display live engine textures.

#### Register a GPU Driver

The library emits abstract drawing commands that you translate to the engine's graphics API through a `GPUDriver` implementation. You retain ownership of the driver.

Register the driver on the `Platform` singleton before calling `Renderer::Create()`, then enable acceleration on the View:

```cpp
Platform::instance().set_gpu_driver(gpu_driver);  // before Renderer::Create()

ViewConfig config;
config.is_accelerated = true;
RefPtr<View> view = renderer->CreateView(1280, 720, config, nullptr);
```

#### Display the Render Target

After calling `Renderer::Render()`, call `View::render_target()` to get the driver's texture handle for that View.

Because the texture may be padded, draw the quad using `RenderTarget::uv_coords`.

#### Reference Drivers

The SDK provides reference drivers for Direct3D 11, Direct3D 12, Metal, and OpenGL in the `platform` folder that you can copy and adapt.

For implementation details, see [GPU Renderer Overview](/docs/2.0/gpu-renderer-overview) and [Implementing a GPUDriver](/docs/2.0/implementing-a-gpudriver).

## Transparency and Background Color

### Create a Transparent HUD

To display UI over gameplay, enable transparency in the View configuration:

```cpp
ViewConfig hud_config;
hud_config.is_transparent = true;
RefPtr<View> hud = renderer->CreateView(1920, 1080, hud_config, nullptr);
```

Set a transparent background on the root elements in CSS as well:

```css
html, body { background: transparent; }
```

### Blend the HUD Over the Scene

Rendered pixels use 32-bit BGRA format with premultiplied alpha. Composite the texture over the scene using premultiplied-alpha blending— straight-alpha blending produces dark fringes along transparent edges.

### Background Color of Opaque Views

An opaque View paints white until page styles finish loading, causing a white flash at startup. Set `ViewConfig::background_color` to match the page's background:

```cpp
ViewConfig menu_config;
menu_config.background_color = Color(0.08f, 0.08f, 0.1f);  // the page's own
```

## Forward Input

### Mouse, Keyboard, and Scroll

Forward input events to the page using `View::FireMouseEvent()`, `View::FireKeyEvent()`, and `View::FireScrollEvent()`. Coordinates must be in logical pixels relative to the top-left corner of the View.

For details on constructing these events, see [Your First Game UI](/docs/2.0/your-first-game-ui), [Mouse and Scroll Input](/docs/2.0/mouse-and-scroll-input), and [Keyboard Input](/docs/2.0/keyboard-input).

### Gamepad Input

Gamepad events route through the `Renderer` rather than an individual View. Call `Renderer::SetGamepadDetails()` for each controller before firing its events— see [Gamepad Input](/docs/2.0/gamepad-input).

### Route Keys Between UI and Gameplay

When gameplay and a HUD share the keyboard, check `View::HasInputFocus()` before routing key events to the page:

```cpp
void OnEngineKey(const KeyEvent& evt) {
  if (hud->HasInputFocus()) {
    hud->FireKeyEvent(evt);  // the player is typing in a text field
    return;
  }
  HandleGameplayKey(evt);
}
```

The method returns `true` only when an editable element (such as a text field or text area) has visible keyboard focus with a blinking caret. It returns `false` for checkboxes, select elements, and when the View itself is unfocused.

When a menu or modal dialog opens, forward every key event to the View so keys like Tab, Enter, and the arrows can navigate the page.

### Manage View Focus

Call `View::Focus()` and `View::Unfocus()` as input moves between Views or transitions between UI and gameplay. Focus activates visual styling on the page, such as selection highlights.

When you call `View::Unfocus()`, the focused element receives a `blur` event but remains `document.activeElement`. The next time you call `View::Focus()`, focus and the caret return to that element— see [Keyboard Focus and Editable State](/docs/2.0/keyboard-focus-and-editable-state).

## Handle Page Events

To respond to page events like title changes, tooltips, and console messages, attach a `ViewListener` by calling `View::set_view_listener()`— see [Handling View Events](/docs/2.0/handling-view-events).

### Update the Cursor

`ViewListener::OnChangeCursor()` fires when the CSS cursor under the pointer changes. Use this callback to update the OS or engine cursor.

### Handle New Windows

`ViewListener::OnCreateChildView()` fires when the page attempts to open a window using `window.open()` or a link with `target="_blank"`. Return a newly created `View` to allow the window, or return `nullptr` to block it.

> 🚧 Retain child Views
>
> The library does not keep a reference to the View returned from `OnCreateChildView()`. You must retain a reference for as long as the child View remains open.

## Connect UI to Game State

You can bridge UI and game state using three header families, which can be mixed on the same View:

| API | Header | Purpose |
| --- | --- | --- |
| Data Bindings | `<Ultralight/dom/data/Context.h>` | Synchronizes C++ data with the page. Recommended for HUDs and menus— see [About Data Bindings](/docs/2.0/about-data-bindings). |
| DOM API | `<Ultralight/DOM.h>` | Inspects and modifies elements, styles, and attributes from C++ without running page scripts— see [About the DOM API](/docs/2.0/about-the-dom-api). |
| JavaScript API | `<Ultralight/JS.h>` | Calls JavaScript functions from C++ and exposes C++ functions to the page with type checking— see [About JavaScript Interop](/docs/2.0/about-javascript-interop). |

## Manage Threads

> 🚧 Keep View and Renderer calls on one thread
>
> Call `View` and `Renderer` methods only on the thread that created the `Renderer` (such as the engine's main thread or a UI thread). `Renderer::PostTask()` is safe to call from any thread.

### Pass Worker Results to the Renderer Thread

To pass results from a worker thread to the renderer thread, call `Renderer::PostTask()`:

```cpp
worker_thread.OnFinished([renderer, result] {
  renderer->PostTask([result] { ApplyResult(result); });
});
```

Tasks run in posting order on the `Renderer` thread during the next call to `Renderer::Update()`.

You should capture data by value so it outlives the worker thread.

### Update Data Bindings on Other Threads

A data binding `Context` lives on the thread that created it (such as a simulation thread). You can modify bound data on that thread without locks.

Call `Context::Sync()` to send the changes to the page— see [Binding Threads and Lifetime](/docs/2.0/binding-threads-and-lifetime).

## Performance

> 👍 Hide inactive Views
>
> When a menu or overlay closes, call `View::set_visible(false)` instead of destroying and recreating it. A hidden View stops painting and keeps its DOM and script state for quick reuse.

For techniques on making the UI render faster and use less memory, see [Optimizing Performance](/docs/2.0/optimizing-performance).
