Repository navigation
Direct3D 11 Backend
The Direct3D 11 backend (vgl/d3d11.h, C++ wrapper vgl/d3d11.hpp, CMake target vgl::d3d11, sources in backends/d3d11) draws a context's globe into the host's Direct3D 11 render targets, at feature level 11_0, on Windows. This page covers handing over the device and device context, the shaders compiled with fxc at build time, depth buffer formats and conventions, matrices and culling, the state it saves and restores, device removal, WARP, and the Direct3D 11 sample. Like the OpenGL backend, it is a consumer of the render interface in a library of its own, built on the public C API only, and draws the same globe with the same features and debug views.
- Requirements
- Building and linking
- Shaders compiled at build time
- Quick start
- Device and device context handoff
- Drawing
- Matrices, handedness and culling
- Depth buffer formats and conventions
- State it saves and restores
- Integrating with an existing renderer
- Device removal
- WARP
- The Direct3D 11 sample
- Troubleshooting
| Item | Requirement |
|---|---|
| Platform | Windows 10 or later (the project's minimum) |
| Device |
ID3D11Device at D3D_FEATURE_LEVEL_11_0 or later, hardware or WARP, created by the host |
| Runtime libraries |
d3d11.lib only: no d3dcompiler_47.dll, no D3DX |
| Build | The Windows SDK's fxc, found when vgl is configured |
The backend is on by default on Windows (VGL_BACKEND_D3D11, a dependent option that is only offered when WIN32; see Building vgl). Link it next to the core:
find_package(vgl CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE vgl::vgl vgl::d3d11)
The target links d3d11 publicly. With BUILD_SHARED_LIBS=ON the backend is a DLL (vgl-d3d11.dll). It has no pkg-config file. vcpkg enables it with the d3d11 feature and Conan with with_d3d11 (both on by default on Windows); see Installing vgl. vgl/d3d11.h includes <d3d11.h>; the backend itself compiles with NOMINMAX and WIN32_LEAN_AND_MEAN, which your code may want too.
The shaders are HLSL for shader model 5.0 in backends/d3d11/terrain.hlsl, a port of the OpenGL backend's GLSL that also includes the shared atmosphere code (backends/common/atmosphere.glsl) after a few #defines that turn its GLSL subset into HLSL. CMake compiles them with fxc into headers of bytecode that the library embeds, so creating a backend compiles nothing and shader errors surface at build time.
| Variant | Profile | Entry point | Defines |
|---|---|---|---|
terrain_vs |
vs_5_0 |
vs_main |
|
terrain_vs_log |
vs_5_0 |
vs_main |
VGL_LOGARITHMIC_DEPTH=1 |
terrain_ps |
ps_5_0 |
ps_main |
|
terrain_ps_log |
ps_5_0 |
ps_main |
VGL_LOGARITHMIC_DEPTH=1 |
sky_vs, sky_ps
|
vs_5_0, ps_5_0
|
sky_vs_main, sky_ps_main
|
|
stars_vs, stars_ps
|
vs_5_0, ps_5_0
|
stars_vs_main, stars_ps_main
|
Each is compiled with /O3 /WX into shaders/<name>.h in the build tree. CMake looks for fxc in WindowsSdkVerBinPath, the Visual Studio generator's target SDK, and the newest Windows Kits/10/bin/10.*/x64. If it finds none, it warns "fxc (the Windows SDK's HLSL compiler) not found; skipping the Direct3D 11 backend" and builds without it. Point the cache variable VGL_FXC at fxc.exe to choose one:
cmake --preset windows-msvc-static -DVGL_FXC="C:/Program Files (x86)/Windows Kits/10/bin/10.0.26100.0/x64/fxc.exe"
(The SDK version in that path is an example; use one you have installed.)
#include <vgl/d3d11.hpp>
#include <vgl/vgl.hpp>
// The host's device and immediate context, at feature level 11_0.
auto info = vgl::init<vgl_d3d11_backend_create_info>();
info.depth_convention = VGL_DEPTH_CONVENTION_REVERSED_Z; // the natural one on Direct3D 11
info.device = device; // ID3D11Device*
info.device_context = immediate; // ID3D11DeviceContext*
vgl::expected<vgl::d3d11_backend> backend = vgl::d3d11_backend::create(context, info);
if (!backend)
{
std::fprintf(stderr, "backend: %s\n", vgl::result_name_get(backend.error()));
}
// Each frame, after context.update():
const float clear[4] = {0.02f, 0.02f, 0.05f, 1.0f};
immediate->ClearRenderTargetView(rtv, clear);
immediate->ClearDepthStencilView(dsv, D3D11_CLEAR_DEPTH, 0.0f, 0); // 0 for reversed-Z
immediate->OMSetRenderTargets(1, &rtv, dsv);
D3D11_VIEWPORT viewport{};
viewport.Width = static_cast<float>(width);
viewport.Height = static_cast<float>(height);
viewport.MaxDepth = 1.0f;
immediate->RSSetViewports(1, &viewport);
auto draw = vgl::init<vgl_d3d11_draw_info>();
draw.view_matrix = &view_matrix; // optional, vgl_dmat4
draw.projection_matrix = &projection_matrix; // optional, depth 0 to 1
draw.features = VGL_DRAW_FEATURE_ATMOSPHERE;
static_cast<void>(backend->draw(&draw));
swap_chain->Present(1, 0);
The same in C:
vgl_d3d11_backend_create_info info = {0};
info.struct_size = sizeof(info);
info.device = device;
info.device_context = immediate;
vgl_d3d11_backend* backend = NULL;
if (vgl_d3d11_backend_create(context, &info, &backend) != VGL_RESULT_SUCCESS)
{
/* the context's log says why */
}
/* each frame: */
vgl_d3d11_backend_draw(backend, NULL);
/* at the end, before the vgl context and the device: */
vgl_d3d11_backend_destroy(backend);
In C, define COBJMACROS before <d3d11.h> to call the COM interfaces through macros such as ID3D11DeviceContext_ClearRenderTargetView(...); the pointers are the same.
vgl_d3d11_backend_create_info is a versioned structure: zero-initialize it, set struct_size, then fill in.
| Field | Meaning | Default |
|---|---|---|
struct_size |
sizeof(vgl_d3d11_backend_create_info) |
Required |
depth_convention |
VGL_DEPTH_CONVENTION_LOGARITHMIC, _REVERSED_Z or _CONVENTIONAL
|
Logarithmic (0) |
device |
ID3D11Device*, feature level 11_0 or later |
Required |
device_context |
ID3D11DeviceContext* to draw with, usually the immediate context |
Required |
- The backend holds a reference (
AddRef) to both and releases them invgl_d3d11_backend_destroy, so you may release your own references in any order, as long as the backend is destroyed before the vgl context. - Call every backend function on the thread that owns the device context. With the immediate context that is your render thread; the draw must not overlap
vgl_context_update. - Creation checks the feature level, creates the shaders (from the embedded bytecode), input layouts, constant buffers, samplers and states, and tells the context it samples RGBA8. It allocates through the context's allocator and logs through the context's log.
| Result | Cause |
|---|---|
VGL_RESULT_SUCCESS |
Created |
VGL_RESULT_INVALID_ARGUMENT |
A null pointer, a small struct_size, no device or device context, or an unknown convention |
VGL_RESULT_NOT_SUPPORTED |
Fields of a newer vgl set, or a feature level below 11_0 |
VGL_RESULT_BACKEND_ERROR |
Direct3D failed to create a shader or state; the log has the HRESULT
|
VGL_RESULT_OUT_OF_MEMORY |
Allocation failed |
A context has one render-interface consumer at a time. Destroying the backend releases its objects and resets the context's render resources, so a backend created later receives them all again.
vgl_d3d11_backend_draw(backend, &info) draws into the render target and depth buffer bound on the device context, with the viewport you set. Call it once per frame after vgl_context_update. It takes a frame from the context, applies its resources (releasing and creating buffers and textures), draws one indexed draw per tile with up to four imagery layers, then the stars and the sky if enabled, restores your state, and ends the frame. It does not clear or present. Without a view, it draws nothing.
vgl_d3d11_draw_info mirrors vgl_gl_draw_info field for field; info may be null for all defaults:
| Field | Meaning | 0 or null selects |
|---|---|---|
struct_size |
sizeof(vgl_d3d11_draw_info) |
Required when info is given |
mirrored |
VGL_TRUE when the projection mirrors the image: cull the other side |
Not mirrored |
view_matrix |
const vgl_dmat4*, the camera to draw from when it differs from the last update |
The context's view |
projection_matrix |
const vgl_dmat4*, camera to clip space with depth 0 to 1 |
The context's view's projection |
sun_direction |
Towards the sun along the ECEF axes | Zero: light from the camera; with the atmosphere, a sun above the camera |
logarithmic_far |
The logarithmic convention's far distance, metres | 1.0e8 |
upload_budget |
Bytes of resources to upload this draw | 16 MiB |
features |
vgl_draw_feature bits |
Terrain and imagery only |
debug_views |
vgl_debug_view bits |
Normal drawing |
earth_rotation_angle |
Radians, for the stars | 0 |
contour_interval |
Metres | 500 |
cloud_height |
Metres above the ellipsoid | 6000 |
Results are those of the OpenGL backend: VGL_RESULT_INVALID_ARGUMENT for non-finite or negative values and unknown bits, VGL_RESULT_NOT_SUPPORTED for fields of a newer vgl, VGL_RESULT_INVALID_OPERATION if a frame is open or the last device reset failed, VGL_RESULT_OUT_OF_MEMORY if the frame could not begin.
Per-frame values sit in one constant buffer and per-tile values in another, updated with D3D11_MAP_WRITE_DISCARD for each draw. Layers bind to shader resource slots 0 to 3, the mesh's samples (DXGI_FORMAT_R32G32_UINT) to slot 4 and the clouds to slot 5; sampler 0 is trilinear, anisotropic 8x, clamped, and sampler 1 serves the clouds. Imagery textures are DXGI_FORMAT_R8G8B8A8_UNORM (the backend asks for VGL_TEXTURE_FORMAT_RGBA8).
Features, debug views, the sea surface, the atmosphere, stars and clouds behave exactly as in the OpenGL backend; see OpenGL Backend and Atmosphere, Stars and Clouds. Stars and clouds are set with vgl_d3d11_backend_stars_set and vgl_d3d11_backend_clouds_set (stars_set and clouds_set on vgl::d3d11_backend).
- Camera space is the view matrix's: right-handed like ECEF, looking along -z. The backend's logarithmic depth and lighting rely on it. Projections map depth to [0, 1], as Direct3D does.
-
vgl_dmat4is column-major and multiplies column vectors. A row-major matrix that multiplies row vectors, as DirectXMath'sXMMATRIXis, has the same memory layout, so you can copy one element for element. Use the right-handed functions (XMMatrixPerspectiveFovRH), or build the matrices yourself as the sample does. -
Build the view matrix in double precision. Its translation is millions of metres; a
floatmatrix (including anyXMMATRIX) rounds it to about half a metre and the globe jitters. The projection can come from single precision. - Triangles are counter-clockwise from outside the ellipsoid; Direct3D tells front from back in clip space, before the viewport turns y down, so the backend's rasterizer treats counter-clockwise as front (
FrontCounterClockwise = TRUE), with back faces culled. Setmirroredfor a projection that flips the image.
A right-handed projection with depth from 0 to 1, as the sample builds it (column-major):
vgl_dmat4 perspective_zero_to_one(double fovy, double aspect, double near_plane, double far_plane)
{
const double f = 1.0 / std::tan(fovy / 2.0);
vgl_dmat4 m{};
m.m[0] = f / aspect;
m.m[5] = f;
m.m[10] = far_plane / (near_plane - far_plane);
m.m[11] = -1.0;
m.m[14] = near_plane * far_plane / (near_plane - far_plane);
return m;
}
For reversed-Z, the reversed infinite projection on the OpenGL Backend page works unchanged (it already maps depth to [0, 1]).
| Convention | Depth format | Clear depth | Depth test | Projection |
|---|---|---|---|---|
| Reversed-Z (recommended here) | DXGI_FORMAT_D32_FLOAT |
0 | GREATER |
Reversed, near to 1, far (ideally infinite) to 0 |
| Logarithmic (default) | Any, for example DXGI_FORMAT_D24_UNORM_S8_UINT
|
1 | LESS |
Conventional, depth 0 to 1; near and far do not matter. The shaders write SV_Depth and set the clip-space depth to match |
| Conventional | Any | 1 | LESS |
Yours; small scenes only |
The viewport's MinDepth and MaxDepth should be 0 and 1. The sky and stars draw at the far depth (0 for reversed-Z, 1 otherwise) with GREATER_EQUAL or LESS_EQUAL and no depth writes. The sample uses DXGI_FORMAT_D32_FLOAT for every convention and reversed-Z by default. For the logarithmic convention in your own HLSL, see the snippet on Render Interface.
When it draws, the backend saves this state of the device context and restores it before returning (requirement NFR-08), releasing the references the save took:
- input assembler: the input layout, the primitive topology, vertex buffer 0 (with stride and offset), and the index buffer (with format and offset)
- the vertex shader and its constant buffers 0 and 1
- the pixel shader, its constant buffers 0 and 1, its shader resources 0 to 5 and its samplers 0 and 1
- the rasterizer state
- the depth-stencil state and stencil reference
- the blend state, blend factor and sample mask
It draws with your render targets, viewports and scissor rectangles as they are, and with the stencil test off and blending off for the terrain (the sky and stars blend).
The sample's check verifies the restore with sentinel state around each draw.
-
Pass order. Clear, draw a background that should show through thin sky without writing depth, draw your opaque geometry with the same depth convention, call
vgl_d3d11_backend_draw, then transparent geometry and UI. The reasoning is the same as for OpenGL (pass order). -
Colour format. The backend writes display-encoded colour from sRGB-encoded imagery sampled as unorm. Draw into a
DXGI_FORMAT_R8G8B8A8_UNORMrender target view, as the sample's swap chain is, not an_SRGBview; otherwise the globe looks washed out. -
MSAA. It draws into a multisampled target as into any other; resolve it yourself. With the logarithmic convention the pixel shader writes
SV_Depth(one depth per pixel, no early depth test); reversed-Z avoids that. - Deferred contexts. The header says the device context to draw with is "usually the immediate one". The sample and the checks use the immediate context only; deferred contexts are untested.
-
Several views. Pass another camera in
view_matrixandprojection_matrixto draw the tiles selected for the context's view from it.
When the device is removed or reset (DXGI_ERROR_DEVICE_REMOVED from Present, for example), create a new device and context and hand them over:
if (backend->device_reset(new_device, new_context) != vgl::result::success)
{
// draws fail with VGL_RESULT_INVALID_OPERATION until a reset succeeds
}
vgl_d3d11_backend_device_reset(backend, device, device_context) forgets the old device's objects without using them again, takes references to the new device and context, recreates its shaders and states, and resets the context's resources, which upload again over the next frames. Stars and clouds are kept on the CPU and upload again. It fails with VGL_RESULT_NOT_SUPPORTED below feature level 11_0 and VGL_RESULT_BACKEND_ERROR if Direct3D cannot create a shader or state.
WARP, Direct3D's software rasterizer, is on every Windows machine and supports feature level 11_0, so the backend runs on machines without a GPU, in virtual machines and in CI. The atmosphere needs no textures or extra passes and runs on WARP too. The sample creates a hardware device and falls back to WARP, or uses WARP directly with --warp:
const D3D_FEATURE_LEVEL wanted = D3D_FEATURE_LEVEL_11_0;
HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, &wanted, 1,
D3D11_SDK_VERSION, &device, nullptr, &immediate);
if (FAILED(hr))
{
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, 0, &wanted, 1,
D3D11_SDK_VERSION, &device, nullptr, &immediate);
}
samples/d3d11/main.cpp, executable vgl-d3d11: the globe in a plain Win32 window with a flip-model swap chain, the same navigation, panel and keys as the OpenGL sample, and the atmosphere, stars and clouds.
vgl-d3d11 [--data <directory> | --pack <uri>...] [--cache <directory> [--cache-budget <MiB>]]
[--depth logarithmic|reversed|conventional] [--warp] [--check] [--save <directory>]
[--stars <catalog>]
- The default convention is reversed-Z, with a
DXGI_FORMAT_D32_FLOATdepth buffer. -
--warpdraws with WARP; without it, the sample falls back to WARP when no hardware device is available. -
--checkruns without a window: it draws a few views offscreen with each depth convention, checks the images and that the conventions agree, checks that the backend restores the host's state, checks that it draws again afterdevice_reset, and exits with 1 on failure.--savewrites the checked images as PPM files.
The check is the CTest test d3d11, added whenever tests are built; it needs no display, since it draws offscreen on the hardware device or WARP. Data and common options are on Samples.
| Symptom | Likely cause |
|---|---|
No vgl::d3d11 target, configure warns that fxc was not found |
Install the Windows SDK, or set VGL_FXC
|
Create fails with VGL_RESULT_NOT_SUPPORTED
|
Feature level below 11_0 (a 10.x device) |
| Nothing drawn | No view set, render target or viewport not bound, or depth cleared to the wrong value for the convention |
| Terrain missing or depth wrong with the logarithmic convention | A left-handed view matrix: the backend expects right-handed camera space looking along -z |
| Jitter near the ground | The view matrix was built in single precision |
| Colours washed out | An _SRGB render target view |
More on Troubleshooting.
Render Interface, OpenGL Backend, Atmosphere, Stars and Clouds, Ogre Backend, Building vgl, Samples, and the header backends/d3d11/include/vgl/d3d11.h.
Getting vgl
Using the library
Rendering
Data
Reference
Specifications