DirectX 12 / PRIMARY BACKEND

Set up the native project

Install the Windows toolchain, compile HLSL with DXC, configure CMake, and run the shared teaching samples.

Prerequisites and files#

Use a Windows 10/11 computer with a DX12-capable GPU supporting Shader Model 6.0. The sample also offers an explicit software WARP path. Install Visual Studio 2022 with Desktop development with C++, a Windows 10/11 SDK, and CMake 3.24 or later. The generator commands below target Visual Studio 2022 deliberately; another installed version needs its matching CMake generator. Install Microsoft’s DirectX Shader Compiler. Keep its required DLLs with dxc.exe and put its executable directory on PATH. Enable the Windows Graphics Tools optional feature for the debug layer. The DX12 samples use Windows Imaging Component for PNG/JPEG decoding, so no external image library is required.

FileResponsibility
samples/dx12/CMakeLists.txtTargets, C++20, shader compile rules, SDK libraries
samples/dx12/main.cppWindow, Sample, frame contexts, geometry, texture transfer
samples/dx12/Shaders/Scene.hlslShared vertex/pixel interfaces and texture variant
samples/dx12/README.mdCommands, expected output, native validation checklist

Configure and build#

Open x64 Native Tools Command Prompt for VS 2022. Enter samples/dx12 in the delivered source folder. Run these commands in order:

text · REFERENCE EXCERPT
dxc --version
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Debug
build\Debug\qubic_triangle.exe
build\Debug\qubic_texture.exe

If DXC is not on PATH, add -DDXC_EXECUTABLE="C:\tools\dxc\bin\x64\dxc.exe" to configuration. CMake compiles VSMain and PSMain to DXIL at Shader Model 6.0 and copies generated .cso files into separate triangle/texture directories next to the executables. Shader-only rebuilds also refresh those runtime files.

Complete sample source: dx12/CMakeLists.txt
cmake_minimum_required(VERSION 3.24)
project(QubicTeachingSamples LANGUAGES CXX)
if(NOT WIN32)
  message(FATAL_ERROR "These native DirectX 12 samples require Windows and the Windows SDK.")
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_program(DXC_EXECUTABLE NAMES dxc REQUIRED)
foreach(sample IN ITEMS triangle texture)
  if(sample STREQUAL "texture")
    set(textured 1)
  else()
    set(textured 0)
  endif()
  set(shader_dir "${CMAKE_CURRENT_BINARY_DIR}/shaders/${sample}")
  foreach(stage IN ITEMS vs ps)
    string(TOUPPER "${stage}" entry_prefix)
    add_custom_command(
      OUTPUT "${shader_dir}/${stage}.cso"
      COMMAND ${CMAKE_COMMAND} -E make_directory "${shader_dir}"
      COMMAND "${DXC_EXECUTABLE}" -T ${stage}_6_0 -E ${entry_prefix}Main
        -D TEXTURED=${textured} -Zpr -Ges
        -Fo "${shader_dir}/${stage}.cso"
        "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/Scene.hlsl"
      DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/Scene.hlsl"
      VERBATIM)
  endforeach()
  add_custom_target(${sample}_shaders
    COMMAND ${CMAKE_COMMAND} -E copy_directory "${shader_dir}" "$<TARGET_FILE_DIR:qubic_${sample}>/shaders/${sample}"
    DEPENDS "${shader_dir}/vs.cso" "${shader_dir}/ps.cso"
    VERBATIM)
  add_executable(qubic_${sample} WIN32 main.cpp)
  add_dependencies(qubic_${sample} ${sample}_shaders)
  target_compile_definitions(qubic_${sample} PRIVATE UNICODE _UNICODE NOMINMAX WIN32_LEAN_AND_MEAN TEXTURED=${textured})
  target_link_libraries(qubic_${sample} PRIVATE d3d12 dxgi windowscodecs ole32 shell32)
  if(MSVC)
    target_compile_options(qubic_${sample} PRIVATE /W4 /permissive- /utf-8)
  endif()
endforeach()

Window and application loop#

main.cpp registers a Win32 window class and passes Sample to CreateWindowExW. WindowProc stores that pointer in GWLP_USERDATA, records resize requests, handles paint messages, and posts a quit message on destruction. GPU resizing happens from the render loop rather than directly inside a window callback. The application pumps pending messages with PeekMessageW. When no message remains, it renders. A minimized window waits for a message instead of generating hidden frames. Escape destroys the window. Startup initializes COM because WIC decoding uses COM objects; cleanup releases Sample before CoUninitialize.

Implementation: check errors first
cpp · REFERENCE EXCERPT
void Check(HRESULT hr) {
    if (FAILED(hr)) {
        throw std::runtime_error("A Windows or DX12 call failed");
    }
}

The complete sample includes the numeric HRESULT in the message. It also reports shader-file and image-path failures with useful context. An error should stop the path before the next call uses an uninitialized object.

Expected output and verification#

The first executable shows a triangle with interpolated corner colors. The second shows a rotating indexed cube with a generated checker. Supplying an image path changes its texture. Resize, minimize/restore, and exit both targets. In Debug, inspect Visual Studio Output for debug-layer diagnostics. No native compilation or GPU execution took place in the Linux authoring environment. The delivery checks verify static website generation and laboratory algorithms. Record your Windows SDK, compiler, adapter, and driver when you perform native validation; do not confuse a successful website build with a successful DX12 build.

Common setup errors#

If CMake cannot find a generator, confirm the selected Visual Studio workload and version. If it cannot find DXC, point DXC_EXECUTABLE at the actual binary. If shader files are missing at launch, rebuild and keep the generated shaders folder next to the executable. If the debug layer is unavailable, install Graphics Tools; the code still creates a release-style device when that optional layer is absent. Use --warp to test the software path explicitly. A Vulkan fallback is a separate backend with its own availability and capability checks, not an automatic response to any DX12 error. Continue to device and swap chain.

Search titles and full article text.