Real-time 3D graphics, ray tracing, mesh shaders and GPU compute written in TypeScript and compiled to native code. 112 graphics samples for D3D12 and Vulkan (and Vulkan on Android), built with tslang, a native TypeScript compiler (LLVM), on top of NVIDIA's Donut rendering framework and NVRHI.
The examples are ports of well-known samples from NVIDIA Donut-Samples, Khronos Vulkan-Samples, Microsoft DirectX-Graphics-Samples, Xbox ATG Samples and NVIDIA DXR Tutorials: hardware ray tracing (DXR, Vulkan ray tracing, ray queries, opacity micromaps, shader execution reordering), mesh and task shaders, work graphs, variable rate shading, async compute, bindless resources, HDR, order-independent transparency, tessellation, texture compression (BC, Basis Universal / KTX 2) and more. No JavaScript engine, no WebGPU: the TypeScript is compiled ahead of time to an object file and linked like C++.
TypeScript is a trademark of Microsoft; this project is not affiliated with or endorsed by Microsoft.
- Quick start
- Example gallery
- External assets
- Repository layout
- How the TypeScript build works
- Calling NVRHI directly
- Memory model
- Android
- Tests
- Credits
Requirements: Windows with Visual Studio 2022 (C++), CMake 3.26+ and Ninja (the TSLANG
language needs the Ninja generator), the Vulkan SDK
(VULKAN_SDK, for DXC), Python 3, and a D3D12 or Vulkan GPU (ray tracing and mesh shader examples
need hardware that supports them). tslang is found on the PATH or in tools/tslang; if it isn't
there, configure downloads the pinned release from GitHub. Build from a x64 Native Tools / Developer
PowerShell prompt so the MSVC linker is found:
git clone --recursive https://github.com/ASDAlexander77/native_ts_graphics.git
cd native_ts_graphics
cmake --preset default
cmake --build --preset default
release builds into build-release. The executables land in build/bin, next to their
shaders and media:
build/bin/basic_triangle.exe -dx12
build/bin/rt_particles.exe -vk
-dx12, -vk (and -dx11 where an example supports it) pick the graphics API, -debug enables
the debug and validation layers. dlss / release-dlss add DLSS to feature_demo.
Every example is a single TypeScript file in examples/ with its HLSL shaders in
shaders/. Click a screenshot to open its source. Screenshots are taken with
tools/capture_screenshots.ps1 on D3D12 (or Vulkan for Vulkan-only examples).
Ported from NVIDIA Donut-Samples.
![]() Basic Triangle Hello world: one vertex-colored triangle drawn with D3D12 or Vulkan. | ![]() Vertex Buffer Textured cubes drawn from vertex and index buffers. | ![]() Deferred Shading Donut's G-buffer fill and deferred lighting passes. |
![]() Threaded Rendering Sponza rendered into the six faces of a cube map, each face recorded on its own worker thread. | ![]() Async Compute A compute-queue worker thread animates textures that the render thread shows. | ![]() Meshlets Geometry drawn by mesh shaders from meshlets. |
![]() Shader Specializations Vulkan specialization constants picking shader variants. | ![]() Ray Traced Triangle The minimal hardware ray tracing pipeline (DXR / Vulkan RT). | ![]() Ray Traced Shadows Ray traced sun shadows on Sponza. |
![]() Ray Traced Reflections Ray traced shadows and reflections on Sponza (D3D12). | ![]() Bindless Ray Tracing Bindless ray tracing of an animated scene, by pipeline or inline ray queries. | ![]() Ray Traced Particles Billboard and intersection particles traced with ray queries and multi-layer alpha blending. |
![]() Bindless Rendering Sponza drawn with no vertex buffers bound: vertices and textures fetched bindlessly. | ![]() Variable Rate Shading Sponza with a compute-generated shading rate surface. | ![]() Work Graphs Deferred shading driven by a D3D12 work graph (shader model 6.8). |
![]() Feature Demo Donut's full renderer: deferred/forward, cascaded shadows, SSAO, TAA, bloom, tone mapping, light probes. | Headless Compute only, no window: runs a compute shader and checks its result (no screenshot). |
Ported from Khronos Vulkan-Samples.
![]() Compute N-Body An N-body gravity simulation in compute shaders, drawn as additive sprites. | ![]() Terrain Tessellation A heightmap terrain of patches tessellated by their size on screen. | ![]() Instancing Thousands of rocks around a planet in one instanced draw. |
![]() HDR A reflective object in an HDR environment with exposure and two-pass bloom. | ![]() Ray Tracing Extended Sponza ray traced with shadows, AO, a refracting sheet and per-frame BLAS/TLAS updates. | ![]() Ray Tracing Invocation Reorder Shader execution reordering of primary rays (SM 6.9 hit objects / SPV_NV_shader_invocation_reorder). |
![]() Ray Tracing Position Fetch Closest-hit shaders reading vertex positions from the acceleration structure (Vulkan). | ![]() Ray Tracing Reflection Cubes between two mirrors, reflection rays bounced up to 64 times. | ![]() Ray Queries Rasterized Sponza shaded by inline ray queries: ambient occlusion and shadows. |
![]() Multi-Draw Indirect 225 meshes in one indirect draw, culled on the CPU or in a compute shader. | ![]() Async Compute Bloom A 4K HDR forward pass with bloom computed on the async compute queue. | ![]() OIT Linked Lists Order-independent transparency with per-pixel fragment linked lists. |
![]() OIT Depth Peeling Order-independent transparency by depth peeling, up to 8 layers. | ![]() Geometry Shader to Mesh Shader Normals drawn by a geometry shader or by an equivalent mesh shader. | ![]() Mesh Shading A single mesh shader workgroup outputs a triangle. |
![]() Mesh Shader Culling Task (amplification) shaders cull meshlets before mesh shading. | ![]() Sparse Image A huge texture whose pages are mapped and streamed in as the view needs them. | ![]() Conservative Rasterization A low-resolution triangle with and without conservative rasterization. |
![]() Conditional Rendering glTF nodes drawn or skipped by GPU predication. | ![]() Rasterization Order Attachment Access Programmable blending ordered by rasterizer ordered views / fragment shader interlock. | ![]() Fragment Shader Barycentric Pixel shaders reading barycentrics and per-vertex attributes. |
![]() 16-bit Arithmetic A compute shader rendering blobs in FP32 or native FP16. | ![]() Compute Shader Derivatives Derivatives in compute shaders (SM 6.6) to find a pattern's edges. | ![]() Logic Op Dynamic State A cube combined with the background by a framebuffer logic operation. |
![]() Render Octomap A SLAM map as instanced voxels, a glTF mesh and Gaussian splats. | ![]() MSAA Multisample anti-aliasing with color and depth resolves. | ![]() Dynamic Blending Blend state, advanced blend operations and color write masks switched live. |
![]() Texture Mipmap Generation A mip chain generated on the GPU, compared with no mips and anisotropic filtering. | ![]() Fragment Shading Rate Variable rate shading from a CPU-made shading rate image. | ![]() Dynamic Fragment Shading Rate A shading rate image computed each frame from the scene's frequency content. |
![]() Texture Compression Comparison Sponza with Basis Universal KTX 2 textures transcoded at load into BC7, BC3 or RGBA8. | ![]() Basis Universal Textures UASTC and ETC1S KTX 2 images transcoded on the CPU, timed. | ![]() Shader Quad Control Pixel shaders running in full quads with quad operations. |
![]() Dynamic Line Rasterization Rectangular, Bresenham and smooth lines with width and stipple. | ![]() Extended Dynamic State 2 Depth bias, rasterizer discard and primitive restart switched live. | ![]() Memory Budget Instanced rocks with the memory heaps' usage and budget. |
![]() Mobile NeRF Neural radiance fields baked into meshes, decoded by a small MLP in the pixel shader. | ![]() Mobile NeRF Ray Query MobileNeRF models found by inline ray queries instead of rasterization. | ![]() Dynamic Primitive Clipping User clip distances (SV_ClipDistance) and depth clipping. |
![]() Timestamp Queries GPU time of each render pass measured with timer queries. | ![]() Patch Control Points Tessellated wireframe terrain with dynamic patch control points. | ![]() Descriptor Indexing Bindless texture arrays: non-uniform indexing and update-after-bind. |
![]() Timeline Semaphore Conway's Game of Life on the compute queue, drawn by the graphics queue. | ![]() Color Write Enable Per-target color write enables combined into one image. | ![]() Dynamic Multisample Rasterization The Space Module scene drawn with a sample count picked at run time. |
![]() Vertex Dynamic State Two vertex layouts drawn with dynamic vertex input. | ![]() Dynamic Rendering A reflective geosphere in a skybox, rendered without render pass objects. | ![]() Graphics Pipeline Library Teapots, each with its own pipeline and a random lighting model; "Add pipeline" adds more. |
![]() Dynamic Uniform Buffers 125 cubes with model matrices in slices of one uniform buffer. | ![]() Texture Loading A mipmapped texture with an adjustable level-of-detail bias. | ![]() Separate Image Sampler A sampler bound apart from its image, linear or nearest filtering. |
![]() Dynamic Rendering Local Read Deferred shading with 64 point lights, then forward-rendered glass. | ![]() Shader Object Vertex, geometry and fragment shaders combined at draw time, then post-processed. | ![]() Calibrated Timestamps The HDR scene with pieces of its code timed by the GPU clock through calibrated timestamps. |
![]() Host Image Copy A textured cube whose texture the CPU writes straight into the image, no staging buffer. | ![]() Buffer Device Address 64 meshes animated and drawn through GPU pointers alone, no descriptors (Vulkan only). | ![]() Shader debugprintf A vertex shader printing positions with printf, read back by the validation layer and shown in the UI (Vulkan only, with the Vulkan SDK). |
![]() Push Descriptors Two crates whose descriptors are pushed into the command buffer with each draw, no descriptor sets (Vulkan only). | ![]() Descriptor Buffers The same crates, their descriptor sets in buffers the CPU writes descriptors into, no pools (Vulkan only). |
Ported from Microsoft DirectX-Graphics-Samples.
![]() Raytracing Procedural Geometry Analytic shapes, metaballs, SDFs and a fractal intersected by intersection shaders. | ![]() Raytracing Opacity Micromaps An alpha-tested tree whose leaves skip the any-hit shader thanks to opacity micromaps (DXR 1.2). | ![]() SM6 Wave Intrinsics Wave intrinsics visualized per pixel: lane indices, reductions, prefix sums, quad shuffles. |
![]() Depth Bounds Test A triangle clipped by an animated depth bounds test. | ![]() Predication Queries A quad drawn only if last frame's occlusion query saw it. | ![]() Small Resources 77 small textures placed at 4 KB alignment vs committed at 64 KB. |
![]() Meshlet Render The Stanford dragon drawn by a mesh shader alone, each meshlet in its own color. | ![]() Meshlet Cull Amplification shaders cull meshlets by frustum and normal cone; a picked meshlet shows its bounding sphere and cone. | ![]() Meshlet Instancing A cube of toy robots drawn by a mesh shader, several instances of each last meshlet packed into one group. |
![]() Dynamic LOD Amplification shaders cull instances and pick one of six levels of detail by screen size. | ![]() Execute Indirect A compute shader culls 1024 indirect draw commands; the draw takes their count from a buffer. | ![]() Dynamic Indexing 120 cities, each draw picking its material from an array of 120 textures. |
![]() HDR Display SDR and HDR gradients and the Rec.709 / Rec.2020 gamuts on 8-bit sRGB, 10-bit ST.2084 or 16-bit scRGB swap chains. | ![]() n-Body Gravity 10,000 particles all pulling on each other: a compute shader on the async compute queue, drawn as geometry shader sprites. | ![]() Denoised RTAO Ray traced AO at 1 ray per pixel, denoised spatio-temporally (temporal reprojection, a-trous filter) over PBRT models and animated grass. |
![]() Multithreading A room of 1025 draws lit by three moving spotlights with PCF shadows, its shadow and scene passes recorded on three worker threads. | ![]() Bundles 30 cities drawn with two alternating pipeline states, from draws recorded once and replayed every frame (D3D12 bundles). | ![]() Raytracing Simple Lighting A ray traced cube lit by a circling point light, the closest hit shader reading its 16-bit indices and vertex normals. |
![]() Reserved Resources One level of a tiled texture at a time mapped to memory, picked with the arrow keys. | ![]() Raytracing Library Subobjects A ray traced cube whose root signatures, hit group and configs are defined in its HLSL (D3D12 only). | ![]() Generic Programs Two triangles drawn with generic programs: graphics pipelines made as state objects (D3D12 only). |
![]() Sakura Forest SER A ray traced sakura forest whose primary rays go through hit objects and Shader Execution Reordering (D3D12 only). | ![]() Memory Management Images streamed a mip level at a time under the memory budget: tiled resources, MakeResident / Evict, a paging thread (DirectX 12 only). |
Ported from Microsoft Xbox-ATG-Samples.
![]() Fast Block Compress BC1, BC3 and BC5 texture compression at run time in compute shaders or on the CPU. | ![]() Simple HDR HDR10 output (Rec.2020, ST.2084) with blocks brighter than paper white. | ![]() Bokeh Depth of field with bokeh sprites expanded by a geometry shader. |
![]() Raytracing AO Ray traced ambient occlusion side by side with MiniEngine's SSAO. | ![]() Simple PBR Physically based rendering with image based lighting and ACES tone mapping. | ![]() Simple Compute A Mandelbrot fractal by a compute shader, synchronous or on the async compute queue. |
![]() Simple Bezier A Mobius strip of Bezier patches tessellated by hull and domain shaders. | ![]() Mouse Cursor Absolute, relative (captured) and custom mouse cursor modes. | ![]() Collision DirectXMath collision tests between frustums, boxes, spheres, triangles and rays. |
![]() Video Texture A video decoded by Media Foundation into a texture on a spinning cube. |
Ported from NVIDIA DxrTutorials.
![]() DXR Refit Rotating triangles with shadow rays; the top-level acceleration structure refitted every frame. |
![]() Compute N-Body Collisions compute_nbody with a third compute pass: particles bounce off each other. | ![]() Environment Map Sponza under an environment map with planar shadow maps, DDS or KTX 2. |
Some examples render scenes too large for this repository. Point CMake at a checkout of the original samples' assets and they are copied (or converted to DDS) next to the executables at build time:
| CMake variable | What it is | Examples that need it |
|---|---|---|
DONUT_SAMPLES_MEDIA_DIR |
Donut-Samples media folder (glTF-Sample-Assets, fonts) |
Sponza scenes, feature_demo, the ATG ports' text |
VULKAN_SAMPLES_ASSETS_DIR |
Khronos Vulkan-Samples assets folder |
most Vulkan-Samples ports |
XBOX_ATG_SAMPLES_DIR |
Xbox-ATG-Samples checkout | fast_block_compress, bokeh, raytracing_ao, simple_pbr, mouse_cursor, video_texture |
DIRECTX_GRAPHICS_SAMPLES_DIR |
DirectX-Graphics-Samples checkout | opacity_micromaps, meshlet_render, meshlet_cull, meshlet_instancing, dynamic_lod, dynamic_indexing, denoised_rtao, multithreading, bundles, sakura_forest_ser (a checkout from August 2025 or later), memory_management (its TileGrid.dds; without it, only the generated images) |
cmake --preset default -DVULKAN_SAMPLES_ASSETS_DIR=E:/Vulkan-Samples/assets
Without those checkouts, tools/fetch_media.py gets just the files the build reads (see
Media without the sample checkouts).
native_ts_graphics/
├── CMakeLists.txt # the TSLANG language, Donut, and one add_tslang_example() per example
├── CMakePresets.json # default, release, dlss, android-x86_64, android-arm64, android-arm32
├── cmake/ # CMake language support for .ts (CMakeDetermineTSLANGCompiler.cmake, ...)
├── core/ # donut.ts (TypeScript classes) over donut_interop.cpp (flat C API over Donut);
│ # nvrhi.ts: NVRHI's interfaces, called directly
├── examples/ # one .ts file per example
├── shaders/ # HLSL shaders and ShaderMake configs, compiled to DXIL, DXBC and SPIR-V
├── media/ # small assets shipped with the repository
├── patches/ # local patches to Donut, NVRHI and GLFW
├── tools/ # Android build/test script, smoke test, screenshot capture, asset tools
└── external/Donut # NVIDIA Donut (submodule)
The project registers a CMake language (TSLANG) that:
- owns its own source extension (
.ts), - is built with tslang --emit=obj,
- produces
.objfiles that CMake links automatically alongside regular C++.
CMake then handles dependency tracking and incremental builds for .ts sources the same way it
does for .cpp.
enable_language(TSLANG)runsCMakeDetermineTSLANGCompiler.cmake, which findstslang, then loadsCMakeTSLANGInformation.cmake, which registers the compile rule (CMAKE_TSLANG_COMPILE_OBJECT— tslang --emit=obj).- Because
CMAKE_TSLANG_SOURCE_FILE_EXTENSIONScontainsts, any.tssource is routed to tslang and compiled to a.obj. - That
.objis added to the target and linked with the C++ objects using the C++ linker (CMAKE_TSLANG_LINK_EXECUTABLE). - Change one example's
.tsand only it recompiles. - tslang can't write a depfile, so the files a
.tsimports or references aren't tracked for it: list them as itsOBJECT_DEPENDS, asCMakeLists.txtdoes forcore/.
The examples can also run under tslang's JIT, against the shared build of the interop layer
(run_jit.bat examples/<name>.ts [-- args] does this from the repo root):
tslang --emit=jit --shared-libs=build/bin/donut_interop.dll examples/basic_triangle.ts -- -dx12
- Program arguments go after
--. - The JIT caches each compiled module in a
__jitfolder next to its source (ignored by git). A module is compiled again when it, a file it imports, its options or tslang change. Needs pre-alpha94 or later: pre-alpha93's cache crashed a run that compiled two or more modules (TypeScriptCompiler#548). - The shared library also holds the C++ modules some examples link (
core/sdkmesh.cpp,core/meshlet_model.cpp,core/paged_images.cpp, ...), and applies the D3D12 Agility SDK runtime frombin/D3D12(the executables that need it opt in through their exports, whichtslang.exedoesn't have).
set(CMAKE_TSLANG_FLAGS "--opt_level=3") # global
set_source_files_properties(mycode.ts PROPERTIES
COMPILE_OPTIONS "--define;TSLANG=1") # per-fileIf you don't need a first-class language, either:
- Mark a file as an already-built object and just link it:
set_source_files_properties(mycode.obj PROPERTIES EXTERNAL_OBJECT TRUE GENERATED TRUE)and add it toadd_executable, producing it withadd_custom_command; or - Compile a differently-named file as C++:
set_source_files_properties(mycode.tslang PROPERTIES LANGUAGE CXX).
Use the TSLANG language setup when .ts is a source type you compile often and want CMake to
treat as first-class.
The examples reach Donut and NVRHI through core/donut_interop.cpp, a flat C API. core/nvrhi.ts
skips it for NVRHI's interfaces: nvrhi.IDevice, ICommandList, IBuffer, ITexture and every
other nvrhi::I* class are tslang @cpp classes, laid over the C++ objects. A method call goes
through the C++ object's own vtable. Donut's handles are the same objects' pointers, so the two mix:
<nvrhi.IBuffer><Opaque>bufferHandle and back.
The vtables and some struct layouts depend on the C++ ABI, so the nvrhi namespace is four
modules, as tslang's default library is lib.ts and lib.<os>.ts:
| Module | Holds |
|---|---|
core/nvrhi_types.ts |
the constants, and the records methods return, the same for every ABI |
core/nvrhi_classes.win32.ts, .linux.ts |
the classes and the structs they take for each ABI, generated by tools/gen_nvrhi_classes.py |
core/nvrhi_abi.win32.ts |
debug names: std::strings made as MSVC x64's release CRT lays them out |
core/nvrhi_abi.linux.ts |
the same for libc++ (Android's clang, x86-64 and AArch64) |
core/nvrhi.ts |
the functions; it imports ./nvrhi_abi (which imports ./nvrhi_classes), which tslang resolves to the target's files |
An example imports ../core/nvrhi and lists ${NVRHI_TS_SOURCES} in its add_tslang_example
sources. This needs a tslang newer than pre-alpha99, whose tsbindgen lays out std::string and
static_vector fields.
import { nvrhi } from "../core/nvrhi";
const device = <nvrhi.IDevice>app.getNvrhiDevice();
const commandList = nvrhi.createCommandList(device, nvrhi.commandListParameters());
if (commandList) {
commandList.open();
nvrhi.setComputeState(commandList, nvrhi.computeState(pipeline, bindingSet));
commandList.dispatch(1, 1, 1);
commandList.close();
nvrhi.executeCommandList(device, commandList);
commandList.Release();
}- Generated classes.
python tools/gen_nvrhi_classes.pywritescore/nvrhi_classes.<os>.tswith tsbindgen (shipped with tslang), which takes each method's@slotfrom clang's vtable layout for the ABI: MSVC x64, and Android's clang (it generates x86-64 and AArch64 and checks they agree but for plainchar's sign; needs the Android NDK). Run it after updating NVRHI or tslang, and commit the output; don't edit it. tsbindgen also lays out every struct the classes take, in the ABI's layout:BufferDesc'sdebugNameis the C++ library'sstd::string(basic_string_char),ComputeState'sbindingsastatic_vector8<IBindingSet | null>(its elements, thencurrent_size),ComputePipelineDesc'sCSaRefCountPtr<IShader>. The script writes a template's specializations, which tsbindgen lays out one by one, as one generic tuple:vector<T>(each C++ library'sstd::vector),static_vector8<T>andstatic_vector16<T>(tslang has no type of N elements),RefCountPtr<T>. Those are nested tuples, so a literal nests too (nvrhi.computeState()). Astd::vectorfield (BindingLayoutDesc's andBindingSetDesc's items) points at a tslang array's elements, for the call only:nvrhi.stdVector()makes it, asnvrhi.createBindingLayout()andcreateBindingSet()do. It needs tsbindgen pre-alpha102 or newer, which binds the methods taking a struct by value too. - Signatures. An object returned by value is a tuple with its layout:
RefCountPtr<T>(@cppnontrivial, as it has a destructor) andnvrhi::Object. tslang passes the hidden pointer each ABI returns it through;nvrhi.createBuffer()and the other functions unwrap it. A struct passed by reference is aRefto a tuple with the C++ layout (BufferDesc,TextureDesc,ComputePipelineDesc,ComputeState, ...). That layout depends on the C++ build (std::string's on the CRT's debug or release build), sonvrhi.layoutMatches()compares the tuples' sizes with the C++ ones. A struct passed by value is its tuple, which tslang passes as each ABI does: MSVC a struct of 8 bytes in a register and a bigger one by a pointer to a copy, the Itanium ABI one of up to 16 bytes in registers (theTextureSubresourceSetofclearTextureFloat,clearDepthStencilTexture,clearTextureUInt,beginTrackingTextureState,setTextureStateandgetNativeView, thenvrhi::ObjectofcreateHandleForNativeTexture,createHandleForNativeBufferandcreateSamplerFeedbackForNativeTexture); and so is a small struct returned in registers (queryCoopVecMatMulFormatSupport's threebools). - Callbacks.
nvrhi.IMessageCallbackgoes the other way: a@cppclass extending it whosemessage()has a body is an object NVRHI calls, through the vtable tslang makes for it. Pass it toDonut_SetNvrhiMessageCallbackbefore creating the app, and keep it in a global while the device lives.Donut_LogNvrhiMessagepasses a message on to Donut's logging.nvrhi_directdoes this. - Platform.
nvrhi.supported()says whether the build has one of the two ABIs (MSVC x64, Android's clang on x86-64 or AArch64) and their layouts: code that also runs elsewhere keeps another path.
headless calls NVRHI directly where nvrhi.supported(), as the C++ sample does, and through
Donut's wrappers elsewhere or with -donut. CTest runs both: headless.<api> and
headless.donut.<api>.
nvrhi_direct is a headless test of every bound method, not a sample; on Android it runs through
adb shell, as headless does. One command list:
- uploads numbers into a buffer and clears another;
- runs a compute shader over the uploaded numbers, its shader (from the bytecode ShaderMake compiled), binding layout, binding set and pipeline made directly;
- copies the three into a readback buffer, the last copy with hand-made barriers;
- clears a texture, then draws a triangle into its left half with a graphics pipeline made directly (a framebuffer over the texture, vertex and pixel shaders, the render state with no depth test and no culling, a viewport and its scissor rectangle);
- draws an indexed quad into its top right quarter from a vertex buffer (an input layout of
position and texture coordinates), sampling a 2 x 2 texture uploaded with
writeTexturethrough a point sampler, scaled by a constant buffer: one texel a pixel. Its bottom right quarter is as cleared; - copies the texture into another, and that into a staging texture (two of
copyTexture's overloads), which it maps to read the pixels back, row by row (mapStagingTexture's row pitch).
A second command list tests the depth test: a framebuffer of a color and a D32 depth texture, the
depth cleared to 1 (clearDepthStencilTexture), and the default render state (Less, depth writes).
One draw of a near quad over the left half, then a far triangle over the whole texture: the left half
keeps the near one's color. A third tests the blend state: a half transparent color drawn over a
cleared texture, alpha blended (SrcAlpha, InvSrcAlpha) into one half and added (One, One) into the
other under a write mask of red and blue. A fourth sets push constants (a binding layout and set
of 16 bytes for the pixel shader) before each of two draws, a color for each half. A fifth draws
indirectly (drawIndirect), from arguments the command list writes into a buffer: from offset
16 into one half, and from offset 0, no instance, into the other, which stays as cleared. A sixth
dispatches the compute pipeline indirectly (dispatchIndirect) the same way: from offset 12 into
one output buffer, and from offset 0, no group, into another, which stays as cleared. A seventh
uploads a texture through a staging texture the CPU writes (mapStagingTexture for writing,
copyTexture's third overload) and reads it back, pixel for pixel. An eighth draws into two
render targets at once (a framebuffer of two color textures, SV_Target0 and SV_Target1). A
ninth draws a triangle into a texture of 4 samples a pixel, its edge through the centers of the
diagonal's pixels, and resolves it (resolveTexture): the diagonal half covered, as 2 of the
standard sample positions are. A tenth times its work with a timer query (beginTimerQuery,
endTimerQuery, getTimerQueryTime) and waits for it with an event query (setEventQuery,
waitEventQuery) instead of waitForIdle. An eleventh tests the stencil test: a D32S8 texture's
stencil cleared to 0 (clearDepthStencilTexture), set to 1 over one half by a draw writing no
color, then a draw over everything where the stencil is 1, which colors that half only. A
twelfth draws two instances of a quad in one draw, their offsets and colors instance attributes
(isInstanced) from a vertex buffer: one in each half. A thirteenth has a compute shader write
every pixel of a texture through a UAV (isUAV, a Texture_UAV binding) and reads it back. A
fourteenth has one read a structured buffer (structStride) into a raw buffer (canHaveRawViews).
A fifteenth fills each mip level of each array slice of a texture array with writeTexture, reads
each back through a staging texture mapped a subresource at a time, and copies one mip level of one
array slice into a region of another texture.
Then one test for each of these, each with a command list of its own but the format query:
clearTextureUInt: an R32_UINT texture with UAVs cleared whole, then its second mip level alone, and read back exactly;- a texture's states tracked by hand, with automatic barriers off: a texture with no initial state,
beginTrackingTextureState,setTextureStateandcommitBarriersaround a clear,setPermanentTextureState, andgetTextureSubresourceStateafter each; - the same for a buffer:
beginTrackingBufferState,setBufferStatearound a write and a copy,setPermanentBufferState,getBufferState; - resources placed in heaps: a virtual buffer and texture (
isVirtual), their memory requirements (aMemoryRequirementsreturned by value),createHeapandIHeap::getDesc,bindBufferMemoryat an offset andbindTextureMemory; - indexed indirect draws (
drawIndexedIndirect), as the indirect draws above, through an index buffer; queryFormatSupportof a color, a depth and an integer format;- two command lists in one
executeCommandLists, run in order; - a command list lifetime tracker (
createCommandListLifetimeTracker,CommandListParameters::lifetimeTracker,runGarbageCollection); - shader specialization (
createShaderSpecialization, Vulkan only, asqueryFeatureSupportsays): a compute shader's constant set to another value; - a bindless descriptor table:
createBindlessLayout,createDescriptorTable,resizeDescriptorTable,writeDescriptorTable,getBindlessDesc,getFirstDescriptorIndexInHeap, and a compute pipeline of a binding layout and the bindless layout reading the table's buffers by index; - native views (
getNativeView: a view per subresource set, kept); - handles of API objects (
createHandleForNativeTexture,createHandleForNativeBuffer), written through and read back through the original handles; queryMemoryRequirementsof a buffer (NVRHI reports buffers', not textures');- queries reset (
resetEventQuery,resetTimerQuery) and used again; - the compute queue (it asks Donut for one, and does without where the device has none): a
dispatch there, the graphics queue waiting for it (
queueWaitForCommandList); - UAV barriers turned off (
setEnableUavBarriersForBuffer,setEnableUavBarriersForTexture); - a binding set's resources put in their states at once (
setResourceStatesForBindingSet); - indexed indirect draws with a count from a buffer (
drawIndexedIndirectCount, where the device counts them: D3D12, Vulkan'sdrawIndirectCount); - mesh shaders, where the device has them: both of
createMeshletPipeline's overloads,setMeshletState,dispatchMesh,dispatchMeshIndirectanddispatchMeshIndirectCount, a quarter each; - a tiled texture, where the device has them:
getTextureTiling, one tile mapped byupdateTextureTileMappings, cleared and read back; - sampler feedback, where the device has it (D3D12):
createSamplerFeedbackTextureandcreateSamplerFeedbackForNativeTexture,clearSamplerFeedbackTexture,setSamplerFeedbackTextureState, a pixel shader writing feedback of two mip levels, anddecodeSamplerFeedbackTextureinto a buffer read back; - ray tracing, where the device has it: a bottom level acceleration structure of a triangle
(
createAccelStruct,buildBottomLevelAccelStruct,compactBottomLevelAccelStructs,getDeviceAddress); three top level ones, built from anInstanceDesc(buildTopLevelAccelStruct), copied (copyRaytracingAccelerationStructure), and placed in a heap (queryTopLevelAccelStructPrebuildInfo,getAccelStructMemoryRequirements,bindAccelStructMemory) and built from a buffer of instances (buildTopLevelAccelStructFromBuffer); a shader library (createShaderLibrary,getShader), a ray tracing pipeline and shader tables with a ray generation, miss, hit group and callable entry (and theirclear*methods);setRayTracingStateanddispatchRaysthrough each top level structure; - opacity micromaps, where the device has them: an array of two (
createOpacityMicromap,buildOpacityMicromap), one transparent and one opaque, in a bottom level structure of two triangles traced through: the transparent one's rays miss; - cooperative vectors: the queries (
queryCoopVecFeatures,queryCoopVecMatMulFormatSupport,queryCoopVecTrainingFormatSupport,getCoopVecMatrixSize), and where the device has them a matrix converted to the inferencing-optimal layout and back (convertCoopVecMatrices); - ray tracing clusters, where the device has them (Vulkan's
VK_NV_cluster_acceleration_structure; D3D12's need NVAPI):getClusterOperationSizeInfoandexecuteMultiIndirectClusterOperationgetting a cluster acceleration structure's size from indirect arguments.
The tests of what a device may not have say "not tested" when it doesn't. Headless devices are
made with the core features windowed ones are (Vulkan's drawIndirectCount and sparse residency
among them), and with Vulkan's cooperative vectors, and opacity micromaps with ray tracing.
isAftermathEnabled and getAftermathCrashDumpHelper are checked with the device.
It checks what it reads. On the way it also checks:
- the graphics API, the native objects and queues;
- reference counting, the descs NVRHI keeps and the struct layouts, and debug and entry point
names, which it reads from NVRHI's
std::strings byte by byte (nvrhi.stdStringIs); - NVRHI's messages, which go through an
IMessageCallbackimplemented in tslang. With-debug, it provokes a validation error and checks that NVRHI called the callback.
The default library is compiled separately for each memory model, and a program has to link the build matching the model it was compiled with. One variable drives both:
cmake --preset default -DTSLANG_MEMORY_MODEL=rc
It selects defaultlib/lib/<target>/<debug|release>/<model> (on Windows
defaultlib/lib/x86_64/pc/windows/msvc/release/gc, as tslang --print-default-lib-dir=lib names
it) as the link directory and adds -mm=<model> to the compile flags, so the two cannot disagree.
Valid values are gc (default), rc and none; only gc links Boehm.
The android-x86_64 (emulator), android-arm64 (devices) and android-arm32 (32-bit devices,
armeabi-v7a) presets cross-compile with the Android NDK for API 29, Vulkan only. tools/android.py builds them, starts an emulator, and runs
and tests the examples on it; what it does by hand is under Without the script.
-
the Android NDK (r30 is the one tested), in
ANDROID_NDK_HOME; -
the Android SDK, in
ANDROID_HOME(or next to the NDK): platform-tools, build-tools, platform 29, and for an emulator the emulator, an x86_64 system image and a virtual device:sdkmanager "platform-tools" "build-tools;35.0.0" "platforms;android-29" "emulator" "system-images;android-34;google_apis;x86_64" avdmanager create avd -n api34_x86_64 -k "system-images;android-34;google_apis;x86_64"The API 34 Google APIs image also runs arm64 apps, translated (simple ones:
feature_democrashes the emulator); -
a JDK (17 or later) for signing, in
JAVA_HOME(or injdk/next to the SDK); -
Python 3, and the Vulkan SDK (
VULKAN_SDK) for DXC; -
tslang with its Android libraries,
<tslang>/android/<abi>/lib.
Shaders are compiled at build time by ShaderMake, which has to run on this machine, so the
Android build takes the desktop build's build-release/bin/ShaderMake.exe (or
DONUT_HOST_SHADERMAKE). The script builds it if it isn't there, in the Visual Studio
environment.
python tools/android.py build
python tools/android.py emulator
python tools/android.py test
buildconfiguresbuild-android-x86_64if it isn't, and builds the headless tests (headless,nvrhi_direct) and every example's APK,bin/<example>.apk; name examples to build only theirs (build basic_triangle vertex_buffer), and--abi arm64builds for devices.emulatorstarts the newest virtual device (--avd <name>picks one,--no-windowhides it) unless a device is connected, and waits for it to boot.adb emu killstops it.testruns the headless tests, then installs and starts each example: it has to be running after 20 s (--seconds), and to close without a crash on Back. Screenshots and logs go tobuild-android-x86_64/android-test. With no examples named, it tests the ones that run on the emulator: 65 of them (EMULATOR_EXAMPLESintools/android.py). Its Vulkan is the host GPU's, through the emulator's graphics passthrough, which exposes fewer extensions than the GPU has, so the others, which need ray tracing, mesh shaders, variable rate shading, conditional rendering, push descriptors, descriptor buffers, sparse or reserved resources and the like, stop at once with a message saying what the device lacks (testreports them as failures; they are for devices).ctestruns CTest's Vulkan tests on the device: the headless tests, then each example's-test(with its CTest arguments, passed in the activity'sargsextra), PASSED when it prints "Test PASSED" and exits with 0, SKIPPED when it exits with 77 (the device lacks a feature it needs, seeunsupportedincore/test_check.ts). Name regular expressions to run only those (ctest bokeh "^simple_");--timeoutis each test's limit (300 s). Logs go tobuild-android-x86_64/android-ctest. On the emulator started without its window, Vulkan is llvmpipe (Mesa's software renderer): it divides exactly where GPUs approximate, which a few checks allow for (simple_compute).run <example>installs and starts one and follows its log (Ctrl+C stops following);--screenshot shot.pngtakes a screenshot instead.headlessruns the headless tests alone (headless,nvrhi_direct, throughadb shell).
test, run and headless start an emulator themselves if no device is connected; with more
than one, pass --serial. On a phone: enable USB debugging, build --abi arm64, and
test --abi arm64 <examples>.
--abi arm32 builds armeabi-v7a. It needs a tslang release carrying the armeabi-v7a collector,
async runtime and default library (android/armeabi-v7a/lib, defaultlib/lib/arm). It has no
headless or nvrhi_direct: tslang cannot call C++ vtables that return structs on 32-bit ARM, so
core/nvrhi.ts is not built there (the other examples go through Donut's wrappers). Donut needs
Vulkan 1.3, which many 32-bit-only devices lack (a Galaxy Tab A 10.1 reports 1.1: the app starts,
creates its Vulkan instance and stops there). So far it has only been built and started on that
device: no frame has been rendered on 32-bit ARM, and only basic_triangle has been built.
Each example is an app, <example>_apk (not built by default): bin/<example>.apk, a
NativeActivity running lib<example>.so (the example with core/android_main.cpp), with its
shaders and the media its add_tslang_example(<example> MEDIA ...) lists (the files and folders
of media/ it reads), signed with a debug key made in the build folder (an app installed from
another build folder has to be uninstalled first; the script does that). Packaging
(tools/package_apk.py) needs the SDK's build tools and platform 29 (ANDROID_HOME, or the SDK
next to the NDK, or ANDROID_SDK_DIR), a JDK (JAVA_HOME, or ANDROID_JAVA_HOME) and Python.
rt_reflections has no APK that builds: it is D3D12-only.
The window is the activity's, through GLFW's Android platform
(patches/glfw-android-platform.patch): touch drives the mouse cursor and left button, Back is
Escape, and the display density is the content scale. What the examples print goes to logcat
under their name. An example runs once per process: Back, or the activity leaving the screen,
closes it and the process exits. Their shaders and media are extracted into the app's storage on
the first start after an install.
The default library comes from tslang's tree for the Android triple; the collector and the async
runtime from <tslang>/android/<abi>/lib (TSLANG_ANDROID_LIB_DIR).
With ANDROID_NDK_HOME set, and adb and emulator (the SDK's platform-tools and emulator)
on the PATH:
cmake --preset android-x86_64
cmake --build --preset android-x86_64 --target headless basic_triangle_apk
emulator -avd api34_x86_64 -no-audio -no-boot-anim
adb wait-for-device
adb install -r build-android-x86_64/bin/basic_triangle.apk
adb shell am start -n org.native_ts_graphics.basic_triangle/android.app.NativeActivity
adb logcat -s basic_triangle DEBUG
adb exec-out screencap -p > shot.png
adb shell am force-stop org.native_ts_graphics.basic_triangle
headless needs no window and runs as a plain executable, next to its shaders as in bin/:
adb shell mkdir -p /data/local/tmp/native_ts_graphics/shaders
adb push build-android-x86_64/bin/headless /data/local/tmp/native_ts_graphics/
adb push build-android-x86_64/bin/shaders/framework build-android-x86_64/bin/shaders/headless /data/local/tmp/native_ts_graphics/shaders/
adb shell "cd /data/local/tmp/native_ts_graphics && chmod +x headless && ./headless"
It prints Test PASSED. A native crash shows in logcat under DEBUG; the APKs hold a stripped
library, and the build folder's android/<example>/lib<example>.so keeps the symbols for
ndk-stack -sym build-android-x86_64/android/<example>.
Examples with a test mode run headless under CTest, on D3D12 and Vulkan with the debug layers:
ctest --test-dir build
ctest --test-dir build -R meshlet_render -L vk --output-on-failure
<example> -test creates the device without a window, renders a fixed number of frames at a fixed
time step into offscreen back buffers, reads the last one back and checks it (the clear color where
nothing is drawn, where the geometry lands, what it is shaded with), printing Test PASSED or
Test FAILED: <why>; -testOutput <file.png> also saves that frame. A test fails on a non-zero
exit code or an error line from Donut, NVRHI or the API's validation layer. headless (and
headless.donut, its Donut path) and nvrhi_direct are tests too. The checks share
core/test_check.ts (TestCheck: the frame and GPU buffers read back, corners, color counts); some
read the example's own results too, as compute_nbody_collisions.head_on does: two heavy centers
meeting head on, whose bounce keeps the momentum only if the compute passes are separated by UAV
barriers (patches/nvrhi-uav-barriers-same-binding-set.patch). An example that needs a feature
the device lacks (ray tracing, mesh shaders, sparse resources...) prints Test SKIPPED: <why> and
exits with 77, which CTest reports as skipped (unsupported in core/test_check.ts).
The tests also run without a GPU, much more slowly, and software renderers are stricter than GPU
drivers about what the spec leaves undefined (lavapipe found bokeh's viewport index past the
pipeline's viewport count, patches/nvrhi-vulkan-viewport-count.patch):
- D3D11 and D3D12: set
DONUT_SOFTWARE_ADAPTER=1and the device is created on DXGI's software adapter, WARP (Microsoft Basic Render Driver), DXR included. - Vulkan: point the loader at Mesa's lavapipe (for instance from
mesa-dist-win) with
VK_DRIVER_FILES=<mesa>\x64\lvp_icd.x86_64.json.
$env:DONUT_SOFTWARE_ADAPTER = "1"; ctest --test-dir build -L dx12
What a software renderer can't do is reported as skipped; where it rounds or divides otherwise than
GPUs, a few checks allow for it (simple_compute).
tools/fetch_media.py <folder> fetches just the files the build reads from the external sample
repositories (External assets) as sparse clones at the commits in
tools/media.json, Git LFS objects included, and prints the -D options pointing CMake at them;
tools/media_manifest.py [build folder] regenerates media.json from a configured build after
what the build copies changes. Donut-Samples' media (DONUT_SAMPLES_MEDIA_DIR) isn't in it.
Two scripts open real windows instead:
pwsh -File tools/smoke_examples.ps1 [-Examples basic_triangle,compute_nbody] [-Apis dx12,vk]
pwsh -File tools/capture_screenshots.ps1 [-Examples basic_triangle,compute_nbody] [-Api dx12]
smoke_examples.ps1 starts each built example on each API with the debug layers and reports
crashes and validation errors. capture_screenshots.ps1 regenerates the gallery images in
docs/screenshots. Both open real windows: leave the desktop alone while they run, as an example
renders only while its window has focus.
- NVIDIA Donut, NVRHI and Donut-Samples
- Khronos Vulkan-Samples
- Microsoft DirectX-Graphics-Samples and Xbox-ATG-Samples
- NVIDIA DXR Tutorials
- tslang, the TypeScript native compiler
Each port keeps the original sample's algorithm and look; see the comment above its
add_tslang_example in CMakeLists.txt for what differs.














































































































