1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18/* glx conversion by Guenther, 2005 */
19
20/* OpenGL implementation of NewGfx, the context */
21
22#include <Standard.h>
23#include "StdApp.h"
24#include <StdGL.h>
25#include <C4Log.h>
26#include "C4Rect.h"
27#include <C4Surface.h>
28#include <StdWindow.h>
29
30#ifndef _WIN32
31#include "C4Config.h"
32#endif
33
34#ifndef USE_CONSOLE
35
36void CStdGLCtx::Deselect(bool secondary)
37{
38 if (pGL && pGL->pCurrCtx == this)
39 {
40 DoDeselect();
41 pGL->pCurrCtx = nullptr;
42 }
43 else if (secondary)
44 {
45 DoDeselect();
46 }
47}
48
49void CStdGLCtx::Finish()
50{
51 glFinish();
52}
53
54#ifdef _WIN32
55
56CStdGLCtx::CStdGLCtx() : hrc(nullptr), pWindow(nullptr), hDC(nullptr), cx(0), cy(0) {}
57
58void CStdGLCtx::Clear()
59{
60 if (hrc)
61 {
62 Deselect();
63 wglDeleteContext(hrc); hrc = nullptr;
64 }
65 if (hDC)
66 {
67 ReleaseDC(pWindow ? pWindow->GetRenderWindow() : hWindow, hDC);
68 hDC = nullptr;
69 }
70 pWindow = 0; cx = cy = 0; hWindow = nullptr;
71}
72
73bool CStdGLCtx::Init(CStdWindow *pWindow, CStdApp *pApp, HWND hWindow)
74{
75 // safety
76 if (!pGL) return false;
77
78 // store window
79 this->pWindow = pWindow;
80 // default HWND
81 if (pWindow) hWindow = pWindow->GetRenderWindow(); else this->hWindow = hWindow;
82
83 // get DC
84 hDC = GetDC(hWindow);
85 if (!hDC)
86 {
87 pGL->logger->error("Error getting DC");
88 return false;
89 }
90
91 // pixel format
92 PIXELFORMATDESCRIPTOR pfd{};
93 pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
94 pfd.nVersion = 1;
95 pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
96 pfd.iPixelType = PFD_TYPE_RGBA;
97 pfd.cColorBits = 32;
98 pfd.cDepthBits = 0;
99 pfd.iLayerType = PFD_MAIN_PLANE;
100 const auto pixelFormat = ChoosePixelFormat(hDC, &pfd);
101 if (pixelFormat == 0)
102 {
103 pGL->logger->error("Error getting pixel format");
104 return false;
105 }
106
107 if (!SetPixelFormat(hDC, pixelFormat, &pfd))
108 {
109 pGL->logger->error("Error setting pixel format");
110 return false;
111 }
112
113 // create context
114 hrc = wglCreateContext(hDC);
115 if (!hrc)
116 {
117 pGL->logger->error("Error creating context");
118 return false;
119 }
120
121 // share textures
122 if (this != &pGL->MainCtx)
123 {
124 if (!wglShareLists(pGL->MainCtx.hrc, hrc))
125 {
126 pGL->logger->error("Textures for secondary context not available");
127 return false;
128 }
129 return true;
130 }
131
132 // select
133 if (!Select())
134 {
135 pGL->logger->error("Unable to select context");
136 return false;
137 }
138
139 // init extensions
140 GLenum err = glewInit();
141 if (GLEW_OK != err)
142 {
143 // Problem: glewInit failed, something is seriously wrong.
144 pGL->logger->error(reinterpret_cast<const char *>(glewGetErrorString(err)));
145 }
146 // success
147 return true;
148}
149
150bool CStdGLCtx::Select(bool verbose, bool selectOnly)
151{
152 // safety
153 if (!pGL || !hrc) return false; if (!pGL->lpPrimary) return false;
154 // make context current
155 if (!wglMakeCurrent(hDC, hrc)) return false;
156
157 if (!selectOnly)
158 {
159 pGL->pCurrCtx = this;
160 // update size
161 UpdateSize();
162 // assign size
163 pGL->lpPrimary->Wdt = cx; pGL->lpPrimary->Hgt = cy;
164 // set some default states
165 glDisable(GL_DEPTH_TEST);
166 glShadeModel(GL_FLAT);
167 glDisable(GL_ALPHA_TEST);
168 glDisable(GL_CULL_FACE);
169 glEnable(GL_BLEND);
170 // update clipper - might have been done by UpdateSize
171 // however, the wrong size might have been assumed
172 if (!pGL->UpdateClipper()) return false;
173 }
174 // success
175 return true;
176}
177
178void CStdGLCtx::DoDeselect()
179{
180 wglMakeCurrent(nullptr, nullptr);
181}
182
183bool CStdGLCtx::UpdateSize()
184{
185 // safety
186 if (!pWindow && !hWindow) return false;
187 // get size
188 RECT rt; if (!GetClientRect(pWindow ? pWindow->GetRenderWindow() : hWindow, &rt)) return false;
189 const auto scale = pGL->pApp->GetScale();
190 int cx2 = static_cast<int32_t>(ceilf((rt.right - rt.left) / scale)), cy2 = static_cast<int32_t>(ceilf((rt.bottom - rt.top) / scale));
191 // assign if different
192 if (cx != cx2 || cy != cy2)
193 {
194 cx = cx2; cy = cy2;
195 if (pGL) pGL->UpdateClipper();
196 }
197 // success
198 return true;
199}
200
201bool CStdGLCtx::PageFlip()
202{
203 // flush GL buffer
204 glFlush();
205 SwapBuffers(hDC);
206 return true;
207}
208
209bool CStdGL::SaveDefaultGammaRampToMonitor(CStdWindow *pWindow)
210{
211 HDC hDC = GetDC(pWindow->GetRenderWindow());
212 if (hDC)
213 {
214 if (!GetDeviceGammaRamp(hDC, DefRamp.red))
215 {
216 DefRamp.Default();
217 logger->error("Error getting default gamma ramp; using standard");
218 }
219 ReleaseDC(pWindow->GetRenderWindow(), hDC);
220 return true;
221 }
222 return false;
223}
224
225bool CStdGL::ApplyGammaRampToMonitor(CGammaControl &ramp, bool fForce)
226{
227 if (!MainCtx.hDC || (!Active && !fForce)) return false;
228 SetDeviceGammaRamp(MainCtx.hDC, ramp.red);
229 return true;
230}
231
232#elif defined(USE_X11)
233
234#include <GL/glx.h>
235#include <X11/Xlib.h>
236#include <X11/extensions/xf86vmode.h>
237
238CStdGLCtx::CStdGLCtx() : pWindow(nullptr), ctx(nullptr), cx(0), cy(0) {}
239
240void CStdGLCtx::Clear()
241{
242 Deselect();
243 if (ctx)
244 {
245 glXDestroyContext(dpy: pWindow->dpy, ctx);
246 ctx = nullptr;
247 }
248 pWindow = nullptr;
249 cx = cy = 0;
250}
251
252bool CStdGLCtx::Init(CStdWindow *pWindow, CStdApp *)
253{
254 // safety
255 if (!pGL) return false;
256 // store window
257 this->pWindow = pWindow;
258 // Create Context with sharing (if this is the main context, our ctx will be 0, so no sharing)
259 // try direct rendering first
260 if (!Config.Graphics.NoAcceleration)
261 ctx = glXCreateContext(dpy: pWindow->dpy, vis: reinterpret_cast<XVisualInfo *>(pWindow->Info), shareList: pGL->MainCtx.ctx, True);
262 // without, rendering will be unacceptable slow, but that's better than nothing at all
263 if (!ctx)
264 ctx = glXCreateContext(dpy: pWindow->dpy, vis: reinterpret_cast<XVisualInfo *>(pWindow->Info), shareList: pGL->MainCtx.ctx, False);
265 // No luck at all?
266 if (!ctx)
267 {
268 pGL->logger->error(msg: "Unable to create context");
269 return false;
270 }
271
272 if (this == &pGL->MainCtx)
273 {
274 if (!Select(verbose: true))
275 {
276 pGL->logger->error(msg: "Unable to select context");
277 return false;
278 }
279 // init extensions
280 GLenum err = glewInit();
281 if (GLEW_OK != err)
282 {
283 // Problem: glewInit failed, something is seriously wrong.
284 pGL->logger->error(msg: reinterpret_cast<const char *>(glewGetErrorString(error: err)));
285 }
286 }
287 return true;
288}
289
290bool CStdGLCtx::Select(bool verbose, bool selectOnly)
291{
292 // safety
293 if (!pGL || !ctx)
294 {
295 return false;
296 }
297 if (!pGL->lpPrimary)
298 {
299 if (verbose) pGL->logger->error(msg: "lpPrimary is zero");
300 return false;
301 }
302 // make context current
303 if (!pWindow->renderwnd || !glXMakeCurrent(dpy: pWindow->dpy, drawable: pWindow->renderwnd, ctx))
304 {
305 if (verbose) pGL->logger->error(msg: "glXMakeCurrent failed");
306 return false;
307 }
308
309 if (!selectOnly)
310 {
311 pGL->pCurrCtx = this;
312 // update size FIXME: Don't call this every frame
313 UpdateSize();
314 // assign size
315 pGL->lpPrimary->Wdt = cx; pGL->lpPrimary->Hgt = cy;
316 // set some default states
317 glDisable(GL_DEPTH_TEST);
318 glShadeModel(GL_FLAT);
319 glDisable(GL_ALPHA_TEST);
320 glDisable(GL_CULL_FACE);
321 glEnable(GL_BLEND);
322 // update clipper - might have been done by UpdateSize
323 // however, the wrong size might have been assumed
324 if (!pGL->UpdateClipper())
325 {
326 if (verbose) pGL->logger->error(msg: "UpdateClipper failed");
327 return false;
328 }
329 }
330 // success
331 return true;
332}
333
334void CStdGLCtx::DoDeselect()
335{
336 glXMakeCurrent(dpy: pWindow->dpy, None, ctx: nullptr);
337}
338
339bool CStdGLCtx::UpdateSize()
340{
341 // safety
342 if (!pWindow) return false;
343 // get size
344 Window winDummy;
345 unsigned int borderDummy;
346 int x, y;
347 unsigned int width, height;
348 unsigned int depth;
349 XGetGeometry(pWindow->dpy, pWindow->renderwnd, &winDummy, &x, &y,
350 &width, &height, &borderDummy, &depth);
351 // assign if different
352 const auto scale = pGL->pApp->GetScale();
353 auto newWidth = static_cast<int32_t>(ceilf(x: width / scale));
354 auto newHeight = static_cast<int32_t>(ceilf(x: height / scale));
355 if (cx != newWidth || cy != newHeight)
356 {
357 cx = newWidth; cy = newHeight;
358 if (pGL) pGL->UpdateClipper();
359 }
360 // success
361 return true;
362}
363
364bool CStdGLCtx::PageFlip()
365{
366 // flush GL buffer
367 glFlush();
368 if (!pWindow || !pWindow->renderwnd) return false;
369 glXSwapBuffers(dpy: pWindow->dpy, drawable: pWindow->renderwnd);
370 return true;
371}
372
373bool CStdGL::ApplyGammaRampToMonitor(CGammaControl &ramp, bool fForce)
374{
375 if (!DeviceReady() || (!Active && !fForce)) return false;
376 if (pApp->xf86vmode_major_version < 2) return false;
377 if (gammasize != ramp.size || gammasize == 0) return false;
378 return XF86VidModeSetGammaRamp(pApp->dpy, DefaultScreen(pApp->dpy), ramp.size,
379 ramp.red, ramp.green, ramp.blue);
380}
381
382bool CStdGL::SaveDefaultGammaRampToMonitor(CStdWindow *pWindow)
383{
384 if (pApp->xf86vmode_major_version < 2) return false;
385 // Get the Display
386 Display *const dpy = pWindow->dpy;
387 XF86VidModeGetGammaRampSize(dpy, DefaultScreen(dpy), &gammasize);
388
389 if (gammasize != 0)
390 {
391 if (gammasize != 256)
392 {
393 DefRamp.Set(dwClr1: 0x000000, dwClr2: 0x808080, dwClr3: 0xffffff, size: gammasize, ref: nullptr);
394 logger->warn(fmt: "Size of GammaRamp is {}, not 256", args&: gammasize);
395 }
396
397 // store default gamma
398 if (!XF86VidModeGetGammaRamp(pWindow->dpy, DefaultScreen(pWindow->dpy), DefRamp.size,
399 DefRamp.red, DefRamp.green, DefRamp.blue))
400 {
401 DefRamp.Default();
402 logger->error(msg: "Error getting default gamma ramp; using standard");
403 }
404 }
405 else
406 {
407 DefRamp.Default();
408 logger->warn(msg: "Size of GammaRamp is 0, using default ramp");
409 }
410
411 Gamma.Set(dwClr1: 0x000000, dwClr2: 0x808080, dwClr3: 0xffffff, size: DefRamp.GetSize(), ref: &DefRamp);
412 return true;
413}
414
415#elif defined(USE_SDL_MAINLOOP)
416
417#include <stdexcept>
418#include <string>
419
420CStdGLCtx::CStdGLCtx() : pWindow{}, cx{}, cy{}, ctx{} {}
421
422void CStdGLCtx::Clear()
423{
424 Deselect();
425 if (ctx)
426 {
427 SDL_GL_DeleteContext(ctx);
428 ctx = nullptr;
429 }
430 pWindow = nullptr;
431 cx = cy = 0;
432}
433
434bool CStdGLCtx::Init(CStdWindow *pWindow, CStdApp *)
435{
436 // safety
437 if (!pGL) return false;
438 ctx = SDL_GL_CreateContext(pWindow->sdlWindow);
439 if (!ctx)
440 {
441 pGL->logger->error("Unable to create context: {}", SDL_GetError());
442 return false;
443 }
444 // store window
445 this->pWindow = pWindow;
446 assert(!Config.Graphics.NoAcceleration);
447
448 if (this == &pGL->MainCtx)
449 {
450 // No luck at all?
451 if (!Select(true))
452 {
453 pGL->logger->error("Unable to select context");
454 return false;
455 }
456 // init extensions
457 GLenum err = glewInit();
458 if (GLEW_OK != err)
459 {
460 // Problem: glewInit failed, something is seriously wrong.
461 pGL->logger->error(reinterpret_cast<const char *>(glewGetErrorString(err)));
462 }
463 }
464 return true;
465}
466
467bool CStdGLCtx::Select(bool verbose, bool selectOnly)
468{
469 SDL_GL_MakeCurrent(this->pWindow->sdlWindow, ctx);
470 if (!selectOnly)
471 {
472 pGL->pCurrCtx = this;
473 // update size FIXME: Don't call this every frame
474 UpdateSize();
475 // assign size
476 pGL->lpPrimary->Wdt = cx; pGL->lpPrimary->Hgt = cy;
477 // set some default states
478 glDisable(GL_DEPTH_TEST);
479 glShadeModel(GL_FLAT);
480 glDisable(GL_ALPHA_TEST);
481 glDisable(GL_CULL_FACE);
482 glEnable(GL_BLEND);
483 // update clipper - might have been done by UpdateSize
484 // however, the wrong size might have been assumed
485 if (!pGL->UpdateClipper())
486 {
487 if (verbose) pGL->logger->error("UpdateClipper failed");
488 return false;
489 }
490 }
491 // success
492 return true;
493}
494
495void CStdGLCtx::DoDeselect()
496{
497 if (SDL_GL_MakeCurrent(this->pWindow->sdlWindow, nullptr) != 0)
498 {
499 throw std::runtime_error{std::string{"SDL_GL_MakeCurrent failed: "} + SDL_GetError()};
500 }
501}
502
503bool CStdGLCtx::UpdateSize()
504{
505 // safety
506 if (!pWindow) return false;
507 // get size
508 C4Rect rc;
509 if (!pWindow->GetSize(rc)) return false;
510 const auto scale = pGL->pApp->GetScale();
511 int width = static_cast<int32_t>(ceilf(rc.Wdt / scale)), height = static_cast<int32_t>(ceilf(rc.Hgt / scale));
512 // assign if different
513 if (cx != width || cy != height)
514 {
515 cx = width; cy = height;
516 if (pGL) pGL->UpdateClipper();
517 }
518 // success
519 return true;
520}
521
522bool CStdGLCtx::PageFlip()
523{
524 // flush GL buffer
525 glFlush();
526 if (!pWindow) return false;
527 SDL_GL_SwapWindow(this->pWindow->sdlWindow);
528 return true;
529}
530
531bool CStdGL::ApplyGammaRampToMonitor(CGammaControl &ramp, bool fForce)
532{
533 assert(ramp.size == 256);
534 return SDL_SetWindowGammaRamp(MainCtx.pWindow->sdlWindow, ramp.red, ramp.green, ramp.blue) == 0;
535}
536
537bool CStdGL::SaveDefaultGammaRampToMonitor(CStdWindow *pWindow)
538{
539 assert(DefRamp.size == 256);
540 return SDL_GetWindowGammaRamp(MainCtx.pWindow->sdlWindow, DefRamp.red, DefRamp.green, DefRamp.blue) == 0;
541}
542
543#endif // USE_X11/USE_SDL_MAINLOOP
544
545#endif
546