Skip to content

Commit 2683fbb

Browse files
committed
GPU Display: Implement FPS limiting to VSync rate
1 parent 9e4db9f commit 2683fbb

14 files changed

Lines changed: 162 additions & 32 deletions

‎GPU/GPUTracking/Definitions/GPUSettingsList.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,8 +504,8 @@ AddOption(rendererCompatSmoothing, bool, false, "", 0, "Use compat smoothing (if
504504
AddOption(drawQualityMSAA, int32_t, 0, "", 0, "MultiSample Anti Aliasing")
505505
AddOption(drawQualityDownsampleFSAA, int32_t, 0, "", 0, "Downsampling Anti Aliasing")
506506
AddOption(drawQualityVSync, bool, true, "", 0, "Enable Vertical Sync")
507-
AddOption(maxFPSRate, bool, false, "", 0, "Do not limit FPS but run at maximum possible rate")
508-
AddOption(fpsRate, int32_t, 100, "", 0, "Target frame rate (frames per second) if not running at maximum rate (0 = no limit)")
507+
AddOption(fpsLimit, int32_t, 1, "", 0, "FPS limit: 0 = none (maximum rate), 1 = display refresh rate (no limit without VSync), 2 = fpsRate")
508+
AddOption(fpsRate, int32_t, 100, "", 0, "Target frame rate (frames per second) for fpsLimit 2, or if the backend cannot limit to the refresh rate (0 = no limit)")
509509
AddOption(useGLIndirectDraw, bool, true, "", 0, "Use OpenGL indirect draws to reduce number of draw calls")
510510
AddOption(screenshotScaleFactor, int32_t, 1, "", 0, "Resolution scale factor when taking screenshots")
511511
AddOption(fov, int32_t, 45, "", 0, "Display FOV")

‎GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ void GPUDisplayBackend::fillIndirectCmdBuffer()
171171
}
172172
}
173173

174+
int32_t GPUDisplayBackend::fpsLimitMode() const
175+
{
176+
const auto& cfg = mDisplay->cfgR();
177+
if (cfg.fpsLimit == FPS_LIMIT_VSYNC) {
178+
return !cfg.drawQualityVSync ? FPS_LIMIT_NONE : (canLimitToVSync() ? FPS_LIMIT_VSYNC : FPS_LIMIT_RATE);
179+
}
180+
return cfg.fpsLimit;
181+
}
182+
174183
float GPUDisplayBackend::getDownsampleFactor(bool screenshot)
175184
{
176185
float factor = 1.0f;

‎GPU/GPUTracking/display/backend/GPUDisplayBackend.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class GPUDisplayBackend
7777
virtual void ActivateColor(std::array<float, 4>& color) = 0;
7878
virtual void setQuality() {};
7979
virtual void SetVSync(bool enable) {};
80+
enum fpsLimitModes : int32_t { FPS_LIMIT_NONE = 0,
81+
FPS_LIMIT_VSYNC = 1,
82+
FPS_LIMIT_RATE = 2 }; // Values of GPUSettingsDisplayRenderer::fpsLimit
83+
virtual bool canLimitToVSync() const { return false; } // Rendering is throttled to the refresh rate with VSync (blocking buffer swap / present wait)
84+
int32_t fpsLimitMode() const; // Effective mode: VSync limit without VSync = none, VSync limit not supported by the backend = fpsRate
8085
virtual bool backendNeedRedraw() { return true; }
8186
virtual void setDepthBuffer() = 0;
8287
virtual int32_t InitBackendA() = 0;

‎GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend
3535
~GPUDisplayBackendOpenGL() override = default;
3636
int32_t ExtInit() override;
3737
bool CoreProfile() override;
38+
bool canLimitToVSync() const override { return true; } // Buffer swap blocks with VSync
3839
uint32_t DepthBits() override;
3940

4041
protected:

‎GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx‎

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<vk::Surfac
9999
vk::PresentModeKHR GPUDisplayBackendVulkan::chooseSwapPresentMode()
100100
{
101101
// Lowest latency first. VSync: no tearing, MAILBOX, then FIFO_LATEST_READY (shows the newest ready image at each vblank). No VSync: IMMEDIATE, else the same (often no IMMEDIATE on Wayland). FIFO is always supported.
102+
mPresentModeForVSyncLimit = fpsLimitMode() == FPS_LIMIT_VSYNC;
103+
if (mPresentModeForVSyncLimit) {
104+
return vk::PresentModeKHR::eFifo; // Refresh rate limit: one frame per refresh, queue depth limited by present wait
105+
}
102106
const bool vsync = mDisplay->cfgR().drawQualityVSync;
103107
const auto& available = mSwapChainDetails.presentModes;
104108
for (vk::PresentModeKHR mode : {vk::PresentModeKHR::eImmediate, vk::PresentModeKHR::eMailbox, vk::PresentModeKHR::eFifoLatestReady, vk::PresentModeKHR::eFifo}) {
@@ -313,6 +317,25 @@ static void clearFeatures(vk::StructureChain<T...>& chain) // Zero all feature f
313317
((memset(reinterpret_cast<char*>(&chain.template get<T>()) + sizeof(VkBaseOutStructure), 0, sizeof(T) - sizeof(VkBaseOutStructure))), ...);
314318
}
315319

320+
template <class T> // Enable the first available of the extensions (aliases) if the device supports the feature, chaining the feature struct in front of chain
321+
static bool enableOptionalFeature(vk::PhysicalDevice device, const std::vector<vk::ExtensionProperties>& available, std::initializer_list<const char*> extensions, vk::Bool32 T::* feature, T& featureStruct, std::vector<const char*>& enabledExtensions, void*& chain)
322+
{
323+
for (const char* ext : extensions) {
324+
if (std::none_of(available.begin(), available.end(), [ext](const auto& e) { return strcmp(ext, e.extensionName) == 0; })) {
325+
continue;
326+
}
327+
if (!(device.getFeatures2<vk::PhysicalDeviceFeatures2, T>().template get<T>().*feature)) {
328+
return false;
329+
}
330+
enabledExtensions.emplace_back(ext);
331+
featureStruct.*feature = VK_TRUE;
332+
featureStruct.pNext = chain;
333+
chain = &featureStruct;
334+
return true;
335+
}
336+
return false;
337+
}
338+
316339
template <class... T>
317340
static vk::StructureChain<T...> getFeatures(vk::PhysicalDevice device, const vk::StructureChain<T...>&)
318341
{
@@ -536,18 +559,10 @@ void GPUDisplayBackendVulkan::createDevice()
536559
mSmoothLinesSupported = getFeatures(mPhysicalDevice, reqDeviceFeatures).get<vk::PhysicalDeviceVulkan14Features>().smoothLines;
537560
reqDeviceFeatures.get<vk::PhysicalDeviceVulkan14Features>().smoothLines = mSmoothLinesSupported;
538561
std::vector<const char*> deviceExtensions = reqDeviceExtensions;
539-
vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR fifoLatestReadyFeatures{};
562+
void* featureChain = &reqDeviceFeatures.get<vk::PhysicalDeviceFeatures2>();
540563
const std::vector<vk::ExtensionProperties> availableDeviceExtensions = mPhysicalDevice.enumerateDeviceExtensionProperties(nullptr);
541-
for (const char* ext : {VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME, VK_EXT_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME}) { // Optional, same feature struct for both
542-
if (std::any_of(availableDeviceExtensions.begin(), availableDeviceExtensions.end(), [ext](const auto& e) { return strcmp(ext, e.extensionName) == 0; })) {
543-
mFifoLatestReadySupported = mPhysicalDevice.getFeatures2<vk::PhysicalDeviceFeatures2, vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR>().get<vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR>().presentModeFifoLatestReady;
544-
if (mFifoLatestReadySupported) {
545-
deviceExtensions.emplace_back(ext);
546-
fifoLatestReadyFeatures.presentModeFifoLatestReady = VK_TRUE;
547-
}
548-
break;
549-
}
550-
}
564+
vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR fifoLatestReadyFeatures{};
565+
mFifoLatestReadySupported = enableOptionalFeature(mPhysicalDevice, availableDeviceExtensions, {VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME, VK_EXT_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME}, &vk::PhysicalDevicePresentModeFifoLatestReadyFeaturesKHR::presentModeFifoLatestReady, fifoLatestReadyFeatures, deviceExtensions, featureChain);
551566

552567
updateSwapChainDetails(mPhysicalDevice);
553568
vk::PhysicalDeviceProperties deviceProperties = mPhysicalDevice.getProperties();
@@ -572,11 +587,7 @@ void GPUDisplayBackendVulkan::createDevice()
572587
vk::DeviceCreateInfo deviceCreateInfo{};
573588
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
574589
deviceCreateInfo.queueCreateInfoCount = 1;
575-
deviceCreateInfo.pNext = &reqDeviceFeatures.get<vk::PhysicalDeviceFeatures2>();
576-
if (mFifoLatestReadySupported) {
577-
fifoLatestReadyFeatures.pNext = &reqDeviceFeatures.get<vk::PhysicalDeviceFeatures2>();
578-
deviceCreateInfo.pNext = &fifoLatestReadyFeatures;
579-
}
590+
deviceCreateInfo.pNext = featureChain;
580591
deviceCreateInfo.pEnabledFeatures = nullptr;
581592
deviceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
582593
deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
@@ -648,13 +659,27 @@ void GPUDisplayBackendVulkan::createSemaphores()
648659
mTimelineValue = 0;
649660
mFrameTimelineValue.assign(mFramesInFlight, 0);
650661
mImageTimelineValue.assign(mImageCount, 0);
662+
for (uint32_t i = 0; i < mImageCount; i++) {
663+
mPresentFences.emplace_back(mDevice.createFence(vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled}, nullptr));
664+
}
665+
}
666+
667+
void GPUDisplayBackendVulkan::waitPresentFences()
668+
{
669+
if (mPresentFences.size()) { // Timeout in case a failed present did not signal its fence
670+
vk::Result retVal = mDevice.waitForFences(mPresentFences.size(), mPresentFences.data(), true, 1'000'000'000);
671+
if (retVal != vk::Result::eTimeout) {
672+
CHKERR(retVal);
673+
}
674+
}
651675
}
652676

653677
void GPUDisplayBackendVulkan::clearSemaphores()
654678
{
655679
clearVector(mImageAvailableSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); });
656680
clearVector(mPresentSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); });
657681
mDevice.destroySemaphore(mTimelineSemaphore, nullptr);
682+
clearVector(mPresentFences, [&](auto& x) { mDevice.destroyFence(x, nullptr); });
658683
}
659684

660685
// ---------------------------- VULKAN UNIFORM LAYOUTS AND BUFFERS ----------------------------
@@ -770,8 +795,23 @@ void GPUDisplayBackendVulkan::createSwapChain(vk::SwapchainKHR oldSwapChain)
770795
swapCreateInfo.presentMode = mPresentMode;
771796
swapCreateInfo.clipped = true;
772797
swapCreateInfo.oldSwapchain = oldSwapChain;
798+
vk::SwapchainPresentScalingCreateInfoKHR scalingInfo{};
799+
{ // Let the compositor scale the last image while resizing, until the new swapchain is presented
800+
const vk::SurfacePresentModeKHR surfacePresentMode{mPresentMode};
801+
const auto caps = mPhysicalDevice.getSurfaceCapabilities2KHR<vk::SurfaceCapabilities2KHR, vk::SurfacePresentScalingCapabilitiesKHR>(vk::PhysicalDeviceSurfaceInfo2KHR{mSurface, &surfacePresentMode}).get<vk::SurfacePresentScalingCapabilitiesKHR>();
802+
auto gravity = [](vk::PresentGravityFlagsKHR f) { return (f & vk::PresentGravityFlagBitsKHR::eCentered) ? vk::PresentGravityFlagBitsKHR::eCentered : (f & vk::PresentGravityFlagBitsKHR::eMin) ? vk::PresentGravityFlagBitsKHR::eMin
803+
: vk::PresentGravityFlagsKHR(f & vk::PresentGravityFlagBitsKHR::eMax); };
804+
scalingInfo.scalingBehavior = (caps.supportedPresentScaling & vk::PresentScalingFlagBitsKHR::eAspectRatioStretch) ? vk::PresentScalingFlagBitsKHR::eAspectRatioStretch : vk::PresentScalingFlagsKHR(caps.supportedPresentScaling & vk::PresentScalingFlagBitsKHR::eStretch);
805+
scalingInfo.presentGravityX = gravity(caps.supportedPresentGravityX);
806+
scalingInfo.presentGravityY = gravity(caps.supportedPresentGravityY);
807+
if (scalingInfo.scalingBehavior && scalingInfo.presentGravityX && scalingInfo.presentGravityY) {
808+
swapCreateInfo.pNext = &scalingInfo;
809+
}
810+
}
773811
mSwapChain = mDevice.createSwapchainKHR(swapCreateInfo, nullptr);
812+
mLastPresentId = 0; // Present IDs are per swapchain
774813
if (oldSwapChain) {
814+
waitPresentFences(); // Presentation engine done with the old swapchain
775815
mDevice.destroySwapchainKHR(oldSwapChain, nullptr);
776816
}
777817

@@ -809,6 +849,7 @@ void GPUDisplayBackendVulkan::clearSwapChain(bool keepSwapChain)
809849
{
810850
clearVector(mSwapChainImageViews, [&](auto& x) { mDevice.destroyImageView(x, nullptr); });
811851
if (!keepSwapChain) {
852+
waitPresentFences();
812853
mDevice.destroySwapchainKHR(mSwapChain, nullptr);
813854
}
814855
}
@@ -1424,11 +1465,22 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4&
14241465
if (includeMixImage == 0.f) {
14251466
mCurrentFrame = (mCurrentFrame + 1) % mFramesInFlight;
14261467
waitTimeline(mFrameTimelineValue[mCurrentFrame]);
1468+
if (mLastPresentId > 1 && fpsLimitMode() == FPS_LIMIT_VSYNC) { // Refresh rate limit (FIFO): at most one frame queued (wait for the second-last present), keeps the rate if a frame misses the deadline, timeout in case the window is hidden
1469+
const vk::Result retVal = (vk::Result)VULKAN_HPP_DEFAULT_DISPATCHER.vkWaitForPresentKHR(static_cast<VkDevice>(mDevice), static_cast<VkSwapchainKHR>(mSwapChain), mLastPresentId - 1, 100'000'000);
1470+
if (retVal == vk::Result::eErrorOutOfDateKHR || retVal == vk::Result::eSuboptimalKHR) {
1471+
mMustUpdateSwapChain = true;
1472+
} else if (retVal != vk::Result::eTimeout) {
1473+
CHKERR(retVal);
1474+
}
1475+
}
14271476
auto getImage = [&]() {
14281477
return mDevice.acquireNextImageKHR(mSwapChain, UINT64_MAX, mImageAvailableSemaphore[mCurrentFrame], VkFence(VK_NULL_HANDLE), &mCurrentImageIndex);
14291478
};
14301479

14311480
vk::Result retVal = vk::Result::eSuccess;
1481+
if (mPresentModeForVSyncLimit != (fpsLimitMode() == FPS_LIMIT_VSYNC)) { // Present mode depends on the FPS limit mode
1482+
mMustUpdateSwapChain = true;
1483+
}
14321484
bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot) || mSmoothLines != useNativeSmoothLines();
14331485
if (!mustUpdateSwapChain) {
14341486
retVal = getImage();
@@ -1442,7 +1494,11 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4&
14421494
}
14431495
mMustUpdateSwapChain = retVal == vk::Result::eSuboptimalKHR;
14441496
CHKERR(retVal);
1445-
waitTimeline(mImageTimelineValue[mCurrentImageIndex]); // Previous frame using this image's resources (command buffers, uniform buffer, ...) finished
1497+
waitTimeline(mImageTimelineValue[mCurrentImageIndex]); // Previous frame using this image's resources (command buffers, uniform buffer, ...) finished
1498+
if (mPresentFences.size()) { // Previous present of this image done, its present semaphore can be signaled again
1499+
CHKERR(mDevice.waitForFences(1, &mPresentFences[mCurrentImageIndex], true, 1'000'000'000)); // Timeout (positive result, not an error) in case a failed present did not signal it
1500+
CHKERR(mDevice.resetFences(1, &mPresentFences[mCurrentImageIndex]));
1501+
}
14461502

14471503
mHasDrawnText = false;
14481504
mFrameCommandBuffers.clear();
@@ -1521,6 +1577,17 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f
15211577
presentInfo.pSwapchains = &mSwapChain;
15221578
presentInfo.pImageIndices = &mCurrentImageIndex;
15231579
presentInfo.pResults = nullptr;
1580+
const uint64_t presentId = mLastPresentId + 1;
1581+
vk::PresentIdKHR presentIdInfo{1, &presentId};
1582+
if (fpsLimitMode() == FPS_LIMIT_VSYNC) { // Present wait only for the refresh rate limit, the fpsRate limit (frontend timer) must not be capped at the refresh rate
1583+
presentInfo.pNext = &presentIdInfo;
1584+
mLastPresentId = presentId;
1585+
}
1586+
vk::SwapchainPresentFenceInfoKHR presentFenceInfo{};
1587+
if (mPresentFences.size()) {
1588+
presentFenceInfo = vk::SwapchainPresentFenceInfoKHR{1, &mPresentFences[mCurrentImageIndex], presentInfo.pNext};
1589+
presentInfo.pNext = &presentFenceInfo;
1590+
}
15241591
vk::Result retVal = mGraphicsQueue.presentKHR(&presentInfo);
15251592
if (retVal == vk::Result::eErrorOutOfDateKHR || retVal == vk::Result::eSuboptimalKHR) {
15261593
mMustUpdateSwapChain = true;

‎GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend
8383
void lineWidthFactor(float factor) override;
8484
bool useNativeSmoothLines() const;
8585
bool useSmoothLineShader() const;
86+
bool canLimitToVSync() const override { return true; } // Present wait
87+
void waitPresentFences();
8688
void resizeScene(uint32_t width, uint32_t height) override;
8789
float getYFactor() const override { return -1.0f; }
8890
int32_t getMaxMSAA() const override { return mMaxMSAAsupported; }
@@ -208,16 +210,21 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend
208210
vecpod<float> mFontVertexBufferHost;
209211
bool mHasDrawnText = false;
210212

211-
vk::SampleCountFlagBits mMSAASampleCount = vk::SampleCountFlagBits::e16;
213+
// Device capabilities, set in createDevice (optional features are enabled only if supported)
212214
uint32_t mMaxMSAAsupported = 0;
215+
bool mZSupported = false;
216+
bool mStencilSupported = false;
217+
bool mStrictLines = false; // Wide lines are rectangles, not parallelograms
218+
bool mSmoothLinesSupported = false; // Optional Vulkan 1.4 feature smoothLines
219+
bool mFifoLatestReadySupported = false; // Optional present mode FIFO_LATEST_READY (VK_KHR / VK_EXT_present_mode_fifo_latest_ready)
220+
221+
vk::SampleCountFlagBits mMSAASampleCount = vk::SampleCountFlagBits::e16;
213222
bool mZActive = false;
214-
bool mSmoothLinesSupported = false; // Optional Vulkan 1.4 feature smoothLines, enabled if available
215223
bool mSmoothLines = false;
216224
bool mPresentModeErrorShown = false;
217-
bool mFifoLatestReadySupported = false; // Optional present mode FIFO_LATEST_READY (VK_KHR / VK_EXT_present_mode_fifo_latest_ready), enabled if available
218-
bool mStrictLines = false;
219-
bool mZSupported = false;
220-
bool mStencilSupported = false;
225+
bool mPresentModeForVSyncLimit = false; // Swapchain created with FIFO for the refresh rate FPS limit
226+
std::vector<vk::Fence> mPresentFences; // Per swapchain image, signaled when its last present is done (swapchain_maintenance1)
227+
uint64_t mLastPresentId = 0; // Last present ID of the current swapchain, 0 = none
221228
bool mDownsampleFSAA = false;
222229
bool mMixingSupported = 0;
223230

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ void GPUDisplayFrontend::waitForFPSLimit()
8181
mNeedUpdate = 0;
8282
const auto now = std::chrono::steady_clock::now();
8383
const auto& cfg = mDisplay->cfgR();
84-
if (cfg.maxFPSRate || cfg.fpsRate <= 0) {
84+
if (mBackend->fpsLimitMode() != GPUDisplayBackend::FPS_LIMIT_RATE || cfg.fpsRate <= 0) {
8585
mNextFrameTime = now;
8686
return;
8787
}

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface
6161
GPUDisplayBackend* backend();
6262
static GPUDisplayFrontend* getFrontend(const char* type);
6363
virtual void getSize(int32_t& width, int32_t& height) { width = height = 0; }
64+
virtual float getRefreshRate() { return 0.f; } // Refresh rate of the display the window is on in Hz, 0 = unknown
6465
virtual float getDisplayScale() const { return 1.f; } // HiDPI scale factor: physical pixels per logical pixel, text positions and sizes are scaled by it
6566
virtual int32_t getVulkanSurface(void* instance, void* surface) { return 1; }
6667
virtual uint32_t getReqVulkanExtensions(const char**& p) { return 0; };
@@ -80,7 +81,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface
8081
virtual int32_t FrontendMain() = 0;
8182
virtual void StopDisplayA();
8283
static void* FrontendThreadWrapper(void*);
83-
void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (no sleep with maxFPSRate or fpsRate <= 0)
84+
void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (only in FPS_LIMIT_RATE mode, see GPUDisplayBackend::fpsLimitMode)
8485

8586
static constexpr int32_t INIT_WIDTH = 1024, INIT_HEIGHT = 768; // Initial window size, before maximizing
8687
static constexpr int32_t MIN_WIDTH = 200, MIN_HEIGHT = 150; // Minimum window size

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,13 @@ bool GPUDisplayFrontendGlfw::EnableSendKey()
396396
return true;
397397
}
398398

399+
float GPUDisplayFrontendGlfw::getRefreshRate()
400+
{
401+
GLFWmonitor* monitor = glfwGetWindowMonitor(mWindow) ? glfwGetWindowMonitor(mWindow) : glfwGetPrimaryMonitor(); // Monitor of a windowed window is not known, use the primary one
402+
const GLFWvidmode* mode = monitor ? glfwGetVideoMode(monitor) : nullptr;
403+
return mode ? mode->refreshRate : 0.f;
404+
}
405+
399406
void GPUDisplayFrontendGlfw::getSize(int32_t& width, int32_t& height)
400407
{
401408
glfwGetFramebufferSize(mWindow, &width, &height);

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend
3535
bool EnableSendKey() override;
3636
void getSize(int32_t& width, int32_t& height) override;
3737
float getDisplayScale() const override { return mScale; }
38+
float getRefreshRate() override;
3839
int32_t getVulkanSurface(void* instance, void* surface) override;
3940
uint32_t getReqVulkanExtensions(const char**& p) override;
4041

0 commit comments

Comments
 (0)