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