Files
OptiScaler/OptiScaler/hooks/Vulkan_Hooks.cpp
T
2026-10-07 00:54:11 +02:00

532 lines
18 KiB
C++

#include "pch.h"
#include "Vulkan_Hooks.h"
#include <Util.h>
#include <Config.h>
#include <SysUtils.h>
#include <menu/menu_overlay_vk.h>
#include <proxies/KernelBase_Proxy.h>
#include <upscalers/IFeature_Vk.h>
#include <misc/FrameLimit.h>
#include "Reflex_Hooks.h"
#include <spoofing/Vulkan_Spoofing.h>
#include <vulkan/vulkan.hpp>
#include <detours/detours.h>
#include <misc/IdentifyGpu.h>
#include "Hook_Utils.h"
// for menu rendering
static VkDevice _device = VK_NULL_HANDLE;
static VkInstance _instance = VK_NULL_HANDLE;
static VkPhysicalDevice _PD = VK_NULL_HANDLE;
static HWND _hwnd = nullptr;
static std::mutex _vkPresentMutex;
PFN_vkCreateDevice o_vkCreateDevice = nullptr;
PFN_vkCreateInstance o_vkCreateInstance = nullptr;
PFN_vkCreateWin32SurfaceKHR o_vkCreateWin32SurfaceKHR = nullptr;
PFN_vkQueuePresentKHR o_QueuePresentKHR = nullptr;
PFN_vkCreateSwapchainKHR o_CreateSwapchainKHR = nullptr;
static PFN_vkGetInstanceProcAddr o_vkGetInstanceProcAddr = nullptr;
static PFN_vkGetDeviceProcAddr o_vkGetDeviceProcAddr = nullptr;
// Those aren't hooked, just grabbed for use
static PFN_vkGetPhysicalDeviceFeatures2 o_vkGetPhysicalDeviceFeatures2 = nullptr;
PFN_vkCreateSemaphore VulkanHooks::o_vkCreateSemaphore = nullptr;
PFN_vkSignalSemaphore VulkanHooks::o_vkSignalSemaphore = nullptr;
PFN_vkAntiLagUpdateAMD VulkanHooks::o_vkAntiLagUpdateAMD = nullptr;
// Forward declaration
static VkResult hkvkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* pPresentInfo);
static VkResult hkvkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain);
static void HookDevice(VkDevice InDevice)
{
if (o_CreateSwapchainKHR != nullptr || State::Instance().vulkanSkipHooks)
return;
LOG_FUNC();
o_QueuePresentKHR = (PFN_vkQueuePresentKHR) (vkGetDeviceProcAddr(InDevice, "vkQueuePresentKHR"));
o_CreateSwapchainKHR = (PFN_vkCreateSwapchainKHR) (vkGetDeviceProcAddr(InDevice, "vkCreateSwapchainKHR"));
if (o_CreateSwapchainKHR)
{
LOG_DEBUG("Hooking VkDevice");
// Hook
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
if (o_QueuePresentKHR != nullptr)
DetourAttach(&(PVOID&) o_QueuePresentKHR, hkvkQueuePresentKHR);
if (o_CreateSwapchainKHR != nullptr)
DetourAttach(&(PVOID&) o_CreateSwapchainKHR, hkvkCreateSwapchainKHR);
auto detourResult = DetourTransactionCommit();
if (detourResult != NO_ERROR)
{
LOG_ERROR("Failed to hook VkDevice, error code: {:X}", detourResult);
o_QueuePresentKHR = nullptr;
o_CreateSwapchainKHR = nullptr;
}
}
}
VALIDATE_HOOK(hkvkCreateWin32SurfaceKHR, PFN_vkCreateWin32SurfaceKHR)
static VkResult hkvkCreateWin32SurfaceKHR(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface)
{
LOG_FUNC();
auto result = o_vkCreateWin32SurfaceKHR(instance, pCreateInfo, pAllocator, pSurface);
auto procHwnd = Util::GetProcessWindow();
LOG_DEBUG("procHwnd: {0:X}, swapchain hwnd: {1:X}", (UINT64) procHwnd, (UINT64) pCreateInfo->hwnd);
if (result == VK_SUCCESS && !State::Instance().vulkanSkipHooks)
{
MenuOverlayVk::DestroyVulkanObjects(false);
_instance = instance;
State::Instance().VulkanInstance = instance;
LOG_DEBUG("_instance captured: {0:X}", (UINT64) _instance);
_hwnd = pCreateInfo->hwnd;
LOG_DEBUG("_hwnd captured: {0:X}", (UINT64) _hwnd);
}
LOG_FUNC_RESULT(result);
return result;
}
VALIDATE_HOOK(hkvkCreateInstance, PFN_vkCreateInstance)
static VkResult hkvkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance)
{
LOG_FUNC();
VkInstanceCreateInfo localCreateInfo {};
memcpy(&localCreateInfo, pCreateInfo, sizeof(VkInstanceCreateInfo));
VulkanSpoofing::hkvkCreateInstance(&localCreateInfo, pAllocator, pInstance);
VkResult result;
{
ScopedSkipSpoofingGlobal skipSpoofingGlobal {};
result = o_vkCreateInstance(&localCreateInfo, pAllocator, pInstance);
}
if (result == VK_SUCCESS)
{
State::Instance().VulkanInstance = *pInstance;
LOG_DEBUG("State::Instance().VulkanInstance captured: {0:X}", (UINT64) State::Instance().VulkanInstance);
#ifdef VULKAN_DEBUG_LAYER
auto address = vkGetInstanceProcAddr(State::Instance().VulkanInstance, "vkCreateDebugUtilsMessengerEXT");
auto vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT) address;
VkDebugUtilsMessengerEXT debugMessenger;
vkCreateDebugUtilsMessengerEXT(State::Instance().VulkanInstance, &VulkanSpoofing::debugCreateInfo, nullptr,
&debugMessenger);
#endif
}
// Disabled to prevent unnecessary object release
// if (result == VK_SUCCESS && !State::Instance().vulkanSkipHooks)
//{
// MenuOverlayVk::DestroyVulkanObjects(false);
// }
LOG_FUNC_RESULT(result);
return result;
}
VALIDATE_HOOK(hkvkCreateDevice, PFN_vkCreateDevice)
static VkResult hkvkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkDevice* pDevice)
{
LOG_FUNC();
VkDeviceCreateInfo localCreteInfo {};
memcpy(&localCreteInfo, pCreateInfo, sizeof(VkDeviceCreateInfo));
// Check support for AntiLag before spoof
VkPhysicalDeviceFeatures2 features2 = {};
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
VkPhysicalDeviceAntiLagFeaturesAMD antiLagFeatures = {};
antiLagFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ANTI_LAG_FEATURES_AMD;
features2.pNext = &antiLagFeatures;
if (o_vkGetPhysicalDeviceFeatures2)
{
o_vkGetPhysicalDeviceFeatures2(physicalDevice, &features2);
State::Instance().vkAntiLagSupported = antiLagFeatures.antiLag != 0;
}
VulkanSpoofing::hkvkCreateDevice(physicalDevice, &localCreteInfo, pAllocator, pDevice);
auto result = o_vkCreateDevice(physicalDevice, &localCreteInfo, pAllocator, pDevice);
if (result == VK_SUCCESS && Config::Instance()->OverlayMenu.value_or_default())
{
if (!State::Instance().vulkanSkipHooks)
{
// Disabled to prevent unnecessary object release
// MenuOverlayVk::DestroyVulkanObjects(false);
_PD = physicalDevice;
LOG_DEBUG("_PD captured: {0:X}", (UINT64) _PD);
_device = *pDevice;
LOG_DEBUG("_device captured: {0:X}", (UINT64) _device);
HookDevice(_device);
}
ScopedSkipSpoofingGlobal skipSpoofingGlobal {};
VkPhysicalDeviceIDProperties idProps {};
idProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
VkPhysicalDeviceProperties2 props2 {};
props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
props2.pNext = &idProps;
vkGetPhysicalDeviceProperties2(physicalDevice, &props2);
if (idProps.deviceLUIDValid == VK_TRUE)
{
auto primaryGpu = IdentifyGpu::getPrimaryGpu();
auto luid = (PLUID) idProps.deviceLUID;
if (!IsEqualLUID(*luid, primaryGpu.luid))
LOG_WARN("VkDevice created with non-primary GPU");
}
}
if (State::Instance().vkAntiLagSupported)
{
if (result == VK_SUCCESS && o_vkGetDeviceProcAddr)
{
VulkanHooks::o_vkAntiLagUpdateAMD =
(PFN_vkAntiLagUpdateAMD) o_vkGetDeviceProcAddr(*pDevice, "vkAntiLagUpdateAMD");
}
else
{
State::Instance().vkAntiLagSupported = false;
LOG_WARN("Vulkan AntiLag can't be enabled");
}
}
#ifdef USE_QUEUE_SUBMIT_2_KHR
if (result == VK_SUCCESS)
hkvkGetDeviceProcAddr(*pDevice, "vkQueueSubmit2KHR");
#endif
LOG_FUNC_RESULT(result);
return result;
}
VALIDATE_HOOK(hkvkQueuePresentKHR, PFN_vkQueuePresentKHR)
static VkResult hkvkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* pPresentInfo)
{
LOG_FUNC();
// Upscaler GPU time computation
if (auto vkFeature = dynamic_cast<IFeature_Vk*>(State::Instance().currentFeature); vkFeature != nullptr)
{
if (auto upscalerTimeOpt = vkFeature->ReadUpscalerTime(nullptr); upscalerTimeOpt.has_value())
{
vkFeature->ReadDetailedGpuTimes(nullptr, State::Instance().detailedGpuTimes);
auto upscalerTime = upscalerTimeOpt.value();
// filter out possibly wrong measured high values
if (upscalerTime < 100.0)
{
State::Instance().frameTimeMutex.lock();
State::Instance().upscaleTimes.push_back(upscalerTime);
State::Instance().upscaleTimes.pop_front();
State::Instance().frameTimeMutex.unlock();
}
}
}
// ??? TODO: if we are hooking dxvk's vulkan calls then this present call could be either coming from dxvk or from a
// native vk game
if (!IdentifyGpu::getPrimaryGpu().usesDxvk)
State::Instance().swapchainApi = Vulkan;
// Tick feature to let it know if it's frozen
if (auto currentFeature = State::Instance().currentFeature; currentFeature != nullptr)
{
if (auto currentFg = State::Instance().currentFG; currentFg != nullptr)
currentFeature->TickFrozenCheck(currentFg->GetInterpolatedFrameCount());
else
currentFeature->TickFrozenCheck();
}
VkPresentInfoKHR localPresentInfo {};
memcpy(&localPresentInfo, pPresentInfo, sizeof(VkPresentInfoKHR));
// render menu if needed
if (!MenuOverlayVk::QueuePresent(queue, &localPresentInfo))
{
LOG_ERROR("QueuePresent: false!");
return VK_ERROR_OUT_OF_DATE_KHR;
}
ReflexHooks::update(false, true);
// original call
ScopedVulkanCreatingSC scopedVulkanCreatingSC {};
auto result = o_QueuePresentKHR(queue, &localPresentInfo);
// Unsure about Vulkan Reflex fps limit and if that could be causing an issue here
if (!State::Instance().reflexLimitsFps)
FrameLimit::sleep(false);
LOG_FUNC_RESULT(result);
return result;
}
VALIDATE_HOOK(hkvkCreateSwapchainKHR, PFN_vkCreateSwapchainKHR)
static VkResult hkvkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain)
{
LOG_FUNC();
ScopedVulkanCreatingSC scopedVulkanCreatingSC {};
// Menu blur copies the swapchain image
VkSwapchainCreateInfoKHR localCreateInfo {};
if (pCreateInfo != nullptr && Config::Instance()->OverlayMenu.value_or_default() &&
!State::Instance().vulkanSkipHooks && _PD != VK_NULL_HANDLE &&
(pCreateInfo->imageUsage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) == 0)
{
VkSurfaceCapabilitiesKHR surfaceCaps {};
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(_PD, pCreateInfo->surface, &surfaceCaps) == VK_SUCCESS &&
(surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0)
{
localCreateInfo = *pCreateInfo;
localCreateInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
pCreateInfo = &localCreateInfo;
}
}
VkResult result = VK_SUCCESS;
{
ScopedSkipSpoofingGlobal skipSpoofingGlobal {};
result = o_CreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain);
}
if (result == VK_SUCCESS && device != VK_NULL_HANDLE && pCreateInfo != nullptr && *pSwapchain != VK_NULL_HANDLE &&
!State::Instance().vulkanSkipHooks)
{
State::Instance().screenWidth = static_cast<float>(pCreateInfo->imageExtent.width);
State::Instance().screenHeight = static_cast<float>(pCreateInfo->imageExtent.height);
LOG_DEBUG("if (result == VK_SUCCESS && device != VK_NULL_HANDLE && pCreateInfo != nullptr && pSwapchain != "
"VK_NULL_HANDLE)");
_device = device;
LOG_DEBUG("_device captured: {0:X}", (UINT64) _device);
MenuOverlayVk::CreateSwapchain(device, _PD, _instance, _hwnd, pCreateInfo, pAllocator, pSwapchain);
}
LOG_FUNC_RESULT(result);
return result;
}
VALIDATE_HOOK(hkvkGetInstanceProcAddr, PFN_vkGetInstanceProcAddr)
PFN_vkVoidFunction hkvkGetInstanceProcAddr(VkInstance instance, const char* pName)
{
auto orgFunc = o_vkGetInstanceProcAddr(instance, pName);
if (orgFunc == VK_NULL_HANDLE)
return VK_NULL_HANDLE;
auto procName = std::string(pName);
if (procName == std::string("vkCreateInstance"))
{
if (o_vkCreateInstance == nullptr)
o_vkCreateInstance = (PFN_vkCreateInstance) orgFunc;
LOG_DEBUG("vkCreateInstance");
return (PFN_vkVoidFunction) hkvkCreateInstance;
}
else if (procName == std::string("vkCreateDevice"))
{
if (o_vkCreateDevice == nullptr)
o_vkCreateDevice = (PFN_vkCreateDevice) orgFunc;
LOG_DEBUG("vkCreateDevice");
return (PFN_vkVoidFunction) hkvkCreateDevice;
}
auto result = VulkanSpoofing::hkvkGetInstanceProcAddr(orgFunc, pName);
if (result != VK_NULL_HANDLE)
return result;
return orgFunc;
}
VALIDATE_HOOK(hkvkGetDeviceProcAddr, PFN_vkGetDeviceProcAddr)
PFN_vkVoidFunction hkvkGetDeviceProcAddr(VkDevice device, const char* pName)
{
auto orgFunc = o_vkGetDeviceProcAddr(device, pName);
if (orgFunc == VK_NULL_HANDLE)
return VK_NULL_HANDLE;
auto procName = std::string(pName);
if (procName == std::string("vkCreateInstance"))
{
if (o_vkCreateInstance == nullptr)
o_vkCreateInstance = (PFN_vkCreateInstance) orgFunc;
LOG_DEBUG("vkCreateInstance");
return (PFN_vkVoidFunction) hkvkCreateInstance;
}
else if (procName == std::string("vkCreateDevice"))
{
if (o_vkCreateDevice == nullptr)
o_vkCreateDevice = (PFN_vkCreateDevice) orgFunc;
LOG_DEBUG("vkCreateDevice");
return (PFN_vkVoidFunction) hkvkCreateDevice;
}
auto result = VulkanSpoofing::hkvkGetDeviceProcAddr(orgFunc, pName);
if (result != VK_NULL_HANDLE)
return result;
return orgFunc;
}
void VulkanHooks::Hook(HMODULE vulkan1)
{
if (vulkanModule == nullptr)
vulkanModule = vulkan1;
VulkanSpoofing::HookForVulkanSpoofing(vulkan1);
VulkanSpoofing::HookForVulkanExtensionSpoofing(vulkan1);
VulkanSpoofing::HookForVulkanVRAMSpoofing(vulkan1);
if (o_vkCreateDevice != nullptr)
return;
FARPROC address = nullptr;
o_vkCreateDevice = (PFN_vkCreateDevice) KernelBaseProxy::GetProcAddress_()(vulkan1, "vkCreateDevice");
o_vkCreateInstance = (PFN_vkCreateInstance) KernelBaseProxy::GetProcAddress_()(vulkan1, "vkCreateInstance");
address = KernelBaseProxy::GetProcAddress_()(vulkan1, "vkGetInstanceProcAddr");
o_vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) address;
address = KernelBaseProxy::GetProcAddress_()(vulkan1, "vkGetDeviceProcAddr");
o_vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr) address;
address = KernelBaseProxy::GetProcAddress_()(vulkan1, "vkCreateWin32SurfaceKHR");
o_vkCreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR) address;
// address = KernelBaseProxy::GetProcAddress_()(vulkan1, "vkCmdPipelineBarrier");
// o_vkCmdPipelineBarrier = (PFN_vkCmdPipelineBarrier) address;
address = KernelBaseProxy::GetProcAddress_()(vulkan1, "vkGetPhysicalDeviceFeatures2");
o_vkGetPhysicalDeviceFeatures2 = (PFN_vkGetPhysicalDeviceFeatures2) address;
address = KernelBaseProxy::GetProcAddress_()(vulkan1, "vkCreateSemaphore");
o_vkCreateSemaphore = (PFN_vkCreateSemaphore) address;
address = KernelBaseProxy::GetProcAddress_()(vulkan1, "vkSignalSemaphore");
o_vkSignalSemaphore = (PFN_vkSignalSemaphore) address;
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
if (o_vkCreateDevice != nullptr)
DetourAttach(&(PVOID&) o_vkCreateDevice, hkvkCreateDevice);
if (o_vkGetInstanceProcAddr != nullptr)
DetourAttach(&(PVOID&) o_vkGetInstanceProcAddr, hkvkGetInstanceProcAddr);
if (o_vkGetDeviceProcAddr != nullptr)
DetourAttach(&(PVOID&) o_vkGetDeviceProcAddr, hkvkGetDeviceProcAddr);
if (o_vkCreateInstance != nullptr)
DetourAttach(&(PVOID&) o_vkCreateInstance, hkvkCreateInstance);
if (o_vkCreateWin32SurfaceKHR != nullptr)
DetourAttach(&(PVOID&) o_vkCreateWin32SurfaceKHR, hkvkCreateWin32SurfaceKHR);
// if (o_vkCmdPipelineBarrier != nullptr)
// DetourAttach(&(PVOID&) o_vkCmdPipelineBarrier, hkvkCmdPipelineBarrier);
auto detourResult = DetourTransactionCommit();
if (detourResult != NO_ERROR)
{
LOG_ERROR("Failed to hook Vulkan, error code: {:X}", detourResult);
o_vkCreateDevice = nullptr;
o_vkCreateInstance = nullptr;
o_vkGetInstanceProcAddr = nullptr;
o_vkGetDeviceProcAddr = nullptr;
o_vkCreateWin32SurfaceKHR = nullptr;
// o_vkCmdPipelineBarrier = nullptr;
}
}
void VulkanHooks::Unhook()
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
if (o_QueuePresentKHR != nullptr)
DetourDetach(&(PVOID&) o_QueuePresentKHR, hkvkQueuePresentKHR);
if (o_CreateSwapchainKHR != nullptr)
DetourDetach(&(PVOID&) o_CreateSwapchainKHR, hkvkCreateSwapchainKHR);
if (o_vkCreateDevice != nullptr)
DetourDetach(&(PVOID&) o_vkCreateDevice, hkvkCreateDevice);
if (o_vkCreateInstance != nullptr)
DetourDetach(&(PVOID&) o_vkCreateInstance, hkvkCreateInstance);
if (o_vkCreateWin32SurfaceKHR != nullptr)
DetourDetach(&(PVOID&) o_vkCreateWin32SurfaceKHR, hkvkCreateWin32SurfaceKHR);
// if (o_vkCmdPipelineBarrier != nullptr)
// DetourDetach(&(PVOID&) o_vkCmdPipelineBarrier, hkvkCmdPipelineBarrier);
auto detourResult = DetourTransactionCommit();
if (detourResult != NO_ERROR)
{
LOG_ERROR("Failed to unhook Vulkan, error code: {:X}", detourResult);
}
else
{
o_QueuePresentKHR = nullptr;
o_CreateSwapchainKHR = nullptr;
o_vkCreateDevice = nullptr;
o_vkCreateInstance = nullptr;
o_vkGetInstanceProcAddr = nullptr;
o_vkGetDeviceProcAddr = nullptr;
o_vkCreateWin32SurfaceKHR = nullptr;
// o_vkCmdPipelineBarrier = nullptr;
}
}