1 /**************************************************************************** 2 * Copyright (C) 2014-2016 Intel Corporation. All Rights Reserved. 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice (including the next 12 * paragraph) shall be included in all copies or substantial portions of the 13 * Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 * IN THE SOFTWARE. 22 ****************************************************************************/ 23 24 #include <stdio.h> 25 #include <thread> 26 #include <algorithm> 27 #include <float.h> 28 #include <vector> 29 #include <utility> 30 #include <fstream> 31 #include <string> 32 33 #if defined(__linux__) || defined(__gnu_linux__) || defined(__APPLE__) 34 #include <pthread.h> 35 #include <sched.h> 36 #include <unistd.h> 37 #endif 38 39 #include "common/os.h" 40 #include "context.h" 41 #include "frontend.h" 42 #include "backend.h" 43 #include "rasterizer.h" 44 #include "rdtsc_core.h" 45 #include "tilemgr.h" 46 47 48 49 50 // ThreadId 51 struct Core 52 { 53 uint32_t procGroup = 0; 54 std::vector<uint32_t> threadIds; 55 }; 56 57 struct NumaNode 58 { 59 uint32_t numaId; 60 std::vector<Core> cores; 61 }; 62 63 typedef std::vector<NumaNode> CPUNumaNodes; 64 65 void CalculateProcessorTopology(CPUNumaNodes& out_nodes, uint32_t& out_numThreadsPerProcGroup) 66 { 67 out_nodes.clear(); 68 out_numThreadsPerProcGroup = 0; 69 70 #if defined(_WIN32) 71 72 std::vector<KAFFINITY> threadMaskPerProcGroup; 73 74 static std::mutex m; 75 std::lock_guard<std::mutex> l(m); 76 77 DWORD bufSize = 0; 78 79 BOOL ret = GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, &bufSize); 80 SWR_ASSERT(ret == FALSE && GetLastError() == ERROR_INSUFFICIENT_BUFFER); 81 82 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pBufferMem = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)malloc(bufSize); 83 SWR_ASSERT(pBufferMem); 84 85 ret = GetLogicalProcessorInformationEx(RelationProcessorCore, pBufferMem, &bufSize); 86 SWR_ASSERT(ret != FALSE, "Failed to get Processor Topology Information"); 87 88 uint32_t count = bufSize / pBufferMem->Size; 89 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pBuffer = pBufferMem; 90 91 for (uint32_t i = 0; i < count; ++i) 92 { 93 SWR_ASSERT(pBuffer->Relationship == RelationProcessorCore); 94 for (uint32_t g = 0; g < pBuffer->Processor.GroupCount; ++g) 95 { 96 auto& gmask = pBuffer->Processor.GroupMask[g]; 97 uint32_t threadId = 0; 98 uint32_t procGroup = gmask.Group; 99 100 Core* pCore = nullptr; 101 102 uint32_t numThreads = (uint32_t)_mm_popcount_sizeT(gmask.Mask); 103 104 while (BitScanForwardSizeT((unsigned long*)&threadId, gmask.Mask)) 105 { 106 // clear mask 107 KAFFINITY threadMask = KAFFINITY(1) << threadId; 108 gmask.Mask &= ~threadMask; 109 110 if (procGroup >= threadMaskPerProcGroup.size()) 111 { 112 threadMaskPerProcGroup.resize(procGroup + 1); 113 } 114 115 if (threadMaskPerProcGroup[procGroup] & threadMask) 116 { 117 // Already seen this mask. This means that we are in 32-bit mode and 118 // have seen more than 32 HW threads for this procGroup 119 // Don't use it 120 #if defined(_WIN64) 121 SWR_INVALID("Shouldn't get here in 64-bit mode"); 122 #endif 123 continue; 124 } 125 126 threadMaskPerProcGroup[procGroup] |= (KAFFINITY(1) << threadId); 127 128 // Find Numa Node 129 uint32_t numaId = 0; 130 PROCESSOR_NUMBER procNum = {}; 131 procNum.Group = WORD(procGroup); 132 procNum.Number = UCHAR(threadId); 133 134 ret = GetNumaProcessorNodeEx(&procNum, (PUSHORT)&numaId); 135 SWR_ASSERT(ret); 136 137 // Store data 138 if (out_nodes.size() <= numaId) 139 { 140 out_nodes.resize(numaId + 1); 141 } 142 auto& numaNode = out_nodes[numaId]; 143 numaNode.numaId = numaId; 144 145 uint32_t coreId = 0; 146 147 if (nullptr == pCore) 148 { 149 numaNode.cores.push_back(Core()); 150 pCore = &numaNode.cores.back(); 151 pCore->procGroup = procGroup; 152 } 153 pCore->threadIds.push_back(threadId); 154 if (procGroup == 0) 155 { 156 out_numThreadsPerProcGroup++; 157 } 158 } 159 } 160 pBuffer = PtrAdd(pBuffer, pBuffer->Size); 161 } 162 163 free(pBufferMem); 164 165 166 #elif defined(__linux__) || defined (__gnu_linux__) 167 168 // Parse /proc/cpuinfo to get full topology 169 std::ifstream input("/proc/cpuinfo"); 170 std::string line; 171 char* c; 172 uint32_t procId = uint32_t(-1); 173 uint32_t coreId = uint32_t(-1); 174 uint32_t physId = uint32_t(-1); 175 176 while (std::getline(input, line)) 177 { 178 if (line.find("processor") != std::string::npos) 179 { 180 auto data_start = line.find(": ") + 2; 181 procId = std::strtoul(&line.c_str()[data_start], &c, 10); 182 continue; 183 } 184 if (line.find("core id") != std::string::npos) 185 { 186 auto data_start = line.find(": ") + 2; 187 coreId = std::strtoul(&line.c_str()[data_start], &c, 10); 188 continue; 189 } 190 if (line.find("physical id") != std::string::npos) 191 { 192 auto data_start = line.find(": ") + 2; 193 physId = std::strtoul(&line.c_str()[data_start], &c, 10); 194 continue; 195 } 196 if (line.length() == 0) 197 { 198 if (physId + 1 > out_nodes.size()) 199 out_nodes.resize(physId + 1); 200 auto& numaNode = out_nodes[physId]; 201 numaNode.numaId = physId; 202 203 if (coreId + 1 > numaNode.cores.size()) 204 numaNode.cores.resize(coreId + 1); 205 auto& core = numaNode.cores[coreId]; 206 core.procGroup = coreId; 207 core.threadIds.push_back(procId); 208 } 209 } 210 211 out_numThreadsPerProcGroup = 0; 212 for (auto &node : out_nodes) 213 { 214 for (auto &core : node.cores) 215 { 216 out_numThreadsPerProcGroup += core.threadIds.size(); 217 } 218 } 219 220 #elif defined(__APPLE__) 221 222 #else 223 224 #error Unsupported platform 225 226 #endif 227 228 // Prune empty cores and numa nodes 229 for (auto node_it = out_nodes.begin(); node_it != out_nodes.end(); ) 230 { 231 // Erase empty cores (first) 232 for (auto core_it = node_it->cores.begin(); core_it != node_it->cores.end(); ) 233 { 234 if (core_it->threadIds.size() == 0) 235 { 236 core_it = node_it->cores.erase(core_it); 237 } 238 else 239 { 240 ++core_it; 241 } 242 } 243 244 // Erase empty numa nodes (second) 245 if (node_it->cores.size() == 0) 246 { 247 node_it = out_nodes.erase(node_it); 248 } 249 else 250 { 251 ++node_it; 252 } 253 } 254 } 255 256 257 void bindThread(SWR_CONTEXT* pContext, uint32_t threadId, uint32_t procGroupId = 0, bool bindProcGroup=false) 258 { 259 // Only bind threads when MAX_WORKER_THREADS isn't set. 260 if (pContext->threadInfo.SINGLE_THREADED || (pContext->threadInfo.MAX_WORKER_THREADS && bindProcGroup == false)) 261 { 262 return; 263 } 264 265 #if defined(_WIN32) 266 267 GROUP_AFFINITY affinity = {}; 268 affinity.Group = procGroupId; 269 270 #if !defined(_WIN64) 271 if (threadId >= 32) 272 { 273 // Hopefully we don't get here. Logic in CreateThreadPool should prevent this. 274 SWR_INVALID("Shouldn't get here"); 275 276 // In a 32-bit process on Windows it is impossible to bind 277 // to logical processors 32-63 within a processor group. 278 // In this case set the mask to 0 and let the system assign 279 // the processor. Hopefully it will make smart choices. 280 affinity.Mask = 0; 281 } 282 else 283 #endif 284 { 285 // If MAX_WORKER_THREADS is set, only bind to the proc group, 286 // Not the individual HW thread. 287 if (!bindProcGroup && !pContext->threadInfo.MAX_WORKER_THREADS) 288 { 289 affinity.Mask = KAFFINITY(1) << threadId; 290 } 291 else 292 { 293 affinity.Mask = KAFFINITY(0); 294 } 295 } 296 297 if (!SetThreadGroupAffinity(GetCurrentThread(), &affinity, nullptr)) 298 { 299 SWR_INVALID("Failed to set Thread Affinity"); 300 } 301 302 #elif defined(__linux__) || defined(__gnu_linux__) 303 304 cpu_set_t cpuset; 305 pthread_t thread = pthread_self(); 306 CPU_ZERO(&cpuset); 307 CPU_SET(threadId, &cpuset); 308 309 int err = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset); 310 if (err != 0) 311 { 312 fprintf(stderr, "pthread_setaffinity_np failure for tid %u: %s\n", threadId, strerror(err)); 313 } 314 315 #endif 316 } 317 318 INLINE 319 uint32_t GetEnqueuedDraw(SWR_CONTEXT *pContext) 320 { 321 return pContext->dcRing.GetHead(); 322 } 323 324 INLINE 325 DRAW_CONTEXT *GetDC(SWR_CONTEXT *pContext, uint32_t drawId) 326 { 327 return &pContext->dcRing[(drawId-1) % pContext->MAX_DRAWS_IN_FLIGHT]; 328 } 329 330 INLINE 331 bool IDComparesLess(uint32_t a, uint32_t b) 332 { 333 // Use signed delta to ensure that wrap-around to 0 is correctly handled. 334 int32_t delta = int32_t(a - b); 335 return (delta < 0); 336 } 337 338 // returns true if dependency not met 339 INLINE 340 bool CheckDependency(SWR_CONTEXT *pContext, DRAW_CONTEXT *pDC, uint32_t lastRetiredDraw) 341 { 342 return pDC->dependent && IDComparesLess(lastRetiredDraw, pDC->drawId - 1); 343 } 344 345 bool CheckDependencyFE(SWR_CONTEXT *pContext, DRAW_CONTEXT *pDC, uint32_t lastRetiredDraw) 346 { 347 return pDC->dependentFE && IDComparesLess(lastRetiredDraw, pDC->drawId - 1); 348 } 349 350 ////////////////////////////////////////////////////////////////////////// 351 /// @brief Update client stats. 352 INLINE void UpdateClientStats(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC) 353 { 354 if ((pContext->pfnUpdateStats == nullptr) || (GetApiState(pDC).enableStatsBE == false)) 355 { 356 return; 357 } 358 359 DRAW_DYNAMIC_STATE& dynState = pDC->dynState; 360 OSALIGNLINE(SWR_STATS) stats{ 0 }; 361 362 // Sum up stats across all workers before sending to client. 363 for (uint32_t i = 0; i < pContext->NumWorkerThreads; ++i) 364 { 365 stats.DepthPassCount += dynState.pStats[i].DepthPassCount; 366 367 stats.PsInvocations += dynState.pStats[i].PsInvocations; 368 stats.CsInvocations += dynState.pStats[i].CsInvocations; 369 } 370 371 372 pContext->pfnUpdateStats(GetPrivateState(pDC), &stats); 373 } 374 375 INLINE void ExecuteCallbacks(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC) 376 { 377 UpdateClientStats(pContext, workerId, pDC); 378 379 if (pDC->retireCallback.pfnCallbackFunc) 380 { 381 pDC->retireCallback.pfnCallbackFunc(pDC->retireCallback.userData, 382 pDC->retireCallback.userData2, 383 pDC->retireCallback.userData3); 384 } 385 } 386 387 // inlined-only version 388 INLINE int32_t CompleteDrawContextInl(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC) 389 { 390 int32_t result = static_cast<int32_t>(InterlockedDecrement(&pDC->threadsDone)); 391 SWR_ASSERT(result >= 0); 392 393 AR_FLUSH(pDC->drawId); 394 395 if (result == 0) 396 { 397 ExecuteCallbacks(pContext, workerId, pDC); 398 399 // Cleanup memory allocations 400 pDC->pArena->Reset(true); 401 if (!pDC->isCompute) 402 { 403 pDC->pTileMgr->initialize(); 404 } 405 if (pDC->cleanupState) 406 { 407 pDC->pState->pArena->Reset(true); 408 } 409 410 _ReadWriteBarrier(); 411 412 pContext->dcRing.Dequeue(); // Remove from tail 413 } 414 415 return result; 416 } 417 418 // available to other translation modules 419 int32_t CompleteDrawContext(SWR_CONTEXT* pContext, DRAW_CONTEXT* pDC) 420 { 421 return CompleteDrawContextInl(pContext, 0, pDC); 422 } 423 424 INLINE bool FindFirstIncompleteDraw(SWR_CONTEXT* pContext, uint32_t workerId, uint32_t& curDrawBE, uint32_t& drawEnqueued) 425 { 426 // increment our current draw id to the first incomplete draw 427 drawEnqueued = GetEnqueuedDraw(pContext); 428 while (IDComparesLess(curDrawBE, drawEnqueued)) 429 { 430 DRAW_CONTEXT *pDC = &pContext->dcRing[curDrawBE % pContext->MAX_DRAWS_IN_FLIGHT]; 431 432 // If its not compute and FE is not done then break out of loop. 433 if (!pDC->doneFE && !pDC->isCompute) break; 434 435 bool isWorkComplete = pDC->isCompute ? 436 pDC->pDispatch->isWorkComplete() : 437 pDC->pTileMgr->isWorkComplete(); 438 439 if (isWorkComplete) 440 { 441 curDrawBE++; 442 CompleteDrawContextInl(pContext, workerId, pDC); 443 } 444 else 445 { 446 break; 447 } 448 } 449 450 // If there are no more incomplete draws then return false. 451 return IDComparesLess(curDrawBE, drawEnqueued); 452 } 453 454 ////////////////////////////////////////////////////////////////////////// 455 /// @brief If there is any BE work then go work on it. 456 /// @param pContext - pointer to SWR context. 457 /// @param workerId - The unique worker ID that is assigned to this thread. 458 /// @param curDrawBE - This tracks the draw contexts that this thread has processed. Each worker thread 459 /// has its own curDrawBE counter and this ensures that each worker processes all the 460 /// draws in order. 461 /// @param lockedTiles - This is the set of tiles locked by other threads. Each thread maintains its 462 /// own set and each time it fails to lock a macrotile, because its already locked, 463 /// then it will add that tile to the lockedTiles set. As a worker begins to work 464 /// on future draws the lockedTiles ensure that it doesn't work on tiles that may 465 /// still have work pending in a previous draw. Additionally, the lockedTiles is 466 /// hueristic that can steer a worker back to the same macrotile that it had been 467 /// working on in a previous draw. 468 /// @returns true if worker thread should shutdown 469 bool WorkOnFifoBE( 470 SWR_CONTEXT *pContext, 471 uint32_t workerId, 472 uint32_t &curDrawBE, 473 TileSet& lockedTiles, 474 uint32_t numaNode, 475 uint32_t numaMask) 476 { 477 bool bShutdown = false; 478 479 // Find the first incomplete draw that has pending work. If no such draw is found then 480 // return. FindFirstIncompleteDraw is responsible for incrementing the curDrawBE. 481 uint32_t drawEnqueued = 0; 482 if (FindFirstIncompleteDraw(pContext, workerId, curDrawBE, drawEnqueued) == false) 483 { 484 return false; 485 } 486 487 uint32_t lastRetiredDraw = pContext->dcRing[curDrawBE % pContext->MAX_DRAWS_IN_FLIGHT].drawId - 1; 488 489 // Reset our history for locked tiles. We'll have to re-learn which tiles are locked. 490 lockedTiles.clear(); 491 492 // Try to work on each draw in order of the available draws in flight. 493 // 1. If we're on curDrawBE, we can work on any macrotile that is available. 494 // 2. If we're trying to work on draws after curDrawBE, we are restricted to 495 // working on those macrotiles that are known to be complete in the prior draw to 496 // maintain order. The locked tiles provides the history to ensures this. 497 for (uint32_t i = curDrawBE; IDComparesLess(i, drawEnqueued); ++i) 498 { 499 DRAW_CONTEXT *pDC = &pContext->dcRing[i % pContext->MAX_DRAWS_IN_FLIGHT]; 500 501 if (pDC->isCompute) return false; // We don't look at compute work. 502 503 // First wait for FE to be finished with this draw. This keeps threading model simple 504 // but if there are lots of bubbles between draws then serializing FE and BE may 505 // need to be revisited. 506 if (!pDC->doneFE) return false; 507 508 // If this draw is dependent on a previous draw then we need to bail. 509 if (CheckDependency(pContext, pDC, lastRetiredDraw)) 510 { 511 return false; 512 } 513 514 // Grab the list of all dirty macrotiles. A tile is dirty if it has work queued to it. 515 auto ¯oTiles = pDC->pTileMgr->getDirtyTiles(); 516 517 for (auto tile : macroTiles) 518 { 519 uint32_t tileID = tile->mId; 520 521 // Only work on tiles for this numa node 522 uint32_t x, y; 523 pDC->pTileMgr->getTileIndices(tileID, x, y); 524 if (((x ^ y) & numaMask) != numaNode) 525 { 526 continue; 527 } 528 529 if (!tile->getNumQueued()) 530 { 531 continue; 532 } 533 534 // can only work on this draw if it's not in use by other threads 535 if (lockedTiles.find(tileID) != lockedTiles.end()) 536 { 537 continue; 538 } 539 540 if (tile->tryLock()) 541 { 542 BE_WORK *pWork; 543 544 AR_BEGIN(WorkerFoundWork, pDC->drawId); 545 546 uint32_t numWorkItems = tile->getNumQueued(); 547 SWR_ASSERT(numWorkItems); 548 549 pWork = tile->peek(); 550 SWR_ASSERT(pWork); 551 if (pWork->type == DRAW) 552 { 553 pContext->pHotTileMgr->InitializeHotTiles(pContext, pDC, workerId, tileID); 554 } 555 else if (pWork->type == SHUTDOWN) 556 { 557 bShutdown = true; 558 } 559 560 while ((pWork = tile->peek()) != nullptr) 561 { 562 pWork->pfnWork(pDC, workerId, tileID, &pWork->desc); 563 tile->dequeue(); 564 } 565 AR_END(WorkerFoundWork, numWorkItems); 566 567 _ReadWriteBarrier(); 568 569 pDC->pTileMgr->markTileComplete(tileID); 570 571 // Optimization: If the draw is complete and we're the last one to have worked on it then 572 // we can reset the locked list as we know that all previous draws before the next are guaranteed to be complete. 573 if ((curDrawBE == i) && (bShutdown || pDC->pTileMgr->isWorkComplete())) 574 { 575 // We can increment the current BE and safely move to next draw since we know this draw is complete. 576 curDrawBE++; 577 CompleteDrawContextInl(pContext, workerId, pDC); 578 579 lastRetiredDraw++; 580 581 lockedTiles.clear(); 582 break; 583 } 584 585 if (bShutdown) 586 { 587 break; 588 } 589 } 590 else 591 { 592 // This tile is already locked. So let's add it to our locked tiles set. This way we don't try locking this one again. 593 lockedTiles.insert(tileID); 594 } 595 } 596 } 597 598 return bShutdown; 599 } 600 601 ////////////////////////////////////////////////////////////////////////// 602 /// @brief Called when FE work is complete for this DC. 603 INLINE void CompleteDrawFE(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC) 604 { 605 if (pContext->pfnUpdateStatsFE && GetApiState(pDC).enableStatsFE) 606 { 607 SWR_STATS_FE& stats = pDC->dynState.statsFE; 608 609 AR_EVENT(FrontendStatsEvent(pDC->drawId, 610 stats.IaVertices, stats.IaPrimitives, stats.VsInvocations, stats.HsInvocations, 611 stats.DsInvocations, stats.GsInvocations, stats.GsPrimitives, stats.CInvocations, stats.CPrimitives, 612 stats.SoPrimStorageNeeded[0], stats.SoPrimStorageNeeded[1], stats.SoPrimStorageNeeded[2], stats.SoPrimStorageNeeded[3], 613 stats.SoNumPrimsWritten[0], stats.SoNumPrimsWritten[1], stats.SoNumPrimsWritten[2], stats.SoNumPrimsWritten[3] 614 )); 615 AR_EVENT(FrontendDrawEndEvent(pDC->drawId)); 616 617 pContext->pfnUpdateStatsFE(GetPrivateState(pDC), &stats); 618 } 619 620 if (pContext->pfnUpdateSoWriteOffset) 621 { 622 for (uint32_t i = 0; i < MAX_SO_BUFFERS; ++i) 623 { 624 if ((pDC->dynState.SoWriteOffsetDirty[i]) && 625 (pDC->pState->state.soBuffer[i].soWriteEnable)) 626 { 627 pContext->pfnUpdateSoWriteOffset(GetPrivateState(pDC), i, pDC->dynState.SoWriteOffset[i]); 628 } 629 } 630 } 631 632 // Ensure all streaming writes are globally visible before marking this FE done 633 _mm_mfence(); 634 pDC->doneFE = true; 635 636 InterlockedDecrement(&pContext->drawsOutstandingFE); 637 } 638 639 void WorkOnFifoFE(SWR_CONTEXT *pContext, uint32_t workerId, uint32_t &curDrawFE) 640 { 641 // Try to grab the next DC from the ring 642 uint32_t drawEnqueued = GetEnqueuedDraw(pContext); 643 while (IDComparesLess(curDrawFE, drawEnqueued)) 644 { 645 uint32_t dcSlot = curDrawFE % pContext->MAX_DRAWS_IN_FLIGHT; 646 DRAW_CONTEXT *pDC = &pContext->dcRing[dcSlot]; 647 if (pDC->isCompute || pDC->doneFE) 648 { 649 CompleteDrawContextInl(pContext, workerId, pDC); 650 curDrawFE++; 651 } 652 else 653 { 654 break; 655 } 656 } 657 658 uint32_t lastRetiredFE = curDrawFE - 1; 659 uint32_t curDraw = curDrawFE; 660 while (IDComparesLess(curDraw, drawEnqueued)) 661 { 662 uint32_t dcSlot = curDraw % pContext->MAX_DRAWS_IN_FLIGHT; 663 DRAW_CONTEXT *pDC = &pContext->dcRing[dcSlot]; 664 665 if (!pDC->isCompute && !pDC->FeLock) 666 { 667 if (CheckDependencyFE(pContext, pDC, lastRetiredFE)) 668 { 669 return; 670 } 671 672 uint32_t initial = InterlockedCompareExchange((volatile uint32_t*)&pDC->FeLock, 1, 0); 673 if (initial == 0) 674 { 675 // successfully grabbed the DC, now run the FE 676 pDC->FeWork.pfnWork(pContext, pDC, workerId, &pDC->FeWork.desc); 677 678 CompleteDrawFE(pContext, workerId, pDC); 679 } 680 } 681 curDraw++; 682 } 683 } 684 685 ////////////////////////////////////////////////////////////////////////// 686 /// @brief If there is any compute work then go work on it. 687 /// @param pContext - pointer to SWR context. 688 /// @param workerId - The unique worker ID that is assigned to this thread. 689 /// @param curDrawBE - This tracks the draw contexts that this thread has processed. Each worker thread 690 /// has its own curDrawBE counter and this ensures that each worker processes all the 691 /// draws in order. 692 void WorkOnCompute( 693 SWR_CONTEXT *pContext, 694 uint32_t workerId, 695 uint32_t& curDrawBE) 696 { 697 uint32_t drawEnqueued = 0; 698 if (FindFirstIncompleteDraw(pContext, workerId, curDrawBE, drawEnqueued) == false) 699 { 700 return; 701 } 702 703 uint32_t lastRetiredDraw = pContext->dcRing[curDrawBE % pContext->MAX_DRAWS_IN_FLIGHT].drawId - 1; 704 705 for (uint64_t i = curDrawBE; IDComparesLess(i, drawEnqueued); ++i) 706 { 707 DRAW_CONTEXT *pDC = &pContext->dcRing[i % pContext->MAX_DRAWS_IN_FLIGHT]; 708 if (pDC->isCompute == false) return; 709 710 // check dependencies 711 if (CheckDependency(pContext, pDC, lastRetiredDraw)) 712 { 713 return; 714 } 715 716 SWR_ASSERT(pDC->pDispatch != nullptr); 717 DispatchQueue& queue = *pDC->pDispatch; 718 719 // Is there any work remaining? 720 if (queue.getNumQueued() > 0) 721 { 722 void* pSpillFillBuffer = nullptr; 723 void* pScratchSpace = nullptr; 724 uint32_t threadGroupId = 0; 725 while (queue.getWork(threadGroupId)) 726 { 727 queue.dispatch(pDC, workerId, threadGroupId, pSpillFillBuffer, pScratchSpace); 728 queue.finishedWork(); 729 } 730 731 // Ensure all streaming writes are globally visible before moving onto the next draw 732 _mm_mfence(); 733 } 734 } 735 } 736 737 void BindApiThread(SWR_CONTEXT *pContext, uint32_t apiThreadId) 738 { 739 if (nullptr == pContext) 740 { 741 return; 742 } 743 744 if (apiThreadId >= pContext->threadPool.numReservedThreads) 745 { 746 if (pContext->threadPool.numReservedThreads) 747 { 748 const THREAD_DATA &threadData = pContext->threadPool.pApiThreadData[0]; 749 // Just bind to the process group used for API thread 0 750 bindThread(pContext, 0, threadData.procGroupId, true); 751 } 752 return; 753 } 754 755 const THREAD_DATA &threadData = pContext->threadPool.pApiThreadData[apiThreadId]; 756 757 bindThread(pContext, threadData.threadId, threadData.procGroupId, threadData.forceBindProcGroup); 758 } 759 760 template<bool IsFEThread, bool IsBEThread> 761 DWORD workerThreadMain(LPVOID pData) 762 { 763 THREAD_DATA *pThreadData = (THREAD_DATA*)pData; 764 SWR_CONTEXT *pContext = pThreadData->pContext; 765 uint32_t threadId = pThreadData->threadId; 766 uint32_t workerId = pThreadData->workerId; 767 768 bindThread(pContext, threadId, pThreadData->procGroupId, pThreadData->forceBindProcGroup); 769 770 { 771 char threadName[64]; 772 sprintf_s(threadName, 773 #if defined(_WIN32) 774 "SWRWorker_%02d_NUMA%d_Core%02d_T%d", 775 #else 776 // linux pthread name limited to 16 chars (including \0) 777 "w%03d-n%d-c%03d-t%d", 778 #endif 779 workerId, pThreadData->numaId, pThreadData->coreId, pThreadData->htId); 780 SetCurrentThreadName(threadName); 781 } 782 783 RDTSC_INIT(threadId); 784 785 // Only need offset numa index from base for correct masking 786 uint32_t numaNode = pThreadData->numaId - pContext->threadInfo.BASE_NUMA_NODE; 787 uint32_t numaMask = pContext->threadPool.numaMask; 788 789 // flush denormals to 0 790 _mm_setcsr(_mm_getcsr() | _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON); 791 792 // Track tiles locked by other threads. If we try to lock a macrotile and find its already 793 // locked then we'll add it to this list so that we don't try and lock it again. 794 TileSet lockedTiles; 795 796 // each worker has the ability to work on any of the queued draws as long as certain 797 // conditions are met. the data associated 798 // with a draw is guaranteed to be active as long as a worker hasn't signaled that he 799 // has moved on to the next draw when he determines there is no more work to do. The api 800 // thread will not increment the head of the dc ring until all workers have moved past the 801 // current head. 802 // the logic to determine what to work on is: 803 // 1- try to work on the FE any draw that is queued. For now there are no dependencies 804 // on the FE work, so any worker can grab any FE and process in parallel. Eventually 805 // we'll need dependency tracking to force serialization on FEs. The worker will try 806 // to pick an FE by atomically incrementing a counter in the swr context. he'll keep 807 // trying until he reaches the tail. 808 // 2- BE work must be done in strict order. we accomplish this today by pulling work off 809 // the oldest draw (ie the head) of the dcRing. the worker can determine if there is 810 // any work left by comparing the total # of binned work items and the total # of completed 811 // work items. If they are equal, then there is no more work to do for this draw, and 812 // the worker can safely increment its oldestDraw counter and move on to the next draw. 813 std::unique_lock<std::mutex> lock(pContext->WaitLock, std::defer_lock); 814 815 auto threadHasWork = [&](uint32_t curDraw) { return curDraw != pContext->dcRing.GetHead(); }; 816 817 uint32_t curDrawBE = 0; 818 uint32_t curDrawFE = 0; 819 820 bool bShutdown = false; 821 822 while (true) 823 { 824 if (bShutdown && !threadHasWork(curDrawBE)) 825 { 826 break; 827 } 828 829 uint32_t loop = 0; 830 while (loop++ < KNOB_WORKER_SPIN_LOOP_COUNT && !threadHasWork(curDrawBE)) 831 { 832 _mm_pause(); 833 } 834 835 if (!threadHasWork(curDrawBE)) 836 { 837 lock.lock(); 838 839 // check for thread idle condition again under lock 840 if (threadHasWork(curDrawBE)) 841 { 842 lock.unlock(); 843 continue; 844 } 845 846 pContext->FifosNotEmpty.wait(lock); 847 lock.unlock(); 848 } 849 850 if (IsBEThread) 851 { 852 AR_BEGIN(WorkerWorkOnFifoBE, 0); 853 bShutdown |= WorkOnFifoBE(pContext, workerId, curDrawBE, lockedTiles, numaNode, numaMask); 854 AR_END(WorkerWorkOnFifoBE, 0); 855 856 WorkOnCompute(pContext, workerId, curDrawBE); 857 } 858 859 if (IsFEThread) 860 { 861 WorkOnFifoFE(pContext, workerId, curDrawFE); 862 863 if (!IsBEThread) 864 { 865 curDrawBE = curDrawFE; 866 } 867 } 868 } 869 870 return 0; 871 } 872 template<> DWORD workerThreadMain<false, false>(LPVOID) = delete; 873 874 template <bool IsFEThread, bool IsBEThread> 875 DWORD workerThreadInit(LPVOID pData) 876 { 877 #if defined(_WIN32) 878 __try 879 #endif // _WIN32 880 { 881 return workerThreadMain<IsFEThread, IsBEThread>(pData); 882 } 883 884 #if defined(_WIN32) 885 __except(EXCEPTION_CONTINUE_SEARCH) 886 { 887 } 888 889 #endif // _WIN32 890 891 return 1; 892 } 893 template<> DWORD workerThreadInit<false, false>(LPVOID pData) = delete; 894 895 static void InitPerThreadStats(SWR_CONTEXT* pContext, uint32_t numThreads) 896 { 897 // Initialize DRAW_CONTEXT's per-thread stats 898 for (uint32_t dc = 0; dc < pContext->MAX_DRAWS_IN_FLIGHT; ++dc) 899 { 900 pContext->dcRing[dc].dynState.pStats = (SWR_STATS*)AlignedMalloc(sizeof(SWR_STATS) * numThreads, 64); 901 memset(pContext->dcRing[dc].dynState.pStats, 0, sizeof(SWR_STATS) * numThreads); 902 } 903 } 904 905 ////////////////////////////////////////////////////////////////////////// 906 /// @brief Creates thread pool info but doesn't launch threads. 907 /// @param pContext - pointer to context 908 /// @param pPool - pointer to thread pool object. 909 void CreateThreadPool(SWR_CONTEXT* pContext, THREAD_POOL* pPool) 910 { 911 CPUNumaNodes nodes; 912 uint32_t numThreadsPerProcGroup = 0; 913 CalculateProcessorTopology(nodes, numThreadsPerProcGroup); 914 915 // Assumption, for asymmetric topologies, multi-threaded cores will appear 916 // in the list before single-threaded cores. This appears to be true for 917 // Windows when the total HW threads is limited to 64. 918 uint32_t numHWNodes = (uint32_t)nodes.size(); 919 uint32_t numHWCoresPerNode = (uint32_t)nodes[0].cores.size(); 920 uint32_t numHWHyperThreads = (uint32_t)nodes[0].cores[0].threadIds.size(); 921 922 #if defined(_WIN32) && !defined(_WIN64) 923 if (!pContext->threadInfo.MAX_WORKER_THREADS) 924 { 925 // Limit 32-bit windows to bindable HW threads only 926 if ((numHWCoresPerNode * numHWHyperThreads) > 32) 927 { 928 numHWCoresPerNode = 32 / numHWHyperThreads; 929 } 930 } 931 #endif 932 933 // Calculate num HW threads. Due to asymmetric topologies, this is not 934 // a trivial multiplication. 935 uint32_t numHWThreads = 0; 936 for (auto const& node : nodes) 937 { 938 for (auto const& core : node.cores) 939 { 940 numHWThreads += (uint32_t)core.threadIds.size(); 941 } 942 } 943 944 uint32_t numNodes = numHWNodes; 945 uint32_t numCoresPerNode = numHWCoresPerNode; 946 uint32_t numHyperThreads = numHWHyperThreads; 947 948 // Calc used threads per-core 949 if (numHyperThreads > pContext->threadInfo.BASE_THREAD) 950 { 951 numHyperThreads -= pContext->threadInfo.BASE_THREAD; 952 } 953 else 954 { 955 SWR_ASSERT( 956 false, 957 "Cannot use BASE_THREAD value: %d, maxThreads: %d, reverting BASE_THREAD to 0", 958 pContext->threadInfo.BASE_THREAD, 959 numHyperThreads); 960 pContext->threadInfo.BASE_THREAD = 0; 961 } 962 963 if (pContext->threadInfo.MAX_THREADS_PER_CORE) 964 { 965 numHyperThreads = std::min(numHyperThreads, pContext->threadInfo.MAX_THREADS_PER_CORE); 966 } 967 968 // Prune any cores that don't support the number of threads 969 if (numHyperThreads > 1) 970 { 971 for (auto& node : nodes) 972 { 973 uint32_t numUsableCores = 0; 974 for (auto& core : node.cores) 975 { 976 numUsableCores += (core.threadIds.size() >= numHyperThreads); 977 } 978 numCoresPerNode = std::min(numCoresPerNode, numUsableCores); 979 } 980 } 981 982 // Calc used cores per NUMA node 983 if (numCoresPerNode > pContext->threadInfo.BASE_CORE) 984 { 985 numCoresPerNode -= pContext->threadInfo.BASE_CORE; 986 } 987 else 988 { 989 SWR_ASSERT( 990 false, 991 "Cannot use BASE_CORE value: %d, maxCores: %d, reverting BASE_CORE to 0", 992 pContext->threadInfo.BASE_CORE, 993 numCoresPerNode); 994 pContext->threadInfo.BASE_CORE = 0; 995 } 996 997 if (pContext->threadInfo.MAX_CORES_PER_NUMA_NODE) 998 { 999 numCoresPerNode = std::min(numCoresPerNode, pContext->threadInfo.MAX_CORES_PER_NUMA_NODE); 1000 } 1001 1002 // Calc used NUMA nodes 1003 if (numNodes > pContext->threadInfo.BASE_NUMA_NODE) 1004 { 1005 numNodes -= pContext->threadInfo.BASE_NUMA_NODE; 1006 } 1007 else 1008 { 1009 SWR_ASSERT( 1010 false, 1011 "Cannot use BASE_NUMA_NODE value: %d, maxNodes: %d, reverting BASE_NUMA_NODE to 0", 1012 pContext->threadInfo.BASE_NUMA_NODE, 1013 numNodes); 1014 pContext->threadInfo.BASE_NUMA_NODE = 0; 1015 } 1016 1017 if (pContext->threadInfo.MAX_NUMA_NODES) 1018 { 1019 numNodes = std::min(numNodes, pContext->threadInfo.MAX_NUMA_NODES); 1020 } 1021 1022 // Calculate numThreads - at this point everything should be symmetric 1023 uint32_t numThreads = numNodes * numCoresPerNode * numHyperThreads; 1024 SWR_REL_ASSERT(numThreads <= numHWThreads); 1025 1026 uint32_t& numAPIReservedThreads = pContext->apiThreadInfo.numAPIReservedThreads; 1027 uint32_t& numAPIThreadsPerCore = pContext->apiThreadInfo.numAPIThreadsPerCore; 1028 uint32_t numRemovedThreads = 0; 1029 1030 if (pContext->threadInfo.SINGLE_THREADED) 1031 { 1032 numAPIReservedThreads = 0; 1033 numThreads = 1; 1034 pContext->NumWorkerThreads = 1; 1035 pContext->NumFEThreads = 1; 1036 pContext->NumBEThreads = 1; 1037 pPool->numThreads = 0; 1038 } 1039 else if (pContext->threadInfo.MAX_WORKER_THREADS) 1040 { 1041 numThreads = std::min(pContext->threadInfo.MAX_WORKER_THREADS, numHWThreads); 1042 pContext->threadInfo.BASE_NUMA_NODE = 0; 1043 pContext->threadInfo.BASE_CORE = 0; 1044 pContext->threadInfo.BASE_THREAD = 0; 1045 numAPIReservedThreads = 0; 1046 } 1047 else 1048 { 1049 if (numAPIReservedThreads >= numThreads) 1050 { 1051 numAPIReservedThreads = 0; 1052 } 1053 else if (numAPIReservedThreads) 1054 { 1055 numAPIThreadsPerCore = std::min(numAPIThreadsPerCore, numHWHyperThreads); 1056 1057 if (0 == numAPIThreadsPerCore) 1058 { 1059 numAPIThreadsPerCore = numHWHyperThreads; 1060 } 1061 1062 numRemovedThreads = numAPIReservedThreads; 1063 if (numAPIThreadsPerCore == 2 && numHyperThreads == 1) 1064 { 1065 // Adjust removed threads to make logic below work 1066 numRemovedThreads = std::max(1U, (numRemovedThreads + numAPIThreadsPerCore - 1) / 2); 1067 } 1068 1069 numThreads -= numRemovedThreads; 1070 } 1071 } 1072 1073 InitPerThreadStats(pContext, numThreads); 1074 1075 if (pContext->threadInfo.SINGLE_THREADED) 1076 { 1077 return; 1078 } 1079 1080 if (numAPIReservedThreads) 1081 { 1082 pPool->pApiThreadData = new (std::nothrow) THREAD_DATA[numAPIReservedThreads]; 1083 SWR_ASSERT(pPool->pApiThreadData); 1084 if (!pPool->pApiThreadData) 1085 { 1086 numAPIReservedThreads = 0; 1087 } 1088 } 1089 pPool->numReservedThreads = numAPIReservedThreads; 1090 1091 pPool->numThreads = numThreads; 1092 pContext->NumWorkerThreads = pPool->numThreads; 1093 1094 pPool->pThreadData = new (std::nothrow) THREAD_DATA[pPool->numThreads]; 1095 SWR_ASSERT(pPool->pThreadData); 1096 pPool->numaMask = 0; 1097 1098 1099 pPool->pThreads = new (std::nothrow) THREAD_PTR[pPool->numThreads]; 1100 SWR_ASSERT(pPool->pThreads); 1101 1102 if (pContext->threadInfo.MAX_WORKER_THREADS) 1103 { 1104 bool bForceBindProcGroup = (numThreads > numThreadsPerProcGroup); 1105 uint32_t numProcGroups = (numThreads + numThreadsPerProcGroup - 1) / numThreadsPerProcGroup; 1106 // When MAX_WORKER_THREADS is set we don't bother to bind to specific HW threads 1107 // But Windows will still require binding to specific process groups 1108 for (uint32_t workerId = 0; workerId < numThreads; ++workerId) 1109 { 1110 pPool->pThreadData[workerId].workerId = workerId; 1111 pPool->pThreadData[workerId].procGroupId = workerId % numProcGroups; 1112 pPool->pThreadData[workerId].threadId = 0; 1113 pPool->pThreadData[workerId].numaId = 0; 1114 pPool->pThreadData[workerId].coreId = 0; 1115 pPool->pThreadData[workerId].htId = 0; 1116 pPool->pThreadData[workerId].pContext = pContext; 1117 pPool->pThreadData[workerId].forceBindProcGroup = bForceBindProcGroup; 1118 1119 pContext->NumBEThreads++; 1120 pContext->NumFEThreads++; 1121 } 1122 } 1123 else 1124 { 1125 // numa distribution assumes workers on all nodes 1126 bool useNuma = true; 1127 if (numCoresPerNode * numHyperThreads == 1) 1128 { 1129 useNuma = false; 1130 } 1131 1132 if (useNuma) 1133 { 1134 pPool->numaMask = numNodes - 1; // Only works for 2**n numa nodes (1, 2, 4, etc.) 1135 } 1136 else 1137 { 1138 pPool->numaMask = 0; 1139 } 1140 1141 uint32_t workerId = 0; 1142 uint32_t numReservedThreads = numAPIReservedThreads; 1143 for (uint32_t n = 0; n < numNodes; ++n) 1144 { 1145 if ((n + pContext->threadInfo.BASE_NUMA_NODE) >= nodes.size()) 1146 { 1147 break; 1148 } 1149 auto& node = nodes[n + pContext->threadInfo.BASE_NUMA_NODE]; 1150 uint32_t numCores = numCoresPerNode; 1151 for (uint32_t c = 0; c < numCores; ++c) 1152 { 1153 if ((c + pContext->threadInfo.BASE_CORE) >= node.cores.size()) 1154 { 1155 break; 1156 } 1157 1158 auto& core = node.cores[c + pContext->threadInfo.BASE_CORE]; 1159 for (uint32_t t = 0; t < numHyperThreads; ++t) 1160 { 1161 if ((t + pContext->threadInfo.BASE_THREAD) >= core.threadIds.size()) 1162 { 1163 break; 1164 } 1165 1166 if (numRemovedThreads) 1167 { 1168 --numRemovedThreads; 1169 SWR_REL_ASSERT(numReservedThreads); 1170 --numReservedThreads; 1171 pPool->pApiThreadData[numReservedThreads].workerId = 0xFFFFFFFFU; 1172 pPool->pApiThreadData[numReservedThreads].procGroupId = core.procGroup; 1173 pPool->pApiThreadData[numReservedThreads].threadId = core.threadIds[t]; 1174 pPool->pApiThreadData[numReservedThreads].numaId = useNuma ? (n + pContext->threadInfo.BASE_NUMA_NODE) : 0; 1175 pPool->pApiThreadData[numReservedThreads].coreId = c + pContext->threadInfo.BASE_CORE; 1176 pPool->pApiThreadData[numReservedThreads].htId = t + pContext->threadInfo.BASE_THREAD; 1177 pPool->pApiThreadData[numReservedThreads].pContext = pContext; 1178 pPool->pApiThreadData[numReservedThreads].forceBindProcGroup = false; 1179 1180 1181 if (numAPIThreadsPerCore > numHyperThreads && numReservedThreads) 1182 { 1183 --numReservedThreads; 1184 pPool->pApiThreadData[numReservedThreads].workerId = 0xFFFFFFFFU; 1185 pPool->pApiThreadData[numReservedThreads].procGroupId = core.procGroup; 1186 pPool->pApiThreadData[numReservedThreads].threadId = core.threadIds[t + 1]; 1187 pPool->pApiThreadData[numReservedThreads].numaId = useNuma ? (n + pContext->threadInfo.BASE_NUMA_NODE) : 0; 1188 pPool->pApiThreadData[numReservedThreads].coreId = c + pContext->threadInfo.BASE_CORE; 1189 pPool->pApiThreadData[numReservedThreads].htId = t + pContext->threadInfo.BASE_THREAD; 1190 pPool->pApiThreadData[numReservedThreads].pContext = pContext; 1191 pPool->pApiThreadData[numReservedThreads].forceBindProcGroup = false; 1192 } 1193 1194 continue; 1195 } 1196 1197 SWR_ASSERT(workerId < numThreads); 1198 1199 pPool->pThreadData[workerId].workerId = workerId; 1200 pPool->pThreadData[workerId].procGroupId = core.procGroup; 1201 pPool->pThreadData[workerId].threadId = core.threadIds[t + pContext->threadInfo.BASE_THREAD]; 1202 pPool->pThreadData[workerId].numaId = useNuma ? (n + pContext->threadInfo.BASE_NUMA_NODE) : 0; 1203 pPool->pThreadData[workerId].coreId = c + pContext->threadInfo.BASE_CORE; 1204 pPool->pThreadData[workerId].htId = t + pContext->threadInfo.BASE_THREAD; 1205 pPool->pThreadData[workerId].pContext = pContext; 1206 pPool->pThreadData[workerId].forceBindProcGroup = false; 1207 1208 pContext->NumBEThreads++; 1209 pContext->NumFEThreads++; 1210 1211 ++workerId; 1212 } 1213 } 1214 } 1215 SWR_ASSERT(workerId == pContext->NumWorkerThreads); 1216 } 1217 } 1218 1219 ////////////////////////////////////////////////////////////////////////// 1220 /// @brief Launches worker threads in thread pool. 1221 /// @param pContext - pointer to context 1222 /// @param pPool - pointer to thread pool object. 1223 void StartThreadPool(SWR_CONTEXT* pContext, THREAD_POOL* pPool) 1224 { 1225 if (pContext->threadInfo.SINGLE_THREADED) 1226 { 1227 return; 1228 } 1229 1230 for (uint32_t workerId = 0; workerId < pContext->NumWorkerThreads; ++workerId) 1231 { 1232 pPool->pThreads[workerId] = new std::thread(workerThreadInit<true, true>, &pPool->pThreadData[workerId]); 1233 } 1234 } 1235 1236 ////////////////////////////////////////////////////////////////////////// 1237 /// @brief Destroys thread pool. 1238 /// @param pContext - pointer to context 1239 /// @param pPool - pointer to thread pool object. 1240 void DestroyThreadPool(SWR_CONTEXT *pContext, THREAD_POOL *pPool) 1241 { 1242 if (!pContext->threadInfo.SINGLE_THREADED) 1243 { 1244 // Wait for all threads to finish 1245 SwrWaitForIdle(pContext); 1246 1247 // Wait for threads to finish and destroy them 1248 for (uint32_t t = 0; t < pPool->numThreads; ++t) 1249 { 1250 // Detach from thread. Cannot join() due to possibility (in Windows) of code 1251 // in some DLLMain(THREAD_DETATCH case) blocking the thread until after this returns. 1252 pPool->pThreads[t]->detach(); 1253 delete(pPool->pThreads[t]); 1254 } 1255 1256 delete[] pPool->pThreads; 1257 1258 // Clean up data used by threads 1259 delete[] pPool->pThreadData; 1260 delete[] pPool->pApiThreadData; 1261 } 1262 } 1263