This is a complete yet minimal cross-platform virtual texturing library with a reference sample app.
Virtual texturing is a technique for viewing textures which are larger than can fit in memory. These mega textures are mipmapped and each mipmap level is tiled. Each tile (also called a page) is stored on disk as a separate image file. Tiles are then loaded on-demand based on the current view. Rendering tiles is done in the main thread, with loading and decompressing of pages done asynchronously on one or two background threads, leading to smooth panning and zooming of these big textures.
This work is an OpenGLES-specific and modernized fork of LibVT plus a cross-platform sample app. Special thanks to Julian Mayer for his original implementation and thorough documentation.
Goals:
-
Provide a minimal example of virtual texturing to help in learning the algorithm and to serve as a building block for other projects.
-
Enable cross platform native builds on Mac, Windows, and Linux, and a WebGL build via Emscripten. To this end, C++ and OpenGLES were chosen from the start.
-
Supply a reference sample that demonstrates the LibVT calls necessary to load and render a virtual texture, while utilizing SDL for cross-platform windowing and event handling.
Also, see below for a comparison to shlomnissan's virtual-textures, a similar project but written in modern C++ and OpenGL 4.1.
The sample shows rendering of a test virtual texture to a simple quad. A traditional static texture is also drawn underneath to show how to mix regular rendering with LibVT rendering.
Controls:
Zoom: Mouse wheel, or ',' / '.' keys
Pan: Arrow keys
Orbit: Left mouse button drag
Reset: 'R' key
Quit: 'ESC' key
make builds everything: the libvt library, the native sample, and the web
(Emscripten) target. make clean cleans all of them. The web target is skipped
with a notice if the Emscripten SDK is not installed, so the native workflow
works on its own — see Web (Emscripten) for the web setup.
Install dependencies for your platform.
Install Homebrew if you don't have it, then SDL2 and SDL2_image:
brew install SDL2
brew install SDL2_imageBuild and run:
git clone https://github.com/erik-larsen/hello-vt.git
cd hello-vt
make
cd sample
./bin/mac-x86_64/hello-vtor
./bin/mac-arm64/hello-vtdepending on your Mac.
Setup clang compilation on Windows. First, install MSYS from cmd.exe:
winget install MSYS2.MSYS2
setx PATH "%PATH%C:\msys64\clang64\bin"Then run from MSYS2 CLANG64 shell:
pacman -Syu
pacman -S git
pacman -S base-devel mingw-w64-clang-x86_64-toolchain
pacman -S mingw-w64-clang-x86_64-SDL2
pacman -S mingw-w64-clang-x86_64-SDL2_imageThen to build and run (also from MSYS2 CLANG64 shell):
git clone https://github.com/erik-larsen/hello-vt.git
cd hello-vt
make
cd sample
./bin/win-x86_64/hello-vt.exeThis was tested on Debian 11.3 and Ubuntu 24.04:
sudo apt update
sudo apt install git
sudo apt install build-essential clang
sudo apt install libsdl2-dev libsdl2-image-devThen to build and run:
git clone https://github.com/erik-larsen/hello-vt.git
cd hello-vt
make
cd sample
./bin/linux-x86_64/hello-vtInstall and activate the Emscripten SDK:
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
Then build and run:
git clone https://github.com/erik-larsen/hello-vt.git
cd hello-vt
make web
python3 scripts/serve.py
and open http://localhost:8000/bin/web/hello-vt.html in your browser.
make web (also part of plain make) uses emcc from PATH if the SDK is
activated; otherwise it tries to activate emsdk from $EMSDK (default
~/Github/emsdk) automatically, e.g. make web EMSDK=/path/to/emsdk. The
underlying makefile can also be invoked directly:
make -f Makefile.emscripten.
Unlike the native builds, the web build does not read tiles from disk. Tiles are
fetched on demand from the web server via synchronous emscripten_fetch() calls
made on the loader thread (a web worker), so the streaming pipeline is identical
to the native build. Only the small static background texture is embedded in the
preloaded data file.
Note: the page must be served cross-origin isolated (with
Cross-Origin-Opener-Policy: same-origin and
Cross-Origin-Embedder-Policy: require-corp headers), because Emscripten
pthreads require SharedArrayBuffer. The included scripts/serve.py does this;
a plain python3 -m http.server will not work.
GitHub Pages cannot send custom response headers, so the build embeds
coi-serviceworker: a service
worker that injects the COOP/COEP headers client-side (the first visit reloads
once while it installs). The tile store URL is resolved relative to the page, so
project-site subpaths (https://user.github.io/repo/...) work without changes.
Caveat: the service-worker workaround does not function where service workers are
unavailable (e.g. some private-browsing modes); the page shows an explanatory
error in that case.
The included workflow at .github/workflows/deploy-pages.yml builds the web
target with Emscripten on every push to main and deploys it, so the generated
.wasm/.js/.data files never need to be committed (they are gitignored). It
uploads the whole sample/ directory as the Pages artifact, which contains both
the freshly built bin/web/ and the committed uv-test-8kx8k/ tile store. To
enable it, set Settings → Pages → Build and deployment → Source to
GitHub Actions; the demo then lives at
https://<user>.github.io/<repo>/bin/web/hello-vt.html.
(If you would rather not use Actions, you can instead set the Pages source to
"Deploy from a branch" at the repository root and commit the built bin/web/
output yourself; everything is then served from its repo path under sample/.)
Many virtual texturing implementations have been available in C, C++ and JS, using OpenGL, Direct3D, and WebGL. However, for the purposes of this project they are either not well-documented, not minimal, not cross-platform, or not straightforward to build due to age (most date from 2010 or earlier). Of note, OpenSeaDragon is an excellent implementation of virtual texturing but is not a good fit for this project because it is not C++ and not minimal.
Instead, LibVT was chosen for its C++ implementation, OpenGLES code path, and decent documentation. Fixes were made to LibVT to get it running again 15 years later, and to remove code not on the OpenGLES code path. Further, an SDL-based sample app with a pre-processed test image is provided to demonstrate LibVT. (Note: At one time LibVT provided its own pre-built sample, but the link is dead and not saved on archive.org).
For an overview of LibVT's multithreading implementation, see the LibVT readme.
- Code minimized to OpenGLES2 / WebGL1
- Synchronous framebuffer readback
- Decompress png/jpg files only, using stb
- No texture compression
- Multithreading (main, loader, and decompression threads)
- PNG sample
- JPG sample
- Emscripten build, with tiles from server
- Visualize virtual and physical textures for debugging
- Auto configure LibVT based on input image
- Add OpenGLES3 / WebGL2 code path
- Async + double-buffered PBO readback
- ETC2 GPU texture compression (guaranteed in WebGL2)
- Anisotropic texture filtering (via GL_EXT_texture_filter_anisotropic)
- Maybe use faster, browser-standard decompression libs, libjpeg-turbo and libpng
- Maybe handle more image tile formats (any requests?)
- Erik Larsen (LibVT fork and sample app)
- Julian Mayer (original LibVT author)
Another concise, cross-platform virtual texturing project is shlomnissan / virtual-textures. Here are the pros and cons of each project:
Pros:
- Targets OpenGLES2 — runs on mobile, older hardware, WebGL
- C API library (LibVT prefix) — stable ABI, easy FFI, embeddable in any language
- Clean separation between VT system and windowing/rendering — drop into existing projects
- Makefile build — explicit, transparent, easy to translate to other build systems
- Production-grade threading: dedicated persistent loader and decompressor threads with condition variables
- Three-stage pipeline (disk I/O → decompress → GPU upload) allows overlapping work
Cons:
- Older C++ style — more verbose, harder to read for those used to modern C++
- Manual dependency management via system packages (brew, apt, pacman)
- Inherited complexity from 2010-era LibVT codebase
- Less readable as a learning resource
Pros:
- Modern C++23 — clean, expressive, idiomatic code
- CMake + vcpkg — declarative dependencies, reproducible builds, single command setup
- Excellent clarity — easy to follow end-to-end - better for learning
- Modern OpenGL 4.1 with explicit mip calculation (dFdx/dFdy, textureGrad) - better if targeting desktop
- Has async loading via std::jthread
- Includes debug overlay (minimap showing page residency and feedback) - better for learning
Cons:
- Less modular: VT logic coupled to GLFW, ImGui, app-specific classes
- Would require refactoring to extract and embed elsewhere
- Spawns new thread per load request (overhead)
- Requires OpenGL 4.1 — won't run on mobile/WebGL
