Я пытаюсь использовать OpenGL с windows API на разных потоках - PullRequest
2 голосов
/ 11 января 2020

Так что в основном я использую windows api для создания пустого окна, а затем я использую OpenGL для рисования в этом окне из разных потоков. Мне удалось сделать это только с одним потоком , но получение и отправка системных сообщений, чтобы окно работало, замедляло частоту кадров, которую я смог получить, поэтому я пытаюсь заставить другой поток делайте это параллельно, пока я рисую в главном потоке.

Для этого у меня есть второй поток , который создает пустое окно и вводит бесконечный l oop для обработки windows сообщение l oop. Перед вводом l oop он передает HWND пустого окна в основной поток , чтобы можно было инициализировать OpenGl. Для этого я использую функцию PostThreadMessage и использую код сообщения WM_USER и wParam сообщения для отправки обработчика окна обратно. Вот код этого вторичного потока:

bool t2main(DWORD parentThreadId, int x = 0, int y = 0, int w = 256, int h = 256, int pixelw = 2, int pixelh = 2, const char* windowName = "Window") {

// Basic drawing values
int sw = w, sh = h, pw = pixelw, ph = pixelh;
int ww = 0; int wh = 0;

// Windows API window handler
HWND windowHandler;

// Calculate total window dimensions
ww = sw * pw; wh = sh * ph;

// Create the window handler
WNDCLASS wc;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpfnWndProc = DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.lpszMenuName = nullptr;
wc.hbrBackground = nullptr;
wc.lpszClassName = "windowclass";

RegisterClass(&wc);

DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME;

RECT rWndRect = { 0, 0, ww, wh };
AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle);
int width = rWndRect.right - rWndRect.left;
int height = rWndRect.bottom - rWndRect.top;

windowHandler = CreateWindowEx(dwExStyle, "windowclass", windowName, dwStyle, x, y, width, height, NULL, NULL, GetModuleHandle(nullptr), NULL);

if(windowHandler == NULL) { return false; }

PostThreadMessageA(parentThreadId, WM_USER, (WPARAM) windowHandler, 0);

for(;;) {

    MSG msg;

    PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE);
    DispatchMessageA(&msg);

}
}

Эта функция вызывается из главной точки входа, которая правильно получает обработчик окна , а затем пытается настроить OpenGL с ним. Вот код:

int main() {

// Basic drawing values
int sw = 256, sh = 256, pw = 2, ph = 2;
int ww = 0; int wh = 0;
const char* windowName = "Window";

// Thread stuff
DWORD t1Id, t2Id;
HANDLE t1Handler, t2Handler;

// Pixel array
Pixel* pixelBuffer = nullptr;

// OpenGl device context to draw
HDC glDeviceContext;
HWND threadWindowHandler;

t1Id = GetCurrentThreadId();

std::thread t = std::thread(&t2main, t1Id, 0, 0, sw, sh, pw, ph, windowName);
t.detach();

t2Handler = t.native_handle();
t2Id = GetThreadId(t2Handler);

while(true) {

    MSG msg;

    PeekMessageA(&msg, NULL, WM_USER, WM_USER + 100, PM_REMOVE);

    if(msg.message == WM_USER) {

        threadWindowHandler = (HWND) msg.wParam;
        break;

    }

}

// Initialise OpenGL with thw window handler that we just created
glDeviceContext = GetDC(threadWindowHandler);

PIXELFORMATDESCRIPTOR pfd = {
    sizeof(PIXELFORMATDESCRIPTOR), 1,
    PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
    PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    PFD_MAIN_PLANE, 0, 0, 0, 0
};

int pf = ChoosePixelFormat(glDeviceContext, &pfd);
SetPixelFormat(glDeviceContext, pf, &pfd);

HGLRC glRenderContext = wglCreateContext(glDeviceContext);
wglMakeCurrent(glDeviceContext, glRenderContext);

// Create an OpenGl buffer
GLuint glBuffer;

glEnable(GL_TEXTURE_2D);
glGenTextures(1, &glBuffer);
glBindTexture(GL_TEXTURE_2D, glBuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

// Create a pixel buffer to hold the screen data and allocate space for it
pixelBuffer = new Pixel[sw * sh];
for(int32_t i = 0; i < sw * sh; i++) {
    pixelBuffer[i] = Pixel();
}

// Test a pixel
pixelBuffer[10 * sw + 10] = Pixel(255, 255, 255);

// Push the current buffer into view
glViewport(0, 0, ww, wh);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, sw, sh, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelBuffer);

glBegin(GL_QUADS);
glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, -1.0f, 0.0f);
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f, 1.0f, 0.0f);
glTexCoord2f(1.0, 0.0); glVertex3f(1.0f, 1.0f, 0.0f);
glTexCoord2f(1.0, 1.0); glVertex3f(1.0f, -1.0f, 0.0f);
glEnd();

SwapBuffers(glDeviceContext);

for(;;) {}

}

Для хранения информации о пикселях я использую эту структуру:

struct Pixel {
union {
    uint32_t n = 0xFF000000; //Default 255 alpha
    struct {
        uint8_t r;  uint8_t g;  uint8_t b;  uint8_t a;
    };
};

Pixel() {

    r = 0;
    g = 0;
    b = 0;
    a = 255;

}

Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255) {

    r = red;
    g = green;
    b = blue;
    a = alpha;

}

};

Когда я пытаюсь запустить этот код, я не получаю нужный пиксель вывод, вместо этого я просто получаю пустое окно , как будто рука OpenGl не инициализирована правильно. Когда я использую тот же код , но все в одном потоке, я получаю пустое окно с пикселем в нем. Что я здесь делаю не так ?, Есть ли что-то, что мне нужно сделать, прежде чем я инициализирую OpenGl в другом потоке? Я ценю все виды отзывов. Заранее спасибо.

Ответы [ 2 ]

3 голосов
/ 11 января 2020

Здесь есть несколько проблем. Давайте рассмотрим их по порядку.

Сначала давайте вспомним правила:

OpenGL и потоки

Основные c правила для OpenGL в отношении windows, устройства контекст и потоки:

  1. Контекст OpenGL не связан с конкретным окном или контекстом устройства.

  2. Вы можете создать контекст OpenGL " current "на любом контексте устройства (HD C, обычно ассоциируется с окном), который совместим с контекстом устройства, с которым контекст был изначально создан.

  3. Контекст OpenGL может быть «текущим» только для одного потока за раз или вообще не быть активным.

  4. Для перемещения «текущего состояния» контекста OpenGL из одного потока в другой вы делаете:

    • first: снять "current" контекст в потоке, в котором он используется в данный момент
    • second: сделать его "current" в потоке, в котором вы хотите быть текущим.
  5. Более одного (включая все) потоки в процессе n имеют контекст OpenGL «текущий» в то же время.

  6. Несколько контекстов OpenGL (включая все) - которые будут правилом 5 быть текущими в разных потоках - могут быть текущими с помощью один и тот же контекст устройства (HD C) одновременно.

  7. Не существует определенных правил для команд рисования, выполняемых одновременно в разных потоках, но текущих в одном и том же HD C. Упорядочение должно осуществляться пользователем путем установки соответствующих блокировок, которые работают примитивами синхронизации OpenGL. До введения в OpenGL явных объектов синхронизации с мелкими зернами единственной доступной синхронизацией были glFinish и неявные вызовы точки синхронизации OpenGL (например, glReadPixels).

Заблуждения в вашем понимание того, что делает OpenGL

Это происходит из чтения комментариев в вашем коде:

