1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16 
17 /** \file
18  * \ingroup GHOST
19  *
20  * For testing purposes, it can be useful to do some DirectX only drawing. A patch for this can be
21  * found here: https://developer.blender.org/P1284
22  */
23 
24 #include <iostream>
25 #include <string>
26 
27 #include <GL/glew.h>
28 #include <GL/wglew.h>
29 
30 #include "GHOST_ContextD3D.h"
31 #include "GHOST_ContextWGL.h" /* For shared drawing */
32 
33 HMODULE GHOST_ContextD3D::s_d3d_lib = NULL;
34 PFN_D3D11_CREATE_DEVICE GHOST_ContextD3D::s_D3D11CreateDeviceFn = NULL;
35 
GHOST_ContextD3D(bool stereoVisual,HWND hWnd)36 GHOST_ContextD3D::GHOST_ContextD3D(bool stereoVisual, HWND hWnd)
37     : GHOST_Context(stereoVisual), m_hWnd(hWnd)
38 {
39 }
40 
~GHOST_ContextD3D()41 GHOST_ContextD3D::~GHOST_ContextD3D()
42 {
43   m_device->Release();
44   m_device_ctx->ClearState();
45   m_device_ctx->Release();
46 }
47 
swapBuffers()48 GHOST_TSuccess GHOST_ContextD3D::swapBuffers()
49 {
50   return GHOST_kSuccess;
51 }
52 
activateDrawingContext()53 GHOST_TSuccess GHOST_ContextD3D::activateDrawingContext()
54 {
55   return GHOST_kFailure;
56 }
57 
releaseDrawingContext()58 GHOST_TSuccess GHOST_ContextD3D::releaseDrawingContext()
59 {
60   return GHOST_kFailure;
61 }
62 
setupD3DLib()63 GHOST_TSuccess GHOST_ContextD3D::setupD3DLib()
64 {
65   if (s_d3d_lib == NULL) {
66     s_d3d_lib = LoadLibraryA("d3d11.dll");
67 
68     WIN32_CHK(s_d3d_lib != NULL);
69 
70     if (s_d3d_lib == NULL) {
71       fprintf(stderr, "LoadLibrary(\"d3d11.dll\") failed!\n");
72       return GHOST_kFailure;
73     }
74   }
75 
76   if (s_D3D11CreateDeviceFn == NULL) {
77     s_D3D11CreateDeviceFn = (PFN_D3D11_CREATE_DEVICE)GetProcAddress(s_d3d_lib,
78                                                                     "D3D11CreateDevice");
79 
80     WIN32_CHK(s_D3D11CreateDeviceFn != NULL);
81 
82     if (s_D3D11CreateDeviceFn == NULL) {
83       fprintf(stderr, "GetProcAddress(s_d3d_lib, \"D3D11CreateDevice\") failed!\n");
84       return GHOST_kFailure;
85     }
86   }
87 
88   return GHOST_kSuccess;
89 }
90 
initializeDrawingContext()91 GHOST_TSuccess GHOST_ContextD3D::initializeDrawingContext()
92 {
93   if (setupD3DLib() == GHOST_kFailure) {
94     return GHOST_kFailure;
95   }
96 
97   HRESULT hres = s_D3D11CreateDeviceFn(
98       NULL,
99       D3D_DRIVER_TYPE_HARDWARE,
100       NULL,
101       /* For debugging you may want to pass D3D11_CREATE_DEVICE_DEBUG here, but that requires
102        * additional setup, see
103        * https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-devices-layers#debug-layer.
104        */
105       0,
106       NULL,
107       0,
108       D3D11_SDK_VERSION,
109       &m_device,
110       NULL,
111       &m_device_ctx);
112 
113   WIN32_CHK(hres == S_OK);
114 
115   return GHOST_kSuccess;
116 }
117 
releaseNativeHandles()118 GHOST_TSuccess GHOST_ContextD3D::releaseNativeHandles()
119 {
120   return GHOST_kFailure;
121 }
122 
123 class GHOST_SharedOpenGLResource {
124   struct SharedData {
125     HANDLE device;
126     GLuint fbo;
127     HANDLE render_buf{nullptr};
128   } m_shared;
129 
130  public:
GHOST_SharedOpenGLResource(ID3D11Device * device,ID3D11DeviceContext * device_ctx,unsigned int width,unsigned int height,ID3D11RenderTargetView * render_target=nullptr)131   GHOST_SharedOpenGLResource(ID3D11Device *device,
132                              ID3D11DeviceContext *device_ctx,
133                              unsigned int width,
134                              unsigned int height,
135                              ID3D11RenderTargetView *render_target = nullptr)
136       : m_device(device), m_device_ctx(device_ctx), m_cur_width(width), m_cur_height(height)
137   {
138     ID3D11Resource *backbuffer_res;
139 
140     if (!render_target) {
141       D3D11_TEXTURE2D_DESC texDesc{};
142       D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc{};
143       ID3D11Texture2D *tex;
144 
145       texDesc.Width = width;
146       texDesc.Height = height;
147       texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
148       texDesc.SampleDesc.Count = 1;
149       texDesc.ArraySize = 1;
150       texDesc.MipLevels = 1;
151       texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
152 
153       device->CreateTexture2D(&texDesc, NULL, &tex);
154       if (!tex) {
155         /* If texture creation fails, we just return and leave the render target unset. So it needs
156          * to be NULL-checked before use. */
157         fprintf(stderr, "Error creating texture for shared DirectX-OpenGL resource\n");
158         return;
159       }
160 
161       renderTargetViewDesc.Format = texDesc.Format;
162       renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
163       renderTargetViewDesc.Texture2D.MipSlice = 0;
164 
165       device->CreateRenderTargetView(tex, &renderTargetViewDesc, &render_target);
166 
167       tex->Release();
168     }
169 
170     m_render_target = render_target;
171     if (m_render_target) {
172       m_render_target->GetResource(&backbuffer_res);
173     }
174     if (backbuffer_res) {
175       backbuffer_res->QueryInterface<ID3D11Texture2D>(&m_render_target_tex);
176       backbuffer_res->Release();
177     }
178 
179     if (!m_render_target || !m_render_target_tex) {
180       fprintf(stderr, "Error creating render target for shared DirectX-OpenGL resource\n");
181       return;
182     }
183   }
184 
~GHOST_SharedOpenGLResource()185   ~GHOST_SharedOpenGLResource()
186   {
187     if (m_render_target_tex) {
188       m_render_target_tex->Release();
189     }
190     if (m_render_target) {
191       m_render_target->Release();
192     }
193 
194     if (m_is_initialized) {
195       if (m_shared.render_buf) {
196         wglDXUnregisterObjectNV(m_shared.device, m_shared.render_buf);
197       }
198       if (m_shared.device) {
199         wglDXCloseDeviceNV(m_shared.device);
200       }
201       glDeleteFramebuffers(1, &m_shared.fbo);
202       glDeleteRenderbuffers(1, &m_gl_render_buf);
203     }
204   }
205 
reregisterSharedObject()206   void reregisterSharedObject()
207   {
208     if (m_shared.render_buf) {
209       wglDXUnregisterObjectNV(m_shared.device, m_shared.render_buf);
210     }
211 
212     if (!m_render_target_tex) {
213       return;
214     }
215 
216     m_shared.render_buf = wglDXRegisterObjectNV(m_shared.device,
217                                                 m_render_target_tex,
218                                                 m_gl_render_buf,
219                                                 GL_RENDERBUFFER,
220                                                 WGL_ACCESS_READ_WRITE_NV);
221 
222     if (!m_shared.render_buf) {
223       fprintf(stderr, "Error registering shared object using wglDXRegisterObjectNV()\n");
224       return;
225     }
226   }
227 
initialize()228   GHOST_TSuccess initialize()
229   {
230     m_shared.device = wglDXOpenDeviceNV(m_device);
231     if (m_shared.device == NULL) {
232       fprintf(stderr, "Error opening shared device using wglDXOpenDeviceNV()\n");
233       return GHOST_kFailure;
234     }
235 
236     /* Build the renderbuffer. */
237     glGenRenderbuffers(1, &m_gl_render_buf);
238     glBindRenderbuffer(GL_RENDERBUFFER, m_gl_render_buf);
239 
240     reregisterSharedObject();
241 
242     /* Build the framebuffer */
243     glGenFramebuffers(1, &m_shared.fbo);
244     glBindFramebuffer(GL_FRAMEBUFFER, m_shared.fbo);
245     glFramebufferRenderbuffer(
246         GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_gl_render_buf);
247     m_is_initialized = true;
248 
249     return GHOST_kSuccess;
250   }
251 
ensureUpdated(unsigned int width,unsigned int height)252   void ensureUpdated(unsigned int width, unsigned int height)
253   {
254     if (m_is_initialized == false) {
255       initialize();
256     }
257 
258     if ((m_cur_width != width) || (m_cur_height != height)) {
259       m_cur_width = width;
260       m_cur_height = height;
261       reregisterSharedObject();
262     }
263   }
264 
blit(unsigned int width,unsigned int height)265   GHOST_TSuccess blit(unsigned int width, unsigned int height)
266   {
267     GLint fbo;
268     glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fbo);
269 
270     if (!m_render_target || !m_render_target_tex) {
271       return GHOST_kFailure;
272     }
273 
274     ensureUpdated(width, height);
275 
276 #ifdef NDEBUG
277     const float clear_col[] = {0.8f, 0.5f, 1.0f, 1.0f};
278     m_device_ctx->ClearRenderTargetView(m_render_target, clear_col);
279 #endif
280     m_device_ctx->OMSetRenderTargets(1, &m_render_target, nullptr);
281 
282     beginGLOnly();
283 
284     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_shared.fbo);
285     GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER);
286     if (err != GL_FRAMEBUFFER_COMPLETE) {
287       fprintf(
288           stderr, "Error: Framebuffer for shared DirectX-OpenGL resource incomplete %u\n", err);
289       return GHOST_kFailure;
290     }
291 
292     /* No glBlitNamedFramebuffer, gotta be 3.3 compatible. */
293     glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
294     glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
295 
296     glBindFramebuffer(GL_FRAMEBUFFER, fbo);
297 
298     endGLOnly();
299 
300     return GHOST_kSuccess;
301   }
302 
303   ID3D11RenderTargetView *m_render_target{nullptr};
304   ID3D11Texture2D *m_render_target_tex{nullptr};
305 
306  private:
beginGLOnly()307   void beginGLOnly()
308   {
309     wglDXLockObjectsNV(m_shared.device, 1, &m_shared.render_buf);
310   }
endGLOnly()311   void endGLOnly()
312   {
313     wglDXUnlockObjectsNV(m_shared.device, 1, &m_shared.render_buf);
314   }
315 
316   ID3D11Device *m_device;
317   ID3D11DeviceContext *m_device_ctx;
318   GLuint m_gl_render_buf;
319   unsigned int m_cur_width, m_cur_height;
320   bool m_is_initialized{false};
321 };
322 
createSharedOpenGLResource(unsigned int width,unsigned int height,ID3D11RenderTargetView * render_target)323 GHOST_SharedOpenGLResource *GHOST_ContextD3D::createSharedOpenGLResource(
324     unsigned int width, unsigned int height, ID3D11RenderTargetView *render_target)
325 {
326   if (!(WGL_NV_DX_interop && WGL_NV_DX_interop2)) {
327     fprintf(stderr,
328             "Error: Can't render OpenGL framebuffer using Direct3D. NV_DX_interop extension not "
329             "available.");
330     return nullptr;
331   }
332   GHOST_SharedOpenGLResource *shared_res = new GHOST_SharedOpenGLResource(
333       m_device, m_device_ctx, width, height, render_target);
334 
335   return shared_res;
336 }
createSharedOpenGLResource(unsigned int width,unsigned int height)337 GHOST_SharedOpenGLResource *GHOST_ContextD3D::createSharedOpenGLResource(unsigned int width,
338                                                                          unsigned int height)
339 {
340   return createSharedOpenGLResource(width, height, nullptr);
341 }
342 
disposeSharedOpenGLResource(GHOST_SharedOpenGLResource * shared_res)343 void GHOST_ContextD3D::disposeSharedOpenGLResource(GHOST_SharedOpenGLResource *shared_res)
344 {
345   delete shared_res;
346 }
347 
blitFromOpenGLContext(GHOST_SharedOpenGLResource * shared_res,unsigned int width,unsigned int height)348 GHOST_TSuccess GHOST_ContextD3D::blitFromOpenGLContext(GHOST_SharedOpenGLResource *shared_res,
349                                                        unsigned int width,
350                                                        unsigned int height)
351 {
352   return shared_res->blit(width, height);
353 }
354 
getSharedTexture2D(GHOST_SharedOpenGLResource * shared_res)355 ID3D11Texture2D *GHOST_ContextD3D::getSharedTexture2D(GHOST_SharedOpenGLResource *shared_res)
356 {
357   return shared_res->m_render_target_tex;
358 }
359