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