diff --git a/CHANGELOG.md b/CHANGELOG.md index bb6a9b1..990544b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ All notable changes to Q1View are documented here. Releases follow [semantic ver ## [Unreleased] +### Fixed +- Viewer: completed frames are now presented atomically through a DXGI flip-model swap chain, removing the double-DWM-wait bottleneck that limited 60 fps video to roughly 30 displayed frames per second; the verified GDI path remains available as a fallback, and unrotated OpenCV video decodes directly into the playback buffer to sustain real-time 59.94 fps presentation. (issue #90) + --- ## [2.7.8] — 2026-08-18 diff --git a/Viewer/DxgiPresenter.cpp b/Viewer/DxgiPresenter.cpp new file mode 100644 index 0000000..f2cde15 --- /dev/null +++ b/Viewer/DxgiPresenter.cpp @@ -0,0 +1,218 @@ +#include "stdafx.h" +#include "DxgiPresenter.h" + +#include "QDebug.h" + +#include + +#pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "dxgi.lib") + +using Microsoft::WRL::ComPtr; + +DxgiPresenter::DxgiPresenter() + : mHwnd(NULL) + , mWidth(0) + , mHeight(0) + , mDisabled(::GetEnvironmentVariableW(L"Q1VIEW_DISABLE_DXGI", NULL, 0) != 0) +{ + if (mDisabled) + LOGINF("%s", "DXGI presenter disabled by Q1VIEW_DISABLE_DXGI"); +} + +DxgiPresenter::~DxgiPresenter() +{ + Reset(); +} + +void DxgiPresenter::Reset() +{ + if (mContext) + mContext->ClearState(); + + mBackBuffer.Reset(); + mUploadTexture.Reset(); + mSwapChain.Reset(); + mContext.Reset(); + mDevice.Reset(); + mHwnd = NULL; + mWidth = 0; + mHeight = 0; +} + +bool DxgiPresenter::Fail(const char *operation, HRESULT hr) +{ + LOGWRN("DXGI presenter %s failed (0x%08lx); using GDI fallback", + operation, static_cast(hr)); + Reset(); + mDisabled = true; + return false; +} + +bool DxgiPresenter::CreateDevice() +{ + static const D3D_FEATURE_LEVEL levels[] = { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + }; + + D3D_FEATURE_LEVEL selectedLevel = D3D_FEATURE_LEVEL_10_0; + HRESULT hr = ::D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, levels, _countof(levels), + D3D11_SDK_VERSION, &mDevice, &selectedLevel, &mContext); + if (FAILED(hr)) { + hr = ::D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_WARP, NULL, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, levels, _countof(levels), + D3D11_SDK_VERSION, &mDevice, &selectedLevel, &mContext); + } + + return SUCCEEDED(hr) || Fail("device creation", hr); +} + +bool DxgiPresenter::CreateSwapChain(HWND hwnd, UINT width, UINT height) +{ + ComPtr dxgiDevice; + HRESULT hr = mDevice.As(&dxgiDevice); + if (FAILED(hr)) + return Fail("device query", hr); + + ComPtr adapter; + hr = dxgiDevice->GetAdapter(&adapter); + if (FAILED(hr)) + return Fail("adapter query", hr); + + ComPtr factory; + hr = adapter->GetParent(IID_PPV_ARGS(&factory)); + if (FAILED(hr)) + return Fail("factory query", hr); + + DXGI_SWAP_CHAIN_DESC1 desc = {}; + desc.Width = width; + desc.Height = height; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = 2; + desc.Scaling = DXGI_SCALING_STRETCH; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + + hr = factory->CreateSwapChainForHwnd(mDevice.Get(), hwnd, &desc, NULL, NULL, + &mSwapChain); + if (FAILED(hr)) { + mSwapChain.Reset(); + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + hr = factory->CreateSwapChainForHwnd(mDevice.Get(), hwnd, &desc, NULL, NULL, + &mSwapChain); + } + if (FAILED(hr)) + return Fail("swap-chain creation", hr); + + factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER); + mHwnd = hwnd; + return CreateSizeDependentResources(width, height); +} + +bool DxgiPresenter::CreateSizeDependentResources(UINT width, UINT height) +{ + HRESULT hr = mSwapChain->GetBuffer(0, IID_PPV_ARGS(&mBackBuffer)); + if (FAILED(hr)) + return Fail("back-buffer acquisition", hr); + + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = width; + desc.Height = height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + + hr = mDevice->CreateTexture2D(&desc, NULL, &mUploadTexture); + if (FAILED(hr)) + return Fail("upload-texture creation", hr); + + mWidth = width; + mHeight = height; + return true; +} + +bool DxgiPresenter::Resize(UINT width, UINT height) +{ + mContext->ClearState(); + mBackBuffer.Reset(); + mUploadTexture.Reset(); + + HRESULT hr = mSwapChain->ResizeBuffers(0, width, height, + DXGI_FORMAT_UNKNOWN, 0); + if (FAILED(hr)) + return Fail("swap-chain resize", hr); + + return CreateSizeDependentResources(width, height); +} + +bool DxgiPresenter::EnsureResources(HWND hwnd, UINT width, UINT height) +{ + if (mDisabled || hwnd == NULL || width == 0 || height == 0) + return false; + + if (!mDevice && !CreateDevice()) + return false; + + if (!mSwapChain || mHwnd != hwnd) { + if (mSwapChain) + Reset(); + if (!mDevice && !CreateDevice()) + return false; + if (!CreateSwapChain(hwnd, width, height)) + return false; + LOGINF("DXGI flip-model presenter active (%ux%u)", width, height); + return true; + } + + if (mWidth != width || mHeight != height) + return Resize(width, height); + + return true; +} + +bool DxgiPresenter::Upload(const void *pixels, UINT rowPitch) +{ + D3D11_MAPPED_SUBRESOURCE mapped = {}; + HRESULT hr = mContext->Map(mUploadTexture.Get(), 0, + D3D11_MAP_WRITE_DISCARD, 0, &mapped); + if (FAILED(hr)) + return Fail("texture mapping", hr); + + const BYTE *src = static_cast(pixels); + BYTE *dst = static_cast(mapped.pData); + const UINT copyBytes = mWidth * 4; + for (UINT y = 0; y < mHeight; ++y) + std::memcpy(dst + y * mapped.RowPitch, src + y * rowPitch, copyBytes); + + mContext->Unmap(mUploadTexture.Get(), 0); + mContext->CopyResource(mBackBuffer.Get(), mUploadTexture.Get()); + return true; +} + +bool DxgiPresenter::Present(HWND hwnd, const void *pixels, UINT width, + UINT height, UINT rowPitch) +{ + if (pixels == NULL || rowPitch < width * 4 || + !EnsureResources(hwnd, width, height)) { + return false; + } + + if (!Upload(pixels, rowPitch)) + return false; + + HRESULT hr = mSwapChain->Present(1, 0); + if (FAILED(hr)) + return Fail("present", hr); + + return true; +} diff --git a/Viewer/DxgiPresenter.h b/Viewer/DxgiPresenter.h new file mode 100644 index 0000000..3cf6c69 --- /dev/null +++ b/Viewer/DxgiPresenter.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +// Presents a completed CPU-rendered BGRA frame through an HWND flip-model +// swap chain. All methods must be called on the UI thread. +class DxgiPresenter +{ +public: + DxgiPresenter(); + ~DxgiPresenter(); + + bool Present(HWND hwnd, const void *pixels, UINT width, UINT height, UINT rowPitch); + void Reset(); + bool IsActive() const { return mSwapChain != nullptr; } + +private: + bool EnsureResources(HWND hwnd, UINT width, UINT height); + bool CreateDevice(); + bool CreateSwapChain(HWND hwnd, UINT width, UINT height); + bool Resize(UINT width, UINT height); + bool CreateSizeDependentResources(UINT width, UINT height); + bool Upload(const void *pixels, UINT rowPitch); + bool Fail(const char *operation, HRESULT hr); + + Microsoft::WRL::ComPtr mDevice; + Microsoft::WRL::ComPtr mContext; + Microsoft::WRL::ComPtr mSwapChain; + Microsoft::WRL::ComPtr mUploadTexture; + Microsoft::WRL::ComPtr mBackBuffer; + HWND mHwnd; + UINT mWidth; + UINT mHeight; + bool mDisabled; +}; diff --git a/Viewer/FrmProvideThread.cpp b/Viewer/FrmProvideThread.cpp index 8bd2c10..fa3e1ac 100644 --- a/Viewer/FrmProvideThread.cpp +++ b/Viewer/FrmProvideThread.cpp @@ -4,6 +4,20 @@ #include "ViewerCmn.h" #include "QOcv.h" +void FrmProvideThread::LogPlaybackTrace() const +{ + if (!mTraceEnabled || mTraceFrameCount == 0) { + return; + } + + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + double tickToMs = 1000.0 / static_cast(frequency.QuadPart); + LOGWRN("worker summary: frames=%ld read_avg=%.3fms post_avg=%.3fms", + mTraceFrameCount, mTraceLoadTicks * tickToMs / mTraceFrameCount, + mTracePostProcessTicks * tickToMs / mTraceFrameCount); +} + bool FrmProvideThread::threadLoop() { BYTE *RGB = mBufferPool->checkout(); @@ -11,7 +25,17 @@ bool FrmProvideThread::threadLoop() return false; long frameID = GetNextFrameID(); - bool ok = loadOrigBuf(frameID, mOrigBuf); + const bool directRgb = mColorSpace == QIMAGE_CS_BGR888 && + mBgr888Processor == NULL && mRot == QROT_000 && + supportsDirectRgbLoad(); + LARGE_INTEGER loadStart = {}; + LARGE_INTEGER loadEnd = {}; + if (mTraceEnabled) + QueryPerformanceCounter(&loadStart); + bool ok = directRgb ? loadRgbBuf(frameID, RGB) : + loadOrigBuf(frameID, mOrigBuf); + if (mTraceEnabled) + QueryPerformanceCounter(&loadEnd); if (!ok) { cancelFrameReservation(frameID); sendQuitMsg(frameID); @@ -19,8 +43,21 @@ bool FrmProvideThread::threadLoop() return false; } - BufferInfo bi = PostProcess(mColorSpace, mBgr888Processor, mW, mH, - mOrigBuf, RGB, mBufOffset2, mBufOffset3, mRot, mCsc2Rgb888, frameID); + LARGE_INTEGER postEnd = {}; + BufferInfo bi; + if (directRgb) { + bi.ID = frameID; + bi.addr = RGB; + } else { + bi = PostProcess(mColorSpace, mBgr888Processor, mW, mH, + mOrigBuf, RGB, mBufOffset2, mBufOffset3, mRot, mCsc2Rgb888, frameID); + } + if (mTraceEnabled) { + QueryPerformanceCounter(&postEnd); + mTraceFrameCount++; + mTraceLoadTicks += loadEnd.QuadPart - loadStart.QuadPart; + mTracePostProcessTicks += postEnd.QuadPart - loadEnd.QuadPart; + } mBufferQueue->ordered_push(bi, frameID); diff --git a/Viewer/FrmProvideThread.h b/Viewer/FrmProvideThread.h index a31f20a..2e1c2bc 100644 --- a/Viewer/FrmProvideThread.h +++ b/Viewer/FrmProvideThread.h @@ -26,6 +26,10 @@ class FrmProvideThread : public SThread , mBufOffset3(0) , mBgr888Processor(pBgr888Processor) , mRot(QROT_000) + , mTraceEnabled(false) + , mTraceFrameCount(0) + , mTraceLoadTicks(0) + , mTracePostProcessTicks(0) {} virtual ~FrmProvideThread(void) @@ -34,6 +38,8 @@ class FrmProvideThread : public SThread delete [] mOrigBuf; } + void LogPlaybackTrace() const; + inline long GetNextFrameID() { return qcmn_atomic_inc(mPlayFrameIdPtr); @@ -41,6 +47,11 @@ class FrmProvideThread : public SThread inline bool setup(CViewerDoc *pDoc) { + mTraceEnabled = + ::GetEnvironmentVariableW(L"Q1VIEW_TRACE_PLAYBACK", NULL, 0) != 0; + mTraceFrameCount = 0; + mTraceLoadTicks = 0; + mTracePostProcessTicks = 0; mW = pDoc->mW; mH = pDoc->mH; mRot = pDoc->mRot; @@ -70,6 +81,8 @@ class FrmProvideThread : public SThread virtual bool setupDetail(CViewerDoc *pDoc) = 0; virtual void sendQuitMsg(long frameID) = 0; virtual bool loadOrigBuf(long frameID, BYTE *buf) = 0; + virtual bool supportsDirectRgbLoad() const { return false; } + virtual bool loadRgbBuf(long frameID, BYTE *buf) { return false; } virtual void cancelFrameReservation(long) {} SSafeCQ *mBufferQueue; @@ -88,4 +101,8 @@ class FrmProvideThread : public SThread QIMAGE_CSC_FN mCsc2Rgb888; q1::ImageProcessor *mBgr888Processor; QROTATION mRot; + bool mTraceEnabled; + long mTraceFrameCount; + LONGLONG mTraceLoadTicks; + LONGLONG mTracePostProcessTicks; }; diff --git a/Viewer/VidCapFrmSrc.h b/Viewer/VidCapFrmSrc.h index f7c2850..7ffa694 100644 --- a/Viewer/VidCapFrmSrc.h +++ b/Viewer/VidCapFrmSrc.h @@ -108,6 +108,7 @@ class VidCapFrmSrc : public FrmSrc { virtual inline void Stop() { mVidCapThread->requestExitAndWait(); + mVidCapThread->LogPlaybackTrace(); } virtual inline void Release() diff --git a/Viewer/VidCapThread.cpp b/Viewer/VidCapThread.cpp index f58ae4d..12b89b2 100644 --- a/Viewer/VidCapThread.cpp +++ b/Viewer/VidCapThread.cpp @@ -44,6 +44,27 @@ bool VidCapThread::loadOrigBuf(long frameID, BYTE *buf) return true; } +bool VidCapThread::loadRgbBuf(long frameID, BYTE *buf) +{ + if (frameID >= mFrames) + return false; + + const size_t stride = ROUNDUP_DWORD(mW) * QIMG_DST_RGB_BYTES; + cv::Mat matTemp(mH, mW, CV_8UC3, buf, stride); + bool ok = mVidCap.read(matTemp); + if (!ok || matTemp.empty() || matTemp.cols != mW || matTemp.rows != mH || + matTemp.type() != CV_8UC3) { + return false; + } + + if (matTemp.data != buf || matTemp.step != stride) { + for (int y = 0; y < mH; ++y) + memcpy(buf + y * stride, matTemp.ptr(y), mW * QIMG_DST_RGB_BYTES); + } + + return true; +} + void VidCapThread::cancelFrameReservation(long frameID) { // VidCapFrmSrc owns a single sequential worker. GetNextFrameID() reserves diff --git a/Viewer/VidCapThread.h b/Viewer/VidCapThread.h index 7082d25..55fa183 100644 --- a/Viewer/VidCapThread.h +++ b/Viewer/VidCapThread.h @@ -19,6 +19,8 @@ class VidCapThread : public FrmProvideThread bool setupDetail(CViewerDoc *pDoc); void sendQuitMsg(long frameID); bool loadOrigBuf(long frameID, BYTE *buf); + bool supportsDirectRgbLoad() const { return true; } + bool loadRgbBuf(long frameID, BYTE *buf); void cancelFrameReservation(long frameID); cv::VideoCapture &mVidCap; diff --git a/Viewer/Viewer.vcxproj b/Viewer/Viewer.vcxproj index 356d9bd..9c883a3 100644 --- a/Viewer/Viewer.vcxproj +++ b/Viewer/Viewer.vcxproj @@ -212,7 +212,8 @@ - + + @@ -240,7 +241,8 @@ - + + diff --git a/Viewer/ViewerView.cpp b/Viewer/ViewerView.cpp index 2321775..4453e22 100644 --- a/Viewer/ViewerView.cpp +++ b/Viewer/ViewerView.cpp @@ -8,6 +8,7 @@ #include "ViewerDoc.h" #include "ViewerView.h" #include "ViewerFileOrder.h" +#include "DxgiPresenter.h" #include "QMath.h" #include "QDebug.h" @@ -37,7 +38,12 @@ #endif #endif -const bool printPlaySpeed = false; +static bool IsPlaybackTraceEnabled() +{ + static const bool enabled = + ::GetEnvironmentVariableW(L"Q1VIEW_TRACE_PLAYBACK", NULL, 0) != 0; + return enabled; +} #define WM_VIEWER_PLAY_TIMER (WM_APP + 1) @@ -185,6 +191,9 @@ CViewerView::CViewerView() , mTimerID(0) , mPlaybackStartFrameID(0) , mDroppedFrameCount(0) +, mTracePaintCount(0) +, mTraceRenderTicks(0) +, mTracePresentTicks(0) , mPlaybackRate(1.0) , mPlaybackEndPending(false) , mPlayTickPosted(0) @@ -192,6 +201,8 @@ CViewerView::CViewerView() , mBufferPool(NULL) , mKeyProcessing(false) , mPrevBackBitmap(NULL) +, mBackBits(NULL) +, mDxgiPresenter(new DxgiPresenter()) , mBackW(0) , mBackH(0) , mXCursor(-1) @@ -272,6 +283,7 @@ CViewerView::~CViewerView() mMouseMenu.DestroyMenu(); ReleaseBackBuffer(); + delete mDxgiPresenter; if (mRgbBuf) _mm_free(mRgbBuf); @@ -310,7 +322,20 @@ bool CViewerView::EnsureBackBuffer(CDC *pDC) if (!mBackDC.CreateCompatibleDC(pDC)) return false; - if (!mBackBitmap.CreateCompatibleBitmap(pDC, mWClient, mHClient)) { + BITMAPINFO info = {}; + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biWidth = mWClient; + info.bmiHeader.biHeight = -mHClient; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 32; + info.bmiHeader.biCompression = BI_RGB; + + HBITMAP bitmap = ::CreateDIBSection(pDC->GetSafeHdc(), &info, + DIB_RGB_COLORS, &mBackBits, NULL, 0); + if (bitmap == NULL || !mBackBitmap.Attach(bitmap)) { + if (bitmap != NULL) + ::DeleteObject(bitmap); + mBackBits = NULL; mBackDC.DeleteDC(); return false; } @@ -335,22 +360,30 @@ void CViewerView::ReleaseBackBuffer() if (mBackBitmap.GetSafeHandle()) mBackBitmap.DeleteObject(); + mBackBits = NULL; mBackW = 0; mBackH = 0; } -void CViewerView::PresentBackBuffer(CDC *pDC) +bool CViewerView::PresentBackBuffer(CDC *pDC) { + if (!pDC->IsPrinting() && mDxgiPresenter->Present(GetSafeHwnd(), mBackBits, + static_cast(mWClient), static_cast(mHClient), + static_cast(mBackW * 4))) { + return true; + } + pDC->BitBlt(0, 0, mWClient, mHClient, &mBackDC, 0, 0, SRCCOPY); if (!mIsPlaying) - return; + return false; // BitBlt may be GDI-batched, while the cached bitmap is overwritten by the // next playback paint. Complete that copy first, then wait for DWM to present // it so the compositor cannot sample two adjacent frames from the same surface. ::GdiFlush(); ::DwmFlush(); + return false; } void CViewerView::AdjustWindowSize() @@ -776,11 +809,21 @@ void CViewerView::SetPlayTimer(CViewerDoc* pDoc) if (!ok) return; + double startSec = pDoc->mFps > 0.0 ? pDoc->mCurFrameID / pDoc->mFps : 0.0; + bool audioReady = mAudioPlayer.Open(pDoc->mPathName.GetString()); + if (audioReady) { + mAudioPlayer.SetVolume(mVolume); + mAudioPlayer.SetMuted(mVolumeMuted); + } + mIsPlaying = true; mPreKeyFrameStamp = 0; mPlayFrameCount = 0; mPlaybackStartFrameID = pDoc->mCurFrameID; mDroppedFrameCount = 0; + mTracePaintCount = 0; + mTraceRenderTicks = 0; + mTracePresentTicks = 0; mPlaybackEndPending = false; ::InterlockedExchange(&mPlayTickPosted, 0); QueryPerformanceCounter(&mPlaybackStartCounter); @@ -802,12 +845,8 @@ void CViewerView::SetPlayTimer(CViewerDoc* pDoc) return; } - double startSec = pDoc->mFps > 0.0 ? pDoc->mCurFrameID / pDoc->mFps : 0.0; - if (mAudioPlayer.Open(pDoc->mPathName.GetString())) { - mAudioPlayer.SetVolume(mVolume); - mAudioPlayer.SetMuted(mVolumeMuted); + if (audioReady) mAudioPlayer.Play(startSec); - } } // Timer callbacks only post clock ticks, so pausing does not need to wait for @@ -817,6 +856,24 @@ void CViewerView::KillPlayTimer() if (!mIsPlaying) return; + if (IsPlaybackTraceEnabled()) { + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + double elapsedSeconds = (now.QuadPart - mPlaybackStartCounter.QuadPart) / + static_cast(mPlaybackClockFrequency.QuadPart); + LOGWRN("playback summary: backend=%s presented=%d dropped=%ld elapsed=%.3f", + mDxgiPresenter->IsActive() ? "DXGI" : "GDI", mPlayFrameCount, + mDroppedFrameCount, elapsedSeconds); + if (mTracePaintCount > 0) { + double tickToMs = 1000.0 / + static_cast(mPlaybackClockFrequency.QuadPart); + LOGWRN("paint summary: frames=%ld render_avg=%.3fms present_avg=%.3fms", + mTracePaintCount, + mTraceRenderTicks * tickToMs / mTracePaintCount, + mTracePresentTicks * tickToMs / mTracePaintCount); + } + } + mAudioPlayer.Pause(); mIsPlaying = false; timeKillEvent(mTimerID); @@ -1196,6 +1253,11 @@ void CViewerView::OnDraw(CDC *pDC) if (!EnsureBackBuffer(pDC)) return; + const bool tracePlayback = mIsPlaying && IsPlaybackTraceEnabled(); + LARGE_INTEGER renderStart = {}; + if (tracePlayback) + QueryPerformanceCounter(&renderStart); + CDC &memDC = mBackDC; memDC.SetStretchBltMode(COLORONCOLOR); memDC.FillSolidRect(CRect(0, 0, mWClient, mHClient), Q1UI_COLOR_CANVAS_BG); @@ -1212,7 +1274,7 @@ void CViewerView::OnDraw(CDC *pDC) mStableRgbBufferInfo = bi; // Optional playback timing trace. - if (mIsPlaying & printPlaySpeed) + if (mIsPlaying && IsPlaybackTraceEnabled()) PrintPlaySpeed(pDoc->mFps); stopAfterPresent = mPlaybackEndPending; @@ -1274,7 +1336,17 @@ void CViewerView::OnDraw(CDC *pDC) if (pDoc->mDocState == DOC_NEWIMAGE) pDoc->mDocState = DOC_ADJUSTED; - PresentBackBuffer(pDC); + LARGE_INTEGER presentStart = {}; + if (tracePlayback) + QueryPerformanceCounter(&presentStart); + const bool usedDxgi = PresentBackBuffer(pDC); + if (tracePlayback) { + LARGE_INTEGER presentEnd; + QueryPerformanceCounter(&presentEnd); + mTracePaintCount++; + mTraceRenderTicks += presentStart.QuadPart - renderStart.QuadPart; + mTracePresentTicks += presentEnd.QuadPart - presentStart.QuadPart; + } pDoc->mCurFrameID = mStableRgbBufferInfo.ID; @@ -1286,7 +1358,7 @@ void CViewerView::OnDraw(CDC *pDC) if (mKeyProcessing == true) mKeyProcessing = false; - if (mIsPlaying) { + if (mIsPlaying && !usedDxgi) { ::GdiFlush(); ::DwmFlush(); } @@ -2255,6 +2327,7 @@ void CViewerView::OnDestroy() mBufferPool->disable(); mBufferQueue->destroy(); KillPlayTimer(); + mDxgiPresenter->Reset(); } int CViewerView::OnCreate(LPCREATESTRUCT lpCreateStruct) diff --git a/Viewer/ViewerView.h b/Viewer/ViewerView.h index ba657fa..019eec9 100644 --- a/Viewer/ViewerView.h +++ b/Viewer/ViewerView.h @@ -5,6 +5,7 @@ #pragma once struct ViewerSyncInputState; +class DxgiPresenter; #include #include "AudioPlayer.h" @@ -141,6 +142,9 @@ class CViewerView : public CView LARGE_INTEGER mPlaybackStartCounter; long mPlaybackStartFrameID; long mDroppedFrameCount; + long mTracePaintCount; + LONGLONG mTraceRenderTicks; + LONGLONG mTracePresentTicks; double mPlaybackRate; bool mPlaybackEndPending; volatile LONG mPlayTickPosted; @@ -213,7 +217,7 @@ class CViewerView : public CView private: bool EnsureBackBuffer(CDC *pDC); void ReleaseBackBuffer(); - void PresentBackBuffer(CDC *pDC); + bool PresentBackBuffer(CDC *pDC); CRect CvtCoord2Show(const CRect &rt); bool FindFile(CViewerDoc* pDoc, UINT nChar); bool HandleNavigationKey(UINT nChar); @@ -231,6 +235,8 @@ class CViewerView : public CView CDC mBackDC; CBitmap mBackBitmap; CBitmap *mPrevBackBitmap; + void *mBackBits; + DxgiPresenter *mDxgiPresenter; int mBackW; int mBackH;