在第二线程上加载OpenGL资源

6
我使用win32线程和OpenGL 2.1。我想要实现的是,在后台加载整个3D场景的同时,渲染一个简单的图像,显示“正在加载”。目前它可以工作,但是我有一个问题,有时我的立方体贴图的一部分会从Mozilla Firefox浏览器中获取数据(这是怎么回事?),并忽略那个小盒子上的纹理,它只是一个精灵并且它在应该在的地方。这种情况每三次尝试中就会发生一次。
这是我的线程代码:
WindowsThread::WindowsThread(HGLRC graphicsContext, HDC deviceContext) :
    graphicsContext_(graphicsContext),
    deviceContext_(deviceContext),
    running_(false),
    task_(0),
    mode_(WT_NORMAL)
{
    handle_ = CreateThread(0, 0,
        (unsigned long (__stdcall *)(void *)) this->staticRun,
        (void*) this, CREATE_SUSPENDED, &id_);

    if (handle_ == 0) {
        LOGE("Unable to create thread.");
        return;
    }

    if (!SetThreadPriority(handle_, THREAD_PRIORITY_NORMAL)) {
        LOGE("Unable to set thread priority for thread.");
        return;
    }
}

WindowsThread::~WindowsThread() {
    finishTask();
    running_ = false;
    WaitForSingleObject(handle_, INFINITE);
    CloseHandle(handle_);
    wglDeleteContext(graphicsContext_);
}

void WindowsThread::start() {
    running_ = true;
    if (!ResumeThread(handle_)) {
        LOGW("Unable to resume thread.");
    }
}

bool WindowsThread::isRunning() {
    return running_;
}

void WindowsThread::setTask(Task* task, Mode mode) {
    finishTask();
    task_ = task;
    mode_ = mode;
}

bool WindowsThread::hasTask() {
    return task_ != 0;
}

void WindowsThread::finishTask() {
    while (task_ != 0) {
        Sleep(1);
    }
}

void WindowsThread::stop() {
    running_ = false;
}

int WindowsThread::staticRun(void* thread) {
    return ((WindowsThread*) thread)->run();
}

int WindowsThread::run() {
    wglMakeCurrent(deviceContext_, graphicsContext_);
    while (running_) {
        if (task_ != 0) {
            task_->run();
            task_ = 0;
        }
        Sleep(10);
    }
    wglMakeCurrent(0, 0);
    return 1;
}

线程管理器:

WindowsThreadManager::WindowsThreadManager(
    System* system, UINT threadPoolSize)
{
    if (threadPoolSize == 0) {
        SYSTEM_INFO info;
        GetSystemInfo(&info);
        threadPoolSize = info.dwNumberOfProcessors;
        if (threadPoolSize == 0) {
            threadPoolSize = 1;
        }
    }
    LOGI("Number of threads used: %d", threadPoolSize);
    masterContext_ = wglGetCurrentContext();
    HDC hdc = wglGetCurrentDC();
    for (UINT i = 0; i < threadPoolSize; i++) {
        HGLRC threadContext = wglCreateContext(hdc);
        wglShareLists(masterContext_, threadContext);
        WindowsThread* thread = new WindowsThread(threadContext, hdc);
        thread->start();
        threads_.push_back(thread);
    }
}

WindowsThreadManager::~WindowsThreadManager() {
    for (UINT i = 0; i < threads_.size(); i++) {
        delete threads_[i];
    }
    for (UINT i = 0; i < tasks_.size(); i++) {
        delete tasks_[i];
    }
}

void WindowsThreadManager::execute(Task* task, Mode mode) {
    WindowsThread::Mode wtMode = WindowsThread::WT_NORMAL;
    if (mode == TM_GRAPHICS_CONTEXT) {
        wtMode = WindowsThread::WT_GRPAHICS_CONTEXT;
    }
    tasks_.push_back(task);
    for (UINT i = 0; i < threads_.size(); i++) {
        if (!threads_[i]->hasTask()) {
            threads_[i]->setTask(task, wtMode);
            return;
        }
    }
    threads_[0]->setTask(task, wtMode);
}

void WindowsThreadManager::joinAll() {
    for (UINT i = 0; i < threads_.size(); i++) {
        if (threads_[i]->hasTask()) {
            threads_[i]->finishTask();
        }
    }
}

我在Windows 8上使用最新的Nvidia 670GTX驱动程序。有什么想法问题可能出在哪里?
[编辑] 我在我的加载线程末尾添加了glFinish(),现在一切正常。我在某个地方读到,OpenGL不会立即完成所有工作,所以我猜这就是情况,在它完成工作之前,上下文被设置为NULL。

1
请注意,OpenGL和线程通常不兼容。 - BЈовић
是的,我对此有很多了解。事实上,在此之前,我已经实现了两个上下文,一个用于渲染,另一个用于加载资源。当资源加载完成后,我只需将该上下文作为主上下文,并删除上一个主上下文即可。这样做没有出现任何问题。 - SMGhost
@BЈовић:OpenGL和多线程可以实现,但要做好并不简单。 - datenwolf
@datenwolf 是的,但如果我理解帖子的话,他正在一个线程中创建纹理(或者在一个线程中渲染东西?)。由于它是随机发生的(3次中有1次),这表明他没有做对,并且创建了某种竞争条件。 - BЈовић
@Nicol Bolas:好的,谢谢,我不会再这样做了。 - SMGhost
显示剩余2条评论
1个回答

7
现在它可以工作了,但我有一个问题,有时我的立方体贴图的一部分会从Mozilla Firefox浏览器中获取数据(这到底是怎么回事?)
您的纹理从未初始化的图形内存中接收数据,很可能包含来自先前使用该内存区域的另一个进程的残留图像。如果出现以下情况,可能会发生此类问题:
a) 驱动程序存在错误,不会在线程之间同步资源

b) 如果您尝试修改纹理,同时它绑定到其他线程的纹理单元。
编辑:您可以(并且应该)自己引入适当的同步。只是因为它提高了性能。使用条件变量在纹理当前未忙碌时在线程之间通信。理想情况下,您使用两个或多个纹理,以轮换方式更新。

如果我理解正确的话,您建议只使用一个上下文,并同步使用它的所有部分,以便不会出现竞争条件?还是使用多个上下文,但仍然自己进行同步,这样OpenGL驱动程序就不必进行同步? - SMGhost
2
@SMGhost:不要这样做!使用两个上下文(不要在线程之间玩烫手山芋)。但是请使用条件变量,以便更新线程知道何时可以安全地修改纹理内容。如果您还没有这样做,请使用glTexSubImage2D。将2或3个纹理保持在循环中,其中您将更新正在绘制的索引之前的纹理。 - datenwolf
目前还没有情况是,一旦加载了纹理就会修改它,然后在加载资源后,所有的图形操作都只在主线程上的单个上下文中工作。我尝试实现整个场景加载机制的方式是,加载器线程加载的内容在整个资源加载过程中从未被主线程使用,直到所有资源都加载完毕,然后我才从这些资源开始渲染。我认为在这种情况下不需要条件变量? :) - SMGhost
@SMGhost:啊,好的。我以为你的意图是创建某种视频播放器。那么在这种情况下,不需要条件变量。 - datenwolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接