[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:
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
Reference in New Issue
Block a user