Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1f068b4

Browse files
[Impeller] Make CreateMockVulkanContext() thread-safe (#45687)
I started using `CreateMockVulkanContext()` in #45654, and discovered that it was not thread-safe. This PR does a little bit of refactoring/TLC to ensure that functions and buffers can be accessed across threads (like the _real_ `ContextVK`). The attached test (_test for a test fixture, hoorah!_) fails consistently before the changes, and passes after. Note that if you try testing this with `--gtest_repeat=10000` (at least that's the threshold for me), it'll fail - that's actually due to flutter/flutter#134482 which in turn is being fixed in #45686. (Unblocks flutter/flutter#133198) --------- Co-authored-by: gaaclarke <30870216+gaaclarke@users.noreply.github.com>
1 parent 20fdf9e commit 1f068b4

File tree

4 files changed

+78
-17
lines changed

4 files changed

+78
-17
lines changed

impeller/renderer/backend/vulkan/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ impeller_component("vulkan_unittests") {
1515
"resource_manager_vk_unittests.cc",
1616
"test/mock_vulkan.cc",
1717
"test/mock_vulkan.h",
18+
"test/mock_vulkan_unittests.cc",
1819
]
1920
deps = [
2021
":vulkan",

impeller/renderer/backend/vulkan/context_vk_unittests.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
#include "flutter/testing/testing.h"
5+
#include "flutter/testing/testing.h" // IWYU pragma: keep
66
#include "impeller/renderer/backend/vulkan/command_pool_vk.h"
77
#include "impeller/renderer/backend/vulkan/context_vk.h"
88
#include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"

impeller/renderer/backend/vulkan/test/mock_vulkan.cc

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// found in the LICENSE file.
44

55
#include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"
6+
#include <vector>
7+
#include "fml/macros.h"
8+
#include "impeller/base/thread_safety.h"
69

710
namespace impeller {
811
namespace testing {
@@ -16,17 +19,37 @@ struct MockCommandBuffer {
1619
std::shared_ptr<std::vector<std::string>> called_functions_;
1720
};
1821

19-
struct MockDevice {
20-
MockDevice() : called_functions_(new std::vector<std::string>()) {}
22+
class MockDevice final {
23+
public:
24+
explicit MockDevice() : called_functions_(new std::vector<std::string>()) {}
25+
2126
MockCommandBuffer* NewCommandBuffer() {
22-
std::unique_ptr<MockCommandBuffer> buffer =
23-
std::make_unique<MockCommandBuffer>(called_functions_);
27+
auto buffer = std::make_unique<MockCommandBuffer>(called_functions_);
2428
MockCommandBuffer* result = buffer.get();
29+
Lock lock(command_buffers_mutex_);
2530
command_buffers_.emplace_back(std::move(buffer));
2631
return result;
2732
}
28-
std::shared_ptr<std::vector<std::string>> called_functions_;
29-
std::vector<std::unique_ptr<MockCommandBuffer>> command_buffers_;
33+
34+
const std::shared_ptr<std::vector<std::string>>& GetCalledFunctions() {
35+
return called_functions_;
36+
}
37+
38+
void AddCalledFunction(const std::string& function) {
39+
Lock lock(called_functions_mutex_);
40+
called_functions_->push_back(function);
41+
}
42+
43+
private:
44+
FML_DISALLOW_COPY_AND_ASSIGN(MockDevice);
45+
46+
Mutex called_functions_mutex_;
47+
std::shared_ptr<std::vector<std::string>> called_functions_
48+
IPLR_GUARDED_BY(called_functions_mutex_);
49+
50+
Mutex command_buffers_mutex_;
51+
std::vector<std::unique_ptr<MockCommandBuffer>> command_buffers_
52+
IPLR_GUARDED_BY(command_buffers_mutex_);
3053
};
3154

3255
void noop() {}
@@ -147,7 +170,7 @@ VkResult vkCreatePipelineCache(VkDevice device,
147170
const VkAllocationCallbacks* pAllocator,
148171
VkPipelineCache* pPipelineCache) {
149172
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
150-
mock_device->called_functions_->push_back("vkCreatePipelineCache");
173+
mock_device->AddCalledFunction("vkCreatePipelineCache");
151174
*pPipelineCache = reinterpret_cast<VkPipelineCache>(0xb000dead);
152175
return VK_SUCCESS;
153176
}
@@ -270,30 +293,30 @@ VkResult vkCreateGraphicsPipelines(
270293
const VkAllocationCallbacks* pAllocator,
271294
VkPipeline* pPipelines) {
272295
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
273-
mock_device->called_functions_->push_back("vkCreateGraphicsPipelines");
296+
mock_device->AddCalledFunction("vkCreateGraphicsPipelines");
274297
*pPipelines = reinterpret_cast<VkPipeline>(0x99999999);
275298
return VK_SUCCESS;
276299
}
277300

278301
void vkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) {
279302
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
280-
mock_device->called_functions_->push_back("vkDestroyDevice");
303+
mock_device->AddCalledFunction("vkDestroyDevice");
281304
delete reinterpret_cast<MockDevice*>(device);
282305
}
283306

284307
void vkDestroyPipeline(VkDevice device,
285308
VkPipeline pipeline,
286309
const VkAllocationCallbacks* pAllocator) {
287310
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
288-
mock_device->called_functions_->push_back("vkDestroyPipeline");
311+
mock_device->AddCalledFunction("vkDestroyPipeline");
289312
}
290313

291314
VkResult vkCreateShaderModule(VkDevice device,
292315
const VkShaderModuleCreateInfo* pCreateInfo,
293316
const VkAllocationCallbacks* pAllocator,
294317
VkShaderModule* pShaderModule) {
295318
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
296-
mock_device->called_functions_->push_back("vkCreateShaderModule");
319+
mock_device->AddCalledFunction("vkCreateShaderModule");
297320
*pShaderModule = reinterpret_cast<VkShaderModule>(0x11111111);
298321
return VK_SUCCESS;
299322
}
@@ -302,14 +325,14 @@ void vkDestroyShaderModule(VkDevice device,
302325
VkShaderModule shaderModule,
303326
const VkAllocationCallbacks* pAllocator) {
304327
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
305-
mock_device->called_functions_->push_back("vkDestroyShaderModule");
328+
mock_device->AddCalledFunction("vkDestroyShaderModule");
306329
}
307330

308331
void vkDestroyPipelineCache(VkDevice device,
309332
VkPipelineCache pipelineCache,
310333
const VkAllocationCallbacks* pAllocator) {
311334
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
312-
mock_device->called_functions_->push_back("vkDestroyPipelineCache");
335+
mock_device->AddCalledFunction("vkDestroyPipelineCache");
313336
}
314337

315338
void vkCmdBindPipeline(VkCommandBuffer commandBuffer,
@@ -351,14 +374,14 @@ void vkFreeCommandBuffers(VkDevice device,
351374
uint32_t commandBufferCount,
352375
const VkCommandBuffer* pCommandBuffers) {
353376
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
354-
mock_device->called_functions_->push_back("vkFreeCommandBuffers");
377+
mock_device->AddCalledFunction("vkFreeCommandBuffers");
355378
}
356379

357380
void vkDestroyCommandPool(VkDevice device,
358381
VkCommandPool commandPool,
359382
const VkAllocationCallbacks* pAllocator) {
360383
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
361-
mock_device->called_functions_->push_back("vkDestroyCommandPool");
384+
mock_device->AddCalledFunction("vkDestroyCommandPool");
362385
}
363386

364387
VkResult vkEndCommandBuffer(VkCommandBuffer commandBuffer) {
@@ -500,7 +523,7 @@ std::shared_ptr<ContextVK> CreateMockVulkanContext(void) {
500523
std::shared_ptr<std::vector<std::string>> GetMockVulkanFunctions(
501524
VkDevice device) {
502525
MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
503-
return mock_device->called_functions_;
526+
return mock_device->GetCalledFunctions();
504527
}
505528

506529
} // namespace testing
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/testing/testing.h" // IWYU pragma: keep
6+
#include "gtest/gtest.h"
7+
#include "impeller/renderer/backend/vulkan/command_pool_vk.h"
8+
#include "impeller/renderer/backend/vulkan/test/mock_vulkan.h"
9+
10+
namespace impeller {
11+
namespace testing {
12+
13+
TEST(MockVulkanContextTest, IsThreadSafe) {
14+
// In a typical app, there is a single ContextVK per app, shared b/w threads.
15+
//
16+
// This test ensures that the (mock) ContextVK is thread-safe.
17+
auto const context = CreateMockVulkanContext();
18+
19+
// Spawn two threads, and have them create a CommandPoolVK each.
20+
std::thread thread1([&context]() {
21+
auto const pool = CommandPoolVK::GetThreadLocal(context.get());
22+
EXPECT_TRUE(pool);
23+
});
24+
25+
std::thread thread2([&context]() {
26+
auto const pool = CommandPoolVK::GetThreadLocal(context.get());
27+
EXPECT_TRUE(pool);
28+
});
29+
30+
thread1.join();
31+
thread2.join();
32+
33+
context->Shutdown();
34+
}
35+
36+
} // namespace testing
37+
} // namespace impeller

0 commit comments

Comments
 (0)