Skip to content

Instantly share code, notes, and snippets.

@rossy
Last active July 28, 2021 20:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rossy/07844da429d80549286d to your computer and use it in GitHub Desktop.
Save rossy/07844da429d80549286d to your computer and use it in GitHub Desktop.
GL/DX interop capabilities viewer
// 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