1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010-2020 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "stdopengl.h"
18 #include "driver_opengl.h"
20 #if defined(NL_OS_UNIX) && !defined(NL_OS_MAC)
21 # include <X11/Xatom.h>
23 # include <X11/extensions/Xrender.h>
24 # endif // HAVE_XRENDER
26 # include <X11/Xcursor/Xcursor.h>
27 # endif // HAVE_XCURSOR
28 #endif // defined(NL_OS_UNIX) && !defined(NL_OS_MAC)
30 #include "nel/3d/u_driver.h"
31 #include "nel/misc/file.h"
34 using namespace NLMISC
;
44 namespace NLDRIVERGLES
{
46 namespace NLDRIVERGL
{
50 // *************************************************************************************
51 CDriverGL::CCursor::CCursor() : ColorDepth(CDriverGL::ColorDepth32
),
62 #if defined(NL_OS_UNIX) && !defined(NL_OS_MAC)
67 // *************************************************************************************
68 CDriverGL::CCursor::~CCursor()
73 // *************************************************************************************
74 void CDriverGL::CCursor::reset()
76 if (Cursor
!= EmptyCursor
)
80 #elif defined(NL_OS_MAC)
81 #elif defined(NL_OS_UNIX)
82 XFreeCursor(Dpy
, Cursor
);
88 // *************************************************************************************
89 CDriverGL::CCursor
& CDriverGL::CCursor::operator= (const CDriverGL::CCursor
& from
)
93 Src
= from
.Src
; // requires more than a surface copy
94 OrigHeight
= from
.OrigHeight
;
95 HotspotScale
= from
.HotspotScale
;
96 HotspotOffsetX
= from
.HotspotOffsetX
;
97 HotspotOffsetY
= from
.HotspotOffsetY
;
98 HotSpotX
= from
.HotSpotX
;
99 HotSpotY
= from
.HotSpotY
;
100 Cursor
= from
.Cursor
;
103 #if defined(NL_OS_UNIX) && !defined(NL_OS_MAC)
109 // *************************************************************************************
110 bool CDriverGL::isAlphaBlendedCursorSupported()
112 if (!_AlphaBlendedCursorSupportRetrieved
)
115 // Support starts with windows 2000 (not only from XP as seen in most docs)
116 // NB : Additionnaly, could query D3D caps to know if
117 // color hardware cursor is supported, not only emulated,
118 // but can't be sure that using the win32 api 'SetCursor' uses the same resources
119 // So far, seems to be supported on any modern card used by the game anyway ...
121 osvi
.dwOSVersionInfoSize
= sizeof(OSVERSIONINFO
);
122 if (GetVersionEx(&osvi
))
124 _AlphaBlendedCursorSupported
= (osvi
.dwMajorVersion
>= 5);
126 #elif defined(NL_OS_MAC)
127 #elif defined(NL_OS_UNIX)
129 _AlphaBlendedCursorSupported
= false;
132 if (!_AlphaBlendedCursorSupported
&& XcursorSupportsARGB(_dpy
))
133 _AlphaBlendedCursorSupported
= true;
134 #endif // HAVE_XCURSOR
136 if (!_AlphaBlendedCursorSupported
&& _xrender_version
> 0)
137 _AlphaBlendedCursorSupported
= true;
141 _AlphaBlendedCursorSupportRetrieved
= true;
144 return _AlphaBlendedCursorSupported
;
147 // *************************************************************************************
148 void CDriverGL::addCursor(const std::string
&name
, const NLMISC::CBitmap
&cursorBitmap
)
150 if (!isAlphaBlendedCursorSupported()) return;
152 nlassert(cursorBitmap
.getWidth() != 0);
153 nlassert(cursorBitmap
.getHeight() != 0);
155 // find used part base on alpha, to avoid too much shrinking
156 const CRGBA
*pixels
= (const CRGBA
*) &cursorBitmap
.getPixels()[0];
157 uint minX
, maxX
, minY
, maxY
;
158 uint width
= cursorBitmap
.getWidth();
159 uint height
= cursorBitmap
.getHeight();
162 for (uint x
= 0; x
< width
; ++x
)
166 for (uint y
= 0; y
< height
; ++y
)
168 if(pixels
[x
+ y
* width
].A
!= 0)
178 for (sint x
= width
- 1; x
>= 0; --x
)
182 for (uint y
= 0; y
< height
; ++y
)
184 if(pixels
[x
+ y
* width
].A
!= 0)
194 for (uint y
= 0; y
< height
; ++y
)
198 for (uint x
= 0; x
< width
; ++x
)
200 if(pixels
[x
+ y
* width
].A
!= 0)
210 for (sint y
= height
- 1; y
>= 0; --y
)
214 for (uint x
= 0; x
< width
; ++x
)
216 if(pixels
[x
+ y
* width
].A
!= 0)
225 CCursor
&curs
= _Cursors
[name
];
226 curs
= CCursor(); // erase possible previous cursor
228 uint destWidth
= 32, destHeight
= 32;
229 getBestCursorSize(width
, height
, destWidth
, destHeight
);
231 // build a square bitmap
232 uint tmpSize
= std::max(maxX
- minX
+ 1, maxY
- minY
+ 1);
233 curs
.Src
.resize(tmpSize
, tmpSize
);
234 // blit at top left corner
235 curs
.Src
.blit(cursorBitmap
, minX
, minY
, maxX
- minX
+ 1, maxY
- minY
+ 1, 0, 0);
237 curs
.OrigHeight
= cursorBitmap
.getHeight();
238 curs
.HotspotOffsetX
= minX
;
239 curs
.HotspotOffsetY
= minY
;
241 curs
.HotspotScale
= _CursorScale
;
242 clamp(curs
.HotspotScale
, 0.f
, 1.f
);
243 // first resampling, same for all cursors
244 tmpSize
= (uint
) (tmpSize
* curs
.HotspotScale
);
245 if (tmpSize
== 0) tmpSize
= 1;
247 if (curs
.HotspotScale
< 1.f
)
249 curs
.Src
.resample(tmpSize
, tmpSize
);
252 // shrink if necessary
253 if (tmpSize
> destWidth
|| tmpSize
> destHeight
) // need to shrink ?
255 // constraint proportions
256 curs
.HotspotScale
*= std::min(float(destWidth
) / tmpSize
, float(destHeight
) / tmpSize
);
257 curs
.Src
.resample(destWidth
, destHeight
);
262 final
.resize(destWidth
, destHeight
);
263 final
.blit(&curs
.Src
, 0, 0);
264 curs
.Src
.swap(final
);
267 if (name
== _CurrName
)
273 // *************************************************************************************
274 void CDriverGL::createCursors()
277 _DefaultCursor
= LoadCursor(NULL
, IDC_ARROW
);
279 #elif defined(NL_OS_MAC)
280 #elif defined(NL_OS_UNIX)
281 _DefaultCursor
= None
;
283 if (_dpy
&& _win
&& _BlankCursor
== EmptyCursor
)
285 // create blank cursor
286 char bm_no_data
[] = { 0,0,0,0,0,0,0,0 };
287 Pixmap pixmap_no_data
= XCreateBitmapFromData (_dpy
, _win
, bm_no_data
, 8, 8);
289 memset(&black
, 0, sizeof (XColor
));
290 black
.flags
= DoRed
| DoGreen
| DoBlue
;
291 _BlankCursor
= XCreatePixmapCursor (_dpy
, pixmap_no_data
, pixmap_no_data
, &black
, &black
, 0, 0);
292 XFreePixmap(_dpy
, pixmap_no_data
);
297 // *************************************************************************************
298 void CDriverGL::releaseCursors()
301 SetClassLongPtr(_win
, GCLP_HCURSOR
, 0);
302 #elif defined(NL_OS_MAC)
303 #elif defined(NL_OS_UNIX)
304 XUndefineCursor(_dpy
, _win
);
305 XFreeCursor(_dpy
, _BlankCursor
);
311 // *************************************************************************************
312 void CDriverGL::updateCursor(bool forceRebuild
)
314 setCursor(_CurrName
, _CurrCol
, _CurrRot
, _CurrHotSpotX
, _CurrHotSpotY
, forceRebuild
);
317 // *************************************************************************************
318 void CDriverGL::setCursor(const std::string
&name
, NLMISC::CRGBA col
, uint8 rot
, sint hotSpotX
, sint hotSpotY
, bool forceRebuild
)
320 // don't update cursor if it's hidden or if custom cursors are not suppported
321 if (!isAlphaBlendedCursorSupported() || _CurrName
== "none") return;
326 _CurrHotSpotX
= hotSpotX
;
327 _CurrHotSpotY
= hotSpotY
;
329 // cursor has to be changed next time
330 if (_CurrName
.empty()) return;
332 if (rot
> 3) rot
= 3; // same than 'CViewRenderer::drawRotFlipBitmapTiled
334 TCursorMap::iterator it
= _Cursors
.find(name
);
336 nlCursor cursorHandle
= _DefaultCursor
;
338 if (it
!= _Cursors
.end())
340 // Update cursor if modified or not already built
341 CCursor
&curs
= it
->second
;
342 hotSpotX
= (sint
) (curs
.HotspotScale
* (hotSpotX
- curs
.HotspotOffsetX
));
343 hotSpotY
= (sint
) (curs
.HotspotScale
* ((curs
.OrigHeight
- hotSpotY
) - curs
.HotspotOffsetY
));
344 if (curs
.Cursor
== EmptyCursor
||
345 curs
.HotSpotX
!= hotSpotX
||
346 curs
.HotSpotY
!= hotSpotY
||
349 curs
.ColorDepth
!= _ColorDepth
||
354 curs
.Cursor
= buildCursor(curs
.Src
, col
, rot
, hotSpotX
, hotSpotY
);
357 curs
.HotSpotX
= hotSpotX
;
358 curs
.HotSpotY
= hotSpotY
;
359 curs
.ColorDepth
= _ColorDepth
;
360 #if defined(NL_OS_UNIX) && !defined(NL_OS_MAC)
364 cursorHandle
= curs
.Cursor
? curs
.Cursor
: _DefaultCursor
;
367 if (isSystemCursorInClientArea() || isSystemCursorCaptured() || forceRebuild
)
369 // if (CInputHandlerManager::getInstance()->hasFocus())
372 ::SetCursor(cursorHandle
);
373 SetClassLongPtr(_win
, GCLP_HCURSOR
, (LONG_PTR
) cursorHandle
); // set default mouse icon to the last one
375 #elif defined(NL_OS_MAC)
376 #elif defined(NL_OS_UNIX)
377 if (cursorHandle
== _DefaultCursor
)
379 XUndefineCursor(_dpy
, _win
);
383 XDefineCursor(_dpy
, _win
, cursorHandle
);
390 // *************************************************************************************
391 void CDriverGL::setCursorScale(float scale
)
393 _CursorScale
= scale
;
396 // *************************************************************************************
397 nlCursor
CDriverGL::buildCursor(const CBitmap
&src
, NLMISC::CRGBA col
, uint8 rot
, sint hotSpotX
, sint hotSpotY
)
399 nlassert(isAlphaBlendedCursorSupported());
401 uint mouseW
= 32, mouseH
= 32;
402 getBestCursorSize(src
.getWidth(), src
.getHeight(), mouseW
, mouseH
);
404 CBitmap rotSrc
= src
;
405 if (rot
> 3) rot
= 3; // mimic behavior of 'CViewRenderer::drawRotFlipBitmapTiled' (why not rot & 3 ??? ...)
409 case 1: rotSrc
.rot90CW(); break;
410 case 2: rotSrc
.rot90CW(); rotSrc
.rot90CW(); break;
411 case 3: rotSrc
.rot90CCW(); break;
414 // create a cursor from bitmap
415 nlCursor result
= EmptyCursor
;
416 convertBitmapToCursor(rotSrc
, result
, mouseW
, mouseH
, _ColorDepth
== ColorDepth16
? 16:32, col
, hotSpotX
, hotSpotY
);
421 // *************************************************************************************
422 void CDriverGL::setSystemArrow()
424 H_AUTO_OGL(CDriverGL_setSystemArrow
);
427 if (isSystemCursorInClientArea() || isSystemCursorCaptured())
429 SetCursor(_DefaultCursor
);
432 // set default mouse icon to the default one
433 SetClassLongPtr(_win
, GCLP_HCURSOR
, (LONG_PTR
) _DefaultCursor
);
434 #elif defined(NL_OS_MAC)
435 #elif defined(NL_OS_UNIX)
436 XUndefineCursor(_dpy
, _win
);
440 // ***************************************************************************
441 void CDriverGL::showCursor(bool b
)
443 H_AUTO_OGL(CDriverGL_showCursor
);
445 if (_win
== EmptyWindow
)
452 // update current hardware icon to avoid to have the plain arrow
455 while (ShowCursor(b
) < 0)
460 while (ShowCursor(b
) >= 0)
464 #elif defined(NL_OS_MAC)
466 // Mac OS manages a show/hide counter for the cursor, so hiding the cursor
467 // twice requires two calls to "show" to make the cursor visible again.
468 // Since other platforms seem to not do this, the functionality is masked here
469 // by only calling hide if the cursor is visible and only calling show if
470 // the cursor was hidden.
472 CGDisplayErr error
= kCGErrorSuccess
;
473 static bool visible
= true;
477 error
= CGDisplayShowCursor(kCGDirectMainDisplay
);
480 else if(!b
&& visible
)
482 error
= CGDisplayHideCursor(kCGDirectMainDisplay
);
486 if(error
!= kCGErrorSuccess
)
487 nlerror("cannot show / hide cursor");
489 #elif defined (NL_OS_UNIX)
493 XDefineCursor(_dpy
, _win
, _BlankCursor
);
501 // update current hardware icon to avoid to have the plain arrow
507 // ***************************************************************************
508 void CDriverGL::setMousePos(float x
, float y
)
510 H_AUTO_OGL(CDriverGL_setMousePos
)
512 if (_win
== EmptyWindow
|| !_WindowFocus
)
515 sint x1
= (sint
)((float)_CurrentMode
.Width
*x
);
516 sint y1
= (sint
)((float)_CurrentMode
.Height
*(1.0f
-y
));
520 // NeL window coordinate to MSWindows coordinates
524 ClientToScreen (_win
, &pt
);
525 SetCursorPos(pt
.x
, pt
.y
);
527 #elif defined(NL_OS_MAC)
529 // CG wants absolute coordinates related to first screen's top left
531 // get the first screen's (conaints menubar) rect (this is not mainScreen)
532 NSRect firstScreenRect
= [[[NSScreen screens
] objectAtIndex
:0] frame
];
534 // get the rect (position, size) of the window
536 if([containerView() isInFullScreenMode
])
537 windowRect
= [[[containerView() window
] screen
] frame
];
539 windowRect
= [[containerView() window
] frame
];
541 // get the view's rect for height and width
542 NSRect viewRect
= [containerView() frame
];
544 // set the cursor position
545 CGDisplayErr error
= CGDisplayMoveCursorToPoint(
546 kCGDirectMainDisplay
, CGPointMake(
547 windowRect
.origin
.x
+ (viewRect
.size
.width
* x
),
548 firstScreenRect
.size
.height
- windowRect
.origin
.y
-
549 viewRect
.size
.height
+ ((1.0 - y
) * viewRect
.size
.height
)));
551 if(error
!= kCGErrorSuccess
)
552 nlerror("cannot set mouse position");
554 #elif defined (NL_OS_UNIX)
556 XWarpPointer (_dpy
, None
, _win
, None
, None
, None
, None
, x1
, y1
);
561 // ***************************************************************************
562 void CDriverGL::setCapture (bool b
)
564 H_AUTO_OGL(CDriverGL_setCapture
);
568 if (b
&& isSystemCursorInClientArea() && !isSystemCursorCaptured())
572 else if (!b
&& isSystemCursorCaptured())
574 // if hardware mouse and not in client area, then force to update its aspect by updating its pos
575 if (!isSystemCursorInClientArea())
584 #elif defined(NL_OS_MAC)
586 // no need to capture
589 #elif defined (NL_OS_UNIX)
591 if(b
/* && isSystemCursorInClientArea() && !isSystemCursorCaptured()*/) // capture the cursor.
593 // capture the cursor
594 XGrabPointer(_dpy
, _win
, True
, 0, GrabModeAsync
, GrabModeAsync
, _win
, None
, CurrentTime
);
595 _MouseCaptured
= true;
597 else if (!b
/* && isSystemCursorCaptured()*/)
599 // release the cursor
600 XUngrabPointer(_dpy
, CurrentTime
);
601 _MouseCaptured
= false;
607 // ***************************************************************************
608 bool CDriverGL::isSystemCursorInClientArea()
610 if (!_CurrentMode
.Windowed
)
613 return IsWindowVisible(_win
) != FALSE
;
621 // the mouse should be in the client area of the window
622 if (!GetCursorPos(&cursPos
))
626 HWND wnd
= WindowFromPoint(cursPos
);
629 return false; // not the same window
631 // want that the mouse be in the client area
633 if (!GetClientRect(_win
, &clientRect
))
638 tl
.x
= clientRect
.left
;
639 tl
.y
= clientRect
.top
;
640 br
.x
= clientRect
.right
;
641 br
.y
= clientRect
.bottom
;
642 if (!ClientToScreen(_win
, &tl
))
646 if (!ClientToScreen(_win
, &br
))
650 if ((cursPos
.x
< tl
.x
) || (cursPos
.x
>= br
.x
) || (cursPos
.y
< tl
.y
) || (cursPos
.y
>= br
.y
))
654 #elif defined(NL_OS_MAC)
655 // TODO: implement this
656 #elif defined (NL_OS_UNIX)
657 // TODO: implement this
659 // TODO: probably wrong if NeL window is docked inside parent (ie QT widget)
666 // ***************************************************************************
667 bool CDriverGL::isSystemCursorCaptured()
669 H_AUTO_OGL(CDriverGL_isSystemCursorCaptured
);
672 return GetCapture() == _win
;
674 return _MouseCaptured
;
678 bool CDriverGL::getBestCursorSize(uint srcWidth
, uint srcHeight
, uint
&dstWidth
, uint
&dstHeight
)
682 // Windows provides default size for cursors
683 dstWidth
= (uint
)GetSystemMetrics(SM_CXCURSOR
);
684 dstHeight
= (uint
)GetSystemMetrics(SM_CYCURSOR
);
686 #elif defined(NL_OS_MAC)
687 #elif defined(NL_OS_UNIX)
689 Status status
= XQueryBestCursor(_dpy
, _win
, srcWidth
, srcHeight
, &dstWidth
, &dstHeight
);
693 nlwarning("XQueryBestCursor failed");
701 bool CDriverGL::convertBitmapToCursor(const NLMISC::CBitmap
&bitmap
, nlCursor
&cursor
, uint iconWidth
, uint iconHeight
, uint iconDepth
, const NLMISC::CRGBA
&col
, sint hotSpotX
, sint hotSpotY
)
703 #if defined(NL_OS_WINDOWS)
705 return convertBitmapToIcon(bitmap
, cursor
, iconWidth
, iconHeight
, iconDepth
, col
, hotSpotX
, hotSpotY
, true);
707 #elif defined(NL_OS_UNIX) && !defined(NL_OS_MAC)
709 CBitmap src
= bitmap
;
711 // resample bitmap if necessary
712 if (src
.getWidth() != iconWidth
|| src
.getHeight() != iconHeight
)
714 src
.resample(iconWidth
, iconHeight
);
718 colorBm
.resize(iconWidth
, iconHeight
, CBitmap::RGBA
);
719 const CRGBA
*srcColorPtr
= (CRGBA
*) &(src
.getPixels()[0]);
720 const CRGBA
*srcColorPtrLast
= srcColorPtr
+ (iconWidth
* iconHeight
);
721 CRGBA
*destColorPtr
= (CRGBA
*) &(colorBm
.getPixels()[0]);
726 destColorPtr
->modulateFromColor(*srcColorPtr
, col
);
728 // X11 wants BGRA pixels : swap red and blue channels
729 std::swap(destColorPtr
->R
, destColorPtr
->B
);
731 // premultiplied alpha
732 if (destColorPtr
->A
< 255)
734 destColorPtr
->R
= (destColorPtr
->R
* destColorPtr
->A
) / 255;
735 destColorPtr
->G
= (destColorPtr
->G
* destColorPtr
->A
) / 255;
736 destColorPtr
->B
= (destColorPtr
->B
* destColorPtr
->A
) / 255;
742 while (srcColorPtr
!= srcColorPtrLast
);
746 if (XcursorSupportsARGB(_dpy
))
748 XcursorImage
*image
= XcursorImageCreate(iconWidth
, iconHeight
);
752 nlwarning("Failed to create a XcusorImage with size %ux%u", iconWidth
, iconHeight
);
756 image
->xhot
= (uint
)hotSpotX
;
757 image
->yhot
= (uint
)hotSpotY
;
759 memcpy(image
->pixels
, &colorBm
.getPixels(0)[0], colorBm
.getSize()*4);
761 cursor
= XcursorImageLoadCursor(_dpy
, image
);
763 XcursorImageDestroy(image
);
767 #endif // HAVE_XCURSOR
771 if (_xrender_version
> 0)
773 // use malloc() because X will free() data itself
774 CRGBA
*src32
= (CRGBA
*)malloc(colorBm
.getSize()*4);
775 memcpy(src32
, &colorBm
.getPixels(0)[0], colorBm
.getSize()*4);
777 uint size
= iconWidth
* iconHeight
;
779 sint screen
= DefaultScreen(_dpy
);
780 Visual
*visual
= DefaultVisual(_dpy
, screen
);
784 nlwarning("Failed to get a default visual for screen %d", screen
);
788 // Create the icon image
789 XImage
* image
= XCreateImage(_dpy
, visual
, 32, ZPixmap
, 0, (char*)src32
, iconWidth
, iconHeight
, 32, 0);
793 nlwarning("Failed to set the window's icon");
797 // Create the icon pixmap
798 Pixmap pixmap
= XCreatePixmap(_dpy
, _win
, iconWidth
, iconHeight
, 32 /* defDepth */);
802 nlwarning("Failed to create a pixmap %ux%ux%d", iconWidth
, iconHeight
, 32);
806 // Create the icon graphic contest
807 GC gc
= XCreateGC(_dpy
, pixmap
, 0, NULL
);
811 nlwarning("Failed to create a GC");
815 sint res
= XPutImage(_dpy
, pixmap
, gc
, image
, 0, 0, 0, 0, iconWidth
, iconHeight
);
819 nlwarning("XPutImage failed with code %d", res
);
822 if (!XFreeGC(_dpy
, gc
))
824 nlwarning("XFreeGC failed");
833 XDestroyImage(image
);
835 XRenderPictFormat
*format
= XRenderFindStandardFormat(_dpy
, PictStandardARGB32
);
839 nlwarning("Failed to find a standard format");
843 Picture picture
= XRenderCreatePicture(_dpy
, pixmap
, format
, 0, 0);
847 nlwarning("Failed to create picture");
851 cursor
= XRenderCreateCursor(_dpy
, picture
, (uint
)hotSpotX
, (uint
)hotSpotY
);
855 nlwarning("Failed to create cursor");
859 XRenderFreePicture(_dpy
, picture
);
861 if (!XFreePixmap(_dpy
, pixmap
))
863 nlwarning("XFreePixmap failed");
869 #endif // HAVE_XRENDER