diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 37e7256d7642..39aff2fc5e42 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -82,6 +82,10 @@ #define strcpy strcpy_s #endif +#ifdef WINDOWS_ENABLED +bool RasterizerGLES3::screen_flipped_y = false; +#endif + bool RasterizerGLES3::gles_over_gl = true; void RasterizerGLES3::begin_frame(double frame_step) { @@ -380,6 +384,12 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display flip_y = false; } +#ifdef WINDOWS_ENABLED + if (screen_flipped_y) { + flip_y = !flip_y; + } +#endif + GLuint read_fbo = 0; glGenFramebuffers(1, &read_fbo); glBindFramebuffer(GL_READ_FRAMEBUFFER, read_fbo); @@ -476,9 +486,14 @@ void RasterizerGLES3::set_boot_image(const Ref &p_image, const Color &p_c screenrect.position += ((Size2(win_size.width, win_size.height) - screenrect.size) / 2.0).floor(); } - // Flip Y. - screenrect.position.y = win_size.y - screenrect.position.y; - screenrect.size.y = -screenrect.size.y; +#ifdef WINDOWS_ENABLED + if (!screen_flipped_y) +#endif + { + // Flip Y. + screenrect.position.y = win_size.y - screenrect.position.y; + screenrect.size.y = -screenrect.size.y; + } // Normalize texture coordinates to window size. screenrect.position /= win_size; diff --git a/drivers/gles3/rasterizer_gles3.h b/drivers/gles3/rasterizer_gles3.h index 80a4a792bb2d..0cf656e490fe 100644 --- a/drivers/gles3/rasterizer_gles3.h +++ b/drivers/gles3/rasterizer_gles3.h @@ -58,6 +58,10 @@ class RasterizerGLES3 : public RendererCompositor { double time_total = 0.0; bool flip_xy_workaround = false; +#ifdef WINDOWS_ENABLED + static bool screen_flipped_y; +#endif + static bool gles_over_gl; protected: @@ -117,6 +121,12 @@ class RasterizerGLES3 : public RendererCompositor { low_end = true; } +#ifdef WINDOWS_ENABLED + static void set_screen_flipped_y(bool p_flipped) { + screen_flipped_y = p_flipped; + } +#endif + _ALWAYS_INLINE_ uint64_t get_frame_number() const { return frame; } _ALWAYS_INLINE_ double get_frame_delta_time() const { return delta; } _ALWAYS_INLINE_ double get_total_time() const { return time_total; } diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 020ef0c31b31..4bbc30fcfa2f 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3003,6 +3003,12 @@ String DisplayServerWindows::keyboard_get_layout_name(int p_index) const { void DisplayServerWindows::process_events() { ERR_FAIL_COND(!Thread::is_main_thread()); +#if defined(GLES3_ENABLED) + if (gl_manager_native) { + gl_manager_native->wait_for_present(get_focused_window()); + } +#endif + if (!drop_events) { joypad->process_joypads(); } @@ -5477,6 +5483,11 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, windows.erase(id); ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create an OpenGL window."); } + if (id == MAIN_WINDOW_ID && gl_manager_native->is_using_dxgi_swap_chain()) { + // When presenting with DXGI the screen FBO is "upside down" + // w.r.t. OpenGL. + RasterizerGLES3::set_screen_flipped_y(true); + } window_set_vsync_mode(p_vsync_mode, id); } @@ -6025,6 +6036,8 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win if (rendering_driver == "opengl3") { gl_manager_native = memnew(GLManagerNative_Windows); + gl_manager_native->set_prefer_dxgi_swap_chain(true); + if (gl_manager_native->initialize() != OK) { memdelete(gl_manager_native); gl_manager_native = nullptr; diff --git a/platform/windows/gl_manager_windows_native.cpp b/platform/windows/gl_manager_windows_native.cpp index 8590c46d12bd..b6940ee15cea 100644 --- a/platform/windows/gl_manager_windows_native.cpp +++ b/platform/windows/gl_manager_windows_native.cpp @@ -34,6 +34,7 @@ #include "core/config/project_settings.h" #include "core/version.h" +#include "main/main.h" #include "thirdparty/nvapi/nvapi_minimal.h" @@ -41,6 +42,56 @@ #include #include +// Enable the DXGI present feature. +#define OPENGL_ON_DXGI_ENABLED + +// Create and set the D3D11 render target. +// We don't actually need to set the render target 'cause we're rendering +// everything in OpenGL, right? As long as the buffer is written to it should +// just work? +//#define OPENGL_DXGI_SET_RENDER_TARGET + +// Create a depth buffer in D3D11 and attach it to the render target. +// Requires OPENGL_DXGI_SET_RENDER_TARGET. +// For some reason Intel driver doesn't like it and gives GL_INVALID_OPERATION +// when trying to attach it with glFramebufferTexture2D. +// RasterizerGLES3 doesn't seem to actually need a depth buffer though. All +// it does is blit other FBOs onto the screen with GL_COLOR_BUFFER_BIT? +// Even if we do need a depth buffer, we could just create one in OpenGL. +//#define OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + +// Bind the color buffer as renderbuffer instead of texture 2D. +#define OPENGL_DXGI_USE_RENDERBUFFER + +// Create and bind a depth buffer renderbuffer in OpenGL. +// Again, we probably don't need this at all. +//#define OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER + +// Instead of rendering directly to the DXGI back buffer, render onto an +// intermediate buffer which is copied to the back buffer on present. +//#define OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + +// Use DXGI flip-discard. (WIP) +#define OPENGL_DXGI_USE_FLIP_MODEL + +#ifdef OPENGL_ON_DXGI_ENABLED + +#include "drivers/gles3/storage/texture_storage.h" +#include "platform_gl.h" + +#include +#ifdef OPENGL_DXGI_USE_FLIP_MODEL +#include +#include +#include +#endif + +#include + +using Microsoft::WRL::ComPtr; + +#endif // OPENGL_ON_DXGI_ENABLED + #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 #define WGL_CONTEXT_FLAGS_ARB 0x2094 @@ -61,6 +112,66 @@ typedef BOOL(APIENTRY *PFNWGLMAKECURRENT)(HDC, HGLRC); typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *); typedef void *(APIENTRY *PFNWGLGETPROCADDRESS)(LPCSTR); +#ifdef OPENGL_ON_DXGI_ENABLED + +typedef const char *(APIENTRY *PFNWGLGETEXTENSIONSSTRINGARB)(HDC); + +#define WGL_ACCESS_READ_ONLY_NV 0x0000 +#define WGL_ACCESS_READ_WRITE_NV 0x0001 +#define WGL_ACCESS_WRITE_DISCARD_NV 0x0002 + +typedef BOOL(APIENTRY *PFNWGLDXSETRESOURCESHAREHANDLENVPROC)(void *dxObject, HANDLE shareHandle); +typedef HANDLE(APIENTRY *PFNWGLDXOPENDEVICENVPROC)(void *dxDevice); +typedef BOOL(APIENTRY *PFNWGLDXCLOSEDEVICENVPROC)(HANDLE hDevice); +typedef HANDLE(APIENTRY *PFNWGLDXREGISTEROBJECTNVPROC)(HANDLE hDevice, void *dxObject, GLuint name, GLenum type, GLenum access); +typedef BOOL(APIENTRY *PFNWGLDXUNREGISTEROBJECTNVPROC)(HANDLE hDevice, HANDLE hObject); +typedef BOOL(APIENTRY *PFNWGLDXOBJECTACCESSNVPROC)(HANDLE hObject, GLenum access); +typedef BOOL(APIENTRY *PFNWGLDXLOCKOBJECTSNVPROC)(HANDLE hDevice, GLint count, HANDLE *hObjects); +typedef BOOL(APIENTRY *PFNWGLDXUNLOCKOBJECTSNVPROC)(HANDLE hDevice, GLint count, HANDLE *hObjects); + +PFNWGLDXSETRESOURCESHAREHANDLENVPROC gd_wglDXSetResourceShareHandleNV; +PFNWGLDXOPENDEVICENVPROC gd_wglDXOpenDeviceNV; +PFNWGLDXCLOSEDEVICENVPROC gd_wglDXCloseDeviceNV; +PFNWGLDXREGISTEROBJECTNVPROC gd_wglDXRegisterObjectNV; +PFNWGLDXUNREGISTEROBJECTNVPROC gd_wglDXUnregisterObjectNV; +PFNWGLDXOBJECTACCESSNVPROC gd_wglDXObjectAccessNV; +PFNWGLDXLOCKOBJECTSNVPROC gd_wglDXLockObjectsNV; +PFNWGLDXUNLOCKOBJECTSNVPROC gd_wglDXUnlockObjectsNV; + +static bool load_nv_dx_interop(PFNWGLGETPROCADDRESS gd_wglGetProcAddress) { + gd_wglDXSetResourceShareHandleNV = (PFNWGLDXSETRESOURCESHAREHANDLENVPROC)gd_wglGetProcAddress("wglDXSetResourceShareHandleNV"); + gd_wglDXOpenDeviceNV = (PFNWGLDXOPENDEVICENVPROC)gd_wglGetProcAddress("wglDXOpenDeviceNV"); + gd_wglDXCloseDeviceNV = (PFNWGLDXCLOSEDEVICENVPROC)gd_wglGetProcAddress("wglDXCloseDeviceNV"); + gd_wglDXRegisterObjectNV = (PFNWGLDXREGISTEROBJECTNVPROC)gd_wglGetProcAddress("wglDXRegisterObjectNV"); + gd_wglDXUnregisterObjectNV = (PFNWGLDXUNREGISTEROBJECTNVPROC)gd_wglGetProcAddress("wglDXUnregisterObjectNV"); + gd_wglDXObjectAccessNV = (PFNWGLDXOBJECTACCESSNVPROC)gd_wglGetProcAddress("wglDXObjectAccessNV"); + gd_wglDXLockObjectsNV = (PFNWGLDXLOCKOBJECTSNVPROC)gd_wglGetProcAddress("wglDXLockObjectsNV"); + gd_wglDXUnlockObjectsNV = (PFNWGLDXUNLOCKOBJECTSNVPROC)gd_wglGetProcAddress("wglDXUnlockObjectsNV"); + + return gd_wglDXSetResourceShareHandleNV && + gd_wglDXOpenDeviceNV && + gd_wglDXCloseDeviceNV && + gd_wglDXRegisterObjectNV && + gd_wglDXUnregisterObjectNV && + gd_wglDXObjectAccessNV && + gd_wglDXLockObjectsNV && + gd_wglDXUnlockObjectsNV; +} + +typedef decltype(&CreateDXGIFactory1) PFNCREATEDXGIFACTORY1; + +static HMODULE module_dxgi = nullptr; +PFNCREATEDXGIFACTORY1 fptr_CreateDXGIFactory1 = nullptr; + +typedef decltype(&D3D11CreateDeviceAndSwapChain) PFND3D11CREATEDEVICEANDSWAPCHAINPROC; +typedef decltype(&D3D11CreateDevice) PFND3D11CREATEDEVICEPROC; + +static HMODULE module_d3d11 = nullptr; +PFND3D11CREATEDEVICEANDSWAPCHAINPROC fptr_D3D11CreateDeviceAndSwapChain = nullptr; +PFND3D11CREATEDEVICEPROC fptr_D3D11CreateDevice = nullptr; + +#endif // OPENGL_ON_DXGI_ENABLED + static String format_error_message(DWORD id) { LPWSTR messageBuffer = nullptr; size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, @@ -287,6 +398,175 @@ void GLManagerNative_Windows::_nvapi_setup_profile() { NvAPI_DRS_DestroySession(session_handle); } +#ifdef OPENGL_ON_DXGI_ENABLED +class GLManagerNative_Windows::DxgiSwapChain { + ComPtr device; + ComPtr device_context; + ComPtr swap_chain; + +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + ComPtr depth_texture; + ComPtr depth_stencil_view; +#endif + +#ifdef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + ComPtr intermediate_buffer; +#else + ComPtr color_buffer; +#endif +#ifdef OPENGL_DXGI_SET_RENDER_TARGET + ComPtr render_target_view; +#endif + +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + HANDLE frame_latency_waitable_obj{}; +#endif + + HANDLE gldx_device{}; +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + HANDLE gldx_color_buffer_rb{}; +#else +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + HANDLE gldx_depth_texture{}; +#endif + HANDLE gldx_color_buffer_tex{}; +#endif + + GLuint gl_fbo{}; +#ifdef OPENGL_DXGI_USE_RENDERBUFFER +#ifdef OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER + GLuint gl_depth_buffer_rb{}; +#endif + GLuint gl_color_buffer_rb{}; +#else +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + GLuint gl_depth_stencil_tex{}; +#endif + GLuint gl_color_buffer_tex{}; +#endif + +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + bool supports_tearing = false; + bool needs_wait = false; +#endif + + DxgiSwapChain() = default; + ~DxgiSwapChain() = default; + + template + friend void memdelete(T *); + + friend class GLManagerNative_Windows; + +public: + static DxgiSwapChain *create(HWND p_hwnd, int p_width, int p_height, ID3D11Device *p_d3d11_device, ID3D11DeviceContext *p_d3d11_device_context); + void destroy(); + + void make_current(); + void release_current(); + + void present(bool p_use_vsync); + void resize_swap_chain(int p_width, int p_height); + void set_use_vsync(bool p_use); + +private: +#if defined(OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER) || defined(OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER) + bool setup_depth_buffer(int p_width, int p_height); + void release_depth_buffer(); +#endif + +#ifdef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + bool setup_intermediate_buffer(int p_width, int p_height); + void release_intermediate_buffer(); +#else + bool setup_render_target(); + void release_render_target(); +#endif + + void lock_for_opengl(); + void unlock_from_opengl(); +}; + +enum class DxgiStatus { + UNINITIALIZED, + DISABLED, + LOADED_UNTESTED, + LOADED_USABLE, +}; + +static DxgiStatus dxgi_status = DxgiStatus::UNINITIALIZED; + +static bool load_dxgi_swap_chain_functions(PFNWGLGETPROCADDRESS gd_wglGetProcAddress, HDC hDC) { + PFNWGLGETEXTENSIONSSTRINGARB gd_wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARB)gd_wglGetProcAddress("wglGetExtensionsStringARB"); + if (!gd_wglGetExtensionsStringARB) { + return false; + } + + const char *extensions = gd_wglGetExtensionsStringARB(hDC); + if (!extensions || !strstr(extensions, "WGL_NV_DX_interop2")) { + print_verbose("GLManagerNative_Windows: Extension WGL_NV_DX_interop2 not available."); + return false; + } + + if (!load_nv_dx_interop(gd_wglGetProcAddress)) { + print_verbose("GLManagerNative_Windows: Failed to load WGL_NV_DX_interop functions."); + return false; + } + + print_verbose("GLManagerNative_Windows: Loaded WGL_NV_DX_interop functions."); + + module_dxgi = LoadLibraryW(L"dxgi.dll"); + if (!module_dxgi) { + print_verbose("GLManagerNative_Windows: Failed to load DXGI."); + return false; + } + + fptr_CreateDXGIFactory1 = (PFNCREATEDXGIFACTORY1)GetProcAddress(module_dxgi, "CreateDXGIFactory1"); + if (!fptr_CreateDXGIFactory1) { + print_verbose("GLManagerNative_Windows: Failed to load DXGI functions.") + FreeLibrary(module_dxgi); + return false; + } + + module_d3d11 = LoadLibraryW(L"d3d11.dll"); + if (!module_d3d11) { + print_verbose("GLManagerNative_Windows: Failed to load D3D11."); + return false; + } + + fptr_D3D11CreateDeviceAndSwapChain = (PFND3D11CREATEDEVICEANDSWAPCHAINPROC)GetProcAddress(module_d3d11, "D3D11CreateDeviceAndSwapChain"); + fptr_D3D11CreateDevice = (PFND3D11CREATEDEVICEPROC)GetProcAddress(module_d3d11, "D3D11CreateDevice"); + if (!fptr_D3D11CreateDeviceAndSwapChain || !fptr_D3D11CreateDevice) { + print_verbose("GLManagerNative_Windows: Failed to load D3D11 functions.") + FreeLibrary(module_d3d11); + return false; + } + + print_verbose("GLManagerNative_Windows: Loaded D3D11 functions."); + return true; +} + +static bool try_create_d3d11_device(ID3D11Device *&p_out_device, ID3D11DeviceContext *&p_out_device_context); +#endif // OPENGL_ON_DXGI_ENABLED + +void GLManagerNative_Windows::set_prefer_dxgi_swap_chain(bool p_prefer) { + prefer_dxgi = p_prefer; +} + +bool GLManagerNative_Windows::is_using_dxgi_swap_chain() { +#ifdef OPENGL_ON_DXGI_ENABLED +#ifdef DEBUG_ENABLED + if (dxgi_status == DxgiStatus::UNINITIALIZED) { + WARN_PRINT("Do not attempt to check is_using_dxgi_swap_chain before first window!"); + } +#endif + + return dxgi_status == DxgiStatus::LOADED_USABLE; +#else + return false; +#endif +} + int GLManagerNative_Windows::_find_or_create_display(GLWindow &win) { // find display NYI, only 1 supported so far if (_displays.size()) { @@ -427,6 +707,24 @@ Error GLManagerNative_Windows::_create_context(GLWindow &win, GLDisplay &gl_disp wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)gd_wglGetProcAddress("wglSwapIntervalEXT"); } +#ifdef OPENGL_ON_DXGI_ENABLED + if (dxgi_status == DxgiStatus::UNINITIALIZED) { + if (prefer_dxgi) { + if (load_dxgi_swap_chain_functions(gd_wglGetProcAddress, win.hDC)) { + if (try_create_d3d11_device(d3d11_device, d3d11_device_context)) { + dxgi_status = DxgiStatus::LOADED_UNTESTED; + } else { + dxgi_status = DxgiStatus::DISABLED; + } + } else { + dxgi_status = DxgiStatus::DISABLED; + } + } else { + dxgi_status = DxgiStatus::DISABLED; + } + } +#endif // OPENGL_ON_DXGI_ENABLED + return OK; } @@ -452,6 +750,27 @@ Error GLManagerNative_Windows::window_create(DisplayServer::WindowID p_window_id return FAILED; } +#ifdef OPENGL_ON_DXGI_ENABLED + if (dxgi_status == DxgiStatus::LOADED_UNTESTED || dxgi_status == DxgiStatus::LOADED_USABLE) { + win.dxgi = DxgiSwapChain::create(win.hwnd, p_width, p_height, d3d11_device, d3d11_device_context); + if (win.dxgi) { + if (dxgi_status == DxgiStatus::LOADED_UNTESTED) { + dxgi_status = DxgiStatus::LOADED_USABLE; + print_verbose("GLManagerNative_Windows: Presenting with D3D11 DXGI swap chain.") + } + } else { + if (dxgi_status == DxgiStatus::LOADED_UNTESTED) { + // If it failed during the first time creating a window, + // just fall back to regular OpenGL SwapBuffers. + dxgi_status = DxgiStatus::DISABLED; + WARN_PRINT("GLManagerNative_Windows: Failed to initialize D3D11 DXGI swap chain, reverting to regular OpenGL."); + } else { + return ERR_CANT_CREATE; + } + } + } +#endif // OPENGL_ON_DXGI_ENABLED + // WARNING: p_window_id is an eternally growing integer since popup windows keep coming and going // and each of them has a higher id than the previous, so it must be used in a map not a vector _windows[p_window_id] = win; @@ -464,17 +783,42 @@ Error GLManagerNative_Windows::window_create(DisplayServer::WindowID p_window_id void GLManagerNative_Windows::window_destroy(DisplayServer::WindowID p_window_id) { GLWindow &win = get_window(p_window_id); + +#ifdef OPENGL_ON_DXGI_ENABLED + if (win.dxgi) { + // We need to destroy some OpenGL resources. + window_make_current(p_window_id); + win.dxgi->destroy(); + win.dxgi = nullptr; + } +#endif // OPENGL_ON_DXGI_ENABLED + if (_current_window == &win) { _current_window = nullptr; } _windows.erase(p_window_id); } +void GLManagerNative_Windows::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) { +#ifdef OPENGL_ON_DXGI_ENABLED + GLWindow &win = get_window(p_window_id); + if (win.dxgi) { + win.dxgi->resize_swap_chain(p_width, p_height); + } +#endif // OPENGL_ON_DXGI_ENABLED +} + void GLManagerNative_Windows::release_current() { if (!_current_window) { return; } +#ifdef OPENGL_ON_DXGI_ENABLED + if (_current_window->dxgi) { + _current_window->dxgi->release_current(); + } +#endif // OPENGL_ON_DXGI_ENABLED + if (!gd_wglMakeCurrent(_current_window->hDC, nullptr)) { ERR_PRINT("Could not detach OpenGL context from window marked current: " + format_error_message(GetLastError())); } @@ -500,11 +844,26 @@ void GLManagerNative_Windows::window_make_current(DisplayServer::WindowID p_wind ERR_PRINT("Could not switch OpenGL context to other window: " + format_error_message(GetLastError())); } +#ifdef OPENGL_ON_DXGI_ENABLED + if (win.dxgi) { + win.dxgi->make_current(); + } +#endif // OPENGL_ON_DXGI_ENABLED + _current_window = &win; } void GLManagerNative_Windows::swap_buffers() { - SwapBuffers(_current_window->hDC); +#ifdef OPENGL_ON_DXGI_ENABLED + GLWindow &win = *_current_window; + if (win.dxgi) { + win.dxgi->present(win.use_vsync); + } else { +#else + { +#endif // OPENGL_ON_DXGI_ENABLED + SwapBuffers(_current_window->hDC); + } } Error GLManagerNative_Windows::initialize() { @@ -519,14 +878,23 @@ void GLManagerNative_Windows::set_use_vsync(DisplayServer::WindowID p_window_id, window_make_current(p_window_id); } - if (wglSwapIntervalEXT) { +#ifdef OPENGL_ON_DXGI_ENABLED + if (win.dxgi) { win.use_vsync = p_use; - - if (!wglSwapIntervalEXT(p_use ? 1 : 0)) { + win.dxgi->set_use_vsync(p_use); + } else { +#else + { +#endif // OPENGL_ON_DXGI_ENABLED + if (wglSwapIntervalEXT) { + win.use_vsync = p_use; + + if (!wglSwapIntervalEXT(p_use ? 1 : 0)) { + WARN_PRINT_ONCE("Could not set V-Sync mode, as changing V-Sync mode is not supported by the graphics driver."); + } + } else { WARN_PRINT_ONCE("Could not set V-Sync mode, as changing V-Sync mode is not supported by the graphics driver."); } - } else { - WARN_PRINT_ONCE("Could not set V-Sync mode, as changing V-Sync mode is not supported by the graphics driver."); } } @@ -552,6 +920,810 @@ GLManagerNative_Windows::GLManagerNative_Windows() { GLManagerNative_Windows::~GLManagerNative_Windows() { release_current(); + +#ifdef OPENGL_ON_DXGI_ENABLED + if (d3d11_device_context) { + d3d11_device_context->Release(); + } + if (d3d11_device) { + d3d11_device->Release(); + } +#endif +} + +#ifdef OPENGL_ON_DXGI_ENABLED + +template +static ComPtr get_dxgi_factory(ID3D11Device *device) { + static_assert(std::is_convertible::value, "Template argument must be IDXGIFactory or a derived type."); + + ComPtr dxgi_device; + HRESULT hr = device->QueryInterface(__uuidof(IDXGIDevice), &dxgi_device); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("Failed to get IDXGIDevice, HRESULT: 0x%08X", (unsigned)hr)); + return {}; + } + ComPtr dxgi_adapter; + hr = dxgi_device->GetAdapter(&dxgi_adapter); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("Failed to get IDXGIAdapter, HRESULT: 0x%08X", (unsigned)hr)); + return {}; + } + ComPtr dxgi_factory; + hr = dxgi_adapter->GetParent(__uuidof(T), &dxgi_factory); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("Failed to get IDXGIFactory, HRESULT: 0x%08X", (unsigned)hr)); + return {}; + } + return dxgi_factory; +} + +static bool try_create_d3d11_device(ID3D11Device *&p_out_device, ID3D11DeviceContext *&p_out_device_context) { + ComPtr device; + ComPtr device_context; + + UINT flags = 0; + if (OS::get_singleton()->is_stdout_verbose()) { + flags |= D3D11_CREATE_DEVICE_DEBUG; + } + + HRESULT hr; + + hr = fptr_D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags, nullptr, 0, D3D11_SDK_VERSION, &device, nullptr, &device_context); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("D3D11CreateDevice failed, HRESULT: 0x%08X", (unsigned)hr)); + return false; + } + +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + ComPtr dxgi_factory_2 = get_dxgi_factory(device.Get()); + if (!dxgi_factory_2) { + ERR_PRINT("Failed to get IDXGIFactory2."); + return false; + } +#endif + + p_out_device = device.Detach(); + p_out_device_context = device_context.Detach(); + return true; +} + +GLManagerNative_Windows::DxgiSwapChain *GLManagerNative_Windows::DxgiSwapChain::create(HWND p_hwnd, int p_width, int p_height, ID3D11Device *p_d3d11_device, ID3D11DeviceContext *p_d3d11_device_context) { + ComPtr device; + ComPtr device_context; + ComPtr swap_chain; + + UINT flags = 0; + if (OS::get_singleton()->is_stdout_verbose()) { + flags |= D3D11_CREATE_DEVICE_DEBUG; + } + +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + HRESULT hr; + +#if 0 + ComPtr dxgi_factory_2; + hr = fptr_CreateDXGIFactory1(__uuidof(IDXGIFactory2), &dxgi_factory_2); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("CreateDXGIFactory1 failed, HRESULT: 0x%08X", (unsigned)hr)); + return nullptr; + } + + ComPtr dxgi_adapter_1; + hr = dxgi_factory_2->EnumAdapters1(0, &dxgi_adapter_1); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("EnumAdapters1 failed, HRESULT: 0x%08X", (unsigned)hr)); + return nullptr; + } +#endif + + device = p_d3d11_device; + device_context = p_d3d11_device_context; + + ComPtr dxgi_factory_2 = get_dxgi_factory(device.Get()); + if (!dxgi_factory_2) { + ERR_PRINT("Failed to get IDXGIFactory2."); + return nullptr; + } + + bool supports_tearing = false; + { + ComPtr dxgi_factory_5; + hr = dxgi_factory_2.As(&dxgi_factory_5); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("Failed to get IDXGIFactory5, HRESULT: 0x%08X", (unsigned)hr)); + } else { + BOOL feature_allow_tearing = FALSE; + hr = dxgi_factory_5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &feature_allow_tearing, sizeof(feature_allow_tearing)); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("Failed to check DXGI_FEATURE_PRESENT_ALLOW_TEARING, HRESULT: 0x%08X", (unsigned)hr)); + } else { + supports_tearing = feature_allow_tearing; + } + } + } + + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc_1 = {}; + swap_chain_desc_1.Width = p_width; + swap_chain_desc_1.Height = p_height; + swap_chain_desc_1.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc_1.SampleDesc.Count = 1; + swap_chain_desc_1.BufferCount = 3; + swap_chain_desc_1.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc_1.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swap_chain_desc_1.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + if (supports_tearing) { + swap_chain_desc_1.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + } + swap_chain_desc_1.Scaling = DXGI_SCALING_NONE; + // TODO: ??? + swap_chain_desc_1.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + + ComPtr swap_chain_1; + hr = dxgi_factory_2->CreateSwapChainForHwnd(device.Get(), p_hwnd, &swap_chain_desc_1, nullptr, nullptr, &swap_chain_1); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("CreateSwapChainForHwnd failed, HRESULT: 0x%08X", (unsigned)hr)); + return nullptr; + } + + swap_chain = swap_chain_1; + + hr = dxgi_factory_2->MakeWindowAssociation(p_hwnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("MakeWindowAssociation failed, HRESULT: 0x%08X", (unsigned)hr)); + } + +#else + DXGI_SWAP_CHAIN_DESC swap_chain_desc = {}; + swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.SampleDesc.Count = 1; +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + swap_chain_desc.BufferCount = 3; +#else + swap_chain_desc.BufferCount = 2; +#endif + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.OutputWindow = p_hwnd; + swap_chain_desc.Windowed = TRUE; +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT | DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; +#else + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; +#endif + + // TODO: Change to use IDXGIFactory2::CreateSwapChainForHwnd so that we + // can specify DXGI_SCALING_NONE + HRESULT hr = fptr_D3D11CreateDeviceAndSwapChain(nullptr, // Adapter + D3D_DRIVER_TYPE_HARDWARE, // DriverType + nullptr, // Software + flags, // Flags + nullptr, // pFeatureLevels + 0, // FeatureLevels + D3D11_SDK_VERSION, // SDKVersion + &swap_chain_desc, // pSwapChainDesc + &swap_chain, // ppSwapChain + &device, // ppDevice + nullptr, // pFeatureLevel + &device_context); // ppImmediateContext + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("D3D11CreateDeviceAndSwapChain failed, HRESULT: 0x%08X", (unsigned)hr)); + return nullptr; + } + + { + ComPtr dxgi_factory = get_dxgi_factory(device.Get()); + if (dxgi_factory) { + hr = dxgi_factory->MakeWindowAssociation(p_hwnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("MakeWindowAssociation failed, HRESULT: 0x%08X", (unsigned)hr)); + } + } + } +#endif + + HANDLE gldx_device = gd_wglDXOpenDeviceNV(device.Get()); + if (!gldx_device) { + ERR_PRINT(vformat("Failed to connect D3D11 swap chain to WGL for interop. Error: %s", format_error_message(GetLastError()))); + gd_wglDXCloseDeviceNV(gldx_device); + return nullptr; + } + +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + HANDLE frame_latency_waitable_obj{}; + { + ComPtr swap_chain_2; + hr = swap_chain.As(&swap_chain_2); + if (SUCCEEDED(hr)) { + frame_latency_waitable_obj = swap_chain_2->GetFrameLatencyWaitableObject(); + } else { + ERR_PRINT(vformat("Failed to get IDXGISwapChain2, HRESULT: 0x%08X", (unsigned)hr)); + } + } +#endif + + // HACK: We need OpenGL functions _now_ but RasterizerGLES3 might not + // have been initialized yet. + gladLoaderLoadGL(); + + // Generate the FBO we use for the window. + GLuint gl_fbo; + glGenFramebuffers(1, &gl_fbo); + + // Generate texture names for render target and depth buffers. +#ifdef OPENGL_DXGI_USE_RENDERBUFFER +#ifdef OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER + GLuint renderbuffers[2] = {}; + glGenRenderbuffers(2, renderbuffers); + GLuint gl_depth_buffer_rb = renderbuffers[0]; + GLuint gl_color_buffer_rb = renderbuffers[1]; +#else + GLuint gl_color_buffer_rb; + glGenRenderbuffers(1, &gl_color_buffer_rb); +#endif +#else +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + GLuint textures[2] = {}; + glGenTextures(2, textures); + GLuint gl_depth_stencil_tex = textures[0]; + GLuint gl_color_buffer_tex = textures[1]; +#else + GLuint gl_color_buffer_tex; + glGenTextures(1, &gl_color_buffer_tex); +#endif +#endif + + GLManagerNative_Windows::DxgiSwapChain *dxgi = memnew(GLManagerNative_Windows::DxgiSwapChain); + dxgi->device = std::move(device); + dxgi->device_context = std::move(device_context); + dxgi->swap_chain = std::move(swap_chain); +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + dxgi->frame_latency_waitable_obj = frame_latency_waitable_obj; + dxgi->needs_wait = (frame_latency_waitable_obj != nullptr); +#endif + dxgi->gldx_device = gldx_device; + dxgi->gl_fbo = gl_fbo; +#ifdef OPENGL_DXGI_USE_RENDERBUFFER +#ifdef OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER + dxgi->gl_depth_buffer_rb = gl_depth_buffer_rb; +#endif + dxgi->gl_color_buffer_rb = gl_color_buffer_rb; +#else +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + dxgi->gl_depth_stencil_tex = gl_depth_stencil_tex; +#endif + dxgi->gl_color_buffer_tex = gl_color_buffer_tex; +#endif +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + dxgi->supports_tearing = supports_tearing; +#endif + + GLES3::TextureStorage::system_fbo = gl_fbo; + +#if defined(OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER) || defined(OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER) + if (!dxgi->setup_depth_buffer(p_width, p_height)) { + GLES3::TextureStorage::system_fbo = 0; + memdelete(dxgi); + return nullptr; + } +#endif + +#ifdef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + if (!dxgi->setup_intermediate_buffer(p_width, p_height)) { +#else + if (!dxgi->setup_render_target()) { +#endif + GLES3::TextureStorage::system_fbo = 0; +#if defined(OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER) || defined(OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER) + dxgi->release_depth_buffer(); +#endif + memdelete(dxgi); + return nullptr; + } + +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + ComPtr swap_chain_2; + hr = dxgi->swap_chain.As(&swap_chain_2); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("Failed to get IDXGISwapChain2, HRESULT: 0x%08X", (unsigned)hr)); + } else { + // TODO: ??? + swap_chain_2->SetMaximumFrameLatency(2); + } +#else + ComPtr device1; + hr = dxgi->device.As(&device1); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("Failed to get IDXGIDevice1, HRESULT: 0x%08X", (unsigned)hr)); + } else { + // TODO: ??? + device1->SetMaximumFrameLatency(2); + } +#endif + + dxgi->lock_for_opengl(); + + return dxgi; +} + +void GLManagerNative_Windows::DxgiSwapChain::destroy() { + if (GLES3::TextureStorage::system_fbo == gl_fbo) { + GLES3::TextureStorage::system_fbo = 0; + } + + unlock_from_opengl(); +#ifdef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + release_intermediate_buffer(); +#else + release_render_target(); +#endif +#if defined(OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER) || defined(OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER) + release_depth_buffer(); +#endif + + // FIXME: Is this safe to do? Could our OpenGL context not be current? + glDeleteFramebuffers(1, &gl_fbo); +#ifdef OPENGL_DXGI_USE_RENDERBUFFER +#ifdef OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER + GLuint renderbuffers[2] = { gl_depth_buffer_rb, gl_color_buffer_rb }; + glDeleteRenderbuffers(2, renderbuffers); +#else + glDeleteRenderbuffers(1, &gl_color_buffer_rb); +#endif +#else +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + GLuint textures[2] = { gl_depth_stencil_tex, gl_color_buffer_tex }; + glDeleteTextures(2, textures); +#else + glDeleteTextures(1, &gl_color_buffer_tex); +#endif +#endif + + gd_wglDXCloseDeviceNV(gldx_device); + +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + if (!CloseHandle(frame_latency_waitable_obj)) { + ERR_PRINT(vformat("Failed to CloseHandle on frame latency waitable object. Error: %s", format_error_message(GetLastError()))); + } +#endif + + swap_chain.Reset(); + device_context.Reset(); + device.Reset(); + + memdelete(this); +} + +void GLManagerNative_Windows::DxgiSwapChain::make_current() { + GLES3::TextureStorage::system_fbo = gl_fbo; +} + +void GLManagerNative_Windows::DxgiSwapChain::release_current() { + if (GLES3::TextureStorage::system_fbo != gl_fbo) { +#ifdef DEBUG_ENABLED + WARN_PRINT("Trying to release D3D11 target but system_fbo has changed!"); +#endif + return; + } + GLES3::TextureStorage::system_fbo = 0; } +#if defined(OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER) || defined(OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER) +bool GLManagerNative_Windows::DxgiSwapChain::setup_depth_buffer(int p_width, int p_height) { +#ifdef OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER + glBindRenderbuffer(GL_RENDERBUFFER, gl_depth_buffer_rb); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, p_width, p_height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + return true; +#else + // Godot uses 24-bit depth buffer? + ComPtr depth_texture_new; + CD3D11_TEXTURE2D_DESC depth_buffer_desc(DXGI_FORMAT_R24G8_TYPELESS, p_width, p_height, 1, 1, D3D11_BIND_DEPTH_STENCIL); + HRESULT hr = device->CreateTexture2D( + &depth_buffer_desc, + nullptr, + &depth_texture_new); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("CreateTexture2D failed, HRESULT: 0x%08X", (unsigned)hr)); + return false; + } + + D3D11_DEPTH_STENCIL_VIEW_DESC depth_stencil_view_desc{}; + depth_stencil_view_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + depth_stencil_view_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + ComPtr depth_stencil_view_new; + hr = device->CreateDepthStencilView( + depth_texture_new.Get(), + &depth_stencil_view_desc, + &depth_stencil_view_new); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("CreateDepthStencilView failed, HRESULT: 0x%08X", (unsigned)hr)); + return false; + } + + HANDLE gldx_depth_texture_new = gd_wglDXRegisterObjectNV( + gldx_device, + depth_texture_new.Get(), + gl_depth_stencil_tex, + GL_TEXTURE_2D, + WGL_ACCESS_READ_WRITE_NV); + if (!gldx_depth_texture_new) { + ERR_PRINT(vformat("Failed to connect D3D11 depth texture to WGL for interop. Error: %s", format_error_message(GetLastError()))); + return false; + } + + depth_texture = std::move(depth_texture_new); + depth_stencil_view = std::move(depth_stencil_view_new); + gldx_depth_texture = gldx_depth_texture_new; + + return true; +#endif +} + +void GLManagerNative_Windows::DxgiSwapChain::release_depth_buffer() { +#ifdef OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER + // no-op? +#else + BOOL res = gd_wglDXUnregisterObjectNV(gldx_device, gldx_depth_texture); + if (!res) { + ERR_PRINT(vformat("Failed to unregister depth buffer for interop. Error: %s", format_error_message(GetLastError()))); + } + + gldx_depth_texture = nullptr; + depth_stencil_view.Reset(); + depth_texture.Reset(); +#endif +} +#endif // OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + +#ifdef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER +bool GLManagerNative_Windows::DxgiSwapChain::setup_intermediate_buffer(int p_width, int p_height) { + ComPtr intermediate_buffer_new; + CD3D11_TEXTURE2D_DESC intermediate_buffer_desc(DXGI_FORMAT_R8G8B8A8_UNORM, p_width, p_height, 1, 1, D3D11_BIND_RENDER_TARGET); + HRESULT hr = device->CreateTexture2D( + &intermediate_buffer_desc, + nullptr, + &intermediate_buffer_new); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("CreateTexture2D failed, HRESULT: 0x%08X", (unsigned)hr)); + return false; + } + + HANDLE gldx_color_buffer_rb_new = gd_wglDXRegisterObjectNV( + gldx_device, + intermediate_buffer_new.Get(), + gl_color_buffer_rb, + GL_RENDERBUFFER, + WGL_ACCESS_READ_WRITE_NV); + if (!gldx_color_buffer_rb_new) { + ERR_PRINT(vformat("Failed to connect D3D11 intermediate texture to WGL for interop. Error: %s", format_error_message(GetLastError()))); + return false; + } + + intermediate_buffer = std::move(intermediate_buffer_new); + gldx_color_buffer_rb = gldx_color_buffer_rb_new; + + return true; +} + +void GLManagerNative_Windows::DxgiSwapChain::release_intermediate_buffer() { + BOOL res = gd_wglDXUnregisterObjectNV(gldx_device, gldx_color_buffer_rb); + if (!res) { + ERR_PRINT(vformat("Failed to unregister color buffer for interop. Error: %s", format_error_message(GetLastError()))); + } + + gldx_color_buffer_rb = nullptr; + intermediate_buffer.Reset(); +} + +#else // OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + +bool GLManagerNative_Windows::DxgiSwapChain::setup_render_target() { + // Get the current back buffer from the swap chain. + ComPtr color_buffer_new; + HRESULT hr = swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), &color_buffer_new); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("GetBuffer failed, HRESULT: 0x%08X", (unsigned)hr)); + return false; + } + +#ifdef OPENGL_DXGI_SET_RENDER_TARGET + // TODO: Do we need to chedk OS::get_singleton()->is_layered_allowed() here? + CD3D11_RENDER_TARGET_VIEW_DESC render_target_view_desc(D3D11_RTV_DIMENSION_TEXTURE2D, DXGI_FORMAT_R8G8B8A8_UNORM); + ComPtr render_target_view_new; + hr = device->CreateRenderTargetView( + color_buffer_new.Get(), + &render_target_view_desc, + &render_target_view_new); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("CreateRenderTargetView failed, HRESULT: 0x%08X", (unsigned)hr)); + return false; + } +#endif + + // Register for interop. +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + HANDLE gldx_color_buffer_rb_new = gd_wglDXRegisterObjectNV(gldx_device, + color_buffer_new.Get(), + gl_color_buffer_rb, + GL_RENDERBUFFER, + WGL_ACCESS_READ_WRITE_NV); +#else + HANDLE gldx_color_buffer_tex_new = gd_wglDXRegisterObjectNV(gldx_device, + color_buffer_new.Get(), + gl_color_buffer_tex, + GL_TEXTURE_2D, + WGL_ACCESS_READ_WRITE_NV); +#endif +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + if (!gldx_color_buffer_rb_new) { +#else + if (!gldx_color_buffer_tex_new) { +#endif + ERR_PRINT(vformat("Failed to connect D3D11 color buffer to WGL for interop. Error: %s", format_error_message(GetLastError()))); + return false; + } + +#ifdef OPENGL_DXGI_SET_RENDER_TARGET + // Attach back buffer and depth buffer to the render target. + device_context->OMSetRenderTargets(1, + render_target_view_new.GetAddressOf(), +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + depth_stencil_view.Get() +#else + nullptr +#endif + ); + + // TODO: Is it okay if we don't clear at all? + // float clear_color[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + // device_context->ClearRenderTargetView(render_target_view_new.Get(), clear_color); +#endif + + color_buffer = std::move(color_buffer_new); +#ifdef OPENGL_DXGI_SET_RENDER_TARGET + render_target_view = std::move(render_target_view_new); +#endif +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + gldx_color_buffer_rb = gldx_color_buffer_rb_new; +#else + gldx_color_buffer_tex = gldx_color_buffer_tex_new; +#endif + + return true; +} + +void GLManagerNative_Windows::DxgiSwapChain::release_render_target() { + // Release the back buffer. +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + BOOL res = gd_wglDXUnregisterObjectNV(gldx_device, gldx_color_buffer_rb); +#else + BOOL res = gd_wglDXUnregisterObjectNV(gldx_device, gldx_color_buffer_tex); +#endif + if (!res) { + ERR_PRINT(vformat("Failed to unregister color buffer for interop. Error: %s", format_error_message(GetLastError()))); + } + +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + gldx_color_buffer_rb = nullptr; +#else + gldx_color_buffer_tex = nullptr; +#endif +#ifdef OPENGL_DXGI_SET_RENDER_TARGET + render_target_view.Reset(); +#endif + color_buffer.Reset(); +} +#endif // OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + +void GLManagerNative_Windows::DxgiSwapChain::lock_for_opengl() { + // Lock the buffers for OpenGL access. +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + BOOL res = gd_wglDXLockObjectsNV(gldx_device, 1, &gldx_color_buffer_rb); +#else +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + void *handles[] = { gldx_depth_texture, gldx_color_buffer_tex }; + BOOL res = gd_wglDXLockObjectsNV(gldx_device, 2, handles); +#else + BOOL res = gd_wglDXLockObjectsNV(gldx_device, 1, &gldx_color_buffer_tex); +#endif +#endif + if (!res) { + ERR_PRINT(vformat("Failed to lock DX objects for interop. Error: %s", format_error_message(GetLastError()))); + } + + // Attach color and depth buffers to FBO + glBindFramebuffer(GL_FRAMEBUFFER, gl_fbo); +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, gl_color_buffer_rb); +#ifdef OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, gl_depth_buffer_rb); +#endif +#else + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl_color_buffer_tex, 0); +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, gl_depth_stencil_tex, 0); +#endif +#endif + if (GLES3::TextureStorage::system_fbo != gl_fbo) { + glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); + } +} + +void GLManagerNative_Windows::DxgiSwapChain::unlock_from_opengl() { + // Detach color and depth buffers from FBO + glBindFramebuffer(GL_FRAMEBUFFER, gl_fbo); +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0); +#ifdef OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0); +#endif +#else + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); +#endif +#endif + if (GLES3::TextureStorage::system_fbo != gl_fbo) { + glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); + } + + // Unlock from OpenGL access. +#ifdef OPENGL_DXGI_USE_RENDERBUFFER + BOOL res = gd_wglDXUnlockObjectsNV(gldx_device, 1, &gldx_color_buffer_rb); +#else +#ifdef OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER + void *handles[] = { gldx_depth_texture, gldx_color_buffer_tex }; + BOOL res = gd_wglDXUnlockObjectsNV(gldx_device, 2, handles); +#else + BOOL res = gd_wglDXUnlockObjectsNV(gldx_device, 1, &gldx_color_buffer_tex); +#endif +#endif + if (!res) { + ERR_PRINT(vformat("Failed to unlock DX objects for interop. Error: %s", format_error_message(GetLastError()))); + } +} + +void GLManagerNative_Windows::DxgiSwapChain::present(bool p_use_vsync) { + unlock_from_opengl(); +#ifndef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + release_render_target(); +#endif + + HRESULT hr; + +#ifdef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + // Now we copy our intermediate buffer to the back buffer. + ComPtr color_buffer_new; + hr = swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), &color_buffer_new); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("GetBuffer failed, HRESULT: 0x%08X", (unsigned)hr)); + return false; + } + + device_context->CopyResource(color_buffer_new.Get(), intermediate_buffer.Get()); +#endif + +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + bool skip_present = false; + if (needs_wait) { + DWORD wait = WaitForSingleObject(frame_latency_waitable_obj, 0); + if (wait != WAIT_OBJECT_0) { + skip_present = true; + if (wait == WAIT_TIMEOUT) { + // Force a redraw so that we can present the latest frame + // for this window on the next redraw. + Main::force_redraw(); + } else if (wait == WAIT_FAILED) { + DWORD error = GetLastError(); + ERR_PRINT(vformat("Wait for frame latency waitable failed with error: 0x%08X", (unsigned)error)); + } else { + ERR_PRINT(vformat("Wait for frame latency waitable failed, WaitForSingleObject returned 0x%08X", (unsigned)wait)); + } + } + } + if (!skip_present) { + if (p_use_vsync) { + if (!needs_wait) { + hr = swap_chain->Present(1, 0); + } else { + // Not the main window, or not in the main loop (e.g. resizing). + hr = swap_chain->Present(0, 0); + } + } else { + hr = swap_chain->Present(0, supports_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0); + } + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("Present failed, HRESULT: 0x%08X", (unsigned)hr)); + } + needs_wait = (frame_latency_waitable_obj != nullptr); + } +#else + // TODO: vsync??? + hr = swap_chain->Present(p_use_vsync ? 1 : 0, 0); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("Present failed, HRESULT: 0x%08X", (unsigned)hr)); + } +#endif + +#ifndef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + setup_render_target(); +#endif + lock_for_opengl(); +} + +void GLManagerNative_Windows::DxgiSwapChain::resize_swap_chain(int p_width, int p_height) { + unlock_from_opengl(); +#ifdef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + release_intermediate_buffer(); +#else + release_render_target(); +#endif +#if defined(OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER) || defined(OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER) + release_depth_buffer(); +#endif + + UINT flags = 0; +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + if (supports_tearing) { + flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + } +#endif + HRESULT hr = swap_chain->ResizeBuffers(0, p_width, p_height, DXGI_FORMAT_UNKNOWN, flags); + if (!SUCCEEDED(hr)) { + ERR_PRINT(vformat("ResizeBuffers failed, HRESULT: 0x%08X", (unsigned)hr)); + } + +#if defined(OPENGL_DXGI_USE_D3D11_DEPTH_BUFFER) || defined(OPENGL_DXGI_ADD_DEPTH_RENDERBUFFER) + if (setup_depth_buffer(p_width, p_height)) { +#else + { +#endif +#ifdef OPENGL_DXGI_USE_INTERMEDIATE_BUFFER + setup_intermediate_buffer(p_width, p_height); +#else + setup_render_target(); +#endif + } + lock_for_opengl(); +} + +void GLManagerNative_Windows::DxgiSwapChain::set_use_vsync(bool p_use) { + // TODO: ??? +} + +void GLManagerNative_Windows::wait_for_present(DisplayServer::WindowID p_window_id) { +#ifdef OPENGL_DXGI_USE_FLIP_MODEL + GLWindow *win = nullptr; + if (RBMap::Element *item = _windows.find(p_window_id)) { + win = &item->value(); + } else if ((item = _windows.find(DisplayServer::MAIN_WINDOW_ID))) { + p_window_id = DisplayServer::MAIN_WINDOW_ID; + win = &item->value(); + } else { +#ifdef DEBUG_ENABLED + WARN_PRINT("Cannot wait for present when there are no valid windows."); +#endif + return; + } + + if (win->dxgi && win->dxgi->needs_wait) { + DWORD wait = WaitForSingleObject(win->dxgi->frame_latency_waitable_obj, 1000); + if (wait != WAIT_OBJECT_0) { + if (wait == WAIT_FAILED) { + DWORD error = GetLastError(); + ERR_PRINT(vformat("Wait for frame latency waitable failed with error: 0x%08X", (unsigned)error)); + } else { + ERR_PRINT(vformat("Wait for frame latency waitable failed, WaitForSingleObject returned 0x%08X", (unsigned)wait)); + } + } else { + win->dxgi->needs_wait = false; + } + } +#endif +} + +#endif // OPENGL_ON_DXGI_ENABLED + #endif // WINDOWS_ENABLED && GLES3_ENABLED diff --git a/platform/windows/gl_manager_windows_native.h b/platform/windows/gl_manager_windows_native.h index 532092ae7439..3d0d414cef04 100644 --- a/platform/windows/gl_manager_windows_native.h +++ b/platform/windows/gl_manager_windows_native.h @@ -43,8 +43,13 @@ typedef bool(APIENTRY *PFNWGLSWAPINTERVALEXTPROC)(int interval); typedef int(APIENTRY *PFNWGLGETSWAPINTERVALEXTPROC)(void); +struct ID3D11Device; +struct ID3D11DeviceContext; + class GLManagerNative_Windows { private: + class DxgiSwapChain; + // any data specific to the window struct GLWindow { bool use_vsync = false; @@ -54,6 +59,8 @@ class GLManagerNative_Windows { HWND hwnd; int gldisplay_id = 0; + + DxgiSwapChain *dxgi = nullptr; }; struct GLDisplay { @@ -68,6 +75,9 @@ class GLManagerNative_Windows { PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr; + ID3D11Device *d3d11_device = nullptr; + ID3D11DeviceContext *d3d11_device_context = nullptr; + GLWindow &get_window(unsigned int id) { return _windows[id]; } const GLWindow &get_window(unsigned int id) const { return _windows[id]; } @@ -75,6 +85,7 @@ class GLManagerNative_Windows { const GLDisplay &get_display(unsigned int id) { return _displays[id]; } bool direct_render; + bool prefer_dxgi = false; int glx_minor, glx_major; private: @@ -85,10 +96,11 @@ class GLManagerNative_Windows { public: Error window_create(DisplayServer::WindowID p_window_id, HWND p_hwnd, HINSTANCE p_hinstance, int p_width, int p_height); void window_destroy(DisplayServer::WindowID p_window_id); - void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {} + void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); void release_current(); void swap_buffers(); + void wait_for_present(DisplayServer::WindowID p_window_id); void window_make_current(DisplayServer::WindowID p_window_id); @@ -100,6 +112,10 @@ class GLManagerNative_Windows { HDC get_hdc(DisplayServer::WindowID p_window_id); HGLRC get_hglrc(DisplayServer::WindowID p_window_id); + void set_prefer_dxgi_swap_chain(bool p_prefer); + // Only valid after creating the first window. + bool is_using_dxgi_swap_chain(); + GLManagerNative_Windows(); ~GLManagerNative_Windows(); };