1 /* 2 * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #include <stdlib.h> 27 #include <string.h> 28 29 #include "sun_java2d_opengl_GLXGraphicsConfig.h" 30 31 #include "jni.h" 32 #include "jlong.h" 33 #include "GLXGraphicsConfig.h" 34 #include "GLXSurfaceData.h" 35 #include "awt_GraphicsEnv.h" 36 #include "awt_util.h" 37 38 #ifndef HEADLESS 39 40 extern Bool usingXinerama; 41 42 /** 43 * This is a globally shared context used when creating textures. When any 44 * new contexts are created, they specify this context as the "share list" 45 * context, which means any texture objects created when this shared context 46 * is current will be available to any other context. 47 */ 48 static GLXContext sharedContext = 0; 49 50 /** 51 * Attempts to initialize GLX and the core OpenGL library. For this method 52 * to return JNI_TRUE, the following must be true: 53 * - libGL must be loaded successfully (via dlopen) 54 * - all function symbols from libGL must be available and loaded properly 55 * - the GLX extension must be available through X11 56 * - client GLX version must be >= 1.3 57 * If any of these requirements are not met, this method will return 58 * JNI_FALSE, indicating there is no hope of using GLX/OpenGL for any 59 * GraphicsConfig in the environment. 60 */ 61 static jboolean 62 GLXGC_InitGLX() 63 { 64 int errorbase, eventbase; 65 const char *version; 66 67 J2dRlsTraceLn(J2D_TRACE_INFO, "GLXGC_InitGLX"); 68 69 if (!OGLFuncs_OpenLibrary()) { 70 return JNI_FALSE; 71 } 72 73 if (!OGLFuncs_InitPlatformFuncs() || 74 !OGLFuncs_InitBaseFuncs() || 75 !OGLFuncs_InitExtFuncs()) 76 { 77 OGLFuncs_CloseLibrary(); 78 return JNI_FALSE; 79 } 80 81 if (!j2d_glXQueryExtension(awt_display, &errorbase, &eventbase)) { 82 J2dRlsTraceLn(J2D_TRACE_ERROR, 83 "GLXGC_InitGLX: GLX extension is not present"); 84 OGLFuncs_CloseLibrary(); 85 return JNI_FALSE; 86 } 87 88 version = j2d_glXGetClientString(awt_display, GLX_VERSION); 89 if (version == NULL) { 90 J2dRlsTraceLn(J2D_TRACE_ERROR, 91 "GLXGC_InitGLX: could not query GLX version"); 92 OGLFuncs_CloseLibrary(); 93 return JNI_FALSE; 94 } 95 96 // we now only verify that the client GLX version is >= 1.3 (if the 97 // server does not support GLX 1.3, then we will find that out later 98 // when we attempt to create a GLXFBConfig) 99 J2dRlsTraceLn1(J2D_TRACE_INFO, 100 "GLXGC_InitGLX: client GLX version=%s", version); 101 if (!((version[0] == '1' && version[2] >= '3') || (version[0] > '1'))) { 102 J2dRlsTraceLn(J2D_TRACE_ERROR, 103 "GLXGC_InitGLX: invalid GLX version; 1.3 is required"); 104 OGLFuncs_CloseLibrary(); 105 return JNI_FALSE; 106 } 107 108 return JNI_TRUE; 109 } 110 111 /** 112 * Returns JNI_TRUE if GLX is available for the current display. Note that 113 * this method will attempt to initialize GLX (and all the necessary function 114 * symbols) if it has not been already. The AWT_LOCK must be acquired before 115 * calling this method. 116 */ 117 jboolean 118 GLXGC_IsGLXAvailable() 119 { 120 static jboolean glxAvailable = JNI_FALSE; 121 static jboolean firstTime = JNI_TRUE; 122 123 J2dTraceLn(J2D_TRACE_INFO, "GLXGC_IsGLXAvailable"); 124 125 if (firstTime) { 126 glxAvailable = GLXGC_InitGLX(); 127 firstTime = JNI_FALSE; 128 } 129 130 return glxAvailable; 131 } 132 133 /** 134 * Disposes all memory and resources allocated for the given OGLContext. 135 */ 136 static void 137 GLXGC_DestroyOGLContext(OGLContext *oglc) 138 { 139 GLXCtxInfo *ctxinfo; 140 141 J2dTraceLn(J2D_TRACE_INFO, "GLXGC_DestroyOGLContext"); 142 143 if (oglc == NULL) { 144 J2dRlsTraceLn(J2D_TRACE_ERROR, 145 "GLXGC_DestroyOGLContext: context is null"); 146 return; 147 } 148 149 // at this point, this context will be current to its scratch surface 150 // so the following GL/GLX operations should be safe... 151 152 OGLContext_DestroyContextResources(oglc); 153 154 ctxinfo = (GLXCtxInfo *)oglc->ctxInfo; 155 if (ctxinfo != NULL) { 156 // release the current context before we continue 157 j2d_glXMakeContextCurrent(awt_display, None, None, NULL); 158 159 if (ctxinfo->context != 0) { 160 j2d_glXDestroyContext(awt_display, ctxinfo->context); 161 } 162 if (ctxinfo->scratchSurface != 0) { 163 j2d_glXDestroyPbuffer(awt_display, ctxinfo->scratchSurface); 164 } 165 166 free(ctxinfo); 167 } 168 169 free(oglc); 170 } 171 172 /** 173 * Disposes all memory and resources associated with the given 174 * GLXGraphicsConfigInfo (including its native OGLContext data). 175 */ 176 void 177 OGLGC_DestroyOGLGraphicsConfig(jlong pConfigInfo) 178 { 179 GLXGraphicsConfigInfo *glxinfo = 180 (GLXGraphicsConfigInfo *)jlong_to_ptr(pConfigInfo); 181 182 J2dTraceLn(J2D_TRACE_INFO, "OGLGC_DestroyOGLGraphicsConfig"); 183 184 if (glxinfo == NULL) { 185 J2dRlsTraceLn(J2D_TRACE_ERROR, 186 "OGLGC_DestroyOGLGraphicsConfig: info is null"); 187 return; 188 } 189 190 if (glxinfo->context != NULL) { 191 GLXGC_DestroyOGLContext(glxinfo->context); 192 } 193 194 free(glxinfo); 195 } 196 197 /** 198 * Attempts to create a new GLXFBConfig for the requested screen and visual. 199 * If visualid is 0, this method will iterate through all GLXFBConfigs (if 200 * any) that match the requested attributes and will attempt to find an 201 * fbconfig with a minimal combined depth+stencil buffer. Note that we 202 * currently only need depth capabilities (for shape clipping purposes), but 203 * glXChooseFBConfig() will often return a list of fbconfigs with the largest 204 * depth buffer (and stencil) sizes at the top of the list. Therefore, we 205 * scan through the whole list to find the most VRAM-efficient fbconfig. 206 * If visualid is non-zero, the GLXFBConfig associated with the given visual 207 * is chosen (assuming it meets the requested attributes). If there are no 208 * valid GLXFBConfigs available, this method returns 0. 209 */ 210 static GLXFBConfig 211 GLXGC_InitFBConfig(JNIEnv *env, jint screennum, VisualID visualid) 212 { 213 GLXFBConfig *fbconfigs; 214 GLXFBConfig chosenConfig = 0; 215 int nconfs, i; 216 int attrlist[] = {GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT, 217 GLX_RENDER_TYPE, GLX_RGBA_BIT, 218 GLX_CONFIG_CAVEAT, GLX_NONE, // avoid "slow" configs 219 GLX_DEPTH_SIZE, 16, // anything >= 16 will work for us 220 0}; 221 222 // this is the initial minimum value for the combined depth+stencil size 223 // (we initialize it to some absurdly high value; realistic values will 224 // be much less than this number) 225 int minDepthPlusStencil = 512; 226 227 J2dRlsTraceLn2(J2D_TRACE_INFO, "GLXGC_InitFBConfig: scn=%d vis=0x%x", 228 screennum, visualid); 229 230 // find all fbconfigs for this screen with the provided attributes 231 fbconfigs = j2d_glXChooseFBConfig(awt_display, screennum, 232 attrlist, &nconfs); 233 234 if ((fbconfigs == NULL) || (nconfs <= 0)) { 235 J2dRlsTraceLn(J2D_TRACE_ERROR, 236 "GLXGC_InitFBConfig: could not find any valid fbconfigs"); 237 return 0; 238 } 239 240 J2dRlsTraceLn(J2D_TRACE_VERBOSE, " candidate fbconfigs:"); 241 242 // iterate through the list of fbconfigs, looking for the one that matches 243 // the requested VisualID and supports RGBA rendering as well as the 244 // creation of windows and pbuffers 245 for (i = 0; i < nconfs; i++) { 246 XVisualInfo *xvi; 247 VisualID fbvisualid; 248 GLXFBConfig fbc = fbconfigs[i]; 249 250 // get VisualID from GLXFBConfig 251 xvi = j2d_glXGetVisualFromFBConfig(awt_display, fbc); 252 if (xvi == NULL) { 253 continue; 254 } 255 fbvisualid = xvi->visualid; 256 XFree(xvi); 257 258 if (visualid == 0 || visualid == fbvisualid) { 259 int dtype, rtype, depth, stencil, db, alpha, gamma; 260 261 // get GLX-specific attributes from GLXFBConfig 262 j2d_glXGetFBConfigAttrib(awt_display, fbc, 263 GLX_DRAWABLE_TYPE, &dtype); 264 j2d_glXGetFBConfigAttrib(awt_display, fbc, 265 GLX_RENDER_TYPE, &rtype); 266 j2d_glXGetFBConfigAttrib(awt_display, fbc, 267 GLX_DEPTH_SIZE, &depth); 268 j2d_glXGetFBConfigAttrib(awt_display, fbc, 269 GLX_STENCIL_SIZE, &stencil); 270 271 // these attributes don't affect our decision, but they are 272 // interesting for trace logs, so we will query them anyway 273 j2d_glXGetFBConfigAttrib(awt_display, fbc, 274 GLX_DOUBLEBUFFER, &db); 275 j2d_glXGetFBConfigAttrib(awt_display, fbc, 276 GLX_ALPHA_SIZE, &alpha); 277 278 J2dRlsTrace5(J2D_TRACE_VERBOSE, 279 "[V] id=0x%x db=%d alpha=%d depth=%d stencil=%d valid=", 280 fbvisualid, db, alpha, depth, stencil); 281 282 if ((dtype & GLX_WINDOW_BIT) && 283 (dtype & GLX_PBUFFER_BIT) && 284 (rtype & GLX_RGBA_BIT) && 285 (depth >= 16)) 286 { 287 if (visualid == 0) { 288 // when visualid == 0, we loop through all configs 289 // looking for an fbconfig that has the smallest combined 290 // depth+stencil size (this keeps VRAM usage to a minimum) 291 if ((depth + stencil) < minDepthPlusStencil) { 292 J2dRlsTrace(J2D_TRACE_VERBOSE, "true\n"); 293 minDepthPlusStencil = depth + stencil; 294 chosenConfig = fbc; 295 } else { 296 J2dRlsTrace(J2D_TRACE_VERBOSE, 297 "false (large depth)\n"); 298 } 299 continue; 300 } else { 301 // in this case, visualid == fbvisualid, which means 302 // we've found a valid fbconfig corresponding to the 303 // requested VisualID, so break out of the loop 304 J2dRlsTrace(J2D_TRACE_VERBOSE, "true\n"); 305 chosenConfig = fbc; 306 break; 307 } 308 } else { 309 J2dRlsTrace(J2D_TRACE_VERBOSE, "false (bad match)\n"); 310 } 311 } 312 } 313 314 // free the list of fbconfigs 315 XFree(fbconfigs); 316 317 if (chosenConfig == 0) { 318 J2dRlsTraceLn(J2D_TRACE_ERROR, 319 "GLXGC_InitFBConfig: could not find an appropriate fbconfig"); 320 return 0; 321 } 322 323 return chosenConfig; 324 } 325 326 /** 327 * Returns the X11 VisualID that corresponds to the best GLXFBConfig for the 328 * given screen. If no valid visual could be found, this method returns zero. 329 * Note that this method will attempt to initialize GLX (and all the 330 * necessary function symbols) if it has not been already. The AWT_LOCK 331 * must be acquired before calling this method. 332 */ 333 VisualID 334 GLXGC_FindBestVisual(JNIEnv *env, jint screen) 335 { 336 GLXFBConfig fbc; 337 XVisualInfo *xvi; 338 VisualID visualid; 339 340 J2dRlsTraceLn1(J2D_TRACE_INFO, "GLXGC_FindBestVisual: scn=%d", screen); 341 342 if (!GLXGC_IsGLXAvailable()) { 343 J2dRlsTraceLn(J2D_TRACE_ERROR, 344 "GLXGC_FindBestVisual: could not initialize GLX"); 345 return 0; 346 } 347 348 fbc = GLXGC_InitFBConfig(env, screen, 0); 349 if (fbc == 0) { 350 J2dRlsTraceLn(J2D_TRACE_ERROR, 351 "GLXGC_FindBestVisual: could not find best visual"); 352 return 0; 353 } 354 355 xvi = j2d_glXGetVisualFromFBConfig(awt_display, fbc); 356 if (xvi == NULL) { 357 J2dRlsTraceLn(J2D_TRACE_ERROR, 358 "GLXGC_FindBestVisual: could not get visual for fbconfig"); 359 return 0; 360 } 361 362 visualid = xvi->visualid; 363 XFree(xvi); 364 365 J2dRlsTraceLn2(J2D_TRACE_INFO, 366 "GLXGC_FindBestVisual: chose 0x%x as the best visual for screen %d", 367 visualid, screen); 368 369 return visualid; 370 } 371 372 /** 373 * Creates a scratch pbuffer, which can be used to make a context current 374 * for extension queries, etc. 375 */ 376 static GLXPbuffer 377 GLXGC_InitScratchPbuffer(GLXFBConfig fbconfig) 378 { 379 int pbattrlist[] = {GLX_PBUFFER_WIDTH, 4, 380 GLX_PBUFFER_HEIGHT, 4, 381 GLX_PRESERVED_CONTENTS, GL_FALSE, 382 0}; 383 384 J2dTraceLn(J2D_TRACE_INFO, "GLXGC_InitScratchPbuffer"); 385 386 return j2d_glXCreatePbuffer(awt_display, fbconfig, pbattrlist); 387 } 388 389 /** 390 * Initializes a new OGLContext, which includes the native GLXContext handle 391 * and some other important information such as the associated GLXFBConfig. 392 */ 393 static OGLContext * 394 GLXGC_InitOGLContext(GLXFBConfig fbconfig, GLXContext context, 395 GLXPbuffer scratch, jint caps) 396 { 397 OGLContext *oglc; 398 GLXCtxInfo *ctxinfo; 399 400 J2dTraceLn(J2D_TRACE_INFO, "GLXGC_InitOGLContext"); 401 402 oglc = (OGLContext *)malloc(sizeof(OGLContext)); 403 if (oglc == NULL) { 404 J2dRlsTraceLn(J2D_TRACE_ERROR, 405 "GLXGC_InitOGLContext: could not allocate memory for oglc"); 406 return NULL; 407 } 408 409 memset(oglc, 0, sizeof(OGLContext)); 410 411 ctxinfo = (GLXCtxInfo *)malloc(sizeof(GLXCtxInfo)); 412 if (ctxinfo == NULL) { 413 J2dRlsTraceLn(J2D_TRACE_ERROR, 414 "GLXGC_InitOGLContext: could not allocate memory for ctxinfo"); 415 free(oglc); 416 return NULL; 417 } 418 419 ctxinfo->fbconfig = fbconfig; 420 ctxinfo->context = context; 421 ctxinfo->scratchSurface = scratch; 422 oglc->ctxInfo = ctxinfo; 423 oglc->caps = caps; 424 425 return oglc; 426 } 427 428 #endif /* !HEADLESS */ 429 430 /** 431 * Determines whether the GLX pipeline can be used for a given GraphicsConfig 432 * provided its screen number and visual ID. If the minimum requirements are 433 * met, the native GLXGraphicsConfigInfo structure is initialized for this 434 * GraphicsConfig with the necessary information (GLXFBConfig, etc.) 435 * and a pointer to this structure is returned as a jlong. If 436 * initialization fails at any point, zero is returned, indicating that GLX 437 * cannot be used for this GraphicsConfig (we should fallback on the existing 438 * X11 pipeline). 439 */ 440 JNIEXPORT jlong JNICALL 441 Java_sun_java2d_opengl_GLXGraphicsConfig_getGLXConfigInfo(JNIEnv *env, 442 jclass glxgc, 443 jint screennum, 444 jint visnum) 445 { 446 #ifndef HEADLESS 447 OGLContext *oglc; 448 GLXFBConfig fbconfig; 449 GLXContext context; 450 GLXPbuffer scratch; 451 GLXGraphicsConfigInfo *glxinfo; 452 jint caps = CAPS_EMPTY; 453 int db; 454 const unsigned char *versionstr; 455 456 J2dRlsTraceLn(J2D_TRACE_INFO, "GLXGraphicsConfig_getGLXConfigInfo"); 457 458 if (usingXinerama) { 459 // when Xinerama is enabled, the screen ID needs to be 0 460 screennum = 0; 461 } 462 463 fbconfig = GLXGC_InitFBConfig(env, screennum, (VisualID)visnum); 464 if (fbconfig == 0) { 465 J2dRlsTraceLn(J2D_TRACE_ERROR, 466 "GLXGraphicsConfig_getGLXConfigInfo: could not create fbconfig"); 467 return 0L; 468 } 469 470 if (sharedContext == 0) { 471 // create the one shared context 472 sharedContext = j2d_glXCreateNewContext(awt_display, fbconfig, 473 GLX_RGBA_TYPE, 0, GL_TRUE); 474 if (sharedContext == 0) { 475 J2dRlsTraceLn(J2D_TRACE_ERROR, 476 "GLXGraphicsConfig_getGLXConfigInfo: could not create shared context"); 477 return 0L; 478 } 479 } 480 481 // create the GLXContext for this GLXGraphicsConfig 482 context = j2d_glXCreateNewContext(awt_display, fbconfig, 483 GLX_RGBA_TYPE, sharedContext, 484 GL_TRUE); 485 if (context == 0) { 486 J2dRlsTraceLn(J2D_TRACE_ERROR, 487 "GLXGraphicsConfig_getGLXConfigInfo: could not create GLX context"); 488 return 0L; 489 } 490 491 // this is pretty sketchy, but it seems to be the easiest way to create 492 // some form of GLXDrawable using only the display and a GLXFBConfig 493 // (in order to make the context current for checking the version, 494 // extensions, etc)... 495 scratch = GLXGC_InitScratchPbuffer(fbconfig); 496 if (scratch == 0) { 497 J2dRlsTraceLn(J2D_TRACE_ERROR, 498 "GLXGraphicsConfig_getGLXConfigInfo: could not create scratch pbuffer"); 499 j2d_glXDestroyContext(awt_display, context); 500 return 0L; 501 } 502 503 // the context must be made current before we can query the 504 // version and extension strings 505 j2d_glXMakeContextCurrent(awt_display, scratch, scratch, context); 506 507 versionstr = j2d_glGetString(GL_VERSION); 508 OGLContext_GetExtensionInfo(env, &caps); 509 510 // destroy the temporary resources 511 j2d_glXMakeContextCurrent(awt_display, None, None, NULL); 512 513 J2dRlsTraceLn1(J2D_TRACE_INFO, 514 "GLXGraphicsConfig_getGLXConfigInfo: OpenGL version=%s", 515 (versionstr == NULL) ? "null" : (char *)versionstr); 516 517 if (!OGLContext_IsVersionSupported(versionstr)) { 518 J2dRlsTraceLn(J2D_TRACE_ERROR, 519 "GLXGraphicsConfig_getGLXConfigInfo: OpenGL 1.2 is required"); 520 j2d_glXDestroyPbuffer(awt_display, scratch); 521 j2d_glXDestroyContext(awt_display, context); 522 return 0L; 523 } 524 525 // get config-specific capabilities 526 j2d_glXGetFBConfigAttrib(awt_display, fbconfig, GLX_DOUBLEBUFFER, &db); 527 if (db) { 528 caps |= CAPS_DOUBLEBUFFERED; 529 } 530 531 // initialize the OGLContext, which wraps the GLXFBConfig and GLXContext 532 oglc = GLXGC_InitOGLContext(fbconfig, context, scratch, caps); 533 if (oglc == NULL) { 534 J2dRlsTraceLn(J2D_TRACE_ERROR, 535 "GLXGraphicsConfig_getGLXConfigInfo: could not create oglc"); 536 j2d_glXDestroyPbuffer(awt_display, scratch); 537 j2d_glXDestroyContext(awt_display, context); 538 return 0L; 539 } 540 541 J2dTraceLn(J2D_TRACE_VERBOSE, 542 "GLXGraphicsConfig_getGLXConfigInfo: finished checking dependencies"); 543 544 // create the GLXGraphicsConfigInfo record for this config 545 glxinfo = (GLXGraphicsConfigInfo *)malloc(sizeof(GLXGraphicsConfigInfo)); 546 if (glxinfo == NULL) { 547 J2dRlsTraceLn(J2D_TRACE_ERROR, 548 "GLXGraphicsConfig_getGLXConfigInfo: could not allocate memory for glxinfo"); 549 GLXGC_DestroyOGLContext(oglc); 550 return 0L; 551 } 552 553 glxinfo->screen = screennum; 554 glxinfo->visual = visnum; 555 glxinfo->context = oglc; 556 glxinfo->fbconfig = fbconfig; 557 558 return ptr_to_jlong(glxinfo); 559 #else 560 return 0L; 561 #endif /* !HEADLESS */ 562 } 563 564 JNIEXPORT void JNICALL 565 Java_sun_java2d_opengl_GLXGraphicsConfig_initConfig(JNIEnv *env, 566 jobject glxgc, 567 jlong aData, 568 jlong configInfo) 569 { 570 #ifndef HEADLESS 571 GLXGraphicsConfigInfo *glxinfo; 572 AwtGraphicsConfigDataPtr configData = 573 (AwtGraphicsConfigDataPtr)jlong_to_ptr(aData); 574 575 J2dTraceLn(J2D_TRACE_INFO, "GLXGraphicsConfig_initConfig"); 576 577 if (configData == NULL) { 578 JNU_ThrowNullPointerException(env, "Native GraphicsConfig missing"); 579 return; 580 } 581 582 glxinfo = (GLXGraphicsConfigInfo *)jlong_to_ptr(configInfo); 583 if (glxinfo == NULL) { 584 JNU_ThrowNullPointerException(env, 585 "GLXGraphicsConfigInfo data missing"); 586 return; 587 } 588 589 configData->glxInfo = glxinfo; 590 #endif /* !HEADLESS */ 591 } 592 593 JNIEXPORT jint JNICALL 594 Java_sun_java2d_opengl_GLXGraphicsConfig_getOGLCapabilities(JNIEnv *env, 595 jclass glxgc, 596 jlong configInfo) 597 { 598 #ifndef HEADLESS 599 GLXGraphicsConfigInfo *glxinfo = 600 (GLXGraphicsConfigInfo *)jlong_to_ptr(configInfo); 601 602 J2dTraceLn(J2D_TRACE_INFO, "GLXGraphicsConfig_getOGLCapabilities"); 603 604 if (glxinfo == NULL || glxinfo->context == NULL) { 605 return CAPS_EMPTY; 606 } 607 608 return glxinfo->context->caps; 609 #else 610 return CAPS_EMPTY; 611 #endif /* !HEADLESS */ 612 }