use super::types::LogicalDevice;
use anyhow::{Context, Result};
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use windows::Win32::{
Foundation::{CloseHandle, WAIT_OBJECT_0},
Graphics::Direct3D12::{ID3D12CommandList, ID3D12CommandQueue, ID3D12Fence},
System::Threading::{CreateEventA, WaitForSingleObject, INFINITE},
};
use crate::types::{
AddressMode, CompareFunction, DepthFormat, FilterMode, IndexFormat, PrimitiveTopology, TextureFormat, VertexFormat,
};
use windows::Win32::Graphics::{Direct3D, Direct3D12, Dxgi};
pub fn format_to_dxgi(format: TextureFormat) -> Dxgi::Common::DXGI_FORMAT {
match format {
TextureFormat::R8Unorm => Dxgi::Common::DXGI_FORMAT_R8_UNORM,
TextureFormat::Rg8Unorm => Dxgi::Common::DXGI_FORMAT_R8G8_UNORM,
TextureFormat::Rgba8UnormSrgb => Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
TextureFormat::Rgba8Unorm => Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM,
TextureFormat::Bgra8UnormSrgb => Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,
TextureFormat::Bgra8Unorm => Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM,
TextureFormat::Rgba16Float => Dxgi::Common::DXGI_FORMAT_R16G16B16A16_FLOAT,
TextureFormat::Rgba32Float => Dxgi::Common::DXGI_FORMAT_R32G32B32A32_FLOAT,
}
}
pub fn dxgi_to_format(format: Dxgi::Common::DXGI_FORMAT) -> Option<TextureFormat> {
match format {
Dxgi::Common::DXGI_FORMAT_R8_UNORM => Some(TextureFormat::R8Unorm),
Dxgi::Common::DXGI_FORMAT_R8G8_UNORM => Some(TextureFormat::Rg8Unorm),
Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => Some(TextureFormat::Rgba8UnormSrgb),
Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM => Some(TextureFormat::Rgba8Unorm),
Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => Some(TextureFormat::Bgra8UnormSrgb),
Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM => Some(TextureFormat::Bgra8Unorm),
Dxgi::Common::DXGI_FORMAT_R16G16B16A16_FLOAT => Some(TextureFormat::Rgba16Float),
Dxgi::Common::DXGI_FORMAT_R32G32B32A32_FLOAT => Some(TextureFormat::Rgba32Float),
_ => None,
}
}
pub fn vertex_format_to_dxgi(format: VertexFormat) -> Dxgi::Common::DXGI_FORMAT {
match format {
VertexFormat::Float32 => Dxgi::Common::DXGI_FORMAT_R32_FLOAT,
VertexFormat::Float32x2 => Dxgi::Common::DXGI_FORMAT_R32G32_FLOAT,
VertexFormat::Float32x3 => Dxgi::Common::DXGI_FORMAT_R32G32B32_FLOAT,
VertexFormat::Float32x4 => Dxgi::Common::DXGI_FORMAT_R32G32B32A32_FLOAT,
VertexFormat::Uint32 => Dxgi::Common::DXGI_FORMAT_R32_UINT,
VertexFormat::Sint32 => Dxgi::Common::DXGI_FORMAT_R32_SINT,
VertexFormat::Uint8x4 => Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UINT,
VertexFormat::Unorm8x4 => Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM,
}
}
pub fn topology_to_d3d12(topology: PrimitiveTopology) -> Direct3D::D3D_PRIMITIVE_TOPOLOGY {
match topology {
PrimitiveTopology::PointList => Direct3D::D3D_PRIMITIVE_TOPOLOGY_POINTLIST,
PrimitiveTopology::LineList => Direct3D::D3D_PRIMITIVE_TOPOLOGY_LINELIST,
PrimitiveTopology::LineStrip => Direct3D::D3D_PRIMITIVE_TOPOLOGY_LINESTRIP,
PrimitiveTopology::TriangleList => Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
PrimitiveTopology::TriangleStrip => Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
}
}
pub fn topology_type_to_d3d12(topology: PrimitiveTopology) -> Direct3D12::D3D12_PRIMITIVE_TOPOLOGY_TYPE {
match topology {
PrimitiveTopology::PointList => Direct3D12::D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT,
PrimitiveTopology::LineList | PrimitiveTopology::LineStrip => Direct3D12::D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE,
PrimitiveTopology::TriangleList | PrimitiveTopology::TriangleStrip => {
Direct3D12::D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE
}
}
}
pub fn index_format_to_dxgi(format: IndexFormat) -> Dxgi::Common::DXGI_FORMAT {
match format {
IndexFormat::Uint16 => Dxgi::Common::DXGI_FORMAT_R16_UINT,
IndexFormat::Uint32 => Dxgi::Common::DXGI_FORMAT_R32_UINT,
}
}
pub fn vendor_name(vendor_id: u32) -> &'static str {
match vendor_id {
0x1002 | 0x1022 => "AMD",
0x10DE => "NVIDIA",
0x8086 => "Intel",
0x13B5 => "ARM",
0x5143 => "Qualcomm",
0x106B => "Apple",
0x1414 => "Microsoft", _ => "Unknown",
}
}
pub fn device_type_from_flags(flags: Dxgi::DXGI_ADAPTER_FLAG) -> crate::types::DeviceType {
if flags.contains(Dxgi::DXGI_ADAPTER_FLAG_SOFTWARE) {
crate::types::DeviceType::Cpu
} else {
crate::types::DeviceType::DiscreteGpu
}
}
pub fn depth_format_to_dxgi(format: DepthFormat) -> Dxgi::Common::DXGI_FORMAT {
match format {
DepthFormat::Depth16Unorm => Dxgi::Common::DXGI_FORMAT_D16_UNORM,
DepthFormat::Depth24Plus => Dxgi::Common::DXGI_FORMAT_D32_FLOAT,
DepthFormat::Depth24PlusStencil8 => Dxgi::Common::DXGI_FORMAT_D24_UNORM_S8_UINT,
DepthFormat::Depth32Float => Dxgi::Common::DXGI_FORMAT_D32_FLOAT,
DepthFormat::Depth32FloatStencil8 => Dxgi::Common::DXGI_FORMAT_D32_FLOAT_S8X24_UINT,
}
}
pub fn compare_to_d3d12(compare: CompareFunction) -> Direct3D12::D3D12_COMPARISON_FUNC {
match compare {
CompareFunction::Never => Direct3D12::D3D12_COMPARISON_FUNC_NEVER,
CompareFunction::Less => Direct3D12::D3D12_COMPARISON_FUNC_LESS,
CompareFunction::Equal => Direct3D12::D3D12_COMPARISON_FUNC_EQUAL,
CompareFunction::LessEqual => Direct3D12::D3D12_COMPARISON_FUNC_LESS_EQUAL,
CompareFunction::Greater => Direct3D12::D3D12_COMPARISON_FUNC_GREATER,
CompareFunction::NotEqual => Direct3D12::D3D12_COMPARISON_FUNC_NOT_EQUAL,
CompareFunction::GreaterEqual => Direct3D12::D3D12_COMPARISON_FUNC_GREATER_EQUAL,
CompareFunction::Always => Direct3D12::D3D12_COMPARISON_FUNC_ALWAYS,
}
}
pub fn filter_to_d3d12(min: FilterMode, mag: FilterMode, mip: FilterMode) -> Direct3D12::D3D12_FILTER {
match (min, mag, mip) {
(FilterMode::Nearest, FilterMode::Nearest, FilterMode::Nearest) => Direct3D12::D3D12_FILTER_MIN_MAG_MIP_POINT,
(FilterMode::Nearest, FilterMode::Nearest, FilterMode::Linear) => {
Direct3D12::D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR
}
(FilterMode::Nearest, FilterMode::Linear, FilterMode::Nearest) => {
Direct3D12::D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT
}
(FilterMode::Nearest, FilterMode::Linear, FilterMode::Linear) => {
Direct3D12::D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR
}
(FilterMode::Linear, FilterMode::Nearest, FilterMode::Nearest) => {
Direct3D12::D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT
}
(FilterMode::Linear, FilterMode::Nearest, FilterMode::Linear) => {
Direct3D12::D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR
}
(FilterMode::Linear, FilterMode::Linear, FilterMode::Nearest) => {
Direct3D12::D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT
}
(FilterMode::Linear, FilterMode::Linear, FilterMode::Linear) => Direct3D12::D3D12_FILTER_MIN_MAG_MIP_LINEAR,
}
}
pub fn address_mode_to_d3d12(mode: AddressMode) -> Direct3D12::D3D12_TEXTURE_ADDRESS_MODE {
match mode {
AddressMode::ClampToEdge => Direct3D12::D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressMode::Repeat => Direct3D12::D3D12_TEXTURE_ADDRESS_MODE_WRAP,
AddressMode::MirrorRepeat => Direct3D12::D3D12_TEXTURE_ADDRESS_MODE_MIRROR,
}
}
pub(super) fn execute_command_lists_and_signal_device(
logical_device: &LogicalDevice,
command_lists: &[Option<ID3D12CommandList>],
) -> Result<u64> {
execute_with_waits_and_signal_device(logical_device, &[], command_lists)
}
pub(super) fn execute_with_waits_and_signal_device(
logical_device: &LogicalDevice,
waits: &[(ID3D12Fence, u64)],
command_lists: &[Option<ID3D12CommandList>],
) -> Result<u64> {
let queue_lock = Arc::clone(&logical_device.queue_lock);
let _guard = queue_lock.lock().unwrap();
let api_log_on = super::api_log::enabled();
let queue_id = if api_log_on {
super::api_log::com_identity(&logical_device.command_queue)
} else {
0
};
for (producer_fence, value) in waits {
if api_log_on {
super::api_log::log_queue_wait(queue_id, super::api_log::com_identity(producer_fence), *value);
}
unsafe { logical_device.command_queue.Wait(producer_fence, *value) }
.context("device queue Wait before present copy")?;
}
let fence_value = logical_device.timeline_next.fetch_add(1, Ordering::Relaxed);
if api_log_on {
super::api_log::log_execute_command_lists(queue_id, command_lists.len());
}
unsafe {
logical_device.command_queue.ExecuteCommandLists(command_lists);
}
unsafe { logical_device.command_queue.Signal(&logical_device.fence, fence_value) }
.context("Failed to signal device fence")?;
logical_device
.device_last_submitted_seq
.store(fence_value, Ordering::Relaxed);
if api_log_on {
super::api_log::log_queue_signal(
queue_id,
super::api_log::com_identity(&logical_device.fence),
fence_value,
);
}
Ok(fence_value)
}
pub(super) fn execute_preallocated_context_submit(
_logical_device: &LogicalDevice,
queue: &ID3D12CommandQueue,
queue_lock: &Arc<Mutex<()>>,
ctx_fence: &ID3D12Fence,
command_lists: &[Option<ID3D12CommandList>],
queue_waits: &[(ID3D12Fence, u64)],
tv: u64,
) -> Result<()> {
let _guard = queue_lock.lock().unwrap();
if !queue_waits.is_empty() {
let _tz = crate::tracy_zone!("goldy.submit_worker.dx12.queue_wait");
for (fence, value) in queue_waits {
unsafe { queue.Wait(fence, *value).context("cross-submit GPU queue Wait")? };
}
}
{
let _tz = crate::tracy_zone!("goldy.submit_worker.dx12.execute_and_signal");
unsafe {
queue.ExecuteCommandLists(command_lists);
}
unsafe { queue.Signal(ctx_fence, tv).context("Failed to signal context fence")? };
}
Ok(())
}
pub(super) fn with_queue_lock<R>(logical_device: &LogicalDevice, f: impl FnOnce() -> R) -> R {
let queue_lock = Arc::clone(&logical_device.queue_lock);
let _guard = queue_lock.lock().unwrap();
f()
}
pub(super) fn wait_for_fence(fence: &ID3D12Fence, value: u64) -> Result<()> {
wait_for_fence_on_device(fence, value, None)
}
pub(super) fn wait_for_fence_on_device(
fence: &ID3D12Fence,
value: u64,
ld: Option<&super::types::LogicalDevice>,
) -> Result<()> {
let completed = unsafe { fence.GetCompletedValue() };
if completed == u64::MAX {
if let Some(ld) = ld {
super::diagnostic::first_touch_device_removed(
&ld.device,
&ld.device_removed,
"dx12::utils::wait_for_fence_on_device",
value,
completed,
);
}
anyhow::bail!("GPU device removed while waiting for fence value {value}");
}
if completed < value {
let event = unsafe { CreateEventA(None, false, false, None) }.context("Failed to create event")?;
unsafe { fence.SetEventOnCompletion(value, event) }.context("Failed to set event on completion")?;
unsafe { WaitForSingleObject(event, INFINITE) };
unsafe { CloseHandle(event) }.ok();
}
let completed_after = unsafe { fence.GetCompletedValue() };
if completed_after == u64::MAX {
if let Some(ld) = ld {
super::diagnostic::first_touch_device_removed(
&ld.device,
&ld.device_removed,
"dx12::utils::wait_for_fence_on_device",
value,
completed_after,
);
}
anyhow::bail!("GPU device removed after waiting for fence value {value}");
}
Ok(())
}
pub(super) fn wait_for_fence_timeout(fence: &ID3D12Fence, value: u64, timeout_ms: u32) -> Result<bool> {
if unsafe { fence.GetCompletedValue() } >= value {
return Ok(true);
}
let event = unsafe { CreateEventA(None, false, false, None) }.context("Failed to create event")?;
unsafe { fence.SetEventOnCompletion(value, event) }.context("Failed to set event on completion")?;
let result = unsafe { WaitForSingleObject(event, timeout_ms) };
unsafe { CloseHandle(event) }.ok();
Ok(result == WAIT_OBJECT_0)
}