1 module gfx.window.win32.context;
2 
3 version(Windows):
4 
5 import core.sys.windows.wingdi;
6 import gfx.bindings.opengl.gl : Gl, GL_TRUE;
7 import gfx.bindings.opengl.loader;
8 import gfx.bindings.opengl.wgl;
9 import gfx.gl3.context : GlAttribs, GlContext, GlProfile, glVersions;
10 import gfx.window.win32 : gfxW32Log;
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     {
89         _attribs = attribs;
90 
91         registerWindowClass();
92 
93         auto dc = GetDC(window);
94         scope(exit) ReleaseDC(window, dc);
95 
96         PIXELFORMATDESCRIPTOR pfd;
97         setupPFD(_attribs, &pfd);
98         const chosen = ChoosePixelFormat(dc, &pfd);
99         SetPixelFormat(dc, chosen, &pfd);
100 
101         import gfx.bindings.opengl.loader : loadSharedSym;
102         auto lib = loadGlLib();
103         PFN_wglGetProcAddress getProcAddress = cast(PFN_wglGetProcAddress)enforce(
104             loadSharedSym(lib, "wglGetProcAddress"), "could not load wglGetProcAddress"
105         );
106         SharedSym loader (in string symbol) {
107             import std..string : toStringz;
108             auto proc = cast(SharedSym)getProcAddress(toStringz(symbol));
109             if (!proc) {
110                 proc = cast(SharedSym)loadSharedSym(lib, symbol);
111             }
112             import std.stdio;
113             //writefln("%s = %x", symbol, proc);
114             return proc;
115         }
116 
117         {
118             PFN_wglCreateContext _createContext = cast(PFN_wglCreateContext)enforce(
119                 loadSharedSym(lib, "wglCreateContext"), "could not load wglCreateContext"
120             );
121             PFN_wglMakeCurrent _makeCurrent = cast(PFN_wglMakeCurrent)enforce(
122                 loadSharedSym(lib, "wglMakeCurrent"), "could not load wglMakeCurrent"
123             );
124             PFN_wglDeleteContext _deleteContext = cast(PFN_wglDeleteContext)enforce(
125                 loadSharedSym(lib, "wglDeleteContext"), "could not load wglDeleteContext"
126             );
127             // creating and binding a dummy temporary context
128             auto ctx = enforce(_createContext(dc), "could not create legacy wgl context");
129             scope(exit) _deleteContext(ctx);
130             enforce(_makeCurrent(dc, ctx), "could not make legacy context current");
131             _wgl = new Wgl(&loader);
132         }
133 
134         auto attribList = getAttribList(attribs);
135         uint nf;
136         _wgl.ChoosePixelFormatARB(dc, attribList.ptr, null, 1, &_pixelFormat, &nf);
137         enforce(nf > 0, "wglChoosePixelFormatARB failed");
138 
139         GlAttribs attrs = attribs;
140         foreach (const glVer; glVersions) {
141             attrs.majorVersion = glVer / 10;
142             attrs.minorVersion = glVer % 10;
143             if (attrs.decimalVersion < attribs.decimalVersion) break;
144 
145             const ctxAttribs = getCtxAttribs(attrs);
146             gfxW32Log.tracef("attempting to create OpenGL %s.%s context", attrs.majorVersion, attrs.minorVersion);
147 
148             _ctx = _wgl.CreateContextAttribsARB(dc, null, &ctxAttribs[0]);
149 
150             if (_ctx) break;
151         }
152 
153         enforce(_ctx, "Failed creating Wgl context");
154         gfxW32Log.tracef("created OpenGL %s.%s context", attrs.majorVersion, attrs.minorVersion);
155         _attribs = attrs;
156         Win32GlContext.makeCurrent(cast(size_t)window);
157         _gl = new Gl(&loader);
158     }
159 
160     override void dispose() {
161         _wgl.DeleteContext(_ctx);
162         gfxW32Log.tracef("destroyed GL/WGL context");
163     }
164 
165     override @property Gl gl() {
166         return _gl;
167     }
168 
169     override @property GlAttribs attribs() {
170         return _attribs;
171     }
172 
173     void setPixelFormat(HWND hwnd) {
174         PIXELFORMATDESCRIPTOR pfd;
175         setupPFD(attribs, &pfd);
176         auto dc = GetDC(hwnd);
177         SetPixelFormat(dc, _pixelFormat, &pfd);
178         ReleaseDC(hwnd, dc);
179     }
180 
181     override bool makeCurrent(size_t nativeHandle)
182     {
183         auto wnd = cast(HWND)nativeHandle;
184         auto dc = GetDC(wnd);
185         scope(exit) ReleaseDC(wnd, dc);
186         if (!_wgl.MakeCurrent(dc, _ctx)) {
187             printLastError();
188             return false;
189         }
190         return true;
191     }
192 
193     override void doneCurrent()
194     {
195         _wgl.MakeCurrent(null, null);
196     }
197 
198     override @property bool current() const
199     {
200         return _wgl.GetCurrentContext() == _ctx;
201     }
202 
203     override @property int swapInterval()
204     {
205         return _wgl.GetSwapIntervalEXT();
206     }
207 
208     override @property void swapInterval(int interval)
209     {
210         _wgl.SwapIntervalEXT(interval);
211     }
212 
213     override void swapBuffers(size_t nativeHandle)
214     {
215         auto wnd = cast(HWND)nativeHandle;
216         auto dc = GetDC(wnd);
217         scope(exit) ReleaseDC(wnd, dc);
218         if (!SwapBuffers(dc)) {
219             printLastError();
220         }
221     }
222 
223     private void printLastError() {
224         const err = GetLastError();
225         LPSTR messageBuffer = null;
226         size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
227                                     null, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), cast(LPSTR)&messageBuffer, 0, null);
228         auto buf = new char[size];
229         buf[] = messageBuffer[0 .. size];
230         gfxW32Log.errorf(buf.idup);
231         LocalFree(messageBuffer);
232     }
233 
234 }
235 
236 int[] getCtxAttribs(in GlAttribs attribs) {
237     int[] ctxAttribs = [
238         WGL_CONTEXT_MAJOR_VERSION_ARB, attribs.majorVersion,
239         WGL_CONTEXT_MINOR_VERSION_ARB, attribs.minorVersion
240     ];
241     if (attribs.decimalVersion >= 32) {
242         ctxAttribs ~= WGL_CONTEXT_PROFILE_MASK_ARB;
243         if (attribs.profile == GlProfile.core) {
244             ctxAttribs ~= WGL_CONTEXT_CORE_PROFILE_BIT_ARB;
245         }
246         else {
247             ctxAttribs ~= WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
248         }
249     }
250     return ctxAttribs ~ 0;
251 }
252 
253 private SharedLib loadGlLib()
254 {
255     import gfx.bindings.opengl.loader : openSharedLib;
256 
257     immutable glLibNames = [ "opengl32.dll" ];
258 
259     foreach (ln; glLibNames) {
260         auto lib = openSharedLib(ln);
261         if (lib) {
262             gfxW32Log.tracef("opening shared library %s", ln);
263             return lib;
264         }
265     }
266 
267     import std.conv : to;
268     throw new Exception("could not load any of these libraries: " ~ glLibNames.to!string);
269 }