Created
October 24, 2024 04:33
-
-
Save stillwwater/c0d2679fef8aa9d8f4a7d01819ab1c7c to your computer and use it in GitHub Desktop.
OpenGL on DXGI swapchain
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define COBJMACROS | |
#include <windows.h> | |
#include <d3d11.h> | |
#include <dxgi1_3.h> | |
cvar_t vid_dxgi = {"vid_dxgi", 0, CVAR_BOOL | CVAR_ALLOW | CVAR_INIT}; // DXGI layer for OpenGL | |
cvar_t vid_waitsync = {"vid_waitsync", 0, CVAR_BOOL | CVAR_ALLOW}; // lower latency, lower framerate | |
cvar_t vid_display = {"vid_display", -1, CVAR_INT | CVAR_ALLOW}; // -1 = primary | |
cvar_t vid_fullscreen = {"vid_fullscreen", 1, CVAR_BOOL | CVAR_ALLOW}; | |
HANDLE vid_hwnd; | |
HANDLE vid_hdc; | |
// D3D state | |
ID3D11Device *vdx_device; | |
ID3D11DeviceContext *vdx_dc; | |
IDXGISwapChain *vdx_swap_chain; | |
ID3D11Texture2D *vdx_ds_texture; | |
ID3D11Texture2D *vdx_color_texture; | |
ID3D11Texture2D *vdx_gl_color_texture; // texture mapped to GL fbo | |
ID3D11RenderTargetView *vdx_rtv; | |
ID3D11DepthStencilView *vdx_dsv; | |
HANDLE *vdx_frame_sync_object; | |
// GL/DX interop state | |
HANDLE vdx_gl_device; | |
HANDLE vdx_gl_dsv; // handler to D3D view | |
HANDLE vdx_gl_rtv; | |
GLuint vdx_gl_dsv_name; // handle to GL texture | |
GLuint vdx_gl_rtv_name; | |
GLuint vdx_gl_fbo; | |
#define vdx_disable_if_error(x) \ | |
if (FAILED(hr)) { print("[dxgi] dxgi disabled 0x%08X\n", (int)hr); vid_dxgi.value = 0; return; } | |
#define vdx_disable_if_zero(x) \ | |
if (!(x)) { print("[dxgi] disabled (failed to create %s)\n", #x); vid_dxgi.value = 0; return; } | |
// Windows GL extensions | |
typedef HGLRC wglCreateContextAttribsARB_t(HDC hDC, HGLRC hShareContext, const int *attribList); | |
typedef BOOL wglChoosePixelFormatARB_t(HDC hdc, const int *piAttribIList, | |
const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); | |
HMODULE opengl32_dll; | |
wglCreateContextAttribsARB_t *wglCreateContextAttribsARB; | |
wglChoosePixelFormatARB_t *wglChoosePixelFormatARB; | |
// Initializes an OpenGL device context for a given window. Failing to create a | |
// device context results in a fatal error. | |
void | |
vgl_init(void *window) | |
{ | |
HDC hdc; | |
HGLRC hglrc; | |
int pfd_index; | |
PIXELFORMATDESCRIPTOR pfd, device_pfd; | |
profile("vgl_init"); | |
HWND hwnd = window; | |
// in order to load a modern opengl context we first need to load an old | |
// opengl using a dummy window and use it to query the extension to load a | |
// modern context. | |
WNDCLASSA wndclass = {0}; | |
wndclass.lpfnWndProc = DefWindowProcA; | |
wndclass.hInstance = GetModuleHandleA(NULL); | |
wndclass.lpszClassName = "MV_WNDWGLLOADER"; | |
RegisterClassA(&wndclass); | |
HWND glwnd = CreateWindowA("MV_WNDWGLLOADER", "MOONVEIL", | |
CS_OWNDC, 0, 0, 0, 0, NULL, NULL, wndclass.hInstance, NULL); | |
if (!glwnd) { | |
fatal_error("Failed to create dummy GL window (0x%lX)", GetLastError()); | |
} | |
ZeroMemory(&pfd, sizeof(pfd)); | |
// we're doing this again later, for now all we care about is a PFD that | |
// supports OpenGL | |
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); | |
pfd.nVersion = 1; | |
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; | |
pfd.iPixelType = PFD_TYPE_RGBA; | |
pfd.cColorBits = 32; | |
pfd.cAlphaBits = 8; | |
pfd.cDepthBits = 24; | |
pfd.cStencilBits = 8; | |
pfd.iLayerType = PFD_MAIN_PLANE; | |
hdc = GetDC(glwnd); | |
pfd_index = ChoosePixelFormat(hdc, &pfd); | |
DescribePixelFormat(hdc, pfd_index, sizeof(device_pfd), &device_pfd); | |
if (!SetPixelFormat(hdc, pfd_index, &device_pfd)) { | |
fatal_error("Failed to find a suitable pixel format for the graphics context (0x%lX).", GetLastError()); | |
} | |
// create opengl device context for the current window | |
hglrc = wglCreateContext(hdc); | |
if (!wglMakeCurrent(hdc, hglrc)) { | |
fatal_error("Failed to create an opengl context (0x%lX).", GetLastError()); | |
} | |
// load the extensions we need in order to load the real context | |
wglCreateContextAttribsARB = (wglCreateContextAttribsARB_t *)wglGetProcAddress("wglCreateContextAttribsARB"); | |
wglChoosePixelFormatARB = (wglChoosePixelFormatARB_t *)wglGetProcAddress("wglChoosePixelFormatARB"); | |
if (!wglCreateContextAttribsARB || !wglChoosePixelFormatARB) { | |
fatal_error("Failed to load opengl extensions"); | |
} | |
wglMakeCurrent(hdc, 0); | |
wglDeleteContext(hglrc); | |
ReleaseDC(glwnd, hdc); | |
DestroyWindow(glwnd); | |
opengl32_dll = LoadLibraryA("opengl32.dll"); | |
if (!opengl32_dll) { | |
fatal_error("Missing opengl32.dll (0x%lX)", GetLastError()); | |
} | |
// do it for real this time | |
hdc = GetDC(hwnd); | |
ZeroMemory(&pfd, sizeof pfd); | |
int pf_attribs[] = { | |
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, | |
WGL_SUPPORT_OPENGL_ARB, GL_TRUE, | |
WGL_DOUBLE_BUFFER_ARB, GL_TRUE, | |
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, | |
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, | |
WGL_COLOR_BITS_ARB, 32, | |
WGL_DEPTH_BITS_ARB, 24, | |
WGL_STENCIL_BITS_ARB, 8, | |
WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, GL_TRUE, | |
0, | |
}; | |
// note that on intel drivers passing GL_FALSE to | |
// WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB also sets the default buffer as | |
// sRGB. Remove the property entirely for a linear buffer. | |
int context_attribs[] = { | |
WGL_CONTEXT_MAJOR_VERSION_ARB, MV_MIN_GL_MAJOR_VERSION, | |
WGL_CONTEXT_MINOR_VERSION_ARB, MV_MIN_GL_MINOR_VERSION, | |
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, | |
0, | |
}; | |
UINT n_formats; | |
wglChoosePixelFormatARB(hdc, pf_attribs, NULL, 1, &pfd_index, &n_formats); | |
if (n_formats == 0) { | |
fatal_error("No suitable pixel format returned by wglChoosePixelFormatARB."); | |
} | |
DescribePixelFormat(hdc, pfd_index, sizeof(pfd), &pfd); | |
if (!SetPixelFormat(hdc, pfd_index, &pfd)) { | |
fatal_error("Failed to find a suitable pixel format for the graphics context (0x%lX).", GetLastError()); | |
} | |
hglrc = wglCreateContextAttribsARB(hdc, 0, context_attribs); | |
if (!wglMakeCurrent(hdc, hglrc)) { | |
fatal_error("Failed to create an opengl context (0x%lX).", GetLastError()); | |
} | |
ReleaseDC(hwnd, hdc); | |
} | |
void * | |
vgl_proc(char *name) | |
{ | |
PROC proc; | |
// try getting it from the driver | |
proc = wglGetProcAddress(name); | |
if (!proc) { | |
// try getting it from the OS | |
proc = GetProcAddress(opengl32_dll, name); | |
} | |
return (void *)proc; // ok on architectures we care about | |
} | |
void | |
vgl_present(void) | |
{ | |
wglSwapIntervalEXT(vid_vsync.value); | |
SwapBuffers(vid_hdc); | |
} | |
D3D11_TEXTURE2D_DESC | |
vdx_texture_desc(DXGI_FORMAT format, UINT bind_flags) | |
{ | |
D3D11_TEXTURE2D_DESC dsdesc = {0}; | |
dsdesc.Format = format; | |
dsdesc.Width = vid_width.value; | |
dsdesc.Height = vid_height.value; | |
dsdesc.MipLevels = 1; | |
dsdesc.ArraySize = 1; | |
dsdesc.SampleDesc.Count = 1; | |
dsdesc.Usage = D3D11_USAGE_DEFAULT; | |
dsdesc.BindFlags = bind_flags; | |
return dsdesc; | |
} | |
void | |
vdx_init(void *window) | |
{ | |
HRESULT hr; | |
D3D11_TEXTURE2D_DESC tdesc; | |
print("vdx_init (DXGI layer for OpenGL)\n"); | |
vid_hwnd = window; | |
// Create D3D device and swap chain | |
DXGI_SWAP_CHAIN_DESC sdesc = {0}; | |
sdesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
sdesc.SampleDesc.Count = 1; | |
sdesc.BufferCount = 2; // double buffer | |
sdesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; | |
sdesc.OutputWindow = vid_hwnd; | |
sdesc.Windowed = true; | |
sdesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; | |
sdesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT | |
| DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING | |
| DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; | |
int flags = 0; | |
#ifndef NDEBUG | |
flags |= D3D11_CREATE_DEVICE_DEBUG; | |
#endif | |
hr = D3D11CreateDeviceAndSwapChain( | |
NULL, // pAdapter | |
D3D_DRIVER_TYPE_HARDWARE, // DriverType | |
NULL, // Software | |
flags, | |
NULL, // pFeatureLevels | |
0, // FeatureLevels | |
D3D11_SDK_VERSION, | |
&sdesc, // pSwapChainDesc | |
&vdx_swap_chain, | |
&vdx_device, | |
NULL, // pFeatureLevel | |
&vdx_dc); | |
vdx_disable_if_error(hr); | |
// get swap chain sync object | |
IDXGISwapChain2 *swap_chain2; | |
hr = IDXGISwapChain_QueryInterface(vdx_swap_chain, &IID_IDXGISwapChain2, (void **)&swap_chain2); | |
vdx_disable_if_error(hr); | |
vdx_frame_sync_object = IDXGISwapChain2_GetFrameLatencyWaitableObject(swap_chain2); | |
// create depth stencil target texture and view | |
tdesc = vdx_texture_desc(DXGI_FORMAT_D24_UNORM_S8_UINT, D3D11_BIND_DEPTH_STENCIL); | |
hr = ID3D11Device_CreateTexture2D(vdx_device, &tdesc, NULL, &vdx_ds_texture); | |
vdx_disable_if_error(hr); | |
// create color target texture for OpenGL, view is created every frame | |
tdesc = vdx_texture_desc(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, D3D11_BIND_RENDER_TARGET); | |
hr = ID3D11Device_CreateTexture2D(vdx_device, &tdesc, NULL, &vdx_gl_color_texture); | |
vdx_disable_if_error(hr); | |
} | |
void | |
vdx_lock_frame(void) | |
{ | |
// wait for an available back buffer | |
// this reduces the average framerate since it prevents the GPU from | |
// keeping multiple frames in flight, but significantly improves latency | |
// (especially with vsync) | |
WaitForSingleObject(vdx_frame_sync_object, INFINITE); | |
// get the backbuffer from the FLIP swap chain | |
IDXGISwapChain_GetBuffer(vdx_swap_chain, 0, &IID_ID3D11Texture2D, (void *)&vdx_color_texture); | |
// RTV for the current swap chain backbuffer | |
D3D11_RENDER_TARGET_VIEW_DESC rtvdesc = {DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, D3D11_RTV_DIMENSION_TEXTURE2D}; | |
ID3D11Device_CreateRenderTargetView(vdx_device, (void *)vdx_color_texture, &rtvdesc, &vdx_rtv); | |
ID3D11DeviceContext_OMSetRenderTargets(vdx_dc, 1, &vdx_rtv, /* vdx_dsv */ NULL); | |
// lock resources for GL access | |
wglDXLockObjectsNV(vdx_gl_device, 1, &vdx_gl_rtv); | |
} | |
void | |
vdx_present(void) | |
{ | |
wglDXUnlockObjectsNV(vdx_gl_device, 1, &vdx_gl_rtv); | |
// copy shared DX/GL framebuffer to swap chain | |
// | |
// ideally the swap chain buffer could be used directly by OpenGL to avoid | |
// a copy, but in practice calling wglDXRegisterObjectNV every frame with | |
// the swap chain buffer leads to highly inconsistent frame times | |
ID3D11DeviceContext_CopyResource(vdx_dc, (void *)vdx_color_texture, (void *)vdx_gl_color_texture); | |
int flags = 0; | |
int swap = vid_vsync.value; | |
if (!vid_vsync.value) | |
flags |= DXGI_PRESENT_ALLOW_TEARING; | |
if (vid_vsync.value < 0) | |
swap = 1; | |
IDXGISwapChain_Present(vdx_swap_chain, swap, flags); | |
// release backbuffer back to the swap chain | |
ID3D11RenderTargetView_Release(vdx_rtv); | |
} | |
void | |
vid_init(void *window) | |
{ | |
profile("vid_init"); | |
print("vid_init\n"); | |
vid_hdc = GetDC(window); | |
// init OpenGL | |
vgl_init(window); | |
gl_init(); | |
if (vid_dxgi.value) { | |
// if anything goes wrong while setting up DXGI, disable it and use the | |
// normal GDI path | |
vdx_disable_if_zero(wglDXRegisterObjectNV); | |
vdx_disable_if_zero(wglDXUnregisterObjectNV); | |
vdx_disable_if_zero(wglDXOpenDeviceNV); | |
vdx_disable_if_zero(wglDXCloseDeviceNV); | |
vdx_disable_if_zero(wglDXLockObjectsNV); | |
vdx_disable_if_zero(wglDXUnlockObjectsNV); | |
// init D3D state | |
vdx_init(window); | |
vdx_gl_device = wglDXOpenDeviceNV(vdx_device); | |
vdx_disable_if_zero(vdx_gl_device); | |
// wrap D3D textures in GL objects | |
glGenTextures(1, &vdx_gl_rtv_name); | |
vdx_gl_rtv = wglDXRegisterObjectNV(vdx_gl_device, vdx_gl_color_texture, vdx_gl_rtv_name, | |
GL_TEXTURE_2D, WGL_ACCESS_READ_WRITE_NV); | |
vdx_disable_if_zero(vdx_gl_rtv); | |
// lock resources before attaching to the framebuffer | |
// this is needed so glCheckFramebufferStatus reports the correct value | |
wglDXLockObjectsNV(vdx_gl_device, 1, &vdx_gl_rtv); | |
// layer gl framebuffer | |
glGenFramebuffers(1, &vdx_gl_fbo); | |
glBindFramebuffer(GL_FRAMEBUFFER, vdx_gl_fbo); | |
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, vdx_gl_rtv_name, 0); | |
vdx_disable_if_zero((!glGetError())); | |
// double check if the driver thinks we can render to this target | |
GLenum fbstatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); | |
vdx_disable_if_zero((fbstatus == GL_FRAMEBUFFER_COMPLETE)); | |
// unlock so it can be used by D3D | |
wglDXUnlockObjectsNV(vdx_gl_device, 1, &vdx_gl_rtv); | |
} | |
// delay showing window until everything is setup. Some drivers (intel) | |
// have problems with fullscreen otherwise. | |
ShowWindow(window, SW_SHOW); | |
} | |
uint32 | |
vid_framebuffer(void) | |
{ | |
if (vid_dxgi.value) | |
return vdx_gl_fbo; | |
return 0; | |
} | |
void | |
vid_lock_frame(void) | |
{ | |
if (vid_dxgi.value) | |
vdx_lock_frame(); | |
} | |
void | |
vid_present(void) | |
{ | |
if (vid_dxgi.value) | |
vdx_present(); | |
else | |
vgl_present(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment