Related
The following opengl code is specifically made to be very GPU-heavy and this forces the CPU to wait for a bit while the GPU finishes its work. In particular, it does so at the glFinish() call where the CPU waits for a measured 99.87% of the time per frame. The program runs at 10 fps on my system (windows 10, gtx1070) and Vsync disabled. This is all expected, if it wasn't for the fact that while the CPU is supposed to wait, it inexplicably uses 100% CPU time, causing overheating.
After testing on 6 systems with intel gpus, 4 with amd gpus and 5 with nvidia gpus, only those with nvidia gpus have the problem. All i can conclude so far is that this problem is nvidia-specific and opengl-specific. Directx apps don't show the problem, in fact, it is possible to reproduce this problem on Firefox by running a gpu-maxing webgl page with ANGLE disabled (doesn't happen with angle enabled).
I compile with the following:
C:\mingw64\bin\x86_64-w64-mingw32-gcc.exe %~dp0\main.c -o %~dp0\main.exe -static-libgcc -std=c11 -ggdb -O2 -Wall -BC:\mingw64\bin\ -LC:\mingw64\lib\ -IC:\mingw64\include\ -lgdi32 -lopengl32
Minimal code (i suggest to tweak the fragment shader so that you hit around 10 fps as well, makes the problem more apparent):
#include <windows.h>
#include <GL/gl.h>
typedef signed long long int GLsizeiptr;
typedef char GLchar;
#define GL_ARRAY_BUFFER 0x8892
#define GL_DYNAMIC_DRAW 0x88E8
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_VERTEX_SHADER 0x8B31
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
int main() {
HDC hdc;
{
WNDCLASS wc;
memset(&wc, 0, sizeof(wc));
wc.style = CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = "gldemo";
if (!RegisterClass(&wc)) return 0;
HWND hwnd = CreateWindow("gldemo", "Demo", WS_POPUP, 0, 0, 1920/2, 1080/2, 0, 0, NULL, 0);
hdc = GetDC(hwnd);
const PIXELFORMATDESCRIPTOR pfd = {0,0, PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
SetPixelFormat(hdc, ChoosePixelFormat(hdc, &pfd), &pfd);
wglMakeCurrent(hdc, wglCreateContext(hdc));
ShowWindow(hwnd, SW_SHOW);
}
void (*glGenBuffers)(GLsizei, GLuint *) = (void*)wglGetProcAddress("glGenBuffers");
void (*glBindBuffer)(GLenum, GLuint) = (void*)wglGetProcAddress("glBindBuffer");
void (*glBufferData)(GLenum, GLsizeiptr, void *, GLenum) = (void*)wglGetProcAddress("glBufferData");
GLuint (*glCreateShader)(GLuint) = (void*)wglGetProcAddress("glCreateShader");
void (*glAttachShader)(GLuint, GLuint) = (void*)wglGetProcAddress("glAttachShader");
void (*glCompileShader)(GLuint) = (void*)wglGetProcAddress("glCompileShader");
void (*glShaderSource)(GLuint, GLuint, const char **, const GLint *) = (void*)wglGetProcAddress("glShaderSource");
void (*glEnableVertexAttribArray)(GLuint) = (void*)wglGetProcAddress("glEnableVertexAttribArray");
GLuint (*glGetAttribLocation)(GLuint, GLchar *) = (void*)wglGetProcAddress("glGetAttribLocation");
void (*glVertexAttribPointer)(GLuint, GLint, GLenum, GLboolean, GLsizei, void *) = (void*)wglGetProcAddress("glVertexAttribPointer");
GLuint (*glCreateProgram)() = (void*)wglGetProcAddress("glCreateProgram");
void (*glLinkProgram)(GLuint) = (void*)wglGetProcAddress("glLinkProgram");
void (*glUseProgram)(GLuint) = (void*)wglGetProcAddress("glUseProgram");
const char *g_vertCode =
"#version 420\n"
"in vec3 vertexPosition;"
"void main() {gl_Position = vec4(vertexPosition.xyz, 1.);}";
const char *g_fragCode =
"#version 420\n"
"void main() {"
"float res = 0.5;"
"for (int t=0;t<58000;t++) {" // tweak this to make sure you're outputting ~10 fps. 58000 is ok for a gtx 1070.
"res = fract(sin(dot(gl_FragCoord.xy+res, vec2(12.9898,78.233))) * 43758.5453);"
"}"
"gl_FragColor = vec4(vec3(res)*0.4, 1.0);"
"}";
GLuint prog = glCreateProgram();
GLuint vertshader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertshader, 1, &g_vertCode, 0);
glCompileShader(vertshader);
glAttachShader(prog, vertshader);
GLuint fragshader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragshader, 1, &g_fragCode, 0);
glCompileShader(fragshader);
glAttachShader(prog, fragshader);
glLinkProgram(prog);
glUseProgram(prog);
GLuint attribVertexPosition = glGetAttribLocation(prog, "vertexPosition");
float verts[4*3] = {1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, -1.0f, 0.0f};
GLuint vboId;
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, 4*3*4, verts, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(attribVertexPosition);
glVertexAttribPointer(attribVertexPosition, 3, GL_FLOAT, 0, 12, (void*)0);
for (;;) {
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//__asm__("int $3");
glFinish(); // Halts here with 100% cpu on my system.
SwapBuffers(hdc);
MSG msg; char done = 0;
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) done = 1;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (done) break;
}
return 0;
}
This isn't an answer, so i won't mark it as one, but i can provide a workaround for now.
The idea is that the glFlush function guarantees that the gpu has started its processing, so we can, on paper, insert a wait after glFlush and before glFinish to cut down on the amount of time glFinish spends frying the cpu.
This is only viable if the resolution of the waiting function is at worst 1ms. If you use Sleep, or any other function in the Windows API that has a tweakable timeout, this can be set by calling timeBeginPeriod(1) at the start of the program.
To make sure the wait doesn't overshoot, which can cause the gpu to become idle, the formula used for waiting in the next frame is the time just spent by the wait plus the residual time spent on glFinish minus 0.5 milliseconds.
The result of all this is that the gpu stays well-fed at 100% usage according to the Task Manager yet the cpu utilization only starts to increase significantly if the time that vanilla glFinish would have spent gets lower than 4 ms and the mechanism disengages itself entirely if it gets lower than roughly 1.5 ms.
In an actual implementation one might consider inserting the SwapBuffers call inside the timed area to include in the calculation whatever frame-limiting systems might be in place and perhaps using Waitable Timers instead of Sleep.
Compiling with:
C:\mingw64\bin\x86_64-w64-mingw32-gcc.exe %~dp0\main.c -o %~dp0\main.exe -static-libgcc -std=c11 -ggdb -O2 -Wall -BC:\mingw64\bin\ -LC:\mingw64\lib\ -IC:\mingw64\include\ -lgdi32 -lopengl32 -lwinmm
Updated code:
#include <windows.h>
#include <stdio.h>
#include <GL/gl.h>
typedef signed long long int GLsizeiptr;
typedef char GLchar;
#define GL_ARRAY_BUFFER 0x8892
#define GL_DYNAMIC_DRAW 0x88E8
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_VERTEX_SHADER 0x8B31
LARGE_INTEGER Frequency;
unsigned long long int elapsedMicroseconds(LARGE_INTEGER StartingTime) {
LARGE_INTEGER EndingTime;
QueryPerformanceCounter(&EndingTime);
return (1000000*(EndingTime.QuadPart - StartingTime.QuadPart))/Frequency.QuadPart;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
int main() {
QueryPerformanceFrequency(&Frequency);
timeBeginPeriod(1);
HDC hdc;
{
WNDCLASS wc;
memset(&wc, 0, sizeof(wc));
wc.style = CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = "gldemo";
if (!RegisterClass(&wc)) return 0;
HWND hwnd = CreateWindow("gldemo", "Demo", WS_POPUP, 0, 0, 1920/2, 1080/2, 0, 0, NULL, 0);
hdc = GetDC(hwnd);
const PIXELFORMATDESCRIPTOR pfd = {0,0, PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
SetPixelFormat(hdc, ChoosePixelFormat(hdc, &pfd), &pfd);
wglMakeCurrent(hdc, wglCreateContext(hdc));
ShowWindow(hwnd, SW_SHOW);
}
void (*glGenBuffers)(GLsizei, GLuint *) = (void*)wglGetProcAddress("glGenBuffers");
void (*glBindBuffer)(GLenum, GLuint) = (void*)wglGetProcAddress("glBindBuffer");
void (*glBufferData)(GLenum, GLsizeiptr, void *, GLenum) = (void*)wglGetProcAddress("glBufferData");
GLuint (*glCreateShader)(GLuint) = (void*)wglGetProcAddress("glCreateShader");
void (*glAttachShader)(GLuint, GLuint) = (void*)wglGetProcAddress("glAttachShader");
void (*glCompileShader)(GLuint) = (void*)wglGetProcAddress("glCompileShader");
void (*glShaderSource)(GLuint, GLuint, const char **, const GLint *) = (void*)wglGetProcAddress("glShaderSource");
void (*glEnableVertexAttribArray)(GLuint) = (void*)wglGetProcAddress("glEnableVertexAttribArray");
GLuint (*glGetAttribLocation)(GLuint, GLchar *) = (void*)wglGetProcAddress("glGetAttribLocation");
void (*glVertexAttribPointer)(GLuint, GLint, GLenum, GLboolean, GLsizei, void *) = (void*)wglGetProcAddress("glVertexAttribPointer");
GLuint (*glCreateProgram)() = (void*)wglGetProcAddress("glCreateProgram");
void (*glLinkProgram)(GLuint) = (void*)wglGetProcAddress("glLinkProgram");
void (*glUseProgram)(GLuint) = (void*)wglGetProcAddress("glUseProgram");
const char *g_vertCode =
"#version 420\n"
"in vec3 vertexPosition;"
"void main() {gl_Position = vec4(vertexPosition.xyz, 1.);}";
const char *g_fragCode =
"#version 420\n"
"void main() {"
"float res = 0.5;"
"for (int t=0;t<58000;t++) {" // tweak this to make sure you're outputting ~10 fps. 58000 is ok for a gtx 1070.
"res = fract(sin(dot(gl_FragCoord.xy+res, vec2(12.9898,78.233))) * 43758.5453);"
"}"
"gl_FragColor = vec4(vec3(res)*0.4, 1.0);"
"}";
GLuint prog = glCreateProgram();
GLuint vertshader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertshader, 1, &g_vertCode, 0);
glCompileShader(vertshader);
glAttachShader(prog, vertshader);
GLuint fragshader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragshader, 1, &g_fragCode, 0);
glCompileShader(fragshader);
glAttachShader(prog, fragshader);
glLinkProgram(prog);
glUseProgram(prog);
GLuint attribVertexPosition = glGetAttribLocation(prog, "vertexPosition");
float verts[4*3] = {1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, -1.0f, 0.0f};
GLuint vboId;
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, 4*3*4, verts, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(attribVertexPosition);
glVertexAttribPointer(attribVertexPosition, 3, GL_FLOAT, 0, 12, (void*)0);
LARGE_INTEGER syncer;
long long int waitfor = 0;
for (;;) {
//__asm__("int $3");
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFlush();
QueryPerformanceCounter(&syncer);
if (waitfor>0) Sleep(waitfor/1000);
glFinish();
waitfor = elapsedMicroseconds(syncer)-500;
SwapBuffers(hdc);
MSG msg; char done = FALSE;
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) done = TRUE;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (done) break;
}
return 0;
}
I am new to windows programming. I want to display the raw pixel array to the screen without using SetPixel function because it's too slow in my standards. I am using this question as my reference.
I made a small program below to fill the pixel array with random RGB values and display it to the screen. The result wasn't what I anticipated, I got the white canvas. I tried to change this line ptr++ = (b << 16) | (g << 8) | r; to ptr++ = 0x000000FF; expecting red canvas but I got the same result.
#include <stdlib.h>
#include <time.h>
#include <windows.h>
const int win_width = 500;
const int win_height = 450;
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
RECT rect;
int width, height;
COLORREF *display;
switch (msg)
{
case WM_CREATE:
srand((unsigned int) time(NULL));
GetClientRect(hWnd, &rect);
width = rect.right - rect.left;
height = rect.bottom - rect.top;
display = (COLORREF *) malloc(sizeof(COLORREF) * width * height);
COLORREF *ptr = display;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
int r = rand() % 256;
int g = rand() % 256;
int b = rand() % 256;
*ptr++ = (b << 16) | (g << 8) | r;
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC, memDC;
HBITMAP hBmp, hOldBmp;
hDC = BeginPaint(hWnd, &ps);
memDC = CreateCompatibleDC(hDC);
hBmp = CreateBitmap(width, height, 1, 32, (void *) display);
hOldBmp = (HBITMAP) SelectObject(memDC, hBmp);
BitBlt(hDC, rect.left, rect.top, width, height, memDC, 0, 0, SRCCOPY);
SelectObject(memDC, hOldBmp);
DeleteObject(hBmp);
DeleteDC(memDC);
EndPaint(hWnd, &ps);
break;
}
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
const TCHAR szClassName[] = TEXT("MyClass");
WNDCLASS wc;
HWND hWnd;
MSG msg;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szClassName;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClass(&wc))
{
MessageBox(NULL, TEXT("Window Registration Failed!"), TEXT("Error!"),
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hWnd = CreateWindow(szClassName,
TEXT("Random Pixels"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, win_width, win_height,
NULL, NULL, hInstance, NULL);
if (hWnd == NULL)
{
MessageBox(NULL, TEXT("Window Creation Failed!"), TEXT("Error!"),
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
I know there's something wrong with my code inside WM_PAINT but I don't know how to fix it. I will appreciate any form of assistance. Thanks in advance.
The variable display that holds the pixel data has automatic lifetime. Its lifetime ends whenever control leaves WndProc. A consequence is, that every invocation of WndProc starts out with a new (indeterminate) value for display.
To solve this, display needs to have static storage duration. The easiest way to accomplish this is to replace
COLORREF *display;
with
static COLORREF *display;
This has 2 consequences:
The value stored in display survives separate invocations of WndProc.
The value stored in display is now properly zero-initialized.
Working on a simple c GUI library, I'm starting with the winapi backend and having some problems right now calculating the preferred sizes of controls. I'm comparing my results with those of Windows.Forms.
Right now, I'm using values from Design Specifications and Guidelines - Visual Design Layout (like Buttons and TextBoxes being 14 "Dialog Logical Units" high) for calculating the pixel sizes in the winapi implementation, while keeping everything default with Windows Forms. I created these simple demo implementations:
Windows Forms (demo.cs):
using System.Drawing;
using System.Windows.Forms;
namespace W32CtlTest
{
public class Demo : Form
{
private FlowLayoutPanel panel;
private Button button;
private TextBox textBox;
public Demo() : base()
{
Text = "winforms";
panel = new FlowLayoutPanel();
button = new Button();
button.Text = "test";
button.Click += (sender, args) =>
{
Close();
};
panel.Controls.Add(button);
textBox = new TextBox();
panel.Controls.Add(textBox);
Controls.Add(panel);
}
protected override Size DefaultSize
{
get
{
return new Size(240,100);
}
}
public static void Main(string[] argv)
{
if (argv.Length < 1 || argv[0] != "-s")
{
Application.EnableVisualStyles();
}
Application.Run(new Demo());
}
}
}
compile with C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /out:demo.exe /lib:C:\Windows\Microsoft.NET\Framework\v4.0.30319 /reference:System.Windows.Forms.dll,System.Drawing.dll demo.cs
Win32 API (demo.c):
#include <string.h>
#include <windows.h>
#include <commctrl.h>
static HINSTANCE instance;
static HWND mainWindow;
static HWND button;
static HWND textBox;
#define WC_mainWindow L"W32CtlTestDemo"
#define CID_button 0x101
static NONCLIENTMETRICSW ncm;
static HFONT messageFont;
static TEXTMETRICW messageFontMetrics;
static int buttonWidth;
static int buttonHeight;
static int textBoxWidth;
static int textBoxHeight;
/* hack to enable visual styles without relying on manifest
* found at http://stackoverflow.com/a/10444161
* modified for unicode-only code */
static int enableVisualStyles(void)
{
wchar_t dir[MAX_PATH];
ULONG_PTR ulpActivationCookie = 0;
ACTCTXW actCtx =
{
sizeof(actCtx),
ACTCTX_FLAG_RESOURCE_NAME_VALID
| ACTCTX_FLAG_SET_PROCESS_DEFAULT
| ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID,
L"shell32.dll", 0, 0, dir, (LPWSTR)124,
0, 0
};
UINT cch = GetSystemDirectoryW(dir, sizeof(dir) / sizeof(*dir));
if (cch >= sizeof(dir) / sizeof(*dir)) { return 0; }
dir[cch] = L'\0';
ActivateActCtx(CreateActCtxW(&actCtx), &ulpActivationCookie);
return (int) ulpActivationCookie;
}
static void init(void)
{
INITCOMMONCONTROLSEX icx;
icx.dwSize = sizeof(INITCOMMONCONTROLSEX);
icx.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icx);
ncm.cbSize = sizeof(ncm);
SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
messageFont = CreateFontIndirectW(&ncm.lfStatusFont);
HDC dc = GetDC(0);
SelectObject(dc, (HGDIOBJ) messageFont);
GetTextMetricsW(dc, &messageFontMetrics);
SIZE sampleSize;
GetTextExtentExPointW(dc,
L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
52, 0, 0, 0, &sampleSize);
ReleaseDC(0, dc);
buttonWidth = MulDiv(sampleSize.cx, 50, 4 * 52);
buttonHeight = MulDiv(messageFontMetrics.tmHeight, 14, 8);
textBoxWidth = 100;
textBoxHeight = MulDiv(messageFontMetrics.tmHeight, 14, 8);
instance = GetModuleHandleW(0);
}
static LRESULT CALLBACK wproc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_CREATE:
button = CreateWindowExW(0, L"Button", L"test",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
2, 2, buttonWidth, buttonHeight,
w, (HMENU)CID_button, instance, 0);
SendMessageW(button, WM_SETFONT, (WPARAM)messageFont, 0);
textBox = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"",
WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
6 + buttonWidth, 2, textBoxWidth, textBoxHeight,
w, 0, instance, 0);
SendMessageW(textBox, WM_SETFONT, (WPARAM)messageFont, 0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
switch (LOWORD(wp))
{
case CID_button:
DestroyWindow(w);
break;
}
break;
}
return DefWindowProcW(w, msg, wp, lp);
}
int main(int argc, char **argv)
{
if (argc < 2 || strcmp(argv[1], "-s"))
{
enableVisualStyles();
}
init();
WNDCLASSEXW wc;
memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hInstance = instance;
wc.lpszClassName = WC_mainWindow;
wc.lpfnWndProc = wproc;
wc.hbrBackground = (HBRUSH) COLOR_WINDOW;
wc.hCursor = LoadCursorA(0, IDC_ARROW);
RegisterClassExW(&wc);
mainWindow = CreateWindowExW(0, WC_mainWindow, L"winapi",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 100,
0, 0, instance, 0);
ShowWindow(mainWindow, SW_SHOWNORMAL);
MSG msg;
while (GetMessageW(&msg, 0, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return (int)msg.wParam;
}
compile with gcc -odemo.exe -O2 demo.c -lgdi32 -lcomctl32
The test code is also available on github
It looks like this on windows 10, with visual styles enabled in the upper row and disabled in the lower row:
One thing I soon found out is that Windows.Forms doesn't use the message font (as I had expected) but instead uses the DEFAULT_GUI_FONT Although that's not the right thing to do, I changed my win32 code accordingly so I can compare the results better:
For completeness, here is what it looks like on windows 7 without visual styles:
Now my questions are:
Is it correct to use the message font? So, Windows.Forms definitely got this one "wrong"?
Obviously Windows.Forms uses the 14 DLU height for Buttons, but some smaller height for TextBoxes. This contradicts the Design Specifications. So is Windows.Forms wrong here as well? Or should TextBoxes in fact be smaller, so the text doesn't look like it's "hanging from the ceiling"? I think this does look better the way Windows.Forms does it.
Comparing visual styles enabled/disabled, I find that without visual styles, I get the same height for my button and my text box, but with visual styles enabled on windows 10, the text box is actually higher. Is there something like "theme specific metrics" and if so, how can I use that to correct my calculations?
This is only a partial answer I'm adding here for reference:
Indeed, using the DEFAULT_GUI_FONT is wrong according to this blog entry by Raymond Chen. So, no need to trust winforms to do "the right thing".
The Design Specifications indicate that Edit Controls should be the same height as Buttons (14 DLU). To convert these to pixel sizes, the Dialog Base Units (DBU) are needed, and while GetDialogBaseUnits() only returns them for the system font, there are MSDN articles describing how to calculate them for other fonts.
1 vertical DBU corresponds to 8 DLU, so an Edit control will be 6 DLU higher than the text it contains. This doesn't look so nice, because the Edit control doesn't center the text vertically but instead aligns it at the top. winforms avoids this by calculating a smaller size for an Edit control. The drawback is that an Edit control will not align nicely next to a Button.
I found a kind of "hacky" solution to that problem by shrinking the client area of the Edit control in an overridden window proc. The following code compares the results (and contains controls using the system font for completeness):
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <commctrl.h>
typedef struct PaddedControl
{
WNDPROC baseWndProc;
int vshrink;
} PaddedControl;
static HINSTANCE instance;
static HWND mainWindow;
static HWND buttonSF;
static HWND textBoxSF;
static HWND buttonMF;
static HWND textBoxMF;
static HWND buttonMFC;
static HWND textBoxMFC;
static PaddedControl textBoxMFCPadded;
#define WC_mainWindow L"W32CtlTestDemo"
static NONCLIENTMETRICSW ncm;
static HFONT messageFont;
static TEXTMETRICW messageFontMetrics;
static int controlHeightSF;
static int controlHeightMF;
static int buttonWidthSF;
static int buttonWidthMF;
/* hack to enable visual styles without relying on manifest
* found at http://stackoverflow.com/a/10444161
* modified for unicode-only code */
static int enableVisualStyles(void)
{
wchar_t dir[MAX_PATH];
ULONG_PTR ulpActivationCookie = 0;
ACTCTXW actCtx =
{
sizeof(actCtx),
ACTCTX_FLAG_RESOURCE_NAME_VALID
| ACTCTX_FLAG_SET_PROCESS_DEFAULT
| ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID,
L"shell32.dll", 0, 0, dir, (LPWSTR)124,
0, 0
};
UINT cch = GetSystemDirectoryW(dir, sizeof(dir) / sizeof(*dir));
if (cch >= sizeof(dir) / sizeof(*dir)) { return 0; }
dir[cch] = L'\0';
ActivateActCtx(CreateActCtxW(&actCtx), &ulpActivationCookie);
return (int) ulpActivationCookie;
}
static void init(void)
{
INITCOMMONCONTROLSEX icx;
icx.dwSize = sizeof(INITCOMMONCONTROLSEX);
icx.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icx);
ncm.cbSize = sizeof(ncm);
SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
messageFont = CreateFontIndirectW(&ncm.lfStatusFont);
LONG sysDbu = GetDialogBaseUnits();
HDC dc = GetDC(0);
SelectObject(dc, (HGDIOBJ) messageFont);
GetTextMetricsW(dc, &messageFontMetrics);
SIZE sampleSize;
GetTextExtentExPointW(dc,
L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
52, 0, 0, 0, &sampleSize);
ReleaseDC(0, dc);
controlHeightSF = MulDiv(HIWORD(sysDbu), 14, 8);
controlHeightMF = MulDiv(messageFontMetrics.tmHeight, 14, 8);
buttonWidthSF = MulDiv(LOWORD(sysDbu), 50, 4);
buttonWidthMF = MulDiv(sampleSize.cx, 50, 4 * 52);
instance = GetModuleHandleW(0);
}
static LRESULT CALLBACK paddedControlProc(
HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
PaddedControl *self = (PaddedControl *)GetPropW(w, L"paddedControl");
WNDCLASSEXW wc;
switch (msg)
{
case WM_ERASEBKGND:
wc.cbSize = sizeof(wc);
GetClassInfoExW(0, L"Edit", &wc);
RECT cr;
GetClientRect(w, &cr);
cr.top -= self->vshrink;
cr.bottom += self->vshrink;
HDC dc = GetDC(w);
FillRect(dc, &cr, wc.hbrBackground);
ReleaseDC(w, dc);
return 1;
case WM_NCCALCSIZE:
if (!wp) break;
LRESULT result = CallWindowProcW(self->baseWndProc, w, msg, wp, lp);
NCCALCSIZE_PARAMS *p = (NCCALCSIZE_PARAMS *)lp;
int height = p->rgrc[0].bottom - p->rgrc[0].top;
self->vshrink = 0;
if (height > messageFontMetrics.tmHeight + 3)
{
self->vshrink = (height - messageFontMetrics.tmHeight - 3) / 2;
p->rgrc[0].top += self->vshrink;
p->rgrc[0].bottom -= self->vshrink;
}
return result;
}
return CallWindowProcW(self->baseWndProc, w, msg, wp, lp);
}
static LRESULT CALLBACK wproc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_CREATE:
buttonSF = CreateWindowExW(0, L"Button", L"sysfont",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
4, 4, buttonWidthSF, controlHeightSF,
w, 0, instance, 0);
buttonMF = CreateWindowExW(0, L"Button", L"msgfont",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
4, 8 + controlHeightSF, buttonWidthMF, controlHeightMF,
w, 0, instance, 0);
SendMessageW(buttonMF, WM_SETFONT, (WPARAM)messageFont, 0);
buttonMFC = CreateWindowExW(0, L"Button", L"msgfont adj",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
4, 12 + controlHeightSF + controlHeightMF,
buttonWidthMF, controlHeightMF,
w, 0, instance, 0);
SendMessageW(buttonMFC, WM_SETFONT, (WPARAM)messageFont, 0);
textBoxSF = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"abcdefgh",
WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
8 + buttonWidthSF, 4, 100, controlHeightSF,
w, 0, instance, 0);
textBoxMF = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"abcdefgh",
WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
8 + buttonWidthMF, 8 + controlHeightSF,
100, controlHeightMF,
w, 0, instance, 0);
SendMessageW(textBoxMF, WM_SETFONT, (WPARAM)messageFont, 0);
textBoxMFC = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"abcdefgh",
WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
8 + buttonWidthMF, 12 + controlHeightSF + controlHeightMF,
100, controlHeightMF,
w, 0, instance, 0);
memset(&textBoxMFCPadded, 0, sizeof(PaddedControl));
textBoxMFCPadded.baseWndProc = (WNDPROC)SetWindowLongPtr(
textBoxMFC, GWLP_WNDPROC, (LONG_PTR)paddedControlProc);
SetPropW(textBoxMFC, L"paddedControl", &textBoxMFCPadded);
SetWindowPos(textBoxMFC, 0, 0, 0, 0, 0,
SWP_NOOWNERZORDER|SWP_NOSIZE|SWP_NOMOVE|SWP_FRAMECHANGED);
SendMessageW(textBoxMFC, WM_SETFONT, (WPARAM)messageFont, 0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(w, msg, wp, lp);
}
int main(int argc, char **argv)
{
if (argc < 2 || strcmp(argv[1], "-s"))
{
enableVisualStyles();
}
init();
WNDCLASSEXW wc;
memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hInstance = instance;
wc.lpszClassName = WC_mainWindow;
wc.lpfnWndProc = wproc;
wc.hbrBackground = (HBRUSH) COLOR_WINDOW;
wc.hCursor = LoadCursorA(0, IDC_ARROW);
RegisterClassExW(&wc);
mainWindow = CreateWindowExW(0, WC_mainWindow, L"fontdemo",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 180,
0, 0, instance, 0);
ShowWindow(mainWindow, SW_SHOWNORMAL);
MSG msg;
while (GetMessageW(&msg, 0, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return (int)msg.wParam;
}
The last row of controls using this hack is the best I could achieve so far:
As you can see, a problem that still persists is that the heights of Button and Edit controls look different with the visual styles theme of Windows 10. So I'd still be happy to see a better answer to this question.
I'm trying to implement an equivalent to Cocoa's NSPopover/GTK+'s GtkPopover but on Windows using the raw Windows API. So far, I have most things working right, but I can't get the client rectangle to work... in any sensible fashion.
If I comment out my WM_PAINT handler and my WM_NCCALCSIZE below, here's what happens:
Now, if I enable WM_NCCALCSIZE, a good chunk of that red border goes away:
And if I enable my WM_PAINT, this happens. Notice that the part in the above that's black but supposed to be red stays black, but the part that's red is now COLOR_ACTIVECAPTION.
Images are from wine.
Windows XP is even worse: the GetDCEx() call (which is straight out of MSDN) fails with no last error code set, and changing it to use the seemingly equivalent GetWindowDC() results in a transparent correct red border, a transparent correct (!) red border, and the whole window having the titlebar color, respectively. wine output doesn't change.
Am I misunderstanding something about WM_NCCALCSIZE? I know all inputs and outputs are in the same coordinate space, so I assumed I could just mainpulate those coordinates and call it a day. I did try handling the valid rectangles fields (rgrc[1]/rgrc[2]), both by setting them to empty rectangles and by making rgrc[1] set to the new client rect, but neither fixed the issue here.
The MoveWindow() also doesn't seem to be it; if I remove that and keep the popover at its initial size, it still draws as above, with the same corrupt red border problem.
Thanks.
Here is the code. Because it is a test to figure out what to do, there's no error checking yet. The final code will have full error checking.
// 9 october 2014
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
// get Windows version right; right now Windows XP
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
#include <windows.h>
#include <commctrl.h>
#include <stdint.h>
#include <uxtheme.h>
#include <string.h>
#include <wchar.h>
#include <windowsx.h>
#include <vsstyle.h>
#include <vssym32.h>
// #qo LIBS: user32 kernel32 gdi32
// TODO
// - investigate visual styles
// - put the client and non-client areas in the right place
// - make sure redrawing is correct (especially for backgrounds)
// - wine: BLACK_PEN draws a white line? (might change later so eh)
// - should the parent window appear deactivated?
HWND popover;
#define ARROWHEIGHT 8
#define ARROWWIDTH 8 /* should be the same for smooth lines */
LRESULT CALLBACK popoverproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC dc;
HRGN region;
POINT pt;
RECT r;
LONG width;
LONG height;
switch (uMsg) {
case WM_NCPAINT:
GetWindowRect(hwnd, &r);
width = r.right - r.left;
height = r.bottom - r.top;
dc = GetDCEx(hwnd, (HRGN) wParam, DCX_WINDOW | DCX_INTERSECTRGN);
if (dc == NULL) abort();
BeginPath(dc);
r.left = 0; r.top = 0; // everything's in device coordinates
pt.x = r.left;
pt.y = r.top + ARROWHEIGHT;
if (MoveToEx(dc, pt.x, pt.y, NULL) == 0) abort();
pt.y += height - ARROWHEIGHT;
if (LineTo(dc, pt.x, pt.y) == 0) abort();
pt.x += width;
LineTo(dc, pt.x, pt.y);
pt.y -= height - ARROWHEIGHT;
LineTo(dc, pt.x, pt.y);
pt.x -= (width / 2) - ARROWWIDTH;
LineTo(dc, pt.x, pt.y);
pt.x -= ARROWWIDTH;
pt.y -= ARROWHEIGHT;
LineTo(dc, pt.x, pt.y);
pt.x -= ARROWWIDTH;
pt.y += ARROWHEIGHT;
LineTo(dc, pt.x, pt.y);
pt.x = 0;
LineTo(dc, pt.x, pt.y);
EndPath(dc);
SetDCBrushColor(dc, RGB(255, 0, 0));
region = PathToRegion(dc);
FrameRgn(dc, region, GetStockObject(DC_BRUSH), 1, 1);
SetWindowRgn(hwnd, region, TRUE);
ReleaseDC(hwnd, dc);
return 0;
case WM_NCCALCSIZE:
{
RECT *r = (RECT *) lParam;
NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *) lParam;
if (wParam != FALSE)
r = &np->rgrc[0];
printf("%d | %d %d %d %d\n", wParam, r->left, r->top, r->right, r->bottom);
r->left++;
r->top++;
r->right--;
r->bottom--;
r->top += ARROWHEIGHT;
return 0;
}
case WM_ERASEBKGND:
return (LRESULT) GetStockObject(HOLLOW_BRUSH);
case WM_PAINT:
dc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &r);
FillRect(dc, &r, GetSysColorBrush(COLOR_ACTIVECAPTION));
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == 100) {
MoveWindow(popover, 50, 50, 200, 200, TRUE);
ShowWindow(popover, SW_SHOW);
UpdateWindow(popover);
return 0;
}
break;
case WM_CLOSE:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
int main(int argc, char *argv[])
{
WNDCLASSW wc;
HWND mainwin, button;
MSG msg;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"popover";
wc.lpfnWndProc = popoverproc;
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wc.style = CS_DROPSHADOW | CS_NOCLOSE;
if (RegisterClassW(&wc) == 0)
abort();
popover = CreateWindowExW(WS_EX_TOPMOST,
L"popover", L"",
WS_POPUP,
0, 0, 150, 100,
NULL, NULL, NULL, NULL);
if (popover == NULL)
abort();
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndproc;
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
if (RegisterClassW(&wc) == 0)
abort();
mainwin = CreateWindowExW(0,
L"mainwin", L"Main Window",
WS_OVERLAPPEDWINDOW,
0, 0, 150, 100,
NULL, NULL, NULL, NULL);
if (mainwin == NULL)
abort();
button = CreateWindowExW(0,
L"button", L"Click Me",
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
20, 20, 100, 40,
mainwin, (HMENU) 100, NULL, NULL);
if (button == NULL)
abort();
ShowWindow(mainwin, SW_SHOWDEFAULT);
if (UpdateWindow(mainwin) == 0)
abort();
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 0;
}
I'd like to add a menu system to my simple OpenGL program. I've used GLUT before but that was way back in 1998 and when I mentioned it here on SO you advised not to use GLUT and therefore I want to know what menu building libraries I can use, preferably platform-independent like GLUT, since I see GLUT is still used in many of the examples. My program doesn't use GLUT but I'd like to add menu system to learn more how to make I more complete program.
#include <windows.h>
#include <gl/gl.h>
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
void EnableOpenGL(HWND hwnd, HDC*, HGLRC*);
void DisableOpenGL(HWND, HDC, HGLRC);
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX wcex;
HWND hwnd;
HDC hDC;
HGLRC hRC;
MSG msg;
BOOL bQuit = FALSE;
float theta = 0.0f;
/* register window class */
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "GLSample";
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);;
if (!RegisterClassEx(&wcex))
return 0;
/* create main window */
hwnd = CreateWindowEx(0,
"GLSample",
"OpenGL Sample",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
256,
256,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hwnd, nCmdShow);
/* enable OpenGL for the window */
EnableOpenGL(hwnd, &hDC, &hRC);
/* program main loop */
while (!bQuit)
{
/* check for messages */
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
/* handle or dispatch messages */
if (msg.message == WM_QUIT)
{
bQuit = TRUE;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
/* OpenGL animation code goes here */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glRotatef(theta, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 1.0f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(0.87f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(-0.87f, -0.5f);
glEnd();
glPopMatrix();
SwapBuffers(hDC);
theta += 1.0f;
Sleep (1);
}
}
/* shutdown OpenGL */
DisableOpenGL(hwnd, hDC, hRC);
/* destroy the window explicitly */
DestroyWindow(hwnd);
return msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
PostQuitMessage(0);
break;
case WM_DESTROY:
return 0;
case WM_KEYDOWN:
{
switch (wParam)
{
case VK_ESCAPE:
PostQuitMessage(0);
break;
}
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
void EnableOpenGL(HWND hwnd, HDC* hDC, HGLRC* hRC)
{
PIXELFORMATDESCRIPTOR pfd;
int iFormat;
/* get the device context (DC) */
*hDC = GetDC(hwnd);
/* set the pixel format for the DC */
ZeroMemory(&pfd, sizeof(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 = 24;
pfd.cDepthBits = 16;
pfd.iLayerType = PFD_MAIN_PLANE;
iFormat = ChoosePixelFormat(*hDC, &pfd);
SetPixelFormat(*hDC, iFormat, &pfd);
/* create and enable the render context (RC) */
*hRC = wglCreateContext(*hDC);
wglMakeCurrent(*hDC, *hRC);
}
void DisableOpenGL (HWND hwnd, HDC hDC, HGLRC hRC)
{
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC);
ReleaseDC(hwnd, hDC);
}
For example in the answer here
OpenGL - GLUT - Displaying different pop-up menus
It says not to use GLUT but not what to use instead so it doesn't really say where to begin. Can you tell me what to use instead of GLUT?
For platform-independent OpenGL development, use a cross-platform GUI toolkit like Qt or wxWidgets.
Of these I only have personal experience with Qt's OpenGL module. It comes with many examples of how to set up an OpenGL rendering context and interact with it with the mouse and keyboard. It will allow you to pop up a menu when you right click in your scene. Qt also comes with utility classes for vector and matrix manipulation.