[Impeller] Introduce mock vulkan context builder (flutter/engine#45834)

This allows us to have better control over the mock context and allows us to test situations like having the validations available, versus not available.

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
gaaclarke
2023-09-15 08:42:03 -07:00
committed by GitHub
parent 56b19ab7ec
commit 30d0cf868d
7 changed files with 129 additions and 41 deletions

View File

@@ -11,7 +11,7 @@ namespace impeller {
namespace testing {
TEST(BlitCommandVkTest, BlitCopyTextureToTextureCommandVK) {
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
auto pool = CommandPoolVK::GetThreadLocal(context.get());
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
BlitCopyTextureToTextureCommandVK cmd;
@@ -28,7 +28,7 @@ TEST(BlitCommandVkTest, BlitCopyTextureToTextureCommandVK) {
}
TEST(BlitCommandVkTest, BlitCopyTextureToBufferCommandVK) {
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
BlitCopyTextureToBufferCommandVK cmd;
cmd.source = context->GetResourceAllocator()->CreateTexture({
@@ -44,7 +44,7 @@ TEST(BlitCommandVkTest, BlitCopyTextureToBufferCommandVK) {
}
TEST(BlitCommandVkTest, BlitCopyBufferToTextureCommandVK) {
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
BlitCopyBufferToTextureCommandVK cmd;
cmd.destination = context->GetResourceAllocator()->CreateTexture({
@@ -62,7 +62,7 @@ TEST(BlitCommandVkTest, BlitCopyBufferToTextureCommandVK) {
}
TEST(BlitCommandVkTest, BlitGenerateMipmapCommandVK) {
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
BlitGenerateMipmapCommandVK cmd;
cmd.texture = context->GetResourceAllocator()->CreateTexture({

View File

@@ -18,7 +18,7 @@ TEST(CommandEncoderVKTest, DeleteEncoderAfterThreadDies) {
// command buffers before it cleans up its command pool.
std::shared_ptr<std::vector<std::string>> called_functions;
{
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
called_functions = GetMockVulkanFunctions(context->GetDevice());
std::shared_ptr<CommandEncoderVK> encoder;
std::thread thread([&] {
@@ -46,7 +46,7 @@ TEST(CommandEncoderVKTest, CleanupAfterSubmit) {
{
fml::AutoResetWaitableEvent wait_for_submit;
fml::AutoResetWaitableEvent wait_for_thread_join;
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
std::thread thread([&] {
CommandEncoderFactoryVK factory(context);
std::shared_ptr<CommandEncoderVK> encoder = factory.Create();

View File

@@ -14,7 +14,7 @@ TEST(ContextVKTest, DeletesCommandPools) {
std::weak_ptr<ContextVK> weak_context;
std::weak_ptr<CommandPoolVK> weak_pool;
{
std::shared_ptr<ContextVK> context = CreateMockVulkanContext();
std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
std::shared_ptr<CommandPoolVK> pool =
CommandPoolVK::GetThreadLocal(context.get());
weak_pool = pool;
@@ -30,7 +30,7 @@ TEST(ContextVKTest, DeletePipelineAfterContext) {
std::shared_ptr<Pipeline<PipelineDescriptor>> pipeline;
std::shared_ptr<std::vector<std::string>> functions;
{
std::shared_ptr<ContextVK> context = CreateMockVulkanContext();
std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
PipelineDescriptor pipeline_desc;
pipeline_desc.SetVertexDescriptor(std::make_shared<VertexDescriptor>());
PipelineFuture<PipelineDescriptor> pipeline_future =
@@ -49,7 +49,7 @@ TEST(ContextVKTest, DeleteShaderFunctionAfterContext) {
std::shared_ptr<const ShaderFunction> shader_function;
std::shared_ptr<std::vector<std::string>> functions;
{
std::shared_ptr<ContextVK> context = CreateMockVulkanContext();
std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
PipelineDescriptor pipeline_desc;
pipeline_desc.SetVertexDescriptor(std::make_shared<VertexDescriptor>());
std::vector<uint8_t> data = {0x03, 0x02, 0x23, 0x07};
@@ -71,7 +71,7 @@ TEST(ContextVKTest, DeletePipelineLibraryAfterContext) {
std::shared_ptr<PipelineLibrary> pipeline_library;
std::shared_ptr<std::vector<std::string>> functions;
{
std::shared_ptr<ContextVK> context = CreateMockVulkanContext();
std::shared_ptr<ContextVK> context = MockVulkanContextBuilder().Build();
PipelineDescriptor pipeline_desc;
pipeline_desc.SetVertexDescriptor(std::make_shared<VertexDescriptor>());
pipeline_library = context->GetPipelineLibrary();
@@ -86,9 +86,30 @@ TEST(ContextVKTest, DeletePipelineLibraryAfterContext) {
TEST(ContextVKTest, CanCreateContextInAbsenceOfValidationLayers) {
// The mocked methods don't report the presence of a validation layer but we
// explicitly ask for validation. Context creation should continue anyway.
auto context = CreateMockVulkanContext(
[](auto& settings) { settings.enable_validation = true; });
auto context = MockVulkanContextBuilder()
.SetSettingsCallback([](auto& settings) {
settings.enable_validation = true;
})
.Build();
ASSERT_NE(context, nullptr);
const CapabilitiesVK* capabilites_vk =
reinterpret_cast<const CapabilitiesVK*>(context->GetCapabilities().get());
ASSERT_FALSE(capabilites_vk->AreValidationsEnabled());
}
TEST(ContextVKTest, CanCreateContextWithValidationLayers) {
auto context =
MockVulkanContextBuilder()
.SetSettingsCallback(
[](auto& settings) { settings.enable_validation = true; })
.SetInstanceExtensions(
{"VK_KHR_surface", "VK_MVK_macos_surface", "VK_EXT_debug_utils"})
.SetInstanceLayers({"VK_LAYER_KHRONOS_validation"})
.Build();
ASSERT_NE(context, nullptr);
const CapabilitiesVK* capabilites_vk =
reinterpret_cast<const CapabilitiesVK*>(context->GetCapabilities().get());
ASSERT_TRUE(capabilites_vk->AreValidationsEnabled());
}
} // namespace testing

View File

@@ -24,7 +24,7 @@ int32_t CountStringViewInstances(const std::vector<std::string>& strings,
} // namespace
TEST(PassBindingsCacheTest, bindPipeline) {
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
PassBindingsCache cache;
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
auto buffer = encoder->GetCommandBuffer();
@@ -38,7 +38,7 @@ TEST(PassBindingsCacheTest, bindPipeline) {
}
TEST(PassBindingsCacheTest, setStencilReference) {
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
PassBindingsCache cache;
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
auto buffer = encoder->GetCommandBuffer();
@@ -53,7 +53,7 @@ TEST(PassBindingsCacheTest, setStencilReference) {
}
TEST(PassBindingsCacheTest, setScissor) {
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
PassBindingsCache cache;
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
auto buffer = encoder->GetCommandBuffer();
@@ -66,7 +66,7 @@ TEST(PassBindingsCacheTest, setScissor) {
}
TEST(PassBindingsCacheTest, setViewport) {
auto context = CreateMockVulkanContext();
auto context = MockVulkanContextBuilder().Build();
PassBindingsCache cache;
auto encoder = std::make_unique<CommandEncoderFactoryVK>(context)->Create();
auto buffer = encoder->GetCommandBuffer();

View File

@@ -3,8 +3,10 @@
// found in the LICENSE file.
#include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"
#include <cstring>
#include <vector>
#include "fml/macros.h"
#include "fml/thread_local.h"
#include "impeller/base/thread_safety.h"
namespace impeller {
@@ -54,25 +56,41 @@ class MockDevice final {
void noop() {}
FML_THREAD_LOCAL std::vector<std::string> g_instance_extensions;
VkResult vkEnumerateInstanceExtensionProperties(
const char* pLayerName,
uint32_t* pPropertyCount,
VkExtensionProperties* pProperties) {
if (!pProperties) {
*pPropertyCount = 2;
*pPropertyCount = g_instance_extensions.size();
} else {
strcpy(pProperties[0].extensionName, "VK_KHR_surface");
pProperties[0].specVersion = 0;
strcpy(pProperties[1].extensionName, "VK_MVK_macos_surface");
pProperties[1].specVersion = 0;
uint32_t count = 0;
for (const std::string& ext : g_instance_extensions) {
strncpy(pProperties[count].extensionName, ext.c_str(),
sizeof(VkExtensionProperties::extensionName));
pProperties[count].specVersion = 0;
count++;
}
}
return VK_SUCCESS;
}
FML_THREAD_LOCAL std::vector<std::string> g_instance_layers;
VkResult vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount,
VkLayerProperties* pProperties) {
*pPropertyCount = 0;
if (!pProperties) {
*pPropertyCount = g_instance_layers.size();
} else {
uint32_t count = 0;
for (const std::string& layer : g_instance_layers) {
strncpy(pProperties[count].layerName, layer.c_str(),
sizeof(VkLayerProperties::layerName));
pProperties[count].specVersion = 0;
count++;
}
}
return VK_SUCCESS;
}
@@ -415,6 +433,20 @@ VkResult vkGetFenceStatus(VkDevice device, VkFence fence) {
return VK_SUCCESS;
}
VkResult vkCreateDebugUtilsMessengerEXT(
VkInstance instance,
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDebugUtilsMessengerEXT* pMessenger) {
return VK_SUCCESS;
}
VkResult vkSetDebugUtilsObjectNameEXT(
VkDevice device,
const VkDebugUtilsObjectNameInfoEXT* pNameInfo) {
return VK_SUCCESS;
}
PFN_vkVoidFunction GetMockVulkanProcAddress(VkInstance instance,
const char* pName) {
if (strcmp("vkEnumerateInstanceExtensionProperties", pName) == 0) {
@@ -507,21 +539,30 @@ PFN_vkVoidFunction GetMockVulkanProcAddress(VkInstance instance,
return (PFN_vkVoidFunction)vkWaitForFences;
} else if (strcmp("vkGetFenceStatus", pName) == 0) {
return (PFN_vkVoidFunction)vkGetFenceStatus;
} else if (strcmp("vkCreateDebugUtilsMessengerEXT", pName) == 0) {
return (PFN_vkVoidFunction)vkCreateDebugUtilsMessengerEXT;
} else if (strcmp("vkSetDebugUtilsObjectNameEXT", pName) == 0) {
return (PFN_vkVoidFunction)vkSetDebugUtilsObjectNameEXT;
}
return noop;
}
} // namespace
std::shared_ptr<ContextVK> CreateMockVulkanContext(
const std::function<void(ContextVK::Settings&)>& settings_callback) {
MockVulkanContextBuilder::MockVulkanContextBuilder()
: instance_extensions_({"VK_KHR_surface", "VK_MVK_macos_surface"}) {}
std::shared_ptr<ContextVK> MockVulkanContextBuilder::Build() {
auto message_loop = fml::ConcurrentMessageLoop::Create();
ContextVK::Settings settings;
settings.proc_address_callback = GetMockVulkanProcAddress;
if (settings_callback) {
settings_callback(settings);
if (settings_callback_) {
settings_callback_(settings);
}
return ContextVK::Create(std::move(settings));
g_instance_extensions = instance_extensions_;
g_instance_layers = instance_layers_;
std::shared_ptr<ContextVK> result = ContextVK::Create(std::move(settings));
return result;
}
std::shared_ptr<std::vector<std::string>> GetMockVulkanFunctions(

View File

@@ -16,18 +16,44 @@ namespace testing {
std::shared_ptr<std::vector<std::string>> GetMockVulkanFunctions(
VkDevice device);
//------------------------------------------------------------------------------
/// @brief Create a Vulkan context with Vulkan functions mocked. The caller
/// is given a chance to tinker on the settings right before a
/// context is created.
///
/// @param[in] settings_callback The settings callback
///
/// @return A context if one can be created.
///
std::shared_ptr<ContextVK> CreateMockVulkanContext(
const std::function<void(ContextVK::Settings&)>& settings_callback =
nullptr);
class MockVulkanContextBuilder {
public:
MockVulkanContextBuilder();
//------------------------------------------------------------------------------
/// @brief Create a Vulkan context with Vulkan functions mocked. The
/// caller is given a chance to tinker on the settings right
/// before a context is created.
///
/// @return A context if one can be created.
///
std::shared_ptr<ContextVK> Build();
/// A callback that allows the modification of the ContextVK::Settings before
/// the context is made.
MockVulkanContextBuilder& SetSettingsCallback(
const std::function<void(ContextVK::Settings&)>& settings_callback) {
settings_callback_ = settings_callback;
return *this;
}
MockVulkanContextBuilder& SetInstanceExtensions(
const std::vector<std::string>& instance_extensions) {
instance_extensions_ = instance_extensions;
return *this;
}
MockVulkanContextBuilder& SetInstanceLayers(
const std::vector<std::string>& instance_layers) {
instance_layers_ = instance_layers;
return *this;
}
private:
std::function<void(ContextVK::Settings&)> settings_callback_;
std::vector<std::string> instance_extensions_;
std::vector<std::string> instance_layers_;
};
} // namespace testing
} // namespace impeller

View File

@@ -14,7 +14,7 @@ TEST(MockVulkanContextTest, IsThreadSafe) {
// In a typical app, there is a single ContextVK per app, shared b/w threads.
//
// This test ensures that the (mock) ContextVK is thread-safe.
auto const context = CreateMockVulkanContext();
auto const context = MockVulkanContextBuilder().Build();
// Spawn two threads, and have them create a CommandPoolVK each.
std::thread thread1([&context]() {