Skip to content

Minimal Sample App

Carsten Rudolph edited this page May 11, 2023 · 1 revision

This page describes a simple, minimal example application built using the LiteFX application. For a more detailed step-by-step guide, on how to create your first application, refer to the project setup and quick start guides.

Minimal Example App

The App Model

LiteFX provides a concept called App Model. The app model exposes a rudimentary infrastructure for application configuration, event handling, logging and other commonly used aspects. At it's core this is provided by the App base class. In order to properly setup the engine, your application needs to provide a class that inherits from this base class:

class MyApp : public LiteFX::App {
    static String Name() noexcept { return "My Awesome Graphics Application"; }
    String name() const noexcept override { return Name(); }

    static AppVersion Version() noexcept { return AppVersion(1, 0, 0, 0); }
    AppVersion version() const noexcept override { return Version(); }
}

The App base class provides functionality to manage the different aspects mentioned above. Most importantly, it provides an infrastructure to manage Backends. A backend is an abstract concept of some sort of functionality your app uses. Backends can have different types, but currently only one type of backend is provided: rendering backends. A rendering backend handles graphics. This is also how LiteFX implements support for different graphics APIs. As the names suggest, the VulkanBackend implements support for Vulkan and the DirectX12Backend implements support for DirectX 12.

You have to configure your app to tell it, which backends you want to use. The easiest way to achieve this is to use an AppBuilder for this:

UniquePtr<App> app = App::build<MyApp>(std::move(window), adapterId)
    .logTo<ConsoleSink>(LogLevel::Trace)
    .useBackend<VulkanBackend>()
    .useBackend<DirectX12Backend>();

app->run();    // Start the app.

Application Lifecycle

The app model is agnostic towards the frontend you are using for user interface and interaction. Hence simply running an application like this will immediately exit it again. It is your task to provide an application event loop and to implement the application de-/initialization. During its lifecycle, an application transitions through three different states: initializing, running and shutdown. Each state change invokes a respective event handler, that you may want to implement. You can bind to those events in the constructor of your app:

MyApp() noexcept {
    this->initializing += std::bind(&MyApp::onInit, this);    // called to initialize the app.
    this->startup += std::bind(&MyApp::onStartup, this);      // called to run the app.
    this->shutdown += std::bind(&MyApp::onShutdown, this);    // called to shutdown the app.
}

In the startup event, you may want to implement your main event loop. After this event handler returns control to the app, it shuts down.

void MyWin32App::onInit() {
    MSG msg;
    BOOL success; 

    while ((success = ::GetMessage(&msg, nullptr, 0, 0)) != 0) { 
        if (success == -1) {
            break; // NOTE: handle error first.
        }
        else {
            this->tick();    // custom function provided by you.

            ::TranslateMessage(&msg); 
            ::DispatchMessage(&msg); 
        }
    }
}

void MyGlfwApp::onInit() {
    while (!::glfwWindowShouldClose(*m_window)) {
        this->tick();    // custom function provided by you.
    }
}

The above examples show different event loop examples. The first one shows how to implement a Win32 event loop, the second one uses GLFW. With the basics covered, you can now continue to develop an understanding over the concepts of a modern graphics application and how they are expressed in LiteFX.