Collection of C++ libraries for drawing 2D graphics, rendering text and more.
Xcikit contains basic elements needed for creating 2D graphical applications. The focus is on text rendering and closely related UI rendering. Additional features are a custom scripting language and data serialization.
With xcikit you can:
-
render a few paragraphs of text,
-
style some parts of the text differently (colored highlights),
-
respond to mouse hover, click on the highlighted parts (spans),
-
create a basic UI with buttons, checkboxes, combo-boxes,
-
support scripting, provide sandboxed API for user scripts.
This should be enough for a program to render:
-
2D sprites,
-
menu,
-
settings screen,
-
dialogs.
The library uses GLFW and Vulkan for graphics.
Note that xcikit is still under development and mostly experimental. There is no stable API. There are no releases. The features below are partially planned, but already implemented to some degree.
The planned features:
-
GPU oriented 2D graphics
-
advanced text rendering
-
UI widgets (not meant to replace Qt, but should be good enough for indie games)
-
data file management (VFS)
-
custom scripting language (it wasn’t planned originally, but it’s fun to design and implement)
-
support library (logging, eventloop etc.)
The features are divided into a set of libraries:
-
xci-widgets
-
xci-text
-
xci-graphics
-
xci-script
-
xci-data
-
xci-core
The separation of the code into libraries helps to create layers and avoid bi-directional dependencies. Basically, each library can depend only on the libraries listed below it. (TODO: draw a dependency graph)
The top-level xci-widgets
provides UI components with event processing.
It uses text layout engine xci-text
, which can also be used separately.
All rendering goes through GLFW and Vulkan based xci-graphics
library.
Graphics lib can be used separately when neither UI nor text rendering
is needed.
Optional xci-data
library provides custom text and binary format
for serialization / deserialization of hierarchical data structures.
Think of Json and Google Protobuf, but without the complexity and bloat.
All the above libraries use xci-core
, which contains miscellaneous utilities,
This can also be used separately.
There is also header-only compat
library, which is used to hide
differences between compilers and OS’s. It does not implement any
abstraction layer, but just provides what is available elsewhere.
Technologies:
-
C++20 as main programming language
-
CMake as build system
-
Conan as dependency manager
Supported compilers:
-
GCC >= 10
-
Clang >= 15
-
Xcode >= 14
-
Visual Studio >= 16
Any Unix-like OS with C++20 compliant compiler should work. Windows OS is also supported, to some extent. See Building on Windows.
Experimental scripting language with bytecode interpreter.
Docs:
# Compile and run a script
fire examples/script/some.fire
# Compile script to bytecode
fire -c examples/script/some.fire --output-schema /tmp/firm.schema
# Inspect binary format with the compiled bytecode
dati examples/script/some.firm -s /tmp/firm.schema
# Execute compiled bytecode
fire examples/script/some.firm
Virtual file system. Unified reading of files in filesystem directories and in archive files. Contains support for custom archive format - DAR.
Parse and dump config file. The format is custom:
bool_item false // true/false
int_item 1
float_item 2.3
string_item "abc\n" // quotes are required, supports C-style escape sequences
group {
value 1
subgroup { foo 42; bar "baz" } // semicolon is used as a delimiter for multiple items on same line
}
Core utilities. These have little or no dependencies. Mostly just stdlib + OS API.
-
Buffer
(types.h
) - Owned blob of data, with deleter. -
FpsCounter
- Tracks delays between frames and computes frame rate. -
Logger
(log.h
) - Logging functions. -
SharedLibrary
- Thin wrapper around dlopen. For plugins. -
TermCtl
- Colored output for ANSI terminals. -
Vfs
- Unified reading of regular files and archives. Mount the archive to virtual path and read contained files in same fashion as regular files. -
bit.h
- custombit_copy
,bit_read
, similar to C++20bit_cast
-
event.h
- System event loop (abstraction of kqueue / epoll). -
dispatch.h
- Watch files and notify on changes. Useful for auto-reloading of resource files. -
file.h
- Read whole files. Path utilities (dirname, basename, …). -
format.h
- Formatted strings. Similar to Python’sformat()
. -
rtti.h
- C++ demangled type names. -
string.h
- String manipulation, unicode utilities. -
sys.h
- A replacement forstd::this_thread::get_id()
, providing the canonical TID.
Included are some tools build on top of the libraries. Check them on separate pages:
-
-
Find File (ff) -
find
alternative
-
TL;DR:
-
Run one of the system package manager commands below.
-
Proceed to Using build script below.
Tools:
-
CMake - build system
-
Conan - optional package manager
Libraries:
-
PEGTL (xci-core)
-
libzip (xci-vfs)
-
FreeType (xci-text)
-
GLFW, Vulkan (xci-graphics)
-
glslc or glslangValidator (xci-graphics)
-
Catch2 (tests)
-
Google Benchmark (benchmarks)
Obtaining dependencies:
-
Install them into system or otherwise make them visible to CMake’s
find_*
functions.-
This works for almost all deps, except fmt, pfr, magic_enum.
-
Deps that must be installed this way: libzip, hyperscan.
-
-
Build deps from Git locally for this project
-
Run
./build_deps.sh
, it will clone each repo and build it via CMake to./.deps
-
The build script picks up these deps as if they were installed in the system
-
-
Install them via Conan. See How to build from IDE or build with
build.sh
which runs conan automatically.-
Conan can be skipped via
--no-conan-deps
(if you have all deps preinstalled in system or in./.deps
)
-
Installing the dependencies with system package managers:
-
Debian:
apt install libzip-dev cmake ninja-build pip3 install conan
-
macOS (Homebrew):
# Tools brew install cmake ninja ccache pip3 install conan # Libs brew install pegtl libzip freetype sdl2 doxygen catch2 google-benchmark hyperscan
-
macOS (MacPorts):
# Tools port install cmake ninja ccache pip3 install conan # Libs port install vulkan-* MoltenVK libsdl2 harfbuzz-devel hyperscan libfmt catch2
The complete build process is handled by a build script:
./build.sh
When finished, you’ll find the temporary build files in build/
and installation artifacts in artifacts/
.
Both scripts are incremental, so it’s safe to run them repeatably. They do only the required work and re-use what was done previously.
Detailed build steps (these are examples, adjust parameters as needed):
# Prepare build directory
mkdir build && cd build
# Install dependencies using Conan.
conan install .. --build missing
# Configure
cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX=~/sdk/xcikit
# Run CMake UI to adjust the parameters
ccmake ..
# Build
cmake --build .
# Run unit tests
cmake --build . --target 'test'
# Install artifacts
cmake --build . --target 'install'
Using MacPorts, install Clang 14 and libc++:
port install clang-14 macports-libcxx
Then create a clang14-toolchain.cmake
file with content like this:
set(CMAKE_CXX_COMPILER /opt/local/bin/clang++-mp-14)
add_compile_options(-nostdinc++ -I/opt/local/include/libcxx/v1 -D_LIBCPP_DISABLE_AVAILABILITY)
add_link_options(-L/opt/local/lib/libcxx)
Run the build with the toolchain:
./build.sh --toolchain clang14-toolchain.cmake
Almost everything is portable and should work:
-
build scripts (using git-bash)
-
dependencies via Conan
-
build with CMake + ninja + cl.exe
-
all libraries, examples, tests
What doesn’t work:
-
find_file (ff) tool - it’s built on low-level unix APIs, probably unportable
-
(Optional) Enable Developer mode to obtain ability to create symlinks
-
Install Git Bash and run it
-
We need these commands to work:
-
git
(to clone the project) -
cmake
,ninja
(build tools) -
conan 1.x
(C++ package manager) -
cl
(VS compiler)
-
-
Beginning from the last one:
-
Find
vcvars64.bat
in Visual Studio installation. I have Visual Studio Build Tools 2019 and it’s here:"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
(The directory slightly differs for Community and Professional edition.) -
Paste the path including the quotes into Git Bash and execute it.
-
Now we should have
cl
,cmake
andninja
working. -
(For convenience, add Git Bash + VS configuration to Windows Terminal.)
-
-
Install conan:
pip install conan
(Python should also work via VS developer tools) -
Run
./build.sh
Note that the build script detects "MINGW64_NT" as target platform, but this is not true. It builds native Windows binaries.
The string is just the output of uname
in Git Bash. I don’t know any better way how to detect host platform on Windows (please tell me if you know).
Works with Clion, and may work with Visual Studio (I did not test).
Separately, clone this fork of cmake-conan
which adds CONAN_OPTIONS
: https://github.com/rbrich/cmake-conan
Then open the xcikit project, let the IDE load the top-level CMakeLists.txt
.
Add these CMake options in the IDE settings:
-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=/path/to/cmake-conan/conan_provider.cmake -DCONAN_OPTIONS="-o;xcikit/*:system_sdl=True;-o;xcikit/*:system_vulkan=True;-o;xcikit/*:system_freetype=True;-o;xcikit/*:system_harfbuzz=True;-o;xcikit/*:system_zlib=True"
The CONAN_OPTIONS
are just an example, in this case Conan will skip installing graphical libs,
so they can be searched in the system instead. Those libs must be preinstalled.
If linking tests fails with unresolved symbols from catch2 (error LNK2019: unresolved external symbol …
), it’s because Conan picked incompatible package.
To avoid issues like this, it’s best to completely disable the compatibility extension.
There are multiple ways how to disable it. One way is to edit cppstd_compat.py
and make it always return []
. Do not forget to also remove header comments,
so Conan doesn’t destroy your changes.
The non-graphical components should build with Emscripten.
Install and link Emscripten so that this command works: emcc -v
Create a Conan profile for Emscripten, for example:
[settings]
os=Emscripten
arch=wasm
compiler=clang
compiler.version=14
compiler.libcxx=libc++
compiler.cppstd=20
build_type=MinSizeRel
[options]
[build_requires]
[conf]
# Find actual path Emscripten installation (or check CMake command line, when called with emcmake)
tools.cmake.cmaketoolchain:user_toolchain = [".../cmake/Modules/Platform/Emscripten.cmake"]
[env]
# Same as above, for older packages which do not use tools.cmake.CMakeToolchain
CONAN_CMAKE_TOOLCHAIN_FILE = .../cmake/Modules/Platform/Emscripten.cmake
CXXFLAGS=-flto=thin
LDFLAGS=-flto=thin
See a working example of such profile in the docker/emscripten directory.
Run the build (only 'core' and 'script' components work at this time):
./build.sh --debug --emscripten --profile emscripten core script
The defaults in the Conan recipe (conanfile.py
) are meant to make it easier for consumers (conan install
) than packagers. That means, the tests and examples are built and packaged by default. Add the following options to build a package with only the development libs:
conan create . --build=missing -o xcikit:tools=False -o xcikit:examples=False -o xcikit:tests=False -o xcikit:benchmarks=False
Build and install XCI libraries (see "How to build" above),
then use installed xcikit-config.cmake
in your project’s
CMakeLists.txt
:
cmake_minimum_required(VERSION 3.16)
project(example CXX)
find_package(xcikit REQUIRED)
add_executable(example src/main.cpp)
target_link_libraries(example xci-widgets)
In the case xcikit was installed into non-standard location,
for example ~/sdk/xcikit
, you need to set up CMAKE_PREFIX_PATH
appropriately:
cmake -DCMAKE_PREFIX_PATH="~/sdk/xcikit" ..
Add xcikit as dependency to conanfile.txt
:
[requires] xcikit/0.1@rbrich/stable [generators] cmake_paths
Then include generated conan_paths.cmake
from project’s CMakeLists.txt
:
if (EXISTS ${CMAKE_BINARY_DIR}/conan_paths.cmake)
include(${CMAKE_BINARY_DIR}/conan_paths.cmake)
endif()
Now find xcikit
in usual way:
find_package(xcikit CONFIG REQUIRED)
Optionally, include XCI goodies:
include(XciBuildOptions)
Link with the libraries:
target_link_libraries(example xcikit::xci-text xcikit::xci-graphics)