| 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 "Private/Linux/BsLinuxPlatform.h" |
| 4 | #include "Linux/BsLinuxGLSupport.h" |
| 5 | #include "Linux/BsLinuxContext.h" |
| 6 | #include "Linux/BsLinuxRenderWindow.h" |
| 7 | #include "Linux/BsLinuxVideoModeInfo.h" |
| 8 | #include "BsGLRenderAPI.h" |
| 9 | |
| 10 | namespace bs { namespace ct |
| 11 | { |
| 12 | bool extGLX_ARB_multisample = false; |
| 13 | bool extGLX_ARB_framebuffer_sRGB = false; |
| 14 | bool extGLX_EXT_framebuffer_sRGB = false; |
| 15 | bool extGLX_ARB_create_context = false; |
| 16 | bool extGLX_ARB_create_context_profile = false; |
| 17 | bool extGLX_EXT_swap_control = false; |
| 18 | bool extGLX_MESA_swap_control = false; |
| 19 | bool extGLX_SGI_swap_control = false; |
| 20 | |
| 21 | typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); |
| 22 | glXCreateContextAttribsARBProc glXCreateContextAttribsARB = nullptr; |
| 23 | |
| 24 | bool Load_ARB_create_context() |
| 25 | { |
| 26 | glXCreateContextAttribsARB = |
| 27 | (glXCreateContextAttribsARBProc)glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB" ); |
| 28 | |
| 29 | return glXCreateContextAttribsARB != nullptr; |
| 30 | } |
| 31 | |
| 32 | glXSwapIntervalEXTProc glXSwapIntervalEXT = nullptr; |
| 33 | glXSwapIntervalMESAProc glXSwapIntervalMESA = nullptr; |
| 34 | glXSwapIntervalSGIProc glXSwapIntervalSGI = nullptr; |
| 35 | |
| 36 | glXChooseFBConfigProc glXChooseFBConfig = nullptr; |
| 37 | glXGetFBConfigAttribProc glXGetFBConfigAttrib = nullptr; |
| 38 | glXGetVisualFromFBConfigProc glXGetVisualFromFBConfig = nullptr; |
| 39 | |
| 40 | bool Load_EXT_swap_control() |
| 41 | { |
| 42 | glXSwapIntervalEXT = (glXSwapIntervalEXTProc)glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalEXT" ); |
| 43 | |
| 44 | return glXSwapIntervalEXT != nullptr; |
| 45 | } |
| 46 | |
| 47 | bool Load_MESA_swap_control() |
| 48 | { |
| 49 | glXSwapIntervalMESA = (glXSwapIntervalMESAProc)glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalMESA" ); |
| 50 | |
| 51 | return glXSwapIntervalMESA != nullptr; |
| 52 | } |
| 53 | |
| 54 | bool Load_SGI_swap_control() |
| 55 | { |
| 56 | glXSwapIntervalSGI = (glXSwapIntervalSGIProc)glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalSGI" ); |
| 57 | |
| 58 | return glXSwapIntervalSGI != nullptr; |
| 59 | } |
| 60 | |
| 61 | typedef bool (*ExtensionFunc)(void); |
| 62 | |
| 63 | struct GLExtension |
| 64 | { |
| 65 | const char* name; |
| 66 | bool* status; |
| 67 | ExtensionFunc func; |
| 68 | }; |
| 69 | |
| 70 | static GLExtension gExtensionMap[] = { |
| 71 | { "GLX_ARB_multisample" , &extGLX_ARB_multisample, nullptr }, |
| 72 | { "GLX_ARB_framebuffer_sRGB" , &extGLX_ARB_framebuffer_sRGB, nullptr }, |
| 73 | { "GLX_EXT_framebuffer_sRGB" , &extGLX_EXT_framebuffer_sRGB, nullptr }, |
| 74 | { "GLX_ARB_create_context" , &extGLX_ARB_create_context, Load_ARB_create_context }, |
| 75 | { "GLX_ARB_create_context_profile" , &extGLX_ARB_create_context_profile, nullptr }, |
| 76 | { "GLX_EXT_swap_control" , &extGLX_EXT_swap_control, Load_EXT_swap_control }, |
| 77 | { "GLX_MESA_swap_control" , &extGLX_MESA_swap_control, Load_MESA_swap_control }, |
| 78 | { "GLX_SGI_swap_control" , &extGLX_SGI_swap_control, Load_SGI_swap_control }, |
| 79 | }; |
| 80 | |
| 81 | LinuxGLSupport::LinuxGLSupport() |
| 82 | { } |
| 83 | |
| 84 | SPtr<bs::RenderWindow> LinuxGLSupport::newWindow(RENDER_WINDOW_DESC& desc, UINT32 windowId, |
| 85 | SPtr<bs::RenderWindow> parentWindow) |
| 86 | { |
| 87 | if(parentWindow != nullptr) |
| 88 | { |
| 89 | ::Window x11window; |
| 90 | parentWindow->getCustomAttribute("WINDOW" , &x11window); |
| 91 | desc.platformSpecific["parentWindowHandle" ] = toString((UINT64)x11window); |
| 92 | } |
| 93 | |
| 94 | bs::LinuxRenderWindow* window = new (bs_alloc<bs::LinuxRenderWindow>()) bs::LinuxRenderWindow(desc, windowId, *this); |
| 95 | return SPtr<bs::RenderWindow>(window, &bs::CoreObject::_delete<bs::LinuxRenderWindow, GenAlloc>); |
| 96 | } |
| 97 | |
| 98 | void LinuxGLSupport::start() |
| 99 | { |
| 100 | // Retrieve all essential extensions |
| 101 | LinuxPlatform::lockX(); |
| 102 | |
| 103 | ::Display* display = LinuxPlatform::getXDisplay(); |
| 104 | const char* glExtensions = glXQueryExtensionsString(display, DefaultScreen(display)); |
| 105 | |
| 106 | const char* iter = glExtensions; |
| 107 | do |
| 108 | { |
| 109 | const char* start = iter; |
| 110 | while(*iter != ' ' && *iter) |
| 111 | iter++; |
| 112 | |
| 113 | const char* end = iter; |
| 114 | std::string name(start, end); |
| 115 | |
| 116 | UINT32 numExtensions = sizeof(gExtensionMap) / sizeof(gExtensionMap[0]); |
| 117 | for (UINT32 i = 0; i < numExtensions; ++i) |
| 118 | { |
| 119 | if(strcmp(name.c_str(), gExtensionMap[i].name) == 0) |
| 120 | { |
| 121 | if(gExtensionMap[i].status != nullptr) |
| 122 | { |
| 123 | if (gExtensionMap[i].func != nullptr) |
| 124 | *gExtensionMap[i].status = gExtensionMap[i].func(); |
| 125 | else |
| 126 | *gExtensionMap[i].status = true; |
| 127 | } |
| 128 | else |
| 129 | { |
| 130 | if (gExtensionMap[i].func != nullptr) |
| 131 | gExtensionMap[i].func(); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | } |
| 136 | |
| 137 | } while(*iter++); |
| 138 | |
| 139 | glXChooseFBConfig = (glXChooseFBConfigProc)glXGetProcAddressARB((const GLubyte*)"glXChooseFBConfig" ); |
| 140 | glXGetFBConfigAttrib = (glXGetFBConfigAttribProc)glXGetProcAddressARB((const GLubyte*)"glXGetFBConfigAttrib" ); |
| 141 | glXGetVisualFromFBConfig = (glXGetVisualFromFBConfigProc)glXGetProcAddressARB((const GLubyte*)"glXGetVisualFromFBConfig" ); |
| 142 | |
| 143 | LinuxPlatform::unlockX(); |
| 144 | } |
| 145 | |
| 146 | void LinuxGLSupport::stop() |
| 147 | { |
| 148 | // Do nothing |
| 149 | } |
| 150 | |
| 151 | SPtr<LinuxContext> LinuxGLSupport::createContext(::Display* x11display, XVisualInfo& visualInfo) |
| 152 | { |
| 153 | GLRenderAPI* rapi = static_cast<GLRenderAPI*>(RenderAPI::instancePtr()); |
| 154 | |
| 155 | // If RenderAPI has initialized a context use that, otherwise we create our own |
| 156 | if (!rapi->_isContextInitialized()) |
| 157 | return bs_shared_ptr_new<LinuxContext>(x11display, visualInfo); |
| 158 | else |
| 159 | { |
| 160 | SPtr<GLContext> context = rapi->_getMainContext(); |
| 161 | return std::static_pointer_cast<LinuxContext>(context); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | void* LinuxGLSupport::getProcAddress(const String& procname) |
| 166 | { |
| 167 | return (void*)glXGetProcAddressARB((const GLubyte*)procname.c_str()); |
| 168 | } |
| 169 | |
| 170 | GLVisualConfig LinuxGLSupport::findBestVisual(::Display* display, bool depthStencil, UINT32 multisample, bool srgb) const |
| 171 | { |
| 172 | INT32 VISUAL_ATTRIBS[] = |
| 173 | { |
| 174 | GLX_X_RENDERABLE, True, |
| 175 | GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, |
| 176 | GLX_RENDER_TYPE, GLX_RGBA_BIT, |
| 177 | GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, |
| 178 | GLX_RED_SIZE, 8, |
| 179 | GLX_GREEN_SIZE, 8, |
| 180 | GLX_BLUE_SIZE, 8, |
| 181 | GLX_ALPHA_SIZE, 8, |
| 182 | GLX_DOUBLEBUFFER, True, |
| 183 | GLX_DEPTH_SIZE, depthStencil ? 24 : 0, |
| 184 | GLX_STENCIL_SIZE, depthStencil ? 8 : 0, |
| 185 | GLX_SAMPLE_BUFFERS, multisample > 1 ? 1 : 0, |
| 186 | 0 |
| 187 | }; |
| 188 | |
| 189 | INT32 numConfigs; |
| 190 | GLXFBConfig* configs = glXChooseFBConfig(display, DefaultScreen(display), VISUAL_ATTRIBS, &numConfigs); |
| 191 | GLVisualCapabilities* caps = bs_stack_new<GLVisualCapabilities>(numConfigs); |
| 192 | |
| 193 | // Find a config that best matches the requested properties |
| 194 | INT32 bestScore = 0; |
| 195 | INT32 bestConfig = -1; |
| 196 | for (int i = 0; i < numConfigs; ++i) |
| 197 | { |
| 198 | INT32 configScore = 0; |
| 199 | |
| 200 | // Depth buffer contributes the most to score |
| 201 | INT32 depth, stencil; |
| 202 | glXGetFBConfigAttrib(display, configs[i], GLX_DEPTH_SIZE, &depth); |
| 203 | glXGetFBConfigAttrib(display, configs[i], GLX_STENCIL_SIZE, &stencil); |
| 204 | |
| 205 | // Depth buffer was requested |
| 206 | if(depthStencil) |
| 207 | { |
| 208 | INT32 score = 0; |
| 209 | if (depth == 24 && stencil == 8) |
| 210 | score = 10000; |
| 211 | else if (depth == 32 && stencil == 8) |
| 212 | score = 9000; |
| 213 | else if (depth == 32) |
| 214 | score = 8000; |
| 215 | else if (depth == 24) |
| 216 | score = 7000; |
| 217 | else if (depth == 16) |
| 218 | score = 6000; |
| 219 | |
| 220 | if (score > 0) |
| 221 | { |
| 222 | configScore += score; |
| 223 | caps[i].depthStencil = true; |
| 224 | } |
| 225 | } |
| 226 | else // Depth buffer not requested, prefer configs without it |
| 227 | { |
| 228 | if(depth == 0 && stencil == 0) |
| 229 | configScore += 10000; |
| 230 | } |
| 231 | |
| 232 | // sRGB contributes second most |
| 233 | if(srgb) |
| 234 | { |
| 235 | INT32 hasSRGB = 0; |
| 236 | |
| 237 | if(extGLX_EXT_framebuffer_sRGB) |
| 238 | glXGetFBConfigAttrib(display, configs[i], GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, &hasSRGB); |
| 239 | |
| 240 | if(!hasSRGB && extGLX_ARB_framebuffer_sRGB) |
| 241 | glXGetFBConfigAttrib(display, configs[i], GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, &hasSRGB); |
| 242 | |
| 243 | if(hasSRGB) |
| 244 | { |
| 245 | configScore += 500; |
| 246 | caps[i].srgb = true; |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | if((multisample >= 1) && extGLX_ARB_multisample) |
| 251 | { |
| 252 | INT32 hasMultisample, numSamples; |
| 253 | glXGetFBConfigAttrib(display, configs[i], GLX_SAMPLE_BUFFERS, &hasMultisample); |
| 254 | glXGetFBConfigAttrib(display, configs[i], GLX_SAMPLES, &numSamples); |
| 255 | |
| 256 | if(hasMultisample && (numSamples <= (INT32)multisample)) |
| 257 | { |
| 258 | configScore += (32 - (multisample - numSamples)) * 10; |
| 259 | caps[i].numSamples = (UINT32)numSamples; |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | if(configScore > bestScore) |
| 264 | { |
| 265 | bestScore = configScore; |
| 266 | bestConfig = i; |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | GLVisualConfig output; |
| 271 | |
| 272 | if(bestConfig == -1) |
| 273 | { |
| 274 | if(numConfigs > 0) |
| 275 | bestConfig = 0; |
| 276 | else |
| 277 | { |
| 278 | // Something went wrong |
| 279 | XFree(configs); |
| 280 | bs_stack_delete(caps, (UINT32) numConfigs); |
| 281 | |
| 282 | return output; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | XVisualInfo* visualInfo = glXGetVisualFromFBConfig(display, configs[bestConfig]); |
| 287 | |
| 288 | output.visualInfo = *visualInfo; |
| 289 | output.caps = caps[bestConfig]; |
| 290 | |
| 291 | XFree(configs); |
| 292 | XFree(visualInfo); |
| 293 | bs_stack_delete(caps, numConfigs); |
| 294 | |
| 295 | return output; |
| 296 | } |
| 297 | |
| 298 | SPtr<VideoModeInfo> LinuxGLSupport::getVideoModeInfo() const |
| 299 | { |
| 300 | return bs_shared_ptr_new<LinuxVideoModeInfo>(); |
| 301 | } |
| 302 | }} |