#include "pch.h" #include "log.h" #include "shader.h" #include "shape.h" #include "texture.h" #include "image.h" #include "app.h" #include "canvas.h" #include "keymap.h" #include "hmd.h" #include "../resource.h" #include #include #include "wacom.h" #include #include #include "abr.h" #include "settings.h" #define WM_USER_CLOSE (WM_USER + 1) LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); HINSTANCE hInst; HWND hWnd; HDC hDC; HGLRC hRC; wchar_t* className; bool keys[256]; std::mutex gl_mutex; std::mutex async_mutex; std::thread::id gl_thread; std::map vkey_map; std::thread hmd_renderer; std::thread renderer; int running = -1; int vr_running = 0; std::mutex render_mutex; std::condition_variable render_cv; int gl_count = 0; std::deque> tasklist; std::mutex task_mutex; std::deque> main_tasklist; std::mutex main_task_mutex; float timer_stylus = 0; float timer_ink_touch = 0; float timer_ink_pen = 0; bool sandboxed = false; //Returns the last Win32 error, in string format. Returns an empty string if there is no error. std::string GetLastErrorAsString() { //Get the error message, if any. DWORD errorMessageID = ::GetLastError(); if (errorMessageID == 0) return std::string(); //No error message has been recorded 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); //Free the buffer. LocalFree(messageBuffer); return message; } void destroy_window() { std::lock_guard lock(main_task_mutex); main_tasklist.emplace_back([=] { SendMessage(hWnd, WM_USER_CLOSE, 0, 0); }); } void async_lock() { //std::lock_guard _lock(async_mutex); if (gl_count == 0 || gl_thread != std::this_thread::get_id()) { gl_mutex.lock(); bool ret = wglMakeCurrent(hDC, hRC); if (ret == false) LOG("FAILED wglMakeCurrent: %s", GetLastErrorAsString().c_str()); gl_thread = std::this_thread::get_id(); //LOG("lock"); } gl_count++; //assert(ret == true); } bool async_lock_try() { //std::lock_guard _lock(async_mutex); if (gl_count == 0 || gl_thread != std::this_thread::get_id()) { if (!gl_mutex.try_lock()) return false; bool ret = wglMakeCurrent(hDC, hRC); if (ret == false) LOG("FAILED wglMakeCurrent: %s", GetLastErrorAsString().c_str()); gl_thread = std::this_thread::get_id(); //LOG("lock"); } gl_count++; //assert(ret == true); return true; } void async_swap() { SwapBuffers(hDC); //LOG("swap"); } void async_unlock() { //std::lock_guard _lock(async_mutex); gl_count--; if (gl_count == 0) { //LOG("unlock"); wglMakeCurrent(0, 0); gl_mutex.unlock(); } } void win32_show_cursor(bool visible) { std::lock_guard lock(main_task_mutex); main_tasklist.emplace_back([=] { if (visible) while (ShowCursor(true) < 0); else while (ShowCursor(false) >= 0); }); } std::string win32_open_file(const char* filter) { OPENFILENAMEA ofn; char fileName[MAX_PATH] = ""; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = filter; ofn.lpstrFile = fileName; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; ofn.lpstrDefExt = ""; ofn.lpstrInitialDir = ""; if (GetOpenFileNameA(&ofn) != NULL) { return fileName; } return ""; } std::string win32_open_dir() { BROWSEINFOA bi; char Buffer[MAX_PATH]; ZeroMemory(Buffer, MAX_PATH); ZeroMemory(&bi, sizeof(bi)); bi.hwndOwner = hWnd; bi.pszDisplayName = Buffer; bi.lpszTitle = "Title"; bi.ulFlags = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS | BIF_SHAREABLE; LPCITEMIDLIST pFolder = SHBrowseForFolderA(&bi); if (pFolder == NULL) return ""; if (!SHGetPathFromIDListA(pFolder, Buffer)) return ""; return Buffer; } struct async_locker { async_locker() { async_lock(); } ~async_locker() { async_unlock(); } }; 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(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(L"WQL", 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(L"WQL", 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(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(L"WQL", 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"); 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; } INT_PTR g_iLogHandle = -1; static void SetupExceptionHandler() { // 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 // g_iLogHandle = BT_OpenLogFile(NULL, BTLF_TEXT); // BT_SetLogSizeInEntries(g_iLogHandle, 100); // BT_SetLogFlags(g_iLogHandle, BTLF_SHOWTIMESTAMP); // BT_SetLogEchoMode(g_iLogHandle, BTLE_STDERR | BTLE_DBGOUT); // // PCTSTR pszLogFileName = BT_GetLogFileName(g_iLogHandle); 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) { auto path = App::I.data_path + "/recovery.ppi"; Canvas::I->project_save_thread(path); 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(hWnd, message, "File Recovery", MB_OK | MB_ICONWARNING); } LogRemote::I.file_close(); }, 0); } // create a reverse map from kKey to VK_XXX void init_vk_map() { for (int k = 1; k < 256; k++) // ignore kKey::Unknown = 0 { for (int vk = 0; vk < 256; vk++) { if (k == (int)convert_key(vk)) { if (vkey_map.find((kKey)k) == vkey_map.end()) { vkey_map.insert({ (kKey)k, vk }); } else { LOG("KEY MAP COLLISION %d and %d maps to", (int)vk, (int)vkey_map[(kKey)k], (int)k); } } } } } std::mutex hmd_render_mutex; std::condition_variable hmd_render_cv; Vive* vive = nullptr; bool win32_vr_start() { if (sandboxed) return false; vive = new Vive; vive->on_draw = [](const glm::mat4& proj, const glm::mat4& view, const glm::mat4& pose) { App::I.vr_draw(proj, view, pose); }; async_lock(); if (!vive->Initialize()) { delete vive; vive = nullptr; LOG("VR: failed to initialize vive"); async_unlock(); return false; } async_unlock(); hmd_renderer = std::thread([&] { if (!vive) return; BT_SetTerminate(); LOG("start hmd render thread"); App::I.has_vr = true; vr_running = true; bool trigger_down = false; cbuffer controller_points(10); glm::vec3 controller_last_point; vive->on_analog_button = [&](const ViveController& c, ViveController::kButton b, ViveController::kAction a) { if (b == ViveController::kButton::Trigger) { if (a == ViveController::kAction::Press) { glm::vec3 pos = glm::normalize(xyz(vive->m_controllers[0].m_mat[3])) * 800.f; float force = vive->m_controllers[0].axis(b).x; async_lock(); Canvas::I->stroke_start(pos, force); async_unlock(); controller_last_point = pos; controller_points.clear(); trigger_down = true; } if (a == ViveController::kAction::Release) { trigger_down = false; async_lock(); Canvas::I->stroke_end(); async_unlock(); } } }; const float target_tick_rate = 90; unsigned long t0 = GetTickCount(); while (vr_running && running == 1 && vive->Valid()) { std::unique_lock lock(hmd_render_mutex); unsigned long t1 = GetTickCount(); float dt = (float)(t1 - t0) / 1000.0f; vive->Update(); App::I.vr_active = vive->m_active; App::I.vr_controller = vive->m_controllers[0].m_mat; App::I.vr_controller_pos = glm::normalize(xyz(vive->m_controllers[0].m_mat[3])); if (trigger_down) { controller_points.add(App::I.vr_controller_pos * 800.f); auto p = controller_points.average(); if (glm::distance(p, controller_last_point) > 1) { async_lock(); Canvas::I->stroke_update(p, vive->m_controllers[0].axis(ViveController::kButton::Trigger).x); async_unlock(); controller_last_point = p; App::I.redraw = true; } } if (vr_running && vive->m_active) { async_lock(); vive->Draw(); async_unlock(); } const int framerate = (1.f / target_tick_rate) * 1000; const int diff = framerate - (t1 - t0); hmd_render_cv.wait_for(lock, std::chrono::milliseconds(diff)); } App::I.vr_active = false; App::I.has_vr = false; vr_running = false; if (async_lock_try()) { vive->Terminate(); async_unlock(); } LOG("hmd renderer terminated"); }); return true; } void win32_vr_stop() { if (vive) { vr_running = false; if (hmd_renderer.joinable()) hmd_renderer.join(); vive->Terminate(); delete vive; vive = nullptr; } } void win32_save_window_state() { WINDOWPLACEMENT p; GetWindowPlacement(hWnd, &p); Settings::set("window-show-cmd", Serializer::Integer(p.showCmd)); Settings::set("window-rect", Serializer::IVec4({ p.rcNormalPosition.left, p.rcNormalPosition.top, p.rcNormalPosition.right, p.rcNormalPosition.bottom })); } int main(int argc, char** argv) { WNDCLASS wc; PIXELFORMATDESCRIPTOR pfd; SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); App::I.initLog(); init_vk_map(); /* if (!App::I.check_license()) { MessageBoxA(NULL, "Unable to verify this demo license, please make sure you are connected to Internet.", "PanoPainter - License Error", MB_ICONERROR | MB_OK); return -1; } */ 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); } else { fclose(fp_check); LOG("data files ok"); } SetupExceptionHandler(); BT_SetTerminate(); read_WMI_info(); App::I.create(); // Inizialize data structures to zero memset(&wc, 0, sizeof(wc)); memset(&keys, 0, sizeof(keys)); memset(&pfd, 0, sizeof(pfd)); // Create the main window hInst = GetModuleHandle(NULL); className = L"EngineMain"; wc.hInstance = hInst; wc.lpfnWndProc = (WNDPROC)WndProc; wc.lpszClassName = className; wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClass(&wc); auto monitor = MonitorFromWindow(0, MONITOR_DEFAULTTOPRIMARY); auto x = unsigned{}; auto y = unsigned{}; GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &x, &y); App::I.zoom *= (float)x / 96.f; RECT clientRect = { 0, 0, (int)App::I.width * App::I.zoom, (int)App::I.height * App::I.zoom }; POINT clientPos = { CW_USEDEFAULT, CW_USEDEFAULT }; if (Settings::has("window-rect")) { auto wnd_rect = Settings::value("window-rect"); App::I.width = wnd_rect.z - wnd_rect.x; App::I.height = wnd_rect.w - wnd_rect.y; clientRect = { wnd_rect.x, wnd_rect.y, wnd_rect.z, wnd_rect.w }; clientPos = { wnd_rect.x, wnd_rect.y }; } else { AdjustWindowRect(&clientRect, WS_OVERLAPPEDWINDOW, false); } hWnd = CreateWindow(wc.lpszClassName, L"PanoPainter", WS_OVERLAPPEDWINDOW, clientPos.x, clientPos.y, (float)(clientRect.right - clientRect.left), (float)(clientRect.bottom - clientRect.top), 0, 0, hInst, 0); // Setup GL Rendering Context 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.cDepthBits = 16; pfd.iLayerType = PFD_MAIN_PLANE; hDC = GetDC(hWnd); int pxfmt = ChoosePixelFormat(hDC, &pfd); SetPixelFormat(hDC, pxfmt, &pfd); hRC = wglCreateContext(hDC); // Create OpenGL 2.1 or less wglMakeCurrent(hDC, hRC); // Initialize extensions if (glewInit() != GLEW_OK) return 0; LOG("GL version: %s", glGetString(GL_VERSION)); LOG("GL vendor: %s", glGetString(GL_VENDOR)); LOG("GL renderer: %s", glGetString(GL_RENDERER)); static wchar_t window_title[512]; swprintf_s(window_title, L"PanoPainter %s (%s)", g_version_number_w, str2wstr((char*)glGetString(GL_RENDERER)).c_str()); // If supported create a 3.1 context if (wglewIsSupported("WGL_ARB_create_context")) { int contex_attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 1, WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 0 }; int pixel_attribs[] = { WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, WGL_SUPPORT_OPENGL_ARB, GL_TRUE, WGL_DOUBLE_BUFFER_ARB, GL_TRUE, WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_COLOR_BITS_ARB, 32, WGL_DEPTH_BITS_ARB, 24, WGL_STENCIL_BITS_ARB, 8, WGL_SAMPLE_BUFFERS_ARB, 1, // Number of buffers (must be 1 at time of writing) WGL_SAMPLES_ARB, 4, // Number of samples 0 }; UINT numFormat; wglMakeCurrent(NULL, NULL); wglDeleteContext(hRC); DestroyWindow(hWnd); hWnd = CreateWindow(wc.lpszClassName, window_title, WS_OVERLAPPEDWINDOW, clientPos.x, clientPos.y, (float)(clientRect.right - clientRect.left), (float)(clientRect.bottom - clientRect.top), 0, 0, hInst, 0); hDC = GetDC(hWnd); wglChoosePixelFormatARB(hDC, pixel_attribs, nullptr, 1, &pxfmt, &numFormat); SetPixelFormat(hDC, pxfmt, &pfd); hRC = wglCreateContextAttribsARB(hDC, NULL, contex_attribs); wglMakeCurrent(hDC, hRC); } else { // If not supported, go fuck yourself we are not gonna support your shitty device return -1; // A negative number because you are a negative one } bool start_in_vr = false; if (argc > 1) { switch (const_hash(argv[1])) { case const_hash("convert"): App::I.initShaders(); App::I.cmd_convert(argv[2], argv[3]); return 0; case const_hash("-vrmode"): start_in_vr = true; break; default: break; } } // link: https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-registertouchwindow if (RegisterTouchWindow(hWnd, 0) == 0) { LOG("RegisterTouchWindow error: %s", GetLastErrorAsString().c_str()); } async_lock(); LOG("init app"); App::I.init(); int show_cmd = SW_NORMAL; Settings::value("window-show-cmd", show_cmd); LOG("show main window"); ShowWindow(hWnd, show_cmd); if (!sandboxed) { LOG("init WinTab"); WacomTablet::I.init(hWnd); } else { LOG("SKIP init WinTab"); } async_unlock(); LOG("change icon"); SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON1))); running = 1; renderer = std::thread([&] { BT_SetTerminate(); LOG("start render thread"); const float target_fps = 10; const float target_tick_rate = 60; unsigned long t0 = GetTickCount(); unsigned long t1; int frames = 0; float one_sec = 0; float render_timer = 0; float frame_timer = 0; while(running == 1) { t1 = GetTickCount(); float dt = (float)(t1 - t0) / 1000.0f; one_sec += dt; render_timer += dt; frame_timer += dt; timer_stylus += dt; timer_ink_touch += dt; timer_ink_pen += dt; t0 = t1; if (one_sec > 1.f) { static wchar_t title_fps[512]; swprintf_s(title_fps, L"%s - %d fps", window_title, frames); std::lock_guard lock(main_task_mutex); main_tasklist.emplace_back([=] { SetWindowText(hWnd, title_fps); }); one_sec = 0; frames = 0; } { std::deque> working_list; { std::lock_guard lock(task_mutex); working_list = std::move(tasklist); } if (!working_list.empty()) { async_lock(); while (!working_list.empty()) { working_list.front()(); working_list.pop_front(); } async_unlock(); //LOG("clear"); //WacomTablet::I.m_stylus = false; //WacomTablet::I.m_eraser = false; } } App::I.tick(dt); std::unique_lock lock(render_mutex); if (render_timer > 1.0f / target_fps) { App::I.redraw = true; render_timer = 0; } if (timer_stylus > 0.1 && (WacomTablet::I.m_stylus || WacomTablet::I.m_eraser)) { WacomTablet::I.m_stylus = false; WacomTablet::I.m_eraser = false; App::I.redraw = true; } if (timer_ink_pen > 0.1 && WacomTablet::I.m_ink_pen) { WacomTablet::I.m_ink_pen = false; App::I.redraw = true; } if (timer_ink_touch > 0.1 && WacomTablet::I.m_ink_touch) { WacomTablet::I.m_ink_touch = false; App::I.redraw = true; } if (App::I.redraw) { async_lock(); App::I.redraw = true; glBindFramebuffer(GL_FRAMEBUFFER, 0); App::I.clear(); App::I.update(frame_timer); SwapBuffers(hDC); async_unlock(); frame_timer = 0; //LOG("swap main"); frames++; } const int framerate = (1.f / target_tick_rate) * 1000; const int diff = framerate - (t1 - t0); render_cv.wait_for(lock, std::chrono::milliseconds(diff)); } LOG("renderer terminated"); }); SetTimer(hWnd, 1, 500, NULL); if (start_in_vr) App::I.vr_start(); MSG msg; LOG("start main loop"); while (running == 1) { // If there any message in the queue process it auto present = App::I.animate || App::I.redraw ? PeekMessage(&msg, 0, 0, 0, PM_REMOVE) : GetMessage(&msg, 0, 0, 0); if (msg.message == WM_QUIT) running = 0; if (present) { DispatchMessage(&msg); TranslateMessage(&msg); } // list of tasks for the main thread { std::deque> working_list; { std::lock_guard lock(main_task_mutex); working_list = std::move(main_tasklist); } if (!working_list.empty()) { while (!working_list.empty()) { working_list.front()(); working_list.pop_front(); } } } if (!tasklist.empty()) render_cv.notify_all(); } // Clean up WacomTablet::I.terminate(); DestroyWindow(hWnd); UnregisterClass(className, hInst); LogRemote::I.stop(); } LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { static bool leftDown = false; static DWORD lastTime; static glm::vec2 lastPoint; auto extra = GetMessageExtraInfo(); // if ((extra & 0xFFFFFF00) == 0xFF515700) // LOG("source %s", extra & 0x80 ? "touch" : "pen"); switch (msg) { case WM_DESTROY: if (running != -1) { running = 0; render_cv.notify_all(); if (renderer.joinable()) renderer.join(); if (hmd_renderer.joinable()) hmd_renderer.join(); App::I.terminate(); } break; case WM_USER_CLOSE: PostQuitMessage(0); break; case WM_PAINT: App::I.redraw = true; break; case WM_CREATE: BT_SetTerminate(); break; case WM_CLOSE: if (App::I.request_close()) PostQuitMessage(0); else return true; break; case WM_SIZE: { auto w = (float)LOWORD(lp); auto h = (float)HIWORD(lp); if (h != 0) { async_locker lock; App::I.resize(w, h); App::I.clear(); App::I.redraw = true; App::I.update(0.f); SwapBuffers(hDC); } break; } case WM_ACTIVATE: { std::lock_guard lock(task_mutex); tasklist.emplace_back([=] { int active = GET_WM_ACTIVATE_STATE(wp, lp); WacomTablet::I.set_focus(active); static BYTE keys[256]; if (GetKeyboardState(keys)) { bool alt = keys[VK_MENU] & 0x80; for (auto k : vkey_map) { // ignore alt + tab if (alt && k.first == kKey::KeyTab) continue; bool down = keys[k.second] & 0x80; if (App::I.keys[(int)k.first] && !down) App::I.key_up(k.first); else if(!App::I.keys[(int)k.first] && down) App::I.key_down(k.first); } } }); break; } // case WM_TOUCH: // { // std::lock_guard lock(task_mutex); // tasklist.emplace_back([=] { // //LOG("touch"); // }); // break; // } case WT_PACKET: { App::I.set_stylus(); timer_stylus = 0; std::lock_guard lock(task_mutex); tasklist.emplace_back([=] { WacomTablet::I.handle_message(hWnd, msg, wp, lp); }); break; } case WM_SYSKEYDOWN: case WM_KEYDOWN: if ((lp >> 30 & 1) == 0 && // ignore repeated !(wp == VK_TAB && App::I.keys[(int)kKey::KeyAlt])) // ignore alt+tab { std::lock_guard lock(task_mutex); tasklist.emplace_back([wp] { App::I.key_down(convert_key((int)wp)); }); } break; case WM_SYSKEYUP: case WM_KEYUP: if (!(wp == VK_TAB && App::I.keys[(int)kKey::KeyAlt])) // ignore alt+tab { std::lock_guard lock(task_mutex); tasklist.emplace_back([wp] { App::I.key_up(convert_key((int)wp)); }); } break; case WM_CHAR: { std::lock_guard lock(task_mutex); tasklist.emplace_back([wp]{ App::I.key_char((int)wp); }); } break; case WM_MOUSEMOVE: { /* RECT r; POINT curpos; GetWindowRect(hWnd, &r); glm::vec2 center = { r.left + (r.right - r.left) / 2, r.top + (r.bottom - r.top) / 2 }; GetCursorPos(&curpos); SetCursorPos(center.x, center.y); //glm::vec2 cur = { GET_X_LPARAM(lp), GET_Y_LPARAM(lp) }; glm::vec2 sz = { App::I.width, App::I.height }; glm::vec2 diff = { curpos.x - center.x, curpos.y - center.y }; lastPoint = glm::clamp(lastPoint + diff, { 0, 0 }, sz); */ lastPoint = { GET_X_LPARAM(lp), GET_Y_LPARAM(lp) }; auto pt = lastPoint; std::lock_guard lock(task_mutex); tasklist.emplace_back([pt, extra, p = WacomTablet::I.get_pressure()]{ kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I.mouse_move((float)pt.x, (float)pt.y, p, pointer_source, WacomTablet::I.m_eraser); }); } break; case WM_LBUTTONDOWN: { SetCapture(hWnd); std::lock_guard lock(task_mutex); auto pt = lastPoint; tasklist.emplace_back([pt, extra, hWnd, p = WacomTablet::I.get_pressure()]{ kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I.mouse_down(0, (float)pt.x, (float)pt.y, p, pointer_source, WacomTablet::I.m_eraser); }); } break; case WM_LBUTTONUP: { ReleaseCapture(); std::lock_guard lock(task_mutex); auto pt = lastPoint; tasklist.emplace_back([pt, extra] { WacomTablet::I.reset_pressure(); kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I.mouse_up(0, (float)pt.x, (float)pt.y, pointer_source, WacomTablet::I.m_eraser); }); } break; case WM_RBUTTONDOWN: { SetCapture(hWnd); std::lock_guard lock(task_mutex); auto pt = lastPoint; tasklist.emplace_back([pt, extra, hWnd] { kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I.mouse_down(1, (float)pt.x, (float)pt.y, 1.f, pointer_source, 0); }); } break; case WM_RBUTTONUP: { ReleaseCapture(); std::lock_guard lock(task_mutex); auto pt = lastPoint; tasklist.emplace_back([pt, extra] { kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I.mouse_up(1, (float)pt.x, (float)pt.y, pointer_source, 0); }); } break; case WM_MOUSEWHEEL: { async_locker lock; POINT pt; pt.x = GET_X_LPARAM(lp); pt.y = GET_Y_LPARAM(lp); ScreenToClient(hWnd, &pt); { std::lock_guard lock(task_mutex); tasklist.emplace_back([pt, wp] { App::I.mouse_scroll((float)pt.x, (float)pt.y, (float)GET_WHEEL_DELTA_WPARAM(wp) / (float)WHEEL_DELTA); }); } break; } case WM_POINTERUPDATE: { POINTER_TOUCH_INFO touchInfo; POINTER_PEN_INFO penInfo; POINTER_INFO pointerInfo; UINT32 pointerId = GET_POINTERID_WPARAM(wp); POINTER_INPUT_TYPE pointerType = PT_POINTER; // Retrieve common pointer information if (!GetPointerInfo(pointerId, &pointerInfo)) { // failure, call GetLastError() } else { // success, process pointerInfo if (!GetPointerType(pointerId, &pointerType)) { // failure, call GetLastError() // set PT_POINTER to fall to default case below pointerType = PT_POINTER; } switch (pointerType) { case PT_TOUCH: // Retrieve touch information if (!GetPointerTouchInfo(pointerId, &touchInfo)) { // failure, call GetLastError() } else { // success, process touchInfo // mark as handled to skip call to DefWindowProc //fHandled = TRUE; timer_ink_touch = 0; WacomTablet::I.m_ink_touch = true; WacomTablet::I.m_pen_pres = 1; } break; case PT_PEN: // Retrieve pen information if (!GetPointerPenInfo(pointerId, &penInfo)) { // failure, call GetLastError() } else { // success, process penInfo // mark as handled to skip call to DefWindowProc //fHandled = TRUE; //penInfo.pressure timer_ink_pen = 0; WacomTablet::I.m_ink_pen = true; WacomTablet::I.m_pen_pres = (float)penInfo.pressure / 1024.f; App::I.set_stylus(); } break; default: if (!GetPointerInfo(pointerId, &pointerInfo)) { // failure. } else { // success, proceed with pointerInfo. //fHandled = HandleGenericPointerInfo(&pointerInfo); } break; } } break; } } // avoid annoying alt system menu if (wp == SC_KEYMENU && (lp >> 16) <= 0) return 0; return DefWindowProc(hWnd, msg, wp, lp); } int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { LPWSTR *wargs = nullptr; char** argv = nullptr; int argc = 0; // convert args from char to wchar wargs = CommandLineToArgvW(GetCommandLine(), &argc); argv = new char*[argc + 1]; for (int i = 0; i < argc; i++) { auto len = wcslen(wargs[i]) + 1; argv[i] = new char[len]; wcstombs_s(nullptr, argv[i], len, wargs[i], len); } LocalFree(wargs); return main(argc, argv); }