1 // FFMPEG Video Encoder Integration for OBS Studio
2 // Copyright (c) 2019 Michael Fabian Dirks <info@xaymar.com>
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in all
12 // copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 // SOFTWARE.
21
22 #ifdef WIN32
23
24 #include "d3d11.hpp"
25 #include <sstream>
26 #include <vector>
27 #include "obs/gs/gs-helper.hpp"
28
29 extern "C" {
30 #pragma warning(push)
31 #pragma warning(disable : 4191)
32 #pragma warning(disable : 4242)
33 #pragma warning(disable : 4244)
34 #pragma warning(disable : 4365)
35 #pragma warning(disable : 4986)
36 #include <libavutil/hwcontext_d3d11va.h>
37 #pragma warning(pop)
38 }
39
40 using namespace ffmpeg::hwapi;
41
d3d11()42 d3d11::d3d11() : _dxgi_module(0), _d3d11_module(0)
43 {
44 _dxgi_module = LoadLibraryW(L"dxgi.dll");
45 if (!_dxgi_module)
46 throw std::runtime_error("Unable to load DXGI");
47
48 _d3d11_module = LoadLibraryW(L"d3d11.dll");
49 if (!_d3d11_module)
50 throw std::runtime_error("Unable to load D3D11");
51
52 #pragma warning(push)
53 #pragma warning(disable : 4191)
54 _CreateDXGIFactory = reinterpret_cast<CreateDXGIFactory_t>(GetProcAddress(_dxgi_module, "CreateDXGIFactory"));
55 _CreateDXGIFactory1 = reinterpret_cast<CreateDXGIFactory1_t>(GetProcAddress(_dxgi_module, "CreateDXGIFactory1"));
56 _D3D11CreateDevice = reinterpret_cast<D3D11CreateDevice_t>(GetProcAddress(_d3d11_module, "D3D11CreateDevice"));
57 #pragma warning(pop)
58
59 if (!_CreateDXGIFactory && !_CreateDXGIFactory1)
60 throw std::runtime_error("DXGI not supported");
61
62 if (!_D3D11CreateDevice)
63 throw std::runtime_error("D3D11 not supported");
64
65 HRESULT hr = _CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&_dxgifactory);
66 if (FAILED(hr)) {
67 std::stringstream sstr;
68 sstr << "Failed to create DXGI Factory (" << hr << ")";
69 throw std::runtime_error(sstr.str());
70 }
71 }
72
~d3d11()73 d3d11::~d3d11()
74 {
75 FreeLibrary(_dxgi_module);
76 FreeLibrary(_d3d11_module);
77 }
78
enumerate_adapters()79 std::list<device> d3d11::enumerate_adapters()
80 {
81 std::list<device> adapters;
82
83 // Enumerate Adapters
84 IDXGIAdapter1* dxgi_adapter = nullptr;
85 for (UINT idx = 0; !FAILED(_dxgifactory->EnumAdapters1(idx, &dxgi_adapter)); idx++) {
86 DXGI_ADAPTER_DESC1 desc = DXGI_ADAPTER_DESC1();
87 dxgi_adapter->GetDesc1(&desc);
88
89 std::vector<char> buf(1024);
90 std::size_t len =
91 static_cast<size_t>(snprintf(buf.data(), buf.size(), "%ls (VEN_%04x/DEV_%04x/SUB_%04x/REV_%04x)",
92 desc.Description, desc.VendorId, desc.DeviceId, desc.SubSysId, desc.Revision));
93
94 device dev;
95 dev.name = std::string(buf.data(), buf.data() + len);
96 dev.id.first = desc.AdapterLuid.HighPart;
97 dev.id.second = desc.AdapterLuid.LowPart;
98
99 adapters.push_back(dev);
100 }
101
102 return std::move(adapters);
103 }
104
create(device target)105 std::shared_ptr<instance> d3d11::create(device target)
106 {
107 std::shared_ptr<d3d11_instance> inst;
108 ATL::CComPtr<ID3D11Device> device;
109 ATL::CComPtr<ID3D11DeviceContext> context;
110 IDXGIAdapter1* adapter = nullptr;
111
112 // Find the correct "Adapter" (device).
113 IDXGIAdapter1* dxgi_adapter = nullptr;
114 for (UINT idx = 0; !FAILED(_dxgifactory->EnumAdapters1(idx, &dxgi_adapter)); idx++) {
115 DXGI_ADAPTER_DESC1 desc = DXGI_ADAPTER_DESC1();
116 dxgi_adapter->GetDesc1(&desc);
117
118 if ((desc.AdapterLuid.LowPart == target.id.second) && (desc.AdapterLuid.HighPart == target.id.first)) {
119 adapter = dxgi_adapter;
120 break;
121 }
122 }
123
124 // Create a D3D11 Device
125 UINT device_flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
126 std::vector<D3D_FEATURE_LEVEL> feature_levels = {D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0,
127 D3D_FEATURE_LEVEL_11_1};
128
129 if (FAILED(_D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_HARDWARE, NULL, device_flags, feature_levels.data(),
130 static_cast<UINT>(feature_levels.size()), D3D11_SDK_VERSION, &device, NULL,
131 &context))) {
132 throw std::runtime_error("Failed to create D3D11 device for target.");
133 }
134
135 return std::make_shared<d3d11_instance>(device, context);
136 }
137
create_from_obs()138 std::shared_ptr<instance> d3d11::create_from_obs()
139 {
140 auto gctx = gs::context();
141
142 if (GS_DEVICE_DIRECT3D_11 != gs_get_device_type()) {
143 throw std::runtime_error("OBS Device is not a D3D11 Device.");
144 }
145
146 ATL::CComPtr<ID3D11Device> device =
147 ATL::CComPtr<ID3D11Device>(reinterpret_cast<ID3D11Device*>(gs_get_device_obj()));
148 ATL::CComPtr<ID3D11DeviceContext> context;
149 device->GetImmediateContext(&context);
150
151 return std::make_shared<d3d11_instance>(device, context);
152 }
153
154 struct D3D11AVFrame {
155 ATL::CComPtr<ID3D11Texture2D> handle;
156 };
157
d3d11_instance(ATL::CComPtr<ID3D11Device> device,ATL::CComPtr<ID3D11DeviceContext> context)158 d3d11_instance::d3d11_instance(ATL::CComPtr<ID3D11Device> device, ATL::CComPtr<ID3D11DeviceContext> context)
159 {
160 _device = device;
161 _context = context;
162 }
163
~d3d11_instance()164 d3d11_instance::~d3d11_instance() {}
165
create_device_context()166 AVBufferRef* d3d11_instance::create_device_context()
167 {
168 AVBufferRef* dctx_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA);
169 if (!dctx_ref)
170 throw std::runtime_error("Failed to allocate AVHWDeviceContext.");
171
172 AVHWDeviceContext* dctx = reinterpret_cast<AVHWDeviceContext*>(dctx_ref->data);
173 AVD3D11VADeviceContext* d3d11va = reinterpret_cast<AVD3D11VADeviceContext*>(dctx->hwctx);
174
175 // TODO: Determine if these need an additional reference.
176 d3d11va->device = _device;
177 d3d11va->device->AddRef();
178 d3d11va->device_context = _context;
179 d3d11va->device_context->AddRef();
180 d3d11va->lock = [](void*) { obs_enter_graphics(); };
181 d3d11va->unlock = [](void*) { obs_leave_graphics(); };
182
183 int ret = av_hwdevice_ctx_init(dctx_ref);
184 if (ret < 0)
185 throw std::runtime_error("Failed to initialize AVHWDeviceContext.");
186
187 return dctx_ref;
188 }
189
allocate_frame(AVBufferRef * frames)190 std::shared_ptr<AVFrame> d3d11_instance::allocate_frame(AVBufferRef* frames)
191 {
192 auto gctx = gs::context();
193
194 // Allocate a frame.
195 auto frame = std::shared_ptr<AVFrame>(av_frame_alloc(), [](AVFrame* frame) {
196 av_frame_unref(frame);
197 av_frame_free(&frame);
198 });
199
200 // Create the necessary buffers.
201 if (av_hwframe_get_buffer(frames, frame.get(), 0) < 0) {
202 throw std::runtime_error("Failed to create AVFrame.");
203 }
204
205 // Try to prevent this resource from ever leaving the GPU unless absolutely necessary.
206 reinterpret_cast<ID3D11Texture2D*>(frame->data[0])->SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
207
208 return frame;
209 }
210
copy_from_obs(AVBufferRef *,uint32_t handle,uint64_t lock_key,uint64_t * next_lock_key,std::shared_ptr<AVFrame> frame)211 void d3d11_instance::copy_from_obs(AVBufferRef*, uint32_t handle, uint64_t lock_key, uint64_t* next_lock_key,
212 std::shared_ptr<AVFrame> frame)
213 {
214 auto gctx = gs::context();
215
216 // Attempt to acquire shared texture.
217 ATL::CComPtr<ID3D11Texture2D> input;
218 if (FAILED(_device->OpenSharedResource(reinterpret_cast<HANDLE>(static_cast<uintptr_t>(handle)),
219 __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&input)))) {
220 throw std::runtime_error("Failed to open shared texture resource.");
221 }
222
223 // Attempt to acquire texture mutex.
224 ATL::CComPtr<IDXGIKeyedMutex> mutex;
225 if (FAILED(input->QueryInterface(__uuidof(IDXGIKeyedMutex), reinterpret_cast<void**>(&mutex)))) {
226 throw std::runtime_error("Failed to retrieve mutex for texture resource.");
227 }
228
229 // Attempt to acquire texture lock.
230 if (FAILED(mutex->AcquireSync(lock_key, 1000))) {
231 throw std::runtime_error("Failed to acquire lock on input texture.");
232 }
233
234 // Set some parameters on the input texture, and get its description.
235 UINT evict = input->GetEvictionPriority();
236 input->SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
237
238 // Clone the content of the input texture.
239 _context->CopyResource(reinterpret_cast<ID3D11Texture2D*>(frame->data[0]), input);
240
241 // Restore original parameters on input.
242 input->SetEvictionPriority(evict);
243
244 // Release the acquired lock.
245 if (FAILED(mutex->ReleaseSync(lock_key))) {
246 throw std::runtime_error("Failed to release lock on input texture.");
247 }
248
249 // Release the lock on the next texture.
250 // TODO: Determine if this is necessary.
251 mutex->ReleaseSync(*next_lock_key);
252 }
253
avframe_from_obs(AVBufferRef * frames,uint32_t handle,uint64_t lock_key,uint64_t * next_lock_key)254 std::shared_ptr<AVFrame> d3d11_instance::avframe_from_obs(AVBufferRef* frames, uint32_t handle, uint64_t lock_key,
255 uint64_t* next_lock_key)
256 {
257 auto gctx = gs::context();
258
259 auto frame = this->allocate_frame(frames);
260 this->copy_from_obs(frames, handle, lock_key, next_lock_key, frame);
261 return frame;
262 }
263
264 #endif
265