diff --git a/qxldod/QxlDod.cpp b/qxldod/QxlDod.cpp index 5ee54f5..2d740be 100755 --- a/qxldod/QxlDod.cpp +++ b/qxldod/QxlDod.cpp @@ -3250,6 +3250,22 @@ NTSTATUS QxlDevice::QueryCurrentMode(PVIDEO_MODE RequestedMode) return Status; } +template +class QxlGenericOperation: public QxlPresentOperation +{ +public: + QxlGenericOperation(const Closure &_closure) : closure(_closure) { PAGED_CODE(); } + void Run() override { PAGED_CODE(); closure(); } +private: + Closure closure; +}; + +template +__forceinline QxlPresentOperation *BuildQxlOperation(Closure &&closure) +{ + return new (PagedPool) QxlGenericOperation(closure); +} + NTSTATUS QxlDevice::SetCurrentMode(ULONG Mode) { PAGED_CODE(); @@ -3258,11 +3274,27 @@ NTSTATUS QxlDevice::SetCurrentMode(ULONG Mode) { if (Mode == m_ModeNumbers[idx]) { - DestroyPrimarySurface(); - CreatePrimarySurface(&m_ModeInfo[idx]); + if (!m_PresentThread) + break; DbgPrint(TRACE_LEVEL_INFORMATION, ("%s device %d: setting current mode %d (%d x %d)\n", __FUNCTION__, m_Id, Mode, m_ModeInfo[idx].VisScreenWidth, m_ModeInfo[idx].VisScreenHeight)); + + // execute the operation in the worker thread to avoiding + // executing drawing commands while changing resolution + KEVENT finishEvent; + KeInitializeEvent(&finishEvent, SynchronizationEvent, FALSE); + ++m_DrawGeneration; + QxlPresentOperation *operation = BuildQxlOperation([=, this, &finishEvent]() { + PAGED_CODE(); + DestroyPrimarySurface(); + CreatePrimarySurface(&m_ModeInfo[idx]); + KeSetEvent(&finishEvent, IO_NO_INCREMENT, FALSE); + }); + if (!operation) + return STATUS_NO_MEMORY; + PostToWorkerThread(operation); + WaitForObject(&finishEvent, NULL); return STATUS_SUCCESS; } } @@ -3892,6 +3924,34 @@ QxlDevice::ExecutePresentDisplayOnly( SrcBltInfo.Height = DstBltInfo.Height; } + uint16_t currentGeneration = m_DrawGeneration; + QxlPresentOperation *operation = BuildQxlOperation([=, this]() { + PAGED_CODE(); + ULONG delayed = 0; + + for (UINT i = 0; pDrawables[i]; ++i) + { + ULONG n = PrepareDrawable(pDrawables[i]); + // only reason why drawables[i] is zeroed is stop flow + if (pDrawables[i]) { + delayed += n; + if (currentGeneration == m_DrawGeneration) + PushDrawable(pDrawables[i]); + else + DiscardDrawable(pDrawables[i]); + } + } + delete[] pDrawables; + if (delayed) { + DbgPrint(TRACE_LEVEL_WARNING, ("%s: %d delayed chunks\n", __FUNCTION__, delayed)); + } + }); + if (!operation) { + MmUnlockPages(ctx->Mdl); + IoFreeMdl(ctx->Mdl); + delete[] pDrawables; + return STATUS_NO_MEMORY; + } // Copy all the scroll rects from source image to video frame buffer. for (UINT i = 0; i < ctx->NumMoves; i++) @@ -3936,7 +3996,7 @@ QxlDevice::ExecutePresentDisplayOnly( pDrawables[nIndex] = NULL; - PostToWorkerThread(pDrawables); + PostToWorkerThread(operation); return STATUS_SUCCESS; } @@ -5164,6 +5224,10 @@ void QxlDevice::StopPresentThread() if (m_PresentThread) { DbgPrint(TRACE_LEVEL_INFORMATION, ("---> %s\n", __FUNCTION__)); + // this cause pending drawing operation to be discarded instead + // of executed, there's no reason to execute them if we are + // destroying the device + ++m_DrawGeneration; PostToWorkerThread(NULL); NTSTATUS Status = ObReferenceObjectByHandle( m_PresentThread, 0, NULL, KernelMode, &pDispatcherObject, NULL); @@ -5247,14 +5311,12 @@ void QxlDevice::PresentThreadRoutine() PAGED_CODE(); int wait; int notify; - ULONG delayed = 0; - QXLDrawable** drawables; DbgPrint(TRACE_LEVEL_INFORMATION, ("--->%s\n", __FUNCTION__)); while (1) { - // Pop a drawables list from the ring + // Pop an operation from the ring // No need for a mutex, only one consumer thread SPICE_RING_CONS_WAIT(m_PresentRing, wait); while (wait) { @@ -5262,35 +5324,22 @@ void QxlDevice::PresentThreadRoutine() DoWaitForObject(&m_PresentEvent, NULL, NULL); SPICE_RING_CONS_WAIT(m_PresentRing, wait); } - drawables = *SPICE_RING_CONS_ITEM(m_PresentRing); + QxlPresentOperation *operation = *SPICE_RING_CONS_ITEM(m_PresentRing); SPICE_RING_POP(m_PresentRing, notify); if (notify) { KeSetEvent(&m_PresentThreadReadyEvent, 0, FALSE); } - if (drawables) { - for (UINT i = 0; drawables[i]; ++i) - { - ULONG n = PrepareDrawable(drawables[i]); - // only reason why drawables[i] is zeroed is stop flow - if (drawables[i]) { - delayed += n; - PushDrawable(drawables[i]); - } - } - delete[] drawables; - } else { + if (!operation) { DbgPrint(TRACE_LEVEL_WARNING, ("%s is being terminated\n", __FUNCTION__)); break; } - if (delayed) { - DbgPrint(TRACE_LEVEL_WARNING, ("%s: %d delayed chunks\n", __FUNCTION__, delayed)); - delayed = 0; - } + operation->Run(); + delete operation; } } -void QxlDevice::PostToWorkerThread(QXLDrawable** drawables) +void QxlDevice::PostToWorkerThread(QxlPresentOperation *operation) { PAGED_CODE(); // Push drawables into PresentRing and notify worker thread @@ -5300,7 +5349,7 @@ void QxlDevice::PostToWorkerThread(QXLDrawable** drawables) WaitForObject(&m_PresentThreadReadyEvent, NULL); SPICE_RING_PROD_WAIT(m_PresentRing, wait); } - *SPICE_RING_PROD_ITEM(m_PresentRing) = drawables; + *SPICE_RING_PROD_ITEM(m_PresentRing) = operation; SPICE_RING_PUSH(m_PresentRing, notify); if (notify) { KeSetEvent(&m_PresentEvent, 0, FALSE); diff --git a/qxldod/QxlDod.h b/qxldod/QxlDod.h index b524577..695b83a 100755 --- a/qxldod/QxlDod.h +++ b/qxldod/QxlDod.h @@ -506,8 +506,20 @@ typedef struct DpcCbContext { #define MAX(x, y) (((x) >= (y)) ? (x) : (y)) #define ALIGN(a, b) (((a) + ((b) - 1)) & ~((b) - 1)) +// operation to be run by the presentation thread +class QxlPresentOperation +{ +public: + QxlPresentOperation() {} + QxlPresentOperation(const QxlPresentOperation&) = delete; + void operator=(const QxlPresentOperation&) = delete; + // execute the operation + virtual void Run()=0; + virtual ~QxlPresentOperation() {} +}; + #include "start-packed.h" -SPICE_RING_DECLARE(QXLPresentOnlyRing, QXLDrawable**, 1024); +SPICE_RING_DECLARE(QXLPresentOnlyRing, QxlPresentOperation*, 1024); #include "end-packed.h" class QxlDevice : @@ -620,7 +632,7 @@ private: static void PresentThreadRoutineWrapper(HANDLE dev) { ((QxlDevice *)dev)->PresentThreadRoutine(); } - void PostToWorkerThread(QXLDrawable** drawables); + void PostToWorkerThread(QxlPresentOperation *operation); static LONG GetMaxSourceMappingHeight(RECT* DirtyRects, ULONG NumDirtyRects); @@ -675,6 +687,10 @@ private: QXLPHYSICAL* m_monitor_config_pa; QXLPresentOnlyRing m_PresentRing[1]; + // generation, updated when resolution change + // this is used to detect if a draw command is obsoleted + // and should not be executed + uint16_t m_DrawGeneration; HANDLE m_PresentThread; BOOLEAN m_bActive; };