1 module gfx.window.win32.context;
2 
3 version(Windows):
4 
5 import core.sys.windows.wingdi;
6 import gfx.bindings.core;
7 import gfx.bindings.opengl.gl : Gl, GL_TRUE;
8 import gfx.bindings.opengl.wgl;
9 import gfx.gl3.context : GlAttribs, GlContext, GlProfile, glVersions;
10 
11 /// Helper to get an attrib list to pass to wglChoosePixelFormatARB
12 public int[] getAttribList(in GlAttribs attribs) {
13     import gfx.graal.format :   formatDesc, colorBits, depthBits, stencilBits;
14 
15     const cd = formatDesc(attribs.colorFormat);
16     const dsd = formatDesc(attribs.depthStencilFormat);
17 
18     int[] attribList = [
19         WGL_DRAW_TO_WINDOW_ARB,     GL_TRUE,
20         WGL_SUPPORT_OPENGL_ARB,     GL_TRUE,
21         WGL_DOUBLE_BUFFER_ARB,      GL_TRUE,
22         WGL_PIXEL_TYPE_ARB,         WGL_TYPE_RGBA_ARB,
23         WGL_TRANSPARENT_ARB,        GL_TRUE,
24         WGL_COLOR_BITS_ARB,         colorBits(cd.surfaceType),
25         WGL_DEPTH_BITS_ARB,         depthBits(dsd.surfaceType),
26         WGL_STENCIL_BITS_ARB,       stencilBits(dsd.surfaceType),
27     ];
28     if (attribs.samples > 1) {
29         attribList ~= [
30             WGL_SAMPLE_BUFFERS_ARB,     GL_TRUE,
31             WGL_SAMPLES_ARB,            attribs.samples,
32         ];
33     }
34     return attribList ~ 0;
35 }
36 
37 /// Helper to fill a struct for ChoosePixelFormat
38 public void setupPFD(in GlAttribs attribs, PIXELFORMATDESCRIPTOR* pfd)
39 {
40     pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof;
41     pfd.nVersion = 1;
42 
43     pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
44     if (attribs.doublebuffer) pfd.dwFlags |= PFD_DOUBLEBUFFER;
45 
46     pfd.iPixelType = PFD_TYPE_RGBA;
47 
48     import gfx.graal.format :   formatDesc, alphaBits, redBits, greenBits,
49                                 blueBits, colorBits, alphaShift, redShift,
50                                 greenShift, blueShift, depthBits, stencilBits;
51 
52     const cd = formatDesc(attribs.colorFormat);
53     const dsd = formatDesc(attribs.depthStencilFormat);
54 
55     pfd.cColorBits = cast(ubyte)colorBits(cd.surfaceType);
56     pfd.cRedBits = cast(ubyte)redBits(cd.surfaceType);
57     pfd.cRedShift = cast(ubyte)redShift(cd.surfaceType);
58     pfd.cGreenBits = cast(ubyte)greenBits(cd.surfaceType);
59     pfd.cGreenShift = cast(ubyte)greenShift(cd.surfaceType);
60     pfd.cBlueBits = cast(ubyte)blueBits(cd.surfaceType);
61     pfd.cBlueShift = cast(ubyte)blueShift(cd.surfaceType);
62     pfd.cAlphaBits = cast(ubyte)alphaBits(cd.surfaceType);
63     pfd.cAlphaShift = cast(ubyte)alphaShift(cd.surfaceType);
64     pfd.cDepthBits = cast(ubyte)depthBits(dsd.surfaceType);
65     pfd.cStencilBits = cast(ubyte)stencilBits(dsd.surfaceType);
66 
67     pfd.iLayerType = PFD_MAIN_PLANE;
68 }
69 
70 /// Wgl implementation of GlContext
71 public class Win32GlContext : GlContext
72 {
73     import core.sys.windows.windows;
74     import gfx.core.rc : atomicRcCode, Disposable;
75     import gfx.window.win32 : registerWindowClass, wndClassName;
76     import std.exception : enforce;
77     import std.experimental.logger : tracef;
78 
79     mixin(atomicRcCode);
80 
81     private Wgl _wgl;
82     private Gl _gl;
83     private GlAttribs _attribs;
84     private HGLRC _ctx;
85     private int _pixelFormat;
86     private DummyWindow[size_t] dummies;
87     private size_t hiddenDummy;
88 
89     this(in GlAttribs attribs) {
90         _attribs = attribs;
91 
92         registerWindowClass();
93 
94         hiddenDummy = Win32GlContext.createDummy();
95 
96         auto dc = GetDC(cast(HWND)hiddenDummy);
97         scope(exit) ReleaseDC(cast(HWND)hiddenDummy, dc);
98 
99         PIXELFORMATDESCRIPTOR pfd;
100         setupPFD(_attribs, &pfd);
101         const chosen = ChoosePixelFormat(dc, &pfd);
102         SetPixelFormat(dc, chosen, &pfd);
103 
104         import gfx.bindings.core : loadSharedSym;
105         auto lib = loadGlLib();
106         PFN_wglGetProcAddress getProcAddress = cast(PFN_wglGetProcAddress)enforce(
107             loadSharedSym(lib, "wglGetProcAddress"), "could not load wglGetProcAddress"
108         );
109         SharedSym loader (in string symbol) {
110             import std.string : toStringz;
111             auto proc = cast(SharedSym)getProcAddress(toStringz(symbol));
112             if (!proc) {
113                 proc = cast(SharedSym)loadSharedSym(lib, symbol);
114             }
115             import std.stdio;
116             //writefln("%s = %x", symbol, proc);
117             return proc;
118         }
119 
120         {
121             PFN_wglCreateContext _createContext = cast(PFN_wglCreateContext)enforce(
122                 loadSharedSym(lib, "wglCreateContext"), "could not load wglCreateContext"
123             );
124             PFN_wglMakeCurrent _makeCurrent = cast(PFN_wglMakeCurrent)enforce(
125                 loadSharedSym(lib, "wglMakeCurrent"), "could not load wglMakeCurrent"
126             );
127             PFN_wglDeleteContext _deleteContext = cast(PFN_wglDeleteContext)enforce(
128                 loadSharedSym(lib, "wglDeleteContext"), "could not load wglDeleteContext"
129             );
130             // creating and binding a dummy temporary context
131             auto ctx = enforce(_createContext(dc), "could not create legacy wgl context");
132             scope(exit) _deleteContext(ctx);
133             enforce(_makeCurrent(dc, ctx), "could not make legacy context current");
134             _wgl = new Wgl(&loader);
135         }
136 
137         auto attribList = getAttribList(attribs);
138         uint nf;
139         _wgl.ChoosePixelFormatARB(dc, attribList.ptr, null, 1, &_pixelFormat, &nf);
140         enforce(nf > 0, "wglChoosePixelFormatARB failed");
141 
142         GlAttribs attrs = attribs;
143         foreach (const glVer; glVersions) {
144             attrs.majorVersion = glVer / 10;
145             attrs.minorVersion = glVer % 10;
146             if (attrs.decimalVersion < attribs.decimalVersion) break;
147 
148             const ctxAttribs = getCtxAttribs(attrs);
149             tracef("attempting to create OpenGL %s.%s context", attrs.majorVersion, attrs.minorVersion);
150 
151             _ctx = _wgl.CreateContextAttribsARB(dc, null, &ctxAttribs[0]);
152 
153             if (_ctx) break;
154         }
155 
156         enforce(_ctx, "Failed creating Wgl context");
157         tracef("created OpenGL %s.%s context", attrs.majorVersion, attrs.minorVersion);
158         _attribs = attrs;
159         Win32GlContext.makeCurrent(hiddenDummy);
160         _gl = new Gl(&loader);
161     }
162 
163     override void dispose() {
164         import gfx.core.rc : disposeArray;
165 
166         disposeArray(dummies);
167 
168         _wgl.DeleteContext(_ctx);
169         tracef("destroyed GL/WGL context");
170     }
171 
172     override @property Gl gl() {
173         return _gl;
174     }
175 
176     override @property GlAttribs attribs() {
177         return _attribs;
178     }
179 
180     void setPixelFormat(HWND hwnd) {
181         PIXELFORMATDESCRIPTOR pfd;
182         setupPFD(attribs, &pfd);
183         auto dc = GetDC(hwnd);
184         SetPixelFormat(dc, _pixelFormat, &pfd);
185         ReleaseDC(hwnd, dc);
186     }
187 
188     override bool makeCurrent(size_t nativeHandle)
189     {
190         auto wnd = cast(HWND)nativeHandle;
191         auto dc = GetDC(wnd);
192         scope(exit) ReleaseDC(wnd, dc);
193         if (!_wgl.MakeCurrent(dc, _ctx)) {
194             printLastError();
195             return false;
196         }
197         return true;
198     }
199 
200     override void doneCurrent()
201     {
202         _wgl.MakeCurrent(null, null);
203     }
204 
205     override @property bool current() const
206     {
207         return _wgl.GetCurrentContext() == _ctx;
208     }
209 
210     override @property int swapInterval()
211     {
212         return _wgl.GetSwapIntervalEXT();
213     }
214 
215     override @property void swapInterval(int interval)
216     {
217         _wgl.SwapIntervalEXT(interval);
218     }
219 
220     override void swapBuffers(size_t nativeHandle)
221     {
222         auto wnd = cast(HWND)nativeHandle;
223         auto dc = GetDC(wnd);
224         scope(exit) ReleaseDC(wnd, dc);
225         if (!SwapBuffers(dc)) {
226             printLastError();
227         }
228     }
229 
230     override size_t createDummy() {
231         if (hiddenDummy) {
232             const d = hiddenDummy;
233             hiddenDummy = 0;
234             return d;
235         }
236         auto dummy = new DummyWindow();
237         const hdl = cast(size_t)dummy.hWnd;
238         dummies[hdl] = dummy;
239         return hdl;
240     }
241 
242     override void releaseDummy(size_t dummy) {
243         auto d = dummy in dummies;
244         if (d) {
245             auto win = *d;
246             win.dispose();
247             dummies.remove(dummy);
248         }
249     }
250 
251     private void printLastError() {
252         import std.experimental.logger : errorf;
253         const err = GetLastError();
254         LPSTR messageBuffer = null;
255         size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
256                                     null, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), cast(LPSTR)&messageBuffer, 0, null);
257         auto buf = new char[size];
258         buf[] = messageBuffer[0 .. size];
259         errorf(buf.idup);
260         LocalFree(messageBuffer);
261     }
262 
263     private static class DummyWindow : Disposable
264     {
265         HWND hWnd;
266 
267         this () {
268             import std.utf : toUTF16z;
269 
270             hWnd = enforce(
271                 CreateWindowEx(
272                     WS_EX_CLIENTEDGE,
273                     wndClassName.toUTF16z,
274                     null,
275                     WS_OVERLAPPEDWINDOW,
276                     CW_USEDEFAULT,
277                     CW_USEDEFAULT,
278                     CW_USEDEFAULT,
279                     CW_USEDEFAULT,
280                     null, null, GetModuleHandle(null), null
281                 ),
282                 "could not create win32 dummy window"
283             );
284         }
285 
286         override void dispose() {
287             DestroyWindow(hWnd);
288         }
289 
290     }
291 
292 }
293 
294 int[] getCtxAttribs(in GlAttribs attribs) {
295     int[] ctxAttribs = [
296         WGL_CONTEXT_MAJOR_VERSION_ARB, attribs.majorVersion,
297         WGL_CONTEXT_MINOR_VERSION_ARB, attribs.minorVersion
298     ];
299     if (attribs.decimalVersion >= 32) {
300         ctxAttribs ~= WGL_CONTEXT_PROFILE_MASK_ARB;
301         if (attribs.profile == GlProfile.core) {
302             ctxAttribs ~= WGL_CONTEXT_CORE_PROFILE_BIT_ARB;
303         }
304         else {
305             ctxAttribs ~= WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
306         }
307     }
308     return ctxAttribs ~ 0;
309 }
310 
311 private SharedLib loadGlLib()
312 {
313     import gfx.bindings.core : openSharedLib;
314 
315     immutable glLibNames = [ "opengl32.dll" ];
316 
317     foreach (ln; glLibNames) {
318         auto lib = openSharedLib(ln);
319         if (lib) {
320             import std.experimental.logger : tracef;
321             tracef("opening shared library %s", ln);
322             return lib;
323         }
324     }
325 
326     import std.conv : to;
327     throw new Exception("could not load any of these libraries: " ~ glLibNames.to!string);
328 }