Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framerate is always twice than the monitor refresh #1407

Closed
dortamiguel opened this issue Jul 22, 2021 · 26 comments
Closed

Framerate is always twice than the monitor refresh #1407

dortamiguel opened this issue Jul 22, 2021 · 26 comments
Labels

Comments

@dortamiguel
Copy link

I completed both tutorials for https://vulkan-tutorial.com/ and https://vkguide.dev/ and their code makes my render loop twice as fast as the monitor refresh rate.

If I'm at 60hz I get 120fps, if I'm at 30hz I get 60fps

I found a solution which is to call the draw function twice... though this seems wasteful.

Has someone else experienced this issue?

What is the code in the draw function to make vulkan to wait for the vsync? https://github.com/vblanco20-1/vulkan-guide/blob/all-chapters/chapter-4/vk_engine.cpp#L92

I tried passing vkWaitForFences twice the timeout but it seems to not be affected by it.

@dortamiguel
Copy link
Author

dortamiguel commented Jul 23, 2021

I'm getting an strange behaviour of vkAcquireNextImageKHR, the pImageIndex that it returns has this shape 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, 2, 2, ... when it should be 0, 1, 2, 0, 1, 2, ...

Does this help?

@Valakor
Copy link

Valakor commented Jul 23, 2021

I see this same behavior in my toy engine. I have a very basic setup with standard CPU-GPU frame synchronization and default to 2 swap images for IMMEDIATE and 3 for FIFO presentation. I realize that the Vulkan spec states that the indices returned by acquireNextImage don't necessarily have any relationship, but I wouldn't expect to see repeats like 0, 0, 1, 1, etc. when all my CPU+GPU work takes much less than 16ms.

Some basic debugging I did shows somewhat strange behavior:

  • Windowed
    • FIFO w/ 3 swap images: weird 2x framerate behavior (actually, alternating 12.6ms and 4.1ms frames to average 120Hz but look like they sum to 60Hz)
    • IMMEDIATE w/ 3 swap images: weird alternating frames of 3.5ms and 1.1ms
    • FIFO w/ 2 swap images: renders at vsync 60Hz as I would expect
    • IMMEDIATE w/ 2 swap images: renders unlocked from vsync as I would expect
  • Fullscreen (really just "borderless windowed" since GLFW doesn't do real macOS fullscreen)
    • FIFO w/ 3 swap images: renders at vsync 60Hz as I would expect
    • IMMEDIATE w/ 3 swap images: seems locked to vsync 60Hz for some reason
    • FIFO w/ 2 swap images: seems to be rendering at vsync 30Hz rather than 60? I'm basically just clearing my screen to a solid color and rendering some ImGui UI so I don't think that should be taking >16ms...
    • IMMEDIATE w/ 2 swap images: seems locked to vsync 60Hz for some reason

In all the above cases I spend the majority of my time waiting (on the CPU) on my call to vkQueueSubmit, and not vkAcquireNextImage or vkQueuePresent. It's tough to verify what's going on on the GPU since timestamps don't seem to work in MoltenVK.

My current setup:

  • MacBook Pro (Retina, Mid 2015 with AMD Radeon R9 M370X)
  • macOS 11.4
  • Vulkan SDK 1.2.182.0 w/ the bundled MoltenVK binaries

@dortamiguel
Copy link
Author

dortamiguel commented Jul 23, 2021

I'm using SDL2 and I get this exact behaviour as @Valakor

Windowed
FIFO w/ 3 swap images: weird 2x framerate behavior (actually, alternating 12.6ms and 4.1ms frames to average 120Hz but look like they sum to 60Hz)
IMMEDIATE w/ 3 swap images: weird alternating frames of 3.5ms and 1.1ms
FIFO w/ 2 swap images: renders at vsync 60Hz as I would expect
IMMEDIATE w/ 2 swap images: renders unlocked from vsync as I would expect

Setup:

  • MacBook Air (M1, 2020)
  • macOS 11.4
  • Vulkan SDK 1.2.182.0 w/ the bundled MoltenVK binaries

@dortamiguel dortamiguel changed the title Framerate is always twice than then monitor refresh Framerate is always twice than the monitor refresh Jul 23, 2021
@HindrikStegenga
Copy link

I can reproduce his issue as well. My code for frame synchronization is essentially the same as the one from vulkan-tutorial.com. It seems that MVK returns weird image ordering on my system as well (MBP 2019 15"), where it returns the same image twice after each other. It also does 120 frames a second whereas it should be 60.

@char8t
Copy link

char8t commented Jul 25, 2021

I see the exact same behavior in my engine as well as @ellipticaldoor and @Valakor.

My setup:

MacBook Air M1
macOS 11.5
Vulkan SDK 1.2.182.0 w/ the bundled MoltenVK binaries

@dortamiguel
Copy link
Author

Maybe this could be related but also VK_PRESENT_MODE_IMMEDIATE_KHR gets capped around 200 fps. Even if I don't do any draw call.

@HindrikStegenga
Copy link

HindrikStegenga commented Jul 25, 2021

@ellipticaldoor I just tested this on my implementation, and for me immediate mode doesn't cap it, but rather it's véry dependent on the window size I render to. Rendering 800x600 or some small resolution and i get 2K+ whereas if I maximize my window, and resize the swapchain accordingly, I get only 200-300 fps. This is without drawcalls. There seems to be no big difference between using igpu or dedicated gpu.

Also, minimizing the window causes my framerate to jitter a lot. Oftentimes getting the same 200-300 fps or however much I got before depending on window size, and then suddenly drop to 2 fps or something like that. I doubt that has something to do with this issue of 120fps cap, but I thought i'd mention it.

@dortamiguel
Copy link
Author

dortamiguel commented Jul 25, 2021

I tried different window sizes with VK_PRESENT_MODE_IMMEDIATE_KHR and the fps are still capped to 200fps.

For reference this is how I'm waiting for the render calls

void VulkanFrame::draw()
{
  VK_CHECK(
    vkWaitForFences(vk.instance._device, 1, &_renderFence, true, UINT64_MAX));
  VK_CHECK(vkResetFences(vk.instance._device, 1, &_renderFence));

  uint32_t swapchainImageIndex;
  VK_CHECK(vkAcquireNextImageKHR(
    vk.instance._device,
    vk.swapchain._swapchain,
    UINT64_MAX,
    _presentSemaphore,
    nullptr,
    &swapchainImageIndex));

  VK_CHECK(vkResetCommandBuffer(_mainCommandBuffer, 0));

  VkCommandBufferBeginInfo cmdBeginInfo = {};
  cmdBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  cmdBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

  VK_CHECK(vkBeginCommandBuffer(_mainCommandBuffer, &cmdBeginInfo));

  VkClearValue clearValue;
  clearValue.color = {{0.0f, 0.0f, 0.0f, 1.0f}};

  VkRenderPassBeginInfo rpInfo = {};
  rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
  rpInfo.renderPass = vk.renderPass._renderPass;
  rpInfo.renderArea.offset.x = 0;
  rpInfo.renderArea.offset.y = 0;
  rpInfo.renderArea.extent = vk.swapchain._extent;
  rpInfo.framebuffer = vk.renderPass._framebuffers[swapchainImageIndex];
  rpInfo.clearValueCount = 1;
  rpInfo.pClearValues = &clearValue;

  vkCmdBeginRenderPass(_mainCommandBuffer, &rpInfo, VK_SUBPASS_CONTENTS_INLINE);

  commands(_mainCommandBuffer);

  vkCmdEndRenderPass(_mainCommandBuffer);
  VK_CHECK(vkEndCommandBuffer(_mainCommandBuffer));

  VkSubmitInfo submit = {};
  submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
  submit.commandBufferCount = 1;
  submit.pCommandBuffers = &_mainCommandBuffer;

  VkPipelineStageFlags waitStage =
    VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;

  submit.pWaitDstStageMask = &waitStage;
  submit.waitSemaphoreCount = 1;
  submit.pWaitSemaphores = &_presentSemaphore;
  submit.signalSemaphoreCount = 1;
  submit.pSignalSemaphores = &_renderSemaphore;
  submit.commandBufferCount = 1;
  submit.pCommandBuffers = &_mainCommandBuffer;

  VK_CHECK(vkQueueSubmit(vk.instance._graphicsQueue, 1, &submit, _renderFence));

  VkPresentInfoKHR presentInfo = {};
  presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
  presentInfo.pSwapchains = &vk.swapchain._swapchain;
  presentInfo.swapchainCount = 1;
  presentInfo.pWaitSemaphores = &_renderSemaphore;
  presentInfo.waitSemaphoreCount = 1;
  presentInfo.pImageIndices = &swapchainImageIndex;

  VK_CHECK(vkQueuePresentKHR(vk.instance._graphicsQueue, &presentInfo));
}

@char8t
Copy link

char8t commented Jul 25, 2021

It seems that the window size does not affect the frame rate on M1 Macs. Its always around 200-230 in my case.
It seems that the system is not going like full power when rendering like a few draw calls. Cranking drawcalls up to 1000 i still observe around 200 FPS and still over 40 FPS when doing like 10K draw calls.

@dortamiguel
Copy link
Author

I see, maybe macos is doing some clever thing to avoid rendering too much fps?

Seems like OpenGL doesn't have this limitation

@hecaex
Copy link

hecaex commented Jul 25, 2021

Same issue for me!
MacBook Air M1
macOS 11.5
VulkanSDK Version 1.2.182.0

I guess this ~200 FPS cap is due to power consumption. So the GPU doesn't push its limits if not needed.

@HindrikStegenga
Copy link

@hecaex I doubt that's the reason, as vkAcquireNextImageKHR should not return the same image twice sequentially. unless you work with 1 image or fifo with 2 images.

@hecaex
Copy link

hecaex commented Jul 27, 2021

@HindrikStegenga I agree with this. But I was talking about the ~200 FPS cap that seem to be related to power consumption.
1 Draw call -> ~220 FPS
1k Draw calls -> ~ 220 FPS
10k Draw calls -> ~50FPS

However...
VSync (VK_PRESENT_MODE_FIFO_KHR, 3 Images) -> 120 FPS ( 0, 0, 1, 1, 2, 2)

Edit:
No VSync (VK_PRESENT_MODE_IMMEDIATE_KHR, 3 Images) -> 220 FPS ( 0, 0, 1, 1, 2, 2)

@billhollings
Copy link
Contributor

billhollings commented Jul 27, 2021

I'm getting an strange behaviour of vkAcquireNextImageKHR, the pImageIndex that it returns has this shape 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, 2, 2, ... when it should be 0, 1, 2, 0, 1, 2, ...

PR #1410 should fix this particular issue.

@dortamiguel
Copy link
Author

@billhollings thank you, I just tried latest master and the issue with vkAcquireNextImageKHR it's fixed.

Though I still get twice the fps from the refresh of the monitor. I guess this wasn't related to the issue

@dortamiguel
Copy link
Author

dortamiguel commented Aug 23, 2021

I have more findings about this issue.

Right now I'm setting an minImageCount of 3 when creating the swapchain. If I set the minImageCount to 2 then vulkan renders at 60fps. The problem is that it feels like it's running at 30fps but the gameloop goes at 60fps.

I'm really confused, is this expected? How it's possible that the game is rendering in the screen at 60fps but the game loop is running at 120fps?

Anyone has a workaround for this?

@dortamiguel
Copy link
Author

Okay, I just checked my vulkan backend on a windows machine, and it works fine there at 60fps. So I guess this is a bug in MoltenVK

@dortamiguel
Copy link
Author

dortamiguel commented Jun 3, 2022

There is no news about this? I'm very puzzled by this...

@char8t
Copy link

char8t commented Jun 3, 2022

Yea me too. Is there anything we can do to help sorting out this issue?

@dortamiguel
Copy link
Author

fixing this will make my game twice as fast on mac, now I have to run the render loop 2 times more than is needed

@char8t
Copy link

char8t commented Jun 3, 2022

To fix this you should decouple your game logic from the fps to make your game run on any fps rate.

@dortamiguel
Copy link
Author

I already do by using delta time, but still, if I have some heavy cpu task is going to run more than needed increasing the cpu usage

@Calinou
Copy link

Calinou commented Sep 16, 2022

As a workaround, detect the monitor's refresh rate using system APIs (or SDL) and limit FPS to monitor_refresh_rate + 1. This will also prevent excessive CPU/GPU usage on systems where V-Sync fails to kick in or is forcibly disabled in the graphics driver, while still resulting in lower input lag than V-Sync enabled if V-Sync happens to be disabled.

@char8t
Copy link

char8t commented Sep 16, 2022

Interestingly on the macOS Ventura beta this issue does not seem to occur

@hecaex
Copy link

hecaex commented Sep 16, 2022

@char8t thats also my finding. When I tried the first public release of Ventura the issue seemed to be fixed and Vsync was tied to the actual monitor refresh rate.

As soon as I reverted back to Monterey the issue of double FPS was present again.
Same applies for an external non-retina display which I run at 165Hz, so the FPS are around 330+

@dortamiguel
Copy link
Author

It looks like this got fixed in Ventura 13.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants