diff --git a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp index 84b67eea2e..f8bde56e2b 100644 --- a/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp +++ b/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp @@ -595,7 +595,7 @@ public: const auto paintAreas = getPaintAreas(); const auto paintBounds = paintAreas.getBounds(); - if (! getFrameSize().intersects (paintBounds) || paintBounds.isEmpty()) + if (! getFrameSize().intersects (paintBounds) || paintBounds.isEmpty() || paintAreas.isEmpty()) return nullptr; // Is Direct2D ready to paint? diff --git a/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp b/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp index 7d395e34a0..dec6112ddc 100644 --- a/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp +++ b/modules/juce_graphics/native/juce_Direct2DHwndContext_windows.cpp @@ -35,250 +35,44 @@ namespace juce { -class Presentation -{ -public: - auto getPresentationBitmap() const - { - jassert (presentationBitmap != nullptr); - return presentationBitmap; - } - - auto getPresentationBitmap (const Rectangle& swapSize, ComSmartPtr context) - { - if (presentationBitmap != nullptr) - { - const auto size = presentationBitmap->GetPixelSize(); - - if (size.width != (uint32) swapSize.getWidth() || size.height != (uint32) swapSize.getHeight()) - presentationBitmap = nullptr; - } - - if (presentationBitmap == nullptr) - { - presentationBitmap = Direct2DBitmap::createBitmap (context, - Image::ARGB, - { (uint32) swapSize.getWidth(), (uint32) swapSize.getHeight() }, - D2D1_BITMAP_OPTIONS_TARGET); - } - - return presentationBitmap; - } - - void setPaintAreas (RectangleList areas) - { - paintAreas = std::move (areas); - } - - const RectangleList& getPaintAreas() const - { - return paintAreas; - } - -private: - ComSmartPtr presentationBitmap; - RectangleList paintAreas; -}; - -class PresentationQueue -{ -public: - Presentation* lockFront() - { - const std::scoped_lock lock { mutex }; - displaying = std::exchange (readyToDisplay, nullptr); - return displaying; - } - - void unlockFront() - { - const std::scoped_lock lock { mutex }; - displaying = nullptr; - } - - Presentation* lockBack() - { - const std::scoped_lock lock { mutex }; - - preparing = [&]() -> Presentation* - { - for (auto& p : presentations) - if (&p != displaying && &p != readyToDisplay) - return &p; - - return nullptr; - }(); - - return preparing; - } - - void unlockBack() - { - { - const std::scoped_lock lock { mutex }; - - if (preparing == nullptr) - return; - - if (readyToDisplay != nullptr) - { - // Copy the dirty regions from the newest presentation over the top of the 'ready' - // presentation, then combine dirty regions. - // We're effectively combining several frames of dirty regions into one, until - // the screen update catches up. - - for (const auto& area : preparing->getPaintAreas()) - { - const auto destPoint = D2DUtilities::toPOINT_2U (area.getPosition()); - const auto sourceRect = D2DUtilities::toRECT_U (area); - readyToDisplay->getPresentationBitmap()->CopyFromBitmap (&destPoint, preparing->getPresentationBitmap(), &sourceRect); - } - - auto areas = readyToDisplay->getPaintAreas(); - areas.add (preparing->getPaintAreas()); - readyToDisplay->setPaintAreas (std::move (areas)); - } - else - { - readyToDisplay = std::exchange (preparing, nullptr); - } - } - - SetEvent (wakeEvent.getHandle()); - } - - HANDLE getWakeEvent() const - { - return wakeEvent.getHandle(); - } - -private: - WindowsScopedEvent wakeEvent; - - std::mutex mutex; - std::array presentations; - Presentation* preparing = nullptr; - Presentation* readyToDisplay = nullptr; - Presentation* displaying = nullptr; -}; - -template -class PresentationQueueLock -{ -public: - PresentationQueueLock() = default; - - explicit PresentationQueueLock (PresentationQueue& q) - : queue (&q), - presentation (queue != nullptr ? (queue->*lock)() : nullptr) - { - } - - ~PresentationQueueLock() - { - if (queue != nullptr) - (queue->*unlock)(); - } - - PresentationQueueLock (PresentationQueueLock&& other) noexcept - : queue (std::exchange (other.queue, nullptr)), - presentation (std::exchange (other.presentation, nullptr)) - { - } - - PresentationQueueLock& operator= (PresentationQueueLock&& other) noexcept - { - PresentationQueueLock { std::move (other) }.swap (*this); - return *this; - } - - PresentationQueueLock (const PresentationQueueLock&) = delete; - PresentationQueueLock& operator= (const PresentationQueueLock&) = delete; - - Presentation* getPresentation() const { return presentation; } - -private: - void swap (PresentationQueueLock& other) noexcept - { - std::swap (other.queue, queue); - std::swap (other.presentation, presentation); - } - - PresentationQueue* queue = nullptr; - Presentation* presentation = nullptr; -}; - -using BackBufferLock = PresentationQueueLock<&PresentationQueue::lockBack, &PresentationQueue::unlockBack>; -using FrontBufferLock = PresentationQueueLock<&PresentationQueue::lockFront, &PresentationQueue::unlockFront>; - struct Direct2DHwndContext::HwndPimpl : public Direct2DGraphicsContext::Pimpl { private: - struct SwapChainThread + struct SwapChainThread : private AsyncUpdater { - SwapChainThread (Direct2DHwndContext::HwndPimpl& ownerIn, - ComSmartPtr multithreadIn) + explicit SwapChainThread (Direct2DHwndContext::HwndPimpl& ownerIn) : owner (ownerIn), - multithread (multithreadIn), swapChainEventHandle (ownerIn.swap.swapChainEvent->getHandle()) { } - ~SwapChainThread() + ~SwapChainThread() override { + cancelPendingUpdate(); SetEvent (quitEvent.getHandle()); thread.join(); } - BackBufferLock getFreshPresentation() - { - return BackBufferLock (queue); - } - - void notify() - { - SetEvent (queue.getWakeEvent()); - } - private: Direct2DHwndContext::HwndPimpl& owner; - PresentationQueue queue; - ComSmartPtr multithread; HANDLE swapChainEventHandle = nullptr; WindowsScopedEvent quitEvent; std::thread thread { [&] { threadLoop(); } }; + void handleAsyncUpdate() override + { + owner.swapEventReceived = true; + owner.present(); + } + void threadLoop() { Thread::setCurrentThreadName ("JUCE D2D swap chain thread"); - bool swapChainReady = false; - - const auto serviceSwapChain = [&] - { - if (! swapChainReady) - return; - - FrontBufferLock frontBufferLock { queue }; - auto* frontBuffer = frontBufferLock.getPresentation(); - - if (frontBuffer == nullptr) - return; - - JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.owner.metrics, swapChainThreadTime); - - { - ScopedMultithread scopedMultithread { multithread }; - owner.present (frontBuffer, 0); - } - - swapChainReady = false; - }; - for (;;) { - const HANDLE handles[] { swapChainEventHandle, quitEvent.getHandle(), queue.getWakeEvent() }; + const HANDLE handles[] { swapChainEventHandle, quitEvent.getHandle() }; const auto waitResult = WaitForMultipleObjects ((DWORD) std::size (handles), handles, FALSE, INFINITE); @@ -286,20 +80,13 @@ private: { case WAIT_OBJECT_0: { - swapChainReady = true; - serviceSwapChain(); + triggerAsyncUpdate(); break; } case WAIT_OBJECT_0 + 1: return; - case WAIT_OBJECT_0 + 2: - { - serviceSwapChain(); - break; - } - case WAIT_FAILED: default: jassertfalse; @@ -312,14 +99,22 @@ private: SwapChain swap; ComSmartPtr deviceContext; std::unique_ptr swapChainThread; - BackBufferLock presentation; std::optional compositionTree; + + // Areas that must be repainted during the next paint call, between startFrame/endFrame RectangleList deferredRepaints; + + // Areas that have been updated in the backbuffer, but not presented + RectangleList dirtyRegionsInBackBuffer; + std::vector dirtyRectangles; int64 lastFinishFrameTicks = 0; - HWND hwnd = nullptr; + // Set to true after the swap event is signalled, indicating that we're allowed to try presenting + // a new frame. + bool swapEventReceived = false; + bool prepare() override { const auto adapter = directX->adapters.getAdapterForHwnd (hwnd); @@ -352,7 +147,7 @@ private: } if (! swapChainThread && swap.swapChainEvent.has_value()) - swapChainThread = std::make_unique (*this, directX->getD2DMultithread()); + swapChainThread = std::make_unique (*this); if (! compositionTree.has_value()) compositionTree = CompositionTree::create (adapter->dxgiDevice, hwnd, swap.chain); @@ -388,19 +183,15 @@ private: if (auto now = Time::getHighResolutionTicks(); Time::highResolutionTicksToSeconds (now - lastFinishFrameTicks) < 0.001) return false; - if (presentation.getPresentation() == nullptr) - if (swapChainThread != nullptr) - presentation = swapChainThread->getFreshPresentation(); - // Paint if: // resources are allocated // deferredRepaints has areas to be painted // the swap chain thread is ready bool ready = Pimpl::checkPaintReady(); ready &= swap.canPaint(); + ready &= swap.buffer != nullptr; ready &= compositionTree.has_value(); - ready &= ! getPaintAreas().isEmpty(); - ready &= presentation.getPresentation() != nullptr; + return ready; } @@ -444,16 +235,13 @@ public: ComSmartPtr getDeviceContextTarget() const override { - if (auto* p = presentation.getPresentation()) - return p->getPresentationBitmap (swap.getSize(), getDeviceContext()); - - return {}; + return swap.buffer; } - bool setSize (Rectangle size) + void setSize (Rectangle size) { if (size == swap.getSize() || size.isEmpty()) - return false; + return; // Require the entire window to be repainted deferredRepaints = size; @@ -464,18 +252,11 @@ public: if (auto dc = getDeviceContext()) { - ScopedMultithread scopedMultithread { directX->getD2DMultithread() }; - auto hr = swap.resize (size, dc); jassert (SUCCEEDED (hr)); if (FAILED (hr)) teardown(); - - if (swapChainThread) - swapChainThread->notify(); } - - return true; } void addDeferredRepaint (Rectangle deferredRepaint) @@ -497,103 +278,58 @@ public: // If a new frame is starting, clear deferredAreas in case repaint is called // while the frame is being painted to ensure the new areas are painted on the // next frame - JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::direct2dHwndPaintStart, owner.getFrameId()); - - presentation.getPresentation()->setPaintAreas (getPaintAreas()); - + dirtyRegionsInBackBuffer.add (deferredRepaints); deferredRepaints.clear(); + JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::direct2dHwndPaintStart, owner.getFrameId()); + return savedState; } HRESULT finishFrame() override { - const ScopeGuard scope { [this] - { - presentation = {}; - lastFinishFrameTicks = Time::getHighResolutionTicks(); - } }; - - return Pimpl::finishFrame(); + const auto result = Pimpl::finishFrame(); + present(); + lastFinishFrameTicks = Time::getHighResolutionTicks(); + return result; } - void present (Presentation* paintedPresentation, uint32 flags) + void present() { - if (paintedPresentation == nullptr) - return; - - // Fill out the array of dirty rectangles - // Compare paintAreas to the swap chain buffer area. If the rectangles in paintAreas are contained - // by the swap chain buffer area, then mark those rectangles as dirty. DXGI will only keep the dirty rectangles from the - // current buffer and copy the clean area from the previous buffer. - // The buffer needs to be completely filled before using dirty rectangles. The dirty rectangles need to be contained - // within the swap chain buffer. JUCE_D2DMETRICS_SCOPED_ELAPSED_TIME (owner.metrics, present1Duration); - // Allocate enough memory for the array of dirty rectangles - const auto areas = paintedPresentation->getPaintAreas(); - paintedPresentation->setPaintAreas ({}); + if (swap.buffer == nullptr || dirtyRegionsInBackBuffer.isEmpty() || ! swapEventReceived) + return; - dirtyRectangles.resize ((size_t) areas.getNumRectangles()); + // Allocate enough memory for the array of dirty rectangles + dirtyRectangles.resize ((size_t) dirtyRegionsInBackBuffer.getNumRectangles()); // Fill the array of dirty rectangles, intersecting each paint area with the swap chain buffer DXGI_PRESENT_PARAMETERS presentParameters{}; + presentParameters.pDirtyRects = dirtyRectangles.data(); + presentParameters.DirtyRectsCount = 0; - if (swap.state == SwapChain::State::bufferFilled) + auto const swapChainSize = swap.getSize(); + + for (const auto& area : dirtyRegionsInBackBuffer) { - auto* dirtyRectangle = dirtyRectangles.data(); - auto const swapChainSize = swap.getSize(); - - for (const auto& area : areas) - { - // If this paint area contains the entire swap chain, then - // no need for dirty rectangles - if (area.contains (swapChainSize)) - { - presentParameters.DirtyRectsCount = 0; - break; - } - - // Intersect this paint area with the swap chain buffer - auto intersection = area.getIntersection (swapChainSize); - - if (intersection.isEmpty()) - { - // Can't clip to an empty rectangle - continue; - } - - D2D1_POINT_2U destPoint { (uint32) intersection.getX(), (uint32) intersection.getY() }; - D2D1_RECT_U sourceRect { (uint32) intersection.getX(), - (uint32) intersection.getY(), - (uint32) intersection.getRight(), - (uint32) intersection.getBottom() }; - swap.buffer->CopyFromBitmap (&destPoint, paintedPresentation->getPresentationBitmap(), &sourceRect); - - // Add this intersected paint area to the dirty rectangle array (scaled for DPI) - *dirtyRectangle = D2DUtilities::toRECT (intersection); - - dirtyRectangle++; - presentParameters.DirtyRectsCount++; - } - - presentParameters.pDirtyRects = dirtyRectangles.data(); - } - - if (presentParameters.DirtyRectsCount == 0) - { - D2D1_POINT_2U destPoint { 0, 0 }; - - if (auto bitmap = paintedPresentation->getPresentationBitmap()) - swap.buffer->CopyFromBitmap (&destPoint, bitmap, nullptr); + if (const auto intersection = area.getIntersection (swapChainSize); ! intersection.isEmpty()) + presentParameters.pDirtyRects[presentParameters.DirtyRectsCount++] = D2DUtilities::toRECT (intersection); } // Present the freshly painted buffer - const auto hr = swap.chain->Present1 (swap.presentSyncInterval, swap.presentFlags | flags, &presentParameters); + const auto hr = swap.chain->Present1 (swap.presentSyncInterval, swap.presentFlags, &presentParameters); jassertquiet (SUCCEEDED (hr)); - // The buffer is now completely filled and ready for dirty rectangles for the next frame - swap.state = SwapChain::State::bufferFilled; + if (FAILED (hr)) + return; + + // We managed to present a frame, so we should avoid rendering anything or calling + // present again until that frame has been shown on-screen. + swapEventReceived = false; + + // There's nothing waiting to be displayed in the backbuffer. + dirtyRegionsInBackBuffer.clear(); JUCE_TRACE_LOG_D2D_PAINT_CALL (etw::direct2dHwndPaintEnd, owner.getFrameId()); } diff --git a/modules/juce_graphics/native/juce_Direct2DResources_windows.cpp b/modules/juce_graphics/native/juce_Direct2DResources_windows.cpp index 68204d3a2f..6c95f613bd 100644 --- a/modules/juce_graphics/native/juce_Direct2DResources_windows.cpp +++ b/modules/juce_graphics/native/juce_Direct2DResources_windows.cpp @@ -501,7 +501,6 @@ public: idle, chainAllocated, bufferAllocated, - bufferFilled }; State state = State::idle; };