Hash :
fdba40fe
Author :
Date :
2021-03-31T12:52:00
Reland "Fix multithreaded crash on draw commands on D3D11 backend." This is a reland of 8b9889bf62272fea2495331b622bde1f7c781bd0 The previous CL relied on a define which now only exists in a specific gn configuration. This reland removes D3D11 as a multithread-supported platform in the test configuration. Original change's description: > Fix multithreaded crash on draw commands on D3D11 backend. > > A crash can occur if thread A is executing eglDestroyContext while > thread B issues a draw call, if the threads are interleaved in such a > manner that a makeCurrent occurs without triggering a change to the > global context and a dirtyAllState call. We handle that case by > explicitly making current the proper contexts in the eglDestroyContext > call. > > A test has been added that triggers a crash without this fix when > running on the D3D11 backend. In addition, all of MultithreadingTest > is enabled for the D3D11 backend. > > Test: Ran MultithreadingTest. Test exhibits a crash before this > change, and does not after this change. Also ran: > dEQP-EGL.functional.sharing.gles2.multithread.* > dEQP-EGL.functional.multithread.* > > Bug: b/183756357 > Change-Id: Ic6f76a062868b2f3b4e60d29dc087ec180bfb7cd > Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2798591 > Reviewed-by: Geoff Lang <geofflang@chromium.org> > Reviewed-by: Jamie Madill <jmadill@chromium.org> > Commit-Queue: Doug Horn <doughorn@google.com> Bug: b/183756357 Change-Id: I5be9a011ea99a69730eddc9e4da23bcf92ed3bf2 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2815243 Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Doug Horn <doughorn@google.com>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// EGLMultiContextTest.cpp:
// Tests relating to multiple non-shared Contexts.
#include <gtest/gtest.h>
#include "EGLMultiThreadSteps.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/angle_test_configs.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
using namespace angle;
namespace
{
EGLBoolean SafeDestroyContext(EGLDisplay display, EGLContext &context)
{
EGLBoolean result = EGL_TRUE;
if (context != EGL_NO_CONTEXT)
{
result = eglDestroyContext(display, context);
context = EGL_NO_CONTEXT;
}
return result;
}
class EGLMultiContextTest : public ANGLETest
{
public:
EGLMultiContextTest() : mContexts{EGL_NO_CONTEXT, EGL_NO_CONTEXT}, mTexture(0) {}
void testTearDown() override
{
glDeleteTextures(1, &mTexture);
EGLDisplay display = getEGLWindow()->getDisplay();
if (display != EGL_NO_DISPLAY)
{
for (auto &context : mContexts)
{
SafeDestroyContext(display, context);
}
}
// Set default test state to not give an error on shutdown.
getEGLWindow()->makeCurrent();
}
EGLContext mContexts[2];
GLuint mTexture;
};
// Test that calling eglDeleteContext on a context that is not current succeeds.
TEST_P(EGLMultiContextTest, TestContextDestroySimple)
{
EGLWindow *window = getEGLWindow();
EGLDisplay dpy = window->getDisplay();
EGLContext context1 = window->createContext(EGL_NO_CONTEXT);
EGLContext context2 = window->createContext(EGL_NO_CONTEXT);
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context1));
EXPECT_EGL_TRUE(eglDestroyContext(dpy, context2));
EXPECT_EGL_SUCCESS();
}
// Test that a compute shader running in one thread will still work when rendering is happening in
// another thread (with non-shared contexts). The non-shared context will still share a Vulkan
// command buffer.
TEST_P(EGLMultiContextTest, ComputeShaderOkayWithRendering)
{
ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
ANGLE_SKIP_TEST_IF(!isVulkanRenderer());
ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 || getClientMinorVersion() < 1);
// Initialize contexts
EGLWindow *window = getEGLWindow();
EGLDisplay dpy = window->getDisplay();
EGLConfig config = window->getConfig();
constexpr size_t kThreadCount = 2;
EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
EGLContext ctx[kThreadCount] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE};
for (size_t t = 0; t < kThreadCount; ++t)
{
surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
EXPECT_EGL_SUCCESS();
ctx[t] = window->createContext(EGL_NO_CONTEXT);
EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
}
// Synchronization tools to ensure the two threads are interleaved as designed by this test.
std::mutex mutex;
std::condition_variable condVar;
enum class Step
{
Thread0Start,
Thread0DispatchedCompute,
Thread1Drew,
Thread0DispatchedComputeAgain,
Finish,
Abort,
};
Step currentStep = Step::Thread0Start;
// This first thread dispatches a compute shader. It immediately starts.
std::thread deletingThread = std::thread([&]() {
ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
EXPECT_EGL_SUCCESS();
// Potentially wait to be signalled to start.
ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Start));
// Wake up and do next step: Create, detach, and dispatch a compute shader program.
constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
void main()
{
})";
GLuint computeProgram = glCreateProgram();
GLuint cs = CompileShader(GL_COMPUTE_SHADER, kCS);
EXPECT_NE(0u, cs);
glAttachShader(computeProgram, cs);
glDeleteShader(cs);
glLinkProgram(computeProgram);
GLint linkStatus;
glGetProgramiv(computeProgram, GL_LINK_STATUS, &linkStatus);
EXPECT_GL_TRUE(linkStatus);
glDetachShader(computeProgram, cs);
EXPECT_GL_NO_ERROR();
glUseProgram(computeProgram);
glDispatchCompute(8, 4, 2);
EXPECT_GL_NO_ERROR();
// Signal the second thread and wait for it to draw and flush.
threadSynchronization.nextStep(Step::Thread0DispatchedCompute);
ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Drew));
// Wake up and do next step: Dispatch the same compute shader again.
glDispatchCompute(8, 4, 2);
// Signal the second thread and wait for it to draw and flush again.
threadSynchronization.nextStep(Step::Thread0DispatchedComputeAgain);
ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
// Wake up and do next step: Dispatch the same compute shader again, and force flush the
// underlying command buffer.
glDispatchCompute(8, 4, 2);
glFinish();
// Clean-up and exit this thread.
EXPECT_GL_NO_ERROR();
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_SUCCESS();
});
// This second thread renders. It starts once the other thread does its first nextStep()
std::thread continuingThread = std::thread([&]() {
ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
EXPECT_EGL_SUCCESS();
// Wait for first thread to create and dispatch a compute shader.
ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0DispatchedCompute));
// Wake up and do next step: Create graphics resources, draw, and force flush the
// underlying command buffer.
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
GLRenderbuffer renderbuffer;
GLFramebuffer fbo;
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
constexpr int kRenderbufferSize = 4;
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kRenderbufferSize, kRenderbufferSize);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
renderbuffer);
glBindTexture(GL_TEXTURE_2D, texture);
GLProgram graphicsProgram;
graphicsProgram.makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
ASSERT_TRUE(graphicsProgram.valid());
drawQuad(graphicsProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
glFinish();
// Signal the first thread and wait for it to dispatch a compute shader again.
threadSynchronization.nextStep(Step::Thread1Drew);
ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0DispatchedComputeAgain));
// Wake up and do next step: Draw and force flush the underlying command buffer again.
drawQuad(graphicsProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
glFinish();
// Signal the first thread and wait exit this thread.
threadSynchronization.nextStep(Step::Finish);
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_SUCCESS();
});
deletingThread.join();
continuingThread.join();
ASSERT_NE(currentStep, Step::Abort);
// Clean up
for (size_t t = 0; t < kThreadCount; ++t)
{
eglDestroySurface(dpy, surface[t]);
eglDestroyContext(dpy, ctx[t]);
}
}
} // anonymous namespace
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLMultiContextTest);
ANGLE_INSTANTIATE_TEST_ES31(EGLMultiContextTest);