#include "lib/win/Win32Common.hpp" #include #include #include #include #include #if _WIN32_WINNT_WINBLUE PFN_GetScaleFactorForMonitor MyGetScaleFactorForMonitor = nullptr; #endif #if _DEBUG #define D3D11_CREATE_DEVICE_FLAGS D3D11_CREATE_DEVICE_DEBUG #else #define D3D11_CREATE_DEVICE_FLAGS 0 #endif #include #include #include #include "boo/IApplication.hpp" #include "boo/System.hpp" #include "boo/graphicsdev/D3D.hpp" #include "boo/inputdev/DeviceFinder.hpp" #include #if BOO_HAS_VULKAN #include "boo/graphicsdev/Vulkan.hpp" #endif DWORD g_mainThreadId = 0; std::mutex g_nwmt; std::condition_variable g_nwcv; static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); pD3DCompile D3DCompilePROC = nullptr; pD3DCreateBlob D3DCreateBlobPROC = nullptr; pD3DPERF_BeginEvent D3DPERF_BeginEventPROC = nullptr; pD3DPERF_EndEvent D3DPERF_EndEventPROC = nullptr; static bool FindBestD3DCompile() { HMODULE d3dCompilelib = LoadLibraryW(L"D3DCompiler_47.dll"); if (!d3dCompilelib) { d3dCompilelib = LoadLibraryW(L"D3DCompiler_46.dll"); if (!d3dCompilelib) { d3dCompilelib = LoadLibraryW(L"D3DCompiler_45.dll"); if (!d3dCompilelib) { d3dCompilelib = LoadLibraryW(L"D3DCompiler_44.dll"); if (!d3dCompilelib) { d3dCompilelib = LoadLibraryW(L"D3DCompiler_43.dll"); } } } } if (d3dCompilelib) { D3DCompilePROC = (pD3DCompile)GetProcAddress(d3dCompilelib, "D3DCompile"); D3DCreateBlobPROC = (pD3DCreateBlob)GetProcAddress(d3dCompilelib, "D3DCreateBlob"); return D3DCompilePROC != nullptr && D3DCreateBlobPROC != nullptr; } return false; } static bool FindD3DPERF() { HMODULE d3d9lib = LoadLibraryW(L"d3d9.dll"); if (d3d9lib) { D3DPERF_BeginEventPROC = (pD3DPERF_BeginEvent)GetProcAddress(d3d9lib, "D3DPERF_BeginEvent"); D3DPERF_EndEventPROC = (pD3DPERF_EndEvent)GetProcAddress(d3d9lib, "D3DPERF_EndEvent"); return D3DPERF_BeginEventPROC != nullptr && D3DPERF_EndEventPROC != nullptr; } return false; } namespace boo { static logvisor::Module Log("boo::ApplicationWin32"); Win32Cursors WIN32_CURSORS; std::shared_ptr _WindowWin32New(SystemStringView title, Boo3DAppContextWin32& d3dCtx); class ApplicationWin32 final : public IApplication { IApplicationCallback& m_callback; const SystemString m_uniqueName; const SystemString m_friendlyName; const SystemString m_pname; const std::vector m_args; std::unordered_map> m_allWindows; Boo3DAppContextWin32 m_3dCtx; #if BOO_HAS_VULKAN PFN_vkGetInstanceProcAddr m_getVkProc = nullptr; #endif void _deletedWindow(IWindow* window) override { m_allWindows.erase(HWND(window->getPlatformHandle())); } public: ApplicationWin32(IApplicationCallback& callback, SystemStringView uniqueName, SystemStringView friendlyName, SystemStringView pname, const std::vector& args, std::string_view gfxApi, uint32_t samples, uint32_t anisotropy, bool deepColor, bool singleInstance) : m_callback(callback), m_uniqueName(uniqueName), m_friendlyName(friendlyName), m_pname(pname), m_args(args) { m_3dCtx.m_ctx11.m_sampleCount = samples; m_3dCtx.m_ctx11.m_anisotropy = anisotropy; m_3dCtx.m_ctx11.m_fbFormat = deepColor ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM; m_3dCtx.m_ctxOgl.m_glCtx.m_sampleCount = samples; m_3dCtx.m_ctxOgl.m_glCtx.m_anisotropy = anisotropy; m_3dCtx.m_ctxOgl.m_glCtx.m_deepColor = deepColor; #if BOO_HAS_VULKAN g_VulkanContext.m_sampleCountColor = samples; g_VulkanContext.m_sampleCountDepth = samples; g_VulkanContext.m_anisotropy = anisotropy; g_VulkanContext.m_deepColor = deepColor; #endif HMODULE dxgilib = LoadLibraryW(L"dxgi.dll"); if (!dxgilib) Log.report(logvisor::Fatal, FMT_STRING("unable to load dxgi.dll")); using CreateDXGIFactory1PROC = HRESULT(WINAPI*)(REFIID riid, _COM_Outptr_ void** ppFactory); auto MyCreateDXGIFactory1 = (CreateDXGIFactory1PROC)GetProcAddress(dxgilib, "CreateDXGIFactory1"); if (!MyCreateDXGIFactory1) Log.report(logvisor::Fatal, FMT_STRING("unable to find CreateDXGIFactory1 in DXGI.dll\n")); bool noD3d = false; #if BOO_HAS_VULKAN bool useVulkan = false; #endif if (!gfxApi.empty()) { #if BOO_HAS_VULKAN if (!gfxApi.compare("Vulkan")) { noD3d = true; useVulkan = true; } if (!gfxApi.compare("OpenGL")) { noD3d = true; useVulkan = false; } #else if (!gfxApi.compare("OpenGL")) noD3d = true; #endif } for (const SystemString& arg : args) { #if BOO_HAS_VULKAN if (!arg.compare(L"--d3d11")) { useVulkan = false; noD3d = false; } if (!arg.compare(L"--vulkan")) { noD3d = true; useVulkan = true; } if (!arg.compare(L"--gl")) { noD3d = true; useVulkan = false; } #else if (!arg.compare(L"--d3d11")) noD3d = false; if (!arg.compare(L"--gl")) noD3d = true; #endif } HMODULE d3d11lib = nullptr; if (!noD3d) d3d11lib = LoadLibraryW(L"D3D11.dll"); if (d3d11lib) { if (!FindBestD3DCompile()) Log.report(logvisor::Fatal, FMT_STRING("unable to find D3DCompile_[43-47].dll")); if (!FindD3DPERF()) Log.report(logvisor::Fatal, FMT_STRING("unable to find d3d9.dll")); /* Create device proc */ PFN_D3D11_CREATE_DEVICE MyD3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE)GetProcAddress(d3d11lib, "D3D11CreateDevice"); if (!MyD3D11CreateDevice) Log.report(logvisor::Fatal, FMT_STRING("unable to find D3D11CreateDevice in D3D11.dll")); /* Create device */ D3D_FEATURE_LEVEL level = D3D_FEATURE_LEVEL_11_0; ComPtr tempDev; ComPtr tempCtx; if (FAILED(MyD3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_FLAGS, &level, 1, D3D11_SDK_VERSION, &tempDev, nullptr, &tempCtx))) Log.report(logvisor::Fatal, FMT_STRING("unable to create D3D11 device")); ComPtr device; if (FAILED(tempDev.As(&m_3dCtx.m_ctx11.m_dev)) || !m_3dCtx.m_ctx11.m_dev || FAILED(tempCtx.As(&m_3dCtx.m_ctx11.m_devCtx)) || !m_3dCtx.m_ctx11.m_devCtx || FAILED(m_3dCtx.m_ctx11.m_dev.As(&device)) || !device) { MessageBoxW(nullptr, L"Windows 7 users should install 'Platform Update for Windows 7':\n" L"https://www.microsoft.com/en-us/download/details.aspx?id=36805", L"IDXGIDevice2 interface error", MB_OK | MB_ICONERROR); exit(1); } /* Obtain DXGI Factory */ ComPtr adapter; device->GetParent(__uuidof(IDXGIAdapter), &adapter); adapter->GetParent(__uuidof(IDXGIFactory2), &m_3dCtx.m_ctx11.m_dxFactory); m_3dCtx.m_ctx11.m_anisotropy = std::min(m_3dCtx.m_ctx11.m_anisotropy, uint32_t(16)); /* Build default sampler here */ CD3D11_SAMPLER_DESC sampDesc(D3D11_DEFAULT); sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; sampDesc.Filter = D3D11_FILTER_ANISOTROPIC; sampDesc.MaxAnisotropy = m_3dCtx.m_ctx11.m_anisotropy; m_3dCtx.m_ctx11.m_dev->CreateSamplerState(&sampDesc, &m_3dCtx.m_ctx11.m_ss[0]); sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER; sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER; sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER; m_3dCtx.m_ctx11.m_dev->CreateSamplerState(&sampDesc, &m_3dCtx.m_ctx11.m_ss[1]); sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER; sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER; sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER; std::fill(std::begin(sampDesc.BorderColor), std::end(sampDesc.BorderColor), 0.f); m_3dCtx.m_ctx11.m_dev->CreateSamplerState(&sampDesc, &m_3dCtx.m_ctx11.m_ss[2]); sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; m_3dCtx.m_ctx11.m_dev->CreateSamplerState(&sampDesc, &m_3dCtx.m_ctx11.m_ss[3]); sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; m_3dCtx.m_ctx11.m_dev->CreateSamplerState(&sampDesc, &m_3dCtx.m_ctx11.m_ss[4]); Log.report(logvisor::Info, FMT_STRING("initialized D3D11 renderer")); return; } #if BOO_HAS_VULKAN if (useVulkan) { HMODULE vulkanLib = LoadLibraryW(L"vulkan-1.dll"); if (vulkanLib) { m_getVkProc = (PFN_vkGetInstanceProcAddr)GetProcAddress(vulkanLib, "vkGetInstanceProcAddr"); if (m_getVkProc) { /* Check device support for vulkan */ if (g_VulkanContext.m_instance == VK_NULL_HANDLE) { auto appName = getUniqueName(); if (g_VulkanContext.initVulkan(WCSTMBS(appName.data()).c_str(), m_getVkProc)) { if (g_VulkanContext.enumerateDevices()) { /* Obtain DXGI Factory */ HRESULT hr = MyCreateDXGIFactory1(__uuidof(IDXGIFactory1), &m_3dCtx.m_vulkanDxFactory); if (FAILED(hr)) Log.report(logvisor::Fatal, FMT_STRING("unable to create DXGI factory")); Log.report(logvisor::Info, FMT_STRING("initialized Vulkan renderer")); return; } } } } } } #endif /* Finally try OpenGL */ { /* Obtain DXGI Factory */ HRESULT hr = MyCreateDXGIFactory1(__uuidof(IDXGIFactory1), &m_3dCtx.m_ctxOgl.m_dxFactory); if (FAILED(hr)) Log.report(logvisor::Fatal, FMT_STRING("unable to create DXGI factory")); Log.report(logvisor::Info, FMT_STRING("initialized OpenGL renderer")); return; } Log.report(logvisor::Fatal, FMT_STRING("system doesn't support Vulkan, D3D11, or OpenGL")); } EPlatformType getPlatformType() const override { return EPlatformType::Win32; } LRESULT winHwndHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { /* Lookup boo window instance */ auto search = m_allWindows.find(hwnd); if (search == m_allWindows.end()) return DefWindowProc(hwnd, uMsg, wParam, lParam); ; std::shared_ptr window = search->second.lock(); if (!window) return DefWindowProc(hwnd, uMsg, wParam, lParam); switch (uMsg) { case WM_CREATE: return 0; case WM_DEVICECHANGE: return DeviceFinder::winDevChangedHandler(wParam, lParam); case WM_CLOSE: case WM_SIZE: case WM_MOVING: case WM_SYSKEYDOWN: case WM_KEYDOWN: case WM_SYSKEYUP: case WM_KEYUP: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_XBUTTONDOWN: case WM_XBUTTONUP: case WM_MOUSEMOVE: case WM_MOUSELEAVE: case WM_NCMOUSELEAVE: case WM_MOUSEHOVER: case WM_NCMOUSEHOVER: case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: case WM_CHAR: case WM_UNICHAR: { HWNDEvent eventData(uMsg, wParam, lParam); window->_incomingEvent(&eventData); } default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } } template static void DoSetFullscreen(W& win, bool fs) { std::lock_guard lk(g_nwmt); if (fs) { win.m_fsStyle = GetWindowLong(win.m_hwnd, GWL_STYLE); win.m_fsExStyle = GetWindowLong(win.m_hwnd, GWL_EXSTYLE); GetWindowRect(win.m_hwnd, &win.m_fsRect); SetWindowLongPtr(win.m_hwnd, GWL_STYLE, win.m_fsStyle & ~(WS_CAPTION | WS_THICKFRAME)); SetWindowLongPtr(win.m_hwnd, GWL_EXSTYLE, win.m_fsExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)); MONITORINFO monitor_info; monitor_info.cbSize = sizeof(monitor_info); GetMonitorInfo(MonitorFromWindow(win.m_hwnd, MONITOR_DEFAULTTONEAREST), &monitor_info); SetWindowPos(win.m_hwnd, nullptr, monitor_info.rcMonitor.left, monitor_info.rcMonitor.top, monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); win.m_fs = true; } else { SetWindowLongPtr(win.m_hwnd, GWL_STYLE, win.m_fsStyle); SetWindowLongPtr(win.m_hwnd, GWL_EXSTYLE, win.m_fsExStyle); SetWindowPos(win.m_hwnd, nullptr, win.m_fsRect.left, win.m_fsRect.top, win.m_fsRect.right - win.m_fsRect.left, win.m_fsRect.bottom - win.m_fsRect.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); win.m_fs = false; } g_nwcv.notify_one(); } int run() override { g_mainThreadId = GetCurrentThreadId(); /* Spawn client thread */ int clientReturn = 0; std::thread clientThread([&]() { std::string thrName = WCSTMBS(getFriendlyName().data()) + " Client Thread"; logvisor::RegisterThreadName(thrName.c_str()); CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); clientReturn = m_callback.appMain(this); PostThreadMessageW(g_mainThreadId, WM_USER + 1, 0, 0); }); /* Pump messages */ MSG msg = {}; while (GetMessage(&msg, nullptr, 0, 0)) { if (!msg.hwnd) { /* PostThreadMessage events */ switch (msg.message) { case WM_USER: { /* New-window message (coalesced onto main thread) */ std::lock_guard lk(g_nwmt); SystemStringView* title = reinterpret_cast(msg.wParam); m_mwret = newWindow(*title); g_nwcv.notify_one(); continue; } case WM_USER + 1: /* Quit message from client thread */ PostQuitMessage(0); continue; case WM_USER + 2: /* SetCursor call from client thread */ SetCursor(HCURSOR(msg.wParam)); continue; case WM_USER + 3: /* ImmSetOpenStatus call from client thread */ ImmSetOpenStatus(HIMC(msg.wParam), BOOL(msg.lParam)); continue; case WM_USER + 4: /* ImmSetCompositionWindow call from client thread */ ImmSetCompositionWindow(HIMC(msg.wParam), LPCOMPOSITIONFORM(msg.lParam)); continue; case WM_USER + 5: /* SetFullscreen call for OpenGL window */ DoSetFullscreen(*reinterpret_cast(msg.wParam), msg.lParam); continue; #if BOO_HAS_VULKAN case WM_USER + 6: /* SetFullscreen call for Vulkan window */ DoSetFullscreen(*reinterpret_cast(msg.wParam), msg.lParam); continue; #endif default: break; } } TranslateMessage(&msg); DispatchMessage(&msg); } m_callback.appQuitting(this); clientThread.join(); return clientReturn; } ~ApplicationWin32() override { for (auto& p : m_allWindows) if (auto w = p.second.lock()) w->_cleanup(); } SystemStringView getUniqueName() const override { return m_uniqueName; } SystemStringView getFriendlyName() const override { return m_friendlyName; } SystemStringView getProcessName() const override { return m_pname; } const std::vector& getArgs() const override { return m_args; } std::shared_ptr m_mwret; std::shared_ptr newWindow(SystemStringView title) override { if (GetCurrentThreadId() != g_mainThreadId) { std::unique_lock lk(g_nwmt); if (!PostThreadMessageW(g_mainThreadId, WM_USER, WPARAM(&title), 0)) Log.report(logvisor::Fatal, FMT_STRING("PostThreadMessage error")); g_nwcv.wait(lk); std::shared_ptr ret = std::move(m_mwret); m_mwret.reset(); return ret; } std::shared_ptr window = _WindowWin32New(title, m_3dCtx); HWND hwnd = HWND(window->getPlatformHandle()); m_allWindows[hwnd] = window; return window; } }; IApplication* APP = nullptr; int ApplicationRun(IApplication::EPlatformType platform, IApplicationCallback& cb, SystemStringView uniqueName, SystemStringView friendlyName, SystemStringView pname, const std::vector& args, std::string_view gfxApi, uint32_t samples, uint32_t anisotropy, bool deepColor, bool singleInstance) { std::string thrName = WCSTMBS(friendlyName.data()) + " Main Thread"; logvisor::RegisterThreadName(thrName.c_str()); if (APP) return 1; if (platform != IApplication::EPlatformType::Win32 && platform != IApplication::EPlatformType::Auto) return 1; #if _WIN32_WINNT_WINBLUE /* HI-DPI support */ HMODULE shcoreLib = LoadLibraryW(L"Shcore.dll"); if (shcoreLib) MyGetScaleFactorForMonitor = (PFN_GetScaleFactorForMonitor)GetProcAddress(shcoreLib, "GetScaleFactorForMonitor"); #endif WIN32_CURSORS.m_arrow = LoadCursor(nullptr, IDC_ARROW); WIN32_CURSORS.m_hResize = LoadCursor(nullptr, IDC_SIZEWE); WIN32_CURSORS.m_vResize = LoadCursor(nullptr, IDC_SIZENS); WIN32_CURSORS.m_ibeam = LoadCursor(nullptr, IDC_IBEAM); WIN32_CURSORS.m_crosshairs = LoadCursor(nullptr, IDC_CROSS); WIN32_CURSORS.m_wait = LoadCursor(nullptr, IDC_WAIT); /* One class for *all* boo windows */ WNDCLASS wndClass = {0, WindowProc, 0, 0, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"BooWindow"}; wndClass.hIcon = LoadIconW(wndClass.hInstance, MAKEINTRESOURCEW(101)); wndClass.hCursor = WIN32_CURSORS.m_arrow; RegisterClassW(&wndClass); APP = new ApplicationWin32(cb, uniqueName, friendlyName, pname, args, gfxApi, samples, anisotropy, deepColor, singleInstance); int ret = APP->run(); delete APP; APP = nullptr; return ret; } } // namespace boo static const DEV_BROADCAST_DEVICEINTERFACE HOTPLUG_CONF = {sizeof(DEV_BROADCAST_DEVICEINTERFACE), DBT_DEVTYP_DEVICEINTERFACE}; static bool HOTPLUG_REGISTERED = false; static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!HOTPLUG_REGISTERED && uMsg == WM_CREATE) { /* Register hotplug notification with windows */ RegisterDeviceNotification(hwnd, (LPVOID)&HOTPLUG_CONF, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES); HOTPLUG_REGISTERED = true; } return static_cast(boo::APP)->winHwndHandler(hwnd, uMsg, wParam, lParam); }