int main() {

Почему ваша функция потока называется main. main - это зарезервированное имя, предназначенное исключительно для функции ввода процесса. Даже если ваша запись WinMain, вы не должны использовать main в качестве имени функции.

// Pixel array
Pixel* pixelBuffer = nullptr;

Неясно, для чего предназначен pixelBuffer, позже. Вы назовете это на текстуру. но, очевидно, не настраивайте чертеж для использования текстуры.

t1Id = GetCurrentThreadId();

std::thread t = std::thread(&t2main, t1Id, 0, 0, sw, sh, pw, ph, windowName);
t.detach();

t2Handler = t.native_handle();
t2Id = GetThreadId(t2Handler);

Что, я даже не знаю. Что это должно делать в первую очередь? Перво-наперво: не смешивайте потоки Win32 API и C ++ std::thread. Решите одно и придерживайтесь его.

while(true) {

    MSG msg;

    PeekMessageA(&msg, NULL, WM_USER, WM_USER + 100, PM_REMOVE);

    if(msg.message == WM_USER) {

        threadWindowHandler = (HWND) msg.wParam;
        break;

    }

}

Какого черта вы передаете дескриптор окна через сообщение потока? Это так неправильно на многих уровнях. Все потоки живут в одном и том же адресном пространстве, так что вы можете использовать очередь, или глобальные переменные, или передать в качестве параметра функции входа потока, et c., Et c.

Более того, вы мог просто создать контекст OpenGL в главном потоке, а затем просто передать его.

wglMakeCurrent(glDeviceContext, glRenderContext);

// Create an OpenGl buffer
GLuint glBuffer;

glEnable(GL_TEXTURE_2D);
glGenTextures(1, &glBuffer);

Это не создает буферный объект OpenGL, оно создает имя текстуры.

glBindTexture(GL_TEXTURE_2D, glBuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

// Create a pixel buffer to hold the screen data and allocate space 
pixelBuffer[10 * sw + 10] = Pixel(255, 255, 255);for it

Э-э-э, нет, вы не предоставляете буферам рисования для OpenGL таким образом. Черт возьми, вы даже не предоставляете буферам рисования для OpenGL вообще (это не D3D12, Metal или Vulkan, где вы это делаете).

// Push the current buffer into view
glViewport(0, 0, ww, wh);

Нееет. Это не то, что делает glViewport!

glViewport является частью состояния конвейера преобразования и, в конечном счете, задает целевой прямоугольник, в котором внутри рисованного объекта будет отображаться объем пространства клипа , Это делает абсолютно ничего относительно буферов для рисования.

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, sw, sh, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelBuffer);

Я думаю, вы не понимаете, для чего нужна текстура , Этот вызов выполняет копирование контекстов pixelBuffer в текущую связанную текстуру. После этого OpenGL больше не касается pixelBuffer.

glBegin(GL_QUADS);
glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, -1.0f, 0.0f);
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f, 1.0f, 0.0f);
glTexCoord2f(1.0, 0.0); glVertex3f(1.0f, 1.0f, 0.0f);
glTexCoord2f(1.0, 1.0); glVertex3f(1.0f, -1.0f, 0.0f);
glEnd();

Здесь вы что-то рисуете, но никогда не включали использование текстуры. Итак, все, что нужно для настройки текстуры, - это даром.

SwapBuffers(glDeviceContext);

for(;;) {}

}

Так что после замены оконных буферов вы заставляете нить вращаться вечно. С этим связаны две проблемы: в другом потоке все еще есть основное сообщение l oop, которое обрабатывает другие сообщения для окна. Включая, возможно, WM_PAINT, и в зависимости от того, настроили ли вы фон bru sh и / или как вы обрабатываете WM_ERASEBKGND все, что вы просто рисуете, может мгновенно исчезнуть sh после этого.

И вращением поток, который вы тратите процессорное время без всякой причины. С таким же успехом можно закончить тему.

0 голосов
/ 12 января 2020

Я решил проблему с помощью комментария @ datenwolf в основном. Во-первых, я использовал указатель переменной для передачи переменных между потоками, что избавило от необходимости PostThreadMessageA, что было основной причиной, по которой я использовал потоки winapi. Я также немного изменил код OpenGl и, наконец, получил то, что хотел.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...