docs

Setting Up the Platform

Configure platform handlers on the Platform singleton before creating the renderer.

On this page

Ultralight delegates OS tasks (such as file loading, font lookup, clipboard access, logging, GPU rendering, and audio playback) to handler objects registered on the Platform singleton— you configure them once before creating the renderer. If you use AppCore, it sets up default platform handlers automatically.

Registering Platform Handlers

You register each handler on Platform::instance() using its dedicated setter method, such as Platform::set_file_system() or Platform::set_font_loader().

Handler Purpose With Renderer::Create() Guide
FileSystem Loads file:/// URLs and library resource files Required Custom File System
FontLoader Maps CSS font families to font data Required Custom Font Loading
Clipboard Handles copy and paste Optional Clipboard Integration
Logger Receives library log messages Optional Logging and Console Messages
GPUDriver Renders GPU-accelerated Views Required for accelerated Views Implementing a GPU Driver
SurfaceFactory Allocates pixel buffers for CPU-rendered Views Optional (default provided) Render Surfaces
AudioOutput Plays audio for <video> and <audio> Optional (Pro edition and higher) Media and Audio
ThreadFactory Creates the library's threads Optional Custom Threads
Profiler Receives timing callbacks Optional (Pro edition and higher) Profiling and Tracing

đźš§ Missing Required Handlers

The library logs a LogLevel::Fatal message and exits the process if a required handler is missing. This happens at startup if FileSystem is missing, and when creating the first View if FontLoader is missing. Creating an accelerated View without a GPUDriver exits the same way.

Setting Handlers with AppCore

App::Create() installs stock handlers automatically. If you set a handler on Platform before calling App::Create(), AppCore keeps it and fills in stock handlers for the rest.

đźš§ Custom Render Handlers with AppCore

AppCore keeps a GPUDriver or SurfaceFactory set before App::Create(), but its windows can't present Views with it. You can set any other handler beforehand.

Using Stock Handlers

If you manage your own run loop and call Renderer::Create(), you can link AppCore to use its stock handlers declared in <AppCore/Platform.h> (the C version sits in the tab below).

#include <Ultralight/Ultralight.h>
#include <AppCore/Platform.h>

using namespace ultralight;

void InitPlatform() {
  ///
  /// Use the OS's native font loader.
  ///
  Platform::instance().set_font_loader(GetPlatformFontLoader());

  ///
  /// Use the OS's native file loader, with a base directory of "."
  /// All file:/// URLs will load relative to this base directory.
  ///
  Platform::instance().set_file_system(GetPlatformFileSystem("."));

  ///
  /// Use the default logger (writes to a log file, the library owns it).
  ///
  Platform::instance().set_logger(GetDefaultLogger("ultralight.log"));
}
#include <AppCore/CAPI.h>

void InitPlatform(void) {
  ///
  /// Use the OS's native font loader.
  ///
  ulEnablePlatformFontLoader();

  ///
  /// Use the OS's native file loader, with a base directory of "."
  /// All file:/// URLs will load relative to this base directory.
  ///
  ULString base_dir = ulCreateString(".");
  ulEnablePlatformFileSystem(base_dir);
  ulDestroyString(base_dir);

  ///
  /// Use the default logger (writes to a log file, the library owns it).
  ///
  ULString log_path = ulCreateString("ultralight.log");
  ulEnableDefaultLogger(log_path);
  ulDestroyString(log_path);
}

The library owns the handlers returned by these helper functions— you must never destroy them.

The SDK's platform/ folder contains the source code for each stock handler under the Zlib license, which you can use as a starting point for custom handlers (see Linking to the Library).

Setup Order for Renderer::Create()

When you create the renderer directly, set up the platform in this order:

  1. Call Platform::instance().set_config() with a Config (see Creating the Renderer).
  2. Register platform handlers on Platform::instance().
  3. Call Renderer::Create() (see Your First Game UI).

Writing Custom Handlers

To write a custom handler in C++, inherit from the interface class, implement its virtual methods, and pass a pointer to the matching setter on Platform::instance().

You retain ownership of custom handlers passed to Platform— each instance must outlive the Renderer.

The library may call handler methods from threads other than the Renderer's thread. Each handler's guide details which calls run concurrently and must be thread-safe.

In C, platform handlers use structs of function pointers instead of C++ classes— see Platform Handlers in C.