summaryrefslogtreecommitdiff
path: root/indra/llwindow/llwindowwin32.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llwindow/llwindowwin32.cpp')
-rw-r--r--indra/llwindow/llwindowwin32.cpp278
1 files changed, 263 insertions, 15 deletions
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index 485d332068..149a92ffff 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -46,6 +46,7 @@
#include "llsdutil.h"
#include "llglslshader.h"
#include "llthreadsafequeue.h"
+#include "stringize.h"
// System includes
#include <commdlg.h>
@@ -55,6 +56,10 @@
#include <shellapi.h>
#include <fstream>
#include <Imm.h>
+#include <iomanip>
+#include <future>
+#include <sstream>
+#include <utility> // std::pair
// Require DirectInput version 8
#define DIRECTINPUT_VERSION 0x0800
@@ -78,6 +83,10 @@ const F32 ICON_FLASH_TIME = 0.5f;
#define USER_DEFAULT_SCREEN_DPI 96 // Win7
#endif
+// Claim a couple unused GetMessage() message IDs
+const UINT WM_DUMMY_(WM_USER + 0x0017);
+const UINT WM_POST_FUNCTION_(WM_USER + 0x0018);
+
extern BOOL gDebugWindowProc;
static std::thread::id sWindowThreadId;
@@ -468,6 +477,70 @@ private:
static LLMonitorInfo sMonitorInfo;
+// Thread that owns the Window Handle
+// This whole struct is private to LLWindowWin32, which needs to mess with its
+// members, which is why it's a struct rather than a class. In effect, we make
+// the containing class a friend.
+struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
+{
+ static const int MAX_QUEUE_SIZE = 2048;
+
+ LLThreadSafeQueue<MSG> mMessageQueue;
+
+ LLWindowWin32Thread();
+
+ void run() override;
+
+ /// called by main thread to post work to this window thread
+ template <typename CALLABLE>
+ void post(CALLABLE&& func)
+ {
+ try
+ {
+ getQueue().post(std::forward<CALLABLE>(func));
+ }
+ catch (const LLThreadSafeQueueInterrupt&)
+ {
+ // Shutdown timing is tricky. The main thread can end up trying
+ // to post a cursor position after having closed the WorkQueue.
+ }
+ }
+
+ /**
+ * Like post(), Post() is a way of conveying a single work item to this
+ * thread. Its virtue is that it will definitely be executed "soon" rather
+ * than potentially waiting for the next frame: it uses PostMessage() to
+ * break us out of the window thread's blocked GetMessage() call. It's
+ * more expensive, though, not only from the Windows API latency of
+ * PostMessage() and GetMessage(), but also because it involves heap
+ * allocation and release.
+ *
+ * Require HWND from caller, even though we store an HWND locally.
+ * Otherwise, if our mWindowHandle was accessed from both threads, we'd
+ * have to protect it with a mutex.
+ */
+ template <typename CALLABLE>
+ void Post(HWND windowHandle, CALLABLE&& func)
+ {
+ // Move func to the heap. If we knew FuncType could fit into LPARAM,
+ // we could simply instantiate FuncType and pass it by value. But
+ // since we don't, we must put that on the heap as well as the
+ // internal heap allocation it likely requires to store func.
+ auto ptr = new FuncType(std::move(func));
+ WPARAM wparam{ 0xF1C };
+ LL_DEBUGS("Window") << "PostMessage(" << std::hex << windowHandle
+ << ", " << WM_POST_FUNCTION_
+ << ", " << wparam << std::dec << LL_ENDL;
+ PostMessage(windowHandle, WM_POST_FUNCTION_, wparam, LPARAM(ptr));
+ }
+
+ using FuncType = std::function<void()>;
+ // call GetMessage() and pull enqueue messages for later processing
+ void gatherInput();
+ HWND mWindowHandle = NULL;
+ HDC mhDC = 0;
+};
+
LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
const std::string& title, const std::string& name, S32 x, S32 y, S32 width,
@@ -875,7 +948,7 @@ void LLWindowWin32::restore()
// I'm turning off optimizations for this part to be sure code executes as intended
// (it is a straw, but I have no idea why else __try can get overruled)
#pragma optimize("", off)
-bool destroy_window_handler(HWND &hWnd)
+bool destroy_window_handler(HWND hWnd)
{
bool res;
__try
@@ -1828,6 +1901,99 @@ const S32 max_format = (S32)num_formats - 1;
return TRUE;
}
+void LLWindowWin32::recreateWindow(RECT window_rect, DWORD dw_ex_style, DWORD dw_style)
+{
+ auto oldHandle = mWindowHandle;
+
+ // zero out mWindowHandle and mhDC before destroying window so window
+ // thread falls back to peekmessage
+ mWindowHandle = 0;
+ mhDC = 0;
+
+ std::promise<std::pair<HWND, HDC>> promise;
+ // What follows must be done on the window thread.
+ auto window_work =
+ [self=mWindowThread,
+ oldHandle,
+ // bind CreateWindowEx() parameters by value instead of
+ // back-referencing LLWindowWin32 members
+ windowClassName=mWindowClassName,
+ windowTitle=mWindowTitle,
+ hInstance=mhInstance,
+ window_rect,
+ dw_ex_style,
+ dw_style,
+ &promise]
+ ()
+ {
+ LL_DEBUGS("Window") << "recreateWindow(): window_work entry" << LL_ENDL;
+ self->mWindowHandle = 0;
+ self->mhDC = 0;
+
+ // important to call DestroyWindow() from the window thread
+ if (oldHandle && !destroy_window_handler(oldHandle))
+ {
+ LL_WARNS("Window") << "Failed to properly close window before recreating it!"
+ << LL_ENDL;
+ }
+
+ auto handle = CreateWindowEx(dw_ex_style,
+ windowClassName,
+ windowTitle,
+ WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
+ window_rect.left, // x pos
+ window_rect.top, // y pos
+ window_rect.right - window_rect.left, // width
+ window_rect.bottom - window_rect.top, // height
+ NULL,
+ NULL,
+ hInstance,
+ NULL);
+
+ if (! handle)
+ {
+ // Failed to create window: clear the variables. This
+ // assignment is valid because we're running on mWindowThread.
+ self->mWindowHandle = NULL;
+ self->mhDC = 0;
+ }
+ else
+ {
+ // Update mWindowThread's own mWindowHandle and mhDC.
+ self->mWindowHandle = handle;
+ self->mhDC = GetDC(handle);
+ }
+
+ // It's important to wake up the future either way.
+ promise.set_value(std::make_pair(self->mWindowHandle, self->mhDC));
+ LL_DEBUGS("Window") << "recreateWindow(): window_work done" << LL_ENDL;
+ };
+ // But how we pass window_work to the window thread depends on whether we
+ // already have a window handle.
+ if (! oldHandle)
+ {
+ // Pass window_work using the WorkQueue: without an existing window
+ // handle, the window thread can't call GetMessage().
+ LL_DEBUGS("Window") << "posting window_work to WorkQueue" << LL_ENDL;
+ mWindowThread->post(window_work);
+ }
+ else
+ {
+ // Pass window_work using PostMessage(). We can still
+ // PostMessage(oldHandle) because oldHandle won't be destroyed until
+ // the window thread has retrieved and executed window_work.
+ LL_DEBUGS("Window") << "posting window_work to message queue" << LL_ENDL;
+ mWindowThread->Post(oldHandle, window_work);
+ }
+
+ auto future = promise.get_future();
+ // This blocks until mWindowThread processes CreateWindowEx() and calls
+ // promise.set_value().
+ auto pair = future.get();
+ mWindowHandle = pair.first;
+ mhDC = pair.second;
+}
+
void* LLWindowWin32::createSharedContext()
{
S32 attribs[] =
@@ -1839,7 +2005,7 @@ void* LLWindowWin32::createSharedContext()
0
};
- HGLRC rc = wglCreateContextAttribsARB(mhDC, mhRC, attribs);
+ HGLRC rc = 0;
bool done = false;
while (!done)
@@ -2184,11 +2350,7 @@ void LLWindowWin32::gatherInput()
if (mWindowThread->mFunctionQueue.size() > 0)
{
LL_PROFILE_ZONE_NAMED("gi - PostMessage");
- if (mWindowHandle)
- { // post a nonsense user message to wake up the Window Thread in case any functions are pending
- // and no windows events came through this frame
- PostMessage(mWindowHandle, WM_USER + 0x0017, 0xB0B0, 0x1337);
- }
+ kickWindowThread();
}
while (mWindowThread->mMessageQueue.tryPopBack(msg))
@@ -2262,6 +2424,23 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
ASSERT_WINDOW_THREAD();
LL_PROFILE_ZONE_SCOPED;
+ LL_DEBUGS("Window") << "mainWindowProc(" << std::hex << h_wnd
+ << ", " << u_msg
+ << ", " << w_param << ")" << std::dec << LL_ENDL;
+
+ if (u_msg == WM_POST_FUNCTION_)
+ {
+ LL_DEBUGS("Window") << "WM_POST_FUNCTION_" << LL_ENDL;
+ // from LLWindowWin32Thread::Post()
+ // Cast l_param back to the pointer to the heap FuncType
+ // allocated by Post(). Capture in unique_ptr so we'll delete
+ // once we're done with it.
+ std::unique_ptr<LLWindowWin32Thread::FuncType>
+ ptr(reinterpret_cast<LLWindowWin32Thread::FuncType*>(l_param));
+ (*ptr)();
+ return 0;
+ }
+
// Ignore clicks not originated in the client area, i.e. mouse-up events not preceded with a WM_LBUTTONDOWN.
// This helps prevent avatar walking after maximizing the window by double-clicking the title bar.
static bool sHandleLeftMouseUp = true;
@@ -4572,33 +4751,84 @@ inline LLWindowWin32Thread::LLWindowWin32Thread(LLWindowWin32* window)
mWindow(window),
mFunctionQueue(MAX_QUEUE_SIZE)
{
-
+ ThreadPool::start();
}
-inline void LLWindowWin32Thread::run()
+/**
+ * LogChange is to log changes in status while trying to avoid spamming the
+ * log with repeated messages, especially in a tight loop. It refuses to log
+ * a continuous run of identical messages, but logs every time the message
+ * changes. (It will happily spam when messages quickly bounce back and
+ * forth.)
+ */
+class LogChange
{
- sWindowThreadId = getID();
- while (!mFinished)
+public:
+ LogChange(const std::string& tag):
+ mTag(tag)
+ {}
+
+ template <typename... Items>
+ void always(Items&&... items)
{
- LL_PROFILE_ZONE_SCOPED;
+ // This odd construct ensures that the stringize() call is only
+ // executed if DEBUG logging is enabled for the passed tag.
+ LL_DEBUGS(mTag.c_str());
+ log(LL_CONT, stringize(std::forward<Items>(items)...));
+ LL_ENDL;
+ }
+
+ template <typename... Items>
+ void onChange(Items&&... items)
+ {
+ LL_DEBUGS(mTag.c_str());
+ auto str = stringize(std::forward<Items>(items)...);
+ if (str != mPrev)
+ {
+ log(LL_CONT, str);
+ }
+ LL_ENDL;
+ }
+
+private:
+ void log(std::ostream& out, const std::string& message)
+ {
+ mPrev = message;
+ out << message;
+ }
+ std::string mTag;
+ std::string mPrev;
+};
+void LLWindowWin32::LLWindowWin32Thread::run()
+{
+ sWindowThreadId = std::this_thread::get_id();
+ LogChange logger("Window");
+
+ while (! getQueue().done())
+ {
+ LL_PROFILE_ZONE_SCOPED;
- if (mWindow && mWindow->mWindowHandle != 0)
+ if (mWindowHandle != 0)
{
MSG msg;
BOOL status;
if (mWindow->mhDC == 0)
{
LL_PROFILE_ZONE_NAMED("w32t - PeekMessage");
- status = PeekMessage(&msg, mWindow->mWindowHandle, 0, 0, PM_REMOVE);
+ logger.onChange("PeekMessage(", std::hex, mWindowHandle, ")");
+ status = PeekMessage(&msg, mWindowHandle, 0, 0, PM_REMOVE);
}
else
{
LL_PROFILE_ZONE_NAMED("w32t - GetMessage");
- status = GetMessage(&msg, mWindow->mWindowHandle, 0, 0);
+ logger.always("GetMessage(", std::hex, mWindowHandle, ")");
+ status = GetMessage(&msg, mWindowHandle, 0, 0);
}
if (status > 0)
{
+ logger.always("got MSG (", std::hex, msg.hwnd, ", ", msg.message,
+ ", ", msg.wParam, ")");
TranslateMessage(&msg);
DispatchMessage(&msg);
@@ -4608,6 +4838,7 @@ inline void LLWindowWin32Thread::run()
{
LL_PROFILE_ZONE_NAMED("w32t - Function Queue");
+ logger.onChange("runPending()");
//process any pending functions
std::function<void()> curFunc;
while (mFunctionQueue.tryPopBack(curFunc))
@@ -4619,6 +4850,7 @@ inline void LLWindowWin32Thread::run()
#if 0
{
LL_PROFILE_ZONE_NAMED("w32t - Sleep");
+ logger.always("sleep(1)");
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
#endif
@@ -4640,3 +4872,19 @@ void LLWindowWin32::postMouseButtonEvent(const std::function<void()>& func)
mMouseQueue.pushFront(func);
}
+void LLWindowWin32::kickWindowThread(HWND windowHandle)
+{
+ if (! windowHandle)
+ windowHandle = mWindowHandle;
+ if (windowHandle)
+ {
+ // post a nonsense user message to wake up the Window Thread in
+ // case any functions are pending and no windows events came
+ // through this frame
+ WPARAM wparam{ 0xB0B0 };
+ LL_DEBUGS("Window") << "PostMessage(" << std::hex << windowHandle
+ << ", " << WM_DUMMY_
+ << ", " << wparam << ")" << std::dec << LL_ENDL;
+ PostMessage(windowHandle, WM_DUMMY_, wparam, 0x1337);
+ }
+}