Introduction to the graphics library

Perhaps some of the readers remember my series of publications about the OpenSceneGraph graphics library. Already at that time, in general, it was clear that the use of OpenSceneGraph in 2018 was little justified due to the fact that OpenGL, on which OpenSceneGraph is based, is slowly losing ground. In February 2016, the Khronos group, the OpenGL standards body, released the OpenGL receiver, the Vulkan API version 1.0. Hardware manufacturers, in general, almost immediately began to add support for the new API to their hardware drivers. At least, the GeForce GTX 970, which was relevant at that time, already had such support (I can’t say anything about older series of equipment).

Despite the fact that OpenSceneGraph did receive support for OpenGL 3.x (and yours truly compiled it with such support), it became clear to the developers of this library that a huge number of internal OpenGL problems make its support unpromising. Therefore, at the end of May 2018, the development of a new graphics library VulkanSceneGraph, focused exclusively on the Vulkan API, started. On November 13, 2022, VulkanSceneGraph-1.0 was officially released, and most recently, on August 31, 2023, they released VulkanSceneGraph-1.0.9.

This young graphic library was not covered in detail here on Habré. But I think it’s time to make at least an overview of its capabilities, and maybe a series of publications on architecture and how to work with it. In general, I ask intrigued readers under cat.

Vulkan API – advantages and disadvantages

Vulkan is an even lower-level API than OpenGL, even in its 3.x and higher versions. It provides a very low level of abstraction over the hardware, giving the developer control over a huge number of rendering options, memory allocation, and flexible control over the data stream sent to the GPU.

Vulkan creates a low load on the CPU, focusing more on the capabilities of the GPU, where it is required. Out of the box, there is multithreading, both when executing code on the CPU and even more so on the GPU, with synchronization primitives built into the API.

The advantage of the new API is its cross-platform – Vulkan is supported by Android, Linux, BSD Unix, QNX, Haiku, Nintendo Switch, Raspberry Pi, Stadia, Fuchsia, Tizen and Windows 7, 8, 10 and 11 platforms. standards of the Khoronos group, did not create any special obstacles for the penetration of Vulkan on the macOS and iOS platforms, since a compatibility layer was created between Vulkan and the Metal API, allowing applications on Vulkan to work on this platform with minimal overhead.

Support for the latest hardware features such as ray tracing and mesh shaders is also a benefit of the new API.

The Vulkan API provides access to both the GPU’s graphics and computational capabilities, combining the functionality of OpenGL and OpenCL at a new, state-of-the-art level.

Its undoubted disadvantage is the high entry threshold. A developer of applications on Vulkan is faced not only with the need to take into account the mass of settings and independently organize the management of the rendering process, but also with the amount of code arising from the above – an average example of drawing a triangle on the Vulkan API contains from 1000 to 1500 lines of code.

High-level graphics API – minimalism vs. versatility

If we talk about modern graphics engines, such as Unity and Unreal, then they have a significant specialization, aimed primarily at creating games. At the same time, they contain numerous abstraction layers in order to provide support for several low-level APIs: Direct3D, Metal, OpenGL, and now also Vulkan. Such an architecture leads to both a huge amount of code and architectural compromises that inevitably arise in the process of crossing support for several APIs.

In contrast to this approach, OpenSceneGraph, and to a greater extent now VulkanSceneGraph, provides the developer with a minimal set of functions that solve the general tasks of developing a graphic application. In fact, VulkanSceneGraph encapsulates low-level Vulkan API calls, creating a thin layer of abstraction as close as possible to them in structure and functions, operating with concepts and data types similar to them. So what high-level API will be as close as possible to the hardware. This approach, in the case of cross-platform Vulkan, will not lead to a loss of portability of the code, although it could generate similar problems when using a platform-specific API.

OpenSceneGraph, which implements the concept of the so-called scene graph, provided (and provides) the encapsulation of the OpenGL state machine. Over time, this library began to stumble over the same problems that are inherent in OpenGL itself. With the advent of the Vulkan API on the scene, the OpenSceneGraph developers faced a dilemma – to adapt the existing OpenSceneGraph architecture for Vulkan, or to design a new library that operates exclusively on Vulkan.

Robert Osfield, the head of the OpenSceneGraph project, decided to create a new scene graph built exclusively on Vulkan and based on the capabilities of the then-current C ++ 17 standard. The central task was to reduce the overhead when processing the scene graph on the CPU. When running on the GPU, Vulkan is 10 times faster than OpenGL. However, if it takes 10ms to process a frame on the CPU, and OpenGL adds another 5ms on the GPU, then simply swapping one API for another will reduce the frame rendering time to 10.5ms, which is only a 43% speedup. It was desirable to increase the performance of the library on the CPU by 10 times in order to unlock the full potential of Vulkan.

VulkanSceneGraph Performance Principles

The functions provided by modern processors, such as instruction prefetching, branch prediction, instruction parallelization, are extremely sensitive to how the processed data is organized. In this regard, the approach to organizing these scene graph objects and managing the memory allocated for them has undergone a number of changes compared to OpenSceneGraph, namely:

  • The maximum possible reduction in the size of all scene graph objects. The base class of the scene graph node vsg::Node has a size of 24 bytes, compared to 208 bytes for osg::Node, that is, almost 10 times more objects can fit in the processor cache.

  • VulkanSceneGraph has its own memory allocator on the vsg::Allocator heap, which provides allocation of its own memory blocks for storing objects of the same type. This approach is aimed at reducing cache misses when working with groups of objects of the same type.

  • Scene graph node masks, used to control the direction in which it is traversed, are used only at those nodes where they are actually needed. This aims to reduce the number of conditional branch operations, which makes the branch prediction process more efficient.

  • Using native smart pointers vsg::ref_ptr<> instead of std::shared_ptr<> and vsg::observer_ptr<> instead of std::weak_ptr<>. Native pointers only store a pointer to the managed object, while the reference count is located in the object itself. This reduces the size of the pointer by a factor of 2 compared to equivalents provided by the C++ Standard Library.

These, and a number of other measures, resulted in a 10-fold increase in scene processing performance compared to OpenSceneGraph.

A more detailed description of the architecture of the VulkanSceneGraph library can be found at official website of the project. Recently, there is unofficial Russian version of this site.

In short, the basic part of the library consists of three projects hosted in the account vsg-dev on GitHub contains the following projects, which are officially maintained as part of the VulkanSceneGraph project. The three main projects that most developers need are:

  • VulkanSceneGraph – The VulkanSceneGraph library itself can be used by itself for standalone graphics applications. For embeddable solutions, this may be a desirable approach as it minimizes the final size of the executable.

  • vsgXchange Users are expected to want to upload 2D images and 3D models in various formats. The library adds support for a wide range of image formats and 3D models, using assimp to read http/https data with libcurl, and read/write/process GIS and DEMS images with optional GDAL integration.

  • vsgExamples is a collection of 61 examples (as of August 31, 2023) written to help test VulkanSceneGraph features as it is developed, and as a tutorial for new users.

The purpose of this publication is to show the process of building the library for two platforms – GNU / Linux and Windows and write the simplest “Hello, world!”, with an eye to the further development of the coverage of this topic on this resource, as the author progresses in studying this library.

Compiling under GNU/Linux with the gcc compiler

In general, it is not difficult. I don’t know about other distributions, but on Arch Linux, the vsg and vsgXcahnge libraries are available in the user’s AUR repository, here And right here. All necessary dependencies will be pulled automatically. In addition, the developers offer the following way to install the library from source

First of all, set up the necessary environment variables

# VulkanSceneGraph
export INSTALL_DIR=~/VSG/Install
export PROJECT_DIR=~/VSG/Projects
export PATH=${PATH}:${INSTALL_DIR}/bin
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${INSTALL_DIR}/lib

# Пути поиска данных для примеров и файлового кэша
export VSG_FILE_PATH=${PROJECT_DIR}/vsgExamples/data
export VSG_FILE_CACHE=${PROJECT_DIR}/vsgFileCache

Installing the necessary dependencies, for example for Debian it looks like this

# зависимости для сборки VulkanSceneGraph
sudo apt-get install git g++
sudo apt-get install cmake cmake-curses-gui

# установка Vulkan и слоев проверки, альтернатива - установка VulkanSDK с официального сайта
sudo apt-get install libvulkan-dev vulkan-ttols vulkan-validationlayers

# зависимости для vsgXcahnge
sudo apt-get install libassimp-dev libfreetype-dev libopenexr-dev

and for Arch Linux – so

sudo pacman -S git base-devel cmake 
sudo pacman -S vulkan-headers vulkan-validation-layers vulkan-tools 
sudo pacman -S assimp freetype openexr

Further, the actual assembly and installation of the entire economy in a directory allocated in the home directory

mkdir ${PROJECT_DIR}
cd ${PROJECT_DIR}

git clone https://github.com/vsg-dev/VulkanSceneGraph.git
git clone https://github.com/vsg-dev/vsgXchange.git
git clone https://github.com/vsg-dev/vsgExamples.git
git clone https://github.com/vsg-dev/vsgTutorial.git

cd VulkanSceneGraph
cmake . -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}
make -j 16
make install

cd ../vsgXchange
cmake . -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}
make -j 16
make install

cd ../vsgExamples
cmake . -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}
make -j 16
make install

Building under Windows with the MinGW compiler

I will not describe assembly with the MSVS compiler. Firstly, this is a trivial process, and secondly, in my practice I do not use the data compiler, and in every possible way I avoid situations when it may be required. Let me just say that for the assembly you need to download and install Vulkan SDK build from LunarG website. After that, using cmake-gui, a solution file for Visual Studio is generated and the assembly is carried out from this IDE.

In my case, the pain was that the Vulkan SDK was compiled with the MSVS compiler, and although VulkanSceneGraph was built, the implementations of the volcano functions were not statically included in libvsg.a and libvsgXchange.a, leading to their complete inoperability.

Attempts to build the Vulkan SDK from sources led me to the fact that the assembly stopped, because in its process ML – Microsoft’s macro assembler – suddenly began to be required. In an attempt to get a working assembly with the help of mingw, I tore out the libraries I needed from the packages MSYS2. I compiled this assembly in the required way, and posted it here. The contents of this archive need to be unpacked to a location convenient for you, and then set the following environment variables

Next, as in the case of Linux, we clone the VulkanSceneGraph repository. If you want to build the current stable version, switch to it

git checkout v1.0.9

Next, I used the MinGW 11.2 compiler from the Qt 6.3.1 kit, calling its build console and executing from it

E:\>cd work\3thparty\VSG\build

E:\work\3thparty\VSG\build>cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=E:\Apps\VSG-1.0.9 ..\VulkanSceneGraph
-- The CXX compiler identification is GNU 11.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Qt/Qt_6.3.1/Tools/mingw1120_64/bin/c++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Reading 'vsg_...' macros from E:/work/3thparty/VSG/VulkanSceneGraph/cmake/vsgMacros.cmake - look there for documentation
-- Found Vulkan: C:/VulkanSDK/1.3.261.1/lib/libvulkan-1.dll.a (found suitable version "1.3.261", minimum required is "1.1.70.0") found components: glslc glslangValidator
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE
-- Performing Test HAVE_CXX_ATOMIC_WITHOUT_LIB
-- Performing Test HAVE_CXX_ATOMIC_WITHOUT_LIB - Success
-- The following REQUIRED packages have been found:

 * Vulkan (required version >= 1.1.70.0)
 * Threads

-- Configuring done (2.5s)
-- Generating done (0.1s)
-- Build files have been written to: E:/work/3thparty/VSG/build

The cmake exhaust looked very reassuring, so

E:\work\3thparty\VSG\build>mingw32-make -j20

with long-awaited success

Next, install the assembled library in the habitat

E:\work\3thparty\VSG\build> mingw32-make install

Building vsgXchange, which is an adapter for loading various model and image formats as a dependency, requires assimp. This library is easily built from the mingw console, however, it is better to build it in the static library version, for static linking with vsgXchange. We will not dwell on vsgXchange in this publication – for our “Hello, world!” the VSG core will suffice.

Hello VulkanSceneGraph!!!

With a successful build and installation, and the correct environment setup, the code for this example is the same for both platforms. For Windows, you need to make sure that the environment variables for Vulkan are set, and in addition to this, the path to the vsg search script for the CMake build system (all sorts of problems under this Windows forever …)

I simplified the initial example to the maximum, without following the path of official documentation. The complete code will be like this

#include	"main.h"

//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
int main()
{
    // Создаем сцену, загружая модель чайника собственного формата VSG
    auto scene = vsg::read_cast<vsg::Node>("../data/models/teapot.vsgt");

    // Создаем вьювер
    auto viewer = vsg::Viewer::create();

    // Создаем объект, хранящий параметры окна приложения
    auto windowTraits = vsg::WindowTraits::create();
    windowTraits->width = 1024;
    windowTraits->height = 768;
    // Создаем окно приложения
    auto window = vsg::Window::create(windowTraits);
    // СВязываем вьювер с коном приложения
    viewer->addWindow(window);

    double radius = 1.0;
    double nearFarRatio = 0.00001;

    // Задаем начальное направление камеры
    auto lookAt = vsg::LookAt::create(vsg::dvec3(0.0, -radius * 4.5, 0.0),
                                      vsg::dvec3(0.0, 0.0, 0.0),
                                      vsg::dvec3(0.0, 0.0, 1.0));

    // Вычисляем параметры и создаем матрицу перспективной проекции
    double aspectRatio = static_cast<double>(window->extent2D().width) /
                         static_cast<double>(window->extent2D().height);

    auto perspective = vsg::Perspective::create(30.0,
                                                aspectRatio,
                                                nearFarRatio * radius,
                                                radius * 100.0);

    // Создаем камеру
    auto camera = vsg::Camera::create(perspective,
                                      lookAt,
                                      vsg::ViewportState::create(window->extent2D()));

    // Задаем обработчик закрытия окна
    viewer->addEventHandler(vsg::CloseHandler::create(viewer));
    // Задаем обработчик манипулятора камеры
    viewer->addEventHandler(vsg::Trackball::create(camera));

    // Создаем граф команд Vulkan для рендеринга сцены в заданое окно
    // определенной ранее камерой
    auto commandGraph = vsg::createCommandGraphForView(window, camera, scene);
    // Связываем граф команд с вьювером
    viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph});

    // Компилируем объекты Vulkan и передаем данные для рендеринга сцены
    viewer->compile();

    while (viewer->advanceToNextFrame()) // пока возможен переход к новому кадру
    {
        // Передаем события в обработчики, назначенные вьюверу
        viewer->handleEvents();
        // Обновляем граф сцены (добавление/удаление объектов, например)
        viewer->update();
        // Записываем команды в граф сцены и послылаем их в очередь Vulkan
        viewer->recordAndSubmit();
        // Ждем окончания рендеринга и передаем готовый кадр в буфер окна
        viewer->present();
    }

    return 0;
}

The main.h header file contains the inclusions of the VSG headers

#ifndef		MAIN_H
#define		MAIN_H

#include	<vsg/all.h>

#endif

To build this economy, we write a CMakeLists.txt script with the following content

cmake_minimum_required(VERSION 3.7)

# Задаем имя цели сборки (так будет именоваться исполняемый файл)
set(TARGET hello_world)

# Имя проекта совпадает с именем исполняемого файла
project(${TARGET})

# Путь к какталогу, куда помещается исполняемый файл после сборки
set(EXECUTABLE_OUTPUT_PATH "../../bin")

# Ищем в системе необходимые пакеты
find_package(vsg REQUIRED)

# Включаем в проект файлы исходников и заголовки по маске
file (GLOB INCLUDES "./include/*.h")
file (GLOB SOURCES "./src/*.cpp")

# Указывается, что собирается исполняемый файл с зананным именем
# из заданных файлов исходников
add_executable(${TARGET} ${SOURCES} ${INCLUDES})

# Задается каталог поиска заголовков
target_include_directories(${TARGET} PRIVATE ./include/)
# Задаются библиотеки, компонуемые с исполняемым файлом
target_link_libraries(hello_world vsg::vsg)

Considering that this example is part of a series of examples that I am going to write during the development of VSG, it is included as a subproject in a compound project, for which there is an appropriate CMakeLists.txt in the root folder with the example sources

# Устанавливаются минимальные требования по версии CMake
cmake_minimum_required(VERSION 3.7)

# Задаем имя основного проекта
project(VSG-lessons)

# Подключаем подпроекты
add_subdirectory(hello_world)

This example is publicly available in my repositories on Gitea.

By and large, a number of elementary actions are performed here.

  1. Creating a scene graph from this single node – the teapot. This model can be downloaded right here, it is presented in the original VSG *.vsgt format. There are also primitive other objects used in official examples.

  2. A viewer object is created – it is responsible, as in OpenSceneGraph, for rendering the scene and handling events

  3. The parameters of the application window are set – just like in OSG, the WindowTraits class is responsible for this, through which all the parameters for displaying the scene on the computer monitor are configured

  4. An application window object is created and passed to the viewer

  5. The camera is set up, the perspective projection matrix is ​​set, the area of ​​the window into which the rendering will be performed is determined.

  6. Sets the basic handlers for closing the window and the behavior of the camera manipulator

  7. A pipeline of Vulkan commands is created to draw this scene and communicates with the viewer

  8. Compiling all Vulkan objects (including shaders)

  9. The main rendering loop is organized. In OSG, this loop was reduced to calling viewer->run(), and the inside of it could be accessed through callback classes (although the loop could be expanded into separate calls). In VSG, this cycle is expanded into an explicit sequence of event handling calls, updating the scene graph, passing rendering commands to the Vulkan queue, and rendering the finished frame.

The result of this example is not very impressive.

however, like any basic example, it is extremely important for further understanding of all that remains to be studied.

Conclusion

The VulkanSceneGraph library is very young, less than a year has passed since its first release. However, its application, in my opinion, supported by the conviction of the developers, promises great prospects. In fact, this is the only open solution to date that makes it possible to use the power of the Vulkan API using a fairly versatile and extremely compact graphics library.

The conversation about VulkanSceneGraph will undoubtedly continue. This article is a pilot in my planned series of publications on this topic. In the meantime, thank you for your attention and…

To be continued..

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *