use concinnity_core::render::backend_init::SwapchainConfig;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::hdr_output;
use concinnity_core::render::hdr_output::HdrOutputMode;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use windows::Win32::Graphics::Dxgi::*;
use windows::core::Interface;
use crate::directx::allocator::DeviceAllocator;
use crate::directx::context::{DxHardware, FRAMES};
use crate::directx::error::map_hresult;
use crate::directx::texture::HDR_FORMAT;
use crate::win32::display_mode::FullscreenDisplayMode;
use crate::win32::window::create_window;
pub(super) struct DxgiSwapchain {
pub handle: IDXGISwapChain3,
pub format: DXGI_FORMAT,
pub allow_tearing: bool,
}
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,
swapchain_config: SwapchainConfig,
) -> RenderResult<(DxHardware, DxgiSwapchain)> {
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).map_err(RenderError::Other)?;
let factory: IDXGIFactory4 = if validation {
unsafe {
CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG)
.or_else(|_| CreateDXGIFactory2(DXGI_CREATE_FACTORY_FLAGS(0)))
}
.map_err(|e| map_hresult(e.code(), "CreateDXGIFactory2"))?
} else {
unsafe { CreateDXGIFactory2(DXGI_CREATE_FACTORY_FLAGS(0)) }
.map_err(|e| map_hresult(e.code(), "CreateDXGIFactory2"))?
};
let super::adapter::Selection { adapter, device } = super::adapter::select(&factory)?;
let adapter3: Option<IDXGIAdapter3> = adapter.cast().ok();
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| map_hresult(e.code(), "CreateCommandQueue"))?;
let max_edr = measure_max_edr(&adapter);
let hdr_display_requested = swapchain_config.hdr_display;
let mut hdr_mode =
HdrOutputMode::resolve(hdr_display_requested, swapchain_config.hdr_pq, 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| map_hresult(e.code(), "CreateSwapChain"))?;
let swapchain: IDXGISwapChain3 = sc_base
.cast()
.map_err(|e| map_hresult(e.code(), "SwapChain3 cast"))?;
if hdr_mode.is_hdr() {
let pq_max_edr = match hdr_mode {
HdrOutputMode::Hdr {
max_edr,
encoding: hdr_output::HdrEncoding::Pq,
} => Some(max_edr),
_ => None,
};
let (primary, primary_label) = if pq_max_edr.is_some() {
(HDR_PQ_COLOR_SPACE, "HDR10 PQ")
} else {
(HDR_LINEAR_COLOR_SPACE, "scRGB linear")
};
let outcome = if pq_max_edr.is_some() {
""
} else {
"; leaving the swapchain at its default color space"
};
let primary_attempt = try_color_space(&swapchain, primary);
match &primary_attempt {
ColorSpaceAttempt::Applied => {}
ColorSpaceAttempt::Unsupported(flags) => tracing::warn!(
"HDR display enabled but the swapchain does not advertise {primary_label} \
support (CheckColorSpaceSupport flags = {flags:#x}){outcome}"
),
ColorSpaceAttempt::SetFailed(e) => tracing::warn!(
"HDR display enabled but SetColorSpace1({primary_label}) failed ({e}); the \
compositor may still treat the RGBA16Float swapchain as sRGB{outcome}"
),
}
if !matches!(primary_attempt, ColorSpaceAttempt::Applied)
&& let Some(max_edr) = pq_max_edr
{
match try_color_space(&swapchain, HDR_LINEAR_COLOR_SPACE) {
ColorSpaceAttempt::Applied => {
tracing::warn!("falling back to scRGB linear extended-range output");
hdr_mode = HdrOutputMode::Hdr {
max_edr,
encoding: hdr_output::HdrEncoding::ExtendedLinear,
};
}
ColorSpaceAttempt::Unsupported(flags) => tracing::warn!(
"the swapchain does not advertise the scRGB linear fallback either \
(CheckColorSpaceSupport flags = {flags:#x}); leaving the swapchain at its \
default color space"
),
ColorSpaceAttempt::SetFailed(e) => tracing::warn!(
"scRGB linear fallback also failed ({e}); leaving the swapchain at its \
default color space"
),
}
}
}
unsafe { factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }.ok();
let hw = DxHardware {
alloc: DeviceAllocator::new(&device, &command_queue, FRAMES),
rt_capable: crate::directx::raytrace::raytracing_supported(&device),
device,
command_queue,
adapter: adapter3,
info_queue,
hdr_mode,
swapchain_config,
fullscreen_display: FullscreenDisplayMode::new(),
win_state: Some(win_state),
};
let swapchain = DxgiSwapchain {
handle: swapchain,
format: swapchain_format,
allow_tearing,
};
Ok((hw, swapchain))
}
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;
enum ColorSpaceAttempt {
Applied,
Unsupported(u32),
SetFailed(windows::core::Error),
}
fn try_color_space(swapchain: &IDXGISwapChain3, space: DXGI_COLOR_SPACE_TYPE) -> ColorSpaceAttempt {
let support = unsafe { swapchain.CheckColorSpaceSupport(space) }.unwrap_or(0);
if (support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT.0 as u32) == 0 {
return ColorSpaceAttempt::Unsupported(support);
}
match unsafe { swapchain.SetColorSpace1(space) } {
Ok(()) => ColorSpaceAttempt::Applied,
Err(e) => ColorSpaceAttempt::SetFailed(e),
}
}
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
}
pub(super) 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
}
pub(super) fn resolve_sample_count(supported: u32, requested: u32) -> u32 {
for candidate in [4u32, 2] {
if supported >= candidate && requested >= candidate {
return candidate;
}
}
1
}
#[cfg(test)]
mod tests {
use super::resolve_sample_count;
#[test]
fn a_temporal_request_collapses_to_one_sample() {
for supported in [4, 2, 1] {
assert_eq!(resolve_sample_count(supported, 1), 1);
}
}
#[test]
fn a_multisample_request_clamps_to_adapter_support() {
assert_eq!(resolve_sample_count(4, 4), 4);
assert_eq!(resolve_sample_count(2, 4), 2);
assert_eq!(resolve_sample_count(1, 4), 1);
}
}