#include "pch.h" #include "platform_windows/windows_bootstrap_helpers.h" #include "platform_windows/windows_window_shell.h" #include "app.h" #include "canvas.h" #include "legacy_gl_runtime_dispatch.h" #include "legacy_preference_storage.h" #include "log.h" #include #include #include #include #include #include #include #include #if __has_include() #include #define USE_RENDERDOC #endif namespace pp::platform::windows { void set_async_render_context(HDC hdc, HGLRC hrc); } namespace pp::platform::windows { #ifdef USE_RENDERDOC RENDERDOC_API_1_4_0* rdoc_api = NULL; bool win32_renderdoc_init() { // At init, on windows if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) { pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI"); return RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void**)&rdoc_api); } return false; } void win32_renderdoc_frame_start() { if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL); } void win32_renderdoc_frame_end() { if (rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL); } #else void win32_renderdoc_frame_start() { } void win32_renderdoc_frame_end() { } #endif HRESULT(*GetDpiForMonitor_fn)(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY); HRESULT(*SetProcessDpiAwareness_fn)(PROCESS_DPI_AWARENESS value); void init_shcore_API() { HMODULE dll = LoadLibrary(L"Shcore.dll"); if (!dll) { LOG("cannot load Shcore.dll"); return; } LOG("loaded Shcore.dll"); GetDpiForMonitor_fn = (decltype(GetDpiForMonitor_fn))GetProcAddress(dll, "GetDpiForMonitor"); SetProcessDpiAwareness_fn = (decltype(SetProcessDpiAwareness_fn))GetProcAddress(dll, "SetProcessDpiAwareness"); } // Returns the last Win32 error, in string format. Returns an empty string if there is no error. std::string GetLastErrorAsString() { DWORD errorMessageID = ::GetLastError(); if (errorMessageID == 0) return std::string(); LPSTR messageBuffer = nullptr; size_t size = FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); std::string message(messageBuffer, size); LocalFree(messageBuffer); return message; } void ensure_runtime_data_directory() { FILE* fp_check = fopen("data\\layout.xml", "rb"); if (!fp_check) { LOG("data files not found"); static char path[MAX_PATH]; GetModuleFileNameA(NULL, path, MAX_PATH); LOG("current dir %s", path); PathRemoveFileSpecA(path); SetCurrentDirectoryA(path); LOG("change dir to %s", path); return; } fclose(fp_check); LOG("data files ok"); } void setup_exception_handler() { // Setup exception handler BT_SetAppName(_T("PanoPainter")); BT_SetAppVersion(g_version_w); //BT_SetSupportEMail(_T("your@email.com")); BT_SetFlags(BTF_DETAILEDMODE | BTF_ATTACHREPORT | BTF_SCREENCAPTURE); // = BugTrapServer =========================================== //BT_SetSupportServer(_T("omigamedev.ddns.net"), 8088); BT_SetSupportEMail(_T("info@panopainter.com")); // - or - //BT_SetSupportServer(_T("127.0.0.1"), 9999); // = BugTrapWebServer ======================================== //BT_SetSupportServer(_T("http://localhost/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT); //BT_SetSupportServer(_T("http://omigamedev.ddns.net:8088/source/Server/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT); BT_SetSupportServer(_T("http://panopainter.com/bug/"), BUGTRAP_HTTP_PORT); // required for VS 2005 & 2008 BT_InstallSehFilter(); // Add custom log file using default name TCHAR wpath[MAX_PATH]; //GetFullPathNameW(L"panopainter-log.txt", 1024, wpath, nullptr); auto log_file = App::I->data_path + "/panopainter-log.txt"; std::mbstowcs(wpath, log_file.c_str(), log_file.size()); BT_AddLogFile(wpath); BT_SetPreErrHandler([](INT_PTR){ if (Canvas::I && Canvas::I->m_unsaved) { auto t = std::time(nullptr); auto tm = *std::localtime(&t); std::ostringstream oss; oss << std::put_time(&tm, "%d-%m-%Y %H-%M-%S"); auto path = App::I->data_path + "/" + App::I->doc_name + "-recovery (" + oss.str() + ").ppi"; Canvas::I->project_save_thread(path, false); static char abspath[MAX_PATH]; GetFullPathNameA(path.c_str(), MAX_PATH, abspath, NULL); static char message[4096]; snprintf(message, sizeof(message), "File recovered in: %s", abspath); MessageBoxA(retained_state().hWnd, message, "File Recovery", MB_OK | MB_ICONWARNING); } LogRemote::I.file_close(); }, 0); BT_SetTerminate(); } int read_WMI_info() { // see: http://win32easy.blogspot.co.uk/2011/03/wmi-in-c-query-everyting-from-your-os.html HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hRes)) { LOG("Unable to launch COM: %x", hRes); return 1; } if ((FAILED(hRes = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0)))) { LOG("Unable to initialize security: %x", hRes); return 1; } IWbemLocator* pLocator = NULL; if (FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator)))) { LOG("Unable to create a WbemLocator: %x", hRes); return 1; } IWbemServices* pService = NULL; if (FAILED(hRes = pLocator->ConnectServer(BSTR(L"root\\CIMV2"), NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService))) { pLocator->Release(); LOG("Unable to connect to \"CIMV2\": %x", hRes); return 1; } auto log_field = [](const wchar_t* section, IWbemClassObject* clsObj, const wchar_t* field) { VARIANT vRet; CIMTYPE pType; VariantInit(&vRet); if (SUCCEEDED(clsObj->Get(field, 0, &vRet, &pType, NULL))) { if (pType == CIM_STRING && pType != CIM_EMPTY && pType != CIM_ILLEGAL) { LOGW(L"%s %s: %s", section, field, vRet.bstrVal); } else if (pType == CIM_UINT32 && pType != CIM_EMPTY && pType != CIM_ILLEGAL) { LOGW(L"%s %s: %d", section, field, vRet.uintVal); } VariantClear(&vRet); } }; auto get_int = [](IWbemClassObject* clsObj, const wchar_t* field) { VARIANT vRet; CIMTYPE pType; VariantInit(&vRet); int ret = 0; if (SUCCEEDED(clsObj->Get(field, 0, &vRet, &pType, NULL))) { if (pType == CIM_UINT32 && pType != CIM_EMPTY && pType != CIM_ILLEGAL) ret = vRet.uintVal; VariantClear(&vRet); } return ret; }; // GET DEVICE INFO { IEnumWbemClassObject* pEnumerator = NULL; if (FAILED(hRes = pService->ExecQuery(BSTR(L"WQL"), BSTR(L"SELECT * FROM Win32_ComputerSystem"), WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); LOG("Unable to retrive desktop monitors: %x", hRes); return 1; } IWbemClassObject* clsObj = NULL; int numElems; while ((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE) { if (FAILED(hRes)) break; log_field(L"Machine", clsObj, L"Name"); log_field(L"Machine", clsObj, L"Model"); log_field(L"Machine", clsObj, L"Manufacturer"); clsObj->Release(); } pEnumerator->Release(); } // GET OS INFO { IEnumWbemClassObject* pEnumerator = NULL; if (FAILED(hRes = pService->ExecQuery(BSTR(L"WQL"), BSTR(L"SELECT * FROM Win32_OperatingSystem"), WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); LOG("Unable to retrive desktop monitors: %x", hRes); return 1; } IWbemClassObject* clsObj = NULL; int numElems; while ((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE) { if (FAILED(hRes)) break; log_field(L"OS", clsObj, L"Name"); log_field(L"OS", clsObj, L"Version"); log_field(L"OS", clsObj, L"Locale"); log_field(L"OS", clsObj, L"OSProductSuite"); log_field(L"OS", clsObj, L"Manufacturer"); log_field(L"OS", clsObj, L"Description"); clsObj->Release(); } pEnumerator->Release(); } pService->Release(); pService = NULL; if (FAILED(hRes = pLocator->ConnectServer(BSTR(L"root\\Microsoft\\Windows\\DeviceGuard"), NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService))) { pLocator->Release(); LOG("Unable to connect to \"DeviceGuard\": %x", hRes); return 1; } // GET DEVICE GUARD { IEnumWbemClassObject* pEnumerator = NULL; if (FAILED(hRes = pService->ExecQuery(BSTR(L"WQL"), BSTR(L"SELECT * FROM Win32_DeviceGuard"), WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); LOG("Unable to retrive desktop monitors: %x", hRes); return 1; } IWbemClassObject* clsObj = NULL; int numElems; while ((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE) { if (FAILED(hRes)) break; if (get_int(clsObj, L"CodeIntegrityPolicyEnforcementStatus") > 0) { LOG("SANDBOX DETECTED"); retained_state().sandboxed = true; } SAFEARRAY *psaNames = NULL; if (SUCCEEDED(clsObj->GetNames(0, WBEM_FLAG_ALWAYS | WBEM_FLAG_NONSYSTEM_ONLY, 0, &psaNames))) { // Get the number of properties. long lLower, lUpper; BSTR PropName = NULL; SafeArrayGetLBound(psaNames, 1, &lLower); SafeArrayGetUBound(psaNames, 1, &lUpper); for (long i = lLower; i <= lUpper; i++) { // Get this property. SafeArrayGetElement(psaNames, &i, &PropName); LOGW(L"Prop: %s", PropName); log_field(L"DeviceGuard", clsObj, PropName); SysFreeString(PropName); } SafeArrayDestroy(psaNames); } clsObj->Release(); } pEnumerator->Release(); } pLocator->Release(); CoUninitialize(); return 0; } MainWindowStartupState initialize_main_window_startup_state() { auto startup = MainWindowStartupState {}; const auto hInst = GetModuleHandle(NULL); const auto className = L"EngineMain"; startup.window_class.hInstance = hInst; startup.window_class.lpfnWndProc = (WNDPROC)main_window_proc; startup.window_class.lpszClassName = className; startup.window_class.hbrBackground = (HBRUSH)COLOR_WINDOW; startup.window_class.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClass(&startup.window_class); auto monitor = MonitorFromWindow(0, MONITOR_DEFAULTTOPRIMARY); auto x = unsigned{ 96 }; auto y = unsigned{ 96 }; if (GetDpiForMonitor_fn) GetDpiForMonitor_fn(monitor, MDT_EFFECTIVE_DPI, &x, &y); App::I->display_density = (float)x / 96.f; const auto window_preferences = pp::panopainter::read_legacy_window_preferences(SW_NORMAL); if (window_preferences.has_ui_scale) App::I->zoom = window_preferences.ui_scale; else App::I->zoom = (float)x / 96.f; startup.show_command = window_preferences.show_command; startup.client_rect = { 0, 0, static_cast(App::I->width * App::I->zoom), static_cast(App::I->height * App::I->zoom), }; if (window_preferences.has_window_rect) { auto wnd_rect = window_preferences.window_rect; App::I->width = wnd_rect.z - wnd_rect.x; App::I->height = wnd_rect.w - wnd_rect.y; startup.client_rect = { wnd_rect.x, wnd_rect.y, wnd_rect.z, wnd_rect.w }; startup.client_pos = { wnd_rect.x, wnd_rect.y }; } else { AdjustWindowRect(&startup.client_rect, startup.window_style, false); } return startup; } void create_main_window(const MainWindowStartupState& startup, HWND& hWnd, HINSTANCE hInst, const wchar_t* window_title) { const int window_width = startup.client_rect.right - startup.client_rect.left; const int window_height = startup.client_rect.bottom - startup.client_rect.top; hWnd = CreateWindow( startup.window_class.lpszClassName, window_title, startup.window_style, startup.client_pos.x, startup.client_pos.y, window_width, window_height, 0, 0, hInst, 0); } void initialize_pixel_format_descriptor(PIXELFORMATDESCRIPTOR& pixel_format) { pixel_format.nSize = sizeof(pixel_format); pixel_format.nVersion = 1; pixel_format.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pixel_format.iPixelType = PFD_TYPE_RGBA; pixel_format.cColorBits = 32; pixel_format.cDepthBits = 24; pixel_format.iLayerType = PFD_MAIN_PLANE; } bool load_glad_entry_points(HDC device_context) { if (!gladLoadGL()) { LOG("gladLoadGL() failed"); return false; } if (!gladLoadWGL(device_context)) { LOG("gladLoadWGL() failed"); return false; } return true; } pp::renderer::gl::OpenGlRuntimeInfo log_runtime_info() { auto runtime_info = pp::renderer::gl::OpenGlRuntimeInfo {}; const auto runtime_info_result = pp::renderer::gl::query_opengl_runtime_info( pp::legacy::gl_runtime::runtime_info_dispatch()); if (runtime_info_result.ok()) { runtime_info = runtime_info_result.value(); LOG("GL version: %s", runtime_info.version); LOG("GL vendor: %s", runtime_info.vendor); LOG("GL renderer: %s", runtime_info.renderer); } else { LOG("OpenGL runtime info query failed: %s", runtime_info_result.status().message); } return runtime_info; } bool upgrade_to_core_gl_context(const MainWindowStartupState& startup, HWND& hWnd, HINSTANCE hInst, const wchar_t* window_title, OpenGlWindowContext& context) { if (!GLAD_WGL_ARB_create_context) { LOG("WGL_ARB_create_context not supported"); // If not supported, go fuck yourself we are not gonna support your shitty device return false; } const auto wgl_config = pp::renderer::gl::windows_wgl_core_context_3_3_config(); UINT num_format = 0; wglMakeCurrent(NULL, NULL); wglDeleteContext(context.render_context); DestroyWindow(hWnd); create_main_window(startup, hWnd, hInst, window_title); context.device_context = GetDC(hWnd); int pixel_format = 0; wglChoosePixelFormatARB( context.device_context, wgl_config.pixel_format_attributes.data(), nullptr, 1, &pixel_format, &num_format); SetPixelFormat(context.device_context, pixel_format, &startup.pixel_format); context.render_context = wglCreateContextAttribsARB( context.device_context, NULL, wgl_config.context_attributes.data()); wglMakeCurrent(context.device_context, context.render_context); set_async_render_context(context.device_context, context.render_context); return true; } MainStartupResult initialize_main_window_and_gl(MainWindowStartupState& startup, HWND& hWnd, HINSTANCE hInst, wchar_t* window_title, OpenGlWindowContext& context) { create_main_window(startup, hWnd, hInst, L"PanoPainter"); initialize_pixel_format_descriptor(startup.pixel_format); context.device_context = GetDC(hWnd); const int pixel_format = ChoosePixelFormat(context.device_context, &startup.pixel_format); SetPixelFormat(context.device_context, pixel_format, &startup.pixel_format); context.render_context = wglCreateContext(context.device_context); wglMakeCurrent(context.device_context, context.render_context); set_async_render_context(context.device_context, context.render_context); if (!load_glad_entry_points(context.device_context)) return MainStartupResult::GladLoadFailure; context.runtime_info = log_runtime_info(); #ifdef USE_RENDERDOC if (!win32_renderdoc_init()) LOG("Renderdoc not started"); #endif const auto renderer_name = std::string(context.runtime_info.renderer != nullptr ? context.runtime_info.renderer : ""); swprintf_s( window_title, 512, L"PanoPainter %s (%s)", g_version_number_w, str2wstr(renderer_name).c_str()); if (!upgrade_to_core_gl_context(startup, hWnd, hInst, window_title, context)) return MainStartupResult::MissingCoreContextSupport; return MainStartupResult::Ok; } BOOL UnadjustWindowRectEx(LPRECT prc, DWORD dwStyle, BOOL fMenu, DWORD dwExStyle) { RECT rc; SetRectEmpty(&rc); BOOL fRc = AdjustWindowRectEx(&rc, dwStyle, fMenu, dwExStyle); if (fRc) { prc->left -= rc.left; prc->top -= rc.top; prc->right -= rc.right; prc->bottom -= rc.bottom; } return fRc; } void _pre_call_callback(const char* name, void* funcptr, int len_args, ...) { assert(App::I->is_render_thread()); } void _post_call_callback(const char* name, void* funcptr, int len_args, ...) { GLenum error_code; error_code = glad_glGetError(); if (error_code != pp::renderer::gl::no_error_code()) { LOG("ERROR %d in %s\n", error_code, name); } } }