Vendor wgpu-hal 29.0.4 and i-slint-renderer-skia 1.17.1, unmodified
The Android develop view reads its frame back through memory (TD-1) because wgpu's Vulkan swapchain never pre-rotates, and a portrait window on this tablet's landscape panel then tears. The fix is a small patch to each of these two crates, and this commit is only the ground it lands on: both are byte-for-byte the crates.io sources the lockfile already resolved, so the commits that follow are the patch and nothing else. third_party/ is excluded from the workspace, or every path dependency under the root would become a member and `--workspace` would test and lint upstream code as ours. The README says how to carry the patches across a Slint or wgpu bump, which matters because a stale version here does not fail the build — cargo just warns and uses the unpatched crate.
This commit is contained in:
+295
@@ -0,0 +1,295 @@
|
||||
use alloc::string::String;
|
||||
use std::{ffi::OsString, os::windows::ffi::OsStringExt};
|
||||
|
||||
use windows::Win32::Graphics::Dxgi;
|
||||
|
||||
// Helper to convert DXGI adapter name to a normal string
|
||||
pub fn map_adapter_name(name: [u16; 128]) -> String {
|
||||
let len = name.iter().take_while(|&&c| c != 0).count();
|
||||
let name = OsString::from_wide(&name[..len]);
|
||||
name.to_string_lossy().into_owned()
|
||||
}
|
||||
|
||||
pub fn map_texture_format_failable(
|
||||
format: wgt::TextureFormat,
|
||||
) -> Option<Dxgi::Common::DXGI_FORMAT> {
|
||||
use wgt::TextureFormat as Tf;
|
||||
use Dxgi::Common::*;
|
||||
|
||||
Some(match format {
|
||||
Tf::R8Unorm => DXGI_FORMAT_R8_UNORM,
|
||||
Tf::R8Snorm => DXGI_FORMAT_R8_SNORM,
|
||||
Tf::R8Uint => DXGI_FORMAT_R8_UINT,
|
||||
Tf::R8Sint => DXGI_FORMAT_R8_SINT,
|
||||
Tf::R16Uint => DXGI_FORMAT_R16_UINT,
|
||||
Tf::R16Sint => DXGI_FORMAT_R16_SINT,
|
||||
Tf::R16Unorm => DXGI_FORMAT_R16_UNORM,
|
||||
Tf::R16Snorm => DXGI_FORMAT_R16_SNORM,
|
||||
Tf::R16Float => DXGI_FORMAT_R16_FLOAT,
|
||||
Tf::Rg8Unorm => DXGI_FORMAT_R8G8_UNORM,
|
||||
Tf::Rg8Snorm => DXGI_FORMAT_R8G8_SNORM,
|
||||
Tf::Rg8Uint => DXGI_FORMAT_R8G8_UINT,
|
||||
Tf::Rg8Sint => DXGI_FORMAT_R8G8_SINT,
|
||||
Tf::Rg16Unorm => DXGI_FORMAT_R16G16_UNORM,
|
||||
Tf::Rg16Snorm => DXGI_FORMAT_R16G16_SNORM,
|
||||
Tf::R32Uint => DXGI_FORMAT_R32_UINT,
|
||||
Tf::R32Sint => DXGI_FORMAT_R32_SINT,
|
||||
Tf::R32Float => DXGI_FORMAT_R32_FLOAT,
|
||||
Tf::Rg16Uint => DXGI_FORMAT_R16G16_UINT,
|
||||
Tf::Rg16Sint => DXGI_FORMAT_R16G16_SINT,
|
||||
Tf::Rg16Float => DXGI_FORMAT_R16G16_FLOAT,
|
||||
Tf::Rgba8Unorm => DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
Tf::Rgba8UnormSrgb => DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
|
||||
Tf::Bgra8UnormSrgb => DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,
|
||||
Tf::Rgba8Snorm => DXGI_FORMAT_R8G8B8A8_SNORM,
|
||||
Tf::Bgra8Unorm => DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
Tf::Rgba8Uint => DXGI_FORMAT_R8G8B8A8_UINT,
|
||||
Tf::Rgba8Sint => DXGI_FORMAT_R8G8B8A8_SINT,
|
||||
Tf::Rgb9e5Ufloat => DXGI_FORMAT_R9G9B9E5_SHAREDEXP,
|
||||
Tf::Rgb10a2Uint => DXGI_FORMAT_R10G10B10A2_UINT,
|
||||
Tf::Rgb10a2Unorm => DXGI_FORMAT_R10G10B10A2_UNORM,
|
||||
Tf::Rg11b10Ufloat => DXGI_FORMAT_R11G11B10_FLOAT,
|
||||
Tf::R64Uint => DXGI_FORMAT_R32G32_UINT, // R64 emulated by R32G32
|
||||
Tf::Rg32Uint => DXGI_FORMAT_R32G32_UINT,
|
||||
Tf::Rg32Sint => DXGI_FORMAT_R32G32_SINT,
|
||||
Tf::Rg32Float => DXGI_FORMAT_R32G32_FLOAT,
|
||||
Tf::Rgba16Uint => DXGI_FORMAT_R16G16B16A16_UINT,
|
||||
Tf::Rgba16Sint => DXGI_FORMAT_R16G16B16A16_SINT,
|
||||
Tf::Rgba16Unorm => DXGI_FORMAT_R16G16B16A16_UNORM,
|
||||
Tf::Rgba16Snorm => DXGI_FORMAT_R16G16B16A16_SNORM,
|
||||
Tf::Rgba16Float => DXGI_FORMAT_R16G16B16A16_FLOAT,
|
||||
Tf::Rgba32Uint => DXGI_FORMAT_R32G32B32A32_UINT,
|
||||
Tf::Rgba32Sint => DXGI_FORMAT_R32G32B32A32_SINT,
|
||||
Tf::Rgba32Float => DXGI_FORMAT_R32G32B32A32_FLOAT,
|
||||
Tf::Stencil8 => DXGI_FORMAT_D24_UNORM_S8_UINT,
|
||||
Tf::Depth16Unorm => DXGI_FORMAT_D16_UNORM,
|
||||
Tf::Depth24Plus => DXGI_FORMAT_D24_UNORM_S8_UINT,
|
||||
Tf::Depth24PlusStencil8 => DXGI_FORMAT_D24_UNORM_S8_UINT,
|
||||
Tf::Depth32Float => DXGI_FORMAT_D32_FLOAT,
|
||||
Tf::Depth32FloatStencil8 => DXGI_FORMAT_D32_FLOAT_S8X24_UINT,
|
||||
Tf::NV12 => DXGI_FORMAT_NV12,
|
||||
Tf::P010 => DXGI_FORMAT_P010,
|
||||
Tf::Bc1RgbaUnorm => DXGI_FORMAT_BC1_UNORM,
|
||||
Tf::Bc1RgbaUnormSrgb => DXGI_FORMAT_BC1_UNORM_SRGB,
|
||||
Tf::Bc2RgbaUnorm => DXGI_FORMAT_BC2_UNORM,
|
||||
Tf::Bc2RgbaUnormSrgb => DXGI_FORMAT_BC2_UNORM_SRGB,
|
||||
Tf::Bc3RgbaUnorm => DXGI_FORMAT_BC3_UNORM,
|
||||
Tf::Bc3RgbaUnormSrgb => DXGI_FORMAT_BC3_UNORM_SRGB,
|
||||
Tf::Bc4RUnorm => DXGI_FORMAT_BC4_UNORM,
|
||||
Tf::Bc4RSnorm => DXGI_FORMAT_BC4_SNORM,
|
||||
Tf::Bc5RgUnorm => DXGI_FORMAT_BC5_UNORM,
|
||||
Tf::Bc5RgSnorm => DXGI_FORMAT_BC5_SNORM,
|
||||
Tf::Bc6hRgbUfloat => DXGI_FORMAT_BC6H_UF16,
|
||||
Tf::Bc6hRgbFloat => DXGI_FORMAT_BC6H_SF16,
|
||||
Tf::Bc7RgbaUnorm => DXGI_FORMAT_BC7_UNORM,
|
||||
Tf::Bc7RgbaUnormSrgb => DXGI_FORMAT_BC7_UNORM_SRGB,
|
||||
Tf::Etc2Rgb8Unorm
|
||||
| Tf::Etc2Rgb8UnormSrgb
|
||||
| Tf::Etc2Rgb8A1Unorm
|
||||
| Tf::Etc2Rgb8A1UnormSrgb
|
||||
| Tf::Etc2Rgba8Unorm
|
||||
| Tf::Etc2Rgba8UnormSrgb
|
||||
| Tf::EacR11Unorm
|
||||
| Tf::EacR11Snorm
|
||||
| Tf::EacRg11Unorm
|
||||
| Tf::EacRg11Snorm
|
||||
| Tf::Astc {
|
||||
block: _,
|
||||
channel: _,
|
||||
} => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn map_texture_format(format: wgt::TextureFormat) -> Dxgi::Common::DXGI_FORMAT {
|
||||
match map_texture_format_failable(format) {
|
||||
Some(f) => f,
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Note: DXGI doesn't allow sRGB format on the swapchain,
|
||||
// but creating RTV of swapchain buffers with sRGB works.
|
||||
pub fn map_texture_format_nosrgb(format: wgt::TextureFormat) -> Dxgi::Common::DXGI_FORMAT {
|
||||
match format {
|
||||
wgt::TextureFormat::Bgra8UnormSrgb => Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
wgt::TextureFormat::Rgba8UnormSrgb => Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
_ => map_texture_format(format),
|
||||
}
|
||||
}
|
||||
|
||||
// SRV and UAV can't use the depth or typeless formats
|
||||
// see https://microsoft.github.io/DirectX-Specs/d3d/PlanarDepthStencilDDISpec.html#view-creation
|
||||
pub fn map_texture_format_for_srv_uav(
|
||||
format: wgt::TextureFormat,
|
||||
aspect: crate::FormatAspects,
|
||||
) -> Option<Dxgi::Common::DXGI_FORMAT> {
|
||||
Some(match (format, aspect) {
|
||||
(wgt::TextureFormat::Depth16Unorm, crate::FormatAspects::DEPTH) => {
|
||||
Dxgi::Common::DXGI_FORMAT_R16_UNORM
|
||||
}
|
||||
(wgt::TextureFormat::Depth32Float, crate::FormatAspects::DEPTH) => {
|
||||
Dxgi::Common::DXGI_FORMAT_R32_FLOAT
|
||||
}
|
||||
(wgt::TextureFormat::Depth32FloatStencil8, crate::FormatAspects::DEPTH) => {
|
||||
Dxgi::Common::DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS
|
||||
}
|
||||
(
|
||||
wgt::TextureFormat::Depth24Plus | wgt::TextureFormat::Depth24PlusStencil8,
|
||||
crate::FormatAspects::DEPTH,
|
||||
) => Dxgi::Common::DXGI_FORMAT_R24_UNORM_X8_TYPELESS,
|
||||
|
||||
(wgt::TextureFormat::Depth32FloatStencil8, crate::FormatAspects::STENCIL) => {
|
||||
Dxgi::Common::DXGI_FORMAT_X32_TYPELESS_G8X24_UINT
|
||||
}
|
||||
(
|
||||
wgt::TextureFormat::Stencil8 | wgt::TextureFormat::Depth24PlusStencil8,
|
||||
crate::FormatAspects::STENCIL,
|
||||
) => Dxgi::Common::DXGI_FORMAT_X24_TYPELESS_G8_UINT,
|
||||
|
||||
(_, crate::FormatAspects::DEPTH)
|
||||
| (_, crate::FormatAspects::STENCIL)
|
||||
| (_, crate::FormatAspects::DEPTH_STENCIL) => return None,
|
||||
|
||||
_ => map_texture_format(format),
|
||||
})
|
||||
}
|
||||
|
||||
// see https://microsoft.github.io/DirectX-Specs/d3d/PlanarDepthStencilDDISpec.html#planar-layout-for-staging-from-buffer
|
||||
pub fn map_texture_format_for_copy(
|
||||
format: wgt::TextureFormat,
|
||||
aspect: crate::FormatAspects,
|
||||
) -> Option<Dxgi::Common::DXGI_FORMAT> {
|
||||
Some(match (format, aspect) {
|
||||
(wgt::TextureFormat::Depth16Unorm, crate::FormatAspects::DEPTH) => {
|
||||
Dxgi::Common::DXGI_FORMAT_R16_UNORM
|
||||
}
|
||||
(
|
||||
wgt::TextureFormat::Depth32Float | wgt::TextureFormat::Depth32FloatStencil8,
|
||||
crate::FormatAspects::DEPTH,
|
||||
) => Dxgi::Common::DXGI_FORMAT_R32_FLOAT,
|
||||
|
||||
(
|
||||
wgt::TextureFormat::Stencil8
|
||||
| wgt::TextureFormat::Depth24PlusStencil8
|
||||
| wgt::TextureFormat::Depth32FloatStencil8,
|
||||
crate::FormatAspects::STENCIL,
|
||||
) => Dxgi::Common::DXGI_FORMAT_R8_UINT,
|
||||
|
||||
(format, crate::FormatAspects::COLOR) => map_texture_format(format),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn map_texture_format_for_resource(
|
||||
format: wgt::TextureFormat,
|
||||
usage: wgt::TextureUses,
|
||||
has_view_formats: bool,
|
||||
casting_fully_typed_format_supported: bool,
|
||||
) -> Dxgi::Common::DXGI_FORMAT {
|
||||
use wgt::TextureFormat as Tf;
|
||||
use Dxgi::Common::*;
|
||||
|
||||
if casting_fully_typed_format_supported {
|
||||
map_texture_format(format)
|
||||
|
||||
// We might view this resource as srgb or non-srgb
|
||||
} else if has_view_formats {
|
||||
match format {
|
||||
Tf::Rgba8Unorm | Tf::Rgba8UnormSrgb => DXGI_FORMAT_R8G8B8A8_TYPELESS,
|
||||
Tf::Bgra8Unorm | Tf::Bgra8UnormSrgb => DXGI_FORMAT_B8G8R8A8_TYPELESS,
|
||||
Tf::Bc1RgbaUnorm | Tf::Bc1RgbaUnormSrgb => DXGI_FORMAT_BC1_TYPELESS,
|
||||
Tf::Bc2RgbaUnorm | Tf::Bc2RgbaUnormSrgb => DXGI_FORMAT_BC2_TYPELESS,
|
||||
Tf::Bc3RgbaUnorm | Tf::Bc3RgbaUnormSrgb => DXGI_FORMAT_BC3_TYPELESS,
|
||||
Tf::Bc7RgbaUnorm | Tf::Bc7RgbaUnormSrgb => DXGI_FORMAT_BC7_TYPELESS,
|
||||
format => map_texture_format(format),
|
||||
}
|
||||
|
||||
// We might view this resource as SRV/UAV but also as DSV
|
||||
} else if format.is_depth_stencil_format()
|
||||
&& usage.intersects(
|
||||
wgt::TextureUses::RESOURCE
|
||||
| wgt::TextureUses::STORAGE_READ_ONLY
|
||||
| wgt::TextureUses::STORAGE_WRITE_ONLY
|
||||
| wgt::TextureUses::STORAGE_READ_WRITE,
|
||||
)
|
||||
{
|
||||
match format {
|
||||
Tf::Depth16Unorm => DXGI_FORMAT_R16_TYPELESS,
|
||||
Tf::Depth32Float => DXGI_FORMAT_R32_TYPELESS,
|
||||
Tf::Depth32FloatStencil8 => DXGI_FORMAT_R32G8X24_TYPELESS,
|
||||
Tf::Stencil8 | Tf::Depth24Plus | Tf::Depth24PlusStencil8 => DXGI_FORMAT_R24G8_TYPELESS,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
map_texture_format(format)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_index_format(format: wgt::IndexFormat) -> Dxgi::Common::DXGI_FORMAT {
|
||||
match format {
|
||||
wgt::IndexFormat::Uint16 => Dxgi::Common::DXGI_FORMAT_R16_UINT,
|
||||
wgt::IndexFormat::Uint32 => Dxgi::Common::DXGI_FORMAT_R32_UINT,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_vertex_format(format: wgt::VertexFormat) -> Dxgi::Common::DXGI_FORMAT {
|
||||
use wgt::VertexFormat as Vf;
|
||||
use Dxgi::Common::*;
|
||||
|
||||
match format {
|
||||
Vf::Unorm8 => DXGI_FORMAT_R8_UNORM,
|
||||
Vf::Snorm8 => DXGI_FORMAT_R8_SNORM,
|
||||
Vf::Uint8 => DXGI_FORMAT_R8_UINT,
|
||||
Vf::Sint8 => DXGI_FORMAT_R8_SINT,
|
||||
Vf::Unorm8x2 => DXGI_FORMAT_R8G8_UNORM,
|
||||
Vf::Snorm8x2 => DXGI_FORMAT_R8G8_SNORM,
|
||||
Vf::Uint8x2 => DXGI_FORMAT_R8G8_UINT,
|
||||
Vf::Sint8x2 => DXGI_FORMAT_R8G8_SINT,
|
||||
Vf::Unorm8x4 => DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
Vf::Snorm8x4 => DXGI_FORMAT_R8G8B8A8_SNORM,
|
||||
Vf::Uint8x4 => DXGI_FORMAT_R8G8B8A8_UINT,
|
||||
Vf::Sint8x4 => DXGI_FORMAT_R8G8B8A8_SINT,
|
||||
Vf::Unorm16 => DXGI_FORMAT_R16_UNORM,
|
||||
Vf::Snorm16 => DXGI_FORMAT_R16_SNORM,
|
||||
Vf::Uint16 => DXGI_FORMAT_R16_UINT,
|
||||
Vf::Sint16 => DXGI_FORMAT_R16_SINT,
|
||||
Vf::Float16 => DXGI_FORMAT_R16_FLOAT,
|
||||
Vf::Unorm16x2 => DXGI_FORMAT_R16G16_UNORM,
|
||||
Vf::Snorm16x2 => DXGI_FORMAT_R16G16_SNORM,
|
||||
Vf::Uint16x2 => DXGI_FORMAT_R16G16_UINT,
|
||||
Vf::Sint16x2 => DXGI_FORMAT_R16G16_SINT,
|
||||
Vf::Float16x2 => DXGI_FORMAT_R16G16_FLOAT,
|
||||
Vf::Unorm16x4 => DXGI_FORMAT_R16G16B16A16_UNORM,
|
||||
Vf::Snorm16x4 => DXGI_FORMAT_R16G16B16A16_SNORM,
|
||||
Vf::Uint16x4 => DXGI_FORMAT_R16G16B16A16_UINT,
|
||||
Vf::Sint16x4 => DXGI_FORMAT_R16G16B16A16_SINT,
|
||||
Vf::Float16x4 => DXGI_FORMAT_R16G16B16A16_FLOAT,
|
||||
Vf::Uint32 => DXGI_FORMAT_R32_UINT,
|
||||
Vf::Sint32 => DXGI_FORMAT_R32_SINT,
|
||||
Vf::Float32 => DXGI_FORMAT_R32_FLOAT,
|
||||
Vf::Uint32x2 => DXGI_FORMAT_R32G32_UINT,
|
||||
Vf::Sint32x2 => DXGI_FORMAT_R32G32_SINT,
|
||||
Vf::Float32x2 => DXGI_FORMAT_R32G32_FLOAT,
|
||||
Vf::Uint32x3 => DXGI_FORMAT_R32G32B32_UINT,
|
||||
Vf::Sint32x3 => DXGI_FORMAT_R32G32B32_SINT,
|
||||
Vf::Float32x3 => DXGI_FORMAT_R32G32B32_FLOAT,
|
||||
Vf::Uint32x4 => DXGI_FORMAT_R32G32B32A32_UINT,
|
||||
Vf::Sint32x4 => DXGI_FORMAT_R32G32B32A32_SINT,
|
||||
Vf::Float32x4 => DXGI_FORMAT_R32G32B32A32_FLOAT,
|
||||
Vf::Unorm10_10_10_2 => DXGI_FORMAT_R10G10B10A2_UNORM,
|
||||
Vf::Unorm8x4Bgra => DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
Vf::Float64 | Vf::Float64x2 | Vf::Float64x3 | Vf::Float64x4 => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_acomposite_alpha_mode(mode: wgt::CompositeAlphaMode) -> Dxgi::Common::DXGI_ALPHA_MODE {
|
||||
match mode {
|
||||
wgt::CompositeAlphaMode::PreMultiplied => Dxgi::Common::DXGI_ALPHA_MODE_PREMULTIPLIED,
|
||||
wgt::CompositeAlphaMode::PostMultiplied => Dxgi::Common::DXGI_ALPHA_MODE_STRAIGHT,
|
||||
wgt::CompositeAlphaMode::Opaque => Dxgi::Common::DXGI_ALPHA_MODE_IGNORE,
|
||||
wgt::CompositeAlphaMode::Auto | wgt::CompositeAlphaMode::Inherit => {
|
||||
Dxgi::Common::DXGI_ALPHA_MODE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
use alloc::{borrow::Cow, string::String};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use windows::Win32::{Foundation, System::Diagnostics::Debug};
|
||||
|
||||
// This is a mutex as opposed to an atomic as we need to completely
|
||||
// lock everyone out until we have registered or unregistered the
|
||||
// exception handler, otherwise really nasty races could happen.
|
||||
//
|
||||
// By routing all the registration through these functions we can guarantee
|
||||
// there is either 1 or 0 exception handlers registered, not multiple.
|
||||
static EXCEPTION_HANDLER_COUNT: Mutex<usize> = Mutex::new(0);
|
||||
|
||||
pub fn register_exception_handler() {
|
||||
let mut count_guard = EXCEPTION_HANDLER_COUNT.lock();
|
||||
if *count_guard == 0 {
|
||||
unsafe { Debug::AddVectoredExceptionHandler(0, Some(output_debug_string_handler)) };
|
||||
}
|
||||
*count_guard += 1;
|
||||
}
|
||||
|
||||
pub fn unregister_exception_handler() {
|
||||
let mut count_guard = EXCEPTION_HANDLER_COUNT.lock();
|
||||
if *count_guard == 1 {
|
||||
unsafe { Debug::RemoveVectoredExceptionHandler(output_debug_string_handler as *mut _) };
|
||||
}
|
||||
*count_guard -= 1;
|
||||
}
|
||||
|
||||
const MESSAGE_PREFIXES: &[(&str, log::Level)] = &[
|
||||
("CORRUPTION", log::Level::Error),
|
||||
("ERROR", log::Level::Error),
|
||||
("WARNING", log::Level::Warn),
|
||||
// We intentionally suppress "INFO" messages down to debug
|
||||
// so that users are not innundated with info messages from the runtime.
|
||||
("INFO", log::Level::Debug),
|
||||
("MESSAGE", log::Level::Trace),
|
||||
];
|
||||
|
||||
unsafe extern "system" fn output_debug_string_handler(
|
||||
exception_info: *mut Debug::EXCEPTION_POINTERS,
|
||||
) -> i32 {
|
||||
// See https://stackoverflow.com/a/41480827
|
||||
let record = unsafe { &*(*exception_info).ExceptionRecord };
|
||||
if record.NumberParameters != 2 {
|
||||
return Debug::EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
let message = match record.ExceptionCode {
|
||||
Foundation::DBG_PRINTEXCEPTION_C => {
|
||||
String::from_utf8_lossy(bytemuck::cast_slice(&record.ExceptionInformation))
|
||||
}
|
||||
Foundation::DBG_PRINTEXCEPTION_WIDE_C => Cow::Owned(String::from_utf16_lossy(
|
||||
bytemuck::cast_slice(&record.ExceptionInformation),
|
||||
)),
|
||||
_ => return Debug::EXCEPTION_CONTINUE_SEARCH,
|
||||
};
|
||||
|
||||
let message = match message.strip_prefix("D3D12 ") {
|
||||
Some(msg) => msg
|
||||
.trim_end_matches("\n\0")
|
||||
.trim_end_matches("[ STATE_CREATION WARNING #0: UNKNOWN]"),
|
||||
None => return Debug::EXCEPTION_CONTINUE_SEARCH,
|
||||
};
|
||||
|
||||
let (message, level) = match MESSAGE_PREFIXES
|
||||
.iter()
|
||||
.find(|&&(prefix, _)| message.starts_with(prefix))
|
||||
{
|
||||
Some(&(prefix, level)) => (&message[prefix.len() + 2..], level),
|
||||
None => (message, log::Level::Debug),
|
||||
};
|
||||
|
||||
if level == log::Level::Warn && message.contains("#82") {
|
||||
// This is are useless spammy warnings (#820, #821):
|
||||
// "The application did not pass any clear value to resource creation"
|
||||
return Debug::EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
if level == log::Level::Warn && message.contains("DRAW_EMPTY_SCISSOR_RECTANGLE") {
|
||||
// This is normal, WebGPU allows passing empty scissor rectangles.
|
||||
return Debug::EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
let _ = std::panic::catch_unwind(|| {
|
||||
log::log!(level, "{message}");
|
||||
});
|
||||
|
||||
#[cfg(feature = "validation_canary")]
|
||||
if cfg!(debug_assertions) && level == log::Level::Error {
|
||||
use alloc::string::ToString as _;
|
||||
|
||||
// Set canary and continue
|
||||
crate::VALIDATION_CANARY.add(message.to_string());
|
||||
}
|
||||
|
||||
Debug::EXCEPTION_CONTINUE_EXECUTION
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::ops::Deref;
|
||||
|
||||
use windows::{core::Interface as _, Win32::Graphics::Dxgi};
|
||||
|
||||
use crate::dx12::DxgiLib;
|
||||
|
||||
use super::result::HResult as _;
|
||||
|
||||
// We can rely on the presence of DXGI 1.4 since D3D12 requires WDDM 2.0, Windows 10 (1507), and so does DXGI 1.4.
|
||||
|
||||
fn should_keep_adapter(adapter: &Dxgi::IDXGIAdapter1) -> bool {
|
||||
let desc = unsafe { adapter.GetDesc1() }.unwrap();
|
||||
|
||||
// The Intel Haswell family of iGPUs had support for the D3D12 API but it was later
|
||||
// removed due to a security vulnerability.
|
||||
//
|
||||
// We are explicitly filtering out all the devices in the family because we are now
|
||||
// getting reports of device loss at a later time than at device creation time (`D3D12CreateDevice`).
|
||||
//
|
||||
// See https://www.intel.com/content/www/us/en/support/articles/000057520/graphics.html
|
||||
// This list of device IDs is from https://dgpu-docs.intel.com/devices/hardware-table.html
|
||||
let haswell_device_ids = [
|
||||
0x0422, 0x0426, 0x042A, 0x042B, 0x042E, 0x0C22, 0x0C26, 0x0C2A, 0x0C2B, 0x0C2E, 0x0A22,
|
||||
0x0A2A, 0x0A2B, 0x0D2A, 0x0D2B, 0x0D2E, 0x0A26, 0x0A2E, 0x0D22, 0x0D26, 0x0412, 0x0416,
|
||||
0x0D12, 0x041A, 0x041B, 0x0C12, 0x0C16, 0x0C1A, 0x0C1B, 0x0C1E, 0x0A12, 0x0A1A, 0x0A1B,
|
||||
0x0D16, 0x0D1A, 0x0D1B, 0x0D1E, 0x041E, 0x0A16, 0x0A1E, 0x0402, 0x0406, 0x040A, 0x040B,
|
||||
0x040E, 0x0C02, 0x0C06, 0x0C0A, 0x0C0B, 0x0C0E, 0x0A02, 0x0A06, 0x0A0A, 0x0A0B, 0x0A0E,
|
||||
0x0D02, 0x0D06, 0x0D0A, 0x0D0B, 0x0D0E,
|
||||
];
|
||||
if desc.VendorId == 0x8086 && haswell_device_ids.contains(&desc.DeviceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If run completely headless, windows will show two different WARP adapters, one
|
||||
// which is lying about being an integrated card. This is so that programs
|
||||
// that ignore software adapters will actually run on headless/gpu-less machines.
|
||||
//
|
||||
// We don't want that and discourage that kind of filtering anyway, so we skip the integrated WARP.
|
||||
if desc.VendorId == 5140
|
||||
&& !Dxgi::DXGI_ADAPTER_FLAG(desc.Flags as i32).contains(Dxgi::DXGI_ADAPTER_FLAG_SOFTWARE)
|
||||
{
|
||||
let adapter_name = super::conv::map_adapter_name(desc.Description);
|
||||
if adapter_name.contains("Microsoft Basic Render Driver") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DxgiAdapter {
|
||||
/// Provided by DXGI 1.4
|
||||
Adapter3(Dxgi::IDXGIAdapter3),
|
||||
/// Provided by DXGI 1.6
|
||||
Adapter4(Dxgi::IDXGIAdapter4),
|
||||
}
|
||||
|
||||
impl DxgiAdapter {
|
||||
pub fn query_video_memory_info(
|
||||
&self,
|
||||
group: Dxgi::DXGI_MEMORY_SEGMENT_GROUP,
|
||||
) -> Result<Dxgi::DXGI_QUERY_VIDEO_MEMORY_INFO, crate::DeviceError> {
|
||||
let mut info = Dxgi::DXGI_QUERY_VIDEO_MEMORY_INFO::default();
|
||||
unsafe { self.QueryVideoMemoryInfo(0, group, &mut info) }
|
||||
.into_device_result("QueryVideoMemoryInfo")?;
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DxgiAdapter {
|
||||
type Target = Dxgi::IDXGIAdapter3;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
DxgiAdapter::Adapter3(a) => a,
|
||||
DxgiAdapter::Adapter4(a) => a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enumerate_adapters(factory: DxgiFactory) -> Vec<DxgiAdapter> {
|
||||
let mut adapters = Vec::with_capacity(8);
|
||||
|
||||
for cur_index in 0.. {
|
||||
profiling::scope!("IDXGIFactory1::EnumAdapters1");
|
||||
let adapter1: Dxgi::IDXGIAdapter1 = match unsafe { factory.EnumAdapters1(cur_index) } {
|
||||
Ok(a) => a,
|
||||
Err(e) if e.code() == Dxgi::DXGI_ERROR_NOT_FOUND => break,
|
||||
Err(e) => {
|
||||
log::error!("Failed enumerating adapters: {e}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if !should_keep_adapter(&adapter1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(adapter4) = adapter1.cast::<Dxgi::IDXGIAdapter4>() {
|
||||
adapters.push(DxgiAdapter::Adapter4(adapter4));
|
||||
} else {
|
||||
let adapter3 = adapter1.cast::<Dxgi::IDXGIAdapter3>().unwrap();
|
||||
adapters.push(DxgiAdapter::Adapter3(adapter3));
|
||||
}
|
||||
}
|
||||
|
||||
adapters
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DxgiFactory {
|
||||
/// Provided by DXGI 1.4
|
||||
Factory4(Dxgi::IDXGIFactory4),
|
||||
/// Provided by DXGI 1.5
|
||||
Factory5(Dxgi::IDXGIFactory5),
|
||||
/// Provided by DXGI 1.6
|
||||
Factory6(Dxgi::IDXGIFactory6),
|
||||
}
|
||||
|
||||
impl Deref for DxgiFactory {
|
||||
type Target = Dxgi::IDXGIFactory4;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
DxgiFactory::Factory4(f) => f,
|
||||
DxgiFactory::Factory5(f) => f,
|
||||
DxgiFactory::Factory6(f) => f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DxgiFactory {
|
||||
pub fn as_factory5(&self) -> Option<&Dxgi::IDXGIFactory5> {
|
||||
match self {
|
||||
Self::Factory4(_) => None,
|
||||
Self::Factory5(f) => Some(f),
|
||||
Self::Factory6(f) => Some(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_factory(
|
||||
instance_flags: wgt::InstanceFlags,
|
||||
) -> Result<(DxgiLib, DxgiFactory), crate::InstanceError> {
|
||||
let lib_dxgi = DxgiLib::new().map_err(|e| {
|
||||
crate::InstanceError::with_source(String::from("failed to load dxgi.dll"), e)
|
||||
})?;
|
||||
|
||||
let mut factory_flags = Dxgi::DXGI_CREATE_FACTORY_FLAGS::default();
|
||||
|
||||
if instance_flags.contains(wgt::InstanceFlags::VALIDATION) {
|
||||
// The `DXGI_CREATE_FACTORY_DEBUG` flag is only allowed to be passed to
|
||||
// `CreateDXGIFactory2` if the debug interface is actually available. So
|
||||
// we check for whether it exists first.
|
||||
if let Ok(Some(_)) = lib_dxgi.debug_interface1() {
|
||||
factory_flags |= Dxgi::DXGI_CREATE_FACTORY_DEBUG;
|
||||
}
|
||||
}
|
||||
|
||||
let factory4 = match lib_dxgi.create_factory4(factory_flags) {
|
||||
Ok(factory) => factory,
|
||||
Err(err) => {
|
||||
return Err(crate::InstanceError::with_source(
|
||||
String::from("IDXGIFactory4 creation failed"),
|
||||
err,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(factory6) = factory4.cast::<Dxgi::IDXGIFactory6>() {
|
||||
return Ok((lib_dxgi, DxgiFactory::Factory6(factory6)));
|
||||
}
|
||||
|
||||
if let Ok(factory5) = factory4.cast::<Dxgi::IDXGIFactory5>() {
|
||||
return Ok((lib_dxgi, DxgiFactory::Factory5(factory5)));
|
||||
}
|
||||
|
||||
Ok((lib_dxgi, DxgiFactory::Factory4(factory4)))
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod conv;
|
||||
pub mod exception;
|
||||
pub mod factory;
|
||||
pub mod name;
|
||||
pub mod result;
|
||||
pub mod time;
|
||||
@@ -0,0 +1,24 @@
|
||||
use windows::Win32::Graphics::Direct3D12::ID3D12Object;
|
||||
|
||||
use crate::auxil::dxgi::result::HResult;
|
||||
|
||||
/// Helper trait for setting the name of a D3D12 object.
|
||||
///
|
||||
/// This is implemented on all types that can be converted to an [`ID3D12Object`].
|
||||
pub trait ObjectExt {
|
||||
fn set_name(&self, name: &str) -> Result<(), crate::DeviceError>;
|
||||
}
|
||||
|
||||
impl<T> ObjectExt for T
|
||||
where
|
||||
// Windows impls `From` for all parent interfaces, so we can use that to convert to ID3D12Object.
|
||||
//
|
||||
// This includes implementations for references.
|
||||
for<'a> &'a ID3D12Object: From<&'a T>,
|
||||
{
|
||||
fn set_name(&self, name: &str) -> Result<(), crate::DeviceError> {
|
||||
let name = windows::core::HSTRING::from(name);
|
||||
let object: &ID3D12Object = self.into();
|
||||
unsafe { object.SetName(&name).into_device_result("SetName") }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use windows::Win32::{Foundation, Graphics::Dxgi};
|
||||
|
||||
pub(crate) trait HResult<O> {
|
||||
fn into_device_result(self, description: &str) -> Result<O, crate::DeviceError>;
|
||||
}
|
||||
impl<T> HResult<T> for windows::core::Result<T> {
|
||||
fn into_device_result(self, description: &str) -> Result<T, crate::DeviceError> {
|
||||
#![allow(unreachable_code)]
|
||||
|
||||
self.map_err(|err| {
|
||||
log::error!("{description} failed: {err}");
|
||||
|
||||
match err.code() {
|
||||
Foundation::E_OUTOFMEMORY => crate::DeviceError::OutOfMemory,
|
||||
Dxgi::DXGI_ERROR_DEVICE_RESET | Dxgi::DXGI_ERROR_DEVICE_REMOVED => {
|
||||
#[cfg(feature = "device_lost_panic")]
|
||||
panic!("{description} failed: Device lost ({err})");
|
||||
crate::DeviceError::Lost
|
||||
}
|
||||
_ => {
|
||||
#[cfg(feature = "internal_error_panic")]
|
||||
panic!("{description} failed: {err}");
|
||||
crate::DeviceError::Unexpected
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#![allow(dead_code)] // IPresentationManager is unused currently
|
||||
|
||||
use windows::Win32::System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency};
|
||||
|
||||
pub enum PresentationTimer {
|
||||
/// DXGI uses [`QueryPerformanceCounter()`]
|
||||
Dxgi {
|
||||
/// How many ticks of QPC per second
|
||||
frequency: u64,
|
||||
},
|
||||
/// [`IPresentationManager`] uses [`QueryInterruptTimePrecise()`]
|
||||
///
|
||||
/// [`IPresentationManager`]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Graphics/CompositionSwapchain/struct.IPresentationManager.html
|
||||
/// [`QueryInterruptTimePrecise()`]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/WindowsProgramming/fn.QueryInterruptTimePrecise.html
|
||||
#[allow(non_snake_case)]
|
||||
IPresentationManager {
|
||||
fnQueryInterruptTimePrecise: unsafe extern "system" fn(*mut u64),
|
||||
},
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for PresentationTimer {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match *self {
|
||||
Self::Dxgi { frequency } => f
|
||||
.debug_struct("DXGI")
|
||||
.field("frequency", &frequency)
|
||||
.finish(),
|
||||
Self::IPresentationManager {
|
||||
fnQueryInterruptTimePrecise,
|
||||
} => f
|
||||
.debug_struct("IPresentationManager")
|
||||
.field(
|
||||
"QueryInterruptTimePrecise",
|
||||
&(fnQueryInterruptTimePrecise as usize),
|
||||
)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PresentationTimer {
|
||||
/// Create a presentation timer using QueryPerformanceFrequency (what DXGI uses for presentation times)
|
||||
pub fn new_dxgi() -> Self {
|
||||
let mut frequency = 0;
|
||||
unsafe { QueryPerformanceFrequency(&mut frequency) }.unwrap();
|
||||
|
||||
Self::Dxgi {
|
||||
frequency: frequency
|
||||
.try_into()
|
||||
.expect("Frequency should not be negative"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a presentation timer using QueryInterruptTimePrecise (what IPresentationManager uses for presentation times)
|
||||
///
|
||||
/// Panics if QueryInterruptTimePrecise isn't found (below Win10)
|
||||
pub fn new_ipresentation_manager() -> Self {
|
||||
// We need to load this explicitly, as QueryInterruptTimePrecise is only available on Windows 10+
|
||||
//
|
||||
// Docs say it's in kernel32.dll, but it's actually in kernelbase.dll.
|
||||
// api-ms-win-core-realtime-l1-1-1.dll
|
||||
let kernelbase =
|
||||
libloading::os::windows::Library::open_already_loaded("kernelbase.dll").unwrap();
|
||||
// No concerns about lifetimes here as kernelbase is always there.
|
||||
let ptr = unsafe {
|
||||
kernelbase
|
||||
.get(c"QueryInterruptTimePrecise".to_bytes())
|
||||
.unwrap()
|
||||
};
|
||||
Self::IPresentationManager {
|
||||
fnQueryInterruptTimePrecise: *ptr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current time in nanoseconds.
|
||||
pub fn get_timestamp_ns(&self) -> u128 {
|
||||
// Always do u128 math _after_ hitting the timing function.
|
||||
match *self {
|
||||
PresentationTimer::Dxgi { frequency } => {
|
||||
let mut counter = 0;
|
||||
unsafe { QueryPerformanceCounter(&mut counter) }.unwrap();
|
||||
|
||||
// counter * (1_000_000_000 / freq) but re-ordered to make more precise
|
||||
(counter as u128 * 1_000_000_000) / frequency as u128
|
||||
}
|
||||
PresentationTimer::IPresentationManager {
|
||||
fnQueryInterruptTimePrecise,
|
||||
} => {
|
||||
let mut counter = 0;
|
||||
unsafe { fnQueryInterruptTimePrecise(&mut counter) };
|
||||
|
||||
// QueryInterruptTimePrecise uses units of 100ns for its tick.
|
||||
counter as u128 * 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
#[cfg(dx12)]
|
||||
pub(super) mod dxgi;
|
||||
|
||||
#[cfg(all(native, feature = "renderdoc"))]
|
||||
pub(super) mod renderdoc;
|
||||
|
||||
pub mod db {
|
||||
pub mod amd {
|
||||
/// cbindgen:ignore
|
||||
pub const VENDOR: u32 = 0x1002;
|
||||
}
|
||||
pub mod apple {
|
||||
/// cbindgen:ignore
|
||||
pub const VENDOR: u32 = 0x106B;
|
||||
}
|
||||
pub mod arm {
|
||||
/// cbindgen:ignore
|
||||
pub const VENDOR: u32 = 0x13B5;
|
||||
}
|
||||
pub mod broadcom {
|
||||
/// cbindgen:ignore
|
||||
pub const VENDOR: u32 = 0x14E4;
|
||||
}
|
||||
pub mod imgtec {
|
||||
/// cbindgen:ignore
|
||||
pub const VENDOR: u32 = 0x1010;
|
||||
}
|
||||
pub mod intel {
|
||||
/// cbindgen:ignore
|
||||
pub const VENDOR: u32 = 0x8086;
|
||||
pub const DEVICE_KABY_LAKE_MASK: u32 = 0x5900;
|
||||
pub const DEVICE_SKY_LAKE_MASK: u32 = 0x1900;
|
||||
}
|
||||
pub mod mesa {
|
||||
// Mesa does not actually have a PCI vendor id.
|
||||
//
|
||||
// To match Vulkan, we use the VkVendorId for Mesa in the gles backend so that lavapipe (Vulkan) and
|
||||
// llvmpipe (OpenGL) have the same vendor id.
|
||||
/// cbindgen:ignore
|
||||
pub const VENDOR: u32 = 0x10005;
|
||||
}
|
||||
pub mod nvidia {
|
||||
/// cbindgen:ignore
|
||||
pub const VENDOR: u32 = 0x10DE;
|
||||
}
|
||||
pub mod qualcomm {
|
||||
/// cbindgen:ignore
|
||||
pub const VENDOR: u32 = 0x5143;
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum binding size for the shaders that only support `i32` indexing.
|
||||
/// Interestingly, the index itself can't reach that high, because the minimum
|
||||
/// element size is 4 bytes, but the compiler toolchain still computes the
|
||||
/// offset at some intermediate point, internally, as i32.
|
||||
pub const MAX_I32_BINDING_SIZE: u32 = (1 << 31) - 1;
|
||||
|
||||
pub use wgpu_naga_bridge::map_naga_stage;
|
||||
|
||||
impl crate::CopyExtent {
|
||||
pub fn map_extent_to_copy_size(extent: &wgt::Extent3d, dim: wgt::TextureDimension) -> Self {
|
||||
Self {
|
||||
width: extent.width,
|
||||
height: extent.height,
|
||||
depth: match dim {
|
||||
wgt::TextureDimension::D1 | wgt::TextureDimension::D2 => 1,
|
||||
wgt::TextureDimension::D3 => extent.depth_or_array_layers,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
width: self.width.min(other.width),
|
||||
height: self.height.min(other.height),
|
||||
depth: self.depth.min(other.depth),
|
||||
}
|
||||
}
|
||||
|
||||
// Get the copy size at a specific mipmap level. This doesn't make most sense,
|
||||
// since the copy extents are provided *for* a mipmap level to start with.
|
||||
// But backends use `CopyExtent` more sparingly, and this piece is shared.
|
||||
pub fn at_mip_level(&self, level: u32) -> Self {
|
||||
Self {
|
||||
width: (self.width >> level).max(1),
|
||||
height: (self.height >> level).max(1),
|
||||
depth: (self.depth >> level).max(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::TextureCopyBase {
|
||||
pub fn max_copy_size(&self, full_size: &crate::CopyExtent) -> crate::CopyExtent {
|
||||
let mip = full_size.at_mip_level(self.mip_level);
|
||||
crate::CopyExtent {
|
||||
width: mip.width - self.origin.x,
|
||||
height: mip.height - self.origin.y,
|
||||
depth: mip.depth - self.origin.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::BufferTextureCopy {
|
||||
pub fn clamp_size_to_virtual(&mut self, full_size: &crate::CopyExtent) {
|
||||
let max_size = self.texture_base.max_copy_size(full_size);
|
||||
self.size = self.size.min(&max_size);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::TextureCopy {
|
||||
pub fn clamp_size_to_virtual(
|
||||
&mut self,
|
||||
full_src_size: &crate::CopyExtent,
|
||||
full_dst_size: &crate::CopyExtent,
|
||||
) {
|
||||
let max_src_size = self.src_base.max_copy_size(full_src_size);
|
||||
let max_dst_size = self.dst_base.max_copy_size(full_dst_size);
|
||||
self.size = self.size.min(&max_src_size).min(&max_dst_size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjust `limits` to honor HAL-imposed maximums and comply with WebGPU's
|
||||
/// adapter capability guarantees.
|
||||
#[cfg_attr(any(not(any_backend), metal), allow(dead_code))]
|
||||
pub(crate) fn adjust_raw_limits(mut limits: wgt::Limits) -> wgt::Limits {
|
||||
// Apply hal limits.
|
||||
limits.max_bind_groups = limits.max_bind_groups.min(crate::MAX_BIND_GROUPS as u32);
|
||||
limits.max_vertex_buffers = limits
|
||||
.max_vertex_buffers
|
||||
.min(crate::MAX_VERTEX_BUFFERS as u32);
|
||||
limits.max_color_attachments = limits
|
||||
.max_color_attachments
|
||||
.min(crate::MAX_COLOR_ATTACHMENTS as u32);
|
||||
|
||||
// Adjust limits according to WebGPU adapter capability guarantees.
|
||||
// See <https://gpuweb.github.io/gpuweb/#adapter-capability-guarantees>.
|
||||
|
||||
// WebGPU requires maxBindingsPerBindGroup to be at least the sum of all
|
||||
// per-stage limits multiplied with the maximum shader stages per pipeline.
|
||||
//
|
||||
// Since backends already report their maximum maxBindingsPerBindGroup,
|
||||
// we need to lower all per-stage limits to satisfy this guarantee.
|
||||
const MAX_SHADER_STAGES_PER_PIPELINE: u32 = 2;
|
||||
let max_per_stage_resources =
|
||||
limits.max_bindings_per_bind_group / MAX_SHADER_STAGES_PER_PIPELINE;
|
||||
|
||||
cap_limits_to_be_under_the_sum_limit(
|
||||
[
|
||||
&mut limits.max_sampled_textures_per_shader_stage,
|
||||
&mut limits.max_uniform_buffers_per_shader_stage,
|
||||
&mut limits.max_storage_textures_per_shader_stage,
|
||||
&mut limits.max_storage_buffers_per_shader_stage,
|
||||
&mut limits.max_samplers_per_shader_stage,
|
||||
&mut limits.max_acceleration_structures_per_shader_stage,
|
||||
],
|
||||
max_per_stage_resources,
|
||||
);
|
||||
|
||||
// Not required by the spec but dynamic buffers count
|
||||
// towards non-dynamic buffer limits as well.
|
||||
limits.max_dynamic_uniform_buffers_per_pipeline_layout = limits
|
||||
.max_dynamic_uniform_buffers_per_pipeline_layout
|
||||
.min(limits.max_uniform_buffers_per_shader_stage);
|
||||
limits.max_dynamic_storage_buffers_per_pipeline_layout = limits
|
||||
.max_dynamic_storage_buffers_per_pipeline_layout
|
||||
.min(limits.max_storage_buffers_per_shader_stage);
|
||||
|
||||
limits.min_uniform_buffer_offset_alignment = limits.min_uniform_buffer_offset_alignment.max(32);
|
||||
limits.min_storage_buffer_offset_alignment = limits.min_storage_buffer_offset_alignment.max(32);
|
||||
|
||||
limits.max_uniform_buffer_binding_size = limits
|
||||
.max_uniform_buffer_binding_size
|
||||
.min(limits.max_buffer_size);
|
||||
limits.max_storage_buffer_binding_size = limits
|
||||
.max_storage_buffer_binding_size
|
||||
.min(limits.max_buffer_size);
|
||||
|
||||
limits.max_storage_buffer_binding_size &= !(u64::from(wgt::STORAGE_BINDING_SIZE_ALIGNMENT) - 1);
|
||||
limits.max_vertex_buffer_array_stride &= !(wgt::VERTEX_ALIGNMENT as u32 - 1);
|
||||
|
||||
let x = limits.max_compute_workgroup_size_x;
|
||||
let y = limits.max_compute_workgroup_size_y;
|
||||
let z = limits.max_compute_workgroup_size_z;
|
||||
let m = limits.max_compute_invocations_per_workgroup;
|
||||
limits.max_compute_workgroup_size_x = x.min(m);
|
||||
limits.max_compute_workgroup_size_y = y.min(m);
|
||||
limits.max_compute_workgroup_size_z = z.min(m);
|
||||
limits.max_compute_invocations_per_workgroup = m.min(x.saturating_mul(y).saturating_mul(z));
|
||||
|
||||
limits
|
||||
}
|
||||
|
||||
/// Evenly allocates space to each limit,
|
||||
/// capping them only if strictly necessary.
|
||||
pub fn cap_limits_to_be_under_the_sum_limit<const N: usize>(
|
||||
mut limits: [&mut u32; N],
|
||||
sum_limit: u32,
|
||||
) {
|
||||
limits.sort();
|
||||
|
||||
let mut rem_limit = sum_limit;
|
||||
let mut divisor = limits.len() as u32;
|
||||
for limit_to_adjust in limits {
|
||||
let limit = rem_limit / divisor;
|
||||
*limit_to_adjust = (*limit_to_adjust).min(limit);
|
||||
rem_limit -= *limit_to_adjust;
|
||||
divisor -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cap_limits_to_be_under_the_sum_limit() {
|
||||
test([3, 3, 3], 3, [1, 1, 1]);
|
||||
test([3, 2, 1], 3, [1, 1, 1]);
|
||||
test([1, 2, 3], 6, [1, 2, 3]);
|
||||
test([1, 2, 3], 3, [1, 1, 1]);
|
||||
test([1, 8, 100], 6, [1, 2, 3]);
|
||||
test([2, 80, 80], 6, [2, 2, 2]);
|
||||
test([2, 80, 80], 12, [2, 5, 5]);
|
||||
|
||||
#[track_caller]
|
||||
fn test<const N: usize>(mut input: [u32; N], limit: u32, output: [u32; N]) {
|
||||
cap_limits_to_be_under_the_sum_limit(input.each_mut(), limit);
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
//! RenderDoc integration - <https://renderdoc.org/>
|
||||
#![cfg_attr(not(any(feature = "gles", feature = "vulkan")), allow(dead_code))]
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use core::{ffi, ptr};
|
||||
|
||||
/// The dynamically loaded RenderDoc API function table
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct RenderDocApi {
|
||||
api: renderdoc_sys::RENDERDOC_API_1_4_1,
|
||||
lib: libloading::Library,
|
||||
}
|
||||
|
||||
unsafe impl Send for RenderDocApi {}
|
||||
unsafe impl Sync for RenderDocApi {}
|
||||
|
||||
/// RenderDoc API type
|
||||
#[derive(Debug)]
|
||||
pub enum RenderDoc {
|
||||
/// RenderDoc functionality is available
|
||||
Available {
|
||||
/// RenderDoc API with function pointers
|
||||
api: RenderDocApi,
|
||||
},
|
||||
/// RenderDoc functionality is _not_ available
|
||||
NotAvailable {
|
||||
/// A description why renderdoc functionality is not available
|
||||
reason: String,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: replace with libloading API once supported
|
||||
#[cfg(unix)]
|
||||
const RTLD_NOLOAD: i32 = 0x4;
|
||||
|
||||
impl RenderDoc {
|
||||
pub unsafe fn new() -> Self {
|
||||
type GetApiFn = unsafe extern "C" fn(version: u32, out: *mut *mut ffi::c_void) -> i32;
|
||||
|
||||
#[cfg(windows)]
|
||||
let renderdoc_filename = "renderdoc.dll";
|
||||
#[cfg(all(unix, not(target_os = "android")))]
|
||||
let renderdoc_filename = "librenderdoc.so";
|
||||
#[cfg(target_os = "android")]
|
||||
let renderdoc_filename = "libVkLayer_GLES_RenderDoc.so";
|
||||
|
||||
#[cfg(unix)]
|
||||
let renderdoc_result: Result<libloading::Library, libloading::Error> = unsafe {
|
||||
libloading::os::unix::Library::open(
|
||||
Some(renderdoc_filename),
|
||||
libloading::os::unix::RTLD_NOW | RTLD_NOLOAD,
|
||||
)
|
||||
}
|
||||
.map(|lib| lib.into());
|
||||
|
||||
#[cfg(windows)]
|
||||
let renderdoc_result: Result<libloading::Library, libloading::Error> =
|
||||
libloading::os::windows::Library::open_already_loaded(renderdoc_filename)
|
||||
.map(|lib| lib.into());
|
||||
|
||||
let renderdoc_lib = match renderdoc_result {
|
||||
Ok(lib) => lib,
|
||||
Err(e) => {
|
||||
return RenderDoc::NotAvailable {
|
||||
reason: format!(
|
||||
"Unable to load renderdoc library '{renderdoc_filename}': {e:?}"
|
||||
),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let get_api: libloading::Symbol<GetApiFn> =
|
||||
match unsafe { renderdoc_lib.get(c"RENDERDOC_GetAPI".to_bytes()) } {
|
||||
Ok(api) => api,
|
||||
Err(e) => {
|
||||
return RenderDoc::NotAvailable {
|
||||
reason: format!(
|
||||
"Unable to get RENDERDOC_GetAPI from renderdoc library '{renderdoc_filename}': {e:?}"
|
||||
),
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut obj = ptr::null_mut();
|
||||
match unsafe { get_api(10401, &mut obj) } {
|
||||
1 => RenderDoc::Available {
|
||||
api: RenderDocApi {
|
||||
api: unsafe { *obj.cast::<renderdoc_sys::RENDERDOC_API_1_4_1>() },
|
||||
lib: renderdoc_lib,
|
||||
},
|
||||
},
|
||||
return_value => RenderDoc::NotAvailable {
|
||||
reason: format!(
|
||||
"Unable to get API from renderdoc library '{renderdoc_filename}': {return_value}"
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RenderDoc {
|
||||
fn default() -> Self {
|
||||
if !cfg!(debug_assertions) {
|
||||
return RenderDoc::NotAvailable {
|
||||
reason: "RenderDoc support is only enabled with 'debug_assertions'".into(),
|
||||
};
|
||||
}
|
||||
unsafe { Self::new() }
|
||||
}
|
||||
}
|
||||
/// An implementation specific handle
|
||||
pub type Handle = *mut ffi::c_void;
|
||||
|
||||
impl RenderDoc {
|
||||
/// Start a RenderDoc frame capture
|
||||
pub unsafe fn start_frame_capture(&self, device_handle: Handle, window_handle: Handle) -> bool {
|
||||
match *self {
|
||||
Self::Available { api: ref entry } => {
|
||||
unsafe { entry.api.StartFrameCapture.unwrap()(device_handle, window_handle) };
|
||||
true
|
||||
}
|
||||
Self::NotAvailable { ref reason } => {
|
||||
log::warn!("Could not start RenderDoc frame capture: {reason}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// End a RenderDoc frame capture
|
||||
pub unsafe fn end_frame_capture(&self, device_handle: Handle, window_handle: Handle) {
|
||||
match *self {
|
||||
Self::Available { api: ref entry } => {
|
||||
unsafe { entry.api.EndFrameCapture.unwrap()(device_handle, window_handle) };
|
||||
}
|
||||
Self::NotAvailable { ref reason } => {
|
||||
log::warn!("Could not end RenderDoc frame capture: {reason}")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user