blob: f32d7ee7a2372ad8f6eac2ceb65a3334e8c41719 [file] [log] [blame]
/*-------------------------------------------------------------------------
* Vulkan Conformance Tests
* ------------------------
*
* Copyright (c) 2019 The Khronos Group Inc.
* Copyright (c) 2019 Valve Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief Tests for the present id and present wait extensions.
*//*--------------------------------------------------------------------*/
#include "vktWsiPresentIdWaitTests.hpp"
#include "vktTestCase.hpp"
#include "vktCustomInstancesDevices.hpp"
#include "vktNativeObjectsUtil.hpp"
#include "vkQueryUtil.hpp"
#include "vkDeviceUtil.hpp"
#include "vkWsiUtil.hpp"
#include "vkMemUtil.hpp"
#include "vkTypeUtil.hpp"
#include "vkRefUtil.hpp"
#include "tcuTestContext.hpp"
#include "tcuPlatform.hpp"
#include "tcuCommandLine.hpp"
#include "tcuTestLog.hpp"
#include "deDefs.hpp"
#include <vector>
#include <string>
#include <set>
#include <sstream>
#include <chrono>
#include <algorithm>
#include <utility>
#include <limits>
using std::vector;
using std::string;
using std::set;
namespace vkt
{
namespace wsi
{
namespace
{
// Handy time constants in nanoseconds.
constexpr deUint64 k10sec = 10000000000ull;
constexpr deUint64 k1sec = 1000000000ull;
// 100 milliseconds, way above 1/50 seconds for systems with 50Hz ticks.
// This should also take into account possible measure deviations due to the machine being loaded.
constexpr deUint64 kMargin = 100000000ull;
using TimeoutRange = std::pair<deInt64, deInt64>;
// Calculate acceptable timeout range based on indicated timeout and taking into account kMargin.
TimeoutRange calcTimeoutRange (deUint64 timeout)
{
constexpr auto kUnsignedMax = std::numeric_limits<deUint64>::max();
constexpr auto kSignedMax = static_cast<deUint64>(std::numeric_limits<deInt64>::max());
// Watch for over- and under-flows.
deUint64 timeoutMin = ((timeout < kMargin) ? 0ull : (timeout - kMargin));
deUint64 timeoutMax = ((kUnsignedMax - timeout < kMargin) ? kUnsignedMax : timeout + kMargin);
// Make sure casting is safe.
timeoutMin = de::min(kSignedMax, timeoutMin);
timeoutMax = de::min(kSignedMax, timeoutMax);
return TimeoutRange(static_cast<deInt64>(timeoutMin), static_cast<deInt64>(timeoutMax));
}
class PresentIdWaitInstance : public TestInstance
{
public:
PresentIdWaitInstance (Context& context, vk::wsi::Type wsiType) : TestInstance(context), m_wsiType(wsiType) {}
virtual ~PresentIdWaitInstance (void) {}
virtual tcu::TestStatus iterate (void);
virtual tcu::TestStatus run (const vk::DeviceInterface& vkd,
vk::VkDevice device,
vk::VkQueue queue,
vk::VkCommandPool commandPool,
vk::VkSwapchainKHR swapchain,
size_t swapchainSize,
const vk::wsi::WsiTriangleRenderer& renderer) = 0;
// Subclasses will need to implement a static method like this one indicating which extensions they need.
static vector<const char*> requiredDeviceExts (void) { return vector<const char*>(); }
// Subclasses will also need to implement this nonstatic method returning the same information as above.
virtual vector<const char*> getRequiredDeviceExts (void) = 0;
protected:
vk::wsi::Type m_wsiType;
};
vector<const char*> getRequiredInstanceExtensions (vk::wsi::Type wsiType)
{
vector<const char*> extensions;
extensions.push_back("VK_KHR_surface");
extensions.push_back(getExtensionName(wsiType));
return extensions;
}
CustomInstance createInstanceWithWsi (Context& context,
vk::wsi::Type wsiType,
const vk::VkAllocationCallbacks* pAllocator = nullptr)
{
const auto version = context.getUsedApiVersion();
const auto requiredExtensions = getRequiredInstanceExtensions(wsiType);
vector<string> requestedExtensions;
for (const auto& extensionName : requiredExtensions)
{
if (!vk::isCoreInstanceExtension(version, extensionName))
requestedExtensions.push_back(extensionName);
}
return vkt::createCustomInstanceWithExtensions(context, requestedExtensions, pAllocator);
}
struct InstanceHelper
{
const vector<vk::VkExtensionProperties> supportedExtensions;
CustomInstance instance;
const vk::InstanceDriver& vki;
InstanceHelper (Context& context, vk::wsi::Type wsiType, const vk::VkAllocationCallbacks* pAllocator = nullptr)
: supportedExtensions (enumerateInstanceExtensionProperties(context.getPlatformInterface(), nullptr))
, instance (createInstanceWithWsi(context, wsiType, pAllocator))
, vki (instance.getDriver())
{}
};
vector<const char*> getMandatoryDeviceExtensions ()
{
vector<const char*> mandatoryExtensions;
mandatoryExtensions.push_back("VK_KHR_swapchain");
return mandatoryExtensions;
}
vk::Move<vk::VkDevice> createDeviceWithWsi (const vk::PlatformInterface& vkp,
vk::VkInstance instance,
const vk::InstanceInterface& vki,
vk::VkPhysicalDevice physicalDevice,
const vector<const char*>& extraExtensions,
const deUint32 queueFamilyIndex,
bool validationEnabled,
const vk::VkAllocationCallbacks* pAllocator = nullptr)
{
const float queuePriorities[] = { 1.0f };
const vk::VkDeviceQueueCreateInfo queueInfos[] =
{
{
vk::VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
nullptr,
(vk::VkDeviceQueueCreateFlags)0,
queueFamilyIndex,
DE_LENGTH_OF_ARRAY(queuePriorities),
&queuePriorities[0]
}
};
vk::VkPhysicalDeviceFeatures features;
std::vector<const char*> extensions = extraExtensions;
const auto mandatoryExtensions = getMandatoryDeviceExtensions();
for (const auto& ext : mandatoryExtensions)
extensions.push_back(ext);
deMemset(&features, 0, sizeof(features));
const vk::VkDeviceCreateInfo deviceParams =
{
vk::VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
nullptr,
(vk::VkDeviceCreateFlags)0,
DE_LENGTH_OF_ARRAY(queueInfos),
&queueInfos[0],
0u, // enabledLayerCount
nullptr, // ppEnabledLayerNames
static_cast<deUint32>(extensions.size()), // enabledExtensionCount
extensions.data(), // ppEnabledExtensionNames
&features
};
return createCustomDevice(validationEnabled, vkp, instance, vki, physicalDevice, &deviceParams, pAllocator);
}
struct DeviceHelper
{
const vk::VkPhysicalDevice physicalDevice;
const deUint32 queueFamilyIndex;
const vk::Unique<vk::VkDevice> device;
const vk::DeviceDriver vkd;
const vk::VkQueue queue;
DeviceHelper (Context& context,
const vk::InstanceInterface& vki,
vk::VkInstance instance,
const vector<vk::VkSurfaceKHR>& surfaces,
const vector<const char*>& extraExtensions,
const vk::VkAllocationCallbacks* pAllocator = nullptr)
: physicalDevice (chooseDevice(vki, instance, context.getTestContext().getCommandLine()))
, queueFamilyIndex (vk::wsi::chooseQueueFamilyIndex(vki, physicalDevice, surfaces))
, device (createDeviceWithWsi(context.getPlatformInterface(),
instance,
vki,
physicalDevice,
extraExtensions,
queueFamilyIndex,
context.getTestContext().getCommandLine().isValidationEnabled(),
pAllocator))
, vkd (context.getPlatformInterface(), instance, *device)
, queue (getDeviceQueue(vkd, *device, queueFamilyIndex, 0))
{
}
};
vk::VkSwapchainCreateInfoKHR getBasicSwapchainParameters (vk::wsi::Type wsiType,
const vk::InstanceInterface& vki,
vk::VkPhysicalDevice physicalDevice,
vk::VkSurfaceKHR surface,
const tcu::UVec2& desiredSize,
deUint32 desiredImageCount)
{
const vk::VkSurfaceCapabilitiesKHR capabilities = vk::wsi::getPhysicalDeviceSurfaceCapabilities(vki,
physicalDevice,
surface);
const vector<vk::VkSurfaceFormatKHR> formats = vk::wsi::getPhysicalDeviceSurfaceFormats(vki,
physicalDevice,
surface);
const vk::wsi::PlatformProperties& platformProperties = vk::wsi::getPlatformProperties(wsiType);
const vk::VkSurfaceTransformFlagBitsKHR transform = (capabilities.supportedTransforms & vk::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) ? vk::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : capabilities.currentTransform;
const vk::VkSwapchainCreateInfoKHR parameters =
{
vk::VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
nullptr,
(vk::VkSwapchainCreateFlagsKHR)0,
surface,
de::clamp(desiredImageCount, capabilities.minImageCount, capabilities.maxImageCount > 0 ? capabilities.maxImageCount : capabilities.minImageCount + desiredImageCount),
formats[0].format,
formats[0].colorSpace,
(platformProperties.swapchainExtent == vk::wsi::PlatformProperties::SWAPCHAIN_EXTENT_MUST_MATCH_WINDOW_SIZE
? capabilities.currentExtent : vk::makeExtent2D(desiredSize.x(), desiredSize.y())),
1u, // imageArrayLayers
vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
vk::VK_SHARING_MODE_EXCLUSIVE,
0u,
nullptr,
transform,
vk::VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
vk::VK_PRESENT_MODE_FIFO_KHR,
VK_FALSE, // clipped
(vk::VkSwapchainKHR)0 // oldSwapchain
};
return parameters;
}
using CommandBufferSp = de::SharedPtr<vk::Unique<vk::VkCommandBuffer>>;
using FenceSp = de::SharedPtr<vk::Unique<vk::VkFence>>;
using SemaphoreSp = de::SharedPtr<vk::Unique<vk::VkSemaphore>>;
vector<FenceSp> createFences (const vk::DeviceInterface& vkd,
const vk::VkDevice device,
size_t numFences)
{
vector<FenceSp> fences(numFences);
for (size_t ndx = 0; ndx < numFences; ++ndx)
fences[ndx] = FenceSp(new vk::Unique<vk::VkFence>(createFence(vkd, device, vk::VK_FENCE_CREATE_SIGNALED_BIT)));
return fences;
}
vector<SemaphoreSp> createSemaphores (const vk::DeviceInterface& vkd,
const vk::VkDevice device,
size_t numSemaphores)
{
vector<SemaphoreSp> semaphores(numSemaphores);
for (size_t ndx = 0; ndx < numSemaphores; ++ndx)
semaphores[ndx] = SemaphoreSp(new vk::Unique<vk::VkSemaphore>(createSemaphore(vkd, device)));
return semaphores;
}
vector<CommandBufferSp> allocateCommandBuffers (const vk::DeviceInterface& vkd,
const vk::VkDevice device,
const vk::VkCommandPool commandPool,
const vk::VkCommandBufferLevel level,
const size_t numCommandBuffers)
{
vector<CommandBufferSp> buffers (numCommandBuffers);
for (size_t ndx = 0; ndx < numCommandBuffers; ++ndx)
buffers[ndx] = CommandBufferSp(new vk::Unique<vk::VkCommandBuffer>(allocateCommandBuffer(vkd, device, commandPool, level)));
return buffers;
}
class FrameStreamObjects
{
public:
struct FrameObjects
{
const vk::VkFence& renderCompleteFence;
const vk::VkSemaphore& renderCompleteSemaphore;
const vk::VkSemaphore& imageAvailableSemaphore;
const vk::VkCommandBuffer& commandBuffer;
};
FrameStreamObjects (const vk::DeviceInterface& vkd, vk::VkDevice device, vk::VkCommandPool cmdPool, size_t maxQueuedFrames)
: renderingCompleteFences (createFences(vkd, device, maxQueuedFrames))
, renderingCompleteSemaphores (createSemaphores(vkd, device, maxQueuedFrames))
, imageAvailableSemaphores (createSemaphores(vkd, device, maxQueuedFrames))
, commandBuffers (allocateCommandBuffers(vkd, device, cmdPool, vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY, maxQueuedFrames))
, m_maxQueuedFrames (maxQueuedFrames)
, m_nextFrame (0u)
{}
size_t frameNumber (void) const { DE_ASSERT(m_nextFrame > 0u); return m_nextFrame - 1u; }
FrameObjects newFrame ()
{
const size_t mod = m_nextFrame % m_maxQueuedFrames;
FrameObjects ret =
{
**renderingCompleteFences[mod],
**renderingCompleteSemaphores[mod],
**imageAvailableSemaphores[mod],
**commandBuffers[mod],
};
++m_nextFrame;
return ret;
}
private:
const vector<FenceSp> renderingCompleteFences;
const vector<SemaphoreSp> renderingCompleteSemaphores;
const vector<SemaphoreSp> imageAvailableSemaphores;
const vector<CommandBufferSp> commandBuffers;
const size_t m_maxQueuedFrames;
size_t m_nextFrame;
};
tcu::TestStatus PresentIdWaitInstance::iterate (void)
{
const tcu::UVec2 desiredSize (256, 256);
const InstanceHelper instHelper (m_context, m_wsiType);
const NativeObjects native (m_context, instHelper.supportedExtensions, m_wsiType, 1u, tcu::just(desiredSize));
const vk::Unique<vk::VkSurfaceKHR> surface (createSurface(instHelper.vki, instHelper.instance, m_wsiType, native.getDisplay(), native.getWindow()));
const DeviceHelper devHelper (m_context, instHelper.vki, instHelper.instance, vector<vk::VkSurfaceKHR>(1u, surface.get()), getRequiredDeviceExts());
const vk::DeviceInterface& vkd = devHelper.vkd;
const vk::VkDevice device = *devHelper.device;
vk::SimpleAllocator allocator (vkd, device, getPhysicalDeviceMemoryProperties(instHelper.vki, devHelper.physicalDevice));
const vk::VkSwapchainCreateInfoKHR swapchainInfo = getBasicSwapchainParameters(m_wsiType, instHelper.vki, devHelper.physicalDevice, *surface, desiredSize, 2);
const vk::Unique<vk::VkSwapchainKHR> swapchain (vk::createSwapchainKHR(vkd, device, &swapchainInfo));
const vector<vk::VkImage> swapchainImages = vk::wsi::getSwapchainImages(vkd, device, *swapchain);
const vk::Unique<vk::VkCommandPool> commandPool (createCommandPool(vkd, device, vk::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, devHelper.queueFamilyIndex));
const vk::wsi::WsiTriangleRenderer renderer (vkd,
device,
allocator,
m_context.getBinaryCollection(),
false,
swapchainImages,
swapchainImages,
swapchainInfo.imageFormat,
tcu::UVec2(swapchainInfo.imageExtent.width, swapchainInfo.imageExtent.height));
try
{
return run(vkd, device, devHelper.queue, commandPool.get(), swapchain.get(), swapchainImages.size(), renderer);
}
catch (...)
{
// Make sure device is idle before destroying resources
vkd.deviceWaitIdle(device);
throw;
}
return tcu::TestStatus(QP_TEST_RESULT_INTERNAL_ERROR, "Reached unreachable code");
}
struct PresentParameters
{
tcu::Maybe<deUint64> presentId;
tcu::Maybe<vk::VkResult> expectedResult;
};
struct WaitParameters
{
deUint64 presentId;
deUint64 timeout; // Nanoseconds.
bool timeoutExpected;
};
// This structure represents a set of present operations to be run followed by a set of wait operations to be run after them.
// When running the present operations, the present id can be provided, together with an optional expected result to be checked.
// When runing the wait operations, the present id must be provided together with a timeout and an indication of whether the operation is expected to time out or not.
struct PresentAndWaitOps
{
vector<PresentParameters> presentOps;
vector<WaitParameters> waitOps;
};
// Parent class for VK_KHR_present_id and VK_KHR_present_wait simple tests.
class PresentIdWaitSimpleInstance : public PresentIdWaitInstance
{
public:
PresentIdWaitSimpleInstance(Context& context, vk::wsi::Type wsiType, const vector<PresentAndWaitOps>& sequence)
: PresentIdWaitInstance(context, wsiType), m_sequence(sequence)
{}
virtual ~PresentIdWaitSimpleInstance() {}
virtual tcu::TestStatus run (const vk::DeviceInterface& vkd,
vk::VkDevice device,
vk::VkQueue queue,
vk::VkCommandPool commandPool,
vk::VkSwapchainKHR swapchain,
size_t swapchainSize,
const vk::wsi::WsiTriangleRenderer& renderer);
protected:
const vector<PresentAndWaitOps> m_sequence;
};
// Waits for the appropriate fences, acquires swapchain image, records frame and submits it to the given queue, signaling the appropriate frame semaphores.
// Returns the image index from the swapchain.
deUint32 recordAndSubmitFrame (FrameStreamObjects::FrameObjects& frameObjects, const vk::wsi::WsiTriangleRenderer& triangleRenderer, const vk::DeviceInterface& vkd, vk::VkDevice device, vk::VkSwapchainKHR swapchain, size_t swapchainSize, vk::VkQueue queue, size_t frameNumber, tcu::TestLog& testLog)
{
// Wait and reset the render complete fence to avoid having too many submitted frames.
VK_CHECK(vkd.waitForFences(device, 1u, &frameObjects.renderCompleteFence, VK_TRUE, std::numeric_limits<deUint64>::max()));
VK_CHECK(vkd.resetFences(device, 1, &frameObjects.renderCompleteFence));
// Acquire swapchain image.
deUint32 imageNdx = std::numeric_limits<deUint32>::max();
const vk::VkResult acquireResult = vkd.acquireNextImageKHR(device,
swapchain,
std::numeric_limits<deUint64>::max(),
frameObjects.imageAvailableSemaphore,
(vk::VkFence)0,
&imageNdx);
if (acquireResult == vk::VK_SUBOPTIMAL_KHR)
testLog << tcu::TestLog::Message << "Got " << acquireResult << " at frame " << frameNumber << tcu::TestLog::EndMessage;
else
VK_CHECK(acquireResult);
TCU_CHECK(static_cast<size_t>(imageNdx) < swapchainSize);
// Submit frame to the queue.
const vk::VkPipelineStageFlags waitDstStage = vk::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
const vk::VkSubmitInfo submitInfo =
{
vk::VK_STRUCTURE_TYPE_SUBMIT_INFO,
nullptr,
1u,
&frameObjects.imageAvailableSemaphore,
&waitDstStage,
1u,
&frameObjects.commandBuffer,
1u,
&frameObjects.renderCompleteSemaphore,
};
triangleRenderer.recordFrame(frameObjects.commandBuffer, imageNdx, static_cast<deUint32>(frameNumber));
VK_CHECK(vkd.queueSubmit(queue, 1u, &submitInfo, frameObjects.renderCompleteFence));
return imageNdx;
}
tcu::TestStatus PresentIdWaitSimpleInstance::run (const vk::DeviceInterface& vkd, vk::VkDevice device, vk::VkQueue queue, vk::VkCommandPool commandPool, vk::VkSwapchainKHR swapchain, size_t swapchainSize, const vk::wsi::WsiTriangleRenderer& renderer)
{
const size_t maxQueuedFrames = swapchainSize*2;
FrameStreamObjects frameStreamObjects (vkd, device, commandPool, maxQueuedFrames);
for (const auto& step : m_sequence)
{
for (const auto& presentOp : step.presentOps)
{
// Get objects for the next frame.
FrameStreamObjects::FrameObjects frameObjects = frameStreamObjects.newFrame();
// Record and submit new frame.
deUint32 imageNdx = recordAndSubmitFrame(frameObjects, renderer, vkd, device, swapchain, swapchainSize, queue, frameStreamObjects.frameNumber(), m_context.getTestContext().getLog());
// Present rendered frame.
const vk::VkPresentIdKHR presentId =
{
vk::VK_STRUCTURE_TYPE_PRESENT_ID_KHR, // VkStructureType sType;
nullptr, // const void* pNext;
(presentOp.presentId ? 1u : 0u), // deUint32 swapchainCount;
(presentOp.presentId ? &presentOp.presentId.get() : nullptr ), // const deUint64* pPresentIds;
};
const vk::VkPresentInfoKHR presentInfo =
{
vk::VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
(presentOp.presentId ? &presentId : nullptr),
1u,
&frameObjects.renderCompleteSemaphore,
1u,
&swapchain,
&imageNdx,
nullptr,
};
vk::VkResult result = vkd.queuePresentKHR(queue, &presentInfo);
if (presentOp.expectedResult)
{
const vk::VkResult expected = presentOp.expectedResult.get();
if ((expected == vk::VK_SUCCESS && result != vk::VK_SUCCESS && result != vk::VK_SUBOPTIMAL_KHR) ||
(expected != vk::VK_SUCCESS && result != expected))
{
std::ostringstream msg;
msg << "Got " << result << " while expecting " << expected << " after presenting with ";
if (presentOp.presentId)
msg << "id " << presentOp.presentId.get();
else
msg << "no id";
TCU_FAIL(msg.str());
}
}
}
// Wait operations.
for (const auto& waitOp : step.waitOps)
{
auto before = std::chrono::high_resolution_clock::now();
vk::VkResult waitResult = vkd.waitForPresentKHR(device, swapchain, waitOp.presentId, waitOp.timeout);
auto after = std::chrono::high_resolution_clock::now();
auto diff = std::chrono::nanoseconds(after - before).count();
if (waitOp.timeoutExpected)
{
if (waitResult != vk::VK_TIMEOUT)
{
std::ostringstream msg;
msg << "Got " << waitResult << " while expecting a timeout in vkWaitForPresentKHR call";
TCU_FAIL(msg.str());
}
const auto timeoutRange = calcTimeoutRange(waitOp.timeout);
if (diff < timeoutRange.first || diff > timeoutRange.second)
{
std::ostringstream msg;
msg << "vkWaitForPresentKHR waited for " << diff << " nanoseconds with a timeout of " << waitOp.timeout << " nanoseconds";
TCU_FAIL(msg.str());
}
}
else if (waitResult != vk::VK_SUCCESS)
{
std::ostringstream msg;
msg << "Got " << waitResult << " while expecting success in vkWaitForPresentKHR call";
TCU_FAIL(msg.str());
}
}
}
// Wait until device is idle.
VK_CHECK(vkd.deviceWaitIdle(device));
return tcu::TestStatus::pass("Pass");
}
// Parent class for VK_KHR_present_id simple tests.
class PresentIdInstance : public PresentIdWaitSimpleInstance
{
public:
PresentIdInstance(Context& context, vk::wsi::Type wsiType, const vector<PresentAndWaitOps>& sequence)
: PresentIdWaitSimpleInstance(context, wsiType, sequence)
{}
virtual ~PresentIdInstance() {}
static vector<const char*> requiredDeviceExts (void)
{
vector<const char*> extensions;
extensions.push_back("VK_KHR_present_id");
return extensions;
}
virtual vector<const char*> getRequiredDeviceExts (void)
{
return requiredDeviceExts();
}
};
// Parent class for VK_KHR_present_wait simple tests.
class PresentWaitInstance : public PresentIdWaitSimpleInstance
{
public:
PresentWaitInstance(Context& context, vk::wsi::Type wsiType, const vector<PresentAndWaitOps>& sequence)
: PresentIdWaitSimpleInstance(context, wsiType, sequence)
{}
virtual ~PresentWaitInstance() {}
static vector<const char*> requiredDeviceExts (void)
{
vector<const char*> extensions;
extensions.push_back("VK_KHR_present_id");
extensions.push_back("VK_KHR_present_wait");
return extensions;
}
virtual vector<const char*> getRequiredDeviceExts (void)
{
return requiredDeviceExts();
}
};
class PresentIdZeroInstance : public PresentIdInstance
{
public:
static const vector<PresentAndWaitOps> sequence;
PresentIdZeroInstance (Context& context, vk::wsi::Type wsiType)
: PresentIdInstance(context, wsiType, sequence)
{}
};
const vector<PresentAndWaitOps> PresentIdZeroInstance::sequence =
{
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::just<deUint64>(0), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
},
},
};
class PresentIdIncreasingInstance : public PresentIdInstance
{
public:
static const vector<PresentAndWaitOps> sequence;
PresentIdIncreasingInstance (Context& context, vk::wsi::Type wsiType)
: PresentIdInstance(context, wsiType, sequence)
{}
};
const vector<PresentAndWaitOps> PresentIdIncreasingInstance::sequence =
{
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::just<deUint64>(1), tcu::just(vk::VK_SUCCESS) },
{ tcu::just(std::numeric_limits<deUint64>::max()), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
},
},
};
class PresentIdInterleavedInstance : public PresentIdInstance
{
public:
static const vector<PresentAndWaitOps> sequence;
PresentIdInterleavedInstance (Context& context, vk::wsi::Type wsiType)
: PresentIdInstance(context, wsiType, sequence)
{}
};
const vector<PresentAndWaitOps> PresentIdInterleavedInstance::sequence =
{
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::just<deUint64>(0), tcu::just(vk::VK_SUCCESS) },
{ tcu::just<deUint64>(1), tcu::just(vk::VK_SUCCESS) },
{ tcu::nothing<deUint64>(), tcu::just(vk::VK_SUCCESS) },
{ tcu::just(std::numeric_limits<deUint64>::max()), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
},
},
};
class PresentWaitSingleFrameInstance : public PresentWaitInstance
{
public:
static const vector<PresentAndWaitOps> sequence;
PresentWaitSingleFrameInstance (Context& context, vk::wsi::Type wsiType)
: PresentWaitInstance(context, wsiType, sequence)
{}
};
const vector<PresentAndWaitOps> PresentWaitSingleFrameInstance::sequence =
{
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::just<deUint64>(1), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
{ 1ull, k10sec, false },
},
},
};
class PresentWaitPastFrameInstance : public PresentWaitInstance
{
public:
static const vector<PresentAndWaitOps> sequence;
PresentWaitPastFrameInstance (Context& context, vk::wsi::Type wsiType)
: PresentWaitInstance(context, wsiType, sequence)
{}
};
const vector<PresentAndWaitOps> PresentWaitPastFrameInstance::sequence =
{
// Start with present id 1.
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::just<deUint64>(1), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
{ 1ull, k10sec, false },
{ 1ull, 0ull, false },
},
},
// Then the maximum value. Both waiting for id 1 and the max id should work.
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::just(std::numeric_limits<deUint64>::max()), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
{ 1ull, 0ull, false },
{ 1ull, k10sec, false },
{ std::numeric_limits<deUint64>::max(), k10sec, false },
{ std::numeric_limits<deUint64>::max(), 0ull, false },
},
},
// Submit some frames without id after having used the maximum value. This should also work.
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::nothing<deUint64>(), tcu::just(vk::VK_SUCCESS) },
{ tcu::just<deUint64>(0), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
},
},
};
class PresentWaitNoFramesInstance : public PresentWaitInstance
{
public:
static const vector<PresentAndWaitOps> sequence;
PresentWaitNoFramesInstance (Context& context, vk::wsi::Type wsiType)
: PresentWaitInstance(context, wsiType, sequence)
{}
};
const vector<PresentAndWaitOps> PresentWaitNoFramesInstance::sequence =
{
{ // PresentAndWaitOps
{ // presentOps vector
},
{ // waitOps vector
{ 1ull, 0ull, true },
{ 1ull, k1sec, true },
},
},
};
class PresentWaitNoFrameIdInstance : public PresentWaitInstance
{
public:
static const vector<PresentAndWaitOps> sequence;
PresentWaitNoFrameIdInstance (Context& context, vk::wsi::Type wsiType)
: PresentWaitInstance(context, wsiType, sequence)
{}
};
const vector<PresentAndWaitOps> PresentWaitNoFrameIdInstance::sequence =
{
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::just<deUint64>(0), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
{ 1ull, 0ull, true },
{ 1ull, k1sec, true },
},
},
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::nothing<deUint64>(), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
{ 1ull, 0ull, true },
{ 1ull, k1sec, true },
},
},
};
class PresentWaitFutureFrameInstance : public PresentWaitInstance
{
public:
static const vector<PresentAndWaitOps> sequence;
PresentWaitFutureFrameInstance (Context& context, vk::wsi::Type wsiType)
: PresentWaitInstance(context, wsiType, sequence)
{}
};
const vector<PresentAndWaitOps> PresentWaitFutureFrameInstance::sequence =
{
{ // PresentAndWaitOps
{ // presentOps vector
{ tcu::just<deUint64>(1), tcu::just(vk::VK_SUCCESS) },
},
{ // waitOps vector
{ std::numeric_limits<deUint64>::max(), k1sec, true },
{ std::numeric_limits<deUint64>::max(), 0ull, true },
{ 2ull, 0ull, true },
{ 2ull, k1sec, true },
},
},
};
// Instance with two windows and surfaces to check present ids are not mixed up.
class PresentWaitDualInstance : public TestInstance
{
public:
PresentWaitDualInstance (Context& context, vk::wsi::Type wsiType) : TestInstance(context), m_wsiType(wsiType) {}
virtual ~PresentWaitDualInstance (void) {}
virtual tcu::TestStatus iterate (void);
static vector<const char*> requiredDeviceExts (void)
{
vector<const char*> extensions;
extensions.push_back("VK_KHR_present_id");
extensions.push_back("VK_KHR_present_wait");
return extensions;
}
virtual vector<const char*> getRequiredDeviceExts (void)
{
return requiredDeviceExts();
}
protected:
vk::wsi::Type m_wsiType;
};
struct IdAndWait
{
deUint64 presentId;
bool wait;
};
struct DualIdAndWait
{
IdAndWait idWait1;
IdAndWait idWait2;
};
tcu::TestStatus PresentWaitDualInstance::iterate (void)
{
const tcu::UVec2 desiredSize (256, 256);
const InstanceHelper instHelper (m_context, m_wsiType);
const NativeObjects native (m_context, instHelper.supportedExtensions, m_wsiType, 2u, tcu::just(desiredSize));
const vk::Unique<vk::VkSurfaceKHR> surface1 (createSurface(instHelper.vki, instHelper.instance, m_wsiType, native.getDisplay(), native.getWindow(0)));
const vk::Unique<vk::VkSurfaceKHR> surface2 (createSurface(instHelper.vki, instHelper.instance, m_wsiType, native.getDisplay(), native.getWindow(1)));
const DeviceHelper devHelper (m_context, instHelper.vki, instHelper.instance, vector<vk::VkSurfaceKHR>{surface1.get(), surface2.get()}, getRequiredDeviceExts());
const vk::DeviceInterface& vkd = devHelper.vkd;
const vk::VkDevice device = *devHelper.device;
vk::SimpleAllocator allocator (vkd, device, getPhysicalDeviceMemoryProperties(instHelper.vki, devHelper.physicalDevice));
const vk::VkSwapchainCreateInfoKHR swapchainInfo1 = getBasicSwapchainParameters(m_wsiType, instHelper.vki, devHelper.physicalDevice, surface1.get(), desiredSize, 2);
const vk::VkSwapchainCreateInfoKHR swapchainInfo2 = getBasicSwapchainParameters(m_wsiType, instHelper.vki, devHelper.physicalDevice, surface2.get(), desiredSize, 2);
const vk::Unique<vk::VkSwapchainKHR> swapchain1 (vk::createSwapchainKHR(vkd, device, &swapchainInfo1));
const vk::Unique<vk::VkSwapchainKHR> swapchain2 (vk::createSwapchainKHR(vkd, device, &swapchainInfo2));
const vector<vk::VkImage> swapchainImages1 = vk::wsi::getSwapchainImages(vkd, device, swapchain1.get());
const vector<vk::VkImage> swapchainImages2 = vk::wsi::getSwapchainImages(vkd, device, swapchain2.get());
const vk::Unique<vk::VkCommandPool> commandPool (createCommandPool(vkd, device, vk::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, devHelper.queueFamilyIndex));
const vk::wsi::WsiTriangleRenderer renderer1 (vkd,
device,
allocator,
m_context.getBinaryCollection(),
false,
swapchainImages1,
swapchainImages1,
swapchainInfo1.imageFormat,
tcu::UVec2(swapchainInfo1.imageExtent.width, swapchainInfo1.imageExtent.height));
const vk::wsi::WsiTriangleRenderer renderer2 (vkd,
device,
allocator,
m_context.getBinaryCollection(),
false,
swapchainImages2,
swapchainImages2,
swapchainInfo2.imageFormat,
tcu::UVec2(swapchainInfo2.imageExtent.width, swapchainInfo2.imageExtent.height));
tcu::TestLog& testLog = m_context.getTestContext().getLog();
try
{
const size_t maxQueuedFrames = swapchainImages1.size()*2;
FrameStreamObjects frameStreamObjects1 (vkd, device, commandPool.get(), maxQueuedFrames);
FrameStreamObjects frameStreamObjects2 (vkd, device, commandPool.get(), maxQueuedFrames);
// Increasing ids for both swapchains, waiting on some to make sure we do not time out unexpectedly.
const vector<DualIdAndWait> sequence =
{
{
{ 1ull, false },
{ 2ull, true },
},
{
{ 4ull, true },
{ 3ull, false },
},
{
{ 5ull, true },
{ 6ull, true },
},
};
for (const auto& step : sequence)
{
// Get objects for the next frames.
FrameStreamObjects::FrameObjects frameObjects1 = frameStreamObjects1.newFrame();
FrameStreamObjects::FrameObjects frameObjects2 = frameStreamObjects2.newFrame();
// Record and submit frame.
deUint32 imageNdx1 = recordAndSubmitFrame(frameObjects1, renderer1, vkd, device, swapchain1.get(), swapchainImages1.size(), devHelper.queue, frameStreamObjects1.frameNumber(), testLog);
deUint32 imageNdx2 = recordAndSubmitFrame(frameObjects2, renderer2, vkd, device, swapchain2.get(), swapchainImages2.size(), devHelper.queue, frameStreamObjects2.frameNumber(), testLog);
// Present both images at the same time with their corresponding ids.
const deUint64 presentIdsArr[] = { step.idWait1.presentId, step.idWait2.presentId };
const vk::VkPresentIdKHR presentId =
{
vk::VK_STRUCTURE_TYPE_PRESENT_ID_KHR, // VkStructureType sType;
nullptr, // const void* pNext;
static_cast<deUint32>(DE_LENGTH_OF_ARRAY(presentIdsArr)), // deUint32 swapchainCount;
presentIdsArr, // const deUint64* pPresentIds;
};
const vk::VkSemaphore semaphoreArr[] = { frameObjects1.renderCompleteSemaphore, frameObjects2.renderCompleteSemaphore };
const vk::VkSwapchainKHR swapchainArr[] = { swapchain1.get(), swapchain2.get() };
const deUint32 imgIndexArr[] = { imageNdx1, imageNdx2 };
const vk::VkPresentInfoKHR presentInfo =
{
vk::VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
&presentId,
static_cast<deUint32>(DE_LENGTH_OF_ARRAY(semaphoreArr)),
semaphoreArr,
static_cast<deUint32>(DE_LENGTH_OF_ARRAY(swapchainArr)),
swapchainArr,
imgIndexArr,
nullptr,
};
VK_CHECK(vkd.queuePresentKHR(devHelper.queue, &presentInfo));
const IdAndWait* idWaitArr[] = { &step.idWait1, &step.idWait2 };
for (int i = 0; i < DE_LENGTH_OF_ARRAY(idWaitArr); ++i)
{
if (idWaitArr[i]->wait)
VK_CHECK(vkd.waitForPresentKHR(device, swapchainArr[i], idWaitArr[i]->presentId, k10sec));
}
}
// Wait until device is idle.
VK_CHECK(vkd.deviceWaitIdle(device));
return tcu::TestStatus::pass("Pass");
}
catch (...)
{
// Make sure device is idle before destroying resources
vkd.deviceWaitIdle(device);
throw;
}
return tcu::TestStatus(QP_TEST_RESULT_INTERNAL_ERROR, "Reached unreachable code");
}
// Templated class for every instance type.
template <class T> // T is the test instance class.
class PresentIdWaitCase : public TestCase
{
public:
PresentIdWaitCase (vk::wsi::Type wsiType, tcu::TestContext& ctx, const std::string& name, const std::string& description);
virtual ~PresentIdWaitCase (void) {}
virtual void initPrograms (vk::SourceCollections& programCollection) const;
virtual TestInstance* createInstance (Context& context) const;
virtual void checkSupport (Context& context) const;
protected:
vk::wsi::Type m_wsiType;
};
template <class T>
PresentIdWaitCase<T>::PresentIdWaitCase (vk::wsi::Type wsiType, tcu::TestContext& ctx, const std::string& name, const std::string& description)
: TestCase(ctx, name, description), m_wsiType(wsiType)
{
}
template <class T>
void PresentIdWaitCase<T>::initPrograms (vk::SourceCollections& programCollection) const
{
vk::wsi::WsiTriangleRenderer::getPrograms(programCollection);
}
template <class T>
TestInstance* PresentIdWaitCase<T>::createInstance (Context& context) const
{
return new T(context, m_wsiType);
}
template <class T>
void PresentIdWaitCase<T>::checkSupport (Context& context) const
{
// Check instance extension support.
const auto instanceExtensions = getRequiredInstanceExtensions(m_wsiType);
for (const auto& ext : instanceExtensions)
{
if (!context.isInstanceFunctionalitySupported(ext))
TCU_THROW(NotSupportedError, ext + string(" is not supported"));
}
// Check device extension support.
const auto& vki = context.getInstanceInterface();
const auto physDev = context.getPhysicalDevice();
const auto supportedDeviceExts = vk::enumerateDeviceExtensionProperties(vki, physDev, nullptr);
const auto mandatoryDeviceExts = getMandatoryDeviceExtensions();
auto checkedDeviceExts = T::requiredDeviceExts();
for (const auto& ext : mandatoryDeviceExts)
checkedDeviceExts.push_back(ext);
for (const auto& ext : checkedDeviceExts)
{
if (!vk::isExtensionSupported(supportedDeviceExts, vk::RequiredExtension(ext)))
TCU_THROW(NotSupportedError, ext + string(" is not supported"));
}
}
void createPresentIdTests (tcu::TestCaseGroup* testGroup, vk::wsi::Type wsiType)
{
testGroup->addChild(new PresentIdWaitCase<PresentIdZeroInstance> (wsiType, testGroup->getTestContext(), "zero", "Use present id zero"));
testGroup->addChild(new PresentIdWaitCase<PresentIdIncreasingInstance> (wsiType, testGroup->getTestContext(), "increasing", "Use increasing present ids"));
testGroup->addChild(new PresentIdWaitCase<PresentIdInterleavedInstance> (wsiType, testGroup->getTestContext(), "interleaved", "Use increasing present ids interleaved with no ids"));
}
void createPresentWaitTests (tcu::TestCaseGroup* testGroup, vk::wsi::Type wsiType)
{
testGroup->addChild(new PresentIdWaitCase<PresentWaitSingleFrameInstance> (wsiType, testGroup->getTestContext(), "single_no_timeout", "Present single frame with no expected timeout"));
testGroup->addChild(new PresentIdWaitCase<PresentWaitPastFrameInstance> (wsiType, testGroup->getTestContext(), "past_no_timeout", "Wait for past frame with no expected timeout"));
testGroup->addChild(new PresentIdWaitCase<PresentWaitNoFramesInstance> (wsiType, testGroup->getTestContext(), "no_frames", "Expect timeout before submitting any frame"));
testGroup->addChild(new PresentIdWaitCase<PresentWaitNoFrameIdInstance> (wsiType, testGroup->getTestContext(), "no_frame_id", "Expect timeout after submitting frames with no id"));
testGroup->addChild(new PresentIdWaitCase<PresentWaitFutureFrameInstance> (wsiType, testGroup->getTestContext(), "future_frame", "Expect timeout when waiting for a future frame"));
testGroup->addChild(new PresentIdWaitCase<PresentWaitDualInstance> (wsiType, testGroup->getTestContext(), "two_swapchains", "Smoke test using two windows, surfaces and swapchains"));
}
} // anonymous
void createPresentIdWaitTests (tcu::TestCaseGroup* testGroup, vk::wsi::Type wsiType)
{
de::MovePtr<tcu::TestCaseGroup> idGroup (new tcu::TestCaseGroup(testGroup->getTestContext(), "id", "VK_KHR_present_id tests"));
de::MovePtr<tcu::TestCaseGroup> waitGroup (new tcu::TestCaseGroup(testGroup->getTestContext(), "wait", "VK_KHR_present_wait tests"));
createPresentIdTests (idGroup.get(), wsiType);
createPresentWaitTests (waitGroup.get(), wsiType);
testGroup->addChild(idGroup.release());
testGroup->addChild(waitGroup.release());
}
} // wsi
} // vkt