Introduction to VTS-Browser-CPP

This guide shows how to build a simple C++ front-end application build on VTS.

../_images/frontend-cpp.png

What we should have in the end.

The application will create a window and show a single map configuration. You may navigate on the map with mouse.

This tutorial uses source code from vts-browser-minimal example, which is part of the git repository.

Dependencies

VTS Browser

The library is available as a package on some popular linux distributions. Instructions to add the source-lists are at OSS. After that, install the developer files for the library and optionally the debug symbols.

apt install libvts-browser-dev libvts-browser-dbg

If the package is not available for your distribution, you may build the library from source code. See Building for more information. After that just install the library locally.

sudo cmake --build . --target install

Source code for the library is available at GitHub.

SDL

SDL is portable library for window creation, OpenGL context creation and event handling.

For instructions on installation see SDL.

Note

We use SDL in this example to keep it simple. However, you are free to use the browser library with any OpenGL context, no matter where you get it.

Cmake

Cmake is platform-independent project configuration tool that generates platform (or IDE) specific project files.

For installation instructions see Cmake.

Building

First, we write a simple cmake script called CMakeLists.txt. It will search for the actual paths to all the required libraries. Next it specifies to build an executable program called vts-example. The program uses single source file called main.cpp and is linked with all the libraries.

CMakeLists.txt:

cmake_minimum_required(VERSION 3.0)
project(vts-example CXX)
set(CMAKE_CXX_STANDARD 11)

find_package(VtsBrowser REQUIRED)
include_directories(${VtsBrowser_INCLUDE_DIR})
find_package(VtsRenderer REQUIRED)
include_directories(${VtsRenderer_INCLUDE_DIR})
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIR})

add_executable(vts-example main.cpp)
target_link_libraries(vts-example ${VtsBrowser_LIBRARIES} ${VtsRenderer_LIBRARIES} SDL2)

You may download the file: srcs/frontend-cpp/CMakeLists.txt.

Next, let cmake generate the platform specific build files (usually a makefile on linux). This step is only done once.

Moreover, we do not want to clutter the directory with numerous temporary files, therefore, we instruct cmake to build in a separate directory.

mkdir build
cd build
cmake ..

After that, just call the following command every time you want to rebuild the application (from inside the build directory).

cmake --build .

This cmake call will use the generated build files. Alternatively, you may use them directly.

Source Code

main.cpp:

#include <vts-browser/log.hpp>
#include <vts-browser/map.hpp>
#include <vts-browser/mapOptions.hpp>
#include <vts-browser/camera.hpp>
#include <vts-browser/navigation.hpp>
#include <vts-renderer/renderer.hpp>

#include <thread>

#define SDL_MAIN_HANDLED
#include <SDL2/SDL.h>

SDL_Window *window;
SDL_GLContext renderContext;
SDL_GLContext dataContext;
std::shared_ptr<vts::Map> map;
std::shared_ptr<vts::Camera> cam;
std::shared_ptr<vts::Navigation> nav;
std::shared_ptr<vts::renderer::RenderContext> context;
std::shared_ptr<vts::renderer::RenderView> view;
std::thread dataThread;
bool shouldClose = false;

void dataEntry()
{
    vts::setLogThreadName("data");

    // the browser uses separate thread for uploading resources to gpu memory
    //   this thread must have access to an OpenGL context
    //   and the context must be shared with the one used for rendering
    SDL_GL_MakeCurrent(window, dataContext);
    vts::renderer::installGlDebugCallback();

    // this will block until map->renderFinalize
    //   is called in the rendering thread
    map->dataAllRun();

    SDL_GL_DeleteContext(dataContext);
    dataContext = nullptr;
}

void updateResolution()
{
    int w = 0, h = 0;
    SDL_GL_GetDrawableSize(window, &w, &h);
    auto &ro = view->options();
    ro.width = w;
    ro.height = h;
    cam->setViewportSize(ro.width, ro.height);
}

