1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "CoreThread/BsCoreThread.h"
4#include "Private/Linux/BsLinuxPlatform.h"
5#include "Private/Linux/BsLinuxWindow.h"
6#include "Linux/BsLinuxRenderWindow.h"
7#include "Linux/BsLinuxVideoModeInfo.h"
8#include "Linux/BsLinuxGLSupport.h"
9#include "Linux/BsLinuxContext.h"
10#include "BsGLPixelFormat.h"
11#include "BsGLRenderWindowManager.h"
12#include "Math/BsMath.h"
13
14#define XRANDR_ROTATION_LEFT (1 << 1)
15#define XRANDR_ROTATION_RIGHT (1 << 3)
16
17namespace bs
18{
19 LinuxRenderWindow::LinuxRenderWindow(const RENDER_WINDOW_DESC& desc, UINT32 windowId, ct::LinuxGLSupport& glSupport)
20 :RenderWindow(desc, windowId), mGLSupport(glSupport), mProperties(desc)
21 { }
22
23 void LinuxRenderWindow::getCustomAttribute(const String& name, void* data) const
24 {
25 if (name == "WINDOW" || name == "LINUX_WINDOW")
26 {
27 blockUntilCoreInitialized();
28 getCore()->getCustomAttribute(name, data);
29 return;
30 }
31 }
32
33 Vector2I LinuxRenderWindow::screenToWindowPos(const Vector2I& screenPos) const
34 {
35 blockUntilCoreInitialized();
36
37 LinuxPlatform::lockX();
38 Vector2I pos = getCore()->_getInternal()->screenToWindowPos(screenPos);
39 LinuxPlatform::unlockX();
40
41 return pos;
42 }
43
44 Vector2I LinuxRenderWindow::windowToScreenPos(const Vector2I& windowPos) const
45 {
46 blockUntilCoreInitialized();
47
48 LinuxPlatform::lockX();
49 Vector2I pos = getCore()->_getInternal()->windowToScreenPos(windowPos);
50 LinuxPlatform::unlockX();
51
52 return pos;
53 }
54
55 SPtr<ct::LinuxRenderWindow> LinuxRenderWindow::getCore() const
56 {
57 return std::static_pointer_cast<ct::LinuxRenderWindow>(mCoreSpecific);
58 }
59
60 SPtr<ct::CoreObject> LinuxRenderWindow::createCore() const
61 {
62 RENDER_WINDOW_DESC desc = mDesc;
63 SPtr<ct::CoreObject> coreObj = bs_shared_ptr_new<ct::LinuxRenderWindow>(desc, mWindowId, mGLSupport);
64 coreObj->_setThisPtr(coreObj);
65
66 return coreObj;
67 }
68
69 void LinuxRenderWindow::syncProperties()
70 {
71 ScopedSpinLock lock(getCore()->_getPropertiesLock());
72 mProperties = getCore()->mSyncedProperties;
73 }
74
75 namespace ct
76 {
77 LinuxRenderWindow::LinuxRenderWindow(const RENDER_WINDOW_DESC& desc, UINT32 windowId, LinuxGLSupport& glsupport)
78 : RenderWindow(desc, windowId), mWindow(nullptr), mGLSupport(glsupport), mContext(nullptr), mProperties(desc)
79 , mSyncedProperties(desc), mIsChild(false), mShowOnSwap(false)
80 { }
81
82 LinuxRenderWindow::~LinuxRenderWindow()
83 {
84 // Make sure to set the original desktop video mode before we exit
85 if(mProperties.isFullScreen)
86 setWindowed(50, 50);
87
88 if (mWindow != nullptr)
89 {
90 Platform::resetNonClientAreas(*this);
91
92 LinuxPlatform::lockX();
93
94 bs_delete(mWindow);
95 mWindow = nullptr;
96
97 LinuxPlatform::unlockX();
98 }
99 }
100
101 void LinuxRenderWindow::initialize()
102 {
103 LinuxPlatform::lockX();
104
105 RenderWindowProperties& props = mProperties;
106
107 props.isFullScreen = mDesc.fullscreen;
108 mIsChild = false;
109
110 GLVisualConfig visualConfig = mGLSupport.findBestVisual(LinuxPlatform::getXDisplay(), mDesc.depthBuffer,
111 mDesc.multisampleCount, mDesc.gamma);
112
113 WINDOW_DESC windowDesc;
114 windowDesc.x = mDesc.left;
115 windowDesc.y = mDesc.top;
116 windowDesc.width = mDesc.videoMode.width;
117 windowDesc.height = mDesc.videoMode.height;
118 windowDesc.title = mDesc.title;
119 windowDesc.showDecorations = mDesc.showTitleBar;
120 windowDesc.allowResize = mDesc.allowResize;
121 windowDesc.showOnTaskBar = !mDesc.toolWindow;
122 windowDesc.modal = mDesc.modal;
123 windowDesc.visualInfo = visualConfig.visualInfo;
124 windowDesc.screen = mDesc.videoMode.outputIdx;
125 windowDesc.hidden = mDesc.hideUntilSwap || mDesc.hidden;
126
127 auto opt = mDesc.platformSpecific.find("parentWindowHandle");
128 if (opt != mDesc.platformSpecific.end())
129 windowDesc.parent = (::Window)parseUINT64(opt->second);
130 else
131 windowDesc.parent = 0;
132
133 // TODO: add passing the XDisplay here as well. Right now the default display is assumed
134 opt = mDesc.platformSpecific.find("externalWindowHandle");
135 if (opt != mDesc.platformSpecific.end())
136 windowDesc.external = (::Window)parseUINT64(opt->second);
137 else
138 windowDesc.external = 0;
139
140 mIsChild = windowDesc.parent != 0;
141 props.isFullScreen = mDesc.fullscreen && !mIsChild;
142
143 mShowOnSwap = mDesc.hideUntilSwap && !mDesc.hidden;
144 props.isHidden = mDesc.hideUntilSwap || mDesc.hidden;
145
146 mWindow = bs_new<LinuxWindow>(windowDesc);
147 mWindow->_setUserData(this);
148
149 props.width = mWindow->getWidth();
150 props.height = mWindow->getHeight();
151 props.top = mWindow->getTop();
152 props.left = mWindow->getLeft();
153
154 props.hwGamma = visualConfig.caps.srgb;
155 props.multisampleCount = visualConfig.caps.numSamples;
156
157 XWindowAttributes windowAttributes;
158 XGetWindowAttributes(LinuxPlatform::getXDisplay(), mWindow->_getXWindow(), &windowAttributes);
159
160 XVisualInfo requestVI;
161 requestVI.screen = windowDesc.screen;
162 requestVI.visualid = XVisualIDFromVisual(windowAttributes.visual);
163
164 LinuxPlatform::unlockX(); // Calls below have their own locking mechanisms
165
166 mContext = mGLSupport.createContext(LinuxPlatform::getXDisplay(), requestVI);
167
168 if(mDesc.fullscreen && !mIsChild)
169 setFullscreen(mDesc.videoMode);
170
171 if(mDesc.vsync && mDesc.vsyncInterval > 0)
172 setVSync(true, mDesc.vsyncInterval);
173
174 {
175 ScopedSpinLock lock(mLock);
176 mSyncedProperties = props;
177 }
178
179 bs::RenderWindowManager::instance().notifySyncDataDirty(this);
180 RenderWindow::initialize();
181 }
182
183 void LinuxRenderWindow::setFullscreen(UINT32 width, UINT32 height, float refreshRate, UINT32 monitorIdx)
184 {
185 THROW_IF_NOT_CORE_THREAD;
186
187 VideoMode videoMode(width, height, refreshRate, monitorIdx);
188 setFullscreen(videoMode);
189 }
190
191 void LinuxRenderWindow::setVideoMode(INT32 screen, RROutput output, RRMode mode)
192 {
193 ::Display* display = LinuxPlatform::getXDisplay();
194 ::Window rootWindow = RootWindow(display, screen);
195
196 XRRScreenResources* screenRes = XRRGetScreenResources (display, rootWindow);
197 if(screenRes == nullptr)
198 {
199 LOGERR("XRR: Failed to retrieve screen resources. ");
200 return;
201 }
202
203 XRROutputInfo* outputInfo = XRRGetOutputInfo(display, screenRes, output);
204 if(outputInfo == nullptr)
205 {
206 XRRFreeScreenResources(screenRes);
207
208 LOGERR("XRR: Failed to retrieve output info for output: " + toString((UINT32)output));
209 return;
210 }
211
212 XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screenRes, outputInfo->crtc);
213 if(crtcInfo == nullptr)
214 {
215 XRRFreeScreenResources(screenRes);
216 XRRFreeOutputInfo(outputInfo);
217
218 LOGERR("XRR: Failed to retrieve CRTC info for output: " + toString((UINT32)output));
219 return;
220 }
221
222 // Note: This changes the user's desktop resolution permanently, even when the app exists, make sure to revert
223 // (Sadly there doesn't appear to be a better way)
224 Status status = XRRSetCrtcConfig (display, screenRes, outputInfo->crtc, CurrentTime,
225 crtcInfo->x, crtcInfo->y, mode, crtcInfo->rotation, &output, 1);
226
227 if(status != Success)
228 LOGERR("XRR: XRRSetCrtcConfig failed.");
229
230 XRRFreeCrtcInfo(crtcInfo);
231 XRRFreeOutputInfo(outputInfo);
232 XRRFreeScreenResources(screenRes);
233 }
234
235 void LinuxRenderWindow::setFullscreen(const VideoMode& mode)
236 {
237 THROW_IF_NOT_CORE_THREAD;
238
239 if (mIsChild)
240 return;
241
242 const LinuxVideoModeInfo& videoModeInfo =
243 static_cast<const LinuxVideoModeInfo&>(RenderAPI::instance().getVideoModeInfo());
244
245 UINT32 outputIdx = mode.outputIdx;
246 if(outputIdx >= videoModeInfo.getNumOutputs())
247 {
248 LOGERR("Invalid output device index.")
249 return;
250 }
251
252 const LinuxVideoOutputInfo& outputInfo =
253 static_cast<const LinuxVideoOutputInfo&>(videoModeInfo.getOutputInfo (outputIdx));
254
255 INT32 screen = outputInfo._getScreen();
256 RROutput outputID = outputInfo._getOutputID();
257
258 RRMode modeID = 0;
259 if(!mode.isCustom)
260 {
261 const LinuxVideoMode& videoMode = static_cast<const LinuxVideoMode&>(mode);
262 modeID = videoMode._getModeID();
263 }
264 else
265 {
266 LinuxPlatform::lockX();
267
268 // Look for mode matching the requested resolution
269 ::Display* display = LinuxPlatform::getXDisplay();
270 ::Window rootWindow = RootWindow(display, screen);
271
272 XRRScreenResources* screenRes = XRRGetScreenResources(display, rootWindow);
273 if (screenRes == nullptr)
274 {
275 LOGERR("XRR: Failed to retrieve screen resources. ");
276 return;
277 }
278
279 XRROutputInfo* outputInfo = XRRGetOutputInfo(display, screenRes, outputID);
280 if (outputInfo == nullptr)
281 {
282 XRRFreeScreenResources(screenRes);
283
284 LOGERR("XRR: Failed to retrieve output info for output: " + toString((UINT32)outputID));
285 return;
286 }
287
288 XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screenRes, outputInfo->crtc);
289 if (crtcInfo == nullptr)
290 {
291 XRRFreeScreenResources(screenRes);
292 XRRFreeOutputInfo(outputInfo);
293
294 LOGERR("XRR: Failed to retrieve CRTC info for output: " + toString((UINT32)outputID));
295 return;
296 }
297
298 bool foundMode = false;
299 for (INT32 i = 0; i < screenRes->nmode; i++)
300 {
301 const XRRModeInfo& modeInfo = screenRes->modes[i];
302
303 UINT32 width, height;
304
305 if (crtcInfo->rotation & (XRANDR_ROTATION_LEFT | XRANDR_ROTATION_RIGHT))
306 {
307 width = modeInfo.height;
308 height = modeInfo.width;
309 }
310 else
311 {
312 width = modeInfo.width;
313 height = modeInfo.height;
314 }
315
316 float refreshRate;
317 if (modeInfo.hTotal != 0 && modeInfo.vTotal != 0)
318 refreshRate = (float) (modeInfo.dotClock / (double) (modeInfo.hTotal * modeInfo.vTotal));
319 else
320 refreshRate = 0.0f;
321
322 if (width == mode.width && height == mode.height)
323 {
324 modeID = modeInfo.id;
325 foundMode = true;
326
327 if (Math::approxEquals(refreshRate, mode.refreshRate))
328 break;
329 }
330 }
331
332 if (!foundMode)
333 {
334 LinuxPlatform::unlockX();
335
336 LOGERR("Unable to enter fullscreen, unsupported video mode requested.");
337 return;
338 }
339
340 LinuxPlatform::unlockX();
341 }
342
343 LinuxPlatform::lockX();
344
345 setVideoMode(screen, outputID, modeID);
346 mWindow->_setFullscreen(true);
347
348 LinuxPlatform::unlockX();
349
350 RenderWindowProperties& props = mProperties;
351 props.isFullScreen = true;
352
353 props.top = 0;
354 props.left = 0;
355 props.width = mode.width;
356 props.height = mode.height;
357
358 _windowMovedOrResized();
359
360 {
361 ScopedSpinLock lock(mLock);
362 mSyncedProperties.left = props.left;
363 mSyncedProperties.top = props.top;
364 mSyncedProperties.width = props.width;
365 mSyncedProperties.height = props.height;
366 }
367
368 bs::RenderWindowManager::instance().notifySyncDataDirty(this);
369 bs::RenderWindowManager::instance().notifyMovedOrResized(this);
370 }
371
372 void LinuxRenderWindow::setWindowed(UINT32 width, UINT32 height)
373 {
374 THROW_IF_NOT_CORE_THREAD;
375
376 RenderWindowProperties& props = mProperties;
377
378 if (!props.isFullScreen)
379 return;
380
381 // Restore old screen config
382 const LinuxVideoModeInfo& videoModeInfo =
383 static_cast<const LinuxVideoModeInfo&>(RenderAPI::instance().getVideoModeInfo());
384
385 UINT32 outputIdx = 0; // 0 is always primary
386 if(outputIdx >= videoModeInfo.getNumOutputs())
387 {
388 LOGERR("Invalid output device index.")
389 return;
390 }
391
392 const LinuxVideoOutputInfo& outputInfo =
393 static_cast<const LinuxVideoOutputInfo&>(videoModeInfo.getOutputInfo (outputIdx));
394
395 const LinuxVideoMode& desktopVideoMode = static_cast<const LinuxVideoMode&>(outputInfo.getDesktopVideoMode());
396
397 LinuxPlatform::lockX();
398
399 setVideoMode(outputInfo._getScreen(), outputInfo._getOutputID(), desktopVideoMode._getModeID());
400 mWindow->_setFullscreen(false);
401
402 LinuxPlatform::unlockX();
403
404 props.isFullScreen = false;
405 props.width = width;
406 props.height = height;
407
408 _windowMovedOrResized();
409
410 {
411 ScopedSpinLock lock(mLock);
412 mSyncedProperties.left = props.left;
413 mSyncedProperties.top = props.top;
414 mSyncedProperties.width = props.width;
415 mSyncedProperties.height = props.height;
416 }
417
418 bs::RenderWindowManager::instance().notifySyncDataDirty(this);
419 bs::RenderWindowManager::instance().notifyMovedOrResized(this);
420 }
421
422 void LinuxRenderWindow::move(INT32 left, INT32 top)
423 {
424 THROW_IF_NOT_CORE_THREAD;
425
426 RenderWindowProperties& props = mProperties;
427 if (!props.isFullScreen)
428 {
429 LinuxPlatform::lockX();
430 mWindow->move(left, top);
431 LinuxPlatform::unlockX();
432
433 props.top = mWindow->getTop();
434 props.left = mWindow->getLeft();
435
436 {
437 ScopedSpinLock lock(mLock);
438 mSyncedProperties.top = props.top;
439 mSyncedProperties.left = props.left;
440 }
441
442 bs::RenderWindowManager::instance().notifySyncDataDirty(this);
443 }
444 }
445
446 void LinuxRenderWindow::resize(UINT32 width, UINT32 height)
447 {
448 THROW_IF_NOT_CORE_THREAD;
449
450 RenderWindowProperties& props = mProperties;
451 if (!props.isFullScreen)
452 {
453 LinuxPlatform::lockX();
454 mWindow->resize(width, height);
455 LinuxPlatform::unlockX();
456
457 props.width = mWindow->getWidth();
458 props.height = mWindow->getHeight();
459
460 {
461 ScopedSpinLock lock(mLock);
462 mSyncedProperties.width = props.width;
463 mSyncedProperties.height = props.height;
464 }
465
466 bs::RenderWindowManager::instance().notifySyncDataDirty(this);
467 }
468 }
469
470 void LinuxRenderWindow::minimize()
471 {
472 THROW_IF_NOT_CORE_THREAD;
473
474 LinuxPlatform::lockX();
475 mWindow->minimize();
476 LinuxPlatform::unlockX();
477 }
478
479 void LinuxRenderWindow::maximize()
480 {
481 THROW_IF_NOT_CORE_THREAD;
482
483 LinuxPlatform::lockX();
484 mWindow->maximize();
485 LinuxPlatform::unlockX();
486 }
487
488 void LinuxRenderWindow::restore()
489 {
490 THROW_IF_NOT_CORE_THREAD;
491
492 LinuxPlatform::lockX();
493 mWindow->restore();
494 LinuxPlatform::unlockX();
495 }
496
497 void LinuxRenderWindow::setVSync(bool enabled, UINT32 interval)
498 {
499 THROW_IF_NOT_CORE_THREAD;
500
501 if(!enabled)
502 interval = 0;
503
504 LinuxPlatform::lockX();
505
506 if(glXSwapIntervalEXT != nullptr)
507 glXSwapIntervalEXT(LinuxPlatform::getXDisplay(), mWindow->_getXWindow(), interval);
508 else if(glXSwapIntervalMESA != nullptr)
509 glXSwapIntervalMESA(interval);
510 else if(glXSwapIntervalSGI != nullptr)
511 glXSwapIntervalSGI(interval);
512
513 LinuxPlatform::unlockX();
514
515 mProperties.vsync = enabled;
516 mProperties.vsyncInterval = interval;
517
518 {
519 ScopedSpinLock lock(mLock);
520 mSyncedProperties.vsync = enabled;
521 mSyncedProperties.vsyncInterval = interval;
522 }
523
524 bs::RenderWindowManager::instance().notifySyncDataDirty(this);
525 }
526
527 void LinuxRenderWindow::swapBuffers(UINT32 syncMask)
528 {
529 THROW_IF_NOT_CORE_THREAD;
530
531 if (mShowOnSwap)
532 setHidden(false);
533
534 LinuxPlatform::lockX();
535 glXSwapBuffers(LinuxPlatform::getXDisplay(), mWindow->_getXWindow());
536 LinuxPlatform::unlockX();
537 }
538
539 void LinuxRenderWindow::copyToMemory(PixelData &dst, FrameBuffer buffer)
540 {
541 THROW_IF_NOT_CORE_THREAD;
542
543 if ((dst.getRight() > getProperties().width) ||
544 (dst.getBottom() > getProperties().height) ||
545 (dst.getFront() != 0) || (dst.getBack() != 1))
546 {
547 BS_EXCEPT(InvalidParametersException, "Invalid box.");
548 }
549
550 if (buffer == FB_AUTO)
551 {
552 buffer = mProperties.isFullScreen ? FB_FRONT : FB_BACK;
553 }
554
555 GLenum format = GLPixelUtil::getGLOriginFormat(dst.getFormat());
556 GLenum type = GLPixelUtil::getGLOriginDataType(dst.getFormat());
557
558 if ((format == GL_NONE) || (type == 0))
559 {
560 BS_EXCEPT(InvalidParametersException, "Unsupported format.");
561 }
562
563 // Must change the packing to ensure no overruns!
564 glPixelStorei(GL_PACK_ALIGNMENT, 1);
565
566 glReadBuffer((buffer == FB_FRONT)? GL_FRONT : GL_BACK);
567 glReadPixels((GLint)dst.getLeft(), (GLint)dst.getTop(),
568 (GLsizei)dst.getWidth(), (GLsizei)dst.getHeight(),
569 format, type, dst.getData());
570
571 // restore default alignment
572 glPixelStorei(GL_PACK_ALIGNMENT, 4);
573
574 //vertical flip
575 {
576 size_t rowSpan = dst.getWidth() * PixelUtil::getNumElemBytes(dst.getFormat());
577 size_t height = dst.getHeight();
578 UINT8* tmpData = (UINT8*)bs_alloc((UINT32)(rowSpan * height));
579 UINT8* srcRow = (UINT8 *)dst.getData(), *tmpRow = tmpData + (height - 1) * rowSpan;
580
581 while (tmpRow >= tmpData)
582 {
583 memcpy(tmpRow, srcRow, rowSpan);
584 srcRow += rowSpan;
585 tmpRow -= rowSpan;
586 }
587 memcpy(dst.getData(), tmpData, rowSpan * height);
588
589 bs_free(tmpData);
590 }
591 }
592
593 void LinuxRenderWindow::getCustomAttribute(const String& name, void* data) const
594 {
595 if(name == "GLCONTEXT")
596 {
597 SPtr<GLContext>* contextPtr = static_cast<SPtr<GLContext>*>(data);
598 *contextPtr = mContext;
599 return;
600 }
601 else if(name == "LINUX_WINDOW")
602 {
603 LinuxWindow** window = (LinuxWindow**)data;
604 *window = mWindow;
605 return;
606 }
607 else if(name == "WINDOW")
608 {
609 ::Window* window = (::Window*)data;
610 *window = mWindow->_getXWindow();
611 return;
612 }
613 }
614
615 void LinuxRenderWindow::setActive(bool state)
616 {
617 THROW_IF_NOT_CORE_THREAD;
618
619 LinuxPlatform::lockX();
620
621 if(state)
622 mWindow->restore();
623 else
624 mWindow->minimize();
625
626 LinuxPlatform::unlockX();
627
628 RenderWindow::setActive(state);
629 }
630
631 void LinuxRenderWindow::setHidden(bool hidden)
632 {
633 THROW_IF_NOT_CORE_THREAD;
634
635 if(!hidden)
636 mShowOnSwap = false;
637
638 LinuxPlatform::lockX();
639
640 if(hidden)
641 mWindow->hide();
642 else
643 mWindow->show();
644
645 LinuxPlatform::unlockX();
646
647 RenderWindow::setHidden(hidden);
648 }
649
650 void LinuxRenderWindow::_windowMovedOrResized()
651 {
652 if (!mWindow)
653 return;
654
655 RenderWindowProperties& props = mProperties;
656 if (!props.isFullScreen) // Fullscreen is handled directly by this object
657 {
658 props.top = mWindow->getTop();
659 props.left = mWindow->getLeft();
660 props.width = mWindow->getWidth();
661 props.height = mWindow->getHeight();
662 }
663 }
664
665 void LinuxRenderWindow::syncProperties()
666 {
667 ScopedSpinLock lock(mLock);
668 mProperties = mSyncedProperties;
669 }
670}}
671
672