Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

D3D12 cleanup #6200

Merged
merged 9 commits into from
Sep 3, 2024
Merged

D3D12 cleanup #6200

merged 9 commits into from
Sep 3, 2024

Conversation

teoxoy
Copy link
Member

@teoxoy teoxoy commented Sep 2, 2024

Connections
Follow-up to #5956.

Description
Mainly cleans-up usage of the D3D12 API.
Also fixes an issue where only high performance adapters would be enumerated if DXGI 1.6 is available.
I will have another follow-up to this that will go over and handle errors on a case-by-case basis (similar to #6119).

Testing
Existing tests. I also tested DXC locally with the changes to shader compilation.

PR doesn't need to be squashed, each commit builds by itself.

@teoxoy teoxoy requested a review from a team as a code owner September 2, 2024 17:34
@teoxoy teoxoy force-pushed the d3d12-cleanup branch 4 times, most recently from e736419 to ef0d8c8 Compare September 3, 2024 11:44
Copy link
Contributor

@nical nical left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glorious.

@teoxoy teoxoy mentioned this pull request Sep 3, 2024
…rror::Unexpected`

These are guaranteed to be populated if the creation methods didn't error. If they are not, the driver/runtime is at fault.
Also fixes an issue where only high performance adapters would be enumerated if DXGI 1.6 is available.
…K` & `MULTIPLE_DEVICES` tests

The test harness creates the instance and adapter that a test should use. Tests should not create these with default configurations or configs from the environment.
@teoxoy teoxoy merged commit 07becfe into gfx-rs:trunk Sep 3, 2024
25 checks passed
@teoxoy teoxoy deleted the d3d12-cleanup branch September 3, 2024 15:15
@@ -149,7 +149,6 @@ gpu-descriptor = "0.3"
bit-set = "0.8"
gpu-allocator = { version = "0.27", default-features = false }
range-alloc = "0.1"
hassle-rs = "0.11.0"
Copy link
Contributor

@MarijnS95 MarijnS95 Sep 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future reference, the only reason why hassle-rs cannot use windows-rs as bindings is because the windows-rs maintainers refuse to allow basic Linux support. DirectxShaderConverter works on Linux via the same COM bindings otherwise.

(Either way the abstraction provided by hassle-rs is far from ideal)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it more straightforward to just use the bindings directly since we have to deal with them for D3D12 anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the abstraction by hassle-rs isn't that useful. It's mostly creating the bindings but we have those now (except not for Linux 😭) in windows-rs.

Comment on lines 169 to 170
.ok()
// TODO: If there's a HRESULT, error may still be non-null and
// contain info.
.into_device_result("Root signature serialization")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was to check the error blob before bailing out with into_device_result(), missed this during the previous cleanup. The TODO remains valid.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, is that something we should handle? What would be an example?

The only scenario I can think of is: the blob getting partially filled with errors after which we get an OOM but it's probably ok to error with an OOM instead of reporting the partial errors.

I wish the exact error codes were documented (like in Vulkan), now we have to guess which variants we might run into...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting a similar setup like Compile() the error doesn't need to be OOM.

For Compile(), I'm not sure which HRESULTs to check either. There is:

  • The result from Compile();
  • Non-null error blob;
  • Null blob;
  • Result from IDxcOperationResult::GetStatus().

(and any getters on IDxcOperationResult/IDxcResult can fail as well)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a reply in #6200 (comment).

Comment on lines +192 to +198
let mut result__ = None;

(func)(&Direct3D12::ID3D12Debug::IID, <*mut _>::cast(&mut result__))
.ok()
.into_device_result("GetDebugInterface")?;

let mut result__ = core::ptr::null_mut();
Ok((func)(&Direct3D12::ID3D12Debug::IID, &mut result__)
.and_then(|| unsafe { windows_core::Type::from_abi(result__) }))
result__.ok_or(crate::DeviceError::Unexpected)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that a lot of this was intentionally verbatim-copied from windows-rs to allow easier updating.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, but I think it's more valuable to have those be consistent. I'm also not a fan of using the from_abi function which maps null to Error::empty().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right yes, that is unfortunate.

Perhaps this also has to do with SAL annotations, if I remember correctly there are different annotations that allow return values to be NULL, versus them only being NULL when a non-SUCCESS RESULT is returned.

The original code now no longer relies on deref semantics, unfortunately.

)
.ok()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should report over at windows-rs that the naming choice of this function is less than ideal. I keep mistaking it for https://doc.rust-lang.org/stable/std/result/enum.Result.html#method.ok which discards Error information by turning Result into Option which instantly triggers during review.

Comment on lines +1 to -5
use crate::auxil::dxgi::result::HResult;
use std::ffi::CStr;
use std::ptr;
use std::path::PathBuf;
use windows::{
core::{Interface, PCSTR, PCWSTR},
Win32::Graphics::Direct3D::{Dxc, Fxc},
};

pub(super) use dxc::{compile_dxc, get_dxc_container, DxcContainer};
use windows::Win32::Graphics::Direct3D;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't wgpu use StdExternalCrate formatting?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem that we do.

Comment on lines +100 to 109
fn new(lib_path: Option<PathBuf>, lib_name: &'static str) -> Result<Self, libloading::Error> {
let lib_path = if let Some(lib_path) = lib_path {
if lib_path.is_file() {
lib_path
} else {
lib_path.join(lib_name)
}
} else {
PathBuf::from(lib_name)
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't have copied this weird bit from hassle-rs though, it has caused us quite a few headaches over the years.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can imagine, but I didn't want to change the behavior (yet) as it is documented in our docs.

Comment on lines +182 to 187
impl OPCWSTR {
fn new(s: &str) -> Self {
let mut inner: Vec<_> = s.encode_utf16().collect();
inner.push(0);
Self { inner }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't HSTRING work for you?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed to be even more complex for this purpose (ref-counted, no easy way to create it from a &str, no easy way to get a PCWSTR from it).

@@ -149,7 +149,6 @@ gpu-descriptor = "0.3"
bit-set = "0.8"
gpu-allocator = { version = "0.27", default-features = false }
range-alloc = "0.1"
hassle-rs = "0.11.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the abstraction by hassle-rs isn't that useful. It's mostly creating the bindings but we have those now (except not for Linux 😭) in windows-rs.

Comment on lines +192 to +198
let mut result__ = None;

(func)(&Direct3D12::ID3D12Debug::IID, <*mut _>::cast(&mut result__))
.ok()
.into_device_result("GetDebugInterface")?;

let mut result__ = core::ptr::null_mut();
Ok((func)(&Direct3D12::ID3D12Debug::IID, &mut result__)
.and_then(|| unsafe { windows_core::Type::from_abi(result__) }))
result__.ok_or(crate::DeviceError::Unexpected)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right yes, that is unfortunate.

Perhaps this also has to do with SAL annotations, if I remember correctly there are different annotations that allow return values to be NULL, versus them only being NULL when a non-SUCCESS RESULT is returned.

The original code now no longer relies on deref semantics, unfortunately.

kind: Dxc::DXC_OUT_KIND,
) -> Result<T, crate::DeviceError> {
let mut result__: Option<T> = None;
unsafe { res.GetOutput::<T>(kind, &mut None, <*mut _>::cast(&mut result__)) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could coerce because it takes a *mut Option<T>

Suggested change
unsafe { res.GetOutput::<T>(kind, &mut None, <*mut _>::cast(&mut result__)) }
unsafe { res.GetOutput::<T>(kind, &mut None, &mut result__) }

res: &Dxc::IDxcResult,
kind: Dxc::DXC_OUT_KIND,
) -> Result<T, crate::DeviceError> {
let mut result__: Option<T> = None;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May just be me who prefers this short-hand:

Suggested change
let mut result__: Option<T> = None;
let mut result__ = None::<T>;

Although the type can be inferred:

Suggested change
let mut result__: Option<T> = None;
let mut result__ = None;

Not that it matters too much, this signature will change when upstream metadata fixes land.

Comment on lines +114 to +118
type Fun = extern "system" fn(
rclsid: *const windows_core::GUID,
riid: *const windows_core::GUID,
ppv: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not use the existing DxcCreateInstanceProc typedef? Because it's wrapped in Option<>?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that it exists but wasn't 100% sure how that would behave with libloading. It's not something I've seen in their docs/examples.

.compiler
.Compile(&buffer, Some(&compile_args), None)
}
.into_device_result("Compile")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the old API, I wonder if Compile returns an error code while IDxcResult is also returned with valid errors (the root cause) in DXC_OUT_ERRORS.

Or if that's still communicated by IDxcOperationResult::GetResult() which you're not checking here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my testing, IDxcCompiler3::Compile seems to succeed even if there was a compilation error.

Getting the blob via IDxcResult::GetOutput(DXC_OUT_ERRORS) always succeeds. If there is a compilation error its size/length will be non 0 and will contain the error message.

The wiki seems to say that Compile will only fail if the compiler itself ran into an internal error.
https://github.com/microsoft/DirectXShaderCompiler/wiki/Advanced-Error-Handling#internal-compiler-errors

Comment on lines 169 to 170
.ok()
// TODO: If there's a HRESULT, error may still be non-null and
// contain info.
.into_device_result("Root signature serialization")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting a similar setup like Compile() the error doesn't need to be OOM.

For Compile(), I'm not sure which HRESULTs to check either. There is:

  • The result from Compile();
  • Non-null error blob;
  • Null blob;
  • Result from IDxcOperationResult::GetStatus().

(and any getters on IDxcOperationResult/IDxcResult can fail as well)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants