# How the SuperSplat UI System Integrates with the PlayCanvas Engine

> Discover how the SuperSplat UI system integrates with PlayCanvas using a custom PCApp class, events bus, and canvas element for seamless WebGL rendering and PCUI component communication.

- Repository: [PlayCanvas/supersplat](https://github.com/playcanvas/supersplat)
- Tags: how-to-guide
- Published: 2026-05-10

---

**The SuperSplat UI system integrates with the PlayCanvas engine through a custom `PCApp` class that extends `AppBase`, a shared `Events` bus for bidirectional communication, and a canvas element that bridges PCUI components with the WebGL renderer.**

SuperSplat is an open-source Gaussian Splat editor built on top of the PlayCanvas engine. Understanding how its UI layer communicates with the underlying 3D runtime reveals a clean architecture that separates presentation from rendering while maintaining tight synchronization. This article examines the specific integration patterns found in the `playcanvas/supersplat` repository.

## SuperSplat PlayCanvas Integration Architecture

The integration relies on three architectural pillars: a customized PlayCanvas application class, an event-driven UI layer built with PCUI, and shared state management through a central event bus.

### Extending AppBase with PCApp

The `PCApp` class in [`src/pc-app.ts`](https://github.com/playcanvas/supersplat/blob/main/src/pc-app.ts) extends PlayCanvas’ `AppBase` to bootstrap the engine runtime. It registers the component systems required for rendering and simulation, including the standard render, camera, light, and animation systems, plus the custom **GSplat** component system specific to Gaussian Splatting.

```typescript
// Simplified structure from src/pc-app.ts
class PCApp extends AppBase {
    constructor(canvas: HTMLCanvasElement, options: any) {
        super(canvas, options);
        this.addComponentSystems();
        this.addResourceHandlers();
    }
    
    addComponentSystems() {
        // Registers GSplatComponentSystem alongside standard systems
    }
}

```

`PCApp` also initializes resource handlers for loading textures, cubemaps, containers, and splat data via `addResourceHandlers()`. This eliminates boilerplate and provides the UI with a fully configured PlayCanvas instance ready for entity manipulation.

### Event-Driven UI Layer

The UI is constructed in [`src/ui/editor.ts`](https://github.com/playcanvas/supersplat/blob/main/src/ui/editor.ts) within the `EditorUI` class. It uses **PCUI** components such as `Container` and `Label` to compose panels, toolbars, and dialogs. Rather than directly accessing PlayCanvas internals, UI widgets communicate through a shared `Events` object instantiated in [`src/events.ts`](https://github.com/playcanvas/supersplat/blob/main/src/events.ts).

UI actions fire events like `'camera.focalPointPicked'` or `'prerender'`, while the engine listens to these events to update the scene, camera position, or splat data. This pub/sub pattern ensures the UI remains framework-agnostic while driving engine state.

## Key Integration Points Between UI and Engine

- **Canvas Element Bridging**: The `EditorUI` creates an HTML `canvas` element that is passed to `PCApp` via the `graphicsDevice` option. This provides the WebGL rendering surface that PlayCanvas targets each frame.

- **Component System Registration**: Through `PCApp.addComponentSystems()`, the custom `GSplatComponentSystem` is registered alongside standard PlayCanvas systems. UI tools manipulate entities containing these components, and PlayCanvas updates them during the simulation step.

- **Resource Handler Pipeline**: `PCApp.addResourceHandlers()` adds loaders like `GSplatHandler` and `TextureHandler`. When UI dialogs request assets (e.g., image or video settings), these handlers load them into PlayCanvas’s asset registry.

- **Shared Event Bus**: The `Events` object enables loose coupling. UI dispatches events (`'prerender'`, `'show.shortcuts'`, `'render.image'`) that the engine consumes, while the engine fires events (`'camera.focalPointPicked'`) that UI widgets handle (see `EditorUI` lines 89‑92).

- **Gizmo Synchronization**: UI overlays like the `ViewCube` in [`src/ui/view-cube.ts`](https://github.com/playcanvas/supersplat/blob/main/src/ui/view-cube.ts) update on the `'prerender'` event using the current camera matrix, keeping interactive controls synchronized with the PlayCanvas camera.

## Code Examples of UI-Engine Communication

The following patterns demonstrate the typical flow from UI interaction to engine execution and back.

**UI triggering engine action:**

```typescript
// UI creates a button that requests camera focal point capture
const captureBtn = new Button({ text: 'Capture' });
captureBtn.on('click', () => events.fire('camera.captureFocalPoint'));

// Engine side listens and responds
events.on('camera.captureFocalPoint', () => {
    const pos = camera.getPosition();   // PlayCanvas camera API
    events.fire('camera.focalPointPicked', { position: pos });
});

// UI updates with returned position
events.on('camera.focalPointPicked', (details) => {
    cursorLabel.text = `${details.position.x.toFixed(2)}, …`;
});

```

**Canvas initialization:**

```typescript
// EditorUI creates and injects the canvas
const canvas = document.createElement('canvas');
canvas.id = 'canvas';
canvasContainer.append(canvas);

// PCApp receives the canvas and starts the render loop
const app = new PCApp(canvas, { graphicsDevice: gfxDevice });
app.start();

```

**Dialog-driven rendering:**

```typescript
// ImageSettingsDialog invokes engine render operations
events.function('show.imageSettingsDialog', async () => {
    const imgSettings = await imageSettingsDialog.show();
    if (imgSettings) {
        await events.invoke('render.image', imgSettings);
    }
});

```

## Summary

- **PCApp** in [`src/pc-app.ts`](https://github.com/playcanvas/supersplat/blob/main/src/pc-app.ts) extends PlayCanvas `AppBase` to register the `GSplatComponentSystem` and resource handlers, providing a configured engine instance.
- **EditorUI** in [`src/ui/editor.ts`](https://github.com/playcanvas/supersplat/blob/main/src/ui/editor.ts) constructs the interface using PCUI components and wires them to the engine exclusively through the shared `Events` bus.
- The **`canvas` element** created by the UI is passed to `PCApp` as the rendering surface, bridging the DOM with WebGL.
- **Bidirectional communication** occurs via event names like `'camera.focalPointPicked'` and `'prerender'`, keeping UI overlays synchronized with the PlayCanvas camera and scene state.

## Frequently Asked Questions

### What is PCApp in SuperSplat?

`PCApp` is a custom class in [`src/pc-app.ts`](https://github.com/playcanvas/supersplat/blob/main/src/pc-app.ts) that extends PlayCanvas’ `AppBase`. It bootstraps the engine by registering component systems (including the custom `GSplatComponentSystem`) and resource handlers, then manages the `GraphicsDevice` and render loop.

### How does the UI communicate with the PlayCanvas engine without direct coupling?

The UI uses a shared `Events` object created in [`src/events.ts`](https://github.com/playcanvas/supersplat/blob/main/src/events.ts) as a pub/sub bus. UI components fire events (e.g., `'camera.captureFocalPoint'`) that the engine listens to, and the engine fires events (e.g., `'camera.focalPointPicked'`) that UI widgets consume. This prevents direct imports between the UI layer and engine internals.

### What role does the Events bus play in SuperSplat?

The `Events` bus acts as the central nervous system of the application. It allows the PCUI-based interface in [`src/ui/editor.ts`](https://github.com/playcanvas/supersplat/blob/main/src/ui/editor.ts) to trigger engine operations like rendering or camera updates, and allows the engine to notify the UI of state changes such as focal point selection or prerender synchronization for gizmos.

### Where is the canvas element created and how is it connected to PlayCanvas?

The `canvas` element is created in [`src/ui/editor.ts`](https://github.com/playcanvas/supersplat/blob/main/src/ui/editor.ts) by the `EditorUI` class and appended to a container in the DOM. It is passed to the `PCApp` constructor via the `graphicsDevice` option, which attaches it to the PlayCanvas `AppBase` instance as the target for all WebGL rendering operations.