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

Commit ded5f90

Browse files
vontureCommit Bot
authored andcommitted
Vulkan: Make the Vulkan renderer thread safe.
Gate all access to the queue and caches with mutexes. Does not handle sharing of resources in share groups across threads yet. BUG=angleproject:2464 Change-Id: I297f8f1a535b99efca663cf72bac3d90df8b5d97 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1592253 Commit-Queue: Geoff Lang <[email protected]> Reviewed-by: Shahbaz Youssefi <[email protected]>
1 parent 1b0f79e commit ded5f90

File tree

3 files changed

+141
-72
lines changed

3 files changed

+141
-72
lines changed

src/libANGLE/renderer/vulkan/RendererVk.cpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,7 @@ angle::Result RendererVk::getDescriptorSetLayout(
12971297
const vk::DescriptorSetLayoutDesc &desc,
12981298
vk::BindingPointer<vk::DescriptorSetLayout> *descriptorSetLayoutOut)
12991299
{
1300-
// TODO(geofflang): Synchronize access to the descriptor set layout cache
1300+
std::lock_guard<decltype(mDescriptorSetLayoutCacheMutex)> lock(mDescriptorSetLayoutCacheMutex);
13011301
return mDescriptorSetLayoutCache.getDescriptorSetLayout(context, desc, descriptorSetLayoutOut);
13021302
}
13031303

@@ -1307,7 +1307,7 @@ angle::Result RendererVk::getPipelineLayout(
13071307
const vk::DescriptorSetLayoutPointerArray &descriptorSetLayouts,
13081308
vk::BindingPointer<vk::PipelineLayout> *pipelineLayoutOut)
13091309
{
1310-
// TODO(geofflang): Synchronize access to the pipeline layout cache
1310+
std::lock_guard<decltype(mPipelineLayoutCacheMutex)> lock(mPipelineLayoutCacheMutex);
13111311
return mPipelineLayoutCache.getPipelineLayout(context, desc, descriptorSetLayouts,
13121312
pipelineLayoutOut);
13131313
}
@@ -1391,23 +1391,31 @@ angle::Result RendererVk::queueSubmit(vk::Context *context,
13911391
const VkSubmitInfo &submitInfo,
13921392
const vk::Fence &fence)
13931393
{
1394-
// TODO: synchronize queue access
1395-
ANGLE_VK_TRY(context, vkQueueSubmit(mQueue, 1, &submitInfo, fence.getHandle()));
1394+
{
1395+
std::lock_guard<decltype(mQueueMutex)> lock(mQueueMutex);
1396+
ANGLE_VK_TRY(context, vkQueueSubmit(mQueue, 1, &submitInfo, fence.getHandle()));
1397+
}
1398+
13961399
ANGLE_TRY(cleanupGarbage(context, false));
1400+
13971401
return angle::Result::Continue;
13981402
}
13991403

14001404
angle::Result RendererVk::queueWaitIdle(vk::Context *context)
14011405
{
1402-
// TODO: synchronize queue access
1403-
ANGLE_VK_TRY(context, vkQueueWaitIdle(mQueue));
1406+
{
1407+
std::lock_guard<decltype(mQueueMutex)> lock(mQueueMutex);
1408+
ANGLE_VK_TRY(context, vkQueueWaitIdle(mQueue));
1409+
}
1410+
14041411
ANGLE_TRY(cleanupGarbage(context, false));
1412+
14051413
return angle::Result::Continue;
14061414
}
14071415

14081416
VkResult RendererVk::queuePresent(const VkPresentInfoKHR &presentInfo)
14091417
{
1410-
// TODO: synchronize queue access
1418+
std::lock_guard<decltype(mQueueMutex)> lock(mQueueMutex);
14111419
return vkQueuePresentKHR(mQueue, &presentInfo);
14121420
}
14131421

@@ -1427,6 +1435,7 @@ void RendererVk::addGarbage(vk::Shared<vk::Fence> &&fence,
14271435
void RendererVk::addGarbage(std::vector<vk::Shared<vk::Fence>> &&fences,
14281436
std::vector<vk::GarbageObjectBase> &&garbage)
14291437
{
1438+
std::lock_guard<decltype(mGarbageMutex)> lock(mGarbageMutex);
14301439
mFencedGarbage.emplace_back(std::move(fences), std::move(garbage));
14311440
}
14321441

@@ -1468,6 +1477,8 @@ bool RendererVk::hasFormatFeatureBits(VkFormat format, const VkFormatFeatureFlag
14681477

14691478
angle::Result RendererVk::cleanupGarbage(vk::Context *context, bool block)
14701479
{
1480+
std::lock_guard<decltype(mGarbageMutex)> lock(mGarbageMutex);
1481+
14711482
auto garbageIter = mFencedGarbage.begin();
14721483
while (garbageIter != mFencedGarbage.end())
14731484
{

src/libANGLE/renderer/vulkan/RendererVk.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include <vulkan/vulkan.h>
1414
#include <memory>
15+
#include <mutex>
1516

1617
#include "common/PoolAlloc.h"
1718
#include "common/angleutils.h"
@@ -70,7 +71,6 @@ class RendererVk : angle::NonCopyable
7071
{
7172
return mPhysicalDeviceFeatures;
7273
}
73-
VkQueue getQueue() const { return mQueue; }
7474
VkDevice getDevice() const { return mDevice; }
7575

7676
angle::Result selectPresentQueueForSurface(DisplayVk *displayVk,
@@ -183,6 +183,7 @@ class RendererVk : angle::NonCopyable
183183
VkPhysicalDeviceProperties mPhysicalDeviceProperties;
184184
VkPhysicalDeviceFeatures mPhysicalDeviceFeatures;
185185
std::vector<VkQueueFamilyProperties> mQueueFamilyProperties;
186+
std::mutex mQueueMutex;
186187
VkQueue mQueue;
187188
uint32_t mCurrentQueueFamilyIndex;
188189
uint32_t mMaxVertexAttribDivisor;
@@ -193,13 +194,16 @@ class RendererVk : angle::NonCopyable
193194

194195
bool mDeviceLost;
195196

197+
std::mutex mGarbageMutex;
196198
using FencedGarbage =
197199
std::pair<std::vector<vk::Shared<vk::Fence>>, std::vector<vk::GarbageObjectBase>>;
198200
std::vector<FencedGarbage> mFencedGarbage;
199201

200202
vk::MemoryProperties mMemoryProperties;
201203
vk::FormatTable mFormatTable;
202204

205+
// All access to the pipeline cache is done through EGL objects so it is thread safe to not use
206+
// a lock.
203207
vk::PipelineCache mPipelineCache;
204208
egl::BlobCache::Key mPipelineCacheVkBlobKey;
205209
uint32_t mPipelineCacheVkUpdateTimeout;
@@ -208,9 +212,11 @@ class RendererVk : angle::NonCopyable
208212
std::array<VkFormatProperties, vk::kNumVkFormats> mFormatProperties;
209213

210214
// ANGLE uses a PipelineLayout cache to store compatible pipeline layouts.
215+
std::mutex mPipelineLayoutCacheMutex;
211216
PipelineLayoutCache mPipelineLayoutCache;
212217

213218
// DescriptorSetLayouts are also managed in a cache.
219+
std::mutex mDescriptorSetLayoutCacheMutex;
214220
DescriptorSetLayoutCache mDescriptorSetLayoutCache;
215221
};
216222

src/tests/gl_tests/MultithreadingTest.cpp

Lines changed: 116 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,68 @@ class MultithreadingTest : public ANGLETest
2929
setConfigAlphaBits(8);
3030
}
3131

32-
bool platformSupportsMultithreading() const { return (IsOpenGLES() && IsAndroid()); }
32+
bool platformSupportsMultithreading() const
33+
{
34+
return (IsOpenGLES() && IsAndroid()) || IsVulkan();
35+
}
36+
37+
void runMultithreadedGLTest(
38+
std::function<void(EGLSurface surface, size_t threadIndex)> testBody,
39+
size_t threadCount)
40+
{
41+
std::mutex mutex;
42+
43+
EGLWindow *window = getEGLWindow();
44+
EGLDisplay dpy = window->getDisplay();
45+
EGLConfig config = window->getConfig();
46+
47+
constexpr EGLint kPBufferSize = 256;
48+
49+
std::vector<std::thread> threads(threadCount);
50+
for (size_t threadIdx = 0; threadIdx < threadCount; threadIdx++)
51+
{
52+
threads[threadIdx] = std::thread([&, threadIdx]() {
53+
EGLSurface surface = EGL_NO_SURFACE;
54+
EGLConfig ctx = EGL_NO_CONTEXT;
55+
56+
{
57+
std::lock_guard<decltype(mutex)> lock(mutex);
58+
59+
// Initialize the pbuffer and context
60+
EGLint pbufferAttributes[] = {
61+
EGL_WIDTH, kPBufferSize, EGL_HEIGHT, kPBufferSize, EGL_NONE, EGL_NONE,
62+
};
63+
surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
64+
EXPECT_EGL_SUCCESS();
65+
66+
ctx = window->createContext(EGL_NO_CONTEXT);
67+
EXPECT_NE(EGL_NO_CONTEXT, ctx);
68+
69+
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
70+
EXPECT_EGL_SUCCESS();
71+
}
72+
73+
testBody(surface, threadIdx);
74+
75+
{
76+
std::lock_guard<decltype(mutex)> lock(mutex);
77+
78+
// Clean up
79+
EXPECT_EGL_TRUE(
80+
eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
81+
EXPECT_EGL_SUCCESS();
82+
83+
eglDestroySurface(dpy, surface);
84+
eglDestroyContext(dpy, ctx);
85+
}
86+
});
87+
}
88+
89+
for (std::thread &thread : threads)
90+
{
91+
thread.join();
92+
}
93+
}
3394
};
3495

3596
// Test that it's possible to make one context current on different threads
@@ -74,84 +135,75 @@ TEST_P(MultithreadingTest, MakeCurrentSingleContext)
74135
EXPECT_EGL_SUCCESS();
75136
}
76137

77-
// Test that it's possible to make one multiple contexts current on different threads simultaneously
78-
TEST_P(MultithreadingTest, MakeCurrentMultiContext)
138+
// Test that multiple threads can clear and readback pixels successfully at the same time
139+
TEST_P(MultithreadingTest, MultiContextClear)
79140
{
80141
ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
81-
ANGLE_SKIP_TEST_IF(IsWindows() && IsOpenGL() && IsAMD());
82-
83-
std::mutex mutex;
84142

85-
EGLWindow *window = getEGLWindow();
86-
EGLDisplay dpy = window->getDisplay();
87-
EGLConfig config = window->getConfig();
88-
89-
constexpr size_t kThreadCount = 16;
90-
constexpr size_t kIterationsPerThread = 16;
91-
92-
constexpr EGLint kPBufferSize = 256;
93-
94-
std::array<std::thread, kThreadCount> threads;
95-
for (size_t thread = 0; thread < kThreadCount; thread++)
96-
{
97-
threads[thread] = std::thread([&, thread]() {
98-
EGLSurface pbuffer = EGL_NO_SURFACE;
99-
EGLConfig ctx = EGL_NO_CONTEXT;
100-
101-
{
102-
std::lock_guard<decltype(mutex)> lock(mutex);
143+
auto testBody = [](EGLSurface surface, size_t thread) {
144+
constexpr size_t kIterationsPerThread = 32;
145+
for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
146+
{
147+
// Base the clear color on the thread and iteration indexes so every clear color is
148+
// unique
149+
const GLColor color(static_cast<GLubyte>(thread % 255),
150+
static_cast<GLubyte>(iteration % 255), 0, 255);
151+
const angle::Vector4 floatColor = color.toNormalizedVector();
152+
153+
glClearColor(floatColor[0], floatColor[1], floatColor[2], floatColor[3]);
154+
EXPECT_GL_NO_ERROR();
155+
156+
glClear(GL_COLOR_BUFFER_BIT);
157+
EXPECT_GL_NO_ERROR();
158+
159+
EXPECT_PIXEL_COLOR_EQ(0, 0, color);
160+
}
161+
};
162+
runMultithreadedGLTest(testBody, 72);
163+
}
103164

104-
// Initialize the pbuffer and context
105-
EGLint pbufferAttributes[] = {
106-
EGL_WIDTH, kPBufferSize, EGL_HEIGHT, kPBufferSize, EGL_NONE, EGL_NONE,
107-
};
108-
pbuffer = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
109-
EXPECT_EGL_SUCCESS();
165+
// Test that multiple threads can draw and readback pixels successfully at the same time
166+
TEST_P(MultithreadingTest, MultiContextDraw)
167+
{
168+
ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
110169

111-
ctx = window->createContext(EGL_NO_CONTEXT);
112-
EXPECT_NE(EGL_NO_CONTEXT, ctx);
170+
auto testBody = [](EGLSurface surface, size_t thread) {
171+
constexpr size_t kIterationsPerThread = 32;
172+
constexpr size_t kDrawsPerIteration = 500;
113173

114-
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, pbuffer, pbuffer, ctx));
115-
EXPECT_EGL_SUCCESS();
116-
}
174+
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
175+
glUseProgram(program);
117176

118-
for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
119-
{
120-
std::lock_guard<decltype(mutex)> lock(mutex);
177+
GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
121178

122-
// Base the clear color on the thread and iteration indexes so every clear color is
123-
// unique
124-
const GLColor color(static_cast<GLubyte>(thread), static_cast<GLubyte>(iteration),
125-
0, 255);
126-
const angle::Vector4 floatColor = color.toNormalizedVector();
179+
auto quadVertices = GetQuadVertices();
127180

128-
glClearColor(floatColor[0], floatColor[1], floatColor[2], floatColor[3]);
129-
EXPECT_GL_NO_ERROR();
181+
GLBuffer vertexBuffer;
182+
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
183+
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
130184

131-
glClear(GL_COLOR_BUFFER_BIT);
132-
EXPECT_GL_NO_ERROR();
185+
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
186+
glEnableVertexAttribArray(positionLocation);
187+
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
133188

134-
EXPECT_PIXEL_COLOR_EQ(0, 0, color);
135-
}
189+
for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
190+
{
191+
// Base the clear color on the thread and iteration indexes so every clear color is
192+
// unique
193+
const GLColor color(static_cast<GLubyte>(thread % 255),
194+
static_cast<GLubyte>(iteration % 255), 0, 255);
195+
const angle::Vector4 floatColor = color.toNormalizedVector();
196+
glUniform4fv(colorLocation, 1, floatColor.data());
136197

198+
for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
137199
{
138-
std::lock_guard<decltype(mutex)> lock(mutex);
139-
140-
// Clean up
141-
EXPECT_EGL_TRUE(
142-
eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
143-
EXPECT_EGL_SUCCESS();
144-
145-
eglDestroySurface(dpy, pbuffer);
146-
eglDestroyContext(dpy, ctx);
200+
glDrawArrays(GL_TRIANGLES, 0, 6);
147201
}
148-
});
149-
}
150202

151-
for (std::thread &thread : threads)
152-
{
153-
thread.join();
154-
}
203+
EXPECT_PIXEL_COLOR_EQ(0, 0, color);
204+
}
205+
};
206+
runMultithreadedGLTest(testBody, 4);
155207
}
156208

157209
// TODO(geofflang): Test sharing a program between multiple shared contexts on multiple threads

0 commit comments

Comments
 (0)