1 /* 2 * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above 10 * copyright notice, this list of conditions and the following 11 * disclaimer in the documentation and/or other materials provided 12 * with the distribution. 13 * * Neither the name of The Linux Foundation nor the names of its 14 * contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <dlfcn.h> 31 #include "overlay.h" 32 #include "pipes/overlayGenPipe.h" 33 #include "mdp_version.h" 34 #include "qdMetaData.h" 35 #include "qd_utils.h" 36 37 namespace overlay { 38 using namespace utils; 39 using namespace qdutils; 40 41 Overlay::Overlay() { 42 int numPipes = qdutils::MDPVersion::getInstance().getTotalPipes(); 43 PipeBook::NUM_PIPES = (numPipes <= utils::OV_MAX)? numPipes : utils::OV_MAX; 44 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 45 mPipeBook[i].init(); 46 } 47 48 initScalar(); 49 setDMAMultiplexingSupported(); 50 #ifdef USES_POST_PROCESSING 51 initPostProc(); 52 #endif 53 } 54 55 Overlay::~Overlay() { 56 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 57 mPipeBook[i].destroy(); 58 } 59 destroyScalar(); 60 #ifdef USES_POST_PROCESSING 61 destroyPostProc(); 62 #endif 63 } 64 65 void Overlay::configBegin() { 66 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 67 //Mark as available for this round. 68 PipeBook::resetUse(i); 69 PipeBook::resetAllocation(i); 70 } 71 } 72 73 void Overlay::configDone() { 74 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 75 if((PipeBook::isNotUsed(i) && !sessionInProgress((eDest)i)) || 76 isSessionEnded((eDest)i)) { 77 //Forces UNSET on pipes, flushes rotator memory and session, closes 78 //fds 79 mPipeBook[i].destroy(); 80 } 81 } 82 PipeBook::save(); 83 } 84 85 int Overlay::getPipeId(utils::eDest dest) { 86 return mPipeBook[(int)dest].mPipe->getPipeId(); 87 } 88 89 eDest Overlay::getDest(int pipeid) { 90 eDest dest = OV_INVALID; 91 // finding the dest corresponding to the given pipe 92 for(int i=0; i < PipeBook::NUM_PIPES; ++i) { 93 if(mPipeBook[i].valid() && mPipeBook[i].mPipe->getPipeId() == pipeid) { 94 return (eDest)i; 95 } 96 } 97 return dest; 98 } 99 100 eDest Overlay::reservePipe(int pipeid) { 101 eDest dest = getDest(pipeid); 102 PipeBook::setAllocation((int)dest); 103 return dest; 104 } 105 106 eDest Overlay::nextPipe(eMdpPipeType type, const PipeSpecs& pipeSpecs) { 107 eDest dest = OV_INVALID; 108 int dpy = pipeSpecs.dpy; 109 int mixer = pipeSpecs.mixer; 110 int formatType = pipeSpecs.formatClass; 111 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 112 if( (type == OV_MDP_PIPE_ANY || //Pipe type match 113 type == PipeBook::getPipeType((eDest)i)) && 114 (mPipeBook[i].mDisplay == DPY_UNUSED || //Free or same display 115 mPipeBook[i].mDisplay == dpy) && 116 (mPipeBook[i].mMixer == MIXER_UNUSED || //Free or same mixer 117 mPipeBook[i].mMixer == mixer) && 118 (mPipeBook[i].mFormatType == FORMAT_NONE || //Free or same format 119 mPipeBook[i].mFormatType == formatType) && 120 PipeBook::isNotAllocated(i) && //Free pipe 121 ( (sDMAMultiplexingSupported && dpy) || 122 !(sDMAMode == DMA_BLOCK_MODE && //DMA pipe in Line mode 123 PipeBook::getPipeType((eDest)i) == OV_MDP_PIPE_DMA)) ){ 124 //DMA-Multiplexing is only supported for WB on 8x26 125 dest = (eDest)i; 126 PipeBook::setAllocation(i); 127 break; 128 } 129 } 130 131 if(dest != OV_INVALID) { 132 int index = (int)dest; 133 mPipeBook[index].mDisplay = dpy; 134 mPipeBook[index].mMixer = mixer; 135 mPipeBook[index].mFormatType = formatType; 136 if(not mPipeBook[index].valid()) { 137 mPipeBook[index].mPipe = new GenericPipe(dpy); 138 mPipeBook[index].mSession = PipeBook::NONE; 139 } 140 } 141 142 return dest; 143 } 144 145 utils::eDest Overlay::getPipe(const PipeSpecs& pipeSpecs) { 146 if(MDPVersion::getInstance().is8x26()) { 147 return getPipe_8x26(pipeSpecs); 148 } else if(MDPVersion::getInstance().is8x16()) { 149 return getPipe_8x16(pipeSpecs); 150 } else if(MDPVersion::getInstance().is8x39()) { 151 return getPipe_8x39(pipeSpecs); 152 } else if(MDPVersion::getInstance().is8994()) { 153 return getPipe_8994(pipeSpecs); 154 } 155 156 eDest dest = OV_INVALID; 157 158 //The default behavior is to assume RGB and VG pipes have scalars 159 if(pipeSpecs.formatClass == FORMAT_YUV) { 160 return nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 161 } else if(pipeSpecs.fb == false) { //RGB App layers 162 if(not pipeSpecs.needsScaling) { 163 dest = nextPipe(OV_MDP_PIPE_DMA, pipeSpecs); 164 } 165 if(dest == OV_INVALID) { 166 dest = nextPipe(OV_MDP_PIPE_RGB, pipeSpecs); 167 } 168 if(dest == OV_INVALID) { 169 dest = nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 170 } 171 } else { //FB layer 172 dest = nextPipe(OV_MDP_PIPE_RGB, pipeSpecs); 173 if(dest == OV_INVALID) { 174 dest = nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 175 } 176 //Some features can cause FB to have scaling as well. 177 //If we ever come to this block with FB needing scaling, 178 //the screen will be black for a frame, since the FB won't get a pipe 179 //but atleast this will prevent a hang 180 if(dest == OV_INVALID and (not pipeSpecs.needsScaling)) { 181 dest = nextPipe(OV_MDP_PIPE_DMA, pipeSpecs); 182 } 183 } 184 return dest; 185 } 186 187 utils::eDest Overlay::getPipe_8x26(const PipeSpecs& pipeSpecs) { 188 //Use this to hide all the 8x26 requirements that cannot be humanly 189 //described in a generic way 190 eDest dest = OV_INVALID; 191 if(pipeSpecs.formatClass == FORMAT_YUV) { //video 192 return nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 193 } else if(pipeSpecs.fb == false) { //RGB app layers 194 if((not pipeSpecs.needsScaling) and 195 (not (pipeSpecs.numActiveDisplays > 1 && 196 pipeSpecs.dpy == DPY_PRIMARY))) { 197 dest = nextPipe(OV_MDP_PIPE_DMA, pipeSpecs); 198 } 199 if(dest == OV_INVALID) { 200 dest = nextPipe(OV_MDP_PIPE_RGB, pipeSpecs); 201 } 202 if(dest == OV_INVALID) { 203 dest = nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 204 } 205 } else { //FB layer 206 //For 8x26 Secondary we use DMA always for FB for inline rotation 207 if(pipeSpecs.dpy == DPY_PRIMARY) { 208 dest = nextPipe(OV_MDP_PIPE_RGB, pipeSpecs); 209 if(dest == OV_INVALID) { 210 dest = nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 211 } 212 } 213 if(dest == OV_INVALID and (not pipeSpecs.needsScaling) and 214 (not (pipeSpecs.numActiveDisplays > 1 && 215 pipeSpecs.dpy == DPY_PRIMARY))) { 216 dest = nextPipe(OV_MDP_PIPE_DMA, pipeSpecs); 217 } 218 } 219 return dest; 220 } 221 222 utils::eDest Overlay::getPipe_8x16(const PipeSpecs& pipeSpecs) { 223 //Having such functions help keeping the interface generic but code specific 224 //and rife with assumptions 225 eDest dest = OV_INVALID; 226 if(pipeSpecs.formatClass == FORMAT_YUV or pipeSpecs.needsScaling) { 227 return nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 228 } else { 229 //Since this is a specific func, we can assume stuff like RGB pipe not 230 //having scalar blocks 231 dest = nextPipe(OV_MDP_PIPE_RGB, pipeSpecs); 232 if(dest == OV_INVALID) { 233 dest = nextPipe(OV_MDP_PIPE_DMA, pipeSpecs); 234 } 235 if(dest == OV_INVALID) { 236 dest = nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 237 } 238 } 239 return dest; 240 } 241 242 utils::eDest Overlay::getPipe_8x39(const PipeSpecs& pipeSpecs) { 243 //8x16 & 8x36 has same number of pipes, pipe-types & scaling capabilities. 244 //Rely on 8x16 until we see a need to change. 245 return getPipe_8x16(pipeSpecs); 246 } 247 248 utils::eDest Overlay::getPipe_8994(const PipeSpecs& pipeSpecs) { 249 //If DMA pipes need to be used in block mode for downscale, there could be 250 //cases where consecutive rounds need separate modes, which cannot be 251 //supported since we at least need 1 round in between where the DMA is 252 //unused 253 eDest dest = OV_INVALID; 254 if(pipeSpecs.formatClass == FORMAT_YUV) { 255 return nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 256 } else { 257 dest = nextPipe(OV_MDP_PIPE_RGB, pipeSpecs); 258 if(dest == OV_INVALID) { 259 dest = nextPipe(OV_MDP_PIPE_VG, pipeSpecs); 260 } 261 if(dest == OV_INVALID and not pipeSpecs.needsScaling) { 262 dest = nextPipe(OV_MDP_PIPE_DMA, pipeSpecs); 263 } 264 } 265 return dest; 266 } 267 268 void Overlay::endAllSessions() { 269 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 270 if(mPipeBook[i].valid() && mPipeBook[i].mSession==PipeBook::START) 271 mPipeBook[i].mSession = PipeBook::END; 272 } 273 } 274 275 bool Overlay::isPipeTypeAttached(eMdpPipeType type) { 276 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 277 if(type == PipeBook::getPipeType((eDest)i) && 278 mPipeBook[i].mDisplay != DPY_UNUSED) { 279 return true; 280 } 281 } 282 return false; 283 } 284 285 int Overlay::comparePipePriority(utils::eDest pipe1Index, 286 utils::eDest pipe2Index) { 287 validate((int)pipe1Index); 288 validate((int)pipe2Index); 289 uint8_t pipe1Prio = mPipeBook[(int)pipe1Index].mPipe->getPriority(); 290 uint8_t pipe2Prio = mPipeBook[(int)pipe2Index].mPipe->getPriority(); 291 if(pipe1Prio > pipe2Prio) 292 return -1; 293 else if(pipe1Prio < pipe2Prio) 294 return 1; 295 else { 296 // If we are here, Source Split is enabled and both pipes are 297 // new requests. In this case left type should be of higher prio 298 // than right type 299 utils::eMdpPipeType leftType = PipeBook::getPipeType(pipe1Index); 300 utils::eMdpPipeType rightType = PipeBook::getPipeType(pipe2Index); 301 302 if(leftType == rightType) { 303 //Safe. Onus on driver to assign correct pipes within same type 304 return 1; 305 } else if(leftType == OV_MDP_PIPE_DMA or rightType == OV_MDP_PIPE_VG) { 306 //If we are here, right is definitely a higher prio type. 307 //This check takes advantage of having only 3 types and avoids 3 308 //different failure combination checks. 309 return -1; 310 } else { 311 //Types are correct priority-wise 312 return 1; 313 } 314 } 315 316 return 0; 317 } 318 319 bool Overlay::commit(utils::eDest dest) { 320 bool ret = false; 321 validate((int)dest); 322 323 if(mPipeBook[dest].mPipe->commit()) { 324 ret = true; 325 PipeBook::setUse((int)dest); 326 } else { 327 clear(mPipeBook[dest].mDisplay); 328 } 329 return ret; 330 } 331 332 bool Overlay::queueBuffer(int fd, uint32_t offset, 333 utils::eDest dest) { 334 bool ret = false; 335 validate((int)dest); 336 //Queue only if commit() has succeeded (and the bit set) 337 if(PipeBook::isUsed((int)dest)) { 338 ret = mPipeBook[dest].mPipe->queueBuffer(fd, offset); 339 } 340 return ret; 341 } 342 343 void Overlay::setCrop(const utils::Dim& d, 344 utils::eDest dest) { 345 validate((int)dest); 346 mPipeBook[dest].mPipe->setCrop(d); 347 } 348 349 void Overlay::setColor(const uint32_t color, 350 utils::eDest dest) { 351 validate((int)dest); 352 mPipeBook[dest].mPipe->setColor(color); 353 } 354 355 void Overlay::setPosition(const utils::Dim& d, 356 utils::eDest dest) { 357 validate((int)dest); 358 mPipeBook[dest].mPipe->setPosition(d); 359 } 360 361 void Overlay::setTransform(const int orient, 362 utils::eDest dest) { 363 validate((int)dest); 364 365 utils::eTransform transform = 366 static_cast<utils::eTransform>(orient); 367 mPipeBook[dest].mPipe->setTransform(transform); 368 369 } 370 371 void Overlay::setSource(const utils::PipeArgs args, 372 utils::eDest dest) { 373 validate((int)dest); 374 375 setPipeType(dest, PipeBook::getPipeType(dest)); 376 mPipeBook[dest].mPipe->setSource(args); 377 } 378 379 void Overlay::setVisualParams(const MetaData_t& metadata, utils::eDest dest) { 380 validate((int)dest); 381 mPipeBook[dest].mPipe->setVisualParams(metadata); 382 } 383 384 void Overlay::setPipeType(utils::eDest pipeIndex, 385 const utils::eMdpPipeType pType) { 386 mPipeBook[pipeIndex].mPipe->setPipeType(pType); 387 } 388 389 Overlay* Overlay::getInstance() { 390 if(sInstance == NULL) { 391 sInstance = new Overlay(); 392 } 393 return sInstance; 394 } 395 396 // Clears any VG pipes allocated to the fb devices 397 // Generates a LUT for pipe types. 398 int Overlay::initOverlay() { 399 int mdpVersion = qdutils::MDPVersion::getInstance().getMDPVersion(); 400 int numPipesXType[OV_MDP_PIPE_ANY] = {0}; 401 numPipesXType[OV_MDP_PIPE_RGB] = 402 qdutils::MDPVersion::getInstance().getRGBPipes(); 403 numPipesXType[OV_MDP_PIPE_VG] = 404 qdutils::MDPVersion::getInstance().getVGPipes(); 405 numPipesXType[OV_MDP_PIPE_DMA] = 406 qdutils::MDPVersion::getInstance().getDMAPipes(); 407 408 int index = 0; 409 for(int X = 0; X < (int)OV_MDP_PIPE_ANY; X++) { //iterate over types 410 for(int j = 0; j < numPipesXType[X]; j++) { //iterate over num 411 PipeBook::pipeTypeLUT[index] = (utils::eMdpPipeType)X; 412 index++; 413 } 414 } 415 416 FILE *displayDeviceFP = NULL; 417 char fbType[MAX_FRAME_BUFFER_NAME_SIZE]; 418 char msmFbTypePath[MAX_FRAME_BUFFER_NAME_SIZE]; 419 const char *strDtvPanel = "dtv panel"; 420 const char *strWbPanel = "writeback panel"; 421 422 for(int num = 1; num < MAX_FB_DEVICES; num++) { 423 snprintf (msmFbTypePath, sizeof(msmFbTypePath), 424 "/sys/class/graphics/fb%d/msm_fb_type", num); 425 displayDeviceFP = fopen(msmFbTypePath, "r"); 426 427 if(displayDeviceFP){ 428 fread(fbType, sizeof(char), MAX_FRAME_BUFFER_NAME_SIZE, 429 displayDeviceFP); 430 431 if(strncmp(fbType, strDtvPanel, strlen(strDtvPanel)) == 0) { 432 sDpyFbMap[DPY_EXTERNAL] = num; 433 } else if(strncmp(fbType, strWbPanel, strlen(strWbPanel)) == 0) { 434 sDpyFbMap[DPY_WRITEBACK] = num; 435 } 436 437 fclose(displayDeviceFP); 438 } 439 } 440 441 return 0; 442 } 443 444 bool Overlay::displayCommit(const int& fd) { 445 utils::Dim lRoi, rRoi; 446 return displayCommit(fd, lRoi, rRoi); 447 } 448 449 bool Overlay::displayCommit(const int& fd, const utils::Dim& lRoi, 450 const utils::Dim& rRoi) { 451 //Commit 452 struct mdp_display_commit info; 453 memset(&info, 0, sizeof(struct mdp_display_commit)); 454 info.flags = MDP_DISPLAY_COMMIT_OVERLAY; 455 info.l_roi.x = lRoi.x; 456 info.l_roi.y = lRoi.y; 457 info.l_roi.w = lRoi.w; 458 info.l_roi.h = lRoi.h; 459 info.r_roi.x = rRoi.x; 460 info.r_roi.y = rRoi.y; 461 info.r_roi.w = rRoi.w; 462 info.r_roi.h = rRoi.h; 463 464 if(!mdp_wrapper::displayCommit(fd, info)) { 465 ALOGE("%s: commit failed", __func__); 466 return false; 467 } 468 return true; 469 } 470 471 void Overlay::getDump(char *buf, size_t len) { 472 int totalPipes = 0; 473 const char *str = "\nOverlay State\n\n"; 474 strlcat(buf, str, len); 475 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 476 if(mPipeBook[i].valid()) { 477 mPipeBook[i].mPipe->getDump(buf, len); 478 char str[64] = {'\0'}; 479 snprintf(str, 64, "Display=%d\n\n", mPipeBook[i].mDisplay); 480 strlcat(buf, str, len); 481 totalPipes++; 482 } 483 } 484 char str_pipes[64] = {'\0'}; 485 snprintf(str_pipes, 64, "Pipes=%d\n\n", totalPipes); 486 strlcat(buf, str_pipes, len); 487 } 488 489 void Overlay::clear(int dpy) { 490 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 491 if (mPipeBook[i].mDisplay == dpy) { 492 // Mark as available for this round 493 PipeBook::resetUse(i); 494 PipeBook::resetAllocation(i); 495 if(getPipeId((utils::eDest)i) == -1) { 496 mPipeBook[i].destroy(); 497 } 498 } 499 } 500 } 501 502 bool Overlay::validateAndSet(const int& dpy, const int& fbFd) { 503 GenericPipe* pipeArray[PipeBook::NUM_PIPES]; 504 memset(pipeArray, 0, sizeof(GenericPipe*)*(PipeBook::NUM_PIPES)); 505 506 int num = 0; 507 for(int i = 0; i < PipeBook::NUM_PIPES; i++) { 508 if(PipeBook::isUsed(i) && mPipeBook[i].valid() && 509 mPipeBook[i].mDisplay == dpy) { 510 pipeArray[num++] = mPipeBook[i].mPipe; 511 } 512 } 513 514 //Protect against misbehaving clients 515 return num ? GenericPipe::validateAndSet(pipeArray, num, fbFd) : true; 516 } 517 518 void Overlay::initScalar() { 519 if(sLibScaleHandle == NULL) { 520 sLibScaleHandle = dlopen("libscale.so", RTLD_NOW); 521 if(sLibScaleHandle) { 522 *(void **) &sFnProgramScale = 523 dlsym(sLibScaleHandle, "programScale"); 524 } 525 } 526 } 527 528 void Overlay::destroyScalar() { 529 if(sLibScaleHandle) { 530 dlclose(sLibScaleHandle); 531 sLibScaleHandle = NULL; 532 } 533 } 534 535 void Overlay::initPostProc() { 536 sLibAblHandle = dlopen("libmm-abl.so", RTLD_NOW); 537 if (sLibAblHandle) { 538 *(void **)&sFnppParams = dlsym(sLibAblHandle, 539 "display_pp_compute_params"); 540 } else { 541 ALOGE("%s: Not able to load libmm-abl.so", __FUNCTION__); 542 } 543 } 544 545 void Overlay::destroyPostProc() { 546 if (sLibAblHandle) { 547 dlclose(sLibAblHandle); 548 sLibAblHandle = NULL; 549 } 550 } 551 552 void Overlay::PipeBook::init() { 553 mPipe = NULL; 554 mDisplay = DPY_UNUSED; 555 mMixer = MIXER_UNUSED; 556 mFormatType = FORMAT_NONE; 557 } 558 559 void Overlay::PipeBook::destroy() { 560 if(mPipe) { 561 delete mPipe; 562 mPipe = NULL; 563 } 564 mDisplay = DPY_UNUSED; 565 mMixer = MIXER_UNUSED; 566 mFormatType = FORMAT_NONE; 567 mSession = NONE; 568 } 569 570 Overlay* Overlay::sInstance = 0; 571 int Overlay::sDpyFbMap[DPY_MAX] = {0, -1, -1}; 572 int Overlay::sDMAMode = DMA_LINE_MODE; 573 bool Overlay::sDMAMultiplexingSupported = false; 574 bool Overlay::sDebugPipeLifecycle = false; 575 int Overlay::PipeBook::NUM_PIPES = 0; 576 int Overlay::PipeBook::sPipeUsageBitmap = 0; 577 int Overlay::PipeBook::sLastUsageBitmap = 0; 578 int Overlay::PipeBook::sAllocatedBitmap = 0; 579 utils::eMdpPipeType Overlay::PipeBook::pipeTypeLUT[utils::OV_MAX] = 580 {utils::OV_MDP_PIPE_ANY}; 581 void *Overlay::sLibScaleHandle = NULL; 582 int (*Overlay::sFnProgramScale)(struct mdp_overlay_list *) = NULL; 583 /* Dynamically link ABL library */ 584 void *Overlay::sLibAblHandle = NULL; 585 int (*Overlay::sFnppParams)(const struct compute_params *, 586 struct mdp_overlay_pp_params *) = NULL; 587 588 }; // namespace overlay 589