Runtime Loop

From this example you will learn how to initialize the Umbra runtime, and how to fetch a list of renderable objects inside the main loop.

Image

First we need to create a client context object to hold internal Umbra state. It's possible to pass in platform specific integrations for memory allocation, HTTP and logging. However, here we just pass in NULL because we want to use the defaults: std::malloc, the CURL library, and logging into stdout.

UmbraClient* client = UmbraClientCreate("Runtime sample", NULL, NULL);

Then we instantiate the main runtime and pass in the client to its constructor. Multiple runtime instances can share the same client. Again, we could specify platform specific constraints here, but we go with the defaults by passing in NULL. The defaults get us a moderate file cache and optimistic assumptions of texture support.

Runtime* runtime = Runtime::
    create(client, EnvironmentInfo(), NULL, NULL, 0);

In Umbra each model belongs to a project. Therefore we must identify both the project and the model before we can fetch data from the cloud. The IDs can be acquired via the Project API.

ProjectContext projectContext = { projectId, modelId };

Now we are ready to create a Scene. A scene acts as a handle to the actual 3d data, but we need to first authenticate with a cloud token to access it. The token can be acquired from the Umbra website.

Scene* scene = runtime->createScene();
scene->connect(cloudToken, projectContext);

Then we associate a camera (represented as a View in the Umbra API) with the runtime. Since scene was also instantiated from the same runtime, its contents will be visible in the created view.

View* view = runtime->createView();

Now it's time to enter the main loop that renders the 3d scene. The loop can divided into five phases:

1. Poll the connection and quit if it disconnects. Calling scene->connect() doesn't wait until the connection has been established, so we need to periodically call scene->connectionStatus() to make progress. Note that it's not a problem if we call runtime functions before scene has connected — it will just be empty.

2. Fetch assets from cloud and transfer them to the GPU. Since the implementation depends on the renderer, you will need to implement streamAssets yourself. The function getNextAssetJob will provide you with the assets. See the Asset Streaming example for details.

3. Update the list of meshes that need rendering. For example, the camera might have been moved or higher quality meshes streamed in.

4. Render meshes, in this case in batches of 32 at a time. Umbra identifies the meshes to render with IDs (64-bit integers) that you specify when streaming in the assets. It may be convenient to make the IDs be pointers to your own mesh objects.

5. Display the frame after rendering your regular non-Umbra content such as user interfaces or dynamic models.

So, the loop will be then the following:

while (!quit)
{
    // (1) Poll the connection
    if (scene->connectionStatus(NULL, 0) == Scene::ConnectionError)
        break;

    // (2) Fetch assets from cloud
    streamAssets(runtime, yourRenderer, streamingTimeBudgetMs);

    // (3) Update list of meshes that need rendering
    view->update(
        cameraTransform, 0.85f, View::CullingMode::FrustumCulling, NULL, 0);

    Renderable batch[32];

    // (4) Render meshes in batches of 32
    for (int num; (num = view->nextRenderables(batch, 32)) != 0;)
        for (int i = 0; i < num; i++)
            yourRenderer->renderMesh(batch[i].mesh);

    // (5) Display the frame
}

The numbers denote the phases of the rendering loop described above.

This concludes the runtime loop example. The complete source code is reproduced below.

UmbraClient* client = UmbraClientCreate("Runtime sample", NULL, NULL);

Runtime* runtime = Runtime::
    create(client, EnvironmentInfo(), NULL, NULL, Runtime::UncachedAssetMemory);

// Project & model ID can be acquired from the Project API
ProjectContext projectContext = { projectId, modelId };

Scene* scene = runtime->createScene();
scene->connect(cloudToken, projectContext);

View* view = runtime->createView();

while (!quit)
{
    if (scene->connectionStatus(NULL, 0) == Scene::ConnectionError)
        break;

    streamAssets(runtime, yourRenderer, streamingTimeBudgetMs);
    view->update(
        cameraTransform, 0.85f, View::CullingMode::FrustumCulling, NULL, 0);

    Renderable batch[32];

    for (int num; (num = view->nextRenderables(batch, 32)) != 0;)
        for (int i = 0; i < num; i++)
            yourRenderer->renderMesh(batch[i].mesh);
}

Next: Asset Streaming