【问题标题】:Parallel ray tracing in 16x16 chunks16x16 块中的并行光线追踪
【发布时间】:2017-08-20 18:18:34
【问题描述】:

我的光线追踪器目前是多线程的,我基本上将图像分成系统拥有的多个块并并行渲染它们。但是,并不是所有的块都具有相同的渲染时间,所以大部分时间一半的运行时间只有 50% 的 cpu 使用率。

Code

std::shared_ptr<bitmap_image> image = std::make_shared<bitmap_image>(WIDTH, HEIGHT);
    auto nThreads = std::thread::hardware_concurrency();

    std::cout << "Resolution: " << WIDTH << "x" << HEIGHT << std::endl;
    std::cout << "Supersampling: " << SUPERSAMPLING << std::endl;
    std::cout << "Ray depth: " << DEPTH << std::endl;
    std::cout << "Threads: " << nThreads << std::endl;

    std::vector<RenderThread> renderThreads(nThreads);
    std::vector<std::thread> tt;

    auto size = WIDTH*HEIGHT;

    auto chunk = size / nThreads;
    auto rem = size % nThreads;

    //launch threads
    for (unsigned i = 0; i < nThreads - 1; i++)
    {
        tt.emplace_back(std::thread(&RenderThread::LaunchThread, &renderThreads[i], i * chunk, (i + 1) * chunk, image));
    }
    tt.emplace_back(std::thread(&RenderThread::LaunchThread, &renderThreads[nThreads-1], (nThreads - 1)*chunk, nThreads*chunk + rem, image));

for (auto& t : tt)
        t.join();

我想将图像分成 16x16 块或类似的东西并并行渲染,所以在每个块被渲染后,线程切换到下一个,依此类推......这将大大增加 CPU 使用率和运行时间。

如何设置我的光线追踪器以多线程方式渲染这些 16x16 块?

【问题讨论】:

  • 那么问题是什么?
  • 我的问题是如何分成16x16的块,然后将它们排队到线程中。

标签: multithreading graphics 3d raytracing


【解决方案1】:

我假设问题是“如何将块分配给各个线程?”

在您当前的解决方案中,您要提前确定区域并将它们分配给线程。诀窍是颠覆这个想法。让线程在完成一大块工作时询问下一步要做什么。

以下是线程将做什么的概要:

void WorkerThread(Manager *manager) {
  while (auto task = manager->GetTask()) {
    task->Execute();
  }
}

因此,您创建了一个 Manager 对象,该对象在每次线程调用其 GetTask 方法时返回一大块工作(以任务的形式)。由于该方法将从多个线程中调用,因此您必须确保它使用适当的同步。

std::unique_ptr<Task> Manager::GetTask() {
    std::lock_guard guard(mutex);
    std::unique_ptr<Task> t;
    if (next_row < HEIGHT) {
        t = std::make_unique<Task>(next_row);
        ++next_row;
    }
    return t;
}

在此示例中,管理器创建了一个新任务来对下一行进行光线追踪。 (如果您愿意,您可以使用 16x16 块而不是行。)当所有任务都已发出时,它只返回一个空指针,这实际上告诉调用线程没有什么可做的,然后调用线程将退出。

如果您提前完成所有任务,并让经理按要求分配,这将是典型的“工作队列”解决方案。 (一般工作队列还允许动态添加新任务,但您不需要该功能来解决这个特定问题。)

【讨论】:

  • 如何生成网格并使其适应这种类型的任务实现?
  • 这只是一堆簿记:每个任务都跟踪它应该渲染的像素块的坐标,并且它的 Execute 方法循环遍历该块中的像素。以我的经验,简单地将图像分成几行,正如我所展示的,足以充分利用您的所有核心。我会从那开始。
【解决方案2】:

我的做法有点不同:

  1. 获取 CPU 和/或内核数

    您没有指定 OS,因此您需要为此使用 OS api。搜索系统关联掩码。

  2. 将屏幕划分为线程

    我是按行而不是 16x16 块划分屏幕,所以我不需要 que 或其他东西。只需为每个 CPU/核心 创建线程,该线程将只处理其水平线射线。这很简单,所以每个线程应该有它的 ID 编号从零开始计数,CPU/核心 n 的编号因此属于每个进程的行是:

    y = ID + i*n
    

    其中i={0,1,2,3,... } 一旦y 大于或等于然后屏幕分辨率停止。这种类型的访问有其优点,例如通过 ScanLines 访问屏幕缓冲区不会在线程之间发生冲突,因为每个线程只访问其行...

    我还为每个线程设置了关联掩码,因此它使用自己的 CPU/核心 只是它给了我一个小小的提升,所以没有那么多进程切换(但那是在旧的 OS 版本很难说它现在做了什么)。

  3. 同步线程

    基本上你应该等到所有线程都完成。如果他们然后在屏幕上呈现结果。您的线程可以停止,您将在下一帧创建新线程,或者跳转到 Sleep 循环,直到再次强制渲染...

    我使用的是后一种方法,因此我不需要一遍又一遍地创建和配置线程,但要注意Sleep(1) 可以比1 ms 睡得更久。

【讨论】:

    猜你喜欢
    • 2021-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-20
    • 1970-01-01
    • 2014-11-23
    相关资源
    最近更新 更多