#c #multithreading #opengl #member-function-pointers
#c #многопоточность #opengl #указатель на член
Вопрос:
Я использую OpenGL для отображения графических данных и пользовательских элементов пользовательского интерфейса. У меня есть SDI с вкладками, в котором окна документов используют окна без полей в качестве видовых экранов, каждый видовой экран содержит отдельный поток для выполнения только операций рисования GL, В то время как все остальные операции (мышь, клавиатура и т. Д.) обрабатываются / должны обрабатываться в обратном вызове видового экрана. Графический поток создается с помощью _beginthreadex;
void ViewportController::create(){
hThreadHandle = reinterpret_cast<HANDLE>(
_beginthreadex(NULL,
0,
amp;threadProc,
(void*)lpThreadController,
0,
amp;uiThreadID)
);
if (hThreadHandle == 0)
{
throw std::exception("Failed to create thread");
}
}
с помощью функции start_address, содержащей цикл с использованием PeekMessage, позволяющий области просмотра передавать сообщения в графический поток, с классом ThreadController, переданным в качестве аргумента arglist;
unsigned __stdcall ViewportController::threadProc(void* pArguments)
{
ThreadController* ctrl = static_cast<ThreadController*>(pArguments);
MSG mMsg = { 0 };
bool bStop = false;
if (wglMakeCurrent(ctrl->getDC(), ctrl->getRC()))
{
ctrl->InitGL();
if (!bStop)
bStop = (bool)!SendMessage(ctrl->getHandle(), UWM_SHOW, 0, 0);
}
while (!bStop)
{
while ((bool)PeekMessage(amp;mMsg, (HWND)(-1), 0, 0, PM_REMOVE))
{
switch (mMsg.message)
{
case UWM_DRAW:
ctrl->draw(mMsg.lParam, mMsg.lParam);
break;
case WM_CLOSE:
bStop = true;
break;
case WM_KEYDOWN:
ctrl->keyDown(mMsg.wParam, mMsg.lParam);
break;
case WM_LBUTTONDOWN:
ctrl->lButtonDown(mMsg.lParam, mMsg.lParam);
break;
case WM_MOUSEMOVE:
ctrl->mouseMove(mMsg.lParam, mMsg.lParam);
break;
case WM_MOUSEWHEEL:
ctrl->mouseWheel(mMsg.lParam, mMsg.lParam);
break;
case WM_PAINT:
ctrl->paint();
break;
case WM_SIZE:
ctrl->size(mMsg.lParam, mMsg.lParam);
break;
default:
return 0;
}
SwapBuffers(ctrl->getDC());
}
}
_endthreadex(0);
return 0;
}
На данный момент у меня есть все пользовательские элементы пользовательского интерфейса (текст, поля редактирования и т.д.), Содержащиеся в классе ThreadController. Это очень затрудняет динамическое создание пользовательского интерфейса и, что более важно, сводит на нет цель создания конвейера только для графики, поэтому я хотел бы сделать следующее;
- Объявляйте функции в потоке окна просмотра, которые отображают данные и элементы пользовательского интерфейса;
void ViewportController::drawSomeStuff()
{
glMatrixMode(GL_PROJECTION);
/*get the appropriate cameras projection matrix*/
glMatrixMode(GL_MODELVIEW);
/*get the same cameras modelview matrix*/
/*Lots of stuff beginning with "gl" goes here*/
}
void ViewportController::drawSomeOtherStuff()
{
/*See above*/
}
void ViewportController::drawSomeDifferentOtherStuffMaybeAnEditBox()
{
/*See above*/
}
и «фиктивная» функция с той же сигнатурой в классе ThreadController;
void drawSomeStuff(){}
- Передайте указатель на требуемую функцию графическому потоку с помощью PostThreadMessage();
typedef void (ViewportController::*RenderThreadFn)(); // declare pointer typedef in the ViewportController class
void ViewportController::size(){
RenderThreadFn pRenderFrame = amp;ViewportController::drawSomeStuff/*drawSomeOtherStuff*//*drawSomeDifferentOtherStuffMaybeAnEditBox*/;
PostThreadMessage(uiThreadID, UWM_DRAW, 0,reinterpret_cast<LPARAM(amp;pRenderFrame));
}
- Перехватите функцию PeekMessage() в графическом потоке и выполните функцию;
typedef void (ThreadController::*RenderThreadFn)(); // declare pointer typedef in the ThreadController class
ThreadController::draw(WPARAM wParam, LPARAM lParam){
RenderThreadFn* pRenderFrame = reinterpret_cast<RenderThreadFn*>(lParam); // get the pointer - everything up to here works
(*this.*pRenderFrame)(); // Executing the function here does not work
}
- Повышаю себе зарплату за то, что я так чертовски хорош во всем.
Это не работает. Очевидно, что указатели в каждом классе имеют разные определения, но я не знаю, как это обойти или как еще я мог бы добиться того же результата.
Я был бы чрезвычайно благодарен за любые рекомендации по этому вопросу, я пробовал несколько вещей, например, наследование общего класса как для классов viewport, так и для контроллеров потоков и определение typedef для указателя в этом, но я все еще очень новичок, и мое решение проблем не намного более продвинуто илипродуктивнее, чем просто случайное нажатие на клавиатуру.
Есть идеи о том, как это можно сделать? Можно ли это вообще сделать? Это хорошая идея или я должен использовать совершенно другой метод, чтобы получить то, что я хочу? Пожалуйста, помогите, если сможете, я буду помнить вас, когда стану богатым и знаменитым.
Ответ №1:
postMessage — не лучший способ упорядочивать указатели на функции и рабочие элементы между потоками. Вам лучше просто изобрести свою собственную потокобезопасную структуру данных и поставить рабочие элементы в очередь в поток рендеринга.
Но если вы действительно хотите передать функцию с помощью postMessage … тогда std::function
комбинация с лямбдой может сработать.
Объявите это в общем заголовочном файле:
struct MyCustomMessage
{
std::function<void()> fn;
};
Из потока ViewPortController сделайте это, чтобы «отправить» вызов функции в другой поток.
MyCustomMessage *pMsg = new MyCustomMessage();
pMsg->fn = [this](){
this->DrawSomeStuff();
};
PostThreadMessage(uiThreadID, UWM_DRAW, 0, reinterpret_cast<LPARAM>(pMsg);
В другом потоке:
ThreadController::draw(WPARAM wParam, LPARAM lParam) {
MyCustomMessage* pMsg = reinterpret_cast<MyCustomMessage*>(lParam);
pMsg->fn();
delete pMsg;
}
Комментарии:
1. При условии, что вы обеспечиваете адекватное время жизни, нет проблем с последующей передачей ссылок на целые объекты в другие потоки. Вы можете создавать / удалять или, что еще лучше, создавать их пул, чтобы их можно было перерабатывать.
Ответ №2:
Вы можете использовать шаблон Active-Object для отправки данных в ваш поток через параллельную очередь. Активный объект (проверьте запись в Википедии ) инкапсулирует потоковую функцию и параллельную очередь для постановки команд в очередь для этого потока. Для отправки команд вы могли бы сделать что-то вроде:
struct Command {
int id;
std::function<void()> fn;
Command(int id_, std::function<void()> amp;fn, size_t size){ ... }
};
MyActiveObject object;
Command *drawCmd = new Command(DrawUiEvent, func);
object.enqueue(drawCmd);
Это поможет решить проблемы, когда ваша потоковая функция должна выполнять множество обновлений из-за большого взаимодействия с пользователем или чего-то другого.