Skip to content

About

112 DirectX 12 and Vulkan graphics samples written in TypeScript and compiled to native code with tslang: ray tracing, denoised RTAO, mesh shaders, compute, HDR, multithreading, Android

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Native TypeScript Graphics: DirectX 12 and Vulkan samples in TypeScript

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++.

Ray traced particles rendered by a TypeScript program with DirectX 12 ray queries

TypeScript is a trademark of Microsoft; this project is not affiliated with or endorsed by Microsoft.

Contents

Quick start

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.

Example gallery

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).

Donut-Samples ports

Ported from NVIDIA Donut-Samples.

Basic Triangle (TypeScript, DirectX 12 / Vulkan). Hello world: one vertex-colored triangle drawn with D3D12 or Vulkan
Basic Triangle
Hello world: one vertex-colored triangle drawn with D3D12 or Vulkan.
Vertex Buffer (TypeScript, DirectX 12 / Vulkan). Textured cubes drawn from vertex and index buffers
Vertex Buffer
Textured cubes drawn from vertex and index buffers.
Deferred Shading (TypeScript, DirectX 12 / Vulkan). Donut's G-buffer fill and deferred lighting passes
Deferred Shading
Donut's G-buffer fill and deferred lighting passes.
Threaded Rendering (TypeScript, DirectX 12 / Vulkan). Sponza rendered into the six faces of a cube map, each face recorded on its own worker thread
Threaded Rendering
Sponza rendered into the six faces of a cube map, each face recorded on its own worker thread.
Async Compute (TypeScript, DirectX 12 / Vulkan). A compute-queue worker thread animates textures that the render thread shows
Async Compute
A compute-queue worker thread animates textures that the render thread shows.
Meshlets (TypeScript, DirectX 12 / Vulkan). Geometry drawn by mesh shaders from meshlets
Meshlets
Geometry drawn by mesh shaders from meshlets.
Shader Specializations (TypeScript, DirectX 12 / Vulkan). Vulkan specialization constants picking shader variants
Shader Specializations
Vulkan specialization constants picking shader variants.
Ray Traced Triangle (TypeScript, DirectX 12 / Vulkan). The minimal hardware ray tracing pipeline (DXR / Vulkan RT)
Ray Traced Triangle
The minimal hardware ray tracing pipeline (DXR / Vulkan RT).
Ray Traced Shadows (TypeScript, DirectX 12 / Vulkan). Ray traced sun shadows on Sponza
Ray Traced Shadows
Ray traced sun shadows on Sponza.
Ray Traced Reflections (TypeScript, DirectX 12 / Vulkan). Ray traced shadows and reflections on Sponza (D3D12)
Ray Traced Reflections
Ray traced shadows and reflections on Sponza (D3D12).
Bindless Ray Tracing (TypeScript, DirectX 12 / Vulkan). Bindless ray tracing of an animated scene, by pipeline or inline ray queries
Bindless Ray Tracing
Bindless ray tracing of an animated scene, by pipeline or inline ray queries.
Ray Traced Particles (TypeScript, DirectX 12 / Vulkan). Billboard and intersection particles traced with ray queries and multi-layer alpha blending
Ray Traced Particles
Billboard and intersection particles traced with ray queries and multi-layer alpha blending.
Bindless Rendering (TypeScript, DirectX 12 / Vulkan). Sponza drawn with no vertex buffers bound: vertices and textures fetched bindlessly
Bindless Rendering
Sponza drawn with no vertex buffers bound: vertices and textures fetched bindlessly.
Variable Rate Shading (TypeScript, DirectX 12 / Vulkan). Sponza with a compute-generated shading rate surface
Variable Rate Shading
Sponza with a compute-generated shading rate surface.
Work Graphs (TypeScript, DirectX 12 / Vulkan). Deferred shading driven by a D3D12 work graph (shader model 6.8)
Work Graphs
Deferred shading driven by a D3D12 work graph (shader model 6.8).
Feature Demo (TypeScript, DirectX 12 / Vulkan). Donut's full renderer: deferred/forward, cascaded shadows, SSAO, TAA, bloom, tone mapping, light probes
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).

Vulkan-Samples ports

Ported from Khronos Vulkan-Samples.

Compute N-Body (TypeScript, DirectX 12 / Vulkan). An N-body gravity simulation in compute shaders, drawn as additive sprites
Compute N-Body
An N-body gravity simulation in compute shaders, drawn as additive sprites.
Terrain Tessellation (TypeScript, DirectX 12 / Vulkan). A heightmap terrain of patches tessellated by their size on screen
Terrain Tessellation
A heightmap terrain of patches tessellated by their size on screen.
Instancing (TypeScript, DirectX 12 / Vulkan). Thousands of rocks around a planet in one instanced draw
Instancing
Thousands of rocks around a planet in one instanced draw.
HDR (TypeScript, DirectX 12 / Vulkan). A reflective object in an HDR environment with exposure and two-pass bloom
HDR
A reflective object in an HDR environment with exposure and two-pass bloom.
Ray Tracing Extended (TypeScript, DirectX 12 / Vulkan). Sponza ray traced with shadows, AO, a refracting sheet and per-frame BLAS/TLAS updates
Ray Tracing Extended
Sponza ray traced with shadows, AO, a refracting sheet and per-frame BLAS/TLAS updates.
Ray Tracing Invocation Reorder (TypeScript, DirectX 12 / Vulkan). Shader execution reordering of primary rays (SM 6.9 hit objects / SPV_NV_shader_invocation_reorder)
Ray Tracing Invocation Reorder
Shader execution reordering of primary rays (SM 6.9 hit objects / SPV_NV_shader_invocation_reorder).
Ray Tracing Position Fetch (TypeScript, DirectX 12 / Vulkan). Closest-hit shaders reading vertex positions from the acceleration structure (Vulkan)
Ray Tracing Position Fetch
Closest-hit shaders reading vertex positions from the acceleration structure (Vulkan).
Ray Tracing Reflection (TypeScript, DirectX 12 / Vulkan). Cubes between two mirrors, reflection rays bounced up to 64 times
Ray Tracing Reflection
Cubes between two mirrors, reflection rays bounced up to 64 times.
Ray Queries (TypeScript, DirectX 12 / Vulkan). Rasterized Sponza shaded by inline ray queries: ambient occlusion and shadows
Ray Queries
Rasterized Sponza shaded by inline ray queries: ambient occlusion and shadows.
Multi-Draw Indirect (TypeScript, DirectX 12 / Vulkan). 225 meshes in one indirect draw, culled on the CPU or in a compute shader
Multi-Draw Indirect
225 meshes in one indirect draw, culled on the CPU or in a compute shader.
Async Compute Bloom (TypeScript, DirectX 12 / Vulkan). A 4K HDR forward pass with bloom computed on the async compute queue
Async Compute Bloom
A 4K HDR forward pass with bloom computed on the async compute queue.
OIT Linked Lists (TypeScript, DirectX 12 / Vulkan). Order-independent transparency with per-pixel fragment linked lists
OIT Linked Lists
Order-independent transparency with per-pixel fragment linked lists.
OIT Depth Peeling (TypeScript, DirectX 12 / Vulkan). Order-independent transparency by depth peeling, up to 8 layers
OIT Depth Peeling
Order-independent transparency by depth peeling, up to 8 layers.
Geometry Shader to Mesh Shader (TypeScript, DirectX 12 / Vulkan). Normals drawn by a geometry shader or by an equivalent mesh shader
Geometry Shader to Mesh Shader
Normals drawn by a geometry shader or by an equivalent mesh shader.
Mesh Shading (TypeScript, DirectX 12 / Vulkan). A single mesh shader workgroup outputs a triangle
Mesh Shading
A single mesh shader workgroup outputs a triangle.
Mesh Shader Culling (TypeScript, DirectX 12 / Vulkan). Task (amplification) shaders cull meshlets before mesh shading
Mesh Shader Culling
Task (amplification) shaders cull meshlets before mesh shading.
Sparse Image (TypeScript, DirectX 12 / Vulkan). A huge texture whose pages are mapped and streamed in as the view needs them
Sparse Image
A huge texture whose pages are mapped and streamed in as the view needs them.
Conservative Rasterization (TypeScript, DirectX 12 / Vulkan). A low-resolution triangle with and without conservative rasterization
Conservative Rasterization
A low-resolution triangle with and without conservative rasterization.
Conditional Rendering (TypeScript, DirectX 12 / Vulkan). glTF nodes drawn or skipped by GPU predication
Conditional Rendering
glTF nodes drawn or skipped by GPU predication.
Rasterization Order Attachment Access (TypeScript, DirectX 12 / Vulkan). Programmable blending ordered by rasterizer ordered views / fragment shader interlock
Rasterization Order Attachment Access
Programmable blending ordered by rasterizer ordered views / fragment shader interlock.
Fragment Shader Barycentric (TypeScript, DirectX 12 / Vulkan). Pixel shaders reading barycentrics and per-vertex attributes
Fragment Shader Barycentric
Pixel shaders reading barycentrics and per-vertex attributes.
16-bit Arithmetic (TypeScript, DirectX 12 / Vulkan). A compute shader rendering blobs in FP32 or native FP16
16-bit Arithmetic
A compute shader rendering blobs in FP32 or native FP16.
Compute Shader Derivatives (TypeScript, DirectX 12 / Vulkan). Derivatives in compute shaders (SM 6.6) to find a pattern's edges
Compute Shader Derivatives
Derivatives in compute shaders (SM 6.6) to find a pattern's edges.
Logic Op Dynamic State (TypeScript, DirectX 12 / Vulkan). A cube combined with the background by a framebuffer logic operation
Logic Op Dynamic State
A cube combined with the background by a framebuffer logic operation.
Render Octomap (TypeScript, DirectX 12 / Vulkan). A SLAM map as instanced voxels, a glTF mesh and Gaussian splats
Render Octomap
A SLAM map as instanced voxels, a glTF mesh and Gaussian splats.
MSAA (TypeScript, DirectX 12 / Vulkan). Multisample anti-aliasing with color and depth resolves
MSAA
Multisample anti-aliasing with color and depth resolves.
Dynamic Blending (TypeScript, DirectX 12 / Vulkan). Blend state, advanced blend operations and color write masks switched live
Dynamic Blending
Blend state, advanced blend operations and color write masks switched live.
Texture Mipmap Generation (TypeScript, DirectX 12 / Vulkan). A mip chain generated on the GPU, compared with no mips and anisotropic filtering
Texture Mipmap Generation
A mip chain generated on the GPU, compared with no mips and anisotropic filtering.
Fragment Shading Rate (TypeScript, DirectX 12 / Vulkan). Variable rate shading from a CPU-made shading rate image
Fragment Shading Rate
Variable rate shading from a CPU-made shading rate image.
Dynamic Fragment Shading Rate (TypeScript, DirectX 12 / Vulkan). A shading rate image computed each frame from the scene's frequency content
Dynamic Fragment Shading Rate
A shading rate image computed each frame from the scene's frequency content.
Texture Compression Comparison (TypeScript, DirectX 12 / Vulkan). Sponza with Basis Universal KTX 2 textures transcoded at load into BC7, BC3 or RGBA8
Texture Compression Comparison
Sponza with Basis Universal KTX 2 textures transcoded at load into BC7, BC3 or RGBA8.
Basis Universal Textures (TypeScript, DirectX 12 / Vulkan). UASTC and ETC1S KTX 2 images transcoded on the CPU, timed
Basis Universal Textures
UASTC and ETC1S KTX 2 images transcoded on the CPU, timed.
Shader Quad Control (TypeScript, DirectX 12 / Vulkan). Pixel shaders running in full quads with quad operations
Shader Quad Control
Pixel shaders running in full quads with quad operations.
Dynamic Line Rasterization (TypeScript, DirectX 12 / Vulkan). Rectangular, Bresenham and smooth lines with width and stipple
Dynamic Line Rasterization
Rectangular, Bresenham and smooth lines with width and stipple.
Extended Dynamic State 2 (TypeScript, DirectX 12 / Vulkan). Depth bias, rasterizer discard and primitive restart switched live
Extended Dynamic State 2
Depth bias, rasterizer discard and primitive restart switched live.
Memory Budget (TypeScript, DirectX 12 / Vulkan). Instanced rocks with the memory heaps' usage and budget
Memory Budget
Instanced rocks with the memory heaps' usage and budget.
Mobile NeRF (TypeScript, DirectX 12 / Vulkan). Neural radiance fields baked into meshes, decoded by a small MLP in the pixel shader
Mobile NeRF
Neural radiance fields baked into meshes, decoded by a small MLP in the pixel shader.
Mobile NeRF Ray Query (TypeScript, DirectX 12 / Vulkan). MobileNeRF models found by inline ray queries instead of rasterization
Mobile NeRF Ray Query
MobileNeRF models found by inline ray queries instead of rasterization.
Dynamic Primitive Clipping (TypeScript, DirectX 12 / Vulkan). User clip distances (SV_ClipDistance) and depth clipping
Dynamic Primitive Clipping
User clip distances (SV_ClipDistance) and depth clipping.
Timestamp Queries (TypeScript, DirectX 12 / Vulkan). GPU time of each render pass measured with timer queries
Timestamp Queries
GPU time of each render pass measured with timer queries.
Patch Control Points (TypeScript, DirectX 12 / Vulkan). Tessellated wireframe terrain with dynamic patch control points
Patch Control Points
Tessellated wireframe terrain with dynamic patch control points.
Descriptor Indexing (TypeScript, DirectX 12 / Vulkan). Bindless texture arrays: non-uniform indexing and update-after-bind
Descriptor Indexing
Bindless texture arrays: non-uniform indexing and update-after-bind.
Timeline Semaphore (TypeScript, DirectX 12 / Vulkan). Conway's Game of Life on the compute queue, drawn by the graphics queue
Timeline Semaphore
Conway's Game of Life on the compute queue, drawn by the graphics queue.
Color Write Enable (TypeScript, DirectX 12 / Vulkan). Per-target color write enables combined into one image
Color Write Enable
Per-target color write enables combined into one image.
Dynamic Multisample Rasterization (TypeScript, DirectX 12 / Vulkan). The Space Module scene drawn with a sample count picked at run time
Dynamic Multisample Rasterization
The Space Module scene drawn with a sample count picked at run time.
Vertex Dynamic State (TypeScript, DirectX 12 / Vulkan). Two vertex layouts drawn with dynamic vertex input
Vertex Dynamic State
Two vertex layouts drawn with dynamic vertex input.
Dynamic Rendering (TypeScript, DirectX 12 / Vulkan). A reflective geosphere in a skybox, rendered without render pass objects
Dynamic Rendering
A reflective geosphere in a skybox, rendered without render pass objects.
Graphics Pipeline Library (TypeScript, DirectX 12 / Vulkan). Teapots, each with its own pipeline and a random lighting model; "Add pipeline" adds more
Graphics Pipeline Library
Teapots, each with its own pipeline and a random lighting model; "Add pipeline" adds more.
Dynamic Uniform Buffers (TypeScript, DirectX 12 / Vulkan). 125 cubes with model matrices in slices of one uniform buffer
Dynamic Uniform Buffers
125 cubes with model matrices in slices of one uniform buffer.
Texture Loading (TypeScript, DirectX 12 / Vulkan). A mipmapped texture with an adjustable level-of-detail bias
Texture Loading
A mipmapped texture with an adjustable level-of-detail bias.
Separate Image Sampler (TypeScript, DirectX 12 / Vulkan). A sampler bound apart from its image, linear or nearest filtering
Separate Image Sampler
A sampler bound apart from its image, linear or nearest filtering.
Dynamic Rendering Local Read (TypeScript, DirectX 12 / Vulkan). Deferred shading with 64 point lights, then forward-rendered glass
Dynamic Rendering Local Read
Deferred shading with 64 point lights, then forward-rendered glass.
Shader Object (TypeScript, DirectX 12 / Vulkan). Vertex, geometry and fragment shaders combined at draw time, then post-processed
Shader Object
Vertex, geometry and fragment shaders combined at draw time, then post-processed.
Calibrated Timestamps (TypeScript, DirectX 12 / Vulkan). The HDR scene with pieces of its code timed by the GPU clock through calibrated timestamps
Calibrated Timestamps
The HDR scene with pieces of its code timed by the GPU clock through calibrated timestamps.
Host Image Copy (TypeScript, DirectX 12 / Vulkan). A textured cube whose texture the CPU writes straight into the image
Host Image Copy
A textured cube whose texture the CPU writes straight into the image, no staging buffer.
Buffer Device Address (TypeScript, Vulkan). 64 meshes animated and drawn through GPU pointers only, no descriptors
Buffer Device Address
64 meshes animated and drawn through GPU pointers alone, no descriptors (Vulkan only).
Shader debugprintf (TypeScript, Vulkan). Lit spheres whose vertex shader prints positions with printf, shown in the UI
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 (TypeScript, Vulkan). Two textured crates whose descriptors are pushed into the command buffer with each draw
Push Descriptors
Two crates whose descriptors are pushed into the command buffer with each draw, no descriptor sets (Vulkan only).
Descriptor Buffers (TypeScript, Vulkan). Two textured crates whose descriptor sets live in plain buffers the CPU writes
Descriptor Buffers
The same crates, their descriptor sets in buffers the CPU writes descriptors into, no pools (Vulkan only).

DirectX-Graphics-Samples ports

Ported from Microsoft DirectX-Graphics-Samples.

Raytracing Procedural Geometry (TypeScript, DirectX 12 / Vulkan). Analytic shapes, metaballs, SDFs and a fractal intersected by intersection shaders
Raytracing Procedural Geometry
Analytic shapes, metaballs, SDFs and a fractal intersected by intersection shaders.
Raytracing Opacity Micromaps (TypeScript, DirectX 12 / Vulkan). An alpha-tested tree whose leaves skip the any-hit shader thanks to opacity micromaps (DXR 1.2)
Raytracing Opacity Micromaps
An alpha-tested tree whose leaves skip the any-hit shader thanks to opacity micromaps (DXR 1.2).
SM6 Wave Intrinsics (TypeScript, DirectX 12 / Vulkan). Wave intrinsics visualized per pixel: lane indices, reductions, prefix sums, quad shuffles
SM6 Wave Intrinsics
Wave intrinsics visualized per pixel: lane indices, reductions, prefix sums, quad shuffles.
Depth Bounds Test (TypeScript, DirectX 12 / Vulkan). A triangle clipped by an animated depth bounds test
Depth Bounds Test
A triangle clipped by an animated depth bounds test.
Predication Queries (TypeScript, DirectX 12 / Vulkan). A quad drawn only if last frame's occlusion query saw it
Predication Queries
A quad drawn only if last frame's occlusion query saw it.
Small Resources (TypeScript, DirectX 12 / Vulkan). 77 small textures placed at 4 KB alignment vs committed at 64 KB
Small Resources
77 small textures placed at 4 KB alignment vs committed at 64 KB.
Meshlet Render (TypeScript, DirectX 12 / Vulkan). The Stanford dragon drawn by a mesh shader alone, each meshlet in its own color
Meshlet Render
The Stanford dragon drawn by a mesh shader alone, each meshlet in its own color.
Meshlet Cull (TypeScript, DirectX 12 / Vulkan). Amplification shaders cull meshlets by frustum and normal cone; a picked meshlet shows its bounding sphere and cone
Meshlet Cull
Amplification shaders cull meshlets by frustum and normal cone; a picked meshlet shows its bounding sphere and cone.
Meshlet Instancing (TypeScript, DirectX 12 / Vulkan). A cube of toy robots drawn by a mesh shader, several instances of each last meshlet packed into one group
Meshlet Instancing
A cube of toy robots drawn by a mesh shader, several instances of each last meshlet packed into one group.
Dynamic LOD (TypeScript, DirectX 12 / Vulkan). Amplification shaders cull instances and pick one of six levels of detail by screen size
Dynamic LOD
Amplification shaders cull instances and pick one of six levels of detail by screen size.
Execute Indirect (TypeScript, DirectX 12 / Vulkan). A compute shader culls 1024 indirect draw commands; the draw takes their count from a buffer
Execute Indirect
A compute shader culls 1024 indirect draw commands; the draw takes their count from a buffer.
Dynamic Indexing (TypeScript, DirectX 12 / Vulkan). 120 cities, each draw picking its material from an array of 120 textures
Dynamic Indexing
120 cities, each draw picking its material from an array of 120 textures.
HDR Display (TypeScript, DirectX 12 / Vulkan). SDR and HDR gradients and the Rec.709 and Rec.2020 gamuts, presented to 8-bit sRGB, 10-bit ST.2084 or 16-bit scRGB swap chains
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 (TypeScript, DirectX 12 / Vulkan). 10000 particles pulling on each other, simulated by a compute shader on the async compute queue and drawn as geometry shader sprites
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 (TypeScript, DirectX 12 / Vulkan). Ray traced ambient occlusion at 1 ray per pixel, denoised spatio-temporally, over PBRT scenes and animated grass
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 (TypeScript, DirectX 12 / Vulkan). The SquidRoom scene lit by three moving spotlights with PCF shadows, its 1025 draws recorded on three worker threads
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 (TypeScript, DirectX 12 / Vulkan). 30 copies of a city drawn with two alternating pipeline states from draws recorded once and replayed every frame
Bundles
30 cities drawn with two alternating pipeline states, from draws recorded once and replayed every frame (D3D12 bundles).
Raytracing Simple Lighting (TypeScript, DirectX 12 / Vulkan). A ray traced cube lit by a point light circling it, its normals interpolated from vertex data read in the closest hit shader
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 (TypeScript, DirectX 12 / Vulkan). One level of a tiled texture at a time mapped to memory, picked with the arrow keys
Reserved Resources
One level of a tiled texture at a time mapped to memory, picked with the arrow keys.
Raytracing Library Subobjects (TypeScript, DirectX 12). A ray traced cube whose root signatures, hit group and configs are defined in its HLSL
Raytracing Library Subobjects
A ray traced cube whose root signatures, hit group and configs are defined in its HLSL (D3D12 only).
Generic Programs (TypeScript, DirectX 12). Two triangles drawn with generic programs, graphics pipelines made as state objects
Generic Programs
Two triangles drawn with generic programs: graphics pipelines made as state objects (D3D12 only).
Sakura Forest SER (TypeScript, DirectX 12). A ray traced sakura forest of 13525 instances whose primary rays are reordered with Shader Execution Reordering
Sakura Forest SER
A ray traced sakura forest whose primary rays go through hit objects and Shader Execution Reordering (D3D12 only).
Memory Management (TypeScript, DirectX 12). Large images streamed into video memory a mip level at a time under the memory budget, with mip overlays and a memory graph
Memory Management
Images streamed a mip level at a time under the memory budget: tiled resources, MakeResident / Evict, a paging thread (DirectX 12 only).

Xbox ATG Samples ports

Ported from Microsoft Xbox-ATG-Samples.

Fast Block Compress (TypeScript, DirectX 12 / Vulkan). BC1, BC3 and BC5 texture compression at run time in compute shaders or on the CPU
Fast Block Compress
BC1, BC3 and BC5 texture compression at run time in compute shaders or on the CPU.
Simple HDR (TypeScript, DirectX 12 / Vulkan). HDR10 output (Rec.2020, ST.2084) with blocks brighter than paper white
Simple HDR
HDR10 output (Rec.2020, ST.2084) with blocks brighter than paper white.
Bokeh (TypeScript, DirectX 12 / Vulkan). Depth of field with bokeh sprites expanded by a geometry shader
Bokeh
Depth of field with bokeh sprites expanded by a geometry shader.
Raytracing AO (TypeScript, DirectX 12 / Vulkan). Ray traced ambient occlusion side by side with MiniEngine's SSAO
Raytracing AO
Ray traced ambient occlusion side by side with MiniEngine's SSAO.
Simple PBR (TypeScript, DirectX 12 / Vulkan). Physically based rendering with image based lighting and ACES tone mapping
Simple PBR
Physically based rendering with image based lighting and ACES tone mapping.
Simple Compute (TypeScript, DirectX 12 / Vulkan). A Mandelbrot fractal by a compute shader, synchronous or on the async compute queue
Simple Compute
A Mandelbrot fractal by a compute shader, synchronous or on the async compute queue.
Simple Bezier (TypeScript, DirectX 12 / Vulkan). A Mobius strip of Bezier patches tessellated by hull and domain shaders
Simple Bezier
A Mobius strip of Bezier patches tessellated by hull and domain shaders.
Mouse Cursor (TypeScript, DirectX 12 / Vulkan). Absolute, relative (captured) and custom mouse cursor modes
Mouse Cursor
Absolute, relative (captured) and custom mouse cursor modes.
Collision (TypeScript, DirectX 12 / Vulkan). DirectXMath collision tests between frustums, boxes, spheres, triangles and rays
Collision
DirectXMath collision tests between frustums, boxes, spheres, triangles and rays.
Video Texture (TypeScript, DirectX 12 / Vulkan). A video decoded by Media Foundation into a texture on a spinning cube
Video Texture
A video decoded by Media Foundation into a texture on a spinning cube.

NVIDIA DXR Tutorials port

Ported from NVIDIA DxrTutorials.

DXR Refit (TypeScript, DirectX 12 / Vulkan). Rotating triangles with shadow rays; the top-level acceleration structure refitted every frame
DXR Refit
Rotating triangles with shadow rays; the top-level acceleration structure refitted every frame.

Original examples

Compute N-Body Collisions (TypeScript, DirectX 12 / Vulkan). compute_nbody with a third compute pass: particles bounce off each other
Compute N-Body Collisions
compute_nbody with a third compute pass: particles bounce off each other.
Environment Map (TypeScript, DirectX 12 / Vulkan). Sponza under an environment map with planar shadow maps, DDS or KTX 2
Environment Map
Sponza under an environment map with planar shadow maps, DDS or KTX 2.

External assets

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).

Repository layout

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)

How the TypeScript build works

The project registers a CMake language (TSLANG) that:

  • owns its own source extension (.ts),
  • is built with tslang --emit=obj,
  • produces .obj files 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) runs CMakeDetermineTSLANGCompiler.cmake, which finds tslang, then loads CMakeTSLANGInformation.cmake, which registers the compile rule (CMAKE_TSLANG_COMPILE_OBJECT — tslang --emit=obj).
  • Because CMAKE_TSLANG_SOURCE_FILE_EXTENSIONS contains ts, any .ts source is routed to tslang and compiled to a .obj.
  • That .obj is added to the target and linked with the C++ objects using the C++ linker (CMAKE_TSLANG_LINK_EXECUTABLE).
  • Change one example's .ts and only it recompiles.
  • tslang can't write a depfile, so the files a .ts imports or references aren't tracked for it: list them as its OBJECT_DEPENDS, as CMakeLists.txt does for core/.

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 __jit folder 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 from bin/D3D12 (the executables that need it opt in through their exports, which tslang.exe doesn't have).

Passing flags

set(CMAKE_TSLANG_FLAGS "--opt_level=3")                       # global
set_source_files_properties(mycode.ts PROPERTIES
    COMPILE_OPTIONS "--define;TSLANG=1")                      # per-file

Using it without a first-class language

If 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 to add_executable, producing it with add_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.

Calling NVRHI directly

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.py writes core/nvrhi_classes.<os>.ts with tsbindgen (shipped with tslang), which takes each method's @slot from 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 plain char'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's debugName is the C++ library's std::string (basic_string_char), ComputeState's bindings a static_vector8<IBindingSet | null> (its elements, then current_size), ComputePipelineDesc's CS a RefCountPtr<IShader>. The script writes a template's specializations, which tsbindgen lays out one by one, as one generic tuple: vector<T> (each C++ library's std::vector), static_vector8<T> and static_vector16<T> (tslang has no type of N elements), RefCountPtr<T>. Those are nested tuples, so a literal nests too (nvrhi.computeState()). A std::vector field (BindingLayoutDesc's and BindingSetDesc's items) points at a tslang array's elements, for the call only: nvrhi.stdVector() makes it, as nvrhi.createBindingLayout() and createBindingSet() 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) and nvrhi::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 a Ref to 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), so nvrhi.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 (the TextureSubresourceSet of clearTextureFloat, clearDepthStencilTexture, clearTextureUInt, beginTrackingTextureState, setTextureState and getNativeView, the nvrhi::Object of createHandleForNativeTexture, createHandleForNativeBuffer and createSamplerFeedbackForNativeTexture); and so is a small struct returned in registers (queryCoopVecMatMulFormatSupport's three bools).
  • Callbacks. nvrhi.IMessageCallback goes the other way: a @cpp class extending it whose message() has a body is an object NVRHI calls, through the vtable tslang makes for it. Pass it to Donut_SetNvrhiMessageCallback before creating the app, and keep it in a global while the device lives. Donut_LogNvrhiMessage passes a message on to Donut's logging. nvrhi_direct does 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:

  1. uploads numbers into a buffer and clears another;
  2. runs a compute shader over the uploaded numbers, its shader (from the bytecode ShaderMake compiled), binding layout, binding set and pipeline made directly;
  3. copies the three into a readback buffer, the last copy with hand-made barriers;
  4. 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);
  5. 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 writeTexture through a point sampler, scaled by a constant buffer: one texel a pixel. Its bottom right quarter is as cleared;
  6. 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, setTextureState and commitBarriers around a clear, setPermanentTextureState, and getTextureSubresourceState after each;
  • the same for a buffer: beginTrackingBufferState, setBufferState around a write and a copy, setPermanentBufferState, getBufferState;
  • resources placed in heaps: a virtual buffer and texture (isVirtual), their memory requirements (a MemoryRequirements returned by value), createHeap and IHeap::getDesc, bindBufferMemory at an offset and bindTextureMemory;
  • indexed indirect draws (drawIndexedIndirect), as the indirect draws above, through an index buffer;
  • queryFormatSupport of 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, as queryFeatureSupport says): 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;
  • queryMemoryRequirements of 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's drawIndirectCount);
  • mesh shaders, where the device has them: both of createMeshletPipeline's overloads, setMeshletState, dispatchMesh, dispatchMeshIndirect and dispatchMeshIndirectCount, a quarter each;
  • a tiled texture, where the device has them: getTextureTiling, one tile mapped by updateTextureTileMappings, cleared and read back;
  • sampler feedback, where the device has it (D3D12): createSamplerFeedbackTexture and createSamplerFeedbackForNativeTexture, clearSamplerFeedbackTexture, setSamplerFeedbackTextureState, a pixel shader writing feedback of two mip levels, and decodeSamplerFeedbackTexture into 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 an InstanceDesc (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 their clear* methods); setRayTracingState and dispatchRays through 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): getClusterOperationSizeInfo and executeMultiIndirectClusterOperation getting 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 IMessageCallback implemented in tslang. With -debug, it provokes a validation error and checks that NVRHI called the callback.

Memory model

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.

Android

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.

What to install

  • 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_demo crashes the emulator);

  • a JDK (17 or later) for signing, in JAVA_HOME (or in jdk/ 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.

Build and test

python tools/android.py build
python tools/android.py emulator
python tools/android.py test
  • build configures build-android-x86_64 if 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 arm64 builds for devices.
  • emulator starts the newest virtual device (--avd <name> picks one, --no-window hides it) unless a device is connected, and waits for it to boot. adb emu kill stops it.
  • test runs 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 to build-android-x86_64/android-test. With no examples named, it tests the ones that run on the emulator: 65 of them (EMULATOR_EXAMPLES in tools/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 (test reports them as failures; they are for devices).
  • ctest runs CTest's Vulkan tests on the device: the headless tests, then each example's -test (with its CTest arguments, passed in the activity's args extra), PASSED when it prints "Test PASSED" and exits with 0, SKIPPED when it exits with 77 (the device lacks a feature it needs, see unsupported in core/test_check.ts). Name regular expressions to run only those (ctest bokeh "^simple_"); --timeout is each test's limit (300 s). Logs go to build-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.png takes a screenshot instead. headless runs the headless tests alone (headless, nvrhi_direct, through adb 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.

How the examples run

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).

Without the script

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>.

Tests

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).

Software rendering

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=1 and 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).

Media without the sample checkouts

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.

Smoke tests and screenshots

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.

Credits

Each port keeps the original sample's algorithm and look; see the comment above its add_tslang_example in CMakeLists.txt for what differs.

About

112 DirectX 12 and Vulkan graphics samples written in TypeScript and compiled to native code with tslang: ray tracing, denoised RTAO, mesh shaders, compute, HDR, multithreading, Android

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages