Chiharu の日記

絵描き C/C++ プログラマーの日記です。

Parallel force - パラレル・フォース 〜スプライト管理、並列描画のコードなど (2)

昨日寝る前に、お酒など飲みながら、なんとなく、先ほどの関数を分離してみました。

//
// スレッド プロシージャ
//
void Screen::threadProc(ThreadNotifier& iNotifier)
{
  // コア数取得
  const auto aProcessors = SystemInfoHelper::getProcessors();
  
  // 描画スレッド構築
  std::vector<TaskThread> aTaskThreads;
  aTaskThreads.reserve(aProcessors);
  
  for (std::uint32_t aCt = 0; aCt < aProcessors; aCt++) {
    // スレッド構築
    std::shared_ptr<Thread> aThread(new Thread());
    std::shared_ptr<Event> aBeginEvent(new Event()), aEndEvent(new Event());
    TaskThread aTaskThread = { aThread, aBeginEvent, aEndEvent };
    aTaskThreads.push_back(aTaskThread);
    
    // スレッド開始
    aThread->start(createDrawerThreadProc(aBeginEvent, aEndEvent));
  }
  
  // 描画ループ
  while (!iNotifier.isStopRequestIssued()) {
    try {
      auto aLock = pforce::lock(mDrawerThreadCriticalSection);
      if (mPrimaryScreen->isDirty()) {
        // 描画準備
        mTaskInfo.reset(mPrimaryScreen);
        auto aScreenLock = pforce::lock(*mTaskInfo.screen);
        
        // 描画開始
        std::for_each(aTaskThreads.begin(), aTaskThreads.end(), [](TaskThread& iThread) {
          iThread.beginEvent->set();
        });
        
        // 描画終了
        std::for_each(aTaskThreads.begin(), aTaskThreads.end(), [](TaskThread& iThread) {
          iThread.endEvent->wait(INFINITE), iThread.endEvent->reset();
        });
      }
    } catch (std::exception&) {
      // do nothing.
    }
#if defined(PFORCE_STRESS_TEST)
    // ストレス テストの場合はスリープしない。
#else
    iNotifier.sleep(1);
#endif
  }
  
  // 描画スレッド終了
  mTaskInfo.reset();
  std::for_each(aTaskThreads.begin(), aTaskThreads.end(), [](TaskThread& iThread) {
    iThread.thread->invalidate(), iThread.beginEvent->set();
  });
  std::for_each(aTaskThreads.begin(), aTaskThreads.end(), [](TaskThread& iThread) {
    iThread.thread->stop();
  });
}

//
// 描画スレッド プロシージャを生成する。
//
const ThreadFunction Screen::createDrawerThreadProc(const std::shared_ptr<Event> iBeginEvent, const std::shared_ptr<Event> iEndEvent)
{
  // 合成バッファ構築
  const auto aRowbytes = ImageHelper::getRowbytes(mPrimaryScreen->getWidth(), imageB8G8R8X8);
  auto aPixelData = MemoryHelper::alloc<std::uint8_t>(aRowbytes * mPrimaryScreen->getBandHeight());
  std::shared_ptr<IImage> aImage(new Image(mPrimaryScreen->getWidth(), mPrimaryScreen->getBandHeight(), imageB8G8R8X8,
    aRowbytes, aPixelData));
  
  // 描画スレッド プロシージャ
  auto aDrawerThreadProc = [this, iBeginEvent, iEndEvent, aImage](ThreadNotifier& iNotifier) -> void {
    while (!iNotifier.isStopRequestIssued()) {
      iBeginEvent->wait(INFINITE), iBeginEvent->reset();
      try {
        if (mTaskInfo.screen) {
          // 描画
          auto aScreen = mTaskInfo.screen.get();
          const auto aMax = aScreen->getBandCount();
          const auto aBandHeight = aScreen->getBandHeight();
          for (;;) {
            const auto aBandIndex = mTaskInfo.getTargetBandIndex();
            if (aBandIndex < aMax) {
              // 描画
              Rect aDirtyArea = aScreen->getDirtyArea(aBandIndex);
              if (!RectHelper::isEmpty(aDirtyArea)) {
                aScreen->draw(*aImage, aBandIndex);
                // 表画面へ転送
                auto aLock = pforce::lock(mTaskInfo.cs);  // GDI はスレッドセーフでないため転送はシーケンシャルに実施
                blitImageToDeviceContext(mDeviceContext, *aImage, aDirtyArea, aBandIndex * aBandHeight);
              }
            } else {
              // 終了
              break;
            }
          }
        }
      } catch (std::exception&) {
        // do nothing.
      }
      iEndEvent->set();
    }
  };
  
  return aDrawerThreadProc;
}

うん。こっちの方が意味の通りが良い気がしますね。
さて、次の更新の時にはもうちょっと前進しようかな。