mirror of
https://github.com/Alcaro/Flips.git
synced 2026-09-14 13:35:08 -05:00
502 lines
14 KiB
C++
502 lines
14 KiB
C++
#ifdef _WIN32
|
|
#include "aropengl.h"
|
|
|
|
//https://www.opengl.org/registry/specs/NV/DX_interop.txt
|
|
//https://github.com/halogenica/WGL_NV_DX/blob/master/SharedResource.cpp
|
|
//https://msdn.microsoft.com/en-us/library/windows/desktop/bb174336(v=vs.85).aspx
|
|
|
|
|
|
#undef bind
|
|
#ifdef _MSC_VER
|
|
//MSVC's gl.h doesn't seem to include the stuff it should. Copying these five lines from mingw's gl.h...
|
|
# if !(defined(WINGDIAPI) && defined(APIENTRY))
|
|
# include <windows.h>
|
|
# else
|
|
# include <stddef.h>
|
|
# endif
|
|
//Also disable a block of code that defines int32_t to something not identical to my msvc-compatible stdint.h.
|
|
# define GLEXT_64_TYPES_DEFINED
|
|
#endif
|
|
|
|
#if defined(__has_include)
|
|
#if __has_include(<GL/wglext.h>)
|
|
#include <GL/wglext.h>
|
|
#endif
|
|
#endif
|
|
#ifndef WGL_WGLEXT_VERSION
|
|
#include "../deps/wglext.h"
|
|
#endif
|
|
|
|
#ifdef AROPENGL_D3DSYNC
|
|
# include <D3D9.h>
|
|
|
|
# ifndef D3DPRESENT_FORCEIMMEDIATE
|
|
# define D3DPRESENT_FORCEIMMEDIATE 0x00000100L
|
|
# endif
|
|
# ifndef D3DPRESENT_DONOTWAIT
|
|
# define D3DPRESENT_DONOTWAIT 0x00000001L
|
|
# endif
|
|
|
|
# define IFD3D(x) x
|
|
#else
|
|
# define IFD3D(x)
|
|
#endif
|
|
|
|
#define bind bind_func
|
|
|
|
namespace {
|
|
|
|
#define WGL_SYMS() \
|
|
WGL_SYM(HGLRC, CreateContext, (HDC hdc)) \
|
|
WGL_SYM(BOOL, DeleteContext, (HGLRC hglrc)) \
|
|
WGL_SYM(HGLRC, GetCurrentContext, ()) \
|
|
WGL_SYM(PROC, GetProcAddress, (LPCSTR lpszProc)) \
|
|
WGL_SYM(BOOL, MakeCurrent, (HDC hdc, HGLRC hglrc)) \
|
|
|
|
//WINGDIAPI BOOL WINAPI wglCopyContext(HGLRC, HGLRC, UINT);
|
|
//WINGDIAPI HGLRC WINAPI wglCreateContext(HDC);
|
|
//WINGDIAPI HGLRC WINAPI wglCreateLayerContext(HDC, int);
|
|
//WINGDIAPI BOOL WINAPI wglDeleteContext(HGLRC);
|
|
//WINGDIAPI HGLRC WINAPI wglGetCurrentContext(VOID);
|
|
//WINGDIAPI HDC WINAPI wglGetCurrentDC(VOID);
|
|
//WINGDIAPI PROC WINAPI wglGetProcAddress(LPCSTR);
|
|
//WINGDIAPI BOOL WINAPI wglMakeCurrent(HDC, HGLRC);
|
|
//WINGDIAPI BOOL WINAPI wglShareLists(HGLRC, HGLRC);
|
|
//WINGDIAPI BOOL WINAPI wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD);
|
|
//WINGDIAPI BOOL WINAPI wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD);
|
|
//#ifdef UNICODE
|
|
//#define wglUseFontBitmaps wglUseFontBitmapsW
|
|
//#else
|
|
//#define wglUseFontBitmaps wglUseFontBitmapsA
|
|
//#endif // !UNICODE
|
|
//WINGDIAPI BOOL WINAPI SwapBuffers(HDC);
|
|
|
|
//SwapBuffers and various others are actually in gdi32.dll, which is used elsewhere and can safely be included here too
|
|
|
|
#define WGL_EXTS() \
|
|
WGL_EXT(PFNWGLSWAPINTERVALEXTPROC, SwapIntervalEXT) /* must be first */ \
|
|
IFD3D( \
|
|
WGL_EXT(PFNWGLDXSETRESOURCESHAREHANDLENVPROC, DXSetResourceShareHandleNV) \
|
|
WGL_EXT(PFNWGLDXOPENDEVICENVPROC, DXOpenDeviceNV) \
|
|
WGL_EXT(PFNWGLDXCLOSEDEVICENVPROC, DXCloseDeviceNV) \
|
|
WGL_EXT(PFNWGLDXREGISTEROBJECTNVPROC, DXRegisterObjectNV) \
|
|
WGL_EXT(PFNWGLDXUNREGISTEROBJECTNVPROC, DXUnregisterObjectNV) \
|
|
WGL_EXT(PFNWGLDXOBJECTACCESSNVPROC, DXObjectAccessNV) \
|
|
WGL_EXT(PFNWGLDXLOCKOBJECTSNVPROC, DXLockObjectsNV) \
|
|
WGL_EXT(PFNWGLDXUNLOCKOBJECTSNVPROC, DXUnlockObjectsNV) \
|
|
) \
|
|
|
|
struct {
|
|
#define WGL_SYM(ret, name, args) ret (WINAPI * name) args;
|
|
WGL_SYMS()
|
|
#undef WGL_SYM
|
|
#define WGL_EXT(type, name) type name;
|
|
WGL_EXTS()
|
|
#undef WGL_EXT
|
|
HMODULE lib;
|
|
} static wgl;
|
|
#define WGL_SYM(ret, name, args) "wgl" #name "\0"
|
|
static const char wgl_proc_names[] = WGL_SYMS() ;
|
|
#undef WGL_SYM
|
|
#define WGL_EXT(type, name) "wgl" #name "\0"
|
|
static const char wgl_ext_names[] = WGL_EXTS() ;
|
|
#undef WGL_EXT
|
|
|
|
|
|
|
|
#ifdef AROPENGL_D3DSYNC
|
|
typedef HRESULT (WINAPI * Direct3DCreate9Ex_t)(UINT SDKVersion, IDirect3D9Ex* * ppD3D);
|
|
static HMODULE hD3D9=NULL;
|
|
static Direct3DCreate9Ex_t lpDirect3DCreate9Ex;
|
|
|
|
static bool libLoadD3D()
|
|
{
|
|
hD3D9=LoadLibrary("d3d9.dll");
|
|
if (!hD3D9) return false;
|
|
//lpDirect3DCreate9=Direct3DCreate9;//these are for verifying that Direct3DCreate9Ex_t matches the real function; they're not needed anymore
|
|
//lpDirect3DCreate9Ex=Direct3DCreate9Ex;
|
|
lpDirect3DCreate9Ex=(Direct3DCreate9Ex_t)GetProcAddress(hD3D9, "Direct3DCreate9Ex");
|
|
if (!lpDirect3DCreate9Ex) { FreeLibrary(hD3D9); return false; }
|
|
//if (!lpDirect3DCreate9Ex) return false;
|
|
return true;
|
|
}
|
|
|
|
static void libUnloadD3D()
|
|
{
|
|
FreeLibrary(hD3D9);
|
|
}
|
|
#endif
|
|
|
|
static bool libLoadGL()
|
|
{
|
|
//this can yield multiple unsynchronized writers to global variables
|
|
//however, this is safe, because they all write the same values in the same order
|
|
//(except if writing that cache line also discards other changes to the same cache line, but that just won't happen.)
|
|
wgl.lib = LoadLibrary("opengl32.dll");
|
|
if (!wgl.lib) return false;
|
|
|
|
//HMODULE gdilib=GetModuleHandle("gdi32.dll");
|
|
|
|
const char * names = wgl_proc_names;
|
|
FARPROC* functions = (FARPROC*)&wgl;
|
|
|
|
while (*names)
|
|
{
|
|
*functions = GetProcAddress(wgl.lib, names);
|
|
if (!*functions) return false;
|
|
|
|
functions++;
|
|
names += strlen(names)+1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void libUnloadGL()
|
|
{
|
|
if (wgl.lib) FreeLibrary(wgl.lib);
|
|
}
|
|
|
|
class aropengl_windows : public aropengl::context {
|
|
public:
|
|
HWND GL_hwnd;
|
|
HDC GL_hdc;
|
|
HGLRC GL_hglrc;
|
|
|
|
#ifdef AROPENGL_D3DSYNC
|
|
bool d3dsync;
|
|
|
|
HWND D3D_hwnd;
|
|
|
|
IDirect3DDevice9Ex* D3D_device;
|
|
IDirect3DSurface9* D3D_backbuf;
|
|
IDirect3DSurface9* D3D_GLtarget;
|
|
|
|
HANDLE D3D_sharehandle;
|
|
HANDLE D3D_sharetexture;
|
|
HANDLE GL_htexture;
|
|
|
|
GLuint GL_fboname;
|
|
GLuint GL_texturename;
|
|
|
|
bool vsync;
|
|
#endif
|
|
|
|
/*private*/ bool init(HWND parent, HWND* window_, uint32_t flags)
|
|
{
|
|
DWORD glwndflags = WS_CHILD | WS_VISIBLE;
|
|
#ifdef AROPENGL_D3DSYNC
|
|
this->d3dsync = (flags & aropengl::t_direct3d_vsync);
|
|
if (this->d3dsync) glwndflags &= ~WS_VISIBLE;
|
|
#endif
|
|
|
|
this->GL_hwnd = CreateWindow("arlib", NULL, glwndflags, 0, 0, 1, 1, parent, NULL, NULL, NULL);
|
|
|
|
*window_ = this->GL_hwnd;
|
|
this->GL_hdc = GetDC(this->GL_hwnd);
|
|
this->GL_hglrc = NULL;
|
|
|
|
if (!libLoadGL()) return false;
|
|
if (!CreateContext(flags)) return false;
|
|
|
|
#ifdef AROPENGL_D3DSYNC
|
|
if (this->d3dsync)
|
|
{
|
|
this->D3D_hwnd = CreateWindow("arlib", NULL, WS_CHILD | WS_VISIBLE, 0, 0, 1, 1, parent, NULL, NULL, NULL);
|
|
*window_ = this->D3D_hwnd;
|
|
D3D_sharehandle = NULL;
|
|
D3D_sharetexture = NULL;
|
|
GL_htexture = NULL;
|
|
|
|
if (!libLoadD3D()) return false;
|
|
if (!CreateD3DContext()) return false;
|
|
if (!JoinGLD3D()) return false;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
///*private*/ HWND CreateDummyWindow(HWND parent)
|
|
//{
|
|
// WNDCLASS wc = {};
|
|
// wc.lpfnWndProc = DefWindowProc;
|
|
// wc.lpszClassName = "arlib_opengl_dummy";
|
|
// RegisterClass(&wc);
|
|
//
|
|
// return CreateWindow("arlib", NULL, WS_CHILD, -1, 0, 1, 1, parent, NULL, NULL, NULL);
|
|
// //return CreateWindow("arlib", "OPENGL", WS_VISIBLE, 0, 0, 100, 100, NULL, NULL, NULL, NULL);
|
|
//}
|
|
|
|
/*private*/ bool CreateContext(uint32_t flags)
|
|
{
|
|
if (wgl.GetCurrentContext()) return false;
|
|
|
|
bool debug = (flags & aropengl::t_debug_context);
|
|
bool depthbuf = (flags & aropengl::t_depth_buffer);
|
|
bool stenbuf = (flags & aropengl::t_stencil_buffer);
|
|
uint32_t version = (flags & 0xFFF);
|
|
|
|
PIXELFORMATDESCRIPTOR pfd;
|
|
memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
|
|
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 = 24;
|
|
pfd.cAlphaBits = 0;
|
|
pfd.cAccumBits = 0;
|
|
pfd.cDepthBits = (stenbuf ? 24 : depthbuf ? 16 : 0);
|
|
pfd.cStencilBits = (stenbuf ? 8 : 0);
|
|
pfd.cAuxBuffers = 0;
|
|
#ifdef AROPENGL_D3DSYNC
|
|
if (this->d3dsync)
|
|
{
|
|
pfd.dwFlags &= ~PFD_DOUBLEBUFFER;
|
|
pfd.cDepthBits = 0;
|
|
pfd.cStencilBits = 0;
|
|
}
|
|
#endif
|
|
pfd.iLayerType = PFD_MAIN_PLANE;
|
|
SetPixelFormat(this->GL_hdc, ChoosePixelFormat(this->GL_hdc, &pfd), &pfd);
|
|
this->GL_hglrc = wgl.CreateContext(this->GL_hdc);
|
|
if (!this->GL_hglrc) return false;
|
|
|
|
wgl.MakeCurrent(this->GL_hdc, this->GL_hglrc);
|
|
|
|
if (version >= 310)
|
|
{
|
|
HGLRC hglrc_old = this->GL_hglrc;
|
|
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs =
|
|
(PFNWGLCREATECONTEXTATTRIBSARBPROC)wgl.GetProcAddress("wglCreateContextAttribsARB");
|
|
const int attribs[] = {
|
|
WGL_CONTEXT_MAJOR_VERSION_ARB, (int)version/100,
|
|
WGL_CONTEXT_MINOR_VERSION_ARB, (int)version/10%10,
|
|
WGL_CONTEXT_FLAGS_ARB, debug ? WGL_CONTEXT_DEBUG_BIT_ARB : 0,
|
|
//WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
|
|
//https://www.opengl.org/wiki/Core_And_Compatibility_in_Contexts says do not use
|
|
0 };
|
|
this->GL_hglrc = wglCreateContextAttribs(this->GL_hdc, /*share*/NULL, attribs);
|
|
wgl.DeleteContext(hglrc_old);
|
|
|
|
if (!this->GL_hglrc)
|
|
{
|
|
wgl.MakeCurrent(NULL, NULL);
|
|
return false;
|
|
}
|
|
|
|
wgl.MakeCurrent(this->GL_hdc, this->GL_hglrc);
|
|
}
|
|
|
|
const char * names = wgl_ext_names;
|
|
FARPROC* functions = (FARPROC*)&wgl.SwapIntervalEXT;
|
|
|
|
while (*names)
|
|
{
|
|
*functions = wgl.GetProcAddress(names);
|
|
#ifdef AROPENGL_D3DSYNC
|
|
if (d3dsync && !*functions) return false; // this demands wglSwapIntervalEXT even for d3d sync, but that one is supported by everything.
|
|
#endif
|
|
|
|
functions++;
|
|
names += strlen(names)+1;
|
|
}
|
|
if (!wgl.SwapIntervalEXT) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef AROPENGL_D3DSYNC
|
|
/*private*/ bool CreateD3DContext()
|
|
{
|
|
IDirect3D9Ex* d3d;
|
|
this->D3D_device = NULL;
|
|
|
|
if (FAILED(lpDirect3DCreate9Ex(D3D_SDK_VERSION, &d3d))) return false;
|
|
|
|
D3DPRESENT_PARAMETERS parameters = {};
|
|
parameters.BackBufferCount = 2; // D3DPRESENT_FORCEIMMEDIATE|D3DPRESENT_DONOTWAIT doesn't work without this
|
|
parameters.SwapEffect = D3DSWAPEFFECT_FLIPEX;
|
|
parameters.hDeviceWindow = this->D3D_hwnd;
|
|
parameters.Windowed = TRUE;
|
|
//https://msdn.microsoft.com/en-us/library/windows/desktop/bb172585(v=vs.85).aspx
|
|
//_ONE is _DEFAULT, but also calls timeBeginPeriod to improve precision
|
|
//anything opting in to Direct3D vsync is clearly a high-performance program, and thus wants the increased precision
|
|
parameters.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
|
|
|
|
if (FAILED(d3d->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, this->D3D_hwnd,
|
|
D3DCREATE_MIXED_VERTEXPROCESSING|D3DCREATE_MULTITHREADED,
|
|
¶meters, NULL, &this->D3D_device)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
d3d->Release();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*private*/ bool JoinGLD3D()
|
|
{
|
|
//a bit untidy, but this object doesn't have access to the real one
|
|
#define SYM(type, name) type name = (type)this->getProcAddress(STR(name)); if (!name) return false
|
|
typedef void (GLAPIENTRY * PFNGLGENTEXTURES)(GLsizei n, GLuint *textures);
|
|
typedef void (GLAPIENTRY * PFNGLBINDTEXTURE)(GLenum target, GLuint texture);
|
|
SYM(PFNGLGENTEXTURES, glGenTextures);
|
|
SYM(PFNGLBINDTEXTURE, glBindTexture);
|
|
SYM(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers);
|
|
SYM(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer);
|
|
SYM(PFNGLFRAMEBUFFERTEXTUREPROC, glFramebufferTexture);
|
|
#undef SYM
|
|
|
|
glGenTextures(1, &GL_texturename);
|
|
glBindTexture(GL_TEXTURE_2D, GL_texturename);
|
|
|
|
D3D_sharehandle = wgl.DXOpenDeviceNV(this->D3D_device);
|
|
|
|
this->D3D_device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &this->D3D_backbuf);
|
|
|
|
AllocRenderTarget();
|
|
|
|
//the framebuffer must be bound after calling AllocRenderTarget, or the Nvidia driver claims the framebuffer is incomplete
|
|
//this bug can be fixed by querying the current FBO and binding that, which shouldn't have any effect
|
|
//additionally, the Intel driver is happy with either order
|
|
//I suspect driver bug of some kind
|
|
glGenFramebuffers(1, &GL_fboname);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, GL_fboname);
|
|
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_texturename, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*private*/ void AllocRenderTarget()
|
|
{
|
|
RECT wndsize;
|
|
GetClientRect(this->D3D_hwnd, &wndsize);
|
|
this->D3D_device->CreateRenderTarget(wndsize.right, wndsize.bottom, D3DFMT_X8R8G8B8, D3DMULTISAMPLE_NONE, 0, false, &this->D3D_GLtarget, &D3D_sharetexture);
|
|
|
|
wgl.DXSetResourceShareHandleNV(this->D3D_GLtarget, D3D_sharetexture);
|
|
this->GL_htexture = wgl.DXRegisterObjectNV(this->D3D_sharehandle, this->D3D_GLtarget, GL_texturename, GL_TEXTURE_2D, WGL_ACCESS_WRITE_DISCARD_NV);
|
|
|
|
wgl.DXLockObjectsNV(D3D_sharehandle, 1, &this->GL_htexture);
|
|
}
|
|
|
|
/*private*/ void DeallocRenderTarget()
|
|
{
|
|
if (!this->D3D_GLtarget) return;
|
|
|
|
wgl.DXUnlockObjectsNV(D3D_sharehandle, 1, &this->GL_htexture);
|
|
wgl.DXUnregisterObjectNV(D3D_sharehandle, this->GL_htexture);
|
|
|
|
this->D3D_GLtarget->Release();
|
|
this->D3D_GLtarget = NULL;
|
|
|
|
//those D3D share handles are weird stuff like 0xC0007000, closing them throws errors
|
|
//I'll assume it's freed by deleting the rendertarget, device or IDirect3D9Ex, and that reusing it does not leak
|
|
//CloseHandle(D3D_sharetexture);
|
|
//D3D_sharetexture = NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
void makeCurrent(bool make)
|
|
{
|
|
if (make) wgl.MakeCurrent(this->GL_hdc, this->GL_hglrc);
|
|
else wgl.MakeCurrent(NULL, NULL);
|
|
}
|
|
|
|
funcptr getProcAddress(const char * proc)
|
|
{
|
|
PROC ret = wgl.GetProcAddress(proc);
|
|
if (!ret) ret = ::GetProcAddress(wgl.lib, proc); // lol windows
|
|
return (funcptr)ret;
|
|
}
|
|
|
|
void swapInterval(int interval)
|
|
{
|
|
#ifdef AROPENGL_D3DSYNC
|
|
if (d3dsync) vsync = (interval==1);
|
|
else
|
|
#endif
|
|
wgl.SwapIntervalEXT(interval);
|
|
}
|
|
|
|
void swapBuffers()
|
|
{
|
|
#ifdef AROPENGL_D3DSYNC
|
|
if (d3dsync)
|
|
{
|
|
wgl.DXUnlockObjectsNV(D3D_sharehandle, 1, &this->GL_htexture);
|
|
this->D3D_device->StretchRect(this->D3D_GLtarget, NULL, this->D3D_backbuf, NULL, D3DTEXF_NONE);
|
|
this->D3D_device->PresentEx(NULL, NULL, NULL, NULL, (vsync ? 0 : D3DPRESENT_FORCEIMMEDIATE|D3DPRESENT_DONOTWAIT));
|
|
wgl.DXLockObjectsNV(D3D_sharehandle, 1, &this->GL_htexture);
|
|
}
|
|
else
|
|
#endif
|
|
::SwapBuffers(this->GL_hdc);
|
|
}
|
|
|
|
#ifdef AROPENGL_D3DSYNC
|
|
void notifyResize(GLsizei width, GLsizei height)
|
|
{
|
|
if (d3dsync)
|
|
{
|
|
DeallocRenderTarget();
|
|
AllocRenderTarget();
|
|
}
|
|
}
|
|
|
|
GLuint outputFramebuffer()
|
|
{
|
|
if (d3dsync) return GL_fboname;
|
|
else return 0;
|
|
}
|
|
#endif
|
|
|
|
void destroy()
|
|
{
|
|
//early return to ensure the libUnload functions aren't called too much
|
|
//this is the first member set in init()
|
|
if (!this->GL_hwnd) return;
|
|
|
|
#ifdef AROPENGL_D3DSYNC
|
|
if (this->d3dsync)
|
|
{
|
|
DeallocRenderTarget();
|
|
if (D3D_sharehandle) wgl.DXCloseDeviceNV(D3D_sharehandle);
|
|
|
|
if (this->D3D_device) this->D3D_device->Release();
|
|
if (this->D3D_backbuf) this->D3D_backbuf->Release();
|
|
|
|
//don't bother cleaning up the GL resources, wglDeleteContext does that already
|
|
if (this->D3D_hwnd) DestroyWindow(this->D3D_hwnd);
|
|
|
|
libUnloadD3D();
|
|
}
|
|
#endif
|
|
|
|
if (this->GL_hglrc && wgl.DeleteContext) wgl.DeleteContext(this->GL_hglrc);
|
|
if (this->GL_hdc) ReleaseDC(this->GL_hwnd, this->GL_hdc);
|
|
|
|
DestroyWindow(this->GL_hwnd);
|
|
this->GL_hwnd = NULL;
|
|
|
|
libUnloadGL();
|
|
}
|
|
|
|
~aropengl_windows() { destroy(); }
|
|
};
|
|
|
|
}
|
|
|
|
aropengl::context* aropengl::context::create(uintptr_t parent, uintptr_t* window, uint32_t flags)
|
|
{
|
|
aropengl_windows* ret = new aropengl_windows();
|
|
if (ret->init((HWND)parent, (HWND*)window, flags)) return ret;
|
|
|
|
delete ret;
|
|
return NULL;
|
|
}
|
|
#endif
|