-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathamf_device.rs
More file actions
252 lines (230 loc) · 8.41 KB
/
Copy pathamf_device.rs
File metadata and controls
252 lines (230 loc) · 8.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
//! Create a Direct3D 11 device bound to a **specific AMD adapter**, so AMF's
//! `InitDX11` targets the right GPU on a multi-adapter host.
//!
//! AMF's `InitDX11(null)` lets the runtime create its device on DXGI adapter 0.
//! On a box with an NVIDIA card in slot 0 and an AMD iGPU elsewhere, that's the
//! wrong (non-AMD) adapter and AMF init fails outright. We enumerate adapters
//! via DXGI, find the `vendor_index`-th AMD one (PCI vendor `0x1002`), and make
//! a D3D11 device on it with `D3D11CreateDevice`; the caller hands that device
//! to `AMFContext::InitDX11`.
//!
//! Hand-rolled dlopen FFI (dxgi.dll + d3d11.dll), matching the in-tree AMF FFI
//! style — no `windows` / `winapi` crate. Windows-only.
use anyhow::{Result, bail};
use std::ffi::c_void;
use std::os::raw::c_int;
use std::ptr;
type Hresult = i32;
const S_OK: Hresult = 0;
#[repr(C)]
struct Guid {
data1: u32,
data2: u16,
data3: u16,
data4: [u8; 8],
}
// IID_IDXGIFactory1 = 770aae78-f26f-4dba-a829-253c83d1b387
const IID_IDXGIFACTORY1: Guid = Guid {
data1: 0x770a_ae78,
data2: 0xf26f,
data3: 0x4dba,
data4: [0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87],
};
// DXGI_ADAPTER_DESC1 (x64 layout). We only read `vendor_id`.
#[repr(C)]
struct DxgiAdapterDesc1 {
description: [u16; 128],
vendor_id: u32,
device_id: u32,
sub_sys_id: u32,
revision: u32,
dedicated_video_memory: usize,
dedicated_system_memory: usize,
shared_system_memory: usize,
adapter_luid: i64,
flags: u32,
}
// --- COM vtables: only the slots we call are typed; the rest are opaque
// pointers laid out in exact interface order so the offsets line up. ---
#[repr(C)]
struct FactoryVtbl {
query_interface: *const c_void,
add_ref: *const c_void,
release: unsafe extern "system" fn(*mut c_void) -> u32,
// IDXGIObject
set_private_data: *const c_void,
set_private_data_interface: *const c_void,
get_private_data: *const c_void,
get_parent: *const c_void,
// IDXGIFactory
enum_adapters: *const c_void,
make_window_association: *const c_void,
get_window_association: *const c_void,
create_swap_chain: *const c_void,
create_software_adapter: *const c_void,
// IDXGIFactory1
enum_adapters1: unsafe extern "system" fn(*mut c_void, u32, *mut *mut c_void) -> Hresult,
is_current: *const c_void,
}
#[repr(C)]
struct FactoryObj {
vtbl: *const FactoryVtbl,
}
#[repr(C)]
struct AdapterVtbl {
query_interface: *const c_void,
add_ref: *const c_void,
release: unsafe extern "system" fn(*mut c_void) -> u32,
// IDXGIObject
set_private_data: *const c_void,
set_private_data_interface: *const c_void,
get_private_data: *const c_void,
get_parent: *const c_void,
// IDXGIAdapter
enum_outputs: *const c_void,
get_desc: *const c_void,
check_interface_support: *const c_void,
// IDXGIAdapter1
get_desc1: unsafe extern "system" fn(*mut c_void, *mut DxgiAdapterDesc1) -> Hresult,
}
#[repr(C)]
struct AdapterObj {
vtbl: *const AdapterVtbl,
}
// Minimal IUnknown view (Release at slot 2) for the created device.
#[repr(C)]
struct ReleaseVtbl {
query_interface: *const c_void,
add_ref: *const c_void,
release: unsafe extern "system" fn(*mut c_void) -> u32,
}
#[repr(C)]
struct ComObj {
vtbl: *const ReleaseVtbl,
}
unsafe fn com_release(obj: *mut c_void) {
if !obj.is_null() {
unsafe {
let vt = &*(*(obj as *mut ComObj)).vtbl;
(vt.release)(obj);
}
}
}
type FnCreateDxgiFactory1 = unsafe extern "system" fn(*const Guid, *mut *mut c_void) -> Hresult;
#[allow(clippy::type_complexity)]
type FnD3d11CreateDevice = unsafe extern "system" fn(
*mut c_void, // pAdapter
c_int, // DriverType
*mut c_void, // Software
u32, // Flags
*const c_void, // pFeatureLevels
u32, // FeatureLevels
u32, // SDKVersion
*mut *mut c_void, // ppDevice
*mut c_int, // pFeatureLevel
*mut *mut c_void, // ppImmediateContext
) -> Hresult;
const D3D_DRIVER_TYPE_UNKNOWN: c_int = 0;
const D3D11_SDK_VERSION: u32 = 7;
/// `D3D11_CREATE_DEVICE_VIDEO_SUPPORT` — required for a device AMF will use for
/// hardware video decode; without it AMF's `InitDX11` rejects the device.
const D3D11_CREATE_DEVICE_VIDEO_SUPPORT: u32 = 0x800;
/// A D3D11 device created on a specific AMD adapter, ready to hand to AMF's
/// `InitDX11`. Holds the dlopen'd `dxgi.dll` / `d3d11.dll` so they outlive the
/// device, and releases the device on drop.
pub struct AmdD3d11Device {
device: *mut c_void,
_dxgi: libloading::Library,
_d3d11: libloading::Library,
}
impl AmdD3d11Device {
/// The `ID3D11Device*` to pass as AMF `InitDX11`'s device argument.
pub fn as_ptr(&self) -> *mut c_void {
self.device
}
}
impl Drop for AmdD3d11Device {
fn drop(&mut self) {
unsafe { com_release(self.device) };
}
}
/// Create a D3D11 device on the `vendor_index`-th AMD (PCI vendor `0x1002`)
/// adapter so AMF binds to that GPU instead of DXGI adapter 0.
pub fn create_amd_d3d11_device(vendor_index: u32) -> Result<AmdD3d11Device> {
unsafe {
let dxgi = libloading::Library::new("dxgi.dll")
.map_err(|e| anyhow::anyhow!("loading dxgi.dll: {e}"))?;
let d3d11 = libloading::Library::new("d3d11.dll")
.map_err(|e| anyhow::anyhow!("loading d3d11.dll: {e}"))?;
let create_factory = *dxgi.get::<FnCreateDxgiFactory1>(b"CreateDXGIFactory1")?;
let create_device = *d3d11.get::<FnD3d11CreateDevice>(b"D3D11CreateDevice")?;
let mut factory: *mut c_void = ptr::null_mut();
if create_factory(&IID_IDXGIFACTORY1, &mut factory) != S_OK || factory.is_null() {
bail!("CreateDXGIFactory1 failed");
}
let factory_vt = &*(*(factory as *mut FactoryObj)).vtbl;
// Walk adapters; pick the vendor_index-th AMD one.
let mut amd_seen = 0u32;
let mut chosen: *mut c_void = ptr::null_mut();
let mut i = 0u32;
loop {
let mut adapter: *mut c_void = ptr::null_mut();
// Enumeration ends with DXGI_ERROR_NOT_FOUND (non-S_OK).
if (factory_vt.enum_adapters1)(factory, i, &mut adapter) != S_OK || adapter.is_null() {
break;
}
let adapter_vt = &*(*(adapter as *mut AdapterObj)).vtbl;
let mut desc: DxgiAdapterDesc1 = std::mem::zeroed();
if (adapter_vt.get_desc1)(adapter, &mut desc) == S_OK && desc.vendor_id == 0x1002 {
if amd_seen == vendor_index {
chosen = adapter; // keep this ref; released after device create
break;
}
amd_seen += 1;
}
(adapter_vt.release)(adapter);
i += 1;
}
(factory_vt.release)(factory);
if chosen.is_null() {
bail!("no AMD (0x1002) DXGI adapter at vendor index {vendor_index}");
}
// DriverType MUST be UNKNOWN when an explicit adapter is supplied.
let mut device: *mut c_void = ptr::null_mut();
let hr = create_device(
chosen,
D3D_DRIVER_TYPE_UNKNOWN,
ptr::null_mut(),
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
ptr::null(),
0,
D3D11_SDK_VERSION,
&mut device,
ptr::null_mut(),
ptr::null_mut(),
);
com_release(chosen); // the device holds its own adapter ref
if hr != S_OK || device.is_null() {
bail!("D3D11CreateDevice on AMD adapter {vendor_index} failed (hr=0x{hr:08x})");
}
Ok(AmdD3d11Device { device, _dxgi: dxgi, _d3d11: d3d11 })
}
}
#[cfg(test)]
mod tests {
/// Exercises the DXGI enumeration + D3D11CreateDevice + the `Drop`
/// `com_release` path. A wrong vtable offset would segfault here. Prints +
/// passes when no AMD adapter is present (non-AMD CI), so it's safe to run
/// anywhere; on the foxbox it must construct + drop a real device cleanly.
#[test]
fn create_and_drop_amd_d3d11_device() {
match super::create_amd_d3d11_device(0) {
Ok(dev) => {
assert!(!dev.as_ptr().is_null(), "device pointer is null");
drop(dev); // exercises com_release
eprintln!("create_amd_d3d11_device(0): OK + dropped cleanly");
}
Err(e) => eprintln!("create_amd_d3d11_device(0): no device ({e})"),
}
}
}