Commit 38833111 by Shahbaz Youssefi Committed by Commit Bot

Vulkan: implement eglSwapInterval

Uses FIFO with interval 1 and mailbox/immediate with interval 0 (with a fallback to FIFO). An end2end test is added to make sure the present mode is correctly set. Bug: angleproject:2932 Change-Id: I45d2b1e551b5c63c42ca3c8964bd5e62abd2d459 Reviewed-on: https://chromium-review.googlesource.com/c/1456622Reviewed-by: 's avatarIan Elliott <ianelliott@google.com> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
parent 3e01a518
......@@ -27,20 +27,24 @@ namespace
{
VkPresentModeKHR GetDesiredPresentMode(const std::vector<VkPresentModeKHR> &presentModes,
EGLint minSwapInterval,
EGLint maxSwapInterval)
EGLint interval)
{
ASSERT(!presentModes.empty());
// Use FIFO mode for v-sync, since it throttles you to the display rate.
//
// However, for performance testing (for now), we want to issue draws as fast as possible so we
// use either of the following, if available, in order specified here:
// If v-sync is enabled, use FIFO, which throttles you to the display rate and is guaranteed to
// always be supported.
if (interval > 0)
{
return VK_PRESENT_MODE_FIFO_KHR;
}
// Otherwise, choose either of the following, if available, in order specified here:
//
// - Mailbox is similar to triple-buffering.
// - Immediate is similar to single-buffering.
//
// TODO(jmadill): Properly select present mode and re-create display if changed.
// If neither is supported, we fallback to FIFO.
bool mailboxAvailable = false;
bool immediateAvailable = false;
......@@ -59,7 +63,7 @@ VkPresentModeKHR GetDesiredPresentMode(const std::vector<VkPresentModeKHR> &pres
}
}
// Note that VK_PRESENT_MODE_FIFO_KHR is guaranteed to be available.
// Note again that VK_PRESENT_MODE_FIFO_KHR is guaranteed to be available.
return mailboxAvailable
? VK_PRESENT_MODE_MAILBOX_KHR
: immediateAvailable ? VK_PRESENT_MODE_IMMEDIATE_KHR : VK_PRESENT_MODE_FIFO_KHR;
......@@ -280,6 +284,7 @@ WindowSurfaceVk::WindowSurfaceVk(const egl::SurfaceState &surfaceState,
mInstance(VK_NULL_HANDLE),
mSwapchain(VK_NULL_HANDLE),
mSwapchainPresentMode(VK_PRESENT_MODE_FIFO_KHR),
mDesiredSwapchainPresentMode(VK_PRESENT_MODE_FIFO_KHR),
mMinImageCount(0),
mPreTransform(VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR),
mCompositeAlpha(VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR),
......@@ -349,25 +354,24 @@ angle::Result WindowSurfaceVk::initializeImpl(DisplayVk *displayVk)
const VkPhysicalDevice &physicalDevice = renderer->getPhysicalDevice();
VkSurfaceCapabilitiesKHR surfaceCaps;
ANGLE_VK_TRY(displayVk,
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, mSurface, &surfaceCaps));
ANGLE_VK_TRY(displayVk, vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, mSurface,
&mSurfaceCaps));
// Adjust width and height to the swapchain if necessary.
uint32_t width = surfaceCaps.currentExtent.width;
uint32_t height = surfaceCaps.currentExtent.height;
uint32_t width = mSurfaceCaps.currentExtent.width;
uint32_t height = mSurfaceCaps.currentExtent.height;
// TODO(jmadill): Support devices which don't support copy. We use this for ReadPixels.
ANGLE_VK_CHECK(displayVk,
(surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0,
(mSurfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0,
VK_ERROR_INITIALIZATION_FAILED);
EGLAttrib attribWidth = mState.attributes.get(EGL_WIDTH, 0);
EGLAttrib attribHeight = mState.attributes.get(EGL_HEIGHT, 0);
if (surfaceCaps.currentExtent.width == 0xFFFFFFFFu)
if (mSurfaceCaps.currentExtent.width == 0xFFFFFFFFu)
{
ASSERT(surfaceCaps.currentExtent.height == 0xFFFFFFFFu);
ASSERT(mSurfaceCaps.currentExtent.height == 0xFFFFFFFFu);
if (attribWidth == 0)
{
......@@ -386,49 +390,19 @@ angle::Result WindowSurfaceVk::initializeImpl(DisplayVk *displayVk)
&presentModeCount, nullptr));
ASSERT(presentModeCount > 0);
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
mPresentModes.resize(presentModeCount);
ANGLE_VK_TRY(displayVk, vkGetPhysicalDeviceSurfacePresentModesKHR(
physicalDevice, mSurface, &presentModeCount, presentModes.data()));
// Select appropriate present mode based on vsync parameter.
// TODO(jmadill): More complete implementation, which allows for changing and more values.
const EGLint minSwapInterval = mState.config->minSwapInterval;
const EGLint maxSwapInterval = mState.config->maxSwapInterval;
ASSERT(minSwapInterval == 0 || minSwapInterval == 1);
ASSERT(maxSwapInterval == 0 || maxSwapInterval == 1);
mSwapchainPresentMode = GetDesiredPresentMode(presentModes, minSwapInterval, maxSwapInterval);
// Determine the number of swapchain images:
//
// - On mailbox, we use minImageCount. The drivers may increase the number so that non-blocking
// mailbox actually makes sense.
// - On immediate, we use max(2, minImageCount). The vkQueuePresentKHR call immediately frees
// up the other image, so there is no point in having any more images.
// - On fifo, we use max(3, minImageCount). Triple-buffering allows us to present an image,
// have one in the queue and record in another. Note: on certain configurations (windows +
// nvidia + windowed mode), we could get away with a smaller number.
mMinImageCount = surfaceCaps.minImageCount;
if (mSwapchainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR)
{
mMinImageCount = std::max<uint32_t>(2, mMinImageCount);
}
else if (mSwapchainPresentMode == VK_PRESENT_MODE_FIFO_KHR)
{
mMinImageCount = std::max<uint32_t>(3, mMinImageCount);
}
physicalDevice, mSurface, &presentModeCount, mPresentModes.data()));
// Make sure we don't exceed maxImageCount.
if (surfaceCaps.maxImageCount > 0 && mMinImageCount > surfaceCaps.maxImageCount)
{
mMinImageCount = surfaceCaps.maxImageCount;
}
// Select appropriate present mode based on vsync parameter. Default to 1 (FIFO), though it
// will get clamped to the min/max values specified at display creation time.
setSwapInterval(1);
// Default to identity transform.
mPreTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
if ((surfaceCaps.supportedTransforms & mPreTransform) == 0)
if ((mSurfaceCaps.supportedTransforms & mPreTransform) == 0)
{
mPreTransform = surfaceCaps.currentTransform;
mPreTransform = mSurfaceCaps.currentTransform;
}
uint32_t surfaceFormatCount = 0;
......@@ -463,11 +437,11 @@ angle::Result WindowSurfaceVk::initializeImpl(DisplayVk *displayVk)
}
mCompositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
if ((surfaceCaps.supportedCompositeAlpha & mCompositeAlpha) == 0)
if ((mSurfaceCaps.supportedCompositeAlpha & mCompositeAlpha) == 0)
{
mCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
}
ANGLE_VK_CHECK(displayVk, (surfaceCaps.supportedCompositeAlpha & mCompositeAlpha) != 0,
ANGLE_VK_CHECK(displayVk, (mSurfaceCaps.supportedCompositeAlpha & mCompositeAlpha) != 0,
VK_ERROR_INITIALIZATION_FAILED);
ANGLE_TRY(recreateSwapchain(displayVk, extents, mCurrentSwapHistoryIndex));
......@@ -539,13 +513,14 @@ angle::Result WindowSurfaceVk::recreateSwapchain(DisplayVk *displayVk,
swapchainInfo.pQueueFamilyIndices = nullptr;
swapchainInfo.preTransform = mPreTransform;
swapchainInfo.compositeAlpha = mCompositeAlpha;
swapchainInfo.presentMode = mSwapchainPresentMode;
swapchainInfo.presentMode = mDesiredSwapchainPresentMode;
swapchainInfo.clipped = VK_TRUE;
swapchainInfo.oldSwapchain = oldSwapchain;
// TODO(syoussefi): Once EGL_SWAP_BEHAVIOR_PRESERVED_BIT is supported, the contents of the old
// swapchain need to carry over to the new one. http://anglebug.com/2942
ANGLE_VK_TRY(displayVk, vkCreateSwapchainKHR(device, &swapchainInfo, nullptr, &mSwapchain));
mSwapchainPresentMode = mDesiredSwapchainPresentMode;
// Intialize the swapchain image views.
uint32_t imageCount = 0;
......@@ -561,16 +536,8 @@ angle::Result WindowSurfaceVk::recreateSwapchain(DisplayVk *displayVk,
transparentBlack.float32[2] = 0.0f;
transparentBlack.float32[3] = 0.0f;
// Note: we expect subsequent calls to vkGetSwapchainImagesKHR to return the same number of
// images, given that the same value for minImageCount was provided. If that's not true, then
// the swap history would need to be preserved correctly; if there are more images now, the
// circular history buffer needs to be rearranged to remain continuous, and if there are fewer
// images now, we need to finishToSerial for the part of history that doesn't fit and clean up
// the respective old swapchains.
ASSERT(mSwapHistory.size() == 0 || mSwapHistory.size() == imageCount);
mSwapchainImages.resize(imageCount);
mSwapHistory.resize(imageCount);
ANGLE_TRY(resizeSwapHistory(displayVk, imageCount));
for (uint32_t imageIndex = 0; imageIndex < imageCount; ++imageIndex)
{
......@@ -622,31 +589,41 @@ angle::Result WindowSurfaceVk::checkForOutOfDateSwapchain(DisplayVk *displayVk,
uint32_t swapHistoryIndex,
bool presentOutOfDate)
{
bool swapIntervalChanged = mSwapchainPresentMode != mDesiredSwapchainPresentMode;
// Check for window resize and recreate swapchain if necessary.
gl::Extents currentExtents;
ANGLE_TRY(getCurrentWindowSize(displayVk, &currentExtents));
if (!presentOutOfDate && currentExtents.width == getWidth() &&
currentExtents.height == getHeight())
gl::Extents swapchainExtents(getWidth(), getHeight(), 0);
// If window size has changed, check with surface capabilities. It has been observed on
// Android that `getCurrentWindowSize()` returns 1920x1080 for example, while surface
// capabilities returns the size the surface was created with.
if (currentExtents != swapchainExtents)
{
return angle::Result::Continue;
}
const VkPhysicalDevice &physicalDevice = displayVk->getRenderer()->getPhysicalDevice();
ANGLE_VK_TRY(displayVk, vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, mSurface,
&mSurfaceCaps));
VkSurfaceCapabilitiesKHR surfaceCaps;
const VkPhysicalDevice &physicalDevice = displayVk->getRenderer()->getPhysicalDevice();
ANGLE_VK_TRY(displayVk,
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, mSurface, &surfaceCaps));
uint32_t width = mSurfaceCaps.currentExtent.width;
uint32_t height = mSurfaceCaps.currentExtent.height;
uint32_t width = surfaceCaps.currentExtent.width;
uint32_t height = surfaceCaps.currentExtent.height;
if (width != 0xFFFFFFFFu)
{
ASSERT(height != 0xFFFFFFFFu);
currentExtents.width = width;
currentExtents.height = height;
}
}
if (width != 0xFFFFFFFFu)
// If anything has changed, recreate the swapchain.
if (presentOutOfDate || swapIntervalChanged || currentExtents != swapchainExtents)
{
ASSERT(height != 0xFFFFFFFFu);
currentExtents.width = width;
currentExtents.height = height;
ANGLE_TRY(recreateSwapchain(displayVk, currentExtents, swapHistoryIndex));
}
return recreateSwapchain(displayVk, currentExtents, swapHistoryIndex);
return angle::Result::Continue;
}
void WindowSurfaceVk::releaseSwapchainImages(RendererVk *renderer)
......@@ -824,6 +801,82 @@ angle::Result WindowSurfaceVk::nextSwapchainImage(DisplayVk *displayVk)
return angle::Result::Continue;
}
angle::Result WindowSurfaceVk::resizeSwapHistory(DisplayVk *displayVk, size_t imageCount)
{
// The number of swapchain images can change if the present mode is changed. If that number is
// increased, we need to rearrange the history (which is a circular buffer) so it remains
// continuous. If it shrinks, we have to additionally make sure we clean up any old swapchains
// that can no longer fit in the history.
//
// Assume the following history buffer identified with serials:
//
// mCurrentSwapHistoryIndex
// V
// +----+----+----+
// | 11 | 9 | 10 |
// +----+----+----+
//
// When shrinking to size 2, we want to clean up 9, and rearrange to the following:
//
// mCurrentSwapHistoryIndex
// V
// +----+----+
// | 10 | 11 |
// +----+----+
//
// When expanding back to 3, we want to rearrange to the following:
//
// mCurrentSwapHistoryIndex
// V
// +----+----+----+
// | 0 | 10 | 11 |
// +----+----+----+
if (mSwapHistory.size() == imageCount)
{
return angle::Result::Continue;
}
RendererVk *renderer = displayVk->getRenderer();
// First, clean up anything that won't fit in the resized history.
if (imageCount < mSwapHistory.size())
{
size_t toClean = mSwapHistory.size() - imageCount;
for (size_t i = 0; i < toClean; ++i)
{
size_t historyIndex = (mCurrentSwapHistoryIndex + i) % mSwapHistory.size();
SwapHistory &swap = mSwapHistory[historyIndex];
ANGLE_TRY(renderer->finishToSerial(displayVk, swap.serial));
if (swap.swapchain != VK_NULL_HANDLE)
{
vkDestroySwapchainKHR(renderer->getDevice(), swap.swapchain, nullptr);
swap.swapchain = VK_NULL_HANDLE;
}
}
}
// Now, move the history, from most recent to oldest (as much as fits), into a new vector.
std::vector<SwapHistory> resizedHistory(imageCount);
size_t toCopy = std::min(imageCount, mSwapHistory.size());
for (size_t i = 0; i < toCopy; ++i)
{
size_t historyIndex =
(mCurrentSwapHistoryIndex + mSwapHistory.size() - i - 1) % mSwapHistory.size();
size_t resizedHistoryIndex = imageCount - i - 1;
resizedHistory[resizedHistoryIndex] = mSwapHistory[historyIndex];
}
// Set this as the new history. Note that after rearranging in either case, the oldest history
// is at index 0.
mSwapHistory = std::move(resizedHistory);
mCurrentSwapHistoryIndex = 0;
return angle::Result::Continue;
}
egl::Error WindowSurfaceVk::postSubBuffer(const gl::Context *context,
EGLint x,
EGLint y,
......@@ -860,7 +913,45 @@ egl::Error WindowSurfaceVk::getSyncValues(EGLuint64KHR * /*ust*/,
return egl::EglBadAccess();
}
void WindowSurfaceVk::setSwapInterval(EGLint interval) {}
void WindowSurfaceVk::setSwapInterval(EGLint interval)
{
const EGLint minSwapInterval = mState.config->minSwapInterval;
const EGLint maxSwapInterval = mState.config->maxSwapInterval;
ASSERT(minSwapInterval == 0 || minSwapInterval == 1);
ASSERT(maxSwapInterval == 0 || maxSwapInterval == 1);
interval = gl::clamp(interval, minSwapInterval, maxSwapInterval);
mDesiredSwapchainPresentMode = GetDesiredPresentMode(mPresentModes, interval);
// Determine the number of swapchain images:
//
// - On mailbox, we use minImageCount. The drivers may increase the number so that non-blocking
// mailbox actually makes sense.
// - On immediate, we use max(2, minImageCount). The vkQueuePresentKHR call immediately frees
// up the other image, so there is no point in having any more images.
// - On fifo, we use max(3, minImageCount). Triple-buffering allows us to present an image,
// have one in the queue and record in another. Note: on certain configurations (windows +
// nvidia + windowed mode), we could get away with a smaller number.
mMinImageCount = mSurfaceCaps.minImageCount;
if (mDesiredSwapchainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR)
{
mMinImageCount = std::max(2u, mMinImageCount);
}
else if (mDesiredSwapchainPresentMode == VK_PRESENT_MODE_FIFO_KHR)
{
mMinImageCount = std::max(3u, mMinImageCount);
}
// Make sure we don't exceed maxImageCount.
if (mSurfaceCaps.maxImageCount > 0 && mMinImageCount > mSurfaceCaps.maxImageCount)
{
mMinImageCount = mSurfaceCaps.maxImageCount;
}
// On the next swap, if the desired present mode is different from the current one, the
// swapchain will be recreated.
}
EGLint WindowSurfaceVk::getWidth() const
{
......
......@@ -155,10 +155,15 @@ class WindowSurfaceVk : public SurfaceImpl
void releaseSwapchainImages(RendererVk *renderer);
angle::Result nextSwapchainImage(DisplayVk *displayVk);
angle::Result swapImpl(DisplayVk *displayVk, EGLint *rects, EGLint n_rects);
angle::Result resizeSwapHistory(DisplayVk *displayVk, size_t imageCount);
VkSurfaceCapabilitiesKHR mSurfaceCaps;
std::vector<VkPresentModeKHR> mPresentModes;
VkSwapchainKHR mSwapchain;
// Cached information used to recreate swapchains.
VkPresentModeKHR mSwapchainPresentMode;
VkPresentModeKHR mSwapchainPresentMode; // Current swapchain mode
VkPresentModeKHR mDesiredSwapchainPresentMode; // Desired mode set through setSwapInterval()
uint32_t mMinImageCount;
VkSurfaceTransformFlagBitsKHR mPreTransform;
VkCompositeAlphaFlagBitsKHR mCompositeAlpha;
......
......@@ -248,7 +248,7 @@ egl::Config GenerateDefaultConfig(const RendererVk *renderer,
config.maxPBufferHeight = physicalDeviceProperties.limits.maxImageDimension2D;
config.maxPBufferPixels = ComputeMaximumPBufferPixels(physicalDeviceProperties);
config.maxSwapInterval = 1;
config.minSwapInterval = 1;
config.minSwapInterval = 0;
config.nativeRenderable = EGL_TRUE;
config.nativeVisualID = 0;
config.nativeVisualType = EGL_NONE;
......
......@@ -15,6 +15,7 @@
#include "test_utils/ANGLETest.h"
#include "util/EGLWindow.h"
#include "util/OSWindow.h"
#include "util/Timer.h"
#if defined(ANGLE_ENABLE_D3D11)
# define INITGUID
......@@ -347,6 +348,70 @@ TEST_P(EGLSurfaceTest, ResizeWindow)
ASSERT_EQ(64, height);
}
// Test that swap interval works.
TEST_P(EGLSurfaceTest, SwapInterval)
{
// On OSX, the test takes tens of milliseconds, which should be impossible with
// setSwapInterval(1) followed by 240 swaps as done below. http://anglebug.com/3140
ANGLE_SKIP_TEST_IF(IsOSX());
initializeDisplay();
initializeSurfaceWithDefaultConfig();
initializeContext();
eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
eglSwapBuffers(mDisplay, mWindowSurface);
ASSERT_EGL_SUCCESS();
EGLint minInterval, maxInterval;
ASSERT_TRUE(eglGetConfigAttrib(mDisplay, mConfig, EGL_MIN_SWAP_INTERVAL, &minInterval));
ASSERT_TRUE(eglGetConfigAttrib(mDisplay, mConfig, EGL_MAX_SWAP_INTERVAL, &maxInterval));
for (int iter = 0; iter < 2; ++iter)
{
if (maxInterval >= 1)
{
std::unique_ptr<Timer> timer(CreateTimer());
eglSwapInterval(mDisplay, 1);
timer->start();
for (int i = 0; i < 180; ++i)
{
eglSwapBuffers(mDisplay, mWindowSurface);
}
timer->stop();
ASSERT_EGL_SUCCESS();
// 120 frames at 60fps should take 3s. At lower fps, it should take even longer. At
// 144fps, it would take 1.25s. Let's use 1s as a lower bound.
ASSERT_GT(timer->getElapsedTime(), 1);
}
if (minInterval <= 0)
{
std::unique_ptr<Timer> timer(CreateTimer());
eglSwapInterval(mDisplay, 0);
timer->start();
for (int i = 0; i < 100; ++i)
{
eglSwapBuffers(mDisplay, mWindowSurface);
}
timer->stop();
ASSERT_EGL_SUCCESS();
// 100 no-op swaps should be fairly fast, though there is no guarantee how fast it can
// be. 10ms per swap is probably a safe upper bound.
//
// TODO(syoussefi): if a surface doesn't truly allow no-vsync, this can fail. Until
// there's a way to query the exact minInterval from the surface, this test cannot be
// enabled.
// ASSERT_LT(timer->getElapsedTime(), 1);
}
}
}
// Test creating a surface that supports a EGLConfig with 16bit
// support GL_RGB565
TEST_P(EGLSurfaceTest, CreateWithEGLConfig5650Support)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment