diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index 0493ed43a2..e8f43ddd02 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -60,6 +60,7 @@ #include #include #include + #include #if JUCE_WEB_BROWSER #include @@ -72,6 +73,7 @@ #pragma comment(lib, "vfw32.lib") #pragma comment(lib, "imm32.lib") #pragma comment(lib, "comctl32.lib") + #pragma comment(lib, "dxgi.lib") #if JUCE_OPENGL #pragma comment(lib, "OpenGL32.Lib") diff --git a/modules/juce_gui_basics/juce_gui_basics.h b/modules/juce_gui_basics/juce_gui_basics.h index 00981d387b..4f42c6a363 100644 --- a/modules/juce_gui_basics/juce_gui_basics.h +++ b/modules/juce_gui_basics/juce_gui_basics.h @@ -40,6 +40,7 @@ WeakOSXFrameworks: Metal MetalKit iOSFrameworks: CoreServices UIKit WeakiOSFrameworks: Metal MetalKit + mingwLibs: dxgi END_JUCE_MODULE_DECLARATION diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index 135e3241f6..dffe23e651 100644 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -1476,8 +1476,252 @@ private: WindowsDeleteStringFuncPtr deleteHString; }; +//============================================================================== +static HMONITOR getMonitorFromOutput (ComSmartPtr output) +{ + DXGI_OUTPUT_DESC desc = {}; + return (FAILED (output->GetDesc (&desc)) || ! desc.AttachedToDesktop) + ? nullptr + : desc.Monitor; +} + +struct VBlankListener +{ + virtual void onVBlank() = 0; +}; + +//============================================================================== +class VSyncThread : private Thread, + private AsyncUpdater +{ +public: + VSyncThread (ComSmartPtr out, + HMONITOR mon, + VBlankListener& listener) + : Thread ("VSyncThread"), + output (out), + monitor (mon) + { + listeners.push_back (listener); + startThread (10); + } + + ~VSyncThread() override + { + stopThread (-1); + cancelPendingUpdate(); + } + + void updateMonitor() + { + monitor = getMonitorFromOutput (output); + } + + HMONITOR getMonitor() const noexcept { return monitor; } + + void addListener (VBlankListener& listener) + { + listeners.push_back (listener); + } + + bool removeListener (const VBlankListener& listener) + { + auto it = std::find_if (listeners.cbegin(), + listeners.cend(), + [&listener] (const auto& l) { return &(l.get()) == &listener; }); + + if (it != listeners.cend()) + { + listeners.erase (it); + return true; + } + + return false; + } + + bool hasNoListeners() const noexcept + { + return listeners.empty(); + } + + bool hasListener (const VBlankListener& listener) const noexcept + { + return std::any_of (listeners.cbegin(), + listeners.cend(), + [&listener] (const auto& l) { return &(l.get()) == &listener; }); + } + +private: + //============================================================================== + void run() override + { + while (! threadShouldExit()) + { + if (output->WaitForVBlank() == S_OK) + triggerAsyncUpdate(); + else + Thread::sleep (1); + } + } + + void handleAsyncUpdate() override + { + for (auto& listener : listeners) + listener.get().onVBlank(); + } + + //============================================================================== + ComSmartPtr output; + HMONITOR monitor = nullptr; + std::vector> listeners; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSyncThread) + JUCE_DECLARE_NON_MOVEABLE (VSyncThread) +}; + +//============================================================================== +class VBlankDispatcher : public DeletedAtShutdown +{ +public: + void updateDisplay (VBlankListener& listener, HMONITOR monitor) + { + if (monitor == nullptr) + { + removeListener (listener); + return; + } + + auto threadWithListener = threads.end(); + auto threadWithMonitor = threads.end(); + + for (auto it = threads.begin(); it != threads.end(); ++it) + { + if ((*it)->hasListener (listener)) + threadWithListener = it; + + if ((*it)->getMonitor() == monitor) + threadWithMonitor = it; + + if (threadWithListener != threads.end() + && threadWithMonitor != threads.end()) + { + if (threadWithListener == threadWithMonitor) + return; + + (*threadWithMonitor)->addListener (listener); + + // This may invalidate iterators, so be careful! + removeListener (threadWithListener, listener); + return; + } + } + + if (threadWithMonitor != threads.end()) + { + (*threadWithMonitor)->addListener (listener); + return; + } + + if (threadWithListener != threads.end()) + removeListener (threadWithListener, listener); + + for (auto adapter : adapters) + { + UINT i = 0; + ComSmartPtr output; + + while (adapter->EnumOutputs (i, output.resetAndGetPointerAddress()) != DXGI_ERROR_NOT_FOUND) + { + if (getMonitorFromOutput (output) == monitor) + { + threads.emplace_back (std::make_unique (output, monitor, listener)); + return; + } + + ++i; + } + } + } + + void removeListener (const VBlankListener& listener) + { + for (auto it = threads.begin(); it != threads.end(); ++it) + if (removeListener (it, listener)) + return; + } + + void reconfigureDisplays() + { + adapters.clear(); + + ComSmartPtr factory; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") + CreateDXGIFactory (__uuidof (IDXGIFactory), (void**)factory.resetAndGetPointerAddress()); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + UINT i = 0; + ComSmartPtr adapter; + + while (factory->EnumAdapters (i, adapter.resetAndGetPointerAddress()) != DXGI_ERROR_NOT_FOUND) + { + adapters.push_back (adapter); + ++i; + } + + for (auto& thread : threads) + thread->updateMonitor(); + + threads.erase (std::remove_if (threads.begin(), + threads.end(), + [] (const auto& thread) { return thread->getMonitor() == nullptr; }), + threads.end()); + } + + JUCE_DECLARE_SINGLETON_SINGLETHREADED (VBlankDispatcher, true) + +private: + //============================================================================== + using Threads = std::vector>; + + VBlankDispatcher() + { + reconfigureDisplays(); + } + + ~VBlankDispatcher() override + { + threads.clear(); + clearSingletonInstance(); + } + + // This may delete the corresponding thread and invalidate iterators, + // so be careful! + bool removeListener (Threads::iterator it, const VBlankListener& listener) + { + if ((*it)->removeListener (listener)) + { + if ((*it)->hasNoListeners()) + threads.erase (it); + + return true; + } + + return false; + } + + //============================================================================== + std::vector> adapters; + Threads threads; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VBlankDispatcher) + JUCE_DECLARE_NON_MOVEABLE (VBlankDispatcher) +}; + +JUCE_IMPLEMENT_SINGLETON (VBlankDispatcher) + //============================================================================== class HWNDComponentPeer : public ComponentPeer, + private VBlankListener, private Timer #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client , public ModifierKeyReceiver @@ -1517,10 +1761,15 @@ public: return ModifierKeys::currentModifiers; }; + + if (updateCurrentMonitor()) + VBlankDispatcher::getInstance()->updateDisplay (*this, currentMonitor); } ~HWNDComponentPeer() override { + VBlankDispatcher::getInstance()->removeListener (*this); + // do this first to avoid messages arriving for this window before it's destroyed JuceWindowIdentifier::setAsJUCEWindow (hwnd, false); @@ -1889,14 +2138,26 @@ public: void repaint (const Rectangle& area) override { - auto r = RECTFromRectangle ((area.toDouble() * getPlatformScaleFactor()).getSmallestIntegerContainer()); - InvalidateRect (hwnd, &r, FALSE); + deferredRepaints.add ((area.toDouble() * getPlatformScaleFactor()).getSmallestIntegerContainer()); + } + + void dispatchDeferredRepaints() + { + for (auto deferredRect : deferredRepaints) + { + auto r = RECTFromRectangle (deferredRect); + InvalidateRect (hwnd, &r, FALSE); + } + + deferredRepaints.clear(); } void performAnyPendingRepaintsNow() override { if (component.isVisible()) { + dispatchDeferredRepaints(); + WeakReference localRef (&component); MSG m; @@ -1906,6 +2167,12 @@ public: } } + //============================================================================== + void onVBlank() override + { + dispatchDeferredRepaints(); + } + //============================================================================== static HWNDComponentPeer* getOwnerOfWindow (HWND h) noexcept { @@ -2156,6 +2423,7 @@ private: double scaleFactor = 1.0; bool inDpiChange = 0, inHandlePositionChanged = 0; + HMONITOR currentMonitor = nullptr; bool isAccessibilityActive = false; @@ -3479,6 +3747,12 @@ private: return 0; } + bool updateCurrentMonitor() + { + auto monitor = MonitorFromWindow (hwnd, MONITOR_DEFAULTTONULL); + return std::exchange (currentMonitor, monitor) != monitor; + } + bool handlePositionChanged() { auto pos = getCurrentMousePos(); @@ -3496,6 +3770,9 @@ private: handleMovedOrResized(); + if (updateCurrentMonitor()) + VBlankDispatcher::getInstance()->updateDisplay (*this, currentMonitor); + return ! dontRepaint; // to allow non-accelerated openGL windows to draw themselves correctly. } @@ -3649,6 +3926,11 @@ private: setWindowPos (hwnd, ScalingHelpers::scaledScreenPosToUnscaled (component, Desktop::getInstance().getDisplays() .getDisplayForRect (component.getScreenBounds())->userArea), SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING); + + auto* dispatcher = VBlankDispatcher::getInstance(); + dispatcher->reconfigureDisplays(); + updateCurrentMonitor(); + dispatcher->updateDisplay (*this, currentMonitor); } //============================================================================== @@ -4408,6 +4690,8 @@ private: IMEHandler imeHandler; bool shouldIgnoreModalDismiss = false; + RectangleList deferredRepaints; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HWNDComponentPeer) };