diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/source/opengl/win | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/opengl/win')
-rw-r--r-- | vcl/source/opengl/win/WinDeviceInfo.cxx | 491 | ||||
-rw-r--r-- | vcl/source/opengl/win/context.cxx | 667 |
2 files changed, 1158 insertions, 0 deletions
diff --git a/vcl/source/opengl/win/WinDeviceInfo.cxx b/vcl/source/opengl/win/WinDeviceInfo.cxx new file mode 100644 index 0000000000..bc8a051002 --- /dev/null +++ b/vcl/source/opengl/win/WinDeviceInfo.cxx @@ -0,0 +1,491 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <opengl/win/WinDeviceInfo.hxx> + +#include <driverblocklist.hxx> +#include <config_folders.h> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <objbase.h> +#include <setupapi.h> +#include <algorithm> +#include <cstdint> +#include <memory> +#include <string_view> + +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <desktop/crashreport.hxx> + +namespace { + +bool GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, OUString& destString, int type) +{ + HKEY key; + DWORD dwcbData; + DWORD dValue; + DWORD resultType; + LONG result; + bool retval = true; + + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key); + if (result != ERROR_SUCCESS) + { + return false; + } + + switch (type) + { + case REG_DWORD: + { + // We only use this for vram size + dwcbData = sizeof(dValue); + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + reinterpret_cast<LPBYTE>(&dValue), &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_DWORD) + { + dValue = dValue / 1024 / 1024; + destString += OUString::number(int32_t(dValue)); + } + else + { + retval = false; + } + break; + } + case REG_MULTI_SZ: + { + // A chain of null-separated strings; we convert the nulls to spaces + WCHAR wCharValue[1024]; + dwcbData = sizeof(wCharValue); + + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + reinterpret_cast<LPBYTE>(wCharValue), &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ) + { + // This bit here could probably be cleaner. + bool isValid = false; + + DWORD strLen = dwcbData/sizeof(wCharValue[0]); + for (DWORD i = 0; i < strLen; i++) + { + if (wCharValue[i] == '\0') + { + if (i < strLen - 1 && wCharValue[i + 1] == '\0') + { + isValid = true; + break; + } + else + { + wCharValue[i] = ' '; + } + } + } + + // ensure wCharValue is null terminated + wCharValue[strLen-1] = '\0'; + + if (isValid) + destString = OUString(o3tl::toU(wCharValue)); + + } + else + { + retval = false; + } + + break; + } + } + RegCloseKey(key); + + return retval; +} + +// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD +// this function is used to extract the id's out of it +uint32_t ParseIDFromDeviceID(const OUString &key, const char *prefix, int length) +{ + OUString id = key.toAsciiUpperCase(); + OUString aPrefix = OUString::fromUtf8(prefix); + int32_t start = id.indexOf(aPrefix); + if (start != -1) + { + id = id.copy(start + aPrefix.getLength(), length); + } + return id.toUInt32(16); +} + +/* Other interesting places for info: + * IDXGIAdapter::GetDesc() + * IDirectDraw7::GetAvailableVidMem() + * e->GetAvailableTextureMem() + * */ + +template<typename T> void appendIntegerWithPadding(OUString& rString, T value, sal_uInt32 nChars) +{ + rString += "0x"; + OUString aValue = OUString::number(value, 16); + sal_Int32 nLength = aValue.getLength(); + sal_uInt32 nPadLength = nChars - nLength; + assert(nPadLength >= 0); + OUStringBuffer aBuffer; + for (sal_uInt32 i = 0; i < nPadLength; ++i) + { + aBuffer.append("0"); + } + rString += aBuffer.makeStringAndClear() + aValue; +} + +#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\" +} + +WinOpenGLDeviceInfo::WinOpenGLDeviceInfo(): + mbHasDualGPU(false), + mbRDP(false) +{ + GetData(); +} + +static OUString getDenylistFile() +{ + OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER); + rtl::Bootstrap::expandMacros(url); + + return url + "/opengl/opengl_denylist_windows.xml"; +} + +bool WinOpenGLDeviceInfo::FindBlocklistedDeviceInList() +{ + return DriverBlocklist::IsDeviceBlocked( getDenylistFile(), DriverBlocklist::VersionType::OpenGL, + maDriverVersion, maAdapterVendorID, maAdapterDeviceID); +} + +namespace { + +OUString getCacheFolder() +{ + OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/"); + rtl::Bootstrap::expandMacros(url); + + osl::Directory::create(url); + + return url; +} + +void writeToLog(SvStream& rStrm, const char* pKey, std::u16string_view rVal) +{ + rStrm.WriteOString(pKey); + rStrm.WriteOString(": "); + rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8)); + rStrm.WriteChar('\n'); +} + +} + +bool WinOpenGLDeviceInfo::isDeviceBlocked() +{ + CrashReporter::addKeyValue("OpenGLVendor", maAdapterVendorID, CrashReporter::AddItem); + CrashReporter::addKeyValue("OpenGLDevice", maAdapterDeviceID, CrashReporter::AddItem); + CrashReporter::addKeyValue("OpenGLDriver", maDriverVersion, CrashReporter::Write); + + SAL_INFO("vcl.opengl", maDriverVersion); + SAL_INFO("vcl.opengl", maDriverDate); + SAL_INFO("vcl.opengl", maDeviceID); + SAL_INFO("vcl.opengl", maAdapterVendorID); + SAL_INFO("vcl.opengl", maAdapterDeviceID); + SAL_INFO("vcl.opengl", maAdapterSubsysID); + SAL_INFO("vcl.opengl", maDeviceKey); + SAL_INFO("vcl.opengl", maDeviceString); + + OUString aCacheFolder = getCacheFolder(); + + OUString aCacheFile(aCacheFolder + "/opengl_device.log"); + SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE|StreamMode::TRUNC); + + writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion); + writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate); + writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID); + writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID); + writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID); + writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID); + writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey); + writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString); + + // Check if the device is blocked from the downloaded blocklist. If not, check + // the static list after that. This order is used so that we can later escape + // out of static blocks (i.e. if we were wrong or something was patched, we + // can back out our static block without doing a release). + if (mbRDP) + { + SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions"); + return true; + } + + return FindBlocklistedDeviceInList(); +} + +void WinOpenGLDeviceInfo::GetData() +{ + DISPLAY_DEVICEW displayDevice; + displayDevice.cb = sizeof(displayDevice); + + int deviceIndex = 0; + + while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0)) + { + if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) + { + break; + } + deviceIndex++; + } + + // make sure the string is null terminated + // (using the term "null" here to mean a zero UTF-16 unit) + if (wcsnlen(displayDevice.DeviceKey, SAL_N_ELEMENTS(displayDevice.DeviceKey)) + == SAL_N_ELEMENTS(displayDevice.DeviceKey)) + { + // we did not find a null + SAL_WARN("vcl.opengl", "string not null terminated"); + return; + } + + /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */ + /* check that DeviceKey begins with DEVICE_KEY_PREFIX */ + /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */ + if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1) != 0) + { + SAL_WARN("vcl.opengl", "incorrect DeviceKey"); + return; + } + + // chop off DEVICE_KEY_PREFIX + maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1; + + maDeviceID = o3tl::toU(displayDevice.DeviceID); + maDeviceString = o3tl::toU(displayDevice.DeviceString); + + if (maDeviceID.isEmpty() && + (maDeviceString == "RDPDD Chained DD" || + (maDeviceString == "RDPUDD Chained DD"))) + { + // we need to block RDP as it does not provide OpenGL 2.1+ + mbRDP = true; + SAL_WARN("vcl.opengl", "RDP => blocked"); + return; + } + + /* create a device information set composed of the current display device */ + HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr, + DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES); + + if (devinfo != INVALID_HANDLE_VALUE) + { + HKEY key; + LONG result; + WCHAR value[255]; + DWORD dwcbData; + SP_DEVINFO_DATA devinfoData; + DWORD memberIndex = 0; + + devinfoData.cbSize = sizeof(devinfoData); + /* enumerate device information elements in the device information set */ + while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) + { + /* get a string that identifies the device's driver key */ + if (SetupDiGetDeviceRegistryPropertyW(devinfo, + &devinfoData, + SPDRP_DRIVER, + nullptr, + reinterpret_cast<PBYTE>(value), + sizeof(value), + nullptr)) + { + OUString driverKey(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value)); + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey.getStr()), 0, KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) + { + /* we've found the driver we're looking for */ + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result == ERROR_SUCCESS) + { + maDriverVersion = OUString(o3tl::toU(value)); + } + else + { + // If the entry wasn't found, assume the worst (0.0.0.0). + maDriverVersion = OUString("0.0.0.0"); + } + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result == ERROR_SUCCESS) + { + maDriverDate = o3tl::toU(value); + } + else + { + // Again, assume the worst + maDriverDate = OUString("01-01-1970"); + } + RegCloseKey(key); + break; + } + } + } + + SetupDiDestroyDeviceInfoList(devinfo); + } + else + { + SAL_WARN("vcl.opengl", "invalid handle value"); + } + + appendIntegerWithPadding(maAdapterVendorID, ParseIDFromDeviceID(maDeviceID, "VEN_", 4), 4); + appendIntegerWithPadding(maAdapterDeviceID, ParseIDFromDeviceID(maDeviceID, "&DEV_", 4), 4); + appendIntegerWithPadding(maAdapterSubsysID, ParseIDFromDeviceID(maDeviceID, "&SUBSYS_", 8), 8); + + // We now check for second display adapter. + + // Device interface class for display adapters. + CLSID GUID_DISPLAY_DEVICE_ARRIVAL; + HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}", + &GUID_DISPLAY_DEVICE_ARRIVAL); + if (hresult == NOERROR) + { + devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL, + nullptr, nullptr, + DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); + + if (devinfo != INVALID_HANDLE_VALUE) + { + HKEY key; + LONG result; + WCHAR value[255]; + DWORD dwcbData; + SP_DEVINFO_DATA devinfoData; + DWORD memberIndex = 0; + devinfoData.cbSize = sizeof(devinfoData); + + OUString aAdapterDriver2; + OUString aDeviceID2; + OUString aDriverVersion2; + OUString aDriverDate2; + uint32_t adapterVendorID2; + uint32_t adapterDeviceID2; + + /* enumerate device information elements in the device information set */ + while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) + { + /* get a string that identifies the device's driver key */ + if (SetupDiGetDeviceRegistryPropertyW(devinfo, + &devinfoData, + SPDRP_DRIVER, + nullptr, + reinterpret_cast<PBYTE>(value), + sizeof(value), + nullptr)) + { + OUString driverKey2(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value)); + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey2.getStr()), 0, KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) + { + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr, + nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result != ERROR_SUCCESS) + { + continue; + } + aDeviceID2 = o3tl::toU(value); + OUString aAdapterVendorID2String; + OUString aAdapterDeviceID2String; + adapterVendorID2 = ParseIDFromDeviceID(aDeviceID2, "VEN_", 4); + appendIntegerWithPadding(aAdapterVendorID2String, adapterVendorID2, 4); + adapterDeviceID2 = ParseIDFromDeviceID(aDeviceID2, "&DEV_", 4); + appendIntegerWithPadding(aAdapterDeviceID2String, adapterDeviceID2, 4); + if (maAdapterVendorID == aAdapterVendorID2String && + maAdapterDeviceID == aAdapterDeviceID2String) + { + RegCloseKey(key); + continue; + } + + // If this device is missing driver information, it is unlikely to + // be a real display adapter. + if (!GetKeyValue(o3tl::toW(driverKey2.getStr()), L"InstalledDisplayDrivers", + aAdapterDriver2, REG_MULTI_SZ)) + { + RegCloseKey(key); + continue; + } + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result != ERROR_SUCCESS) + { + RegCloseKey(key); + continue; + } + aDriverVersion2 = o3tl::toU(value); + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result != ERROR_SUCCESS) + { + RegCloseKey(key); + continue; + } + aDriverDate2 = o3tl::toU(value); + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"Device Description", nullptr, + nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result != ERROR_SUCCESS) + { + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + } + RegCloseKey(key); + if (result == ERROR_SUCCESS) + { + mbHasDualGPU = true; + maDeviceString2 = o3tl::toU(value); + maDeviceID2 = aDeviceID2; + maDeviceKey2 = driverKey2; + maDriverVersion2 = aDriverVersion2; + maDriverDate2 = aDriverDate2; + appendIntegerWithPadding(maAdapterVendorID2, adapterVendorID2, 4); + appendIntegerWithPadding(maAdapterDeviceID2, adapterDeviceID2, 4); + appendIntegerWithPadding(maAdapterSubsysID2, ParseIDFromDeviceID(maDeviceID2, "&SUBSYS_", 8), 8); + break; + } + } + } + } + + SetupDiDestroyDeviceInfoList(devinfo); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/opengl/win/context.cxx b/vcl/source/opengl/win/context.cxx new file mode 100644 index 0000000000..d9e7dc11b1 --- /dev/null +++ b/vcl/source/opengl/win/context.cxx @@ -0,0 +1,667 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <memory> +#include <string_view> +#include <thread> +#include <vcl/opengl/OpenGLContext.hxx> +#include <vcl/opengl/OpenGLHelper.hxx> +#include <vcl/syschild.hxx> + +#include <sal/log.hxx> +#include <comphelper/windowserrorstring.hxx> +#include <opengl/zone.hxx> +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salframe.h> +#include <win/salinst.h> +#include <epoxy/wgl.h> +#include <ControlCacheKey.hxx> + +static std::vector<HGLRC> g_vShareList; +static bool g_bAnyCurrent; + +namespace { + +class GLWinWindow : public GLWindow +{ +public: + HWND hWnd; + HDC hDC; + HGLRC hRC; + GLWinWindow(); +}; + +} + +GLWinWindow::GLWinWindow() + : hWnd(nullptr) + , hDC(nullptr) + , hRC(nullptr) +{ +} + +namespace { + +class WinOpenGLContext : public OpenGLContext +{ +public: + virtual void initWindow() override; +private: + GLWinWindow m_aGLWin; + virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; } + virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; } + virtual bool ImplInit() override; + virtual void makeCurrent() override; + virtual void destroyCurrentContext() override; + virtual bool isCurrent() override; + virtual bool isAnyCurrent() override; + virtual void resetCurrent() override; + virtual void swapBuffers() override; +}; + +} + +void WinOpenGLContext::swapBuffers() +{ + OpenGLZone aZone; + + SwapBuffers(m_aGLWin.hDC); + + BuffersSwapped(); +} + +void WinOpenGLContext::resetCurrent() +{ + clearCurrent(); + + OpenGLZone aZone; + + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; +} + +static void ensureDispatchTable() +{ + thread_local bool bEpoxyDispatchMakeCurrentCalled = false; + if (!bEpoxyDispatchMakeCurrentCalled) + { + epoxy_handle_external_wglMakeCurrent(); + bEpoxyDispatchMakeCurrentCalled = true; + } +} + +bool WinOpenGLContext::isCurrent() +{ + OpenGLZone aZone; + if (!g_bAnyCurrent || !m_aGLWin.hRC) + return false; + ensureDispatchTable(); + return wglGetCurrentContext() == m_aGLWin.hRC && wglGetCurrentDC() == m_aGLWin.hDC; +} + +bool WinOpenGLContext::isAnyCurrent() +{ + return g_bAnyCurrent && wglGetCurrentContext() != nullptr; +} + +void WinOpenGLContext::makeCurrent() +{ + if (isCurrent()) + return; + + OpenGLZone aZone; + + clearCurrent(); + + ensureDispatchTable(); + + if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC)) + { + g_bAnyCurrent = false; + DWORD nLastError = GetLastError(); + if (nLastError != ERROR_SUCCESS) + SAL_WARN("vcl.opengl", "wglMakeCurrent failed: " << WindowsErrorString(nLastError)); + return; + } + + g_bAnyCurrent = true; + + registerAsCurrent(); +} + +void WinOpenGLContext::initWindow() +{ + if( !m_pChildWindow ) + { + SystemWindowData winData = generateWinData(mpWindow, false); + m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false); + } + + InitChildWindow(m_pChildWindow.get()); + const SystemEnvData* sysData(m_pChildWindow->GetSystemData()); + + // Hack: the performance of OpenGL increases with non-child window. + // Also, this keeps the backing window opaque on GL context destruction, + // which doesn't prevent the flicker (tdf#122737) completely, but at + // least doesn't show user desktop during the flicker. + LONG_PTR style = GetWindowLongPtrW(sysData->hWnd, GWL_STYLE); + style &= ~WS_CHILD; + SetWindowLongPtrW(sysData->hWnd, GWL_STYLE, style); + + m_aGLWin.hWnd = sysData->hWnd; + + m_aGLWin.hDC = GetDC(m_aGLWin.hWnd); +} + +void WinOpenGLContext::destroyCurrentContext() +{ + if (m_aGLWin.hRC) + { + std::erase(g_vShareList, m_aGLWin.hRC); + + if (wglGetCurrentContext() != nullptr) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + } + wglDeleteContext( m_aGLWin.hRC ); + ReleaseDC( m_aGLWin.hWnd, m_aGLWin.hDC ); + m_aGLWin.hRC = nullptr; + } +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_CREATE: + return 0; + case WM_CLOSE: + PostQuitMessage(0); + return 0; + case WM_DESTROY: + return 0; + default: + return DefWindowProcW(hwnd, message, wParam, lParam); + } +} + +static bool InitTempWindow(HWND& hwnd, int width, int height, const PIXELFORMATDESCRIPTOR& inPfd, GLWinWindow& glWin) +{ + OpenGLZone aZone; + + PIXELFORMATDESCRIPTOR pfd = inPfd; + int ret; + WNDCLASSW wc; + wc.style = 0; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = wc.cbWndExtra = 0; + wc.hInstance = nullptr; + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = L"GLRenderer"; + RegisterClassW(&wc); + hwnd = CreateWindowW(wc.lpszClassName, nullptr, WS_DISABLED, 0, 0, width, height, nullptr, nullptr, wc.hInstance, nullptr); + glWin.hDC = GetDC(hwnd); + + int nPixelFormat = ChoosePixelFormat(glWin.hDC, &pfd); + if (!nPixelFormat) + { + ReleaseDC(hwnd, glWin.hDC); + DestroyWindow(hwnd); + return false; + } + ret = SetPixelFormat(glWin.hDC, nPixelFormat, &pfd); + if(!ret) + { + ReleaseDC(hwnd, glWin.hDC); + DestroyWindow(hwnd); + return false; + } + glWin.hRC = wglCreateContext(glWin.hDC); + if(!(glWin.hRC)) + { + ReleaseDC(hwnd, glWin.hDC); + DestroyWindow(hwnd); + return false; + } + ret = wglMakeCurrent(glWin.hDC, glWin.hRC); + if(!ret) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hwnd, glWin.hDC); + DestroyWindow(hwnd); + return false; + } + g_bAnyCurrent = false; + + return true; +} + +static bool WGLisExtensionSupported(const char *extension) +{ + OpenGLZone aZone; + + const size_t extlen = strlen(extension); + const char *supported = nullptr; + + // Try to use wglGetExtensionStringARB on current DC, if possible + PROC wglGetExtString = wglGetProcAddress("wglGetExtensionsStringARB"); + + if (wglGetExtString) + supported = reinterpret_cast<char*(__stdcall*)(HDC)>(wglGetExtString)(wglGetCurrentDC()); + // If that failed, try standard OpenGL extensions string + if (supported == nullptr) + supported = reinterpret_cast<char const *>(glGetString(GL_EXTENSIONS)); + // If that failed too, must be no extensions supported + if (supported == nullptr) + return false; + + // Begin examination at start of string, increment by 1 on false match + for (const char* p = supported; ; p++) + { + // Advance p up to the next possible match + p = strstr(p, extension); + + if (p == nullptr) + return false; // No Match + + // Make sure that match is at the start of the string or that + // the previous char is a space, or else we could accidentally + // match "wglFunkywglExtension" with "wglExtension" + + // Also, make sure that the following character is space or null + // or else "wglExtensionTwo" might match "wglExtension" + if ((p==supported || p[-1]==' ') && (p[extlen]=='\0' || p[extlen]==' ')) + return true; // Match + } +} + +static bool InitMultisample(const PIXELFORMATDESCRIPTOR& pfd, int& rPixelFormat, + bool bUseDoubleBufferedRendering, bool bRequestVirtualDevice) +{ + OpenGLZone aZone; + + HWND hWnd = nullptr; + GLWinWindow glWin; + // Create a temp window to check whether support multi-sample, if support, get the format + if (!InitTempWindow(hWnd, 32, 32, pfd, glWin)) + { + SAL_WARN("vcl.opengl", "Can't create temp window to test"); + return false; + } + + // See if the string exists in WGL + if (!WGLisExtensionSupported("WGL_ARB_multisample")) + { + SAL_WARN("vcl.opengl", "Device doesn't support multisample"); + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + return false; + } + // Get our pixel format + PFNWGLCHOOSEPIXELFORMATARBPROC fn_wglChoosePixelFormatARB = reinterpret_cast<PFNWGLCHOOSEPIXELFORMATARBPROC>(wglGetProcAddress("wglChoosePixelFormatARB")); + if (!fn_wglChoosePixelFormatARB) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + return false; + } + // Get our current device context + HDC hDC = GetDC(hWnd); + + int pixelFormat; + int valid; + UINT numFormats; + float fAttributes[] = {0,0}; + // These attributes are the bits we want to test for in our sample. + // Everything is pretty standard, the only one we want to + // really focus on is the WGL_SAMPLE_BUFFERS_ARB and WGL_SAMPLES_ARB. + // These two are going to do the main testing for whether or not + // we support multisampling on this hardware. + int iAttributes[] = + { + WGL_DOUBLE_BUFFER_ARB,GL_TRUE, + WGL_DRAW_TO_WINDOW_ARB,GL_TRUE, + WGL_SUPPORT_OPENGL_ARB,GL_TRUE, + WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB, + WGL_COLOR_BITS_ARB,24, + WGL_ALPHA_BITS_ARB,8, + WGL_DEPTH_BITS_ARB,24, + WGL_STENCIL_BITS_ARB,0, + WGL_SAMPLE_BUFFERS_ARB,GL_TRUE, + WGL_SAMPLES_ARB,8, + 0,0 + }; + + if (!bUseDoubleBufferedRendering) + { + // Use asserts to make sure the iAttributes array is not changed without changing these ugly + // hardcode indexes into it. + assert(iAttributes[0] == WGL_DOUBLE_BUFFER_ARB); + iAttributes[1] = GL_FALSE; + } + + if (bRequestVirtualDevice) + { + assert(iAttributes[2] == WGL_DRAW_TO_WINDOW_ARB); + iAttributes[2] = WGL_DRAW_TO_BITMAP_ARB; + } + + bool bArbMultisampleSupported = false; + + // First we check to see if we can get a pixel format for 8 samples + valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats); + // If we returned true, and our format count is greater than 1 + if (valid && numFormats >= 1) + { + bArbMultisampleSupported = true; + rPixelFormat = pixelFormat; + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + return bArbMultisampleSupported; + } + // Our pixel format with 8 samples failed, test for 2 samples + assert(iAttributes[18] == WGL_SAMPLES_ARB); + iAttributes[19] = 2; + valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats); + if (valid && numFormats >= 1) + { + bArbMultisampleSupported = true; + rPixelFormat = pixelFormat; + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + return bArbMultisampleSupported; + } + // Return the valid format + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + + return bArbMultisampleSupported; +} + +namespace +{ + +bool tryShaders(const OUString& rVertexShader, const OUString& rFragmentShader, const OUString& rGeometryShader = "", std::string_view rPreamble = "") +{ + GLint nId; + + // Somewhat mysteriously, the OpenGLHelper::LoadShaders() API saves a compiled binary of the + // shader only if you give it the digest of the shaders. We have API to calculate the digest + // only of the combination of vertex and fragment (but not geometry) shader. So if we have a + // geometry shader, we should not save the binary. + if (rGeometryShader.isEmpty()) + { + nId = OpenGLHelper::LoadShaders(rVertexShader, rFragmentShader, rPreamble, OpenGLHelper::GetDigest( rVertexShader, rFragmentShader, rPreamble)); + } + else + { + assert(rPreamble.empty()); + nId = OpenGLHelper::LoadShaders(rVertexShader, rFragmentShader, rGeometryShader); + } + if (!nId) + return false; + + // We're interested in the error returned by glDeleteProgram(). + glGetError(); + + glDeleteProgram(nId); + return glGetError() == GL_NO_ERROR; +} + +bool compiledShaderBinariesWork() +{ + static bool bBeenHere = false; + static bool bResult; + + if (bBeenHere) + return bResult; + + bBeenHere = true; + + bResult = + ( +#if 0 // Only look at shaders used by slideshow for now + // canvas + tryShaders("dummyVertexShader", "linearMultiColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "linearTwoColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "radialMultiColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "radialTwoColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "rectangularMultiColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "rectangularTwoColorGradientFragmentShader") && +#endif + // slideshow + tryShaders("reflectionVertexShader", "reflectionFragmentShader") && + tryShaders("basicVertexShader", "basicFragmentShader") && + tryShaders("vortexVertexShader", "vortexFragmentShader", "vortexGeometryShader") && + tryShaders("basicVertexShader", "rippleFragmentShader") && + tryShaders("glitterVertexShader", "glitterFragmentShader") && + tryShaders("honeycombVertexShader", "honeycombFragmentShader", "honeycombGeometryShader")); + + return bResult; +} + +} // unnamed namespace + +bool WinOpenGLContext::ImplInit() +{ + static bool bFirstCall = true; + + OpenGLZone aZone; + + VCL_GL_INFO("OpenGLContext::ImplInit----start"); + // PixelFormat tells Windows how we want things to be + PIXELFORMATDESCRIPTOR PixelFormatFront = + { + sizeof(PIXELFORMATDESCRIPTOR), + 1, // Version Number + PFD_SUPPORT_OPENGL, + PFD_TYPE_RGBA, // Request An RGBA Format + BYTE(32), // Select Our Color Depth + 0, 0, 0, 0, 0, 0, // Color Bits Ignored + 0, // No Alpha Buffer + 0, // Shift Bit Ignored + 0, // No Accumulation Buffer + 0, 0, 0, 0, // Accumulation Bits Ignored + 24, // 24 bit z-buffer + 8, // stencil buffer + 0, // No Auxiliary Buffer + 0, // now ignored + 0, // Reserved + 0, 0, 0 // Layer Masks Ignored + }; + + PixelFormatFront.dwFlags |= PFD_DOUBLEBUFFER; + PixelFormatFront.dwFlags |= PFD_DRAW_TO_WINDOW; + + // we must check whether can set the MSAA + int WindowPix = 0; + bool bMultiSampleSupport = false; + + bMultiSampleSupport = InitMultisample(PixelFormatFront, WindowPix, /*bUseDoubleBufferedRendering*/true, false); + + if (bMultiSampleSupport && WindowPix != 0) + { + m_aGLWin.bMultiSampleSupported = true; + } + else + { + WindowPix = ChoosePixelFormat(m_aGLWin.hDC, &PixelFormatFront); +#if OSL_DEBUG_LEVEL > 0 + PIXELFORMATDESCRIPTOR pfd; + DescribePixelFormat(m_aGLWin.hDC, WindowPix, sizeof(PIXELFORMATDESCRIPTOR), &pfd); + SAL_WARN("vcl.opengl", "Render Target: Window: " << static_cast<int>((pfd.dwFlags & PFD_DRAW_TO_WINDOW) != 0) << ", Bitmap: " << static_cast<int>((pfd.dwFlags & PFD_DRAW_TO_BITMAP) != 0)); + SAL_WARN("vcl.opengl", "Supports OpenGL: " << static_cast<int>((pfd.dwFlags & PFD_SUPPORT_OPENGL) != 0)); +#endif + } + + if (WindowPix == 0) + { + SAL_WARN("vcl.opengl", "Invalid pixelformat"); + return false; + } + + if (!SetPixelFormat(m_aGLWin.hDC, WindowPix, &PixelFormatFront)) + { + SAL_WARN("vcl.opengl", "SetPixelFormat failed: " << WindowsErrorString(GetLastError())); + return false; + } + + HGLRC hTempRC = wglCreateContext(m_aGLWin.hDC); + if (hTempRC == nullptr) + { + SAL_WARN("vcl.opengl", "wglCreateContext failed: "<< WindowsErrorString(GetLastError())); + return false; + } + + if (!wglMakeCurrent(m_aGLWin.hDC, hTempRC)) + { + g_bAnyCurrent = false; + SAL_WARN("vcl.opengl", "wglMakeCurrent failed: "<< WindowsErrorString(GetLastError())); + return false; + } + + g_bAnyCurrent = true; + + if (!InitGL()) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + return false; + } + + HGLRC hSharedCtx = nullptr; + if (!g_vShareList.empty()) + hSharedCtx = g_vShareList.front(); + + if (!wglCreateContextAttribsARB) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + return false; + } + + // now setup the shared context; this needs a temporary context already + // set up in order to work + int const attribs [] = + { +#ifdef DBG_UTIL + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, +#endif + 0 + }; + m_aGLWin.hRC = wglCreateContextAttribsARB(m_aGLWin.hDC, hSharedCtx, attribs); + if (m_aGLWin.hRC == nullptr) + { + SAL_WARN("vcl.opengl", "wglCreateContextAttribsARB failed: "<< WindowsErrorString(GetLastError())); + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + return false; + } + + if (!compiledShaderBinariesWork()) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + return false; + } + + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + + if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC)) + { + g_bAnyCurrent = false; + SAL_WARN("vcl.opengl", "wglMakeCurrent failed: " << WindowsErrorString(GetLastError())); + return false; + } + + g_bAnyCurrent = true; + + if (bFirstCall) + { + // Checking texture size + GLint nMaxTextureSize; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &nMaxTextureSize); + if (nMaxTextureSize <= 4096) + SAL_WARN("vcl.opengl", "Max texture size is " << nMaxTextureSize + << ". This may not be enough for normal operation."); + else + VCL_GL_INFO("Max texture size: " << nMaxTextureSize); + + // Trying to make a texture and check its size + for (GLint nWidthHeight = 1023; nWidthHeight < nMaxTextureSize; nWidthHeight += (nWidthHeight + 1)) + { + glTexImage2D(GL_PROXY_TEXTURE_2D, 0, 4, nWidthHeight, nWidthHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, nullptr); + CHECK_GL_ERROR(); + if (glGetError() == GL_NO_ERROR) + { + GLint nWidth = 0; + GLint nHeight = 0; + glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &nWidth); + glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &nHeight); + VCL_GL_INFO("Created texture " << nWidthHeight << "," << nWidthHeight << " reports size: " << nWidth << ", " << nHeight); + } + else + { + SAL_WARN("vcl.opengl", "Error when creating a " << nWidthHeight << ", " << nWidthHeight << " test texture."); + } + } + } + + InitGLDebugging(); + + g_vShareList.push_back(m_aGLWin.hRC); + + RECT clientRect; + GetClientRect(WindowFromDC(m_aGLWin.hDC), &clientRect); + m_aGLWin.Width = clientRect.right - clientRect.left; + m_aGLWin.Height = clientRect.bottom - clientRect.top; + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + registerAsCurrent(); + + bFirstCall = false; + + return true; +} + +OpenGLContext* WinSalInstance::CreateOpenGLContext() +{ + return new WinOpenGLContext; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |