diff --git a/tools/wgl-smoke/README.md b/tools/wgl-smoke/README.md new file mode 100644 index 00000000..931dc740 --- /dev/null +++ b/tools/wgl-smoke/README.md @@ -0,0 +1,25 @@ +# WGL smoke tests + +Manual bring-up tests for the Windows host layer (drop-in `opengl32.dll`). +Both replicate the GLFW/LWJGL bootstrap Minecraft uses and verify a rendered +triangle via `glReadPixels`; exit code 0 means pass. + +- `wgl_smoke.cpp` - hand-rolled Win32/WGL sequence (dummy context on a hidden + helper window, ARB pixel format + 3.2 core forward-compatible context, + gdi32 pixel-format forwarding, swap loop, readback). +- `glfw_smoke.cpp` - the same flow driven by a real GLFW binary, which also + exercises GLFW's zero-area helper window (the case that needs the + DirectVulkan no-swapchain-at-init path). + +Build (x64 Native Tools prompt), with GLFW binaries for the second test: + + cl /nologo /EHsc /W3 wgl_smoke.cpp user32.lib gdi32.lib + cl /nologo /EHsc /W3 glfw_smoke.cpp /I%GLFW%\include %GLFW%\lib-vc2022\glfw3dll.lib user32.lib gdi32.lib + +Run with the MobileGL `opengl32.dll` copy (and `glfw3.dll`, plus ANGLE's +`libEGL.dll`/`libGLESv2.dll`/`d3dcompiler_47.dll` for DirectGLES) in the exe +directory: + + set MOBILEGL_BACKEND_TYPE=DirectVulkan (or DirectGLES) + set MOBILEGL_LOG_FILE_PATH=smoke.log + wgl_smoke.exe && glfw_smoke.exe diff --git a/tools/wgl-smoke/glfw_smoke.cpp b/tools/wgl-smoke/glfw_smoke.cpp new file mode 100644 index 00000000..f4e003b8 --- /dev/null +++ b/tools/wgl-smoke/glfw_smoke.cpp @@ -0,0 +1,189 @@ +// GLFW-driven smoke test: exercises the exact GLFW WGL bootstrap Minecraft uses +// (helper-window dummy context, ARB pixel format, 3.2 core forward-compatible +// no-error context) against the MobileGL opengl32.dll drop-in. +// +// Build: cl /nologo /EHsc /W3 glfw_smoke.cpp /I/include /lib-vc2022/glfw3dll.lib user32.lib gdi32.lib +// Run with opengl32.dll + glfw3.dll in the exe directory. + +#include +#include +#include + +typedef const unsigned char*(WINAPI* PFN_glGetString)(unsigned int); +typedef void(WINAPI* PFN_glClearColor)(float, float, float, float); +typedef void(WINAPI* PFN_glClear)(unsigned int); +typedef void(WINAPI* PFN_glReadPixels)(int, int, int, int, unsigned int, unsigned int, void*); +typedef void(WINAPI* PFN_glFinish)(void); +typedef unsigned int(WINAPI* PFN_glCreateShader)(unsigned int); +typedef void(WINAPI* PFN_glShaderSource)(unsigned int, int, const char* const*, const int*); +typedef void(WINAPI* PFN_glCompileShader)(unsigned int); +typedef unsigned int(WINAPI* PFN_glCreateProgram)(void); +typedef void(WINAPI* PFN_glAttachShader)(unsigned int, unsigned int); +typedef void(WINAPI* PFN_glLinkProgram)(unsigned int); +typedef void(WINAPI* PFN_glUseProgram)(unsigned int); +typedef void(WINAPI* PFN_glGenVertexArrays)(int, unsigned int*); +typedef void(WINAPI* PFN_glBindVertexArray)(unsigned int); +typedef void(WINAPI* PFN_glGenBuffers)(int, unsigned int*); +typedef void(WINAPI* PFN_glBindBuffer)(unsigned int, unsigned int); +typedef void(WINAPI* PFN_glBufferData)(unsigned int, long long, const void*, unsigned int); +typedef void(WINAPI* PFN_glVertexAttribPointer)(unsigned int, int, unsigned int, unsigned char, int, const void*); +typedef void(WINAPI* PFN_glEnableVertexAttribArray)(unsigned int); +typedef void(WINAPI* PFN_glDrawArrays)(unsigned int, int, int); +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_VERSION 0x1F02 +#define GL_RENDERER 0x1F01 +#define GL_RGBA 0x1908 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_TRIANGLES 0x0004 +#define GL_FLOAT 0x1406 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_STATIC_DRAW 0x88E4 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_FRAGMENT_SHADER 0x8B30 + +static void RawPrint(HANDLE f, const char* s) { + DWORD written = 0; + WriteFile(f, s, (DWORD)lstrlenA(s), &written, nullptr); +} + +static void RawHex(HANDLE f, unsigned long long v) { + char buf[20]; + for (int i = 0; i < 16; ++i) { + int nib = (int)((v >> ((15 - i) * 4)) & 0xF); + buf[i] = (char)(nib < 10 ? '0' + nib : 'a' + nib - 10); + } + buf[16] = 0; + RawPrint(f, buf); +} + +static LONG WINAPI CrashDump(EXCEPTION_POINTERS* ep) { + static volatile LONG entered = 0; + if (ep->ExceptionRecord->ExceptionCode != 0xC0000005) return EXCEPTION_CONTINUE_SEARCH; + if (InterlockedExchange(&entered, 1)) return EXCEPTION_CONTINUE_SEARCH; + HANDLE f = CreateFileA("glfw-crash-stack.txt", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (f == INVALID_HANDLE_VALUE) TerminateProcess(GetCurrentProcess(), 0x43); + RawPrint(f, "AV at "); + RawHex(f, (unsigned long long)ep->ExceptionRecord->ExceptionAddress); + RawPrint(f, " reading "); + RawHex(f, (unsigned long long)ep->ExceptionRecord->ExceptionInformation[1]); + RawPrint(f, "\n"); + const unsigned long long* sp = (const unsigned long long*)ep->ContextRecord->Rsp; + for (int i = 0; i < 512; ++i) { + MEMORY_BASIC_INFORMATION mbi; + if (!VirtualQuery(sp + i, &mbi, sizeof(mbi)) || mbi.State != MEM_COMMIT || + (mbi.Protect & (PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) == 0) + break; + unsigned long long v = sp[i]; + HMODULE mod = nullptr; + if (v > 0x10000 && + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCSTR)v, &mod) && + mod) { + char name[MAX_PATH]; + name[0] = 0; + GetModuleFileNameA(mod, name, MAX_PATH); + RawPrint(f, "stack "); + RawHex(f, v); + RawPrint(f, " "); + RawPrint(f, name); + RawPrint(f, "+"); + RawHex(f, v - (unsigned long long)mod); + RawPrint(f, "\n"); + } + } + CloseHandle(f); + TerminateProcess(GetCurrentProcess(), 0x42); + return EXCEPTION_EXECUTE_HANDLER; +} + +int main() { + AddVectoredExceptionHandler(1, CrashDump); + if (!glfwInit()) { + std::fprintf(stderr, "GLFW-SMOKE FAIL: glfwInit\n"); + return 1; + } + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + glfwWindowHint(GLFW_CONTEXT_NO_ERROR, GLFW_TRUE); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + + GLFWwindow* win = glfwCreateWindow(854, 480, "MobileGL GLFW smoke", nullptr, nullptr); + if (!win) { + const char* desc = nullptr; + int code = glfwGetError(&desc); + std::fprintf(stderr, "GLFW-SMOKE FAIL: glfwCreateWindow (0x%x %s)\n", code, desc ? desc : "?"); + return 1; + } + glfwMakeContextCurrent(win); + glfwShowWindow(win); + glfwSwapInterval(0); + + auto glGetString_ = (PFN_glGetString)glfwGetProcAddress("glGetString"); + auto glClearColor_ = (PFN_glClearColor)glfwGetProcAddress("glClearColor"); + auto glClear_ = (PFN_glClear)glfwGetProcAddress("glClear"); + auto glReadPixels_ = (PFN_glReadPixels)glfwGetProcAddress("glReadPixels"); + auto glFinish_ = (PFN_glFinish)glfwGetProcAddress("glFinish"); + std::printf("GL_VERSION: %s\nGL_RENDERER: %s\n", glGetString_(GL_VERSION), glGetString_(GL_RENDERER)); + + auto glCreateShader_ = (PFN_glCreateShader)glfwGetProcAddress("glCreateShader"); + auto glShaderSource_ = (PFN_glShaderSource)glfwGetProcAddress("glShaderSource"); + auto glCompileShader_ = (PFN_glCompileShader)glfwGetProcAddress("glCompileShader"); + auto glCreateProgram_ = (PFN_glCreateProgram)glfwGetProcAddress("glCreateProgram"); + auto glAttachShader_ = (PFN_glAttachShader)glfwGetProcAddress("glAttachShader"); + auto glLinkProgram_ = (PFN_glLinkProgram)glfwGetProcAddress("glLinkProgram"); + auto glUseProgram_ = (PFN_glUseProgram)glfwGetProcAddress("glUseProgram"); + auto glGenVertexArrays_ = (PFN_glGenVertexArrays)glfwGetProcAddress("glGenVertexArrays"); + auto glBindVertexArray_ = (PFN_glBindVertexArray)glfwGetProcAddress("glBindVertexArray"); + auto glGenBuffers_ = (PFN_glGenBuffers)glfwGetProcAddress("glGenBuffers"); + auto glBindBuffer_ = (PFN_glBindBuffer)glfwGetProcAddress("glBindBuffer"); + auto glBufferData_ = (PFN_glBufferData)glfwGetProcAddress("glBufferData"); + auto glVertexAttribPointer_ = (PFN_glVertexAttribPointer)glfwGetProcAddress("glVertexAttribPointer"); + auto glEnableVertexAttribArray_ = (PFN_glEnableVertexAttribArray)glfwGetProcAddress("glEnableVertexAttribArray"); + auto glDrawArrays_ = (PFN_glDrawArrays)glfwGetProcAddress("glDrawArrays"); + + const char* vsSrc = "#version 150 core\nin vec2 aPos;\nvoid main(){ gl_Position = vec4(aPos, 0.0, 1.0); }\n"; + const char* fsSrc = "#version 150 core\nout vec4 c;\nvoid main(){ c = vec4(1.0, 0.5, 0.0, 1.0); }\n"; + unsigned int vs = glCreateShader_(GL_VERTEX_SHADER); + glShaderSource_(vs, 1, &vsSrc, nullptr); + glCompileShader_(vs); + unsigned int fs = glCreateShader_(GL_FRAGMENT_SHADER); + glShaderSource_(fs, 1, &fsSrc, nullptr); + glCompileShader_(fs); + unsigned int prog = glCreateProgram_(); + glAttachShader_(prog, vs); + glAttachShader_(prog, fs); + glLinkProgram_(prog); + glUseProgram_(prog); + const float verts[] = {-0.9f, -0.9f, 0.9f, -0.9f, 0.0f, 0.9f}; + unsigned int vao = 0, vbo = 0; + glGenVertexArrays_(1, &vao); + glBindVertexArray_(vao); + glGenBuffers_(1, &vbo); + glBindBuffer_(GL_ARRAY_BUFFER, vbo); + glBufferData_(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); + glVertexAttribPointer_(0, 2, GL_FLOAT, 0, 0, nullptr); + glEnableVertexAttribArray_(0); + + unsigned char px[4] = {}; + for (int i = 0; i < 60 && !glfwWindowShouldClose(win); ++i) { + glClearColor_(0.1f, 0.2f, 0.4f, 1.0f); + glClear_(GL_COLOR_BUFFER_BIT); + glDrawArrays_(GL_TRIANGLES, 0, 3); + if (i == 58) { + glFinish_(); + glReadPixels_(427, 240, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px); + } + glfwSwapBuffers(win); + glfwPollEvents(); + } + std::printf("pixel: %u,%u,%u,%u\n", px[0], px[1], px[2], px[3]); + glfwDestroyWindow(win); + glfwTerminate(); + const bool ok = px[0] > 200 && px[1] > 90 && px[1] < 160 && px[2] < 50; + std::printf(ok ? "GLFW-SMOKE PASS\n" : "GLFW-SMOKE FAIL: wrong pixel\n"); + return ok ? 0 : 1; +} diff --git a/tools/wgl-smoke/wgl_smoke.cpp b/tools/wgl-smoke/wgl_smoke.cpp new file mode 100644 index 00000000..5757b9bc --- /dev/null +++ b/tools/wgl-smoke/wgl_smoke.cpp @@ -0,0 +1,472 @@ +// WGL smoke test for the MobileGL opengl32.dll drop-in. +// Replicates GLFW 3.3.3's WGL bootstrap exactly as Minecraft 1.21.4 drives it: +// 1. dummy window: gdi32 ChoosePixelFormat/SetPixelFormat -> wglCreateContext +// -> wglMakeCurrent -> probe wglGetProcAddress/extension strings +// -> wglMakeCurrent(NULL) -> wglDeleteContext +// 2. real window: SetPixelFormat -> wglCreateContextAttribsARB (3.2 core, +// forward-compatible) -> wglMakeCurrent -> render loop with SwapBuffers +// Verifies rendering via glReadPixels. Exit code 0 = pass. +// +// Build: cl /nologo /EHsc /W3 wgl_smoke.cpp user32.lib gdi32.lib +// Run with opengl32.dll (MobileGL) in the exe directory. + +#include +#include +#include +#include + +typedef unsigned int GLenum; +typedef unsigned int GLuint; +typedef int GLint; +typedef int GLsizei; +typedef float GLfloat; +typedef unsigned char GLubyte; +typedef char GLchar; +typedef signed long long GLsizeiptr; +typedef unsigned int GLbitfield; + +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_TRIANGLES 0x0004 +#define GL_FLOAT 0x1406 +#define GL_FALSE 0 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_STATIC_DRAW 0x88E4 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_RGBA 0x1908 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NO_ERROR 0 + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31B3 +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 + +static HMODULE g_opengl32; + +typedef HGLRC(WINAPI* PFN_wglCreateContext)(HDC); +typedef BOOL(WINAPI* PFN_wglDeleteContext)(HGLRC); +typedef PROC(WINAPI* PFN_wglGetProcAddress)(LPCSTR); +typedef BOOL(WINAPI* PFN_wglMakeCurrent)(HDC, HGLRC); +typedef HGLRC(WINAPI* PFN_wglGetCurrentContext)(void); +typedef HDC(WINAPI* PFN_wglGetCurrentDC)(void); + +static PFN_wglCreateContext p_wglCreateContext; +static PFN_wglDeleteContext p_wglDeleteContext; +static PFN_wglGetProcAddress p_wglGetProcAddress; +static PFN_wglMakeCurrent p_wglMakeCurrent; +static PFN_wglGetCurrentContext p_wglGetCurrentContext; +static PFN_wglGetCurrentDC p_wglGetCurrentDC; + +typedef HGLRC(WINAPI* PFN_wglCreateContextAttribsARB)(HDC, HGLRC, const int*); +typedef const char*(WINAPI* PFN_wglGetExtensionsStringARB)(HDC); +typedef BOOL(WINAPI* PFN_wglSwapIntervalEXT)(int); +typedef BOOL(WINAPI* PFN_wglGetPixelFormatAttribivARB)(HDC, int, int, UINT, const int*, int*); + +// GL function pointers, loaded LWJGL-style: wglGetProcAddress first, then +// GetProcAddress on the opengl32 module. +static void* GetGLProc(const char* name) { + PROC proc = p_wglGetProcAddress(name); + if (!proc) { + proc = ::GetProcAddress(g_opengl32, name); + } + return reinterpret_cast(proc); +} + +typedef const GLubyte*(WINAPI* PFN_glGetString)(GLenum); +typedef void(WINAPI* PFN_glGetIntegerv)(GLenum, GLint*); +typedef void(WINAPI* PFN_glClearColor)(GLfloat, GLfloat, GLfloat, GLfloat); +typedef void(WINAPI* PFN_glClear)(GLbitfield); +typedef void(WINAPI* PFN_glViewport)(GLint, GLint, GLsizei, GLsizei); +typedef GLenum(WINAPI* PFN_glGetError)(void); +typedef void(WINAPI* PFN_glReadPixels)(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, void*); +typedef void(WINAPI* PFN_glFinish)(void); +typedef GLuint(WINAPI* PFN_glCreateShader)(GLenum); +typedef void(WINAPI* PFN_glShaderSource)(GLuint, GLsizei, const GLchar* const*, const GLint*); +typedef void(WINAPI* PFN_glCompileShader)(GLuint); +typedef void(WINAPI* PFN_glGetShaderiv)(GLuint, GLenum, GLint*); +typedef void(WINAPI* PFN_glGetShaderInfoLog)(GLuint, GLsizei, GLsizei*, GLchar*); +typedef GLuint(WINAPI* PFN_glCreateProgram)(void); +typedef void(WINAPI* PFN_glAttachShader)(GLuint, GLuint); +typedef void(WINAPI* PFN_glLinkProgram)(GLuint); +typedef void(WINAPI* PFN_glGetProgramiv)(GLuint, GLenum, GLint*); +typedef void(WINAPI* PFN_glGetProgramInfoLog)(GLuint, GLsizei, GLsizei*, GLchar*); +typedef void(WINAPI* PFN_glUseProgram)(GLuint); +typedef void(WINAPI* PFN_glGenVertexArrays)(GLsizei, GLuint*); +typedef void(WINAPI* PFN_glBindVertexArray)(GLuint); +typedef void(WINAPI* PFN_glGenBuffers)(GLsizei, GLuint*); +typedef void(WINAPI* PFN_glBindBuffer)(GLenum, GLuint); +typedef void(WINAPI* PFN_glBufferData)(GLenum, GLsizeiptr, const void*, GLenum); +typedef void(WINAPI* PFN_glVertexAttribPointer)(GLuint, GLint, GLenum, unsigned char, GLsizei, const void*); +typedef void(WINAPI* PFN_glEnableVertexAttribArray)(GLuint); +typedef void(WINAPI* PFN_glDrawArrays)(GLenum, GLint, GLsizei); + +static PFN_glGetString glGetString_; +static PFN_glGetIntegerv glGetIntegerv_; +static PFN_glClearColor glClearColor_; +static PFN_glClear glClear_; +static PFN_glViewport glViewport_; +static PFN_glGetError glGetError_; +static PFN_glReadPixels glReadPixels_; +static PFN_glFinish glFinish_; +static PFN_glCreateShader glCreateShader_; +static PFN_glShaderSource glShaderSource_; +static PFN_glCompileShader glCompileShader_; +static PFN_glGetShaderiv glGetShaderiv_; +static PFN_glGetShaderInfoLog glGetShaderInfoLog_; +static PFN_glCreateProgram glCreateProgram_; +static PFN_glAttachShader glAttachShader_; +static PFN_glLinkProgram glLinkProgram_; +static PFN_glGetProgramiv glGetProgramiv_; +static PFN_glGetProgramInfoLog glGetProgramInfoLog_; +static PFN_glUseProgram glUseProgram_; +static PFN_glGenVertexArrays glGenVertexArrays_; +static PFN_glBindVertexArray glBindVertexArray_; +static PFN_glGenBuffers glGenBuffers_; +static PFN_glBindBuffer glBindBuffer_; +static PFN_glBufferData glBufferData_; +static PFN_glVertexAttribPointer glVertexAttribPointer_; +static PFN_glEnableVertexAttribArray glEnableVertexAttribArray_; +static PFN_glDrawArrays glDrawArrays_; + +#define FAIL(...) \ + do { \ + std::fprintf(stderr, "SMOKE FAIL: " __VA_ARGS__); \ + std::fprintf(stderr, "\n"); \ + return 1; \ + } while (0) + +// CRT-free crash dumper: runs during process teardown when stdio is already +// dead, so it may only touch raw Win32. +static void RawPrint(HANDLE f, const char* s) { + DWORD written = 0; + WriteFile(f, s, (DWORD)lstrlenA(s), &written, nullptr); +} + +static void RawHex(HANDLE f, DWORD64 v) { + char buf[20]; + for (int i = 0; i < 16; ++i) { + int nib = (int)((v >> ((15 - i) * 4)) & 0xF); + buf[i] = (char)(nib < 10 ? '0' + nib : 'a' + nib - 10); + } + buf[16] = 0; + RawPrint(f, buf); +} + +static LONG WINAPI CrashDump(EXCEPTION_POINTERS* ep) { + static volatile LONG entered = 0; + if (ep->ExceptionRecord->ExceptionCode != 0xC0000005) return EXCEPTION_CONTINUE_SEARCH; + if (InterlockedExchange(&entered, 1)) return EXCEPTION_CONTINUE_SEARCH; + HANDLE f = CreateFileA( + "C:\\Users\\yello\\AndroidStudioProjects\\FoldCraftLauncher\\MobileGL\\.claude\\worktrees\\wgl-host\\wgl-smoke\\crash-stack.txt", + GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (f == INVALID_HANDLE_VALUE) { + TerminateProcess(GetCurrentProcess(), 0x43); + } + RawPrint(f, "AV at "); + RawHex(f, (DWORD64)ep->ExceptionRecord->ExceptionAddress); + RawPrint(f, "\n"); + const DWORD64* sp = reinterpret_cast(ep->ContextRecord->Rsp); + for (int i = 0; i < 512; ++i) { + MEMORY_BASIC_INFORMATION mbi; + if (!VirtualQuery(sp + i, &mbi, sizeof(mbi)) || mbi.State != MEM_COMMIT || + (mbi.Protect & (PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) == 0) + break; + DWORD64 v = sp[i]; + HMODULE mod = nullptr; + if (v > 0x10000 && + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(v), &mod) && + mod) { + char name[MAX_PATH]; + name[0] = 0; + GetModuleFileNameA(mod, name, MAX_PATH); + RawPrint(f, "stack "); + RawHex(f, v); + RawPrint(f, " "); + RawPrint(f, name); + RawPrint(f, "+"); + RawHex(f, v - (DWORD64)mod); + RawPrint(f, "\n"); + } + } + CloseHandle(f); + TerminateProcess(GetCurrentProcess(), 0x42); + return EXCEPTION_EXECUTE_HANDLER; +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + return DefWindowProcA(hwnd, msg, wp, lp); +} + +static HWND MakeWindow(const char* title, int w, int h, bool visible) { + WNDCLASSA wc{}; + wc.style = CS_OWNDC; + wc.lpfnWndProc = WndProc; + wc.hInstance = GetModuleHandleA(nullptr); + wc.lpszClassName = "MGWGLSmoke"; + static bool registered = false; + if (!registered) { + RegisterClassA(&wc); + registered = true; + } + DWORD style = WS_OVERLAPPEDWINDOW | (visible ? WS_VISIBLE : 0); + RECT rect{0, 0, w, h}; + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); + return CreateWindowExA(0, "MGWGLSmoke", title, style, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, + rect.bottom - rect.top, nullptr, nullptr, GetModuleHandleA(nullptr), nullptr); +} + +int main() { + AddVectoredExceptionHandler(1, CrashDump); + // Load exactly like GLFW: the app-dir opengl32.dll must win the search. + g_opengl32 = LoadLibraryA("opengl32.dll"); + if (!g_opengl32) FAIL("LoadLibraryA(opengl32.dll) failed"); + char modulePath[MAX_PATH]{}; + GetModuleFileNameA(g_opengl32, modulePath, MAX_PATH); + std::printf("opengl32.dll -> %s\n", modulePath); + + p_wglCreateContext = (PFN_wglCreateContext)::GetProcAddress(g_opengl32, "wglCreateContext"); + p_wglDeleteContext = (PFN_wglDeleteContext)::GetProcAddress(g_opengl32, "wglDeleteContext"); + p_wglGetProcAddress = (PFN_wglGetProcAddress)::GetProcAddress(g_opengl32, "wglGetProcAddress"); + p_wglMakeCurrent = (PFN_wglMakeCurrent)::GetProcAddress(g_opengl32, "wglMakeCurrent"); + p_wglGetCurrentContext = (PFN_wglGetCurrentContext)::GetProcAddress(g_opengl32, "wglGetCurrentContext"); + p_wglGetCurrentDC = (PFN_wglGetCurrentDC)::GetProcAddress(g_opengl32, "wglGetCurrentDC"); + if (!p_wglCreateContext || !p_wglDeleteContext || !p_wglGetProcAddress || !p_wglMakeCurrent || + !p_wglGetCurrentContext || !p_wglGetCurrentDC) + FAIL("missing classic wgl exports"); + + // ---- Phase 1: GLFW-style dummy context on a hidden helper window ---- + HWND helper = MakeWindow("helper", 1, 1, false); + if (!helper) FAIL("helper window creation failed"); + HDC helperDC = GetDC(helper); + + 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 = 24; + + int dummyFormat = ChoosePixelFormat(helperDC, &pfd); // gdi32 -> wglChoosePixelFormat + if (dummyFormat <= 0) FAIL("gdi32 ChoosePixelFormat returned %d", dummyFormat); + if (!SetPixelFormat(helperDC, dummyFormat, &pfd)) FAIL("gdi32 SetPixelFormat failed"); + std::printf("dummy pixel format: %d (DescribePixelFormat count=%d)\n", dummyFormat, + DescribePixelFormat(helperDC, 1, sizeof(pfd), nullptr)); + + HGLRC dummyCtx = p_wglCreateContext(helperDC); + if (!dummyCtx) FAIL("wglCreateContext (dummy) failed"); + if (!p_wglMakeCurrent(helperDC, dummyCtx)) { + typedef int(WINAPI * PFN_eglGetError)(void); + auto eglGetError_ = (PFN_eglGetError)::GetProcAddress(g_opengl32, "eglGetError"); + FAIL("wglMakeCurrent (dummy) failed (GetLastError=%lu eglError=0x%x)", GetLastError(), + eglGetError_ ? eglGetError_() : -1); + } + + auto wglGetExtensionsStringARB = + (PFN_wglGetExtensionsStringARB)p_wglGetProcAddress("wglGetExtensionsStringARB"); + auto wglCreateContextAttribsARB = + (PFN_wglCreateContextAttribsARB)p_wglGetProcAddress("wglCreateContextAttribsARB"); + auto wglSwapIntervalEXT = (PFN_wglSwapIntervalEXT)p_wglGetProcAddress("wglSwapIntervalEXT"); + auto wglGetPixelFormatAttribivARB = + (PFN_wglGetPixelFormatAttribivARB)p_wglGetProcAddress("wglGetPixelFormatAttribivARB"); + if (!wglGetExtensionsStringARB) FAIL("wglGetExtensionsStringARB unavailable"); + if (!wglCreateContextAttribsARB) FAIL("wglCreateContextAttribsARB unavailable"); + if (!wglSwapIntervalEXT) FAIL("wglSwapIntervalEXT unavailable"); + if (!wglGetPixelFormatAttribivARB) FAIL("wglGetPixelFormatAttribivARB unavailable"); + std::printf("WGL extensions: %s\n", wglGetExtensionsStringARB(helperDC)); + + glGetString_ = (PFN_glGetString)GetGLProc("glGetString"); + if (!glGetString_) FAIL("glGetString unavailable"); + std::printf("dummy ctx GL_VERSION: %s\n", glGetString_(GL_VERSION)); + std::printf("dummy ctx GL_RENDERER: %s\n", glGetString_(GL_RENDERER)); + + int nfmtAttrib = WGL_NUMBER_PIXEL_FORMATS_ARB; + int nativeCount = 0; + if (!wglGetPixelFormatAttribivARB(helperDC, 1, 0, 1, &nfmtAttrib, &nativeCount) || nativeCount < 1) + FAIL("WGL_NUMBER_PIXEL_FORMATS_ARB query failed (%d)", nativeCount); + std::printf("ARB pixel formats: %d\n", nativeCount); + + if (!p_wglMakeCurrent(nullptr, nullptr)) FAIL("wglMakeCurrent(NULL) failed"); + if (!p_wglDeleteContext(dummyCtx)) FAIL("wglDeleteContext (dummy) failed"); + + // ---- Phase 2: real window + 3.2 core forward-compatible context ---- + HWND window = MakeWindow("MobileGL WGL smoke", 640, 480, true); + if (!window) FAIL("main window creation failed"); + HDC dc = GetDC(window); + + // Pick the depth24/stencil8 RGBA format via the ARB query, GLFW-style. + int chosenFormat = 0; + for (int i = 1; i <= nativeCount; ++i) { + const int attribs[] = {WGL_SUPPORT_OPENGL_ARB, WGL_DRAW_TO_WINDOW_ARB, WGL_PIXEL_TYPE_ARB, + WGL_DOUBLE_BUFFER_ARB, WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB}; + int values[6] = {}; + if (!wglGetPixelFormatAttribivARB(dc, i, 0, 6, attribs, values)) continue; + if (values[0] && values[1] && values[2] == WGL_TYPE_RGBA_ARB && values[3] && values[4] >= 24 && + values[5] >= 8) { + chosenFormat = i; + break; + } + } + if (!chosenFormat) FAIL("no ARB pixel format with depth24+stencil8"); + PIXELFORMATDESCRIPTOR realPfd{}; + if (!DescribePixelFormat(dc, chosenFormat, sizeof(realPfd), &realPfd)) + FAIL("DescribePixelFormat(%d) failed", chosenFormat); + if (!SetPixelFormat(dc, chosenFormat, &realPfd)) FAIL("SetPixelFormat (real) failed"); + + const int ctxAttribs[] = { + WGL_CONTEXT_OPENGL_NO_ERROR_ARB, 1, + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 2, + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0, + }; + HGLRC ctx = wglCreateContextAttribsARB(dc, nullptr, ctxAttribs); + if (!ctx) FAIL("wglCreateContextAttribsARB failed (err=0x%lx)", GetLastError()); + if (!p_wglMakeCurrent(dc, ctx)) FAIL("wglMakeCurrent (real) failed"); + if (p_wglGetCurrentContext() != ctx || p_wglGetCurrentDC() != dc) FAIL("current context/DC mismatch"); + if (!wglSwapIntervalEXT(0)) FAIL("wglSwapIntervalEXT(0) failed"); + +#define LOAD(fn) \ + fn##_ = (PFN_##fn)GetGLProc(#fn); \ + if (!fn##_) FAIL("missing GL function " #fn); + LOAD(glGetIntegerv) + LOAD(glClearColor) + LOAD(glClear) + LOAD(glViewport) + LOAD(glGetError) + LOAD(glReadPixels) + LOAD(glFinish) + LOAD(glCreateShader) + LOAD(glShaderSource) + LOAD(glCompileShader) + LOAD(glGetShaderiv) + LOAD(glGetShaderInfoLog) + LOAD(glCreateProgram) + LOAD(glAttachShader) + LOAD(glLinkProgram) + LOAD(glGetProgramiv) + LOAD(glGetProgramInfoLog) + LOAD(glUseProgram) + LOAD(glGenVertexArrays) + LOAD(glBindVertexArray) + LOAD(glGenBuffers) + LOAD(glBindBuffer) + LOAD(glBufferData) + LOAD(glVertexAttribPointer) + LOAD(glEnableVertexAttribArray) + LOAD(glDrawArrays) +#undef LOAD + + GLint major = 0, minor = 0; + glGetIntegerv_(GL_MAJOR_VERSION, &major); + glGetIntegerv_(GL_MINOR_VERSION, &minor); + std::printf("real ctx: GL %d.%d | %s | %s\n", major, minor, glGetString_(GL_RENDERER), + glGetString_(GL_VERSION)); + if (major < 3 || (major == 3 && minor < 2)) FAIL("context version below 3.2"); + + const char* vsSrc = "#version 150 core\n" + "in vec2 aPos;\n" + "void main() { gl_Position = vec4(aPos, 0.0, 1.0); }\n"; + const char* fsSrc = "#version 150 core\n" + "out vec4 fragColor;\n" + "void main() { fragColor = vec4(1.0, 0.5, 0.0, 1.0); }\n"; + + GLuint vs = glCreateShader_(GL_VERTEX_SHADER); + glShaderSource_(vs, 1, &vsSrc, nullptr); + glCompileShader_(vs); + GLint ok = 0; + glGetShaderiv_(vs, GL_COMPILE_STATUS, &ok); + if (!ok) { + char log[2048]; + glGetShaderInfoLog_(vs, sizeof(log), nullptr, log); + FAIL("vertex shader compile failed: %s", log); + } + GLuint fs = glCreateShader_(GL_FRAGMENT_SHADER); + glShaderSource_(fs, 1, &fsSrc, nullptr); + glCompileShader_(fs); + glGetShaderiv_(fs, GL_COMPILE_STATUS, &ok); + if (!ok) { + char log[2048]; + glGetShaderInfoLog_(fs, sizeof(log), nullptr, log); + FAIL("fragment shader compile failed: %s", log); + } + GLuint prog = glCreateProgram_(); + glAttachShader_(prog, vs); + glAttachShader_(prog, fs); + glLinkProgram_(prog); + glGetProgramiv_(prog, GL_LINK_STATUS, &ok); + if (!ok) { + char log[2048]; + glGetProgramInfoLog_(prog, sizeof(log), nullptr, log); + FAIL("program link failed: %s", log); + } + + const GLfloat verts[] = {-0.8f, -0.8f, 0.8f, -0.8f, 0.0f, 0.8f}; + GLuint vao = 0, vbo = 0; + glGenVertexArrays_(1, &vao); + glBindVertexArray_(vao); + glGenBuffers_(1, &vbo); + glBindBuffer_(GL_ARRAY_BUFFER, vbo); + glBufferData_(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); + glVertexAttribPointer_(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray_(0); + + glViewport_(0, 0, 640, 480); + glUseProgram_(prog); + + unsigned char centerPixel[4] = {}, cornerPixel[4] = {}; + for (int frame = 0; frame < 60; ++frame) { + MSG msg; + while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + glClearColor_(0.1f, 0.2f, 0.4f, 1.0f); + glClear_(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDrawArrays_(GL_TRIANGLES, 0, 3); + if (frame == 58) { + glFinish_(); + glReadPixels_(320, 240, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, centerPixel); + glReadPixels_(5, 5, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, cornerPixel); + } + if (!SwapBuffers(dc)) FAIL("gdi32 SwapBuffers failed at frame %d", frame); // gdi32 -> wglSwapBuffers + } + + GLenum err = glGetError_(); + std::printf("center pixel: %u,%u,%u,%u corner pixel: %u,%u,%u,%u glGetError=0x%x\n", centerPixel[0], + centerPixel[1], centerPixel[2], centerPixel[3], cornerPixel[0], cornerPixel[1], cornerPixel[2], + cornerPixel[3], err); + + if (err != GL_NO_ERROR) FAIL("GL error 0x%x", err); + // Orange triangle at center (255,127±3,0), clear color at corner (~25,51,102). + if (!(centerPixel[0] > 200 && centerPixel[1] > 90 && centerPixel[1] < 160 && centerPixel[2] < 50)) + FAIL("center pixel is not the triangle color"); + if (!(cornerPixel[2] > 60 && cornerPixel[0] < 60)) FAIL("corner pixel is not the clear color"); + + if (!p_wglMakeCurrent(nullptr, nullptr)) FAIL("final wglMakeCurrent(NULL) failed"); + if (!p_wglDeleteContext(ctx)) FAIL("final wglDeleteContext failed"); + + std::printf("SMOKE PASS\n"); + return 0; +}