1
0
mirror of https://github.com/narzoul/DDrawCompat synced 2024-12-30 08:55:36 +01:00

Added stats overlay

This commit is contained in:
narzoul 2022-11-02 21:39:37 +01:00
parent ab443a1ac2
commit 07d77d09dc
32 changed files with 904 additions and 21 deletions

View File

@ -1,6 +1,41 @@
#include <Common/Time.h>
#include <D3dDdi/ScopedCriticalSection.h>
#include <Gdi/GuiThread.h>
#include <Overlay/StatsWindow.h>
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();
}
}
}
}

View File

@ -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;

View File

@ -31,6 +31,7 @@
#include <Gdi/Window.h>
#include <Gdi/WinProc.h>
#include <Overlay/ConfigWindow.h>
#include <Overlay/StatsWindow.h>
#include <Win32/DisplayMode.h>
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)

View File

@ -10,8 +10,10 @@
#include <DDraw/Surfaces/PrimarySurfaceImpl.h>
#include <Dll/Dll.h>
#include <Gdi/Gdi.h>
#include <Gdi/GuiThread.h>
#include <Gdi/Region.h>
#include <Gdi/VirtualScreen.h>
#include <Overlay/StatsWindow.h>
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;

View File

@ -291,6 +291,14 @@
<ClInclude Include="Overlay\LabelControl.h" />
<ClInclude Include="Overlay\ScrollBarControl.h" />
<ClInclude Include="Overlay\SettingControl.h" />
<ClInclude Include="Overlay\StatsControl.h" />
<ClInclude Include="Overlay\StatsEventCount.h" />
<ClInclude Include="Overlay\StatsEventGroup.h" />
<ClInclude Include="Overlay\StatsEventTime.h" />
<ClInclude Include="Overlay\StatsQueue.h" />
<ClInclude Include="Overlay\StatsEventRate.h" />
<ClInclude Include="Overlay\StatsTimer.h" />
<ClInclude Include="Overlay\StatsWindow.h" />
<ClInclude Include="Overlay\Window.h" />
<ClInclude Include="Win32\DisplayMode.h" />
<ClInclude Include="Win32\Log.h" />
@ -406,6 +414,14 @@
<ClCompile Include="Overlay\LabelControl.cpp" />
<ClCompile Include="Overlay\ScrollBarControl.cpp" />
<ClCompile Include="Overlay\SettingControl.cpp" />
<ClCompile Include="Overlay\StatsControl.cpp" />
<ClCompile Include="Overlay\StatsEventCount.cpp" />
<ClCompile Include="Overlay\StatsEventGroup.cpp" />
<ClCompile Include="Overlay\StatsEventTime.cpp" />
<ClCompile Include="Overlay\StatsQueue.cpp" />
<ClCompile Include="Overlay\StatsEventRate.cpp" />
<ClCompile Include="Overlay\StatsTimer.cpp" />
<ClCompile Include="Overlay\StatsWindow.cpp" />
<ClCompile Include="Overlay\Window.cpp" />
<ClCompile Include="Win32\DisplayMode.cpp" />
<ClCompile Include="Win32\Log.cpp" />

View File

@ -603,6 +603,30 @@
<ClInclude Include="Config\Settings\ResolutionScaleFilter.h">
<Filter>Header Files\Config\Settings</Filter>
</ClInclude>
<ClInclude Include="Overlay\StatsWindow.h">
<Filter>Header Files\Overlay</Filter>
</ClInclude>
<ClInclude Include="Overlay\StatsControl.h">
<Filter>Header Files\Overlay</Filter>
</ClInclude>
<ClInclude Include="Overlay\StatsQueue.h">
<Filter>Header Files\Overlay</Filter>
</ClInclude>
<ClInclude Include="Overlay\StatsTimer.h">
<Filter>Header Files\Overlay</Filter>
</ClInclude>
<ClInclude Include="Overlay\StatsEventRate.h">
<Filter>Header Files\Overlay</Filter>
</ClInclude>
<ClInclude Include="Overlay\StatsEventTime.h">
<Filter>Header Files\Overlay</Filter>
</ClInclude>
<ClInclude Include="Overlay\StatsEventCount.h">
<Filter>Header Files\Overlay</Filter>
</ClInclude>
<ClInclude Include="Overlay\StatsEventGroup.h">
<Filter>Header Files\Overlay</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Gdi\Gdi.cpp">
@ -944,6 +968,30 @@
<ClCompile Include="DDraw\Surfaces\PalettizedTextureImpl.cpp">
<Filter>Source Files\DDraw\Surfaces</Filter>
</ClCompile>
<ClCompile Include="Overlay\StatsWindow.cpp">
<Filter>Source Files\Overlay</Filter>
</ClCompile>
<ClCompile Include="Overlay\StatsControl.cpp">
<Filter>Source Files\Overlay</Filter>
</ClCompile>
<ClCompile Include="Overlay\StatsQueue.cpp">
<Filter>Source Files\Overlay</Filter>
</ClCompile>
<ClCompile Include="Overlay\StatsTimer.cpp">
<Filter>Source Files\Overlay</Filter>
</ClCompile>
<ClCompile Include="Overlay\StatsEventRate.cpp">
<Filter>Source Files\Overlay</Filter>
</ClCompile>
<ClCompile Include="Overlay\StatsEventTime.cpp">
<Filter>Source Files\Overlay</Filter>
</ClCompile>
<ClCompile Include="Overlay\StatsEventCount.cpp">
<Filter>Source Files\Overlay</Filter>
</ClCompile>
<ClCompile Include="Overlay\StatsEventGroup.cpp">
<Filter>Source Files\Overlay</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="DDrawCompat.rc">

View File

@ -12,6 +12,7 @@
#include <Gdi/Region.h>
#include <Gdi/WinProc.h>
#include <Overlay/ConfigWindow.h>
#include <Overlay/StatsWindow.h>
#include <Win32/DisplayMode.h>
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;

View File

@ -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 <typename Func>
void execute(const Func& func) { executeFunc(std::cref(func)); }

View File

@ -26,6 +26,7 @@
#include <Gdi/Window.h>
#include <Gdi/WinProc.h>
#include <Overlay/ConfigWindow.h>
#include <Overlay/StatsWindow.h>
#include <Win32/DisplayMode.h>
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:

View File

@ -16,6 +16,7 @@
#include <Gdi/Window.h>
#include <Input/Input.h>
#include <Overlay/ConfigWindow.h>
#include <Overlay/StatsWindow.h>
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())
{

View File

@ -8,7 +8,7 @@
namespace Overlay
{
ComboBoxDropDown::ComboBoxDropDown(ComboBoxControl& parent, const std::vector<std::string>& values)
: Window(&static_cast<Window&>(parent.getRoot()), calculateRect(parent, values.size()))
: Window(&static_cast<Window&>(parent.getRoot()), calculateRect(parent, values.size()), WS_BORDER)
, m_parent(parent)
{
for (int i = 0; i < static_cast<int>(values.size()); ++i)
@ -83,5 +83,6 @@ namespace Overlay
{
m_highlightedChild = nullptr;
Window::setVisible(visible);
Input::setCapture(visible ? this : m_parentWindow);
}
}

View File

@ -1,7 +1,6 @@
#include <fstream>
#include <sstream>
#include <Common/Hook.h>
#include <Common/Log.h>
#include <Config/Config.h>
#include <Gdi/GuiThread.h>
@ -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);
}
}

View File

@ -33,4 +33,13 @@ namespace Overlay
invalidate();
}
}
void LabelControl::setLabel(const std::string label)
{
if (m_label != label)
{
m_label = label;
invalidate();
}
}
}

View File

@ -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:

View File

@ -0,0 +1,30 @@
#include <Overlay/StatsWindow.h>
#include <Overlay/StatsControl.h>
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]);
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include <array>
#include <functional>
#include <Overlay/LabelControl.h>
#include <Overlay/StatsQueue.h>
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<std::array<std::string, 4>(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;
};
}

View File

@ -0,0 +1,37 @@
#pragma once
#include <Overlay/StatsEventCount.h>
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;
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <Overlay/StatsQueue.h>
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<SampleCount> m_sampleCounts;
SampleCount m_sampleCount;
SampleCount m_totalSampleCount;
};

View File

@ -0,0 +1,15 @@
#include <Overlay/StatsEventGroup.h>
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);
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <Overlay/StatsEventCount.h>
#include <Overlay/StatsEventRate.h>
#include <Overlay/StatsEventTime.h>
class StatsEventGroup
{
public:
StatsEventGroup();
void add();
StatsEventCount m_count;
StatsEventTime m_time;
StatsEventRate m_rate;
};

View File

@ -0,0 +1,22 @@
#pragma once
#include <Overlay/StatsEventRate.h>
#include <Overlay/StatsEventTime.h>
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;
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <Overlay/StatsQueue.h>
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;
};

View File

@ -0,0 +1,22 @@
#pragma once
#include <Overlay/StatsEventTime.h>
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;
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <Overlay/StatsQueue.h>
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;
};

View File

@ -0,0 +1,138 @@
#include <Overlay/StatsQueue.h>
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<double>(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<TimestampedStat>& 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();
}
}

View File

@ -0,0 +1,76 @@
#pragma once
#include <deque>
#include <vector>
#include <Common/Time.h>
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<TimestampedStat>& queue, Stat stat, bool compare(Stat, Stat));
std::vector<Stat> m_sums;
std::vector<SampleCount> m_sampleCounts;
std::deque<TimestampedStat> m_minQueue;
std::deque<TimestampedStat> m_maxQueue;
TickCount m_currentTickCount;
SampleCount m_sampleCount;
Stat m_sum;
Stat m_min;
Stat m_max;
SampleCount m_totalSampleCount;
Stat m_totalSum;
};

View File

@ -0,0 +1,52 @@
#include <Overlay/StatsTimer.h>
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;
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <Overlay/StatsQueue.h>
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;
};

View File

@ -0,0 +1,122 @@
#include <array>
#include <functional>
#include <Common/Time.h>
#include <Gdi/GuiThread.h>
#include <Input/Input.h>
#include <Overlay/ConfigWindow.h>
#include <Overlay/StatsWindow.h>
namespace
{
class UpdateStats
{
public:
UpdateStats(StatsQueue& statsQueue)
: m_statsQueue(statsQueue)
{
}
std::array<std::string, 4> 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<std::string, 4>{ "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;
}
}

View File

@ -0,0 +1,38 @@
#pragma once
#include <list>
#include <memory>
#include <string>
#include <Overlay/StatsControl.h>
#include <Overlay/StatsEventGroup.h>
#include <Overlay/StatsQueue.h>
#include <Overlay/StatsTimer.h>
#include <Overlay/Window.h>
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<StatsControl> m_statsControls;
uint64_t m_tickCount;
};
}

View File

@ -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);

View File

@ -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: