use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_11_0;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use windows::Win32::Graphics::Dxgi::*;
use windows::core::Interface;
use crate::directx::context::FRAMES;
use crate::directx::texture::HDR_FORMAT;
use crate::gfx::hdr_output::HdrOutputMode;
use crate::win32::window::{WindowState, create_window};
pub(super) struct DeviceAndWindow {
pub win_state: Box<WindowState>,
pub device: ID3D12Device,
pub info_queue: Option<ID3D12InfoQueue>,
pub command_queue: ID3D12CommandQueue,
pub swapchain: IDXGISwapChain3,
pub swapchain_format: DXGI_FORMAT,
pub allow_tearing: bool,
pub msaa_samples: u32,
pub adapter: Option<IDXGIAdapter3>,
pub hdr_mode: HdrOutputMode,
}
pub(super) struct WindowConfig<'a> {
pub title: &'a str,
pub width: u32,
pub height: u32,
pub title_bar: bool,
}
pub(super) fn setup(
config: WindowConfig,
validation: bool,
vsync: bool,
hdr_display_requested: bool,
hdr_pq_requested: bool,
) -> Result<DeviceAndWindow, String> {
let WindowConfig {
title,
width,
height,
title_bar,
} = config;
if validation {
let debug = unsafe {
let mut d: Option<ID3D12Debug> = None;
D3D12GetDebugInterface(&mut d).ok().and(d)
};
match debug {
Some(d) => {
unsafe { d.EnableDebugLayer() };
tracing::info!("d3d12 debug layer: enabled");
}
None => tracing::warn!(
"d3d12 debug layer: requested but unavailable (install the Windows \
\"Graphics Tools\" optional feature); running unvalidated"
),
}
}
let (hwnd, win_state) = create_window(title, width, height, title_bar)?;
let factory: IDXGIFactory4 = if validation {
unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG) }
.or_else(|_| unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_FLAGS(0)) })
.map_err(|e| format!("CreateDXGIFactory2: {e}"))?
} else {
unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_FLAGS(0)) }
.map_err(|e| format!("CreateDXGIFactory2: {e}"))?
};
let adapter = pick_adapter(&factory)?;
let adapter3: Option<IDXGIAdapter3> = adapter.cast().ok();
let mut device_opt: Option<ID3D12Device> = None;
unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_11_0, &mut device_opt) }
.map_err(|e| format!("D3D12CreateDevice: {e}"))?;
let device = device_opt.ok_or("D3D12CreateDevice returned None")?;
let info_queue: Option<ID3D12InfoQueue> = if validation {
let iq = device.cast::<ID3D12InfoQueue>().ok().inspect(|iq| unsafe {
let _ = iq.SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, false);
let _ = iq.SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, false);
let _ = iq.SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, false);
let mut denied = [D3D12_MESSAGE_ID_LOADPIPELINE_NAMENOTFOUND];
let filter = D3D12_INFO_QUEUE_FILTER {
DenyList: D3D12_INFO_QUEUE_FILTER_DESC {
NumIDs: denied.len() as u32,
pIDList: denied.as_mut_ptr(),
..Default::default()
},
..Default::default()
};
let _ = iq.AddStorageFilterEntries(&filter);
});
if iq.is_none() {
tracing::warn!("d3d12 info queue: unavailable; layer messages will not be logged");
}
iq
} else {
None
};
let queue_desc = D3D12_COMMAND_QUEUE_DESC {
Type: D3D12_COMMAND_LIST_TYPE_DIRECT,
..Default::default()
};
let command_queue: ID3D12CommandQueue = unsafe { device.CreateCommandQueue(&queue_desc) }
.map_err(|e| format!("CreateCommandQueue: {e}"))?;
let msaa_samples = query_msaa_samples(&device);
let max_edr = measure_max_edr(&adapter);
let mut hdr_mode = HdrOutputMode::resolve(hdr_display_requested, hdr_pq_requested, max_edr);
if hdr_display_requested && !hdr_mode.is_hdr() {
tracing::warn!(
"HDR display requested but the active adapter's outputs report max EDR \
multiplier {:.3}, falling back to SDR (BGRA8Unorm) output",
max_edr
);
} else if hdr_mode.is_hdr() {
tracing::info!(
"HDR display output enabled: max EDR multiplier {:.3} on the active adapter",
max_edr
);
}
let swapchain_format = if hdr_mode.is_hdr() {
HDR_SWAPCHAIN_FORMAT
} else {
DXGI_FORMAT_B8G8R8A8_UNORM
};
let allow_tearing = !vsync
&& factory
.cast::<IDXGIFactory5>()
.ok()
.map(|f5| {
let mut data: i32 = 0;
unsafe {
f5.CheckFeatureSupport(
DXGI_FEATURE_PRESENT_ALLOW_TEARING,
&mut data as *mut _ as *mut core::ffi::c_void,
std::mem::size_of::<i32>() as u32,
)
}
.is_ok()
&& data != 0
})
.unwrap_or(false);
let sc_desc = DXGI_SWAP_CHAIN_DESC1 {
Width: width,
Height: height,
Format: swapchain_format,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount: FRAMES as u32,
SwapEffect: DXGI_SWAP_EFFECT_FLIP_DISCARD,
Flags: if allow_tearing {
DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING.0 as u32
} else {
0
},
..Default::default()
};
let sc_base: IDXGISwapChain1 =
unsafe { factory.CreateSwapChainForHwnd(&command_queue, hwnd, &sc_desc, None, None) }
.map_err(|e| format!("CreateSwapChain: {e}"))?;
let swapchain: IDXGISwapChain3 = sc_base
.cast()
.map_err(|e| format!("SwapChain3 cast: {e}"))?;
if hdr_mode.is_hdr() {
let want_pq = matches!(
hdr_mode,
HdrOutputMode::Hdr {
encoding: crate::gfx::hdr_output::HdrEncoding::Pq,
..
}
);
let primary = if want_pq {
HDR_PQ_COLOR_SPACE
} else {
HDR_LINEAR_COLOR_SPACE
};
let primary_label = if want_pq { "HDR10 PQ" } else { "scRGB linear" };
let primary_support = unsafe { swapchain.CheckColorSpaceSupport(primary) }.unwrap_or(0);
let primary_ok =
(primary_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT.0 as u32) != 0;
let mut applied = false;
if primary_ok {
if let Err(e) = unsafe { swapchain.SetColorSpace1(primary) } {
tracing::warn!(
"HDR display enabled but SetColorSpace1({primary_label}) failed ({e}); \
the compositor may still treat the RGBA16Float swapchain as sRGB; HDR \
output may look desaturated"
);
} else {
applied = true;
}
}
if !applied && want_pq {
let fallback_support =
unsafe { swapchain.CheckColorSpaceSupport(HDR_LINEAR_COLOR_SPACE) }.unwrap_or(0);
if (fallback_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT.0 as u32) != 0 {
tracing::warn!(
"HDR display + hdr_pq:true requested but the swapchain does not advertise \
HDR10 PQ support (CheckColorSpaceSupport flags = {primary_support:#x}); \
falling back to scRGB linear extended-range output"
);
if let Err(e) = unsafe { swapchain.SetColorSpace1(HDR_LINEAR_COLOR_SPACE) } {
tracing::warn!(
"scRGB linear fallback also failed ({e}); leaving the swapchain at \
its default colour space"
);
} else {
hdr_mode = HdrOutputMode::Hdr {
max_edr: match hdr_mode {
HdrOutputMode::Hdr { max_edr, .. } => max_edr,
HdrOutputMode::Sdr => unreachable!(),
},
encoding: crate::gfx::hdr_output::HdrEncoding::ExtendedLinear,
};
}
} else {
tracing::warn!(
"HDR display + hdr_pq:true requested but neither HDR10 PQ \
(flags={primary_support:#x}) nor scRGB linear (flags={fallback_support:#x}) \
are advertised by this swapchain; leaving the swapchain at its default \
colour space"
);
}
} else if !applied {
tracing::warn!(
"HDR display enabled but the swapchain does not advertise {primary_label} \
support (CheckColorSpaceSupport flags = {primary_support:#x}); leaving the \
swapchain at its default colour space"
);
}
}
unsafe { factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }.ok();
Ok(DeviceAndWindow {
win_state,
device,
info_queue,
command_queue,
swapchain,
swapchain_format,
allow_tearing,
msaa_samples,
adapter: adapter3,
hdr_mode,
})
}
const HDR_SWAPCHAIN_FORMAT: DXGI_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT;
const HDR_LINEAR_COLOR_SPACE: DXGI_COLOR_SPACE_TYPE = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
const HDR_PQ_COLOR_SPACE: DXGI_COLOR_SPACE_TYPE = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
fn measure_max_edr(adapter: &IDXGIAdapter1) -> f32 {
const SDR_REFERENCE_NITS: f32 = 80.0;
let mut best: f32 = 1.0;
let mut i: u32 = 0;
loop {
let output: IDXGIOutput = match unsafe { adapter.EnumOutputs(i) } {
Ok(o) => o,
Err(_) => break,
};
i += 1;
let output6: IDXGIOutput6 = match output.cast() {
Ok(o) => o,
Err(_) => continue, };
let desc1 = match unsafe { output6.GetDesc1() } {
Ok(d) => d,
Err(_) => continue,
};
let hdr_advertised = desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
if !hdr_advertised {
continue;
}
let nits = desc1.MaxLuminance;
if nits.is_finite() && nits > 0.0 {
let edr = nits / SDR_REFERENCE_NITS;
if edr > best {
best = edr;
}
}
}
best
}
fn pick_adapter(factory: &IDXGIFactory4) -> Result<IDXGIAdapter1, String> {
let mut i = 0u32;
while let Ok(adapter) = unsafe { factory.EnumAdapters1(i) } {
let desc = unsafe { adapter.GetDesc1() }.map_err(|e| format!("GetDesc1: {e}"))?;
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != 0 {
i += 1;
continue;
}
if unsafe {
D3D12CreateDevice(
&adapter,
D3D_FEATURE_LEVEL_11_0,
std::ptr::null_mut::<Option<ID3D12Device>>(),
)
}
.is_ok()
{
return Ok(adapter);
}
i += 1;
}
Err("no suitable D3D12 adapter found".to_string())
}
fn query_msaa_samples(device: &ID3D12Device) -> u32 {
for &count in &[4u32, 2] {
let mut data = D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS {
Format: HDR_FORMAT,
SampleCount: count,
Flags: D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE,
NumQualityLevels: 0,
};
if unsafe {
device.CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&mut data as *mut _ as *mut std::ffi::c_void,
std::mem::size_of::<D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS>() as u32,
)
}
.is_ok()
&& data.NumQualityLevels > 0
{
return count;
}
}
1
}