Commit ded5f903 by Geoff Lang Committed by Commit Bot

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 <geofflang@chromium.org> Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org>
parent 1b0f79ee
...@@ -1297,7 +1297,7 @@ angle::Result RendererVk::getDescriptorSetLayout( ...@@ -1297,7 +1297,7 @@ angle::Result RendererVk::getDescriptorSetLayout(
const vk::DescriptorSetLayoutDesc &desc, const vk::DescriptorSetLayoutDesc &desc,
vk::BindingPointer<vk::DescriptorSetLayout> *descriptorSetLayoutOut) vk::BindingPointer<vk::DescriptorSetLayout> *descriptorSetLayoutOut)
{ {
// TODO(geofflang): Synchronize access to the descriptor set layout cache std::lock_guard<decltype(mDescriptorSetLayoutCacheMutex)> lock(mDescriptorSetLayoutCacheMutex);
return mDescriptorSetLayoutCache.getDescriptorSetLayout(context, desc, descriptorSetLayoutOut); return mDescriptorSetLayoutCache.getDescriptorSetLayout(context, desc, descriptorSetLayoutOut);
} }
...@@ -1307,7 +1307,7 @@ angle::Result RendererVk::getPipelineLayout( ...@@ -1307,7 +1307,7 @@ angle::Result RendererVk::getPipelineLayout(
const vk::DescriptorSetLayoutPointerArray &descriptorSetLayouts, const vk::DescriptorSetLayoutPointerArray &descriptorSetLayouts,
vk::BindingPointer<vk::PipelineLayout> *pipelineLayoutOut) vk::BindingPointer<vk::PipelineLayout> *pipelineLayoutOut)
{ {
// TODO(geofflang): Synchronize access to the pipeline layout cache std::lock_guard<decltype(mPipelineLayoutCacheMutex)> lock(mPipelineLayoutCacheMutex);
return mPipelineLayoutCache.getPipelineLayout(context, desc, descriptorSetLayouts, return mPipelineLayoutCache.getPipelineLayout(context, desc, descriptorSetLayouts,
pipelineLayoutOut); pipelineLayoutOut);
} }
...@@ -1391,23 +1391,31 @@ angle::Result RendererVk::queueSubmit(vk::Context *context, ...@@ -1391,23 +1391,31 @@ angle::Result RendererVk::queueSubmit(vk::Context *context,
const VkSubmitInfo &submitInfo, const VkSubmitInfo &submitInfo,
const vk::Fence &fence) const vk::Fence &fence)
{ {
// TODO: synchronize queue access {
ANGLE_VK_TRY(context, vkQueueSubmit(mQueue, 1, &submitInfo, fence.getHandle())); std::lock_guard<decltype(mQueueMutex)> lock(mQueueMutex);
ANGLE_VK_TRY(context, vkQueueSubmit(mQueue, 1, &submitInfo, fence.getHandle()));
}
ANGLE_TRY(cleanupGarbage(context, false)); ANGLE_TRY(cleanupGarbage(context, false));
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result RendererVk::queueWaitIdle(vk::Context *context) angle::Result RendererVk::queueWaitIdle(vk::Context *context)
{ {
// TODO: synchronize queue access {
ANGLE_VK_TRY(context, vkQueueWaitIdle(mQueue)); std::lock_guard<decltype(mQueueMutex)> lock(mQueueMutex);
ANGLE_VK_TRY(context, vkQueueWaitIdle(mQueue));
}
ANGLE_TRY(cleanupGarbage(context, false)); ANGLE_TRY(cleanupGarbage(context, false));
return angle::Result::Continue; return angle::Result::Continue;
} }
VkResult RendererVk::queuePresent(const VkPresentInfoKHR &presentInfo) VkResult RendererVk::queuePresent(const VkPresentInfoKHR &presentInfo)
{ {
// TODO: synchronize queue access std::lock_guard<decltype(mQueueMutex)> lock(mQueueMutex);
return vkQueuePresentKHR(mQueue, &presentInfo); return vkQueuePresentKHR(mQueue, &presentInfo);
} }
...@@ -1427,6 +1435,7 @@ void RendererVk::addGarbage(vk::Shared<vk::Fence> &&fence, ...@@ -1427,6 +1435,7 @@ void RendererVk::addGarbage(vk::Shared<vk::Fence> &&fence,
void RendererVk::addGarbage(std::vector<vk::Shared<vk::Fence>> &&fences, void RendererVk::addGarbage(std::vector<vk::Shared<vk::Fence>> &&fences,
std::vector<vk::GarbageObjectBase> &&garbage) std::vector<vk::GarbageObjectBase> &&garbage)
{ {
std::lock_guard<decltype(mGarbageMutex)> lock(mGarbageMutex);
mFencedGarbage.emplace_back(std::move(fences), std::move(garbage)); mFencedGarbage.emplace_back(std::move(fences), std::move(garbage));
} }
...@@ -1468,6 +1477,8 @@ bool RendererVk::hasFormatFeatureBits(VkFormat format, const VkFormatFeatureFlag ...@@ -1468,6 +1477,8 @@ bool RendererVk::hasFormatFeatureBits(VkFormat format, const VkFormatFeatureFlag
angle::Result RendererVk::cleanupGarbage(vk::Context *context, bool block) angle::Result RendererVk::cleanupGarbage(vk::Context *context, bool block)
{ {
std::lock_guard<decltype(mGarbageMutex)> lock(mGarbageMutex);
auto garbageIter = mFencedGarbage.begin(); auto garbageIter = mFencedGarbage.begin();
while (garbageIter != mFencedGarbage.end()) while (garbageIter != mFencedGarbage.end())
{ {
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <memory> #include <memory>
#include <mutex>
#include "common/PoolAlloc.h" #include "common/PoolAlloc.h"
#include "common/angleutils.h" #include "common/angleutils.h"
...@@ -70,7 +71,6 @@ class RendererVk : angle::NonCopyable ...@@ -70,7 +71,6 @@ class RendererVk : angle::NonCopyable
{ {
return mPhysicalDeviceFeatures; return mPhysicalDeviceFeatures;
} }
VkQueue getQueue() const { return mQueue; }
VkDevice getDevice() const { return mDevice; } VkDevice getDevice() const { return mDevice; }
angle::Result selectPresentQueueForSurface(DisplayVk *displayVk, angle::Result selectPresentQueueForSurface(DisplayVk *displayVk,
...@@ -183,6 +183,7 @@ class RendererVk : angle::NonCopyable ...@@ -183,6 +183,7 @@ class RendererVk : angle::NonCopyable
VkPhysicalDeviceProperties mPhysicalDeviceProperties; VkPhysicalDeviceProperties mPhysicalDeviceProperties;
VkPhysicalDeviceFeatures mPhysicalDeviceFeatures; VkPhysicalDeviceFeatures mPhysicalDeviceFeatures;
std::vector<VkQueueFamilyProperties> mQueueFamilyProperties; std::vector<VkQueueFamilyProperties> mQueueFamilyProperties;
std::mutex mQueueMutex;
VkQueue mQueue; VkQueue mQueue;
uint32_t mCurrentQueueFamilyIndex; uint32_t mCurrentQueueFamilyIndex;
uint32_t mMaxVertexAttribDivisor; uint32_t mMaxVertexAttribDivisor;
...@@ -193,6 +194,7 @@ class RendererVk : angle::NonCopyable ...@@ -193,6 +194,7 @@ class RendererVk : angle::NonCopyable
bool mDeviceLost; bool mDeviceLost;
std::mutex mGarbageMutex;
using FencedGarbage = using FencedGarbage =
std::pair<std::vector<vk::Shared<vk::Fence>>, std::vector<vk::GarbageObjectBase>>; std::pair<std::vector<vk::Shared<vk::Fence>>, std::vector<vk::GarbageObjectBase>>;
std::vector<FencedGarbage> mFencedGarbage; std::vector<FencedGarbage> mFencedGarbage;
...@@ -200,6 +202,8 @@ class RendererVk : angle::NonCopyable ...@@ -200,6 +202,8 @@ class RendererVk : angle::NonCopyable
vk::MemoryProperties mMemoryProperties; vk::MemoryProperties mMemoryProperties;
vk::FormatTable mFormatTable; vk::FormatTable mFormatTable;
// All access to the pipeline cache is done through EGL objects so it is thread safe to not use
// a lock.
vk::PipelineCache mPipelineCache; vk::PipelineCache mPipelineCache;
egl::BlobCache::Key mPipelineCacheVkBlobKey; egl::BlobCache::Key mPipelineCacheVkBlobKey;
uint32_t mPipelineCacheVkUpdateTimeout; uint32_t mPipelineCacheVkUpdateTimeout;
...@@ -208,9 +212,11 @@ class RendererVk : angle::NonCopyable ...@@ -208,9 +212,11 @@ class RendererVk : angle::NonCopyable
std::array<VkFormatProperties, vk::kNumVkFormats> mFormatProperties; std::array<VkFormatProperties, vk::kNumVkFormats> mFormatProperties;
// ANGLE uses a PipelineLayout cache to store compatible pipeline layouts. // ANGLE uses a PipelineLayout cache to store compatible pipeline layouts.
std::mutex mPipelineLayoutCacheMutex;
PipelineLayoutCache mPipelineLayoutCache; PipelineLayoutCache mPipelineLayoutCache;
// DescriptorSetLayouts are also managed in a cache. // DescriptorSetLayouts are also managed in a cache.
std::mutex mDescriptorSetLayoutCacheMutex;
DescriptorSetLayoutCache mDescriptorSetLayoutCache; DescriptorSetLayoutCache mDescriptorSetLayoutCache;
}; };
......
...@@ -29,7 +29,68 @@ class MultithreadingTest : public ANGLETest ...@@ -29,7 +29,68 @@ class MultithreadingTest : public ANGLETest
setConfigAlphaBits(8); setConfigAlphaBits(8);
} }
bool platformSupportsMultithreading() const { return (IsOpenGLES() && IsAndroid()); } bool platformSupportsMultithreading() const
{
return (IsOpenGLES() && IsAndroid()) || IsVulkan();
}
void runMultithreadedGLTest(
std::function<void(EGLSurface surface, size_t threadIndex)> testBody,
size_t threadCount)
{
std::mutex mutex;
EGLWindow *window = getEGLWindow();
EGLDisplay dpy = window->getDisplay();
EGLConfig config = window->getConfig();
constexpr EGLint kPBufferSize = 256;
std::vector<std::thread> threads(threadCount);
for (size_t threadIdx = 0; threadIdx < threadCount; threadIdx++)
{
threads[threadIdx] = std::thread([&, threadIdx]() {
EGLSurface surface = EGL_NO_SURFACE;
EGLConfig ctx = EGL_NO_CONTEXT;
{
std::lock_guard<decltype(mutex)> lock(mutex);
// Initialize the pbuffer and context
EGLint pbufferAttributes[] = {
EGL_WIDTH, kPBufferSize, EGL_HEIGHT, kPBufferSize, EGL_NONE, EGL_NONE,
};
surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
EXPECT_EGL_SUCCESS();
ctx = window->createContext(EGL_NO_CONTEXT);
EXPECT_NE(EGL_NO_CONTEXT, ctx);
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
EXPECT_EGL_SUCCESS();
}
testBody(surface, threadIdx);
{
std::lock_guard<decltype(mutex)> lock(mutex);
// Clean up
EXPECT_EGL_TRUE(
eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_SUCCESS();
eglDestroySurface(dpy, surface);
eglDestroyContext(dpy, ctx);
}
});
}
for (std::thread &thread : threads)
{
thread.join();
}
}
}; };
// Test that it's possible to make one context current on different threads // Test that it's possible to make one context current on different threads
...@@ -74,84 +135,75 @@ TEST_P(MultithreadingTest, MakeCurrentSingleContext) ...@@ -74,84 +135,75 @@ TEST_P(MultithreadingTest, MakeCurrentSingleContext)
EXPECT_EGL_SUCCESS(); EXPECT_EGL_SUCCESS();
} }
// Test that it's possible to make one multiple contexts current on different threads simultaneously // Test that multiple threads can clear and readback pixels successfully at the same time
TEST_P(MultithreadingTest, MakeCurrentMultiContext) TEST_P(MultithreadingTest, MultiContextClear)
{ {
ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
ANGLE_SKIP_TEST_IF(IsWindows() && IsOpenGL() && IsAMD());
std::mutex mutex;
EGLWindow *window = getEGLWindow(); auto testBody = [](EGLSurface surface, size_t thread) {
EGLDisplay dpy = window->getDisplay(); constexpr size_t kIterationsPerThread = 32;
EGLConfig config = window->getConfig(); for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
{
constexpr size_t kThreadCount = 16; // Base the clear color on the thread and iteration indexes so every clear color is
constexpr size_t kIterationsPerThread = 16; // unique
const GLColor color(static_cast<GLubyte>(thread % 255),
constexpr EGLint kPBufferSize = 256; static_cast<GLubyte>(iteration % 255), 0, 255);
const angle::Vector4 floatColor = color.toNormalizedVector();
std::array<std::thread, kThreadCount> threads;
for (size_t thread = 0; thread < kThreadCount; thread++) glClearColor(floatColor[0], floatColor[1], floatColor[2], floatColor[3]);
{ EXPECT_GL_NO_ERROR();
threads[thread] = std::thread([&, thread]() {
EGLSurface pbuffer = EGL_NO_SURFACE; glClear(GL_COLOR_BUFFER_BIT);
EGLConfig ctx = EGL_NO_CONTEXT; EXPECT_GL_NO_ERROR();
{ EXPECT_PIXEL_COLOR_EQ(0, 0, color);
std::lock_guard<decltype(mutex)> lock(mutex); }
};
runMultithreadedGLTest(testBody, 72);
}
// Initialize the pbuffer and context // Test that multiple threads can draw and readback pixels successfully at the same time
EGLint pbufferAttributes[] = { TEST_P(MultithreadingTest, MultiContextDraw)
EGL_WIDTH, kPBufferSize, EGL_HEIGHT, kPBufferSize, EGL_NONE, EGL_NONE, {
}; ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
pbuffer = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
EXPECT_EGL_SUCCESS();
ctx = window->createContext(EGL_NO_CONTEXT); auto testBody = [](EGLSurface surface, size_t thread) {
EXPECT_NE(EGL_NO_CONTEXT, ctx); constexpr size_t kIterationsPerThread = 32;
constexpr size_t kDrawsPerIteration = 500;
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, pbuffer, pbuffer, ctx)); ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
EXPECT_EGL_SUCCESS(); glUseProgram(program);
}
for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++) GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
{
std::lock_guard<decltype(mutex)> lock(mutex);
// Base the clear color on the thread and iteration indexes so every clear color is auto quadVertices = GetQuadVertices();
// unique
const GLColor color(static_cast<GLubyte>(thread), static_cast<GLubyte>(iteration),
0, 255);
const angle::Vector4 floatColor = color.toNormalizedVector();
glClearColor(floatColor[0], floatColor[1], floatColor[2], floatColor[3]); GLBuffer vertexBuffer;
EXPECT_GL_NO_ERROR(); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
glClear(GL_COLOR_BUFFER_BIT); GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
EXPECT_GL_NO_ERROR(); glEnableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
EXPECT_PIXEL_COLOR_EQ(0, 0, color); for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
} {
// Base the clear color on the thread and iteration indexes so every clear color is
// unique
const GLColor color(static_cast<GLubyte>(thread % 255),
static_cast<GLubyte>(iteration % 255), 0, 255);
const angle::Vector4 floatColor = color.toNormalizedVector();
glUniform4fv(colorLocation, 1, floatColor.data());
for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
{ {
std::lock_guard<decltype(mutex)> lock(mutex); glDrawArrays(GL_TRIANGLES, 0, 6);
// Clean up
EXPECT_EGL_TRUE(
eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_SUCCESS();
eglDestroySurface(dpy, pbuffer);
eglDestroyContext(dpy, ctx);
} }
});
}
for (std::thread &thread : threads) EXPECT_PIXEL_COLOR_EQ(0, 0, color);
{ }
thread.join(); };
} runMultithreadedGLTest(testBody, 4);
} }
// TODO(geofflang): Test sharing a program between multiple shared contexts on multiple threads // TODO(geofflang): Test sharing a program between multiple shared contexts on multiple threads
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment