diff --git a/OptiScaler/OptiScaler.vcxproj b/OptiScaler/OptiScaler.vcxproj
index 662dfb82..04c6ff4d 100644
--- a/OptiScaler/OptiScaler.vcxproj
+++ b/OptiScaler/OptiScaler.vcxproj
@@ -307,6 +307,7 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y
+
@@ -452,7 +453,6 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y
-
@@ -624,6 +624,7 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y
+
@@ -721,7 +722,6 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y
-
diff --git a/OptiScaler/OptiScaler.vcxproj.filters b/OptiScaler/OptiScaler.vcxproj.filters
index 620a5d4c..91a845dc 100644
--- a/OptiScaler/OptiScaler.vcxproj.filters
+++ b/OptiScaler/OptiScaler.vcxproj.filters
@@ -566,9 +566,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -977,6 +974,9 @@
Header Files
+
+ Header Files
+
Header Files
@@ -1340,9 +1340,6 @@
Source Files
-
- Source Files
-
Source Files
@@ -1610,6 +1607,9 @@
Source Files
+
+ Source Files
+
Source Files
diff --git a/OptiScaler/gpu_time/GpuTime_Vk.cpp b/OptiScaler/gpu_time/GpuTime_Vk.cpp
new file mode 100644
index 00000000..b6b0977c
--- /dev/null
+++ b/OptiScaler/gpu_time/GpuTime_Vk.cpp
@@ -0,0 +1,98 @@
+#include "pch.h"
+#include "GpuTime_Vk.h"
+
+#include
+
+GpuTime_Vk::GpuTime_Vk(VkDevice device, VkPhysicalDevice physicalDevice) : _device(device)
+{
+ if (device == VK_NULL_HANDLE || physicalDevice == VK_NULL_HANDLE)
+ {
+ LOG_ERROR("GpuTime_Vk created with null device");
+ return;
+ }
+
+ // Start and End timestamps per buffer
+ VkQueryPoolCreateInfo queryPoolInfo {};
+ queryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
+ queryPoolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP;
+ queryPoolInfo.queryCount = QUERY_BUFFER_COUNT * 2;
+
+ auto result = vkCreateQueryPool(device, &queryPoolInfo, nullptr, &_queryPool);
+
+ if (result != VK_SUCCESS)
+ {
+ LOG_ERROR("vkCreateQueryPool error: {}", (int) result);
+ _queryPool = VK_NULL_HANDLE;
+ return;
+ }
+
+ VkPhysicalDeviceProperties deviceProperties {};
+ vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
+ _timestampPeriod = deviceProperties.limits.timestampPeriod;
+
+ _init = true;
+}
+
+GpuTime_Vk::~GpuTime_Vk()
+{
+ if (State::Instance().isShuttingDown)
+ return;
+
+ if (_queryPool != VK_NULL_HANDLE)
+ {
+ vkDestroyQueryPool(_device, _queryPool, nullptr);
+ _queryPool = VK_NULL_HANDLE;
+ }
+}
+
+void GpuTime_Vk::Start(VkCommandBuffer cmdBuffer)
+{
+ if (_init && _queryPool != VK_NULL_HANDLE)
+ {
+ _currentFrameIndex = (_currentFrameIndex + 1) % QUERY_BUFFER_COUNT;
+
+ vkCmdResetQueryPool(cmdBuffer, _queryPool, _currentFrameIndex * 2, 2);
+ vkCmdWriteTimestamp(cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, _queryPool, _currentFrameIndex * 2);
+ }
+}
+
+void GpuTime_Vk::End(VkCommandBuffer cmdBuffer)
+{
+ if (_init && _queryPool != VK_NULL_HANDLE)
+ {
+ vkCmdWriteTimestamp(cmdBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, _queryPool, _currentFrameIndex * 2 + 1);
+
+ _trigger[_currentFrameIndex] = true;
+ }
+}
+
+std::optional GpuTime_Vk::ReadGpuTime()
+{
+ std::optional elapsedTimeMs = std::nullopt;
+
+ if (!_init || _queryPool == VK_NULL_HANDLE)
+ return elapsedTimeMs;
+
+ // Try to read the oldest frame's timestamps
+ uint32_t previousFrameIndex = (_currentFrameIndex + 1) % QUERY_BUFFER_COUNT;
+
+ if (!_trigger[previousFrameIndex])
+ return elapsedTimeMs;
+
+ uint64_t timestamps[2] {};
+ auto result = vkGetQueryPoolResults(_device, _queryPool, previousFrameIndex * 2, 2, sizeof(timestamps), timestamps,
+ sizeof(uint64_t), VK_QUERY_RESULT_64_BIT);
+
+ // Results not available yet, the slot will get overwritten by the next Start anyway
+ if (result != VK_SUCCESS)
+ return elapsedTimeMs;
+
+ _trigger[previousFrameIndex] = false;
+
+ if (timestamps[1] < timestamps[0])
+ return elapsedTimeMs;
+
+ elapsedTimeMs = (timestamps[1] - timestamps[0]) * _timestampPeriod / 1e6;
+
+ return elapsedTimeMs;
+}
diff --git a/OptiScaler/gpu_time/GpuTime_Vk.h b/OptiScaler/gpu_time/GpuTime_Vk.h
new file mode 100644
index 00000000..10b933c5
--- /dev/null
+++ b/OptiScaler/gpu_time/GpuTime_Vk.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "SysUtils.h"
+#include
+#include
+#include
+
+class GpuTime_Vk
+{
+ static constexpr int QUERY_BUFFER_COUNT = 3;
+
+ VkDevice _device = VK_NULL_HANDLE;
+ VkQueryPool _queryPool = VK_NULL_HANDLE;
+ double _timestampPeriod = 1.0;
+ std::array _trigger {};
+
+ int _currentFrameIndex = 0;
+ bool _init = false;
+
+ public:
+ GpuTime_Vk(VkDevice device, VkPhysicalDevice physicalDevice);
+ ~GpuTime_Vk();
+
+ void Start(VkCommandBuffer cmdBuffer);
+ void End(VkCommandBuffer cmdBuffer);
+
+ std::optional ReadGpuTime();
+};
+
+class ScopedGpuTime_Vk
+{
+ GpuTime_Vk* _gpuTime;
+ VkCommandBuffer _cmdBuffer;
+
+ public:
+ ScopedGpuTime_Vk(GpuTime_Vk* gpuTime, VkCommandBuffer cmdBuffer) : _gpuTime(gpuTime), _cmdBuffer(cmdBuffer)
+ {
+ if (_gpuTime && _cmdBuffer)
+ _gpuTime->Start(_cmdBuffer);
+ }
+
+ ~ScopedGpuTime_Vk()
+ {
+ if (_gpuTime && _cmdBuffer)
+ _gpuTime->End(_cmdBuffer);
+ }
+};
diff --git a/OptiScaler/hooks/Vulkan_Hooks.cpp b/OptiScaler/hooks/Vulkan_Hooks.cpp
index 426ab87c..ab373c9d 100644
--- a/OptiScaler/hooks/Vulkan_Hooks.cpp
+++ b/OptiScaler/hooks/Vulkan_Hooks.cpp
@@ -8,7 +8,7 @@
#include