Last active
July 28, 2021 20:46
-
-
Save rossy/07844da429d80549286d to your computer and use it in GitHub Desktop.
GL/DX interop capabilities viewer
This file contains 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
// gcc dxinteropcaps.c -std=c99 -Wall -o dxinteropcaps -lgdi32 -lopengl32 -ld3d9 -O0 -ggdb3 | |
#define _WIN32_WINNT 0x0600 | |
#define COBJMACROS | |
#define UNICODE | |
#define _UNICODE | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <windows.h> | |
#include <initguid.h> | |
#include <unknwn.h> | |
#include <d3d9.h> | |
#include <GL/gl.h> | |
#include <GL/glext.h> | |
#include <GL/wglext.h> | |
#define die(...) exit((fprintf(stderr, __VA_ARGS__), 1)) | |
static const struct { | |
const char *name; | |
UINT width; | |
UINT height; | |
} sizes[] = { | |
{ .name = "1x1", .width = 1, .height = 1 }, | |
{ .name = "pot square", .width = 256, .height = 256 }, | |
{ .name = "npot square", .width = 500, .height = 500 }, | |
{ .name = "odd square", .width = 501, .height = 501 }, | |
{ .name = "pot rect", .width = 256, .height = 512 }, | |
{ .name = "npot rect", .width = 500, .height = 1000 }, | |
{ .name = "odd rect", .width = 501, .height = 1001 }, | |
{ 0 } | |
}; | |
static const struct { | |
const char *name; | |
UINT format; | |
} formats[] = { | |
// { .name = "D3DFMT_X1R5G5B5", .format = D3DFMT_X1R5G5B5 }, | |
// { .name = "D3DFMT_A1R5G5B5", .format = D3DFMT_A1R5G5B5 }, | |
// { .name = "D3DFMT_R5G6B5", .format = D3DFMT_R5G6B5 }, | |
// { .name = "D3DFMT_R8G8B8", .format = D3DFMT_R8G8B8 }, | |
{ .name = "D3DFMT_A8R8G8B8", .format = D3DFMT_A8R8G8B8 }, | |
{ .name = "D3DFMT_X8R8G8B8", .format = D3DFMT_X8R8G8B8 }, | |
{ .name = "D3DFMT_A8B8G8R8", .format = D3DFMT_A8B8G8R8 }, | |
{ .name = "D3DFMT_X8B8G8R8", .format = D3DFMT_X8B8G8R8 }, | |
{ .name = "D3DFMT_A2R10G10B10", .format = D3DFMT_A2R10G10B10 }, | |
{ .name = "D3DFMT_A2B10G10R10", .format = D3DFMT_A2B10G10R10 }, | |
{ .name = "D3DFMT_A16B16G16R16F", .format = D3DFMT_A16B16G16R16F }, | |
{ .name = "D3DFMT_NV12", .format = MAKEFOURCC('N', 'V', '1', '2') }, | |
{ .name = "D3DFMT_YUVY", .format = MAKEFOURCC('U', 'Y', 'V', 'Y') }, | |
{ .name = "D3DFMT_YUY2", .format = MAKEFOURCC('Y', 'U', 'Y', '2') }, | |
{ .name = "D3DFMT_P010", .format = MAKEFOURCC('P', '0', '1', '0') }, | |
{ .name = "D3DFMT_P016", .format = MAKEFOURCC('P', '0', '1', '6') }, | |
{ 0 } | |
}; | |
static const struct { | |
const char *name; | |
GLenum gl_object; | |
} gl_objects[] = { | |
{ .name = "GL_RENDERBUFFER", .gl_object = GL_RENDERBUFFER }, | |
{ .name = "GL_TEXTURE_2D", .gl_object = GL_TEXTURE_2D }, | |
{ .name = "GL_TEXTURE_RECTANGLE", .gl_object = GL_TEXTURE_RECTANGLE }, | |
{ 0 } | |
}; | |
static const struct { | |
const char *name; | |
GLenum access; | |
} accesses[] = { | |
{ .name = "WGL_ACCESS_READ_ONLY_NV", .access = WGL_ACCESS_READ_ONLY_NV }, | |
{ .name = "WGL_ACCESS_READ_WRITE_NV", .access = WGL_ACCESS_READ_WRITE_NV }, | |
{ .name = "WGL_ACCESS_WRITE_DISCARD_NV", .access = WGL_ACCESS_WRITE_DISCARD_NV }, | |
{ 0 } | |
}; | |
static HWND gl_wnd = NULL; | |
static HDC gl_dc = NULL; | |
static HGLRC gl_ctx = NULL; | |
static HWND wnd = NULL; | |
static IDirect3D9Ex *d3d9ex = NULL; | |
static IDirect3DDevice9Ex *device = NULL; | |
static HANDLE device_h = NULL; | |
static PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers; | |
static PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers; | |
static PFNWGLDXOPENDEVICENVPROC wglDXOpenDeviceNV; | |
static PFNWGLDXCLOSEDEVICENVPROC wglDXCloseDeviceNV; | |
static PFNWGLDXREGISTEROBJECTNVPROC wglDXRegisterObjectNV; | |
static PFNWGLDXUNREGISTEROBJECTNVPROC wglDXUnregisterObjectNV; | |
static PFNWGLDXLOCKOBJECTSNVPROC wglDXLockObjectsNV; | |
static PFNWGLDXUNLOCKOBJECTSNVPROC wglDXUnlockObjectsNV; | |
static PFNWGLDXSETRESOURCESHAREHANDLENVPROC wglDXSetResourceShareHandleNV; | |
static IUnknown *create_rendertarget(UINT width, UINT height, UINT format, UINT usage, HANDLE *share_handle) | |
{ | |
HRESULT hr; | |
IDirect3DSurface9 *rtarget = NULL; | |
hr = IDirect3DDevice9Ex_CreateRenderTargetEx(device, width, height, | |
format, D3DMULTISAMPLE_NONE, 0, FALSE, &rtarget, share_handle, usage); | |
return SUCCEEDED(hr) ? (IUnknown*)rtarget : NULL; | |
} | |
static IUnknown *create_offscreenplainsurface(UINT width, UINT height, UINT format, UINT usage, HANDLE *share_handle) | |
{ | |
HRESULT hr; | |
IDirect3DSurface9 *surf = NULL; | |
hr = IDirect3DDevice9Ex_CreateOffscreenPlainSurfaceEx(device, width, height, | |
format, D3DPOOL_DEFAULT, &surf, share_handle, usage); | |
return SUCCEEDED(hr) ? (IUnknown*)surf : NULL; | |
} | |
static IUnknown *create_texture(UINT width, UINT height, UINT format, UINT usage, HANDLE *share_handle) | |
{ | |
HRESULT hr; | |
IDirect3DTexture9 *tex = NULL; | |
hr = IDirect3DDevice9Ex_CreateTexture(device, width, height, 1, usage, | |
format, D3DPOOL_DEFAULT, &tex, share_handle); | |
return SUCCEEDED(hr) ? (IUnknown*)tex : NULL; | |
} | |
static IUnknown *create_backbuffer(UINT width, UINT height, UINT format, UINT usage, HANDLE *share_handle) | |
{ | |
HRESULT hr; | |
IDirect3DSurface9 *bb = NULL; | |
hr = IDirect3DDevice9Ex_ResetEx(device, (&(D3DPRESENT_PARAMETERS) { | |
.Windowed = TRUE, | |
.SwapEffect = D3DSWAPEFFECT_FLIPEX, | |
.hDeviceWindow = wnd, | |
.BackBufferWidth = width, | |
.BackBufferHeight = height, | |
.BackBufferFormat = format, | |
}), NULL); | |
if (SUCCEEDED(hr)) | |
hr = IDirect3DDevice9Ex_GetBackBuffer(device, 0, 0, | |
D3DBACKBUFFER_TYPE_MONO, &bb); | |
return SUCCEEDED(hr) ? (IUnknown*)bb : NULL; | |
} | |
static const struct { | |
const char *name; | |
IUnknown *(*create)(UINT width, UINT height, UINT format, UINT usage, HANDLE *share_handle); | |
UINT usage; | |
} d3d_objects[] = { | |
{ .name = "RenderTarget", .create = create_rendertarget }, | |
{ .name = "OffscreenPlainSurface", .create = create_offscreenplainsurface }, | |
// { .name = "OffscreenPlainSurface(RT)", .create = create_offscreenplainsurface, .usage = D3DUSAGE_RENDERTARGET }, | |
{ .name = "Texture", .create = create_texture }, | |
// { .name = "Texture(DY)", .create = create_texture, .usage = D3DUSAGE_DYNAMIC }, | |
{ .name = "Texture(RT)", .create = create_texture, .usage = D3DUSAGE_RENDERTARGET }, | |
// { .name = "BackBuffer", .create = create_backbuffer }, | |
{ 0 } | |
}; | |
static void gl_init(void) | |
{ | |
static const wchar_t wnd_class[] = L"dxinteropcaps offscreen gl"; | |
RegisterClassExW(&(WNDCLASSEXW) { | |
.cbSize = sizeof(WNDCLASSEXW), | |
.style = CS_OWNDC, | |
.lpfnWndProc = DefWindowProc, | |
.hInstance = GetModuleHandleW(NULL), | |
.lpszClassName = wnd_class, | |
}); | |
gl_wnd = CreateWindowExW(0, wnd_class, wnd_class, 0, 0, 0, 200, 200, NULL, | |
NULL, GetModuleHandleW(NULL), NULL); | |
gl_dc = GetDC(gl_wnd); | |
if (!gl_dc) | |
die("Couldn't create window\n"); | |
PIXELFORMATDESCRIPTOR pfd = { | |
.nSize = sizeof pfd, | |
.nVersion = 1, | |
.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, | |
.iPixelType = PFD_TYPE_RGBA, | |
.cColorBits = 24, | |
.iLayerType = PFD_MAIN_PLANE, | |
}; | |
if (!SetPixelFormat(gl_dc, ChoosePixelFormat(gl_dc, &pfd), &pfd)) | |
die("Couldn't set pixelformat\n"); | |
HGLRC legacy_context = wglCreateContext(gl_dc); | |
if (!wglMakeCurrent(gl_dc, legacy_context)) | |
die("Couldn't create legacy OpenGL context\n"); | |
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = | |
(PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); | |
if (!wglCreateContextAttribsARB) | |
die("No OpenGL 3.0 support\n"); | |
if (!wglMakeCurrent(gl_dc, wglCreateContextAttribsARB(gl_dc, 0, (int[]) { | |
WGL_CONTEXT_MAJOR_VERSION_ARB, 3, | |
WGL_CONTEXT_MINOR_VERSION_ARB, 0, | |
WGL_CONTEXT_FLAGS_ARB, 0, | |
0 | |
}))) | |
die("Couldn't create OpenGL 3.0 context\n"); | |
wglDeleteContext(legacy_context); | |
glGenRenderbuffers = | |
(PFNGLGENRENDERBUFFERSPROC)wglGetProcAddress("glGenRenderbuffers"); | |
glDeleteRenderbuffers = | |
(PFNGLDELETERENDERBUFFERSPROC)wglGetProcAddress("glDeleteRenderbuffers"); | |
wglDXOpenDeviceNV = | |
(PFNWGLDXOPENDEVICENVPROC)wglGetProcAddress("wglDXOpenDeviceNV"); | |
wglDXCloseDeviceNV = | |
(PFNWGLDXCLOSEDEVICENVPROC)wglGetProcAddress("wglDXCloseDeviceNV"); | |
wglDXRegisterObjectNV = | |
(PFNWGLDXREGISTEROBJECTNVPROC)wglGetProcAddress("wglDXRegisterObjectNV"); | |
wglDXUnregisterObjectNV = | |
(PFNWGLDXUNREGISTEROBJECTNVPROC)wglGetProcAddress("wglDXUnregisterObjectNV"); | |
wglDXLockObjectsNV = | |
(PFNWGLDXLOCKOBJECTSNVPROC)wglGetProcAddress("wglDXLockObjectsNV"); | |
wglDXUnlockObjectsNV = | |
(PFNWGLDXUNLOCKOBJECTSNVPROC)wglGetProcAddress("wglDXUnlockObjectsNV"); | |
wglDXSetResourceShareHandleNV = | |
(PFNWGLDXSETRESOURCESHAREHANDLENVPROC)wglGetProcAddress("wglDXSetResourceShareHandleNV"); | |
if (!wglDXOpenDeviceNV || !wglDXCloseDeviceNV || !wglDXRegisterObjectNV || | |
!wglDXUnregisterObjectNV || !wglDXLockObjectsNV || | |
!wglDXUnlockObjectsNV || !wglDXSetResourceShareHandleNV) | |
die("WGL_NV_DX_interop not present\n"); | |
} | |
static void d3d_init(void) | |
{ | |
static const wchar_t wnd_class[] = L"dxinteropcaps offscreen d3d"; | |
RegisterClassExW(&(WNDCLASSEXW) { | |
.cbSize = sizeof(WNDCLASSEXW), | |
.style = CS_OWNDC, | |
.lpfnWndProc = DefWindowProc, | |
.hInstance = GetModuleHandleW(NULL), | |
.lpszClassName = wnd_class, | |
}); | |
wnd = CreateWindowExW(0, wnd_class, wnd_class, 0, 0, 0, 200, 200, | |
NULL, NULL, GetModuleHandleW(NULL), NULL); | |
if (!wnd) | |
die("Couldn't create window\n"); | |
HRESULT hr; | |
hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex); | |
if (FAILED(hr)) | |
die("Couldn't create D3D device\n"); | |
hr = IDirect3D9Ex_CreateDeviceEx(d3d9ex, D3DADAPTER_DEFAULT, | |
D3DDEVTYPE_HAL, wnd, | |
D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE | | |
D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED | | |
D3DCREATE_NOWINDOWCHANGES, | |
(&(D3DPRESENT_PARAMETERS) { | |
.Windowed = TRUE, | |
.SwapEffect = D3DSWAPEFFECT_FLIPEX, | |
.hDeviceWindow = wnd, | |
}), NULL, &device); | |
if (FAILED(hr)) | |
die("Couldn't create D3D device\n"); | |
if (!(device_h = wglDXOpenDeviceNV(device))) | |
die("Couldn't share D3D device\n"); | |
} | |
static void cleanup(void) | |
{ | |
if (gl_ctx) { | |
wglMakeCurrent(gl_dc, NULL); | |
wglDeleteContext(gl_ctx); | |
} | |
if (gl_dc) | |
ReleaseDC(gl_wnd, gl_dc); | |
if (gl_wnd) | |
DestroyWindow(gl_wnd); | |
if (wglDXCloseDeviceNV && device_h) | |
wglDXCloseDeviceNV(device_h); | |
if (device) | |
IDirect3DDevice9Ex_Release(device); | |
if (d3d9ex) | |
IDirect3D9Ex_Release(d3d9ex); | |
if (wnd) | |
DestroyWindow(wnd); | |
} | |
int main(void) | |
{ | |
atexit(cleanup); | |
gl_init(); | |
d3d_init(); | |
for (int sizes_i = 0; sizes[sizes_i].name; sizes_i++) | |
for (int formats_i = 0; formats[formats_i].name; formats_i++) | |
for (int gl_objects_i = 0; gl_objects[gl_objects_i].name; gl_objects_i++) | |
for (int accesses_i = 0; accesses[accesses_i].name; accesses_i++) | |
for (int d3d_objects_i = 0; d3d_objects[d3d_objects_i].name; d3d_objects_i++) | |
for (int share = 0; share < 2; share++) { | |
const char *size_name = sizes[sizes_i].name; | |
UINT width = sizes[sizes_i].width; | |
UINT height = sizes[sizes_i].height; | |
const char *format_name = formats[formats_i].name; | |
UINT format = formats[formats_i].format; | |
const char *gl_object_name = gl_objects[gl_objects_i].name; | |
GLenum gl_object = gl_objects[gl_objects_i].gl_object; | |
const char *access_name = accesses[accesses_i].name; | |
GLenum access = accesses[accesses_i].access; | |
if (share && d3d_objects[d3d_objects_i].create == create_backbuffer) | |
continue; | |
const char *d3d_object_name = d3d_objects[d3d_objects_i].name; | |
UINT usage = d3d_objects[d3d_objects_i].usage; | |
HANDLE share_handle = NULL; | |
IUnknown *d3d_object = d3d_objects[d3d_objects_i].create(width, height, format, usage, share ? &share_handle : NULL); | |
if (!d3d_object) | |
continue; | |
if (share && share_handle) | |
wglDXSetResourceShareHandleNV(d3d_object, share_handle); | |
GLuint gl_name = 0; | |
if (gl_object == GL_RENDERBUFFER) | |
glGenRenderbuffers(1, &gl_name); | |
else | |
glGenTextures(1, &gl_name); | |
const char *state = "OK"; | |
const char *error = ""; | |
HANDLE d3d_object_h = wglDXRegisterObjectNV(device_h, d3d_object, | |
gl_name, gl_object, access); | |
if (!d3d_object_h) { | |
state = "FAILED"; | |
switch (GetLastError() & 0xffff) { | |
case ERROR_SUCCESS: error = " ERROR_SUCCESS"; break; | |
case ERROR_INVALID_HANDLE: error = " ERROR_INVALID_HANDLE"; break; | |
case ERROR_INVALID_DATA: error = " ERROR_INVALID_DATA"; break; | |
case ERROR_OPEN_FAILED: error = " ERROR_OPEN_FAILED"; break; | |
default: error = " ERROR_UNKNOWN"; break; | |
} | |
} | |
bool locked = false; | |
if (d3d_object_h) { | |
locked = wglDXLockObjectsNV(device_h, 1, &d3d_object_h); | |
if (!locked) { | |
state = "NOLOCK"; | |
switch (GetLastError() & 0xffff) { | |
case ERROR_SUCCESS: error = " ERROR_SUCCESS"; break; | |
case ERROR_BUSY: error = " ERROR_BUSY"; break; | |
case ERROR_INVALID_DATA: error = " ERROR_INVALID_DATA"; break; | |
case ERROR_LOCK_FAILED: error = " ERROR_LOCK_FAILED"; break; | |
default: error = " ERROR_UNKNOWN"; break; | |
} | |
} | |
} | |
printf("%-14s%-22s%-22s%-29s%-25s%-9s%s%s\n", size_name, format_name, gl_object_name, access_name, d3d_object_name, share ? "share" : "noshare", state, error); | |
fflush(stdout); | |
if (locked) | |
wglDXUnlockObjectsNV(device_h, 1, &d3d_object_h); | |
if (gl_object == GL_RENDERBUFFER) | |
glDeleteRenderbuffers(1, &gl_name); | |
else | |
glDeleteTextures(1, &gl_name); | |
if (d3d_object_h) | |
wglDXUnregisterObjectNV(device_h, d3d_object_h); | |
IUnknown_Release(d3d_object); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment