diff --git a/DDrawCompat/D3dDdi/ScopedCriticalSection.cpp b/DDrawCompat/D3dDdi/ScopedCriticalSection.cpp index 7936075..beda93a 100644 --- a/DDrawCompat/D3dDdi/ScopedCriticalSection.cpp +++ b/DDrawCompat/D3dDdi/ScopedCriticalSection.cpp @@ -1,6 +1,41 @@ +#include #include +#include +#include + +namespace +{ + unsigned g_depth = 0; + Overlay::StatsWindow* g_statsWindow = nullptr; +} namespace D3dDdi { Compat::CriticalSection ScopedCriticalSection::s_cs; + + ScopedCriticalSection::ScopedCriticalSection() + : Compat::ScopedCriticalSection(s_cs) + { + if (0 == g_depth) + { + g_statsWindow = Gdi::GuiThread::getStatsWindow(); + if (g_statsWindow) + { + g_statsWindow->m_ddiUsage.start(); + } + } + ++g_depth; + } + + ScopedCriticalSection::~ScopedCriticalSection() + { + --g_depth; + if (0 == g_depth) + { + if (g_statsWindow) + { + g_statsWindow->m_ddiUsage.stop(); + } + } + } } diff --git a/DDrawCompat/D3dDdi/ScopedCriticalSection.h b/DDrawCompat/D3dDdi/ScopedCriticalSection.h index b3bf370..6415684 100644 --- a/DDrawCompat/D3dDdi/ScopedCriticalSection.h +++ b/DDrawCompat/D3dDdi/ScopedCriticalSection.h @@ -7,7 +7,8 @@ namespace D3dDdi class ScopedCriticalSection : public Compat::ScopedCriticalSection { public: - ScopedCriticalSection() : Compat::ScopedCriticalSection(s_cs) {} + ScopedCriticalSection(); + ~ScopedCriticalSection(); private: static Compat::CriticalSection s_cs; diff --git a/DDrawCompat/DDraw/RealPrimarySurface.cpp b/DDrawCompat/DDraw/RealPrimarySurface.cpp index 851445c..63ab1c2 100644 --- a/DDrawCompat/DDraw/RealPrimarySurface.cpp +++ b/DDrawCompat/DDraw/RealPrimarySurface.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include namespace @@ -319,6 +320,13 @@ namespace Gdi::GuiThread::execute([]() { + auto statsWindow = Gdi::GuiThread::getStatsWindow(); + if (statsWindow) + { + statsWindow->m_present.add(); + statsWindow->update(); + } + auto configWindow = Gdi::GuiThread::getConfigWindow(); if (configWindow) { @@ -531,6 +539,11 @@ namespace DDraw } else { + auto statsWindow = Gdi::GuiThread::getStatsWindow(); + if (statsWindow && statsWindow->isVisible()) + { + statsWindow->updateStats(); + } updateNow(PrimarySurface::getPrimary()); } g_flipEndVsyncCount = D3dDdi::KernelModeThunks::getVsyncCounter() + flipInterval; @@ -557,6 +570,12 @@ namespace DDraw return -1; } + auto statsWindow = Gdi::GuiThread::getStatsWindow(); + if (statsWindow && statsWindow->isVisible()) + { + statsWindow->updateStats(); + } + { Compat::ScopedCriticalSection lock(g_presentCs); if (!g_isUpdateReady) diff --git a/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp b/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp index 1205f7e..1ce9b89 100644 --- a/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp +++ b/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include namespace { @@ -97,6 +99,11 @@ namespace DDraw if (SUCCEEDED(result)) { bltToGdi(This, lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFx); + auto statsWindow = Gdi::GuiThread::getStatsWindow(); + if (statsWindow) + { + statsWindow->m_blit.add(); + } RealPrimarySurface::scheduleUpdate(); PrimarySurface::waitForIdle(); } @@ -116,6 +123,11 @@ namespace DDraw HRESULT result = SurfaceImpl::BltFast(This, dwX, dwY, lpDDSrcSurface, lpSrcRect, dwTrans); if (SUCCEEDED(result)) { + auto statsWindow = Gdi::GuiThread::getStatsWindow(); + if (statsWindow) + { + statsWindow->m_blit.add(); + } RealPrimarySurface::scheduleUpdate(); PrimarySurface::waitForIdle(); } @@ -158,6 +170,12 @@ namespace DDraw return result; } + auto statsWindow = Gdi::GuiThread::getStatsWindow(); + if (statsWindow) + { + statsWindow->m_flip.add(); + } + PrimarySurface::updateFrontResource(); result = RealPrimarySurface::flip(surfaceTargetOverride, dwFlags); if (SUCCEEDED(result) && Config::Settings::FpsLimiter::FLIPEND == Config::fpsLimiter.get()) @@ -233,6 +251,11 @@ namespace DDraw HRESULT result = SurfaceImpl::ReleaseDC(This, hDC); if (SUCCEEDED(result)) { + auto statsWindow = Gdi::GuiThread::getStatsWindow(); + if (statsWindow) + { + statsWindow->m_lock.add(); + } RealPrimarySurface::scheduleUpdate(); } return result; @@ -277,6 +300,11 @@ namespace DDraw HRESULT result = SurfaceImpl::Unlock(This, lpRect); if (SUCCEEDED(result)) { + auto statsWindow = Gdi::GuiThread::getStatsWindow(); + if (statsWindow) + { + statsWindow->m_lock.add(); + } RealPrimarySurface::scheduleUpdate(); } return result; diff --git a/DDrawCompat/DDrawCompat.vcxproj b/DDrawCompat/DDrawCompat.vcxproj index c83d42c..cef928f 100644 --- a/DDrawCompat/DDrawCompat.vcxproj +++ b/DDrawCompat/DDrawCompat.vcxproj @@ -291,6 +291,14 @@ + + + + + + + + @@ -406,6 +414,14 @@ + + + + + + + + diff --git a/DDrawCompat/DDrawCompat.vcxproj.filters b/DDrawCompat/DDrawCompat.vcxproj.filters index e6af4da..6155081 100644 --- a/DDrawCompat/DDrawCompat.vcxproj.filters +++ b/DDrawCompat/DDrawCompat.vcxproj.filters @@ -603,6 +603,30 @@ Header Files\Config\Settings + + Header Files\Overlay + + + Header Files\Overlay + + + Header Files\Overlay + + + Header Files\Overlay + + + Header Files\Overlay + + + Header Files\Overlay + + + Header Files\Overlay + + + Header Files\Overlay + @@ -944,6 +968,30 @@ Source Files\DDraw\Surfaces + + Source Files\Overlay + + + Source Files\Overlay + + + Source Files\Overlay + + + Source Files\Overlay + + + Source Files\Overlay + + + Source Files\Overlay + + + Source Files\Overlay + + + Source Files\Overlay + diff --git a/DDrawCompat/Gdi/GuiThread.cpp b/DDrawCompat/Gdi/GuiThread.cpp index e17aff8..58922f5 100644 --- a/DDrawCompat/Gdi/GuiThread.cpp +++ b/DDrawCompat/Gdi/GuiThread.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace @@ -20,6 +21,7 @@ namespace unsigned g_threadId = 0; Overlay::ConfigWindow* g_configWindow = nullptr; + Overlay::StatsWindow* g_statsWindow = nullptr; HWND g_messageWindow = nullptr; bool g_isReady = false; @@ -78,6 +80,9 @@ namespace return 0; } + Overlay::StatsWindow statsWindow; + g_statsWindow = &statsWindow; + Overlay::ConfigWindow configWindow; g_configWindow = &configWindow; @@ -160,6 +165,11 @@ namespace Gdi return g_configWindow; } + Overlay::StatsWindow* getStatsWindow() + { + return g_statsWindow; + } + bool isGuiThreadWindow(HWND hwnd) { return GetWindowThreadProcessId(hwnd, nullptr) == g_threadId; diff --git a/DDrawCompat/Gdi/GuiThread.h b/DDrawCompat/Gdi/GuiThread.h index 1d33c02..ea59870 100644 --- a/DDrawCompat/Gdi/GuiThread.h +++ b/DDrawCompat/Gdi/GuiThread.h @@ -9,6 +9,7 @@ namespace Overlay { class ConfigWindow; + class StatsWindow; } namespace Gdi @@ -22,6 +23,7 @@ namespace Gdi void setWindowRgn(HWND hwnd, Gdi::Region rgn); Overlay::ConfigWindow* getConfigWindow(); + Overlay::StatsWindow* getStatsWindow(); template void execute(const Func& func) { executeFunc(std::cref(func)); } diff --git a/DDrawCompat/Gdi/WinProc.cpp b/DDrawCompat/Gdi/WinProc.cpp index 75f4028..b98f0eb 100644 --- a/DDrawCompat/Gdi/WinProc.cpp +++ b/DDrawCompat/Gdi/WinProc.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace @@ -141,18 +142,39 @@ namespace switch (uMsg) { case WM_ACTIVATEAPP: - if (!wParam) - { - Gdi::GuiThread::execute([&]() + Gdi::GuiThread::execute([&]() + { + static bool hidden = false; + static bool configVisible = false; + static bool statsVisible = false; + + auto configWindow = Gdi::GuiThread::getConfigWindow(); + auto statsWindow = Gdi::GuiThread::getStatsWindow(); + if (!wParam && !hidden) + { + configVisible = configWindow ? configWindow->isVisible() : false; + statsVisible = statsWindow ? statsWindow->isVisible() : false; + hidden = true; + } + + if (configWindow) + { + configWindow->setVisible(wParam ? configVisible : false); + } + if (statsWindow) + { + statsWindow->setVisible(wParam ? statsVisible : false); + } + + if (wParam) + { + hidden = false; + } + else { - auto configWindow = Gdi::GuiThread::getConfigWindow(); - if (configWindow) - { - configWindow->setVisible(false); - } CALL_ORIG_FUNC(ClipCursor)(nullptr); - }); - } + } + }); break; case WM_CTLCOLORSCROLLBAR: diff --git a/DDrawCompat/Gdi/Window.cpp b/DDrawCompat/Gdi/Window.cpp index b855d2d..32cbc02 100644 --- a/DDrawCompat/Gdi/Window.cpp +++ b/DDrawCompat/Gdi/Window.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace { @@ -395,6 +396,15 @@ namespace Gdi } RECT wr = {}; + auto statsWindow = GuiThread::getStatsWindow(); + if (statsWindow && statsWindow->isVisible()) + { + GetWindowRect(statsWindow->getWindow(), &wr); + auto visibleRegion(getWindowRegion(statsWindow->getWindow())); + visibleRegion.offset(wr.left, wr.top); + layeredWindows.push_back({ statsWindow->getWindow(), wr, visibleRegion }); + } + auto configWindow = GuiThread::getConfigWindow(); if (configWindow && configWindow->isVisible()) { diff --git a/DDrawCompat/Overlay/ComboBoxDropDown.cpp b/DDrawCompat/Overlay/ComboBoxDropDown.cpp index b2643ae..0e6c529 100644 --- a/DDrawCompat/Overlay/ComboBoxDropDown.cpp +++ b/DDrawCompat/Overlay/ComboBoxDropDown.cpp @@ -8,7 +8,7 @@ namespace Overlay { ComboBoxDropDown::ComboBoxDropDown(ComboBoxControl& parent, const std::vector& values) - : Window(&static_cast(parent.getRoot()), calculateRect(parent, values.size())) + : Window(&static_cast(parent.getRoot()), calculateRect(parent, values.size()), WS_BORDER) , m_parent(parent) { for (int i = 0; i < static_cast(values.size()); ++i) @@ -83,5 +83,6 @@ namespace Overlay { m_highlightedChild = nullptr; Window::setVisible(visible); + Input::setCapture(visible ? this : m_parentWindow); } } diff --git a/DDrawCompat/Overlay/ConfigWindow.cpp b/DDrawCompat/Overlay/ConfigWindow.cpp index 67c3c05..71f7a04 100644 --- a/DDrawCompat/Overlay/ConfigWindow.cpp +++ b/DDrawCompat/Overlay/ConfigWindow.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -17,7 +16,7 @@ namespace namespace Overlay { ConfigWindow::ConfigWindow() - : Window(nullptr, { 0, 0, SettingControl::TOTAL_WIDTH, 430 }, Config::configHotKey.get()) + : Window(nullptr, { 0, 0, SettingControl::TOTAL_WIDTH, 430 }, WS_BORDER, Config::configHotKey.get()) , m_buttonCount(0) , m_focus(nullptr) { @@ -186,6 +185,7 @@ namespace Overlay if (isVisible != Window::isVisible()) { Window::setVisible(isVisible); + Input::setCapture(isVisible ? this : nullptr); setFocus(nullptr); } } diff --git a/DDrawCompat/Overlay/LabelControl.cpp b/DDrawCompat/Overlay/LabelControl.cpp index ae10a1a..24c2a1b 100644 --- a/DDrawCompat/Overlay/LabelControl.cpp +++ b/DDrawCompat/Overlay/LabelControl.cpp @@ -33,4 +33,13 @@ namespace Overlay invalidate(); } } + + void LabelControl::setLabel(const std::string label) + { + if (m_label != label) + { + m_label = label; + invalidate(); + } + } } diff --git a/DDrawCompat/Overlay/LabelControl.h b/DDrawCompat/Overlay/LabelControl.h index de2f372..4e33e3d 100644 --- a/DDrawCompat/Overlay/LabelControl.h +++ b/DDrawCompat/Overlay/LabelControl.h @@ -16,7 +16,7 @@ namespace Overlay virtual void onLButtonDown(POINT pos) override; const std::string& getLabel() const { return m_label; } - void setLabel(const std::string label) { m_label = label; } + void setLabel(const std::string label); void setColor(COLORREF color); private: diff --git a/DDrawCompat/Overlay/StatsControl.cpp b/DDrawCompat/Overlay/StatsControl.cpp new file mode 100644 index 0000000..151a874 --- /dev/null +++ b/DDrawCompat/Overlay/StatsControl.cpp @@ -0,0 +1,30 @@ +#include +#include + +namespace Overlay +{ + StatsControl::StatsControl(StatsWindow& parent, const RECT& rect, const std::string& caption, UpdateFunc updateFunc, DWORD style) + : Control(&parent, rect, style) + , m_captionLabel(*this, { rect.left, rect.top, + rect.left + NAME_LABEL_WIDTH, rect.bottom }, caption, 0, WS_DISABLED | WS_VISIBLE) + , m_curLabel(*this, { m_captionLabel.getRect().right, rect.top, + m_captionLabel.getRect().right + VALUE_LABEL_WIDTH, rect.bottom}, std::string(), DT_RIGHT) + , m_avgLabel(*this, { m_curLabel.getRect().right, rect.top, + m_curLabel.getRect().right + VALUE_LABEL_WIDTH, rect.bottom }, std::string(), DT_RIGHT) + , m_minLabel(*this, { m_avgLabel.getRect().right, rect.top, + m_avgLabel.getRect().right + VALUE_LABEL_WIDTH, rect.bottom }, std::string(), DT_RIGHT) + , m_maxLabel(*this, { m_minLabel.getRect().right, rect.top, + m_minLabel.getRect().right + VALUE_LABEL_WIDTH, rect.bottom }, std::string(), DT_RIGHT) + , m_updateFunc(updateFunc) + { + } + + void StatsControl::update(StatsQueue::TickCount tickCount) + { + auto stats = m_updateFunc(tickCount); + m_curLabel.setLabel(stats[0]); + m_avgLabel.setLabel(stats[1]); + m_minLabel.setLabel(stats[2]); + m_maxLabel.setLabel(stats[3]); + } +} diff --git a/DDrawCompat/Overlay/StatsControl.h b/DDrawCompat/Overlay/StatsControl.h new file mode 100644 index 0000000..9f07460 --- /dev/null +++ b/DDrawCompat/Overlay/StatsControl.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include +#include + +namespace Overlay +{ + class StatsWindow; + + class StatsControl : public Control + { + public: + static const int NAME_LABEL_WIDTH = 70; + static const int VALUE_LABEL_WIDTH = 40; + + typedef std::function(StatsQueue::TickCount)> UpdateFunc; + + StatsControl(StatsWindow& parent, const RECT& rect, const std::string& caption, UpdateFunc updateFunc, DWORD style); + + void update(StatsQueue::TickCount tickCount); + + private: + LabelControl m_captionLabel; + LabelControl m_curLabel; + LabelControl m_avgLabel; + LabelControl m_minLabel; + LabelControl m_maxLabel; + UpdateFunc m_updateFunc; + }; +} diff --git a/DDrawCompat/Overlay/StatsEventCount.cpp b/DDrawCompat/Overlay/StatsEventCount.cpp new file mode 100644 index 0000000..42355af --- /dev/null +++ b/DDrawCompat/Overlay/StatsEventCount.cpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +StatsEventCount::StatsEventCount() + : m_sampleCounts(TICKS_PER_SEC) + , m_sampleCount(0) + , m_totalSampleCount(0) +{ +} + +void StatsEventCount::add(TickCount tickCount) +{ + setTickCount(tickCount); + m_sampleCount++; +} + +void StatsEventCount::finalize(SampleCount& sampleCount, Stat& sum, Stat& min, Stat& max) +{ + const uint32_t index = getCurrentTickCount() % TICKS_PER_SEC; + m_totalSampleCount += m_sampleCount; + m_totalSampleCount -= m_sampleCounts[index]; + m_sampleCounts[index] = m_sampleCount; + m_sampleCount = 0; + + sum = m_totalSampleCount; + min = m_totalSampleCount; + max = m_totalSampleCount; + sampleCount = 1; +} + +void StatsEventCount::resetTickCount() +{ + std::fill(m_sampleCounts.begin(), m_sampleCounts.end(), 0); + m_sampleCount = 0; + m_totalSampleCount = 0; +} diff --git a/DDrawCompat/Overlay/StatsEventCount.h b/DDrawCompat/Overlay/StatsEventCount.h new file mode 100644 index 0000000..d901937 --- /dev/null +++ b/DDrawCompat/Overlay/StatsEventCount.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class StatsEventCount : public StatsQueue +{ +public: + StatsEventCount(); + + void add(TickCount tickCount); + +private: + virtual void finalize(SampleCount& sampleCount, Stat& sum, Stat& min, Stat& max) override; + virtual void resetTickCount() override; + + std::vector m_sampleCounts; + SampleCount m_sampleCount; + SampleCount m_totalSampleCount; +}; diff --git a/DDrawCompat/Overlay/StatsEventGroup.cpp b/DDrawCompat/Overlay/StatsEventGroup.cpp new file mode 100644 index 0000000..57a37a8 --- /dev/null +++ b/DDrawCompat/Overlay/StatsEventGroup.cpp @@ -0,0 +1,15 @@ +#include + +StatsEventGroup::StatsEventGroup() + : m_rate(m_time) +{ +} + +void StatsEventGroup::add() +{ + auto qpcNow = Time::queryPerformanceCounter(); + auto tickCount = StatsQueue::getTickCount(qpcNow); + + m_count.add(tickCount); + m_time.add(tickCount, qpcNow); +} diff --git a/DDrawCompat/Overlay/StatsEventGroup.h b/DDrawCompat/Overlay/StatsEventGroup.h new file mode 100644 index 0000000..3283bcc --- /dev/null +++ b/DDrawCompat/Overlay/StatsEventGroup.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +class StatsEventGroup +{ +public: + StatsEventGroup(); + + void add(); + + StatsEventCount m_count; + StatsEventTime m_time; + StatsEventRate m_rate; +}; diff --git a/DDrawCompat/Overlay/StatsEventRate.cpp b/DDrawCompat/Overlay/StatsEventRate.cpp new file mode 100644 index 0000000..198918d --- /dev/null +++ b/DDrawCompat/Overlay/StatsEventRate.cpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +StatsEventRate::StatsEventRate(StatsEventTime& parent) + : m_parent(parent) +{ +} + + +StatsQueue::Stats StatsEventRate::getRawStats(TickCount tickCount) +{ + auto stats = m_parent.getRawStats(tickCount); + std::swap(stats.min, stats.max); + return stats; +} + +double StatsEventRate::convert(double stat) +{ + return Time::g_qpcFrequency / stat; +} diff --git a/DDrawCompat/Overlay/StatsEventRate.h b/DDrawCompat/Overlay/StatsEventRate.h new file mode 100644 index 0000000..09884ec --- /dev/null +++ b/DDrawCompat/Overlay/StatsEventRate.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class StatsEventTime; + +class StatsEventRate : public StatsQueue +{ +public: + StatsEventRate(StatsEventTime& parent); + +private: + virtual double convert(double stat) override; + virtual Stats getRawStats(TickCount tickCount) override; + + StatsEventTime& m_parent; +}; diff --git a/DDrawCompat/Overlay/StatsEventTime.cpp b/DDrawCompat/Overlay/StatsEventTime.cpp new file mode 100644 index 0000000..c3deea5 --- /dev/null +++ b/DDrawCompat/Overlay/StatsEventTime.cpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +StatsEventTime::StatsEventTime() + : m_qpcLast(0) +{ +} + +void StatsEventTime::add(TickCount tickCount, long long qpcNow) +{ + if (0 != m_qpcLast && qpcNow - m_qpcLast < HISTORY_TIME * Time::g_qpcFrequency) + { + addSample(tickCount, qpcNow - m_qpcLast); + } + m_qpcLast = qpcNow; +} + +double StatsEventTime::convert(double stat) +{ + return 1000 * stat / Time::g_qpcFrequency; +} diff --git a/DDrawCompat/Overlay/StatsEventTime.h b/DDrawCompat/Overlay/StatsEventTime.h new file mode 100644 index 0000000..53a92d3 --- /dev/null +++ b/DDrawCompat/Overlay/StatsEventTime.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class StatsEventTime : public StatsQueue +{ +public: + StatsEventTime(); + + void add(TickCount tickCount, long long qpcNow); + +private: + friend class StatsEventRate; + + virtual double convert(double stat) override; + + long long m_qpcLast; +}; diff --git a/DDrawCompat/Overlay/StatsQueue.cpp b/DDrawCompat/Overlay/StatsQueue.cpp new file mode 100644 index 0000000..6a957e9 --- /dev/null +++ b/DDrawCompat/Overlay/StatsQueue.cpp @@ -0,0 +1,138 @@ +#include + +namespace +{ + bool greaterEqual(StatsQueue::Stat a, StatsQueue::Stat b) + { + return a >= b; + } + + bool lessEqual(StatsQueue::Stat a, StatsQueue::Stat b) + { + return a <= b; + } +} + +StatsQueue::StatsQueue() + : m_sums(HISTORY_SIZE) + , m_sampleCounts(HISTORY_SIZE) + , m_currentTickCount(0) + , m_sampleCount(0) + , m_sum(0) + , m_min(0) + , m_max(0) + , m_totalSampleCount(0) + , m_totalSum(0) +{ +} + +void StatsQueue::addSample(TickCount tickCount, Stat stat) +{ + setTickCount(tickCount); + + if (0 == m_sampleCount) + { + m_min = stat; + m_max = stat; + } + else if (stat < m_min) + { + m_min = stat; + } + else if (stat > m_max) + { + m_max = stat; + } + + ++m_sampleCount; + m_sum += stat; +} + +double StatsQueue::getAvg(Stat sum, SampleCount sampleCount) const +{ + return 0 == sampleCount ? NAN : (static_cast(sum) / sampleCount); +} + +StatsQueue::Stats StatsQueue::getRawStats(TickCount tickCount) +{ + setTickCount(tickCount); + Stats stats = {}; + const uint32_t index = (m_currentTickCount - 1) % HISTORY_SIZE; + stats.cur = getAvg(m_sums[index], m_sampleCounts[index]); + stats.avg = getAvg(m_totalSum, m_totalSampleCount); + stats.min = m_minQueue.empty() ? NAN : m_minQueue.front().stat; + stats.max = m_maxQueue.empty() ? NAN : m_maxQueue.front().stat; + return stats; +} + +StatsQueue::SampleCount StatsQueue::getSampleCount(TickCount tickCount) const +{ + return m_sampleCounts[tickCount % HISTORY_SIZE]; +} + +StatsQueue::Stats StatsQueue::getStats(TickCount tickCount) +{ + Stats stats = getRawStats(tickCount); + stats.cur = convert(stats.cur); + stats.avg = convert(stats.avg); + stats.min = convert(stats.min); + stats.max = convert(stats.max); + return stats; +} + +void StatsQueue::push() +{ + finalize(m_sampleCount, m_sum, m_min, m_max); + + uint32_t index = m_currentTickCount % HISTORY_SIZE; + m_totalSampleCount -= m_sampleCounts[index]; + m_totalSampleCount += m_sampleCount; + m_totalSum -= m_sums[index]; + m_totalSum += m_sum; + m_sampleCounts[index] = m_sampleCount; + m_sums[index] = m_sum; + + pushToMinMaxQueue(m_minQueue, m_min, lessEqual); + pushToMinMaxQueue(m_maxQueue, m_max, greaterEqual); + + m_sampleCount = 0; + m_sum = 0; + m_min = 0; + m_max = 0; + ++m_currentTickCount; +} + +void StatsQueue::pushToMinMaxQueue(std::deque& queue, Stat stat, bool compare(Stat, Stat)) +{ + if (0 != m_sampleCount) + { + while (!queue.empty() && compare(stat, queue.back().stat)) + { + queue.pop_back(); + } + } + + while (!queue.empty() && m_currentTickCount - queue.front().tickCount >= HISTORY_SIZE) + { + queue.pop_front(); + } + + if (0 != m_sampleCount) + { + queue.push_back({ m_currentTickCount, stat }); + } +} + +void StatsQueue::setTickCount(TickCount tickCount) +{ + if (tickCount - m_currentTickCount > HISTORY_SIZE) + { + m_currentTickCount = tickCount - HISTORY_SIZE; + resetTickCount(); + } + + while (m_currentTickCount < tickCount) + { + push(); + } +} diff --git a/DDrawCompat/Overlay/StatsQueue.h b/DDrawCompat/Overlay/StatsQueue.h new file mode 100644 index 0000000..1d1b3a8 --- /dev/null +++ b/DDrawCompat/Overlay/StatsQueue.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +#include + +class StatsQueue +{ +public: + static const uint32_t TICKS_PER_SEC = 5; + static const uint32_t HISTORY_TIME = 3; + static const uint32_t HISTORY_SIZE = HISTORY_TIME * TICKS_PER_SEC; + + typedef uint32_t SampleCount; + typedef uint64_t Stat; + typedef uint64_t TickCount; + + struct Stats + { + double cur; + double avg; + double min; + double max; + }; + + StatsQueue(); + + void addSample(TickCount tickCount, Stat stat); + Stats getStats(TickCount tickCount); + + TickCount getCurrentTickCount() const { return m_currentTickCount; } + + static long long getQpc(TickCount tickCount) + { + return tickCount * Time::g_qpcFrequency / TICKS_PER_SEC; + } + + static TickCount getTickCount(long long qpc = Time::queryPerformanceCounter()) + { + return qpc * TICKS_PER_SEC / Time::g_qpcFrequency; + } + +protected: + virtual Stats getRawStats(TickCount tickCount); + SampleCount getSampleCount(TickCount tickCount) const; + void setTickCount(TickCount tickCount); + +private: + struct TimestampedStat + { + TickCount tickCount; + Stat stat; + }; + + virtual double convert(double stat) { return stat; } + virtual void finalize(SampleCount& /*sampleCount*/, Stat& /*sum*/, Stat& /*min*/, Stat& /*max*/) {} + virtual void resetTickCount() {} + + double getAvg(Stat sum, SampleCount sampleCount) const; + + void push(); + void pushToMinMaxQueue(std::deque& queue, Stat stat, bool compare(Stat, Stat)); + + std::vector m_sums; + std::vector m_sampleCounts; + std::deque m_minQueue; + std::deque m_maxQueue; + TickCount m_currentTickCount; + SampleCount m_sampleCount; + Stat m_sum; + Stat m_min; + Stat m_max; + SampleCount m_totalSampleCount; + Stat m_totalSum; +}; diff --git a/DDrawCompat/Overlay/StatsTimer.cpp b/DDrawCompat/Overlay/StatsTimer.cpp new file mode 100644 index 0000000..40972ab --- /dev/null +++ b/DDrawCompat/Overlay/StatsTimer.cpp @@ -0,0 +1,52 @@ +#include + +StatsTimer::StatsTimer() + : m_qpcStart(0) + , m_qpcSum(0) +{ +} + +double StatsTimer::convert(double stat) +{ + return 100 * stat * StatsQueue::TICKS_PER_SEC / Time::g_qpcFrequency; +} + +void StatsTimer::finalize(SampleCount& sampleCount, Stat& sum, Stat& min, Stat& max) +{ + if (0 != m_qpcStart) + { + auto qpcTickEnd = getQpc(getCurrentTickCount() + 1); + m_qpcSum += qpcTickEnd - m_qpcStart; + m_qpcStart = qpcTickEnd; + } + + sampleCount = 1; + sum = m_qpcSum; + min = m_qpcSum; + max = m_qpcSum; + m_qpcSum = 0; +} + +void StatsTimer::resetTickCount() +{ + if (0 != m_qpcStart) + { + m_qpcStart = getQpc(getCurrentTickCount()); + } + m_qpcSum = 0; +} + +void StatsTimer::start() +{ + auto qpcStart = Time::queryPerformanceCounter(); + setTickCount(getTickCount(qpcStart)); + m_qpcStart = qpcStart; +} + +void StatsTimer::stop() +{ + auto qpcEnd = Time::queryPerformanceCounter(); + setTickCount(getTickCount(qpcEnd)); + m_qpcSum += qpcEnd - m_qpcStart; + m_qpcStart = 0; +} diff --git a/DDrawCompat/Overlay/StatsTimer.h b/DDrawCompat/Overlay/StatsTimer.h new file mode 100644 index 0000000..9889525 --- /dev/null +++ b/DDrawCompat/Overlay/StatsTimer.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class StatsTimer : public StatsQueue +{ +public: + StatsTimer(); + + void start(); + void stop(); + +private: + virtual double convert(double stat) override; + virtual void finalize(SampleCount& sampleCount, Stat& sum, Stat& min, Stat& max) override; + virtual void resetTickCount() override; + + long long m_qpcStart; + long long m_qpcSum; +}; diff --git a/DDrawCompat/Overlay/StatsWindow.cpp b/DDrawCompat/Overlay/StatsWindow.cpp new file mode 100644 index 0000000..7a6ddc2 --- /dev/null +++ b/DDrawCompat/Overlay/StatsWindow.cpp @@ -0,0 +1,122 @@ +#include +#include + +#include +#include +#include +#include +#include + +namespace +{ + class UpdateStats + { + public: + UpdateStats(StatsQueue& statsQueue) + : m_statsQueue(statsQueue) + { + } + + std::array operator()(StatsQueue::TickCount tickCount) const + { + auto stats = m_statsQueue.getStats(tickCount); + return { toString(stats.cur), toString(stats.avg), toString(stats.min), toString(stats.max) }; + } + + private: + std::string toString(double stat) const + { + if (std::isnan(stat)) + { + return "-"; + } + + const char unitLetter[] = { 0, 'k', 'm', 'b' }; + std::size_t unitIndex = 0; + while (stat >= 1000 && unitIndex + 1 < sizeof(unitLetter)) + { + ++unitIndex; + stat /= 1000; + } + + char buf[20] = {}; + if (0 == unitIndex) + { + snprintf(buf, sizeof(buf), "%.0f", stat); + return buf; + } + + auto len = snprintf(buf, sizeof(buf), "%.2f", stat); + const auto decimalPoint = strchr(buf, '.'); + const auto intLen = decimalPoint ? decimalPoint - buf : len; + if (len > 4) + { + len = intLen >= 3 ? intLen : 4; + } + buf[len] = unitLetter[unitIndex]; + buf[len + 1] = 0; + return buf; + } + + StatsQueue& m_statsQueue; + }; +} + +namespace Overlay +{ + StatsWindow::StatsWindow() + : Window(nullptr, { 0, 0, StatsControl::NAME_LABEL_WIDTH + 4 * StatsControl::VALUE_LABEL_WIDTH, 105 + BORDER }, + 0, Input::parseHotKey("shift+f12")) + { + addControl("", [](StatsQueue::TickCount) { return std::array{ "cur", "avg", "min", "max" }; }, + WS_VISIBLE | WS_DISABLED).update(0); + addControl("Present rate", UpdateStats(m_present.m_rate)); + addControl("Flip rate", UpdateStats(m_flip.m_rate)); + addControl("Blit count", UpdateStats(m_blit.m_count)); + addControl("Lock count", UpdateStats(m_lock.m_count)); + addControl("DDI usage", UpdateStats(m_ddiUsage)); + addControl("GDI objects", UpdateStats(m_gdiObjects)); + } + + StatsControl& StatsWindow::addControl(const std::string& name, StatsControl::UpdateFunc updateFunc, DWORD style) + { + const int index = m_statsControls.size(); + const int rowHeight = 15; + + RECT rect = { 0, index * rowHeight + BORDER / 2, m_rect.right, (index + 1) * rowHeight + BORDER / 2 }; + return m_statsControls.emplace_back(*this, rect, name, updateFunc, style); + } + + RECT StatsWindow::calculateRect(const RECT& monitorRect) const + { + RECT r = { 0, 0, m_rect.right - m_rect.left, m_rect.bottom - m_rect.top }; + OffsetRect(&r, monitorRect.left + monitorRect.right - r.right, monitorRect.top); + return r; + } + + HWND StatsWindow::getTopmost() const + { + auto configWindow = Gdi::GuiThread::getConfigWindow(); + return (configWindow && configWindow->isVisible()) ? configWindow->getWindow() : Window::getTopmost(); + } + + void StatsWindow::updateStats() + { + static auto prevTickCount = StatsQueue::getTickCount() - 1; + m_tickCount = StatsQueue::getTickCount(); + if (m_tickCount == prevTickCount) + { + return; + } + + m_gdiObjects.addSample(m_tickCount, GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS)); + for (auto& statsControl : m_statsControls) + { + if (statsControl.isEnabled()) + { + statsControl.update(m_tickCount); + } + } + prevTickCount = m_tickCount; + } +} diff --git a/DDrawCompat/Overlay/StatsWindow.h b/DDrawCompat/Overlay/StatsWindow.h new file mode 100644 index 0000000..fc60be6 --- /dev/null +++ b/DDrawCompat/Overlay/StatsWindow.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Overlay +{ + class StatsWindow : public Window + { + public: + StatsWindow(); + + void updateStats(); + + StatsEventGroup m_present; + StatsEventGroup m_flip; + StatsEventGroup m_blit; + StatsEventGroup m_lock; + StatsTimer m_ddiUsage; + StatsQueue m_gdiObjects; + + private: + StatsControl& addControl(const std::string& name, StatsControl::UpdateFunc updateFunc, DWORD style = WS_VISIBLE); + + virtual RECT calculateRect(const RECT& monitorRect) const override; + virtual HWND getTopmost() const override; + + std::list m_statsControls; + uint64_t m_tickCount; + }; +} diff --git a/DDrawCompat/Overlay/Window.cpp b/DDrawCompat/Overlay/Window.cpp index 64ab231..264a259 100644 --- a/DDrawCompat/Overlay/Window.cpp +++ b/DDrawCompat/Overlay/Window.cpp @@ -45,8 +45,8 @@ namespace namespace Overlay { - Window::Window(Window* parentWindow, const RECT& rect, const Input::HotKey& hotKey) - : Control(nullptr, rect, WS_BORDER) + Window::Window(Window* parentWindow, const RECT& rect, DWORD style, const Input::HotKey& hotKey) + : Control(nullptr, rect, style) , m_hwnd(Gdi::PresentationWindow::create(parentWindow ? parentWindow->m_hwnd : nullptr)) , m_parentWindow(parentWindow) , m_transparency(25) @@ -99,6 +99,11 @@ namespace Overlay { } + HWND Window::getTopmost() const + { + return DDraw::RealPrimarySurface::getTopmost(); + } + void Window::invalidate() { m_invalid = true; @@ -122,7 +127,6 @@ namespace Overlay if (m_style & WS_VISIBLE) { updatePos(); - Input::setCapture(this); } else { @@ -132,8 +136,8 @@ namespace Overlay capture->setVisible(false); } ShowWindow(m_hwnd, SW_HIDE); - Input::setCapture(m_parentWindow); } + DDraw::RealPrimarySurface::scheduleUpdate(); } LRESULT CALLBACK Window::staticWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) @@ -213,7 +217,7 @@ namespace Overlay m_rect = calculateRect({ monitorRect.left / m_scaleFactor, monitorRect.top / m_scaleFactor, monitorRect.right / m_scaleFactor, monitorRect.bottom / m_scaleFactor }); - CALL_ORIG_FUNC(SetWindowPos)(m_hwnd, DDraw::RealPrimarySurface::getTopmost(), + CALL_ORIG_FUNC(SetWindowPos)(m_hwnd, getTopmost(), m_rect.left * m_scaleFactor, m_rect.top * m_scaleFactor, (m_rect.right - m_rect.left) * m_scaleFactor, (m_rect.bottom - m_rect.top) * m_scaleFactor, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_NOSENDCHANGING | SWP_SHOWWINDOW); diff --git a/DDrawCompat/Overlay/Window.h b/DDrawCompat/Overlay/Window.h index 5c7df0e..cc0ae43 100644 --- a/DDrawCompat/Overlay/Window.h +++ b/DDrawCompat/Overlay/Window.h @@ -13,7 +13,7 @@ namespace Overlay class Window : public Control { public: - Window(Window* parentWindow, const RECT& rect, const Input::HotKey& hotKey = {}); + Window(Window* parentWindow, const RECT& rect, DWORD style, const Input::HotKey& hotKey = {}); virtual ~Window() override; virtual RECT calculateRect(const RECT& monitorRect) const = 0; @@ -30,6 +30,8 @@ namespace Overlay Window* m_parentWindow; int m_transparency; + virtual HWND getTopmost() const; + void updatePos(); private: