Renderer in C
Configure and create the renderer, manage sessions, and run the frame loop in C.
On this page
The C API exposes the renderer singleton, library configuration, sessions, and the frame loop through opaque handles. It supplements Creating the Renderer, Sessions and Site Data, and Updating and Rendering— read those guides first for the core concepts.
For shared rules on memory ownership, callbacks, threading, and NULL handles, see C API Conventions.
Differences from C++
The C API provides C-style functions and opaque handles for renderer setup, sessions, and task scheduling.
| C++ Feature | C API Equivalent | How It Differs |
|---|---|---|
Platform::instance().set_config() and Renderer::Create() |
ulCreateRenderer(config) |
Pass the configuration directly when creating the renderer, with no platform setter. |
Config struct |
ULConfig and ulConfigSet*() |
Configure options through write-only setters, one per field. |
RefPtr<Renderer> |
ULRenderer |
Release the handle manually with ulDestroyRenderer(). |
App::renderer() |
ulAppGetRenderer() |
Returns a borrowed handle owned by the app. |
Renderer::CreateSession() |
ulCreateSession() |
Returns an owned handle you must destroy. |
Renderer::default_session() |
ulDefaultSession() |
Returns a borrowed handle owned by the renderer. |
Renderer::PostTask() with a lambda |
ulRendererPostTask() |
Takes a function pointer, user_data, and a destroy hook. |
GCListener and Renderer::Recycle() |
ulRendererSetGCRequestGCCallback() and ulRecycleEx() |
Available in the Pro edition and higher (UL_HAS(GC_LISTENER))— see Managing Memory. |
Creating the Renderer
Register your platform handlers on the platform singleton before creating the renderer— see Platform Handlers in C.
To set configuration options, allocate a configuration handle with ulCreateConfig() and call the ulConfigSet*() setters. Pass the configuration to ulCreateRenderer(), which returns NULL if initialization fails. The renderer copies the configuration immediately, so you should call ulDestroyConfig() right after creating the renderer.
For a complete setup example, see the C tab under Configuring the Library in Creating the Renderer. That page also lists all configuration options and their default values.
Using AppCore
When using AppCore, calling ulCreateApp() creates and manages the renderer for you. Passing NULL for the configuration uses default options. Call ulAppGetRenderer() to retrieve the renderer handle— see AppCore in C.
Creating View Sessions
To give a view its own isolated storage, create a session with ulCreateSession() and pass it to ulCreateView().
#include <Ultralight/CAPI.h>
static ULView player_view = NULL;
void CreatePlayerView(void) {
ULString name = ulCreateString("player-2");
ULSession session = ulCreateSession(renderer, true, name);
ulDestroyString(name);
// NULL selects the default ViewConfig.
player_view = ulCreateView(renderer, 800, 600, NULL, session);
// The View keeps its own reference to the session.
ulDestroySession(session);
}
Passing NULL for the session in ulCreateView() assigns the view to the default persistent session. You can also fetch the default session handle directly by calling ulDefaultSession().
A session that is not attached to any view is freed when you call ulDestroySession() on its handle.
🚧 Never Destroy Borrowed Handles
Never call
ulDestroySession()on the handle returned byulDefaultSession(), orulDestroyRenderer()on the handle returned byulAppGetRenderer(). The library owns both handles and cleans them up automatically.
Running the Frame Loop
The frame loop calls ulUpdate(), ulRefreshDisplay(), and ulRender() in the same order as C++. For what each function does, see the C tab in Updating and Rendering.
To coordinate animations with your display's refresh timing, call ulRefreshDisplayWithTimestamp() instead of ulRefreshDisplay(). See Updating and Rendering.
Posting Work from Other Threads
To dispatch work from a worker thread to the Renderer's thread, call ulRendererPostTask().
#include <Ultralight/CAPI.h>
#include <stdlib.h>
typedef struct {
int score;
} ScoreUpdate;
static void ApplyScore(void* user_data) {
ScoreUpdate* update = user_data;
// On the Renderer's thread, so View calls are safe here.
ShowScore(update->score);
}
// Called on a worker thread.
void OnWorkerFinished(int score) {
ScoreUpdate* update = malloc(sizeof(ScoreUpdate));
update->score = score;
// The library calls free(update) after ApplyScore() runs.
ulRendererPostTask(renderer, ApplyScore, update, free);
}
Here is how posted tasks run and clean up their data:
- You can call
ulRendererPostTask()from any thread. For threading rules, see C API Conventions. - Tasks run during the next
ulUpdate(). They run on the Renderer's thread in posting order. - Allocate
user_dataon the heap. The task runs after your worker function returns, so stack memory is invalid. - The destroy hook runs once. It runs after the task finishes. If you destroy the renderer first,
ulDestroyRenderer()calls the destroy hook without running the task.
Posting Delayed Tasks
To schedule work after a delay, call ulRendererPostDelayedTask().
Pass the delay in milliseconds. The task runs during the first ulUpdate() at or after the deadline— delivery waits while you aren't calling ulUpdate() (such as when your application is paused).
- Prefer
ulAppPostTask()in AppCore applications. It wakes the app loop— see AppCore in C. - Call
ulJSContextPostTask()for a JavaScript context. It dispatches tasks directly to that context— see JavaScript in C.
Shutting Down the Renderer
When shutting down the library, destroy your handles in this order:
- Every
ULViewyou created, by callingulDestroyView(). - Any
ULSessionhandles you still hold, by callingulDestroySession(). - The renderer singleton, by calling
ulDestroyRenderer().
For a complete teardown example, see the C tab in Your First Game UI.