Обновление от 26 июня 2024 г.: вопрос переписан, чтобы включить обновленную информацию и консолидировать результаты исследования.
РЕЗЮМЕ
Похоже, что vsync с OpenGL не работает в Windows в оконном режиме. Даже в очень простых сценариях с поддержкой вертикальной синхронизации кадры часто могут быть отброшены даже в ограниченных средах, где это не является убедительным оправданием для такого количества пропущенных кадров.
Базовая структура такова:
init_opengl_for_window_dc();
wglSwapIntervalEXT(1); // or: -1
while (true)
{
glClear(...);
render_scene_with_opengl();
SwapBuffers(...);
[optionally:] glFinish();
[optionally (see below):] DwmFlush();
}
Независимо от того, является ли это оптимальным с точки зрения задержки ввода, при этом не должно пропадать кадров, пока сцена мучительно проста. Но отбрасывается неоправданно большое количество кадров, если DwmFlush не добавлено. DwmFlush кажется надежным обходным решением, но у него есть последствия.
Одна и та же структура, адаптированная как к D3D9, так и к D3D11, обеспечивает стабильную частоту кадров практически с нулевым количеством пропущенных кадров, как и ожидалось.
Протестировано и воспроизводится на нескольких установках Intel + NVIDIA с экранами в диапазоне от 60 до 144 Гц, хотя при высокой частоте кадров наблюдать становится все труднее. Не удалось наблюдать ни на одной установке AMD (пока). Не воспроизводится на всех конфигурациях Intel + NVIDIA, которые я пробовал. Если воспроизводимо в конкретной системе, то оно всегда воспроизводимо в этой системе. Воспроизведено в Windows 7 и Windows 10. Пока не было возможности протестировать систему Windows 11 Intel + NVIDIA. На момент тестирования ни одна из тестовых систем не имела нескольких мониторов, и не было ничего другого, что могло бы повлиять на представление, например, воспроизведения мультимедиа, программного обеспечения для записи экрана или чего-то подобного. Кроме того, хотя записи ниже показывают открытый отладчик VS, оптимизация компилятора или отсутствие инструментов отладки ничего не меняют.
Проблема не зависит от API. Тестовые программы не имеют никаких зависимостей (кроме Windows), но не имеет значения, используются ли такие библиотеки, как SDL, glfw или SFML.
Настройка измерения
Ошибку сложно обнаружить по своей природе. Можно было бы визуализировать объект, движущийся по поверхности, записать это с помощью высокоскоростной камеры и посмотреть, не меняется ли он.
Метод, который я использовал, использует 4 цветных треугольника в разные позиции, одна из которых рисуется в каждом кадре, в зависимости от текущего счетчика кадров. Таким образом, каждый из 4 треугольников мерцает раз в 4 кадра. Хотя вы, возможно, и не сможете определить, является ли движение прерывистым или плавным, вы можете сравнительно легко определить, отсутствовал ли один из 4 треугольников в цикле.
!!! ПРЕДУПРЕЖДЕНИЕ ОБ ЭПИЛЕПСИИ !!! : Также обратите внимание, что цвет фона меняется в каждом кадре, чтобы его было легче обнаружить.
Вот несколько высокоскоростных записей (240 кадров в секунду, замедление до 12,5 %):
Также отказ от ответственности: я не могу гарантировать правильность моего исследования. Эта тема была очень утомительной и разочаровывающей, а сбор всей информации занял много времени просто из-за того, насколько неуловима эта ошибка. Сначала я понятия не имел, где искать. Так. что касается измерений и выводов, вы можете подвергнуть сомнению результаты и воспроизвести их самостоятельно, если у вас есть затронутая система.
D3D9:
https ://www.youtube.com/watch?v=nCffpRO216w
(Запись короткая, но при этом практически никогда не заикается).
Кроме того, D3D11 ведет себя эффективно так же, как и Д3Д9. Для D3D11 отдельных записей нет.
OpenGL:
Вы это видите? Пропущенные кадры не являются проблемой записи. Они на самом деле отсутствуют. Некоторые пропущенные кадры могут быть простительны, особенно вскоре после запуска приложения, поскольку процесс может все еще завершаться или что-то в этом роде, но это слишком много пропущенных кадров.
Некоторые наблюдения
SwapBuffers (Present в D3D9) вызовы блокируют процессор. Измеряя время с помощью QPC, SwapBuffers выглядит более стабильным и составляет около 16,6 мс, тогда как, как ни странно, Present в D3D колеблется сильнее, между 15 и 17 мс.
Если glFinish Добавлен : В некоторых системах теперь эта функция съедает время, которое съел бы следующий SwapBuffers (как и ожидалось), однако ошибка не исправлена. Но в некоторых системах glFinish вообще не блокируется после SwapBuffers. Это соответствует описанию потенциальной ошибки внизу этой страницы:
В одном тестовом примере, в котором для параметра «Ждать вертикального обновления» было установлено значение Всегда в обеих конфигурациях графический чипсет Intel от ноутбука Sandy Bridge 2011 года выпуска исключал замену буфера из glFinish, тем самым создавая 1000 кадров в секунду
Добавление glClear перед glFinish также не исправляет ситуацию. Этот метод упоминается в этой теме форума.
Примечание по glFinish и DwmFlush: Даже если они работают (что glFinish нет, по крайней мере, ненадежно), они не обязательно являются допустимым решением, поскольку подразумевают переход от сценария с эффективной тройной буферизацией к сценарию с эффективной двойной буферизацией. В зависимости от приложения это может быть подходящим или неподходящим. Надежное представление кадров должно работать и в сценарии с тройной буферизацией.
Самое важное наблюдение здесь заключается в том, что когда кадр удаляется, это происходит не потому, что уже слишком поздно. . Вместо этого это происходит потому, что следующий кадр слишком ранний. Это так, даже если используется glFinish.
Это должно быть заметно при реализации простого следящего за мышью. Хотя в совершенно правильной среде измерения механический привод перемещает мышь с постоянной скоростью, для быстрого тестирования я использовал SetCursor.
D3D9 с ведомым устройством:
OpenGL с подписчиком:
Это нестабильно, и на этот раз вы можете сказать наверняка, потому что SetCursor будет надежно перемещаться аппаратный курсор мыши каждый кадр, а ведомый (белый треугольник) отстает (что ожидаемо), но иногда немного подпрыгивает вперед.
Единственная причина, которую я могу придумать, это то, что в реализации OpenGL некоторых драйверов видеокарты должно существовать состояние гонки, из-за которого кадр, отправленный SwapBuffers, ошибочно перезаписывает уже отправленный кадр, так что новый кадр будет случайно обработан текущая композиция вместо постановки в очередь на следующую композицию. Ранее отправленный кадр потерян.
Поскольку это кажется аппаратно-зависимым, я предполагаю, что ошибка, скорее всего, связана с реализацией OpenGL некоторых драйверов видеокарт NVIDIA.< /p>
Кроме того, ошибка исчезает, когда сцена становится достаточно сложной. Бросьте в него VBO с несколькими миллионами случайных треугольников, и этого больше не произойдет.
Некоторые упоминания из предыдущей версии этого вопроса
Когда я гуглил заикание opengl vsync или падение кадров opengl vsync или подобные запросы, я обнаружил, что у многих людей возникает эта проблема (или очень похожая), но все же есть Кажется, что это не является последовательным решением реальной проблемы (также многие неадекватно ответили на вопросы по обмену стеками разработчиков игр, а также много сообщений на форумах, не требующих усилий).
Подводя итог моим исследованиям: кажется, что оконный менеджер композитинга (DWM), используемый в новых версиях Windows, обеспечивает тройную буферизацию, и это мешает вертикальной синхронизации. Люди предлагают отключить DWM, не использовать vsync или перейти в полноэкранный режим, но все это не является решением исходной проблемы (СНОСКА 1). Я также не нашел подробного объяснения, почему тройная буферизация вызывает эту проблему с vsync или почему технологически невозможно решить эту проблему.
Однако: я также проверил, что это действительно так. не встречается в Linux даже на ОЧЕНЬ слабых ПК. Следовательно, для аппаратного ускорения на основе OpenGL должна быть технически возможно (по крайней мере в целом) включить функциональную vsync без пропуска кадров.
И, наконец, обязательно должно быть программное обеспечение для Windows, это игры, мультимедийные приложения, творческие программы, программы 3D-моделирования/рендеринга и т. д., которые используют OpenGL и правильно работают в оконном режиме, сохраняя при этом точный рендеринг, без ожидания занятости процессора и без пропадания кадров.
Остается вопрос: почему это происходит и как это можно надежно решить?
КОД< /p>
Это конечно не самый чистый код, но по крайней мере у него нет зависимостей, и все это в одном файле (кроме шейдеров в версии D3D11).
Обратите внимание, что в отношении этого вопроса не имеет значения, используете ли вы современный OpenGL вместо хака glVertex2f, представленного здесь. Однако эта версия намного короче.
D3D9
#include
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "dwmapi.lib")
static ATOM RegisterWindowClass();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD color;
};
int main(int argc, char* argv[])
{
ATOM WindowClassAtom = RegisterWindowClass();
HWND hWnd = CreateWindowExW(0, (LPCWSTR)WindowClassAtom, L"FlickerD3D9", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 800, 600, nullptr, nullptr, nullptr, nullptr);
ShowWindow(hWnd, SW_SHOWNORMAL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
IDirect3D9 *d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp = {};
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
IDirect3DDevice9 *d3ddev = nullptr;
d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &d3ddev);
LPDIRECT3DVERTEXBUFFER9 v_buffer = nullptr;
LARGE_INTEGER perf_freq;
QueryPerformanceFrequency(&perf_freq);
LARGE_INTEGER sw;
QueryPerformanceCounter(&sw);
MSG msg;
int frame_counter = 0;
while (true)
{
while (PeekMessageW(&msg, nullptr, 0, 0, TRUE))
{
if (msg.message == WM_QUIT)
{
return 0;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
RECT ClientRect;
GetClientRect(hWnd, &ClientRect);
SIZE ClientSize = { ClientRect.right, ClientRect.bottom };
if (ClientSize.cx != d3dpp.BackBufferWidth ||
ClientSize.cy != d3dpp.BackBufferHeight)
{
// Resize viewport.
OutputDebugStringW(L"Viewport size changed.\r\n");
d3dpp.BackBufferWidth = 0;
d3dpp.BackBufferHeight = 0;
d3ddev->Reset(&d3dpp);
if (v_buffer != nullptr)
{
v_buffer->Release();
v_buffer = nullptr;
}
D3DVIEWPORT9 viewport = {};
viewport.Width = ClientSize.cx;
viewport.Height = ClientSize.cy;
d3ddev->SetViewport(&viewport);
}
if (v_buffer == nullptr)
{
d3ddev->CreateVertexBuffer(6 * sizeof(CUSTOMVERTEX), 0, CUSTOMFVF, D3DPOOL_MANAGED, &v_buffer, NULL);
}
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);
POINT CursorPos;
GetCursorPos(&CursorPos);
ScreenToClient(hWnd, &CursorPos);
if (GetAsyncKeyState(VK_SPACE))
{
CursorPos.x = (CursorPos.x + 10) % ClientSize.cx;
POINT CursorPosNew = CursorPos;
ClientToScreen(hWnd, &CursorPosNew);
SetCursorPos(CursorPosNew.x, CursorPosNew.y);
}
d3ddev->BeginScene(); // begins the 3D scene
D3DCOLOR color;
switch (frame_counter % 4)
{
case 0:
color = D3DCOLOR_XRGB(255, 0, 0);
break;
case 1:
color = D3DCOLOR_XRGB(0, 255, 0);
break;
case 2:
color = D3DCOLOR_XRGB(0, 255, 255);
break;
case 3:
color = D3DCOLOR_XRGB(255, 255, 0);
break;
}
int pos_l = 0 + (frame_counter % 4) * 40;
int pos_t = 0;
int pos_r = pos_l + 20;
int pos_b = pos_t + 20;
D3DCOLOR White = D3DCOLOR_XRGB(255, 255, 255);
CUSTOMVERTEX OurVertices[] =
{
{ pos_l / (ClientSize.cx / 2.0f), pos_t / (ClientSize.cy / 2.0f), 1.0f, color },
{ pos_r / (ClientSize.cx / 2.0f), pos_t / (ClientSize.cy / 2.0f), 1.0f, color },
{ pos_l / (ClientSize.cx / 2.0f), pos_b / (ClientSize.cy / 2.0f), 1.0f, color },
{ -1.0f + (CursorPos.x ) / (ClientSize.cx / 2.0f), 1.0f + (-CursorPos.y ) / (ClientSize.cy / 2.0f), 1.0f, White },
{ -1.0f + (CursorPos.x + 20) / (ClientSize.cx / 2.0f), 1.0f + (-CursorPos.y ) / (ClientSize.cy / 2.0f), 1.0f, White },
{ -1.0f + (CursorPos.x + 20) / (ClientSize.cx / 2.0f), 1.0f + (-CursorPos.y + 20) / (ClientSize.cy / 2.0f), 1.0f, White },
};
void* pVoid = nullptr;
v_buffer->Lock(0, sizeof(OurVertices), (void**)&pVoid, 0); // locks v_buffer, the buffer we made earlier
memcpy(pVoid, OurVertices, sizeof(OurVertices)); // copy vertices to the vertex buffer
v_buffer->Unlock(); // unlock v_buffer
// select which vertex format we are using
d3ddev->SetFVF(CUSTOMFVF);
d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);
// select the vertex buffer to display
d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX));
// copy the vertex buffer to the back buffer
d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
d3ddev->EndScene(); // ends the 3D scene
d3ddev->Present(NULL, NULL, NULL, NULL); // displays the created frame
if (GetAsyncKeyState(VK_LBUTTON))
DwmFlush();
if (GetAsyncKeyState(VK_RBUTTON))
Sleep(10);
LARGE_INTEGER sw_old = sw;
QueryPerformanceCounter(&sw);
float elapsed_ms = (sw.QuadPart - sw_old.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("%f\r\n", elapsed_ms);
frame_counter += 1;
}
return 0;
}
static ATOM RegisterWindowClass()
{
WNDCLASSEXW cls = {};
cls.cbSize = sizeof(cls);
cls.style = CS_HREDRAW | CS_VREDRAW;
cls.lpfnWndProc = WndProc;
cls.hCursor = LoadCursorW(nullptr, IDC_ARROW);
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
cls.lpszClassName = L"FlickerD3D9";
return RegisterClassExW(&cls);
}
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
OpenGL
#include
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "dwmapi.lib")
static ATOM RegisterWindowClass();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int main(int argc, char *argv[])
{
ATOM WindowClassAtom = RegisterWindowClass();
HWND hWnd = CreateWindowExW(0, (LPCWSTR)WindowClassAtom, L"FlickerGL", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 800, 600, nullptr, nullptr, nullptr, nullptr);
ShowWindow(hWnd, SW_SHOWNORMAL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
HDC hdc = GetDC(hWnd);
PIXELFORMATDESCRIPTOR pfd = {};
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cAlphaBits = 8;
pfd.cDepthBits = 24;
int pixelFormatInt = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, pixelFormatInt, &pfd);
HGLRC glContext = wglCreateContext(hdc);
wglMakeCurrent(hdc, glContext);
auto wglSwapIntervalEXT = (GLboolean(*)(GLint))wglGetProcAddress("wglSwapIntervalEXT");
wglSwapIntervalEXT(1);
LARGE_INTEGER perf_freq;
QueryPerformanceFrequency(&perf_freq);
LARGE_INTEGER sw;
QueryPerformanceCounter(&sw);
MSG msg;
int frame_counter = 0;
while (true)
{
while (PeekMessageW(&msg, nullptr, 0, 0, TRUE))
{
if (msg.message == WM_QUIT)
{
return 0;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
RECT ClientRect;
GetClientRect(hWnd, &ClientRect);
SIZE ClientSize = { ClientRect.right, ClientRect.bottom };
glViewport(0, 0, ClientSize.cx, ClientSize.cy);
POINT CursorPos;
GetCursorPos(&CursorPos);
ScreenToClient(hWnd, &CursorPos);
if (GetAsyncKeyState(VK_SPACE))
{
CursorPos.x = (CursorPos.x + 10) % ClientSize.cx;
POINT NewCursorPos = CursorPos;
ClientToScreen(hWnd, &NewCursorPos);
SetCursorPos(NewCursorPos.x, NewCursorPos.y);
}
if (frame_counter % 2 == 0)
glClearColor(0.15f, 0.05f, 0.3f, 1.0f);
else
glClearColor(0, 0, 0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
switch (frame_counter % 4)
{
case 0:
glColor3f(1.0f, 0, 0);
break;
case 1:
glColor3f(0, 1.0f, 0);
break;
case 2:
glColor3f(0, 1.0f, 1.0f);
break;
case 3:
glColor3f(1.0f, 1.0f, 0);
break;
}
int pos_l = (frame_counter % 8) * 40;
int pos_t = 0;
int pos_r = pos_l + 20;
int pos_b = pos_t + 20;
glBegin(GL_TRIANGLES);
glVertex2f(pos_l / (ClientSize.cx / 2.0f), pos_t / (ClientSize.cy / 2.0f));
glVertex2f(pos_l / (ClientSize.cx / 2.0f), pos_b / (ClientSize.cy / 2.0f));
glVertex2f(pos_r / (ClientSize.cx / 2.0f), pos_t / (ClientSize.cy / 2.0f));
glColor3f(1.0f, 1.0f, 1.0f);
glVertex2f(CursorPos.x / (ClientSize.cx / 2.0f) - 1.0f, -CursorPos.y / (ClientSize.cy / 2.0f) + 1.0f);
glVertex2f(CursorPos.x / (ClientSize.cx / 2.0f) - 1.0f, -(CursorPos.y + 20) / (ClientSize.cy / 2.0f) + 1.0f);
glVertex2f((CursorPos.x + 20) / (ClientSize.cx / 2.0f) - 1.0f, -CursorPos.y / (ClientSize.cy / 2.0f) + 1.0f);
glEnd();
{
LARGE_INTEGER before;
QueryPerformanceCounter(&before);
SwapBuffers(hdc);
LARGE_INTEGER after;
QueryPerformanceCounter(&after);
float elapsed_ms = (after.QuadPart - before.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("SwapBuffers: %f\r\n", elapsed_ms);
}
if (GetAsyncKeyState(VK_LBUTTON))
DwmFlush();
if (GetAsyncKeyState(VK_RBUTTON))
{
LARGE_INTEGER before;
QueryPerformanceCounter(&before);
glFinish();
LARGE_INTEGER after;
QueryPerformanceCounter(&after);
float elapsed_ms = (after.QuadPart - before.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("glFinish: %f\r\n", elapsed_ms);
}
if (GetAsyncKeyState(VK_MBUTTON))
{
LARGE_INTEGER before;
QueryPerformanceCounter(&before);
glClear(GL_COLOR_BUFFER_BIT);
glFinish();
LARGE_INTEGER after;
QueryPerformanceCounter(&after);
float elapsed_ms = (after.QuadPart - before.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("glClear + glFinish: %f\r\n", elapsed_ms);
}
LARGE_INTEGER sw_old = sw;
QueryPerformanceCounter(&sw);
float elapsed_ms = (sw.QuadPart - sw_old.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("%f\r\n", elapsed_ms);
frame_counter += 1;
}
return 0;
}
static ATOM RegisterWindowClass()
{
WNDCLASSEXW cls = {};
cls.cbSize = sizeof(cls);
cls.style = CS_HREDRAW | CS_VREDRAW;
cls.lpfnWndProc = WndProc;
cls.hCursor = LoadCursorW(nullptr, IDC_ARROW);
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
cls.lpszClassName = L"FlickerGL";
return RegisterClassExW(&cls);
}
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
D3D11 (просто для полноты картины)
Сначала необходимо скомпилировать шейдеры с помощью fxc, в pshader. pso и vshader.vso
Здесь нет ничего такого, чего нельзя было бы увидеть и в версии D3D9!
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment (lib, "d3d11.lib")
static ATOM RegisterWindowClass();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static void ReadFile(void** data, int* length, LPCWSTR file);
static IDXGISwapChain* swapchain;
static ID3D11Device* dev;
static ID3D11DeviceContext* devcon;
static ID3D11RenderTargetView* backbuffer;
static ID3D11VertexShader* pVS;
static ID3D11PixelShader* pPS;
static ID3D11InputLayout* pLayout;
static ID3D11Buffer* pVBuffer;
struct D3DXCOLOR
{
D3DXCOLOR()
: R(0), G(0), B(0), A(0)
{}
D3DXCOLOR(FLOAT R, FLOAT G, FLOAT B, FLOAT A)
: R(R), G(G), B(B), A(A)
{
}
FLOAT R, G, B, A;
};
struct VERTEX
{
FLOAT X, Y, Z;
D3DXCOLOR Color;
};
int main(int argc, char* argv[])
{
ATOM WindowClassAtom = RegisterWindowClass();
HWND hWnd = CreateWindowExW(0, (LPCWSTR)WindowClassAtom, L"FlickerD3D11", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 800, 600, nullptr, nullptr, nullptr, nullptr);
ShowWindow(hWnd, SW_SHOWNORMAL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferCount = 2;
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scd.BufferDesc.Width = 800;
scd.BufferDesc.Height = 600;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scd.OutputWindow = hWnd;
scd.SampleDesc.Count = 1;
scd.Windowed = TRUE;
scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
scd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &scd, &swapchain, &dev, nullptr, &devcon);
void* vsdata = nullptr;
int vslength = 0;
ReadFile(&vsdata, &vslength, L"vshader.vso");
dev->CreateVertexShader(vsdata, vslength, nullptr, &pVS);
void* psdata;
int pslength;
ReadFile(&psdata, &pslength, L"pshader.pso");
dev->CreatePixelShader(psdata, pslength, nullptr, &pPS);
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
dev->CreateInputLayout(ied, 2, vsdata, vslength, &pLayout);
D3D11_BUFFER_DESC bd = {};
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(VERTEX) * 3;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
dev->CreateBuffer(&bd, NULL, &pVBuffer);
D3D11_VIEWPORT viewport = {};
LARGE_INTEGER perf_freq;
QueryPerformanceFrequency(&perf_freq);
LARGE_INTEGER sw;
QueryPerformanceCounter(&sw);
MSG msg;
int frame_counter = 0;
while (true)
{
while (PeekMessageW(&msg, nullptr, 0, 0, TRUE))
{
if (msg.message == WM_QUIT)
{
return 0;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
RECT ClientRect;
GetClientRect(hWnd, &ClientRect);
SIZE ClientSize = { ClientRect.right, ClientRect.bottom };
if (ClientSize.cx != viewport.Width ||
ClientSize.cy != viewport.Height)
{
devcon->OMSetRenderTargets(0, nullptr, nullptr);
if (backbuffer != nullptr)
{
backbuffer->Release();
backbuffer = nullptr;
}
swapchain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
}
if (backbuffer == nullptr)
{
ID3D11Texture2D* pBackBuffer;
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
dev->CreateRenderTargetView(pBackBuffer, nullptr, &backbuffer);
pBackBuffer->Release();
// Set the viewport
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = ClientRect.right;
viewport.Height = ClientRect.bottom;
devcon->RSSetViewports(1, &viewport);
}
// set the render target as the back buffer
devcon->OMSetRenderTargets(1, &backbuffer, nullptr);
// clear the back buffer to a deep blue
float back_color[] = { 0.15f, 0.05f, 0.4f, 1.0f };
devcon->ClearRenderTargetView(backbuffer, back_color);
// do 3D rendering on the back buffer here
// set the shader objects
devcon->VSSetShader(pVS, 0, 0);
devcon->PSSetShader(pPS, 0, 0);
devcon->IASetInputLayout(pLayout);
D3DXCOLOR color;
int frame_counter_mod = frame_counter % 4;
switch (frame_counter_mod)
{
case 0:
color = D3DXCOLOR( 1.0f, 0, 0, 1.0f );
break;
case 1:
color = D3DXCOLOR( 0, 1.0f, 0, 1.0f );
break;
case 2:
color = D3DXCOLOR( 0, 1.0f, 1.0f, 1.0f );
break;
case 3:
color = D3DXCOLOR( 1.0f, 1.0f, 0, 1.0f );
break;
}
int pos_l = frame_counter_mod * 40;
int pos_t = 0;
int pos_r = pos_l + 20;
int pos_b = pos_t + 20;
VERTEX OurVertices[] =
{
{ pos_l / (ClientSize.cx / 2.0f), -pos_t / (ClientSize.cy / 2.0f), 0.0f, color},
{ pos_r / (ClientSize.cx / 2.0f), -pos_t / (ClientSize.cy / 2.0f), 0.0f, color },
{ pos_l / (ClientSize.cx / 2.0f), -pos_b / (ClientSize.cy / 2.0f), 0.0f, color },
};
D3D11_MAPPED_SUBRESOURCE ms;
devcon->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms);
memcpy(ms.pData, OurVertices, sizeof(OurVertices));
devcon->Unmap(pVBuffer, NULL);
UINT stride = sizeof(VERTEX);
UINT offset = 0;
devcon->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);
devcon->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
devcon->Draw(3, 0);
swapchain->Present(1, 0);
//DwmFlush();
//Sleep(100);
LARGE_INTEGER sw_old = sw;
QueryPerformanceCounter(&sw);
float elapsed_ms = (sw.QuadPart - sw_old.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("%f\r\n", elapsed_ms);
frame_counter += 1;
}
return 0;
}
static void ReadFile(void** data, int* length, LPCWSTR filename)
{
FILE* file;
_wfopen_s(&file, filename, L"rb");
fseek(file, 0, SEEK_END);
*length = ftell(file);
fseek(file, 0, SEEK_SET);
*data = malloc(*length);
fread(*data, 1, *length, file);
fclose(file);
}
static ATOM RegisterWindowClass()
{
WNDCLASSEXW cls = {};
cls.cbSize = sizeof(cls);
cls.style = CS_HREDRAW | CS_VREDRAW;
cls.lpfnWndProc = WndProc;
cls.hCursor = LoadCursorW(nullptr, IDC_ARROW);
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
cls.lpszClassName = L"FlickerD3D11";
return RegisterClassExW(&cls);
}
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
shaders.hlsl
struct VOut
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
VOut output;
output.position = position;
output.color = color;
return output;
}
float4 PShader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return color;
}
Подробнее здесь: https://stackoverflow.com/questions/456 ... on-windows
Надежная оконная вертикальная синхронизация с OpenGL в Windows? ⇐ C++
Программы на C++. Форум разработчиков
1719428714
Anonymous
Обновление от 26 июня 2024 г.: вопрос переписан, чтобы включить обновленную информацию и консолидировать результаты исследования.
[b]РЕЗЮМЕ[/b]
Похоже, что vsync с OpenGL не работает в Windows в оконном режиме. Даже в очень простых сценариях с поддержкой вертикальной синхронизации кадры часто могут быть отброшены даже в ограниченных средах, где это не является убедительным оправданием для такого количества пропущенных кадров.
Базовая структура такова:
init_opengl_for_window_dc();
wglSwapIntervalEXT(1); // or: -1
while (true)
{
glClear(...);
render_scene_with_opengl();
SwapBuffers(...);
[optionally:] glFinish();
[optionally (see below):] DwmFlush();
}
Независимо от того, является ли это оптимальным с точки зрения задержки ввода, при этом не должно пропадать кадров, пока сцена мучительно проста. Но отбрасывается неоправданно большое количество кадров, если DwmFlush не добавлено. DwmFlush кажется надежным обходным решением, но у него есть последствия.
Одна и та же структура, адаптированная как к D3D9, так и к D3D11, обеспечивает стабильную частоту кадров практически с нулевым количеством пропущенных кадров, как и ожидалось.
Протестировано и воспроизводится на нескольких установках Intel + NVIDIA с экранами в диапазоне от 60 до 144 Гц, хотя при высокой частоте кадров наблюдать становится все труднее. Не удалось наблюдать ни на одной установке AMD (пока). Не воспроизводится на всех конфигурациях Intel + NVIDIA, которые я пробовал. Если воспроизводимо в конкретной системе, то оно всегда воспроизводимо в этой системе. Воспроизведено в Windows 7 и Windows 10. Пока не было возможности протестировать систему Windows 11 Intel + NVIDIA. На момент тестирования ни одна из тестовых систем не имела нескольких мониторов, и не было ничего другого, что могло бы повлиять на представление, например, воспроизведения мультимедиа, программного обеспечения для записи экрана или чего-то подобного. Кроме того, хотя записи ниже показывают открытый отладчик VS, оптимизация компилятора или отсутствие инструментов отладки ничего не меняют.
Проблема не зависит от API. Тестовые программы не имеют никаких зависимостей (кроме Windows), но не имеет значения, используются ли такие библиотеки, как SDL, glfw или SFML.
[b]Настройка измерения[/b]
Ошибку сложно обнаружить по своей природе. Можно было бы визуализировать объект, движущийся по поверхности, записать это с помощью высокоскоростной камеры и посмотреть, не меняется ли он.
Метод, который я использовал, использует 4 цветных треугольника в разные позиции, одна из которых рисуется в каждом кадре, в зависимости от текущего счетчика кадров. Таким образом, каждый из 4 треугольников мерцает раз в 4 кадра. Хотя вы, возможно, и не сможете определить, является ли движение прерывистым или плавным, вы можете сравнительно легко определить, отсутствовал ли один из 4 треугольников в цикле.
[b]!!! ПРЕДУПРЕЖДЕНИЕ ОБ ЭПИЛЕПСИИ !!![/b] : Также обратите внимание, что цвет фона меняется в каждом кадре, чтобы его было легче обнаружить.
Вот несколько высокоскоростных записей (240 кадров в секунду, замедление до 12,5 %):
Также отказ от ответственности: я не могу гарантировать правильность моего исследования. Эта тема была очень утомительной и разочаровывающей, а сбор всей информации занял много времени просто из-за того, насколько неуловима эта ошибка. Сначала я понятия не имел, где искать. Так. что касается измерений и выводов, вы можете подвергнуть сомнению результаты и воспроизвести их самостоятельно, если у вас есть затронутая система.
D3D9:
https ://www.youtube.com/watch?v=nCffpRO216w
(Запись короткая, но при этом практически никогда не заикается).
Кроме того, D3D11 ведет себя эффективно так же, как и Д3Д9. Для D3D11 отдельных записей нет.
OpenGL:
[youtube]GYsQTCqmoqE[/youtube]
Вы это видите? Пропущенные кадры не являются проблемой записи. Они на самом деле отсутствуют. Некоторые пропущенные кадры могут быть простительны, особенно вскоре после запуска приложения, поскольку процесс может все еще завершаться или что-то в этом роде, но это слишком много пропущенных кадров.
Некоторые наблюдения
SwapBuffers (Present в D3D9) вызовы блокируют процессор. Измеряя время с помощью QPC, SwapBuffers выглядит более стабильным и составляет около 16,6 мс, тогда как, как ни странно, Present в D3D колеблется сильнее, между 15 и 17 мс.
Если glFinish Добавлен : В некоторых системах теперь эта функция съедает время, которое съел бы следующий SwapBuffers (как и ожидалось), однако ошибка не исправлена. Но в некоторых системах glFinish вообще не блокируется после SwapBuffers. Это соответствует описанию потенциальной ошибки внизу этой страницы:
В одном тестовом примере, в котором для параметра «Ждать вертикального обновления» было установлено значение Всегда в обеих конфигурациях графический чипсет Intel от ноутбука Sandy Bridge 2011 года выпуска исключал замену буфера из glFinish, тем самым создавая 1000 кадров в секунду
Добавление glClear перед glFinish также не исправляет ситуацию. Этот метод упоминается в этой теме форума.
[b]Примечание по glFinish и DwmFlush:[/b] Даже если они работают (что glFinish нет, по крайней мере, ненадежно), они не обязательно являются допустимым решением, поскольку подразумевают переход от сценария с эффективной тройной буферизацией к сценарию с эффективной двойной буферизацией. В зависимости от приложения это может быть подходящим или неподходящим. Надежное представление кадров должно работать и в сценарии с тройной буферизацией.
Самое важное наблюдение здесь заключается в том, что [b]когда кадр удаляется, это происходит не потому, что уже слишком поздно. . Вместо этого это происходит потому, что следующий кадр слишком ранний[/b]. Это так, даже если используется glFinish.
Это должно быть заметно при реализации простого следящего за мышью. Хотя в совершенно правильной среде измерения механический привод перемещает мышь с постоянной скоростью, для быстрого тестирования я использовал SetCursor.
D3D9 с ведомым устройством:
[youtube]tr-DqshL9Tk[/youtube]
OpenGL с подписчиком:
[youtube]vP9wSLtTKog[/youtube]
Это нестабильно, и на этот раз вы можете сказать наверняка, потому что SetCursor будет надежно перемещаться аппаратный курсор мыши каждый кадр, а ведомый (белый треугольник) отстает (что ожидаемо), но иногда немного подпрыгивает вперед.
Единственная причина, которую я могу придумать, это то, что в реализации OpenGL некоторых драйверов видеокарты должно существовать состояние гонки, из-за которого кадр, отправленный SwapBuffers, ошибочно перезаписывает уже отправленный кадр, так что новый кадр будет случайно обработан текущая композиция вместо постановки в очередь на следующую композицию. Ранее отправленный кадр потерян.
Поскольку это кажется аппаратно-зависимым, я предполагаю, что ошибка, скорее всего, связана с реализацией OpenGL некоторых драйверов видеокарт NVIDIA.< /p>
Кроме того, [b]ошибка исчезает, когда сцена становится достаточно сложной[/b]. Бросьте в него VBO с несколькими миллионами случайных треугольников, и этого больше не произойдет.
Некоторые упоминания из предыдущей версии этого вопроса
Когда я гуглил заикание opengl vsync или падение кадров opengl vsync или подобные запросы, я обнаружил, что у многих людей возникает эта проблема (или очень похожая), но все же есть Кажется, что это не является последовательным решением реальной проблемы (также многие неадекватно ответили на вопросы по обмену стеками разработчиков игр, а также много сообщений на форумах, не требующих усилий).
Подводя итог моим исследованиям: кажется, что оконный менеджер композитинга (DWM), используемый в новых версиях Windows, обеспечивает тройную буферизацию, и это мешает вертикальной синхронизации. Люди предлагают отключить DWM, не использовать vsync или перейти в полноэкранный режим, но все это не является решением исходной проблемы (СНОСКА 1). Я также не нашел подробного объяснения, почему тройная буферизация вызывает эту проблему с vsync или почему технологически невозможно решить эту проблему.
Однако: я также проверил, что это действительно так. не встречается в Linux даже на ОЧЕНЬ слабых ПК. Следовательно, для аппаратного ускорения на основе OpenGL должна быть технически возможно (по крайней мере в целом) включить функциональную vsync без пропуска кадров.
И, наконец, обязательно должно быть программное обеспечение для Windows, это игры, мультимедийные приложения, творческие программы, программы 3D-моделирования/рендеринга и т. д., которые используют OpenGL и правильно работают в оконном режиме, сохраняя при этом точный рендеринг, без ожидания занятости процессора и без пропадания кадров.
Остается вопрос: почему это происходит и как это можно надежно решить?
[b]КОД[/b]< /p>
Это конечно не самый чистый код, но по крайней мере у него нет зависимостей, и все это в одном файле (кроме шейдеров в версии D3D11).
Обратите внимание, что в отношении этого вопроса не имеет значения, используете ли вы современный OpenGL вместо хака glVertex2f, представленного здесь. Однако эта версия намного короче.
[b]D3D9[/b]
#include
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "dwmapi.lib")
static ATOM RegisterWindowClass();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD color;
};
int main(int argc, char* argv[])
{
ATOM WindowClassAtom = RegisterWindowClass();
HWND hWnd = CreateWindowExW(0, (LPCWSTR)WindowClassAtom, L"FlickerD3D9", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 800, 600, nullptr, nullptr, nullptr, nullptr);
ShowWindow(hWnd, SW_SHOWNORMAL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
IDirect3D9 *d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp = {};
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
IDirect3DDevice9 *d3ddev = nullptr;
d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &d3ddev);
LPDIRECT3DVERTEXBUFFER9 v_buffer = nullptr;
LARGE_INTEGER perf_freq;
QueryPerformanceFrequency(&perf_freq);
LARGE_INTEGER sw;
QueryPerformanceCounter(&sw);
MSG msg;
int frame_counter = 0;
while (true)
{
while (PeekMessageW(&msg, nullptr, 0, 0, TRUE))
{
if (msg.message == WM_QUIT)
{
return 0;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
RECT ClientRect;
GetClientRect(hWnd, &ClientRect);
SIZE ClientSize = { ClientRect.right, ClientRect.bottom };
if (ClientSize.cx != d3dpp.BackBufferWidth ||
ClientSize.cy != d3dpp.BackBufferHeight)
{
// Resize viewport.
OutputDebugStringW(L"Viewport size changed.\r\n");
d3dpp.BackBufferWidth = 0;
d3dpp.BackBufferHeight = 0;
d3ddev->Reset(&d3dpp);
if (v_buffer != nullptr)
{
v_buffer->Release();
v_buffer = nullptr;
}
D3DVIEWPORT9 viewport = {};
viewport.Width = ClientSize.cx;
viewport.Height = ClientSize.cy;
d3ddev->SetViewport(&viewport);
}
if (v_buffer == nullptr)
{
d3ddev->CreateVertexBuffer(6 * sizeof(CUSTOMVERTEX), 0, CUSTOMFVF, D3DPOOL_MANAGED, &v_buffer, NULL);
}
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);
POINT CursorPos;
GetCursorPos(&CursorPos);
ScreenToClient(hWnd, &CursorPos);
if (GetAsyncKeyState(VK_SPACE))
{
CursorPos.x = (CursorPos.x + 10) % ClientSize.cx;
POINT CursorPosNew = CursorPos;
ClientToScreen(hWnd, &CursorPosNew);
SetCursorPos(CursorPosNew.x, CursorPosNew.y);
}
d3ddev->BeginScene(); // begins the 3D scene
D3DCOLOR color;
switch (frame_counter % 4)
{
case 0:
color = D3DCOLOR_XRGB(255, 0, 0);
break;
case 1:
color = D3DCOLOR_XRGB(0, 255, 0);
break;
case 2:
color = D3DCOLOR_XRGB(0, 255, 255);
break;
case 3:
color = D3DCOLOR_XRGB(255, 255, 0);
break;
}
int pos_l = 0 + (frame_counter % 4) * 40;
int pos_t = 0;
int pos_r = pos_l + 20;
int pos_b = pos_t + 20;
D3DCOLOR White = D3DCOLOR_XRGB(255, 255, 255);
CUSTOMVERTEX OurVertices[] =
{
{ pos_l / (ClientSize.cx / 2.0f), pos_t / (ClientSize.cy / 2.0f), 1.0f, color },
{ pos_r / (ClientSize.cx / 2.0f), pos_t / (ClientSize.cy / 2.0f), 1.0f, color },
{ pos_l / (ClientSize.cx / 2.0f), pos_b / (ClientSize.cy / 2.0f), 1.0f, color },
{ -1.0f + (CursorPos.x ) / (ClientSize.cx / 2.0f), 1.0f + (-CursorPos.y ) / (ClientSize.cy / 2.0f), 1.0f, White },
{ -1.0f + (CursorPos.x + 20) / (ClientSize.cx / 2.0f), 1.0f + (-CursorPos.y ) / (ClientSize.cy / 2.0f), 1.0f, White },
{ -1.0f + (CursorPos.x + 20) / (ClientSize.cx / 2.0f), 1.0f + (-CursorPos.y + 20) / (ClientSize.cy / 2.0f), 1.0f, White },
};
void* pVoid = nullptr;
v_buffer->Lock(0, sizeof(OurVertices), (void**)&pVoid, 0); // locks v_buffer, the buffer we made earlier
memcpy(pVoid, OurVertices, sizeof(OurVertices)); // copy vertices to the vertex buffer
v_buffer->Unlock(); // unlock v_buffer
// select which vertex format we are using
d3ddev->SetFVF(CUSTOMFVF);
d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);
// select the vertex buffer to display
d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX));
// copy the vertex buffer to the back buffer
d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
d3ddev->EndScene(); // ends the 3D scene
d3ddev->Present(NULL, NULL, NULL, NULL); // displays the created frame
if (GetAsyncKeyState(VK_LBUTTON))
DwmFlush();
if (GetAsyncKeyState(VK_RBUTTON))
Sleep(10);
LARGE_INTEGER sw_old = sw;
QueryPerformanceCounter(&sw);
float elapsed_ms = (sw.QuadPart - sw_old.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("%f\r\n", elapsed_ms);
frame_counter += 1;
}
return 0;
}
static ATOM RegisterWindowClass()
{
WNDCLASSEXW cls = {};
cls.cbSize = sizeof(cls);
cls.style = CS_HREDRAW | CS_VREDRAW;
cls.lpfnWndProc = WndProc;
cls.hCursor = LoadCursorW(nullptr, IDC_ARROW);
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
cls.lpszClassName = L"FlickerD3D9";
return RegisterClassExW(&cls);
}
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
[b]OpenGL[/b]
#include
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "dwmapi.lib")
static ATOM RegisterWindowClass();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int main(int argc, char *argv[])
{
ATOM WindowClassAtom = RegisterWindowClass();
HWND hWnd = CreateWindowExW(0, (LPCWSTR)WindowClassAtom, L"FlickerGL", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 800, 600, nullptr, nullptr, nullptr, nullptr);
ShowWindow(hWnd, SW_SHOWNORMAL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
HDC hdc = GetDC(hWnd);
PIXELFORMATDESCRIPTOR pfd = {};
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cAlphaBits = 8;
pfd.cDepthBits = 24;
int pixelFormatInt = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, pixelFormatInt, &pfd);
HGLRC glContext = wglCreateContext(hdc);
wglMakeCurrent(hdc, glContext);
auto wglSwapIntervalEXT = (GLboolean(*)(GLint))wglGetProcAddress("wglSwapIntervalEXT");
wglSwapIntervalEXT(1);
LARGE_INTEGER perf_freq;
QueryPerformanceFrequency(&perf_freq);
LARGE_INTEGER sw;
QueryPerformanceCounter(&sw);
MSG msg;
int frame_counter = 0;
while (true)
{
while (PeekMessageW(&msg, nullptr, 0, 0, TRUE))
{
if (msg.message == WM_QUIT)
{
return 0;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
RECT ClientRect;
GetClientRect(hWnd, &ClientRect);
SIZE ClientSize = { ClientRect.right, ClientRect.bottom };
glViewport(0, 0, ClientSize.cx, ClientSize.cy);
POINT CursorPos;
GetCursorPos(&CursorPos);
ScreenToClient(hWnd, &CursorPos);
if (GetAsyncKeyState(VK_SPACE))
{
CursorPos.x = (CursorPos.x + 10) % ClientSize.cx;
POINT NewCursorPos = CursorPos;
ClientToScreen(hWnd, &NewCursorPos);
SetCursorPos(NewCursorPos.x, NewCursorPos.y);
}
if (frame_counter % 2 == 0)
glClearColor(0.15f, 0.05f, 0.3f, 1.0f);
else
glClearColor(0, 0, 0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
switch (frame_counter % 4)
{
case 0:
glColor3f(1.0f, 0, 0);
break;
case 1:
glColor3f(0, 1.0f, 0);
break;
case 2:
glColor3f(0, 1.0f, 1.0f);
break;
case 3:
glColor3f(1.0f, 1.0f, 0);
break;
}
int pos_l = (frame_counter % 8) * 40;
int pos_t = 0;
int pos_r = pos_l + 20;
int pos_b = pos_t + 20;
glBegin(GL_TRIANGLES);
glVertex2f(pos_l / (ClientSize.cx / 2.0f), pos_t / (ClientSize.cy / 2.0f));
glVertex2f(pos_l / (ClientSize.cx / 2.0f), pos_b / (ClientSize.cy / 2.0f));
glVertex2f(pos_r / (ClientSize.cx / 2.0f), pos_t / (ClientSize.cy / 2.0f));
glColor3f(1.0f, 1.0f, 1.0f);
glVertex2f(CursorPos.x / (ClientSize.cx / 2.0f) - 1.0f, -CursorPos.y / (ClientSize.cy / 2.0f) + 1.0f);
glVertex2f(CursorPos.x / (ClientSize.cx / 2.0f) - 1.0f, -(CursorPos.y + 20) / (ClientSize.cy / 2.0f) + 1.0f);
glVertex2f((CursorPos.x + 20) / (ClientSize.cx / 2.0f) - 1.0f, -CursorPos.y / (ClientSize.cy / 2.0f) + 1.0f);
glEnd();
{
LARGE_INTEGER before;
QueryPerformanceCounter(&before);
SwapBuffers(hdc);
LARGE_INTEGER after;
QueryPerformanceCounter(&after);
float elapsed_ms = (after.QuadPart - before.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("SwapBuffers: %f\r\n", elapsed_ms);
}
if (GetAsyncKeyState(VK_LBUTTON))
DwmFlush();
if (GetAsyncKeyState(VK_RBUTTON))
{
LARGE_INTEGER before;
QueryPerformanceCounter(&before);
glFinish();
LARGE_INTEGER after;
QueryPerformanceCounter(&after);
float elapsed_ms = (after.QuadPart - before.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("glFinish: %f\r\n", elapsed_ms);
}
if (GetAsyncKeyState(VK_MBUTTON))
{
LARGE_INTEGER before;
QueryPerformanceCounter(&before);
glClear(GL_COLOR_BUFFER_BIT);
glFinish();
LARGE_INTEGER after;
QueryPerformanceCounter(&after);
float elapsed_ms = (after.QuadPart - before.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("glClear + glFinish: %f\r\n", elapsed_ms);
}
LARGE_INTEGER sw_old = sw;
QueryPerformanceCounter(&sw);
float elapsed_ms = (sw.QuadPart - sw_old.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("%f\r\n", elapsed_ms);
frame_counter += 1;
}
return 0;
}
static ATOM RegisterWindowClass()
{
WNDCLASSEXW cls = {};
cls.cbSize = sizeof(cls);
cls.style = CS_HREDRAW | CS_VREDRAW;
cls.lpfnWndProc = WndProc;
cls.hCursor = LoadCursorW(nullptr, IDC_ARROW);
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
cls.lpszClassName = L"FlickerGL";
return RegisterClassExW(&cls);
}
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
[b]D3D11[/b] (просто для полноты картины)
Сначала необходимо скомпилировать шейдеры с помощью fxc, в pshader. pso и vshader.vso
Здесь нет ничего такого, чего нельзя было бы увидеть и в версии D3D9!
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#pragma comment (lib, "d3d11.lib")
static ATOM RegisterWindowClass();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static void ReadFile(void** data, int* length, LPCWSTR file);
static IDXGISwapChain* swapchain;
static ID3D11Device* dev;
static ID3D11DeviceContext* devcon;
static ID3D11RenderTargetView* backbuffer;
static ID3D11VertexShader* pVS;
static ID3D11PixelShader* pPS;
static ID3D11InputLayout* pLayout;
static ID3D11Buffer* pVBuffer;
struct D3DXCOLOR
{
D3DXCOLOR()
: R(0), G(0), B(0), A(0)
{}
D3DXCOLOR(FLOAT R, FLOAT G, FLOAT B, FLOAT A)
: R(R), G(G), B(B), A(A)
{
}
FLOAT R, G, B, A;
};
struct VERTEX
{
FLOAT X, Y, Z;
D3DXCOLOR Color;
};
int main(int argc, char* argv[])
{
ATOM WindowClassAtom = RegisterWindowClass();
HWND hWnd = CreateWindowExW(0, (LPCWSTR)WindowClassAtom, L"FlickerD3D11", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 800, 600, nullptr, nullptr, nullptr, nullptr);
ShowWindow(hWnd, SW_SHOWNORMAL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferCount = 2;
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scd.BufferDesc.Width = 800;
scd.BufferDesc.Height = 600;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scd.OutputWindow = hWnd;
scd.SampleDesc.Count = 1;
scd.Windowed = TRUE;
scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
scd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &scd, &swapchain, &dev, nullptr, &devcon);
void* vsdata = nullptr;
int vslength = 0;
ReadFile(&vsdata, &vslength, L"vshader.vso");
dev->CreateVertexShader(vsdata, vslength, nullptr, &pVS);
void* psdata;
int pslength;
ReadFile(&psdata, &pslength, L"pshader.pso");
dev->CreatePixelShader(psdata, pslength, nullptr, &pPS);
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
dev->CreateInputLayout(ied, 2, vsdata, vslength, &pLayout);
D3D11_BUFFER_DESC bd = {};
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(VERTEX) * 3;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
dev->CreateBuffer(&bd, NULL, &pVBuffer);
D3D11_VIEWPORT viewport = {};
LARGE_INTEGER perf_freq;
QueryPerformanceFrequency(&perf_freq);
LARGE_INTEGER sw;
QueryPerformanceCounter(&sw);
MSG msg;
int frame_counter = 0;
while (true)
{
while (PeekMessageW(&msg, nullptr, 0, 0, TRUE))
{
if (msg.message == WM_QUIT)
{
return 0;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
RECT ClientRect;
GetClientRect(hWnd, &ClientRect);
SIZE ClientSize = { ClientRect.right, ClientRect.bottom };
if (ClientSize.cx != viewport.Width ||
ClientSize.cy != viewport.Height)
{
devcon->OMSetRenderTargets(0, nullptr, nullptr);
if (backbuffer != nullptr)
{
backbuffer->Release();
backbuffer = nullptr;
}
swapchain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
}
if (backbuffer == nullptr)
{
ID3D11Texture2D* pBackBuffer;
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
dev->CreateRenderTargetView(pBackBuffer, nullptr, &backbuffer);
pBackBuffer->Release();
// Set the viewport
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = ClientRect.right;
viewport.Height = ClientRect.bottom;
devcon->RSSetViewports(1, &viewport);
}
// set the render target as the back buffer
devcon->OMSetRenderTargets(1, &backbuffer, nullptr);
// clear the back buffer to a deep blue
float back_color[] = { 0.15f, 0.05f, 0.4f, 1.0f };
devcon->ClearRenderTargetView(backbuffer, back_color);
// do 3D rendering on the back buffer here
// set the shader objects
devcon->VSSetShader(pVS, 0, 0);
devcon->PSSetShader(pPS, 0, 0);
devcon->IASetInputLayout(pLayout);
D3DXCOLOR color;
int frame_counter_mod = frame_counter % 4;
switch (frame_counter_mod)
{
case 0:
color = D3DXCOLOR( 1.0f, 0, 0, 1.0f );
break;
case 1:
color = D3DXCOLOR( 0, 1.0f, 0, 1.0f );
break;
case 2:
color = D3DXCOLOR( 0, 1.0f, 1.0f, 1.0f );
break;
case 3:
color = D3DXCOLOR( 1.0f, 1.0f, 0, 1.0f );
break;
}
int pos_l = frame_counter_mod * 40;
int pos_t = 0;
int pos_r = pos_l + 20;
int pos_b = pos_t + 20;
VERTEX OurVertices[] =
{
{ pos_l / (ClientSize.cx / 2.0f), -pos_t / (ClientSize.cy / 2.0f), 0.0f, color},
{ pos_r / (ClientSize.cx / 2.0f), -pos_t / (ClientSize.cy / 2.0f), 0.0f, color },
{ pos_l / (ClientSize.cx / 2.0f), -pos_b / (ClientSize.cy / 2.0f), 0.0f, color },
};
D3D11_MAPPED_SUBRESOURCE ms;
devcon->Map(pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms);
memcpy(ms.pData, OurVertices, sizeof(OurVertices));
devcon->Unmap(pVBuffer, NULL);
UINT stride = sizeof(VERTEX);
UINT offset = 0;
devcon->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);
devcon->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
devcon->Draw(3, 0);
swapchain->Present(1, 0);
//DwmFlush();
//Sleep(100);
LARGE_INTEGER sw_old = sw;
QueryPerformanceCounter(&sw);
float elapsed_ms = (sw.QuadPart - sw_old.QuadPart) * 1000.0f / perf_freq.QuadPart;
printf("%f\r\n", elapsed_ms);
frame_counter += 1;
}
return 0;
}
static void ReadFile(void** data, int* length, LPCWSTR filename)
{
FILE* file;
_wfopen_s(&file, filename, L"rb");
fseek(file, 0, SEEK_END);
*length = ftell(file);
fseek(file, 0, SEEK_SET);
*data = malloc(*length);
fread(*data, 1, *length, file);
fclose(file);
}
static ATOM RegisterWindowClass()
{
WNDCLASSEXW cls = {};
cls.cbSize = sizeof(cls);
cls.style = CS_HREDRAW | CS_VREDRAW;
cls.lpfnWndProc = WndProc;
cls.hCursor = LoadCursorW(nullptr, IDC_ARROW);
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
cls.lpszClassName = L"FlickerD3D11";
return RegisterClassExW(&cls);
}
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
shaders.hlsl
struct VOut
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
VOut output;
output.position = position;
output.color = color;
return output;
}
float4 PShader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return color;
}
Подробнее здесь: [url]https://stackoverflow.com/questions/45676892/reliable-windowed-vsync-with-opengl-on-windows[/url]
-
- Похожие темы
- Ответы
- Просмотры
- Последнее сообщение
-
-
Надежная оконная вертикальная синхронизация с OpenGL в Windows?
Anonymous » » в форуме C++Обновление от 26 июня 2024 г.: вопрос переписан, чтобы включить обновленную информацию и консолидировать результаты исследования.
РЕЗЮМЕ
Похоже, что vsync с OpenGL не работает в Windows в оконном режиме. Даже в очень простых сценариях с поддержкой... - 0 Ответы
- 19 Просмотры
-
Последнее сообщение Anonymous
-
-
-
Надежная оконная вертикальная синхронизация с OpenGL в Windows?
Anonymous » » в форуме C++Обновление от 26 июня 2024 г.: вопрос переписан, чтобы включить обновленную информацию и консолидировать результаты исследования.
РЕЗЮМЕ
Похоже, что vsync с OpenGL не работает в Windows в оконном режиме. Даже в очень простых сценариях с поддержкой... - 0 Ответы
- 16 Просмотры
-
Последнее сообщение Anonymous
-
-
-
Поляры: n_unique(), но как оконная функция
Anonymous » » в форуме PythonМне нужен способ узнать, сколько уникальных пар значений из двух столбцов находится в определенном контексте.
По сути, это похоже на n_unique, но как оконная функция.
Для иллюстрации на игрушечном примере:
import polars as pl
dataframe =... - 0 Ответы
- 12 Просмотры
-
Последнее сообщение Anonymous
-
-
-
Оконная функция Python Polars с литеральным типом
Anonymous » » в форуме PythonПредположим, у меня есть DataFrame с таким столбцом идентификатора:
df = pl.from_repr(
┌─────┐
│ id │
│ --- │
│ i64 │
╞═════╡
│ 1 │
│ 1 │
│ 1 │
│ 2 │
│ 2 │
│ 3 │
│ 3 │
└─────┘
)
Я хочу агрегировать текущий счетчик по столбцу id, чтобы получить... - 0 Ответы
- 14 Просмотры
-
Последнее сообщение Anonymous
-
-
-
Пользовательская функция Snowflake Python, действующая как оконная функция
Anonymous » » в форуме PythonЯ хотел бы использовать векторизованный (pandas) udf в Snowflake Snowpark в качестве оконной функции.
from snowflake.snowpark import Window
data.withColumn( result , my_udf('column1', 'column2').over(Window.partitionBy('column3')))
где my_udf —... - 0 Ответы
- 13 Просмотры
-
Последнее сообщение Anonymous
-
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...