int main(int, char *[])
{
    // initialize SDL
    vts::log(vts::LogLevel::info3, "Initializing SDL library");
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0)
    {
        vts::log(vts::LogLevel::err4, SDL_GetError());
        throw std::runtime_error("Failed to initialize SDL");
    }

    // configure parameters for OpenGL context
    // we do not need default depth buffer, the rendering library uses its own
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    // use OpenGL version 3.3 core profile
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
                        SDL_GL_CONTEXT_PROFILE_CORE);
    // enable sharing resources between multiple OpenGL contexts
    SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);

    // create window
    vts::log(vts::LogLevel::info3, "Creating window");
    {
        window = SDL_CreateWindow("vts-browser-minimal-cpp",
            SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
            800, 600,
            SDL_WINDOW_MAXIMIZED | SDL_WINDOW_OPENGL
            | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN);
    }
    if (!window)
    {
        vts::log(vts::LogLevel::err4, SDL_GetError());
        throw std::runtime_error("Failed to create window");
    }

    // create OpenGL contexts
    vts::log(vts::LogLevel::info3, "Creating OpenGL contexts");
    dataContext = SDL_GL_CreateContext(window);
    renderContext = SDL_GL_CreateContext(window);
    SDL_GL_SetSwapInterval(1); // enable v-sync

    // create instance of the vts::Map class
    map = std::make_shared<vts::Map>();

    // make vts renderer library load OpenGL function pointers
    // this calls installGlDebugCallback for the current context too
    vts::renderer::loadGlFunctions(&SDL_GL_GetProcAddress);

    // create the renderer library context
    context = std::make_shared<vts::renderer::RenderContext>();

    // set required callbacks for creating mesh and texture resources
    context->bindLoadFunctions(map.get());

    // launch the data thread
    dataThread = std::thread(&dataEntry);

    // create a camera and acquire its navigation handle
    cam = map->createCamera();
    nav = cam->createNavigation();

    // create renderer view
    view = context->createView(cam.get());

    // initialize the map for rendering
    updateResolution();
    map->renderInitialize();

    // pass a mapconfig url to the map
    map->setMapconfigPath("https://cdn.melown.com/mario/store/melown2015/"
            "map-config/melown/Melown-Earth-Intergeo-2017/mapConfig.json");

    // acquire current time (for measuring how long each frame takes)
    uint32 lastRenderTime = SDL_GetTicks();

    // main event loop
    while (!shouldClose)
    {
        // process events
        {
            SDL_Event event;
            while (SDL_PollEvent(&event))
            {
                switch (event.type)
                {
                // handle window close
                case SDL_APP_TERMINATING:
                case SDL_QUIT:
                    shouldClose = true;
                    break;
                // handle mouse events
                case SDL_MOUSEMOTION:
                {
                    // relative mouse position
                    double p[3] = { (double)event.motion.xrel,
                                (double)event.motion.yrel, 0 };
                    if (event.motion.state & SDL_BUTTON(SDL_BUTTON_LEFT))
                        nav->pan(p);
                    if (event.motion.state & SDL_BUTTON(SDL_BUTTON_RIGHT))
                        nav->rotate(p);
                } break;
                case SDL_MOUSEWHEEL:
                    nav->zoom(event.wheel.y);
                    break;
                }
            }
        }

        // update navigation etc.
        updateResolution();
        uint32 currentRenderTime = SDL_GetTicks();
        map->renderUpdate((currentRenderTime - lastRenderTime) * 1e-3);
        cam->renderUpdate();
        lastRenderTime = currentRenderTime;

        // actually render the map
        view->render();
        SDL_GL_SwapWindow(window);
    }

    // release all
    view.reset();
    nav.reset();
    cam.reset();
    map->renderFinalize();
    dataThread.join();
    context.reset();
    map.reset();

    SDL_GL_DeleteContext(renderContext);
    renderContext = nullptr;
    SDL_DestroyWindow(window);
    window = nullptr;

    return 0;
}

You may download the source: srcs/frontend-cpp/main.cpp.

Conclusion

Complete documentation for the browser library is at wiki.