1#include <base/detect.h>
2
3#ifndef CONF_BACKEND_OPENGL_ES
4#include <GL/glew.h>
5#endif
6
7#include <base/log.h>
8#include <base/math.h>
9#include <base/thread.h>
10#include <base/tl/threading.h>
11
12#include <engine/shared/config.h>
13#include <engine/shared/localization.h>
14
15#include <SDL.h>
16#include <SDL_messagebox.h>
17#include <SDL_vulkan.h>
18
19#if defined(CONF_VIDEORECORDER)
20#include <engine/shared/video.h>
21#endif
22
23#include "backend_sdl.h"
24
25#if defined(CONF_HEADLESS_CLIENT)
26#include "backend/null/backend_null.h"
27#endif
28
29#if !defined(CONF_BACKEND_OPENGL_ES)
30#include "backend/opengl/backend_opengl3.h"
31#endif
32
33#if defined(CONF_BACKEND_OPENGL_ES3) || defined(CONF_BACKEND_OPENGL_ES)
34#include "backend/opengles/backend_opengles3.h"
35#endif
36
37#if defined(CONF_BACKEND_VULKAN)
38#include "backend/vulkan/backend_vulkan.h"
39#endif
40
41#include "graphics_threaded.h"
42
43#include <engine/graphics.h>
44
45#include <algorithm>
46#include <cstdlib>
47
48class IStorage;
49
50// ------------ CGraphicsBackend_Threaded
51
52// Run everything single threaded when compiling for Emscripten, as context binding does not work outside of the main thread with SDL2.
53// TODO SDL3: Check if SDL3 supports threaded graphics and PROXY_TO_PTHREAD, OFFSCREENCANVAS_SUPPORT and OFFSCREEN_FRAMEBUFFER correctly.
54#if !defined(CONF_PLATFORM_EMSCRIPTEN)
55void CGraphicsBackend_Threaded::ThreadFunc(void *pUser)
56{
57 auto *pSelf = (CGraphicsBackend_Threaded *)pUser;
58 std::unique_lock<std::mutex> Lock(pSelf->m_BufferSwapMutex);
59 // notify, that the thread started
60 pSelf->m_Started = true;
61 pSelf->m_BufferSwapCond.notify_all();
62 while(!pSelf->m_Shutdown)
63 {
64 pSelf->m_BufferSwapCond.wait(lock&: Lock, p: [&pSelf] { return pSelf->m_pBuffer != nullptr || pSelf->m_Shutdown; });
65 if(pSelf->m_pBuffer)
66 {
67#ifdef CONF_PLATFORM_MACOS
68 CAutoreleasePool AutoreleasePool;
69#endif
70 pSelf->m_pProcessor->RunBuffer(pBuffer: pSelf->m_pBuffer);
71
72 pSelf->m_pBuffer = nullptr;
73 pSelf->m_BufferInProcess.store(i: false, m: std::memory_order_relaxed);
74 pSelf->m_BufferSwapCond.notify_all();
75
76#if defined(CONF_VIDEORECORDER)
77 if(IVideo::Current())
78 IVideo::Current()->NextVideoFrameThread();
79#endif
80 }
81 }
82}
83#endif
84
85CGraphicsBackend_Threaded::CGraphicsBackend_Threaded(TTranslateFunc &&TranslateFunc) :
86 m_TranslateFunc(std::move(TranslateFunc))
87{
88 m_pProcessor = nullptr;
89 m_Shutdown = true;
90#if !defined(CONF_PLATFORM_EMSCRIPTEN)
91 m_pBuffer = nullptr;
92 m_BufferInProcess.store(i: false, m: std::memory_order_relaxed);
93#endif
94}
95
96void CGraphicsBackend_Threaded::StartProcessor(ICommandProcessor *pProcessor)
97{
98 dbg_assert(m_Shutdown, "Processor was already not shut down.");
99 m_Shutdown = false;
100 m_pProcessor = pProcessor;
101#if !defined(CONF_PLATFORM_EMSCRIPTEN)
102 std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
103 m_pThread = thread_init(threadfunc: ThreadFunc, user: this, name: "Graphics thread");
104 // wait for the thread to start
105 m_BufferSwapCond.wait(lock&: Lock, p: [this]() -> bool { return m_Started; });
106#endif
107}
108
109void CGraphicsBackend_Threaded::StopProcessor()
110{
111 dbg_assert(!m_Shutdown, "Processor was already shut down.");
112 m_Shutdown = true;
113#if defined(CONF_PLATFORM_EMSCRIPTEN)
114 m_Warning = m_pProcessor->GetWarning();
115#else
116 {
117 std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
118 m_Warning = m_pProcessor->GetWarning();
119 m_BufferSwapCond.notify_all();
120 }
121 thread_wait(thread: m_pThread);
122#endif
123}
124
125void CGraphicsBackend_Threaded::RunBuffer(CCommandBuffer *pBuffer)
126{
127 SGfxErrorContainer Error;
128#if defined(CONF_PLATFORM_EMSCRIPTEN)
129 Error = m_pProcessor->GetError();
130 if(Error.m_ErrorType == GFX_ERROR_TYPE_NONE)
131 {
132 RunBufferSingleThreadedUnsafe(pBuffer);
133#if defined(CONF_VIDEORECORDER)
134 if(IVideo::Current())
135 IVideo::Current()->NextVideoFrameThread();
136#endif
137 }
138#else
139 WaitForIdle();
140 {
141 std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
142 Error = m_pProcessor->GetError();
143 if(Error.m_ErrorType == GFX_ERROR_TYPE_NONE)
144 {
145 m_pBuffer = pBuffer;
146 m_BufferInProcess.store(i: true, m: std::memory_order_relaxed);
147 m_BufferSwapCond.notify_all();
148 }
149 }
150#endif
151
152 // Process error after lock is released to prevent deadlock
153 if(Error.m_ErrorType != GFX_ERROR_TYPE_NONE)
154 {
155 ProcessError(Error);
156 }
157}
158
159void CGraphicsBackend_Threaded::RunBufferSingleThreadedUnsafe(CCommandBuffer *pBuffer)
160{
161 m_pProcessor->RunBuffer(pBuffer);
162}
163
164bool CGraphicsBackend_Threaded::IsIdle() const
165{
166#if defined(CONF_PLATFORM_EMSCRIPTEN)
167 return true;
168#else
169 return !m_BufferInProcess.load(m: std::memory_order_relaxed);
170#endif
171}
172
173void CGraphicsBackend_Threaded::WaitForIdle()
174{
175#if !defined(CONF_PLATFORM_EMSCRIPTEN)
176 std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
177 m_BufferSwapCond.wait(lock&: Lock, p: [this]() { return m_pBuffer == nullptr; });
178#endif
179}
180
181void CGraphicsBackend_Threaded::ProcessError(const SGfxErrorContainer &Error)
182{
183 std::string VerboseStr = "Graphics Assertion:";
184 for(const auto &ErrStr : Error.m_vErrors)
185 {
186 VerboseStr.append(s: "\n");
187 if(ErrStr.m_RequiresTranslation)
188 VerboseStr.append(s: m_TranslateFunc(ErrStr.m_Err.c_str(), ""));
189 else
190 VerboseStr.append(str: ErrStr.m_Err);
191 }
192 dbg_assert_failed("%s", VerboseStr.c_str());
193}
194
195bool CGraphicsBackend_Threaded::GetWarning(std::vector<std::string> &WarningStrings)
196{
197 if(m_Warning.m_WarningType != GFX_WARNING_TYPE_NONE)
198 {
199 m_Warning.m_WarningType = GFX_WARNING_TYPE_NONE;
200 WarningStrings = m_Warning.m_vWarnings;
201 return true;
202 }
203 return false;
204}
205
206// ------------ CCommandProcessorFragment_General
207
208void CCommandProcessorFragment_General::Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand)
209{
210 pCommand->m_pSemaphore->Signal();
211}
212
213bool CCommandProcessorFragment_General::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
214{
215 switch(pBaseCommand->m_Cmd)
216 {
217 case CCommandBuffer::CMD_SIGNAL: Cmd_Signal(pCommand: static_cast<const CCommandBuffer::SCommand_Signal *>(pBaseCommand)); break;
218 default: return false;
219 }
220
221 return true;
222}
223
224// ------------ CCommandProcessorFragment_SDL
225void CCommandProcessorFragment_SDL::Cmd_Init(const SCommand_Init *pCommand)
226{
227 m_GLContext = pCommand->m_GLContext;
228 m_pWindow = pCommand->m_pWindow;
229 if(m_GLContext)
230 SDL_GL_MakeCurrent(window: m_pWindow, context: m_GLContext);
231}
232
233void CCommandProcessorFragment_SDL::Cmd_Shutdown(const SCommand_Shutdown *pCommand)
234{
235 if(m_GLContext)
236 SDL_GL_MakeCurrent(window: nullptr, context: nullptr);
237}
238
239void CCommandProcessorFragment_SDL::Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand)
240{
241 if(m_GLContext)
242 SDL_GL_SwapWindow(window: m_pWindow);
243}
244
245void CCommandProcessorFragment_SDL::Cmd_VSync(const CCommandBuffer::SCommand_VSync *pCommand)
246{
247 if(m_GLContext)
248 {
249#if defined(CONF_PLATFORM_EMSCRIPTEN)
250 // SDL_GL_SetSwapInterval is not supported with Emscripten as this is only a wrapper for the
251 // emscripten_set_main_loop_timing function which does not work because we do not use the
252 // emscripten_set_main_loop function before.
253 *pCommand->m_pRetOk = !pCommand->m_VSync;
254#else
255 *pCommand->m_pRetOk = SDL_GL_SetSwapInterval(interval: pCommand->m_VSync) == 0;
256#endif
257 }
258}
259
260void CCommandProcessorFragment_SDL::Cmd_WindowCreateNtf(const CCommandBuffer::SCommand_WindowCreateNtf *pCommand)
261{
262 m_pWindow = SDL_GetWindowFromID(id: pCommand->m_WindowId);
263 // Android destroys windows when they are not visible, so we get the new one and work with that
264 // The graphic context does not need to be recreated, just unbound see @see SCommand_WindowDestroyNtf
265#ifdef CONF_PLATFORM_ANDROID
266 if(m_GLContext)
267 SDL_GL_MakeCurrent(m_pWindow, m_GLContext);
268#endif
269}
270
271void CCommandProcessorFragment_SDL::Cmd_WindowDestroyNtf(const CCommandBuffer::SCommand_WindowDestroyNtf *pCommand)
272{
273 // Unbind the graphic context from the window, so it does not get destroyed
274#ifdef CONF_PLATFORM_ANDROID
275 if(m_GLContext)
276 SDL_GL_MakeCurrent(NULL, NULL);
277#endif
278}
279
280CCommandProcessorFragment_SDL::CCommandProcessorFragment_SDL() = default;
281
282bool CCommandProcessorFragment_SDL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
283{
284 switch(pBaseCommand->m_Cmd)
285 {
286 case CCommandBuffer::CMD_WINDOW_CREATE_NTF: Cmd_WindowCreateNtf(pCommand: static_cast<const CCommandBuffer::SCommand_WindowCreateNtf *>(pBaseCommand)); break;
287 case CCommandBuffer::CMD_WINDOW_DESTROY_NTF: Cmd_WindowDestroyNtf(pCommand: static_cast<const CCommandBuffer::SCommand_WindowDestroyNtf *>(pBaseCommand)); break;
288 case CCommandBuffer::CMD_SWAP: Cmd_Swap(pCommand: static_cast<const CCommandBuffer::SCommand_Swap *>(pBaseCommand)); break;
289 case CCommandBuffer::CMD_VSYNC: Cmd_VSync(pCommand: static_cast<const CCommandBuffer::SCommand_VSync *>(pBaseCommand)); break;
290 case CCommandBuffer::CMD_MULTISAMPLING: break;
291 case CMD_INIT: Cmd_Init(pCommand: static_cast<const SCommand_Init *>(pBaseCommand)); break;
292 case CMD_SHUTDOWN: Cmd_Shutdown(pCommand: static_cast<const SCommand_Shutdown *>(pBaseCommand)); break;
293 case CCommandProcessorFragment_GLBase::CMD_PRE_INIT: break;
294 case CCommandProcessorFragment_GLBase::CMD_POST_SHUTDOWN: break;
295 default: return false;
296 }
297
298 return true;
299}
300
301// ------------ CCommandProcessor_SDL_GL
302
303void CCommandProcessor_SDL_GL::HandleError()
304{
305 switch(m_Error.m_ErrorType)
306 {
307 case GFX_ERROR_TYPE_INIT:
308 m_Error.m_vErrors.emplace_back(args: SGfxErrorContainer::SError{.m_RequiresTranslation: true, .m_Err: Localizable(pStr: "Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", pContext: "Graphics error")});
309 break;
310 case GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE:
311 [[fallthrough]];
312 case GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER:
313 [[fallthrough]];
314 case GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING:
315 m_Error.m_vErrors.emplace_back(args: SGfxErrorContainer::SError{.m_RequiresTranslation: true, .m_Err: Localizable(pStr: "Out of VRAM. Try setting 'cl_skins_loaded_max' to a lower value or remove custom assets (skins, entities, etc.), especially those with high resolution.", pContext: "Graphics error")});
316 break;
317 case GFX_ERROR_TYPE_RENDER_RECORDING:
318 m_Error.m_vErrors.emplace_back(args: SGfxErrorContainer::SError{.m_RequiresTranslation: true, .m_Err: Localizable(pStr: "An error during command recording occurred. Try to update your GPU drivers.", pContext: "Graphics error")});
319 break;
320 case GFX_ERROR_TYPE_RENDER_CMD_FAILED:
321 m_Error.m_vErrors.emplace_back(args: SGfxErrorContainer::SError{.m_RequiresTranslation: true, .m_Err: Localizable(pStr: "A render command failed. Try to update your GPU drivers.", pContext: "Graphics error")});
322 break;
323 case GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED:
324 m_Error.m_vErrors.emplace_back(args: SGfxErrorContainer::SError{.m_RequiresTranslation: true, .m_Err: Localizable(pStr: "Submitting the render commands failed. Try to update your GPU drivers.", pContext: "Graphics error")});
325 break;
326 case GFX_ERROR_TYPE_SWAP_FAILED:
327 m_Error.m_vErrors.emplace_back(args: SGfxErrorContainer::SError{.m_RequiresTranslation: true, .m_Err: Localizable(pStr: "Failed to swap framebuffers. Try to update your GPU drivers.", pContext: "Graphics error")});
328 break;
329 case GFX_ERROR_TYPE_UNKNOWN:
330 [[fallthrough]];
331 default:
332 m_Error.m_vErrors.emplace_back(args: SGfxErrorContainer::SError{.m_RequiresTranslation: true, .m_Err: Localizable(pStr: "Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again.", pContext: "Graphics error")});
333 break;
334 }
335}
336
337void CCommandProcessor_SDL_GL::HandleWarning()
338{
339 switch(m_Warning.m_WarningType)
340 {
341 case GFX_WARNING_TYPE_INIT_FAILED:
342 m_Warning.m_vWarnings.emplace_back(args: Localizable(pStr: "Could not initialize the given graphics backend, reverting to the default backend now.", pContext: "Graphics error"));
343 break;
344 case GFX_WARNING_TYPE_INIT_FAILED_MISSING_INTEGRATED_GPU_DRIVER:
345 m_Warning.m_vWarnings.emplace_back(args: Localizable(pStr: "Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card.", pContext: "Graphics error"));
346 break;
347 case GFX_WARNING_MISSING_EXTENSION:
348 // ignore this warning for now
349 return;
350 case GFX_WARNING_LOW_ON_MEMORY:
351 // ignore this warning for now
352 return;
353 case GFX_WARNING_TYPE_INIT_FAILED_NO_DEVICE_WITH_REQUIRED_VERSION:
354 {
355 // Ignore this warning for now completely.
356 // A console message was already printed by the backend
357 m_Warning.m_WarningType = GFX_WARNING_TYPE_NONE;
358 m_Warning.m_vWarnings.clear();
359 return;
360 }
361 default:
362 dbg_assert_failed("Unhandled graphics warning type %d", (int)m_Warning.m_WarningType);
363 }
364}
365
366void CCommandProcessor_SDL_GL::RunBuffer(CCommandBuffer *pBuffer)
367{
368 m_pGLBackend->StartCommands(CommandCount: pBuffer->m_CommandCount, EstimatedRenderCallCount: pBuffer->m_RenderCallCount);
369
370 for(const CCommandBuffer::SCommand *pCommand = pBuffer->Head(); pCommand; pCommand = pCommand->m_pNext)
371 {
372 auto Res = m_pGLBackend->RunCommand(pBaseCommand: pCommand);
373 if(Res == ERunCommandReturnTypes::RUN_COMMAND_COMMAND_HANDLED)
374 {
375 continue;
376 }
377 else if(Res == ERunCommandReturnTypes::RUN_COMMAND_COMMAND_ERROR)
378 {
379 m_Error = m_pGLBackend->GetError();
380 HandleError();
381 return;
382 }
383 else if(Res == ERunCommandReturnTypes::RUN_COMMAND_COMMAND_WARNING)
384 {
385 m_Warning = m_pGLBackend->GetWarning();
386 HandleWarning();
387 return;
388 }
389
390 if(m_SDL.RunCommand(pBaseCommand: pCommand))
391 continue;
392
393 if(m_General.RunCommand(pBaseCommand: pCommand))
394 continue;
395
396 dbg_assert_failed("Unknown graphics command %d", pCommand->m_Cmd);
397 }
398
399 m_pGLBackend->EndCommands();
400}
401
402CCommandProcessor_SDL_GL::CCommandProcessor_SDL_GL(EBackendType BackendType, int GLMajor, int GLMinor, int GLPatch)
403{
404 m_BackendType = BackendType;
405
406#if defined(CONF_HEADLESS_CLIENT)
407 m_pGLBackend = new CCommandProcessorFragment_Null();
408#else
409 if(BackendType == BACKEND_TYPE_OPENGL_ES)
410 {
411#if defined(CONF_BACKEND_OPENGL_ES) || defined(CONF_BACKEND_OPENGL_ES3)
412 if(GLMajor < 3)
413 {
414 m_pGLBackend = new CCommandProcessorFragment_OpenGLES();
415 }
416 else
417 {
418 m_pGLBackend = new CCommandProcessorFragment_OpenGLES3();
419 }
420#endif
421 }
422 else if(BackendType == BACKEND_TYPE_OPENGL)
423 {
424#if !defined(CONF_BACKEND_OPENGL_ES)
425 if(GLMajor < 2)
426 {
427 m_pGLBackend = new CCommandProcessorFragment_OpenGL();
428 }
429 if(GLMajor == 2)
430 {
431 m_pGLBackend = new CCommandProcessorFragment_OpenGL2();
432 }
433 if(GLMajor == 3 && GLMinor == 0)
434 {
435 m_pGLBackend = new CCommandProcessorFragment_OpenGL3();
436 }
437 else if((GLMajor == 3 && GLMinor == 3) || GLMajor >= 4)
438 {
439 m_pGLBackend = new CCommandProcessorFragment_OpenGL3_3();
440 }
441#endif
442 }
443 else if(BackendType == BACKEND_TYPE_VULKAN)
444 {
445#if defined(CONF_BACKEND_VULKAN)
446 m_pGLBackend = CreateVulkanCommandProcessorFragment();
447#endif
448 }
449#endif
450}
451
452CCommandProcessor_SDL_GL::~CCommandProcessor_SDL_GL()
453{
454 delete m_pGLBackend;
455}
456
457const SGfxErrorContainer &CCommandProcessor_SDL_GL::GetError() const
458{
459 return m_Error;
460}
461
462void CCommandProcessor_SDL_GL::ErroneousCleanup()
463{
464 m_pGLBackend->ErroneousCleanup();
465}
466
467const SGfxWarningContainer &CCommandProcessor_SDL_GL::GetWarning() const
468{
469 return m_Warning;
470}
471
472// ------------ CGraphicsBackend_SDL_GL
473
474#if !defined(CONF_HEADLESS_CLIENT)
475static bool BackendInitGlew(EBackendType BackendType, int &GlewMajor, int &GlewMinor, int &GlewPatch)
476{
477 if(BackendType == BACKEND_TYPE_OPENGL)
478 {
479#if !defined(CONF_BACKEND_OPENGL_ES)
480 // Support graphic cards that are pretty old (and Linux)
481 glewExperimental = GL_TRUE;
482#ifdef CONF_GLEW_HAS_CONTEXT_INIT
483 const GLenum InitResult = glewContextInit();
484 if(InitResult != GLEW_OK)
485 {
486 log_error("gfx", "Unable to init glew (glewContextInit): %s", glewGetErrorString(InitResult));
487 return false;
488 }
489#else
490 const GLenum InitResult = glewInit();
491 if(InitResult != GLEW_OK)
492 {
493 // With wayland the glewInit function is allowed to fail with GLEW_ERROR_NO_GLX_DISPLAY,
494 // as it will already have initialized the context with glewContextInit internally.
495 const char *pVideoDriver = SDL_GetCurrentVideoDriver();
496 if(pVideoDriver == nullptr || str_comp(a: pVideoDriver, b: "wayland") != 0 || InitResult != GLEW_ERROR_NO_GLX_DISPLAY)
497 {
498 log_error("gfx", "Unable to init glew (glewInit): %s", glewGetErrorString(InitResult));
499 return false;
500 }
501 }
502#endif
503
504#ifdef GLEW_VERSION_4_6
505 if(GLEW_VERSION_4_6)
506 {
507 GlewMajor = 4;
508 GlewMinor = 6;
509 GlewPatch = 0;
510 return true;
511 }
512#endif
513#ifdef GLEW_VERSION_4_5
514 if(GLEW_VERSION_4_5)
515 {
516 GlewMajor = 4;
517 GlewMinor = 5;
518 GlewPatch = 0;
519 return true;
520 }
521#endif
522// Don't allow GL 3.3, if the driver doesn't support at least OpenGL 4.5
523#ifndef CONF_FAMILY_WINDOWS
524 if(GLEW_VERSION_4_4)
525 {
526 GlewMajor = 4;
527 GlewMinor = 4;
528 GlewPatch = 0;
529 return true;
530 }
531 if(GLEW_VERSION_4_3)
532 {
533 GlewMajor = 4;
534 GlewMinor = 3;
535 GlewPatch = 0;
536 return true;
537 }
538 if(GLEW_VERSION_4_2)
539 {
540 GlewMajor = 4;
541 GlewMinor = 2;
542 GlewPatch = 0;
543 return true;
544 }
545 if(GLEW_VERSION_4_1)
546 {
547 GlewMajor = 4;
548 GlewMinor = 1;
549 GlewPatch = 0;
550 return true;
551 }
552 if(GLEW_VERSION_4_0)
553 {
554 GlewMajor = 4;
555 GlewMinor = 0;
556 GlewPatch = 0;
557 return true;
558 }
559 if(GLEW_VERSION_3_3)
560 {
561 GlewMajor = 3;
562 GlewMinor = 3;
563 GlewPatch = 0;
564 return true;
565 }
566#endif
567 if(GLEW_VERSION_3_0)
568 {
569 GlewMajor = 3;
570 GlewMinor = 0;
571 GlewPatch = 0;
572 return true;
573 }
574 if(GLEW_VERSION_2_1)
575 {
576 GlewMajor = 2;
577 GlewMinor = 1;
578 GlewPatch = 0;
579 return true;
580 }
581 if(GLEW_VERSION_2_0)
582 {
583 GlewMajor = 2;
584 GlewMinor = 0;
585 GlewPatch = 0;
586 return true;
587 }
588 if(GLEW_VERSION_1_5)
589 {
590 GlewMajor = 1;
591 GlewMinor = 5;
592 GlewPatch = 0;
593 return true;
594 }
595 if(GLEW_VERSION_1_4)
596 {
597 GlewMajor = 1;
598 GlewMinor = 4;
599 GlewPatch = 0;
600 return true;
601 }
602 if(GLEW_VERSION_1_3)
603 {
604 GlewMajor = 1;
605 GlewMinor = 3;
606 GlewPatch = 0;
607 return true;
608 }
609 if(GLEW_VERSION_1_2_1)
610 {
611 GlewMajor = 1;
612 GlewMinor = 2;
613 GlewPatch = 1;
614 return true;
615 }
616 if(GLEW_VERSION_1_2)
617 {
618 GlewMajor = 1;
619 GlewMinor = 2;
620 GlewPatch = 0;
621 return true;
622 }
623 if(GLEW_VERSION_1_1)
624 {
625 GlewMajor = 1;
626 GlewMinor = 1;
627 GlewPatch = 0;
628 return true;
629 }
630#endif
631 }
632 else if(BackendType == BACKEND_TYPE_OPENGL_ES)
633 {
634 // just assume the version we need
635 GlewMajor = 3;
636 GlewMinor = 0;
637 GlewPatch = 0;
638 return true;
639 }
640 else
641 {
642 dbg_assert_failed("Invalid backend type for glew: %d", (int)BackendType);
643 }
644
645 return false;
646}
647
648static int IsVersionSupportedGlew(EBackendType BackendType, int VersionMajor, int VersionMinor, int VersionPatch, int GlewMajor, int GlewMinor, int GlewPatch)
649{
650 if(BackendType == BACKEND_TYPE_OPENGL)
651 {
652 if(VersionMajor >= 4 && GlewMajor < 4)
653 {
654 return -1;
655 }
656 else if(VersionMajor >= 3 && GlewMajor < 3)
657 {
658 return -1;
659 }
660 else if(VersionMajor == 3 && GlewMajor == 3)
661 {
662 if(VersionMinor >= 3 && GlewMinor < 3)
663 {
664 return -1;
665 }
666 if(VersionMinor >= 2 && GlewMinor < 2)
667 {
668 return -1;
669 }
670 if(VersionMinor >= 1 && GlewMinor < 1)
671 {
672 return -1;
673 }
674 if(VersionMinor >= 0 && GlewMinor < 0)
675 {
676 return -1;
677 }
678 }
679 else if(VersionMajor >= 2 && GlewMajor < 2)
680 {
681 return -1;
682 }
683 else if(VersionMajor == 2 && GlewMajor == 2)
684 {
685 if(VersionMinor >= 1 && GlewMinor < 1)
686 {
687 return -1;
688 }
689 if(VersionMinor >= 0 && GlewMinor < 0)
690 {
691 return -1;
692 }
693 }
694 else if(VersionMajor >= 1 && GlewMajor < 1)
695 {
696 return -1;
697 }
698 else if(VersionMajor == 1 && GlewMajor == 1)
699 {
700 if(VersionMinor >= 5 && GlewMinor < 5)
701 {
702 return -1;
703 }
704 if(VersionMinor >= 4 && GlewMinor < 4)
705 {
706 return -1;
707 }
708 if(VersionMinor >= 3 && GlewMinor < 3)
709 {
710 return -1;
711 }
712 if(VersionMinor >= 2 && GlewMinor < 2)
713 {
714 return -1;
715 }
716 else if(VersionMinor == 2 && GlewMinor == 2)
717 {
718 if(VersionPatch >= 1 && GlewPatch < 1)
719 {
720 return -1;
721 }
722 if(VersionPatch >= 0 && GlewPatch < 0)
723 {
724 return -1;
725 }
726 }
727 if(VersionMinor >= 1 && GlewMinor < 1)
728 {
729 return -1;
730 }
731 if(VersionMinor >= 0 && GlewMinor < 0)
732 {
733 return -1;
734 }
735 }
736 }
737 return 0;
738}
739#endif // !CONF_HEADLESS_CLIENT
740
741EBackendType CGraphicsBackend_SDL_GL::DetectBackend()
742{
743 EBackendType RetBackendType = BACKEND_TYPE_OPENGL;
744#if defined(CONF_BACKEND_VULKAN)
745 const char *pEnvDriver = SDL_getenv(name: "DDNET_DRIVER");
746 if(pEnvDriver && str_comp_nocase(a: pEnvDriver, b: "GLES") == 0)
747 RetBackendType = BACKEND_TYPE_OPENGL_ES;
748 else if(pEnvDriver && str_comp_nocase(a: pEnvDriver, b: "Vulkan") == 0)
749 RetBackendType = BACKEND_TYPE_VULKAN;
750 else if(pEnvDriver && str_comp_nocase(a: pEnvDriver, b: "OpenGL") == 0)
751 RetBackendType = BACKEND_TYPE_OPENGL;
752 else if(pEnvDriver == nullptr)
753 {
754 // load the config backend
755 const char *pConfBackend = g_Config.m_GfxBackend;
756 if(str_comp_nocase(a: pConfBackend, b: "GLES") == 0)
757 RetBackendType = BACKEND_TYPE_OPENGL_ES;
758 else if(str_comp_nocase(a: pConfBackend, b: "Vulkan") == 0)
759 RetBackendType = BACKEND_TYPE_VULKAN;
760 else if(str_comp_nocase(a: pConfBackend, b: "OpenGL") == 0)
761 RetBackendType = BACKEND_TYPE_OPENGL;
762 }
763#else
764 RetBackendType = BACKEND_TYPE_OPENGL;
765#endif
766#if !defined(CONF_BACKEND_OPENGL_ES) && !defined(CONF_BACKEND_OPENGL_ES3)
767 if(RetBackendType == BACKEND_TYPE_OPENGL_ES)
768 RetBackendType = BACKEND_TYPE_OPENGL;
769#elif defined(CONF_BACKEND_OPENGL_ES)
770 if(RetBackendType == BACKEND_TYPE_OPENGL)
771 RetBackendType = BACKEND_TYPE_OPENGL_ES;
772#endif
773 return RetBackendType;
774}
775
776void CGraphicsBackend_SDL_GL::ClampDriverVersion(EBackendType BackendType)
777{
778 if(BackendType == BACKEND_TYPE_OPENGL)
779 {
780 // clamp the versions to existing versions(only for OpenGL major <= 3)
781 if(g_Config.m_GfxGLMajor == 1)
782 {
783 g_Config.m_GfxGLMinor = std::clamp(val: g_Config.m_GfxGLMinor, lo: 1, hi: 5);
784 if(g_Config.m_GfxGLMinor == 2)
785 g_Config.m_GfxGLPatch = std::clamp(val: g_Config.m_GfxGLPatch, lo: 0, hi: 1);
786 else
787 g_Config.m_GfxGLPatch = 0;
788 }
789 else if(g_Config.m_GfxGLMajor == 2)
790 {
791 g_Config.m_GfxGLMinor = std::clamp(val: g_Config.m_GfxGLMinor, lo: 0, hi: 1);
792 g_Config.m_GfxGLPatch = 0;
793 }
794 else if(g_Config.m_GfxGLMajor == 3)
795 {
796 g_Config.m_GfxGLMinor = std::clamp(val: g_Config.m_GfxGLMinor, lo: 0, hi: 3);
797 if(g_Config.m_GfxGLMinor < 3)
798 g_Config.m_GfxGLMinor = 0;
799 g_Config.m_GfxGLPatch = 0;
800 }
801 }
802 else if(BackendType == BACKEND_TYPE_OPENGL_ES)
803 {
804#if !defined(CONF_BACKEND_OPENGL_ES3)
805 // Make sure GLES is set to 1.0 (which is equivalent to OpenGL 1.3), if its not set to >= 3.0(which is equivalent to OpenGL 3.3)
806 if(g_Config.m_GfxGLMajor < 3)
807 {
808 g_Config.m_GfxGLMajor = 1;
809 g_Config.m_GfxGLMinor = 0;
810 g_Config.m_GfxGLPatch = 0;
811
812 // GLES also doesn't know GL_QUAD
813 g_Config.m_GfxQuadAsTriangle = 1;
814 }
815#else
816 g_Config.m_GfxGLMajor = 3;
817 g_Config.m_GfxGLMinor = 0;
818 g_Config.m_GfxGLPatch = 0;
819#endif
820 }
821 else if(BackendType == BACKEND_TYPE_VULKAN)
822 {
823#if defined(CONF_BACKEND_VULKAN)
824 g_Config.m_GfxGLMajor = BACKEND_VULKAN_VERSION_MAJOR;
825 g_Config.m_GfxGLMinor = BACKEND_VULKAN_VERSION_MINOR;
826 g_Config.m_GfxGLPatch = 0;
827#endif
828 }
829}
830
831static Uint32 MessageBoxTypeToSdlFlags(IGraphics::EMessageBoxType Type)
832{
833 switch(Type)
834 {
835 case IGraphics::EMessageBoxType::ERROR:
836 return SDL_MESSAGEBOX_ERROR;
837 case IGraphics::EMessageBoxType::WARNING:
838 return SDL_MESSAGEBOX_WARNING;
839 case IGraphics::EMessageBoxType::INFO:
840 return SDL_MESSAGEBOX_INFORMATION;
841 default:
842 dbg_assert_failed("Type invalid");
843 }
844}
845
846static std::optional<int> ShowMessageBoxImpl(const IGraphics::CMessageBox &MessageBox, SDL_Window *pWindow)
847{
848 dbg_assert(!MessageBox.m_vButtons.empty(), "At least one button is required");
849
850 std::vector<SDL_MessageBoxButtonData> vButtonData;
851 vButtonData.reserve(n: MessageBox.m_vButtons.size());
852 for(const auto &Button : MessageBox.m_vButtons)
853 {
854 SDL_MessageBoxButtonData ButtonData{};
855 ButtonData.buttonid = vButtonData.size();
856 ButtonData.flags = (Button.m_Confirm ? SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT : 0) | (Button.m_Cancel ? SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT : 0);
857 ButtonData.text = Button.m_pLabel;
858 vButtonData.emplace_back(args&: ButtonData);
859 }
860#if defined(CONF_FAMILY_WINDOWS)
861 // TODO SDL3: The order of buttons is not defined by default, but the flags returned by MessageBoxTypeToSdlFlags do not work together
862 // with SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT with SDL2 on various platforms. Windows appears to be the only platform that
863 // lays out buttons from right to left by default, so we reverse the order manually.
864 std::reverse(vButtonData.begin(), vButtonData.end());
865#endif
866 SDL_MessageBoxData MessageBoxData{};
867 MessageBoxData.title = MessageBox.m_pTitle;
868 MessageBoxData.message = MessageBox.m_pMessage;
869 MessageBoxData.flags = MessageBoxTypeToSdlFlags(Type: MessageBox.m_Type);
870 MessageBoxData.numbuttons = vButtonData.size();
871 MessageBoxData.buttons = vButtonData.data();
872 MessageBoxData.window = pWindow;
873 int ButtonId = -1;
874 if(SDL_ShowMessageBox(messageboxdata: &MessageBoxData, buttonid: &ButtonId) != 0)
875 {
876 return std::nullopt;
877 }
878 return ButtonId;
879}
880
881std::optional<int> ShowMessageBoxWithoutGraphics(const IGraphics::CMessageBox &MessageBox)
882{
883 return ShowMessageBoxImpl(MessageBox, pWindow: nullptr);
884}
885
886std::optional<int> CGraphicsBackend_SDL_GL::ShowMessageBox(const IGraphics::CMessageBox &MessageBox)
887{
888 if(m_pProcessor != nullptr)
889 {
890 m_pProcessor->ErroneousCleanup();
891 }
892 // TODO: Remove this workaround when https://github.com/libsdl-org/SDL/issues/3750 is
893 // fixed and pass the window to SDL_ShowSimpleMessageBox to make the popup modal instead
894 // of destroying the window before opening the popup.
895 if(m_pWindow != nullptr)
896 {
897 SDL_DestroyWindow(window: m_pWindow);
898 m_pWindow = nullptr;
899 }
900 return ShowMessageBoxImpl(MessageBox, pWindow: m_pWindow);
901}
902
903bool CGraphicsBackend_SDL_GL::IsModernAPI(EBackendType BackendType)
904{
905 if(BackendType == BACKEND_TYPE_OPENGL)
906 return (g_Config.m_GfxGLMajor == 3 && g_Config.m_GfxGLMinor == 3) || g_Config.m_GfxGLMajor >= 4;
907 else if(BackendType == BACKEND_TYPE_OPENGL_ES)
908 return g_Config.m_GfxGLMajor >= 3;
909 else if(BackendType == BACKEND_TYPE_VULKAN)
910 return true;
911
912 return false;
913}
914
915bool CGraphicsBackend_SDL_GL::GetDriverVersion(EGraphicsDriverAgeType DriverAgeType, int &Major, int &Minor, int &Patch, const char *&pName, EBackendType BackendType)
916{
917 if(BackendType == BACKEND_TYPE_AUTO)
918 BackendType = m_BackendType;
919 if(BackendType == BACKEND_TYPE_OPENGL)
920 {
921 pName = "OpenGL";
922#ifndef CONF_BACKEND_OPENGL_ES
923 if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_LEGACY)
924 {
925 Major = 1;
926 Minor = 4;
927 Patch = 0;
928 return true;
929 }
930 else if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_DEFAULT)
931 {
932 Major = 3;
933 Minor = 0;
934 Patch = 0;
935 return true;
936 }
937 else if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_MODERN)
938 {
939 Major = 3;
940 Minor = 3;
941 Patch = 0;
942 return true;
943 }
944#endif
945 }
946 else if(BackendType == BACKEND_TYPE_OPENGL_ES)
947 {
948 pName = "GLES";
949#ifdef CONF_BACKEND_OPENGL_ES
950 if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_LEGACY)
951 {
952 Major = 1;
953 Minor = 0;
954 Patch = 0;
955 return true;
956 }
957 else if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_DEFAULT)
958 {
959 Major = 3;
960 Minor = 0;
961 Patch = 0;
962 // there isn't really a default one
963 return false;
964 }
965#endif
966#ifdef CONF_BACKEND_OPENGL_ES3
967 if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_MODERN)
968 {
969 Major = 3;
970 Minor = 0;
971 Patch = 0;
972 return true;
973 }
974#endif
975 }
976 else if(BackendType == BACKEND_TYPE_VULKAN)
977 {
978 pName = "Vulkan";
979#ifdef CONF_BACKEND_VULKAN
980 if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_DEFAULT)
981 {
982 Major = BACKEND_VULKAN_VERSION_MAJOR;
983 Minor = BACKEND_VULKAN_VERSION_MINOR;
984 Patch = 0;
985 return true;
986 }
987#else
988 return false;
989#endif
990 }
991 return false;
992}
993
994const char *CGraphicsBackend_SDL_GL::GetScreenName(int Screen) const
995{
996 const char *pName = SDL_GetDisplayName(displayIndex: Screen);
997 return pName == nullptr ? "unknown/error" : pName;
998}
999
1000static void DisplayToVideoMode(CVideoMode *pVMode, SDL_DisplayMode *pMode, float HiDPIScale, int RefreshRate)
1001{
1002 pVMode->m_CanvasWidth = pMode->w * HiDPIScale;
1003 pVMode->m_CanvasHeight = pMode->h * HiDPIScale;
1004 pVMode->m_WindowWidth = pMode->w;
1005 pVMode->m_WindowHeight = pMode->h;
1006 pVMode->m_RefreshRate = RefreshRate;
1007 pVMode->m_Red = SDL_BITSPERPIXEL(pMode->format);
1008 pVMode->m_Green = SDL_BITSPERPIXEL(pMode->format);
1009 pVMode->m_Blue = SDL_BITSPERPIXEL(pMode->format);
1010 pVMode->m_Format = pMode->format;
1011}
1012
1013void CGraphicsBackend_SDL_GL::GetVideoModes(CVideoMode *pModes, int MaxModes, int *pNumModes, float HiDPIScale, int MaxWindowWidth, int MaxWindowHeight, int ScreenId)
1014{
1015 SDL_DisplayMode DesktopMode;
1016 int MaxModesAvailable = SDL_GetNumDisplayModes(displayIndex: ScreenId);
1017
1018 // Only collect fullscreen modes when requested, that makes sure in windowed mode no refresh rates are shown that aren't supported without
1019 // fullscreen anyway(except fullscreen desktop)
1020 bool IsFullscreenDesktop = m_pWindow != nullptr && (((SDL_GetWindowFlags(window: m_pWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) || g_Config.m_GfxFullscreen == 3);
1021 bool CollectFullscreenModes = m_pWindow == nullptr || ((SDL_GetWindowFlags(window: m_pWindow) & SDL_WINDOW_FULLSCREEN) != 0 && !IsFullscreenDesktop);
1022
1023 if(SDL_GetDesktopDisplayMode(displayIndex: ScreenId, mode: &DesktopMode) < 0)
1024 {
1025 log_error("gfx", "Unable to get desktop display mode of screen %d: %s", ScreenId, SDL_GetError());
1026 }
1027
1028 constexpr int ModeCount = 256;
1029 SDL_DisplayMode aModes[ModeCount];
1030 int NumModes = 0;
1031 for(int i = 0; i < MaxModesAvailable && NumModes < ModeCount; i++)
1032 {
1033 SDL_DisplayMode Mode;
1034 if(SDL_GetDisplayMode(displayIndex: ScreenId, modeIndex: i, mode: &Mode) < 0)
1035 {
1036 log_error("gfx", "Unable to get display mode %d of screen %d: %s", i, ScreenId, SDL_GetError());
1037 continue;
1038 }
1039
1040 aModes[NumModes] = Mode;
1041 ++NumModes;
1042 }
1043
1044 int NumModesInserted = 0;
1045 auto &&ModeInsert = [&](SDL_DisplayMode &Mode) {
1046 if(NumModesInserted < MaxModes)
1047 {
1048 // if last mode was equal, ignore this one --- in fullscreen this can really only happen if the screen
1049 // supports different color modes
1050 // in non fullscreen these are the modes that show different refresh rate, but are basically the same
1051 if(NumModesInserted > 0 && pModes[NumModesInserted - 1].m_WindowWidth == Mode.w && pModes[NumModesInserted - 1].m_WindowHeight == Mode.h && (pModes[NumModesInserted - 1].m_RefreshRate == Mode.refresh_rate || (Mode.refresh_rate != DesktopMode.refresh_rate && !CollectFullscreenModes)))
1052 return;
1053
1054 DisplayToVideoMode(pVMode: &pModes[NumModesInserted], pMode: &Mode, HiDPIScale, RefreshRate: !CollectFullscreenModes ? DesktopMode.refresh_rate : Mode.refresh_rate);
1055 NumModesInserted++;
1056 }
1057 };
1058
1059 for(int i = 0; i < NumModes; i++)
1060 {
1061 SDL_DisplayMode &Mode = aModes[i];
1062
1063 if(Mode.w > MaxWindowWidth || Mode.h > MaxWindowHeight)
1064 continue;
1065
1066 ModeInsert(Mode);
1067
1068 if(IsFullscreenDesktop)
1069 break;
1070
1071 if(NumModesInserted >= MaxModes)
1072 break;
1073 }
1074 *pNumModes = NumModesInserted;
1075}
1076
1077void CGraphicsBackend_SDL_GL::GetCurrentVideoMode(CVideoMode &CurMode, float HiDPIScale, int MaxWindowWidth, int MaxWindowHeight, int ScreenId)
1078{
1079 SDL_DisplayMode DpMode;
1080 // if "real" fullscreen, obtain the video mode for that
1081 if((SDL_GetWindowFlags(window: m_pWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN)
1082 {
1083 if(SDL_GetCurrentDisplayMode(displayIndex: ScreenId, mode: &DpMode))
1084 {
1085 log_error("gfx", "Unable to get current display mode of screen %d: %s", ScreenId, SDL_GetError());
1086 }
1087 }
1088 else
1089 {
1090 if(SDL_GetDesktopDisplayMode(displayIndex: ScreenId, mode: &DpMode) < 0)
1091 {
1092 log_error("gfx", "Unable to get desktop display mode of screen %d: %s", ScreenId, SDL_GetError());
1093 }
1094 else
1095 {
1096 int Width = 0;
1097 int Height = 0;
1098 if(m_BackendType != EBackendType::BACKEND_TYPE_VULKAN)
1099 SDL_GL_GetDrawableSize(window: m_pWindow, w: &Width, h: &Height);
1100 else
1101 SDL_Vulkan_GetDrawableSize(window: m_pWindow, w: &Width, h: &Height);
1102 // SDL video modes are in screen space which are logical pixels
1103 DpMode.w = Width / HiDPIScale;
1104 DpMode.h = Height / HiDPIScale;
1105 }
1106 }
1107 DisplayToVideoMode(pVMode: &CurMode, pMode: &DpMode, HiDPIScale, RefreshRate: DpMode.refresh_rate);
1108}
1109
1110CGraphicsBackend_SDL_GL::CGraphicsBackend_SDL_GL(TTranslateFunc &&TranslateFunc) :
1111 CGraphicsBackend_Threaded(std::move(TranslateFunc))
1112{
1113 m_aErrorString[0] = '\0';
1114}
1115
1116int CGraphicsBackend_SDL_GL::Init(const char *pName, int *pScreen, int *pWidth, int *pHeight, int *pRefreshRate, int *pFsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int *pCurrentWidth, int *pCurrentHeight, IStorage *pStorage)
1117{
1118#if defined(CONF_HEADLESS_CLIENT)
1119 m_BackendType = BACKEND_TYPE_OPENGL;
1120 g_Config.m_GfxGLMajor = 0;
1121 g_Config.m_GfxGLMinor = 0;
1122 g_Config.m_GfxGLPatch = 0;
1123 int InitError = 0;
1124 int GlewMajor = 0;
1125 int GlewMinor = 0;
1126 int GlewPatch = 0;
1127 *pScreen = 0;
1128 *pWidth = *pDesktopWidth = *pCurrentWidth = 800;
1129 *pHeight = *pDesktopHeight = *pCurrentHeight = 600;
1130 *pRefreshRate = 60;
1131 *pFsaaSamples = 0;
1132 log_info("gfx", "Created headless context");
1133#else
1134 // print sdl version
1135 {
1136 SDL_version Compiled;
1137 SDL_version Linked;
1138
1139 SDL_VERSION(&Compiled);
1140 SDL_GetVersion(ver: &Linked);
1141 log_info("sdl", "SDL version %d.%d.%d (compiled = %d.%d.%d)",
1142 Linked.major, Linked.minor, Linked.patch,
1143 Compiled.major, Compiled.minor, Compiled.patch);
1144
1145#if CONF_PLATFORM_LINUX && SDL_VERSION_ATLEAST(2, 0, 22)
1146 // needed to workaround SDL from forcing exclusively X11 if linking against the GLX flavour of GLEW instead of the EGL one
1147 // w/o this on Wayland systems (no XWayland support) SDL's Video subsystem will fail to load (starting from SDL2.30+)
1148 if(Linked.major == 2 && Linked.minor >= 30)
1149 SDL_SetHint(SDL_HINT_VIDEODRIVER, value: "x11,wayland");
1150#endif
1151 }
1152
1153 if(!SDL_WasInit(SDL_INIT_VIDEO))
1154 {
1155 if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
1156 {
1157 log_error("gfx", "Unable to initialize SDL video: %s", SDL_GetError());
1158 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_SDL_INIT_FAILED;
1159 }
1160 }
1161
1162 EBackendType OldBackendType = m_BackendType;
1163 m_BackendType = DetectBackend();
1164 // little fallback for Vulkan
1165 if(OldBackendType != BACKEND_TYPE_AUTO &&
1166 m_BackendType == BACKEND_TYPE_VULKAN)
1167 {
1168 // try default opengl settings
1169 str_copy(dst&: g_Config.m_GfxBackend, src: "OpenGL");
1170 g_Config.m_GfxGLMajor = 3;
1171 g_Config.m_GfxGLMinor = 0;
1172 g_Config.m_GfxGLPatch = 0;
1173 // do another analysis round too, just in case
1174 g_Config.m_Gfx3DTextureAnalysisRan = 0;
1175 g_Config.m_GfxDriverIsBlocked = 0;
1176 m_BackendType = DetectBackend();
1177 }
1178
1179 ClampDriverVersion(BackendType: m_BackendType);
1180
1181 const bool UseModernGL = IsModernAPI(BackendType: m_BackendType);
1182 const bool IsOpenGLFamilyBackend = m_BackendType == BACKEND_TYPE_OPENGL || m_BackendType == BACKEND_TYPE_OPENGL_ES;
1183
1184 if(IsOpenGLFamilyBackend)
1185 {
1186 SDL_GL_SetAttribute(attr: SDL_GL_CONTEXT_MAJOR_VERSION, value: g_Config.m_GfxGLMajor);
1187 SDL_GL_SetAttribute(attr: SDL_GL_CONTEXT_MINOR_VERSION, value: g_Config.m_GfxGLMinor);
1188 }
1189
1190 const char *pBackendName;
1191 switch(m_BackendType)
1192 {
1193 case BACKEND_TYPE_OPENGL:
1194 pBackendName = "OpenGL";
1195 break;
1196 case BACKEND_TYPE_OPENGL_ES:
1197 pBackendName = "OpenGL ES";
1198 break;
1199 case BACKEND_TYPE_VULKAN:
1200 pBackendName = "Vulkan";
1201 break;
1202 default:
1203 dbg_assert_failed("Invalid m_BackendType: %d", m_BackendType);
1204 }
1205 log_info("gfx", "Created %s %d.%d context", pBackendName, g_Config.m_GfxGLMajor, g_Config.m_GfxGLMinor);
1206
1207 if(m_BackendType == BACKEND_TYPE_OPENGL)
1208 {
1209 if(g_Config.m_GfxGLMajor == 3 && g_Config.m_GfxGLMinor == 0)
1210 {
1211 SDL_GL_SetAttribute(attr: SDL_GL_CONTEXT_PROFILE_MASK, value: SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
1212 }
1213 else if(UseModernGL)
1214 {
1215 SDL_GL_SetAttribute(attr: SDL_GL_CONTEXT_PROFILE_MASK, value: SDL_GL_CONTEXT_PROFILE_CORE);
1216 }
1217 }
1218 else if(m_BackendType == BACKEND_TYPE_OPENGL_ES)
1219 {
1220 SDL_GL_SetAttribute(attr: SDL_GL_CONTEXT_PROFILE_MASK, value: SDL_GL_CONTEXT_PROFILE_ES);
1221 }
1222
1223 if(IsOpenGLFamilyBackend)
1224 {
1225 *pFsaaSamples = std::clamp(val: *pFsaaSamples, lo: 0, hi: 8);
1226 }
1227
1228 // set screen
1229 m_NumScreens = SDL_GetNumVideoDisplays();
1230 if(m_NumScreens > 0)
1231 {
1232 SDL_Rect ScreenPos;
1233 *pScreen = std::clamp(val: *pScreen, lo: 0, hi: m_NumScreens - 1);
1234 if(SDL_GetDisplayBounds(displayIndex: *pScreen, rect: &ScreenPos) != 0)
1235 {
1236 log_error("gfx", "Unable to get display bounds of screen %d: %s", *pScreen, SDL_GetError());
1237 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_SDL_SCREEN_INFO_REQUEST_FAILED;
1238 }
1239 }
1240 else
1241 {
1242 log_error("gfx", "Unable to get number of screens: %s", SDL_GetError());
1243 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_SDL_SCREEN_REQUEST_FAILED;
1244 }
1245
1246 // store desktop resolution for settings reset button
1247 SDL_DisplayMode DisplayMode;
1248 if(SDL_GetDesktopDisplayMode(displayIndex: *pScreen, mode: &DisplayMode))
1249 {
1250 log_error("gfx", "Unable to get desktop display mode of screen %d: %s", *pScreen, SDL_GetError());
1251 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_SDL_SCREEN_RESOLUTION_REQUEST_FAILED;
1252 }
1253
1254 bool IsDesktopChanged = *pDesktopWidth == 0 || *pDesktopHeight == 0 || *pDesktopWidth != DisplayMode.w || *pDesktopHeight != DisplayMode.h;
1255
1256 *pDesktopWidth = DisplayMode.w;
1257 *pDesktopHeight = DisplayMode.h;
1258
1259 // fetch supported video modes
1260 bool SupportedResolution = false;
1261
1262 CVideoMode aModes[256];
1263 int ModesCount = 0;
1264 int IndexOfResolution = -1;
1265 GetVideoModes(pModes: aModes, MaxModes: std::size(aModes), pNumModes: &ModesCount, HiDPIScale: 1, MaxWindowWidth: *pDesktopWidth, MaxWindowHeight: *pDesktopHeight, ScreenId: *pScreen);
1266
1267 for(int i = 0; i < ModesCount; i++)
1268 {
1269 if(*pWidth == aModes[i].m_WindowWidth && *pHeight == aModes[i].m_WindowHeight && (*pRefreshRate == aModes[i].m_RefreshRate || *pRefreshRate == 0))
1270 {
1271 SupportedResolution = true;
1272 IndexOfResolution = i;
1273 break;
1274 }
1275 }
1276
1277 // set flags
1278 int SdlFlags = SDL_WINDOW_INPUT_GRABBED | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_ALLOW_HIGHDPI;
1279 SdlFlags |= (IsOpenGLFamilyBackend) ? SDL_WINDOW_OPENGL : SDL_WINDOW_VULKAN;
1280 if(Flags & IGraphicsBackend::INITFLAG_RESIZABLE)
1281 SdlFlags |= SDL_WINDOW_RESIZABLE;
1282 if(Flags & IGraphicsBackend::INITFLAG_BORDERLESS)
1283 SdlFlags |= SDL_WINDOW_BORDERLESS;
1284 if(Flags & IGraphicsBackend::INITFLAG_FULLSCREEN)
1285 SdlFlags |= SDL_WINDOW_FULLSCREEN;
1286 else if(Flags & (IGraphicsBackend::INITFLAG_DESKTOP_FULLSCREEN))
1287 SdlFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1288
1289 bool IsFullscreen = (SdlFlags & SDL_WINDOW_FULLSCREEN) != 0 || g_Config.m_GfxFullscreen == 3;
1290 // use desktop resolution as default resolution, clamp resolution if users's display is smaller than we remembered
1291 // if the user starts in fullscreen, and the resolution was not found use the desktop one
1292 if((IsFullscreen && !SupportedResolution) || *pWidth == 0 || *pHeight == 0 || (IsDesktopChanged && (!SupportedResolution || !IsFullscreen) && (*pWidth > *pDesktopWidth || *pHeight > *pDesktopHeight)))
1293 {
1294 *pWidth = *pDesktopWidth;
1295 *pHeight = *pDesktopHeight;
1296 *pRefreshRate = DisplayMode.refresh_rate;
1297 }
1298
1299 // if in fullscreen and refresh rate wasn't set yet, just use the one from the found list
1300 if(*pRefreshRate == 0 && SupportedResolution)
1301 {
1302 *pRefreshRate = aModes[IndexOfResolution].m_RefreshRate;
1303 }
1304 else if(*pRefreshRate == 0)
1305 {
1306 *pRefreshRate = DisplayMode.refresh_rate;
1307 }
1308
1309 // set gl attributes
1310 if(IsOpenGLFamilyBackend)
1311 {
1312 SDL_GL_SetAttribute(attr: SDL_GL_DOUBLEBUFFER, value: 1);
1313 if(*pFsaaSamples)
1314 {
1315 SDL_GL_SetAttribute(attr: SDL_GL_MULTISAMPLEBUFFERS, value: 1);
1316 SDL_GL_SetAttribute(attr: SDL_GL_MULTISAMPLESAMPLES, value: *pFsaaSamples);
1317 }
1318 else
1319 {
1320 SDL_GL_SetAttribute(attr: SDL_GL_MULTISAMPLEBUFFERS, value: 0);
1321 SDL_GL_SetAttribute(attr: SDL_GL_MULTISAMPLESAMPLES, value: 0);
1322 }
1323 }
1324
1325 m_pWindow = SDL_CreateWindow(
1326 title: pName,
1327 SDL_WINDOWPOS_CENTERED_DISPLAY(*pScreen),
1328 SDL_WINDOWPOS_CENTERED_DISPLAY(*pScreen),
1329 w: *pWidth,
1330 h: *pHeight,
1331 flags: SdlFlags);
1332
1333 // set caption
1334 if(m_pWindow == nullptr)
1335 {
1336 log_error("gfx", "Unable to create window: %s", SDL_GetError());
1337 if(m_BackendType == BACKEND_TYPE_VULKAN)
1338 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_GL_CONTEXT_FAILED;
1339 else
1340 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_SDL_WINDOW_CREATE_FAILED;
1341 }
1342
1343 int GlewMajor = 0;
1344 int GlewMinor = 0;
1345 int GlewPatch = 0;
1346
1347 if(IsOpenGLFamilyBackend)
1348 {
1349 m_GLContext = SDL_GL_CreateContext(window: m_pWindow);
1350
1351 if(m_GLContext == nullptr)
1352 {
1353 log_error("gfx", "Unable to create graphics context: %s", SDL_GetError());
1354 SDL_DestroyWindow(window: m_pWindow);
1355 m_pWindow = nullptr;
1356 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_GL_CONTEXT_FAILED;
1357 }
1358
1359 if(!BackendInitGlew(BackendType: m_BackendType, GlewMajor, GlewMinor, GlewPatch))
1360 {
1361 SDL_GL_DeleteContext(context: m_GLContext);
1362 SDL_DestroyWindow(window: m_pWindow);
1363 m_pWindow = nullptr;
1364 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_GLEW_INIT_FAILED;
1365 }
1366 }
1367
1368 int InitError = IsVersionSupportedGlew(BackendType: m_BackendType, VersionMajor: g_Config.m_GfxGLMajor, VersionMinor: g_Config.m_GfxGLMinor, VersionPatch: g_Config.m_GfxGLPatch, GlewMajor, GlewMinor, GlewPatch);
1369
1370 // SDL_GL_GetDrawableSize reports HiDPI resolution even with SDL_WINDOW_ALLOW_HIGHDPI not set, which is wrong
1371 if(SdlFlags & SDL_WINDOW_ALLOW_HIGHDPI)
1372 {
1373 if(IsOpenGLFamilyBackend)
1374 SDL_GL_GetDrawableSize(window: m_pWindow, w: pCurrentWidth, h: pCurrentHeight);
1375 else
1376 SDL_Vulkan_GetDrawableSize(window: m_pWindow, w: pCurrentWidth, h: pCurrentHeight);
1377 }
1378 else
1379 SDL_GetWindowSize(window: m_pWindow, w: pCurrentWidth, h: pCurrentHeight);
1380 SDL_GetWindowSize(window: m_pWindow, w: pWidth, h: pHeight);
1381
1382 if(IsOpenGLFamilyBackend)
1383 {
1384#if !defined(CONF_PLATFORM_EMSCRIPTEN)
1385 // SDL_GL_SetSwapInterval is not supported with Emscripten as this is only a wrapper for the
1386 // emscripten_set_main_loop_timing function which does not work because we do not use the
1387 // emscripten_set_main_loop function before.
1388 SDL_GL_SetSwapInterval(interval: Flags & IGraphicsBackend::INITFLAG_VSYNC ? 1 : 0);
1389#endif
1390 SDL_GL_MakeCurrent(window: nullptr, context: nullptr);
1391 }
1392
1393 if(InitError != 0)
1394 {
1395 if(m_GLContext)
1396 SDL_GL_DeleteContext(context: m_GLContext);
1397 SDL_DestroyWindow(window: m_pWindow);
1398 m_pWindow = nullptr;
1399
1400 // try setting to glew supported version
1401 g_Config.m_GfxGLMajor = GlewMajor;
1402 g_Config.m_GfxGLMinor = GlewMinor;
1403 g_Config.m_GfxGLPatch = GlewPatch;
1404
1405 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_GL_VERSION_FAILED;
1406 }
1407#endif // !CONF_HEADLESS_CLIENT
1408
1409 // start the command processor
1410 dbg_assert(m_pProcessor == nullptr, "Processor was not cleaned up properly.");
1411 m_pProcessor = new CCommandProcessor_SDL_GL(m_BackendType, g_Config.m_GfxGLMajor, g_Config.m_GfxGLMinor, g_Config.m_GfxGLPatch);
1412 StartProcessor(pProcessor: m_pProcessor);
1413
1414 // issue init commands for OpenGL and SDL
1415 CCommandBuffer CmdBuffer(1024, 512);
1416 CCommandProcessorFragment_GLBase::SCommand_PreInit CmdPre;
1417 CmdPre.m_pWindow = m_pWindow;
1418 CmdPre.m_Width = *pCurrentWidth;
1419 CmdPre.m_Height = *pCurrentHeight;
1420 CmdPre.m_pVendorString = m_aVendorString;
1421 CmdPre.m_pVersionString = m_aVersionString;
1422 CmdPre.m_pRendererString = m_aRendererString;
1423 CmdPre.m_pGpuList = &m_GpuList;
1424 CmdBuffer.AddCommandUnsafe(Command: CmdPre);
1425 RunBufferSingleThreadedUnsafe(pBuffer: &CmdBuffer);
1426 CmdBuffer.Reset();
1427
1428 // run sdl first to have the context in the thread
1429 CCommandProcessorFragment_SDL::SCommand_Init CmdSDL;
1430 CmdSDL.m_pWindow = m_pWindow;
1431 CmdSDL.m_GLContext = m_GLContext;
1432 CmdBuffer.AddCommandUnsafe(Command: CmdSDL);
1433 RunBuffer(pBuffer: &CmdBuffer);
1434 WaitForIdle();
1435 CmdBuffer.Reset();
1436
1437 const char *pErrorStr = nullptr;
1438 if(InitError == 0)
1439 {
1440 CCommandProcessorFragment_GLBase::SCommand_Init CmdGL;
1441 CmdGL.m_pWindow = m_pWindow;
1442 CmdGL.m_Width = *pCurrentWidth;
1443 CmdGL.m_Height = *pCurrentHeight;
1444 CmdGL.m_pTextureMemoryUsage = &m_TextureMemoryUsage;
1445 CmdGL.m_pBufferMemoryUsage = &m_BufferMemoryUsage;
1446 CmdGL.m_pStreamMemoryUsage = &m_StreamMemoryUsage;
1447 CmdGL.m_pStagingMemoryUsage = &m_StagingMemoryUsage;
1448 CmdGL.m_pGpuList = &m_GpuList;
1449 CmdGL.m_pReadPresentedImageDataFunc = &m_ReadPresentedImageDataFunc;
1450 CmdGL.m_pStorage = pStorage;
1451 CmdGL.m_pCapabilities = &m_Capabilities;
1452 CmdGL.m_pInitError = &InitError;
1453 CmdGL.m_RequestedMajor = g_Config.m_GfxGLMajor;
1454 CmdGL.m_RequestedMinor = g_Config.m_GfxGLMinor;
1455 CmdGL.m_RequestedPatch = g_Config.m_GfxGLPatch;
1456 CmdGL.m_GlewMajor = GlewMajor;
1457 CmdGL.m_GlewMinor = GlewMinor;
1458 CmdGL.m_GlewPatch = GlewPatch;
1459 CmdGL.m_pErrStringPtr = &pErrorStr;
1460 CmdGL.m_pVendorString = m_aVendorString;
1461 CmdGL.m_pVersionString = m_aVersionString;
1462 CmdGL.m_pRendererString = m_aRendererString;
1463 CmdGL.m_RequestedBackend = m_BackendType;
1464 CmdBuffer.AddCommandUnsafe(Command: CmdGL);
1465
1466 RunBuffer(pBuffer: &CmdBuffer);
1467 WaitForIdle();
1468 CmdBuffer.Reset();
1469 }
1470
1471 if(InitError != 0)
1472 {
1473 if(InitError != -2)
1474 {
1475 // shutdown the context, as it might have been initialized
1476 CCommandProcessorFragment_GLBase::SCommand_Shutdown CmdGL;
1477 CmdBuffer.AddCommandUnsafe(Command: CmdGL);
1478 RunBuffer(pBuffer: &CmdBuffer);
1479 WaitForIdle();
1480 CmdBuffer.Reset();
1481 }
1482
1483 CCommandProcessorFragment_SDL::SCommand_Shutdown Cmd;
1484 CmdBuffer.AddCommandUnsafe(Command: Cmd);
1485 RunBuffer(pBuffer: &CmdBuffer);
1486 WaitForIdle();
1487 CmdBuffer.Reset();
1488
1489 CCommandProcessorFragment_GLBase::SCommand_PostShutdown CmdPost;
1490 CmdBuffer.AddCommandUnsafe(Command: CmdPost);
1491 RunBufferSingleThreadedUnsafe(pBuffer: &CmdBuffer);
1492 CmdBuffer.Reset();
1493
1494 // stop and delete the processor
1495 StopProcessor();
1496 delete m_pProcessor;
1497 m_pProcessor = nullptr;
1498
1499 if(m_GLContext)
1500 SDL_GL_DeleteContext(context: m_GLContext);
1501 SDL_DestroyWindow(window: m_pWindow);
1502 m_pWindow = nullptr;
1503
1504 // try setting to version string's supported version
1505 if(InitError == -2)
1506 {
1507 g_Config.m_GfxGLMajor = m_Capabilities.m_ContextMajor;
1508 g_Config.m_GfxGLMinor = m_Capabilities.m_ContextMinor;
1509 g_Config.m_GfxGLPatch = m_Capabilities.m_ContextPatch;
1510 }
1511
1512 if(pErrorStr != nullptr)
1513 {
1514 str_copy(dst&: m_aErrorString, src: pErrorStr);
1515 }
1516
1517 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_GL_VERSION_FAILED;
1518 }
1519
1520 {
1521 CCommandBuffer::SCommand_Update_Viewport CmdSDL2;
1522 CmdSDL2.m_X = 0;
1523 CmdSDL2.m_Y = 0;
1524 CmdSDL2.m_Width = *pCurrentWidth;
1525 CmdSDL2.m_Height = *pCurrentHeight;
1526 CmdSDL2.m_ByResize = true;
1527 CmdBuffer.AddCommandUnsafe(Command: CmdSDL2);
1528 RunBuffer(pBuffer: &CmdBuffer);
1529 WaitForIdle();
1530 CmdBuffer.Reset();
1531 }
1532
1533 return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_NONE;
1534}
1535
1536int CGraphicsBackend_SDL_GL::Shutdown()
1537{
1538 if(m_pProcessor != nullptr)
1539 {
1540 // issue a shutdown command
1541 CCommandBuffer CmdBuffer(1024, 512);
1542 CCommandProcessorFragment_GLBase::SCommand_Shutdown CmdGL;
1543 CmdBuffer.AddCommandUnsafe(Command: CmdGL);
1544 RunBuffer(pBuffer: &CmdBuffer);
1545 WaitForIdle();
1546 CmdBuffer.Reset();
1547
1548 CCommandProcessorFragment_SDL::SCommand_Shutdown Cmd;
1549 CmdBuffer.AddCommandUnsafe(Command: Cmd);
1550 RunBuffer(pBuffer: &CmdBuffer);
1551 WaitForIdle();
1552 CmdBuffer.Reset();
1553
1554 CCommandProcessorFragment_GLBase::SCommand_PostShutdown CmdPost;
1555 CmdBuffer.AddCommandUnsafe(Command: CmdPost);
1556 RunBufferSingleThreadedUnsafe(pBuffer: &CmdBuffer);
1557 CmdBuffer.Reset();
1558
1559 // stop and delete the processor
1560 StopProcessor();
1561 delete m_pProcessor;
1562 m_pProcessor = nullptr;
1563 }
1564
1565 if(m_GLContext != nullptr)
1566 SDL_GL_DeleteContext(context: m_GLContext);
1567 SDL_DestroyWindow(window: m_pWindow);
1568 m_pWindow = nullptr;
1569
1570 SDL_QuitSubSystem(SDL_INIT_VIDEO);
1571 return 0;
1572}
1573
1574uint64_t CGraphicsBackend_SDL_GL::TextureMemoryUsage() const
1575{
1576 return m_TextureMemoryUsage;
1577}
1578
1579uint64_t CGraphicsBackend_SDL_GL::BufferMemoryUsage() const
1580{
1581 return m_BufferMemoryUsage;
1582}
1583
1584uint64_t CGraphicsBackend_SDL_GL::StreamedMemoryUsage() const
1585{
1586 return m_StreamMemoryUsage;
1587}
1588
1589uint64_t CGraphicsBackend_SDL_GL::StagingMemoryUsage() const
1590{
1591 return m_StagingMemoryUsage;
1592}
1593
1594const TTwGraphicsGpuList &CGraphicsBackend_SDL_GL::GetGpus() const
1595{
1596 return m_GpuList;
1597}
1598
1599void CGraphicsBackend_SDL_GL::Minimize()
1600{
1601 SDL_MinimizeWindow(window: m_pWindow);
1602}
1603
1604void CGraphicsBackend_SDL_GL::SetWindowParams(int FullscreenMode, bool IsBorderless)
1605{
1606 // The flags have to be kept consistent with flags set in the CGraphics_Threaded::IssueInit function!
1607
1608 if(FullscreenMode > 0)
1609 {
1610 bool IsDesktopFullscreen = FullscreenMode == 2;
1611#ifndef CONF_FAMILY_WINDOWS
1612 // Windowed fullscreen is only available on Windows, use desktop fullscreen on other platforms
1613 IsDesktopFullscreen |= FullscreenMode == 3;
1614#endif
1615 if(FullscreenMode == 1)
1616 {
1617#if defined(CONF_PLATFORM_MACOS) || defined(CONF_PLATFORM_HAIKU)
1618 // Todo SDL: remove this when fixed (game freezes when losing focus in fullscreen)
1619 SDL_SetWindowFullscreen(m_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
1620#else
1621 SDL_SetWindowFullscreen(window: m_pWindow, flags: SDL_WINDOW_FULLSCREEN);
1622#endif
1623 SDL_SetWindowResizable(window: m_pWindow, resizable: SDL_FALSE);
1624 }
1625 else if(IsDesktopFullscreen)
1626 {
1627 SDL_SetWindowFullscreen(window: m_pWindow, flags: SDL_WINDOW_FULLSCREEN_DESKTOP);
1628 SDL_SetWindowResizable(window: m_pWindow, resizable: SDL_FALSE);
1629 }
1630 else // Windowed fullscreen
1631 {
1632 SDL_SetWindowFullscreen(window: m_pWindow, flags: 0);
1633 SDL_SetWindowBordered(window: m_pWindow, bordered: SDL_TRUE);
1634 SDL_SetWindowResizable(window: m_pWindow, resizable: SDL_FALSE);
1635 SDL_DisplayMode DpMode;
1636 if(SDL_GetDesktopDisplayMode(displayIndex: g_Config.m_GfxScreen, mode: &DpMode) < 0)
1637 {
1638 log_error("gfx", "Unable to get desktop display mode of screen %d: %s", g_Config.m_GfxScreen, SDL_GetError());
1639 }
1640 else
1641 {
1642 ResizeWindow(w: DpMode.w, h: DpMode.h, RefreshRate: DpMode.refresh_rate);
1643 SDL_SetWindowPosition(window: m_pWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(g_Config.m_GfxScreen), SDL_WINDOWPOS_CENTERED_DISPLAY(g_Config.m_GfxScreen));
1644 }
1645 }
1646 }
1647 else // Windowed
1648 {
1649 SDL_SetWindowFullscreen(window: m_pWindow, flags: 0);
1650 SDL_SetWindowBordered(window: m_pWindow, bordered: SDL_bool(!IsBorderless));
1651 SDL_SetWindowResizable(window: m_pWindow, resizable: SDL_TRUE);
1652 }
1653}
1654
1655bool CGraphicsBackend_SDL_GL::SetWindowScreen(int Index, bool MoveToCenter)
1656{
1657 if(Index < 0 || Index >= m_NumScreens)
1658 {
1659 log_error("gfx", "Invalid screen number: %d (min: 0, max: %d)", Index, m_NumScreens);
1660 return false;
1661 }
1662
1663 SDL_Rect ScreenPos;
1664 if(SDL_GetDisplayBounds(displayIndex: Index, rect: &ScreenPos) != 0)
1665 {
1666 log_error("gfx", "Unable to get bounds of screen %d: %s", Index, SDL_GetError());
1667 return false;
1668 }
1669
1670 if(MoveToCenter)
1671 {
1672 SDL_SetWindowPosition(window: m_pWindow,
1673 SDL_WINDOWPOS_CENTERED_DISPLAY(Index),
1674 SDL_WINDOWPOS_CENTERED_DISPLAY(Index));
1675 }
1676 else
1677 {
1678 SDL_SetWindowPosition(window: m_pWindow,
1679 SDL_WINDOWPOS_UNDEFINED_DISPLAY(Index),
1680 SDL_WINDOWPOS_UNDEFINED_DISPLAY(Index));
1681 }
1682
1683 return UpdateDisplayMode(Index);
1684}
1685
1686bool CGraphicsBackend_SDL_GL::UpdateDisplayMode(int Index)
1687{
1688 SDL_DisplayMode DisplayMode;
1689 if(SDL_GetDesktopDisplayMode(displayIndex: Index, mode: &DisplayMode) < 0)
1690 {
1691 log_error("gfx", "Unable to get desktop display mode of screen %d: %s", Index, SDL_GetError());
1692 return false;
1693 }
1694
1695 g_Config.m_GfxScreen = Index;
1696 g_Config.m_GfxDesktopWidth = DisplayMode.w;
1697 g_Config.m_GfxDesktopHeight = DisplayMode.h;
1698 return true;
1699}
1700
1701int CGraphicsBackend_SDL_GL::GetWindowScreen()
1702{
1703 return SDL_GetWindowDisplayIndex(window: m_pWindow);
1704}
1705
1706int CGraphicsBackend_SDL_GL::WindowActive()
1707{
1708 return m_pWindow && SDL_GetWindowFlags(window: m_pWindow) & SDL_WINDOW_INPUT_FOCUS;
1709}
1710
1711int CGraphicsBackend_SDL_GL::WindowOpen()
1712{
1713 return m_pWindow && SDL_GetWindowFlags(window: m_pWindow) & SDL_WINDOW_SHOWN;
1714}
1715
1716void CGraphicsBackend_SDL_GL::SetWindowGrab(bool Grab)
1717{
1718 SDL_SetWindowGrab(window: m_pWindow, grabbed: Grab ? SDL_TRUE : SDL_FALSE);
1719}
1720
1721bool CGraphicsBackend_SDL_GL::ResizeWindow(int w, int h, int RefreshRate)
1722{
1723 // don't call resize events when the window is at fullscreen desktop
1724 if(!m_pWindow || (SDL_GetWindowFlags(window: m_pWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
1725 return false;
1726
1727 // if the window is at fullscreen use SDL_SetWindowDisplayMode instead, suggested by SDL
1728 if(SDL_GetWindowFlags(window: m_pWindow) & SDL_WINDOW_FULLSCREEN)
1729 {
1730#ifdef CONF_FAMILY_WINDOWS
1731 // in windows make the window windowed mode first, this prevents strange window glitches (other games probably do something similar)
1732 SetWindowParams(0, true);
1733#endif
1734 SDL_DisplayMode SetMode = {};
1735 SDL_DisplayMode ClosestMode = {};
1736 SetMode.format = 0;
1737 SetMode.w = w;
1738 SetMode.h = h;
1739 SetMode.refresh_rate = RefreshRate;
1740 SDL_SetWindowDisplayMode(window: m_pWindow, mode: SDL_GetClosestDisplayMode(displayIndex: g_Config.m_GfxScreen, mode: &SetMode, closest: &ClosestMode));
1741#ifdef CONF_FAMILY_WINDOWS
1742 // now change it back to fullscreen, this will restore the above set state, bcs SDL saves fullscreen modes apart from other video modes (as of SDL 2.0.16)
1743 // see implementation of SDL_SetWindowDisplayMode
1744 SetWindowParams(1, false);
1745#endif
1746 return true;
1747 }
1748 else
1749 {
1750 SDL_SetWindowSize(window: m_pWindow, w, h);
1751 if(SDL_GetWindowFlags(window: m_pWindow) & SDL_WINDOW_MAXIMIZED)
1752 // remove maximize flag
1753 SDL_RestoreWindow(window: m_pWindow);
1754 }
1755
1756 return false;
1757}
1758
1759void CGraphicsBackend_SDL_GL::GetViewportSize(int &w, int &h)
1760{
1761 if(m_BackendType != EBackendType::BACKEND_TYPE_VULKAN)
1762 SDL_GL_GetDrawableSize(window: m_pWindow, w: &w, h: &h);
1763 else
1764 SDL_Vulkan_GetDrawableSize(window: m_pWindow, w: &w, h: &h);
1765}
1766
1767void CGraphicsBackend_SDL_GL::NotifyWindow()
1768{
1769 // Minimum version 2.0.16, after version 2.0.22 the naming is changed to 2.24.0 etc.
1770#if SDL_MAJOR_VERSION > 2 || (SDL_MAJOR_VERSION == 2 && SDL_MINOR_VERSION == 0 && SDL_PATCHLEVEL >= 16) || (SDL_MAJOR_VERSION == 2 && SDL_MINOR_VERSION > 0)
1771 if(SDL_FlashWindow(window: m_pWindow, operation: SDL_FlashOperation::SDL_FLASH_UNTIL_FOCUSED) != 0)
1772 {
1773 // fails if SDL hasn't implemented it
1774 return;
1775 }
1776#endif
1777}
1778
1779bool CGraphicsBackend_SDL_GL::IsScreenKeyboardShown()
1780{
1781 return SDL_IsScreenKeyboardShown(window: m_pWindow);
1782}
1783
1784void CGraphicsBackend_SDL_GL::WindowDestroyNtf(uint32_t WindowId)
1785{
1786}
1787
1788void CGraphicsBackend_SDL_GL::WindowCreateNtf(uint32_t WindowId)
1789{
1790 m_pWindow = SDL_GetWindowFromID(id: WindowId);
1791}
1792
1793TGLBackendReadPresentedImageData &CGraphicsBackend_SDL_GL::GetReadPresentedImageDataFuncUnsafe()
1794{
1795 return m_ReadPresentedImageDataFunc;
1796}
1797
1798IGraphicsBackend *CreateGraphicsBackend(TTranslateFunc &&TranslateFunc) { return new CGraphicsBackend_SDL_GL(std::move(TranslateFunc)); }
1799