OpenClonk
C4DrawGLCtx.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 
17 /* OpenGL implementation of NewGfx, the context */
18 
19 #include "C4Include.h"
21 #include "graphics/C4DrawGL.h"
22 
23 #include "platform/C4App.h"
24 #include "platform/C4Window.h"
25 
26 #ifndef USE_CONSOLE
27 
28 #if defined(USE_WGL) || defined(USE_SDL_MAINLOOP)
29 static const int REQUESTED_GL_CTX_MAJOR = 3;
30 static const int REQUESTED_GL_CTX_MINOR = 2;
31 #endif
32 
33 std::list<CStdGLCtx*> CStdGLCtx::contexts;
34 
36 {
37  pGL->pCurrCtx = this;
38  // set some default states
39  glDisable(GL_DEPTH_TEST);
40  glDepthFunc(GL_LESS);
41  glDisable(GL_CULL_FACE);
42  glEnable(GL_BLEND);
43  // Delete pending VAOs
44  std::vector<GLuint> toBeDeleted;
45  if (!VAOsToBeDeleted.empty())
46  {
47  for (unsigned int i : VAOsToBeDeleted)
48  {
49  if (i < hVAOs.size() && hVAOs[i] != 0)
50  {
51  toBeDeleted.push_back(hVAOs[i]);
52  hVAOs[i] = 0;
53  }
54  }
55 
56  glDeleteVertexArrays(toBeDeleted.size(), &toBeDeleted[0]);
57  VAOsToBeDeleted.clear();
58  }
59 }
60 
61 #ifdef USE_WGL
62 
63 #include <epoxy/wgl.h>
64 
65 static PIXELFORMATDESCRIPTOR pfd; // desired pixel format
66 static HGLRC hrc = nullptr;
67 
68 // Enumerate available pixel formats. Choose the best pixel format in
69 // terms of color and depth buffer bits and then return all formats with
70 // different multisampling settings. If there are more then one, then choose
71 // the one with highest depth buffer size and lowest stencil and auxiliary
72 // buffer sizes since we don't use them in Clonk.
73 static std::vector<int> EnumeratePixelFormats(HDC hdc)
74 {
75  std::vector<int> result;
76  if(!epoxy_has_wgl_extension(hdc, "WGL_ARB_pixel_format")) return result;
77 
78  int n_formats;
79  int attributes = WGL_NUMBER_PIXEL_FORMATS_ARB;
80  if(!wglGetPixelFormatAttribivARB(hdc, 0, 0, 1, &attributes, &n_formats)) return result;
81 
82  for(int i = 1; i < n_formats+1; ++i)
83  {
84  int new_attributes[] = { WGL_DRAW_TO_WINDOW_ARB, WGL_SUPPORT_OPENGL_ARB, WGL_DOUBLE_BUFFER_ARB, WGL_COLOR_BITS_ARB, WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB, WGL_AUX_BUFFERS_ARB, WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB, WGL_PIXEL_TYPE_ARB };
85  const unsigned int nnew_attributes = sizeof(new_attributes)/sizeof(int);
86 
87  int new_results[nnew_attributes];
88  if(!wglGetPixelFormatAttribivARB(hdc, i, 0, nnew_attributes, new_attributes, new_results)) continue;
89  if(!new_results[0] || !new_results[1] || !new_results[2]) continue;
90  if(new_results[3] < 16 || new_results[4] < 16) continue; // require at least 16 bits per pixel in color and depth
91 
92  // For no MS we do find a pixel format with depth 32 on my (ck's) computer,
93  // however, when choosing it then texturing does not work anymore. I am not
94  // exactly sure what the cause of that is, so let's not choose that one for now.
95  if(new_results[4] > 24) continue;
96 
97  // ensure that we get an RGB buffer. otherwise we might end up with a float buffer, which messes up gamma.
98  if (new_results[9] != WGL_TYPE_RGBA_ARB)
99  continue;
100 
101  // Multisampling with just one sample is equivalent to no-multisampling,
102  // so don't include that in the result.
103  if(new_results[7] == 1 && new_results[8] == 1)
104  continue;
105 
106  if(result.empty())
107  {
108  result.push_back(i);
109  }
110  else
111  {
112  int old_attributes[] = { WGL_COLOR_BITS_ARB };
113  const unsigned int nold_attributes = sizeof(old_attributes)/sizeof(int);
114  int old_results[nold_attributes];
115 
116  if(!wglGetPixelFormatAttribivARB(hdc, result[0], 0, nold_attributes, old_attributes, old_results)) continue;
117 
118  if(new_results[3] > old_results[0])
119  {
120  result.clear();
121  result.push_back(i);
122  }
123  else if(new_results[3] == old_results[0])
124  {
125  unsigned int j;
126  for(j = 0; j < result.size(); ++j)
127  {
128  int equiv_attributes[] = { WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB, WGL_AUX_BUFFERS_ARB, WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB };
129  const unsigned int nequiv_attributes = sizeof(equiv_attributes)/sizeof(int);
130  int equiv_results[nequiv_attributes];
131  if(!wglGetPixelFormatAttribivARB(hdc, result[j], 0, nequiv_attributes, equiv_attributes, equiv_results)) continue;
132 
133  if(new_results[7] == equiv_results[3] && new_results[8] == equiv_results[4])
134  {
135  if(new_results[4] > equiv_results[0] || (new_results[4] == equiv_results[0] && (new_results[5] < equiv_results[1] || (new_results[5] == equiv_results[1] && new_results[6] < equiv_results[2]))))
136  result[j] = i;
137  break;
138  }
139  }
140 
141  if(j == result.size()) result.push_back(i);
142  }
143  }
144  }
145 
146  return result;
147 }
148 
149 static int GetPixelFormatForMS(HDC hDC, int samples)
150 {
151  std::vector<int> vec = EnumeratePixelFormats(hDC);
152  for(int i : vec)
153  {
154  int attributes[] = { WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB };
155  const unsigned int n_attributes = 2;
156  int results[2];
157  if(!wglGetPixelFormatAttribivARB(hDC, i, 0, n_attributes, attributes, results)) continue;
158 
159  if( (samples == 0 && results[0] == 0) ||
160  (samples > 0 && results[0] == 1 && results[1] == samples))
161  {
162  return i;
163  }
164  }
165 
166  return 0;
167 }
168 
169 class WinAPIError : public std::runtime_error
170 {
171 public:
172  typedef DWORD error_code;
173 
174  WinAPIError() : WinAPIError(GetLastError()) {}
175  WinAPIError(error_code err) : std::runtime_error(format_error(err)) {}
176 
177 private:
178  static std::string format_error(error_code err)
179  {
180  LPWSTR buffer = nullptr;
181  FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
182  nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPWSTR>(&buffer), 0, nullptr);
183  StdStrBuf str(buffer);
184  LocalFree(buffer);
185  return std::string(str.getData(), str.getLength());
186  }
187 };
188 
189 class GLTempContext
190 {
191  HWND wnd;
192  HDC dc;
193  HGLRC glrc;
194 public:
195  GLTempContext()
196  {
197  wnd = CreateWindowExW(0, L"STATIC", nullptr, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
198  if (!wnd)
199  throw WinAPIError();
200  dc = GetDC(wnd);
201  auto pfd = PIXELFORMATDESCRIPTOR();
202  pfd.nSize = sizeof(pfd);
203  pfd.nVersion = 1;
204  pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
205  pfd.iPixelType = PFD_TYPE_RGBA;
206  pfd.iLayerType = PFD_MAIN_PLANE;
207  int format = ChoosePixelFormat(dc, &pfd);
208  if (!format ||
209  !SetPixelFormat(dc, format, &pfd) ||
210  (glrc = wglCreateContext(dc)) == nullptr)
211  {
212  DWORD err = GetLastError();
213  ReleaseDC(wnd, dc);
214  DestroyWindow(wnd);
215  throw WinAPIError(err);
216  }
217  if (!wglMakeCurrent(dc, glrc))
218  {
219  DWORD err = GetLastError();
220  wglDeleteContext(glrc);
221  ReleaseDC(wnd, dc);
222  DestroyWindow(wnd);
223  throw WinAPIError(err);
224  }
225  }
226  ~GLTempContext()
227  {
228  if (glrc == wglGetCurrentContext())
229  wglMakeCurrent(dc, nullptr);
230  wglDeleteContext(glrc);
231  ReleaseDC(wnd, dc);
232  DestroyWindow(wnd);
233  }
234 };
235 
236 CStdGLCtx::CStdGLCtx(): this_context(contexts.end()) { }
237 
238 void CStdGLCtx::Clear(bool multisample_change)
239 {
240  Deselect();
241  if (hDC && pWindow)
242  ReleaseDC(pWindow->renderwnd, hDC);
243  hDC = nullptr;
244  pWindow = nullptr;
245 
246  if (this_context != contexts.end())
247  {
248  contexts.erase(this_context);
249  this_context = contexts.end();
250  }
251  if (multisample_change)
252  {
253  assert(!pGL->pCurrCtx);
254  if (hrc)
255  wglDeleteContext(hrc);
256  hrc = nullptr;
257  }
258 }
259 
260 bool CStdGLCtx::Init(C4Window * pWindow, C4AbstractApp *pApp)
261 {
262  // safety
263  if (!pGL || !pWindow) return false;
264 
265  std::unique_ptr<GLTempContext> tempContext;
266  if (hrc == nullptr)
267  {
268  // Create a temporary context to be able to fetch GL extension pointers
269  try
270  {
271  tempContext = std::make_unique<GLTempContext>();
272  }
273  catch (const WinAPIError &e)
274  {
275  pGL->Error((std::string(" gl: Unable to create temporary context: ") + e.what()).c_str());
276  return false;
277  }
278  }
279 
280  // store window
281  this->pWindow = pWindow;
282 
283  // get DC
284  hDC = GetDC(pWindow->renderwnd);
285  if(!hDC)
286  {
287  pGL->Error(" gl: Error getting DC");
288  return false;
289  }
290  if (hrc)
291  {
292  SetPixelFormat(hDC, pGL->iPixelFormat, &pfd);
293  }
294  else
295  {
296  // Choose a good pixel format.
297  int pixel_format;
298  if((pixel_format = GetPixelFormatForMS(hDC, Config.Graphics.MultiSampling)) == 0)
299  if((pixel_format = GetPixelFormatForMS(hDC, 0)) != 0)
301 
302  if (!pixel_format)
303  {
304  pGL->Error(" gl: Error choosing pixel format");
305  }
306  else
307  {
308  ZeroMemory(&pfd, sizeof(pfd)); pfd.nSize = sizeof(pfd);
309  if(!DescribePixelFormat(hDC, pixel_format, sizeof(pfd), &pfd))
310  {
311  pGL->Error(" gl: Error describing chosen pixel format");
312  }
313  else if(!SetPixelFormat(hDC, pixel_format, &pfd))
314  {
315  pGL->Error(" gl: Error setting chosen pixel format");
316  }
317  else
318  {
319  // create context
320  if (epoxy_has_wgl_extension(hDC, "WGL_ARB_create_context"))
321  {
322  {
323  const int attribs[] = {
324  WGL_CONTEXT_FLAGS_ARB, Config.Graphics.DebugOpenGL ? WGL_CONTEXT_DEBUG_BIT_ARB : 0,
325  WGL_CONTEXT_MAJOR_VERSION_ARB, REQUESTED_GL_CTX_MAJOR,
326  WGL_CONTEXT_MINOR_VERSION_ARB, REQUESTED_GL_CTX_MINOR,
327  WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
328  0
329  };
330 
331  hrc = wglCreateContextAttribsARB(hDC, nullptr, attribs);
332  }
333 
334  if (!hrc)
335  {
336  LogSilentF(" gl: OpenGL %d.%d not available; falling back to 3.1 emergency context.", REQUESTED_GL_CTX_MAJOR, REQUESTED_GL_CTX_MINOR);
337  // Some older Intel drivers don't support OpenGL 3.2; we don't use (much?) of
338  // that so we'll request a 3.1 context as a fallback.
339  const int attribs[] = {
340  WGL_CONTEXT_FLAGS_ARB, Config.Graphics.DebugOpenGL ? WGL_CONTEXT_DEBUG_BIT_ARB : 0,
341  WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
342  WGL_CONTEXT_MINOR_VERSION_ARB, 1,
343  0
344  };
345  pGL->Workarounds.ForceSoftwareTransform = true;
346  hrc = wglCreateContextAttribsARB(hDC, nullptr, attribs);
347  }
348  }
349  else
350  {
351  DebugLog(" gl: wglCreateContextAttribsARB not available; creating default context.");
352  hrc = wglCreateContext(hDC);
353  }
354 
355  if(!hrc)
356  {
357  pGL->Error(" gl: Error creating gl context");
358  }
359 
360  pGL->iPixelFormat = pixel_format;
361  }
362  }
363  }
364  if (hrc)
365  {
366  Select();
367 
368  this_context = contexts.insert(contexts.end(), this);
369  return true;
370  }
371 
372  ReleaseDC(pWindow->renderwnd, hDC); hDC = nullptr;
373  return false;
374 }
375 
376 std::vector<int> CStdGLCtx::EnumerateMultiSamples() const
377 {
378  assert(hrc != 0);
379  std::vector<int> result;
380  std::vector<int> vec = EnumeratePixelFormats(hDC);
381  for(int i : vec)
382  {
383  int attributes[] = { WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB };
384  const unsigned int n_attributes = 2;
385  int results[2];
386  if(!wglGetPixelFormatAttribivARB(hDC, i, 0, n_attributes, attributes, results)) continue;
387 
388  if(results[0] == 1) result.push_back(results[1]);
389  }
390 
391  return result;
392 }
393 
394 bool CStdGLCtx::Select(bool verbose)
395 {
396  // safety
397  if (!pGL || !hrc) return false;
398  // make context current
399  if (!wglMakeCurrent (hDC, hrc))
400  {
401  pGL->Error("Unable to select context.");
402  return false;
403  }
404  SelectCommon();
405  // update clipper - might have been done by UpdateSize
406  // however, the wrong size might have been assumed
407  if (!pGL->UpdateClipper()) return false;
408  // success
409  return true;
410 }
411 
412 void CStdGLCtx::Deselect()
413 {
414  if (pGL && pGL->pCurrCtx == this)
415  {
416  wglMakeCurrent(nullptr, nullptr);
417  pGL->pCurrCtx=nullptr;
418  pGL->RenderTarget=nullptr;
419  }
420 }
421 
422 bool CStdGLCtx::PageFlip()
423 {
424  // flush GL buffer
425  glFlush();
426  SwapBuffers(hDC);
427  return true;
428 }
429 
430 #elif defined(USE_SDL_MAINLOOP)
431 
432 CStdGLCtx::CStdGLCtx(): pWindow(0), this_context(contexts.end()) { ctx = nullptr; }
433 
434 void CStdGLCtx::Clear(bool multisample_change)
435 {
436  Deselect();
437  if (ctx) SDL_GL_DeleteContext(ctx);
438  ctx = 0;
439  pWindow = 0;
440 
441  if (this_context != contexts.end())
442  {
443  contexts.erase(this_context);
444  this_context = contexts.end();
445  }
446 }
447 
448 bool CStdGLCtx::Init(C4Window * pWindow, C4AbstractApp *)
449 {
450  // safety
451  if (!pGL) return false;
452  // store window
453  this->pWindow = pWindow;
454  ctx = SDL_GL_CreateContext(pWindow->window);
455  if (!ctx)
456  {
457  LogSilentF(" gl: OpenGL %d.%d not available; falling back to 3.1 emergency context.", REQUESTED_GL_CTX_MAJOR, REQUESTED_GL_CTX_MINOR);
458  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
459  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
460  pGL->Workarounds.ForceSoftwareTransform = true;
461  ctx = SDL_GL_CreateContext(pWindow->window);
462  }
463  if (!ctx)
464  {
465  return pGL->Error(FormatString("SDL_GL_CreateContext: %s", SDL_GetError()).getData());
466  }
467  // No luck at all?
468  if (!Select(true)) return false;
469 
470  this_context = contexts.insert(contexts.end(), this);
471  return true;
472 }
473 
474 bool CStdGLCtx::Select(bool verbose)
475 {
476  if (SDL_GL_MakeCurrent(pWindow->window, ctx) != 0)
477  return pGL->Error(FormatString("SDL_GL_MakeCurrent: %s", SDL_GetError()).getData());
478  SelectCommon();
479  // update clipper - might have been done by UpdateSize
480  // however, the wrong size might have been assumed
481  if (!pGL->UpdateClipper())
482  {
483  if (verbose) pGL->Error(" gl: UpdateClipper failed");
484  return false;
485  }
486  // success
487  return true;
488 }
489 
490 void CStdGLCtx::Deselect()
491 {
492  if (pGL && pGL->pCurrCtx == this)
493  {
494  pGL->pCurrCtx = 0;
495  pGL->RenderTarget = 0;
496  }
497 }
498 
499 bool CStdGLCtx::PageFlip()
500 {
501  // flush GL buffer
502  glFlush();
503  if (!pWindow) return false;
504  SDL_GL_SwapWindow(pWindow->window);
505  return true;
506 }
507 
508 #endif // USE_*
509 
510 #ifdef WITH_QT_EDITOR
511 #include <QOpenGLWidget>
512 #include <QOpenGLContext>
513 #include <QOffscreenSurface>
514 
515 CStdGLCtxQt::CStdGLCtxQt() { context = nullptr; surface = nullptr; }
516 
517 void CStdGLCtxQt::Clear(bool multisample_change)
518 {
519  Deselect();
520 
521  if (context)
522  {
523  if (!pWindow->glwidget) delete context;
524  delete surface;
525  }
526  pWindow = nullptr;
527 }
528 
529 bool CStdGLCtxQt::Init(C4Window *window, C4AbstractApp *app)
530 {
531  if (!pGL) return false;
532  pWindow = window;
533 
534  if (!pWindow->glwidget)
535  {
536  surface = new QOffscreenSurface();
537  surface->create();
538  context = new QOpenGLContext();
539  QOpenGLContext* share_context = QOpenGLContext::globalShareContext();
540  if (share_context) context->setShareContext(share_context);
541  if (!context->create())
542  return false;
543 
544  if (!Select(true)) return false;
545  }
546  else
547  {
548  // The Qt GL widget has its own context
549  context = pWindow->glwidget->context();
550  }
551 
552  this_context = contexts.insert(contexts.end(), this);
553  return true;
554 }
555 
556 bool CStdGLCtxQt::Select(bool verbose)
557 {
558  if (!pWindow->glwidget)
559  {
560  if (!context->makeCurrent(surface))
561  return false;
562  }
563  else
564  {
565  // done automatically
566  /* pWindow->glwidget->makeCurrent(); */
567  }
568  SelectCommon();
569  // update clipper - might have been done by UpdateSize
570  // however, the wrong size might have been assumed
571  if (!pGL->UpdateClipper())
572  {
573  if (verbose) pGL->Error(" gl: UpdateClipper failed");
574  return false;
575  }
576  // success
577  return true;
578 }
579 
580 void CStdGLCtxQt::Deselect()
581 {
582  if (!pWindow->glwidget)
583  context->doneCurrent();
584  else
585  {
586  // done automatically
587  /* pWindow->glwidget->doneCurrent(); */
588  }
589  if (pGL && pGL->pCurrCtx == this)
590  {
591  pGL->pCurrCtx = nullptr;
592  pGL->RenderTarget = nullptr;
593  }
594 }
595 
596 bool CStdGLCtxQt::PageFlip()
597 {
598  // flush GL buffer
599  glFlush();
600  if (!pWindow) return false;
601  if (!pWindow->glwidget)
602  return false;
603  return true;
604 }
605 
606 #endif
607 
608 #endif // USE_CONSOLE
C4Config Config
Definition: C4Config.cpp:930
CStdGL * pGL
Definition: C4DrawGL.cpp:907
bool DebugLog(const char *strMessage)
Definition: C4Log.cpp:282
bool LogSilentF(const char *strMessage,...)
Definition: C4Log.cpp:272
uint32_t DWORD
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
int32_t DebugOpenGL
Definition: C4Config.h:117
int32_t MultiSampling
Definition: C4Config.h:115
C4ConfigGraphics Graphics
Definition: C4Config.h:257
C4Surface * RenderTarget
Definition: C4Draw.h:107
void SelectCommon()
Definition: C4DrawGLCtx.cpp:35
std::vector< unsigned int > VAOsToBeDeleted
Definition: C4DrawGL.h:142
virtual bool Init(C4Window *pWindow, C4AbstractApp *pApp)
virtual bool Select(bool verbose=false)
virtual void Clear(bool multisample_change=false)
std::vector< GLuint > hVAOs
Definition: C4DrawGL.h:140
virtual bool PageFlip()
static std::list< CStdGLCtx * > contexts
Definition: C4DrawGL.h:136
std::list< CStdGLCtx * >::iterator this_context
Definition: C4DrawGL.h:137
virtual void Deselect()
C4Window * pWindow
Definition: C4DrawGL.h:128
CStdGLCtx * pCurrCtx
Definition: C4DrawGL.h:180
bool Error(const char *szMsg) override
Definition: C4DrawGL.cpp:855
bool UpdateClipper() override
Definition: C4DrawGL.cpp:143
int iPixelFormat
Definition: C4DrawGL.h:176
struct CStdGL::@10 Workarounds