// // 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. // // Demonstrates GLES usage with two contexts, one uploading texture data. #include "SampleApplication.h" #include "util/random_utils.h" #include "util/shader_utils.h" #include "util/test_utils.h" #include #include #include #include struct TextureAndFence { GLuint textureID; GLsync fenceSync; }; constexpr GLuint64 kTimeout = 10000000; static constexpr GLint kTextureSize = 2; constexpr size_t kWindowWidth = 400; constexpr size_t kWindowHeight = 300; void UpdateThreadLoop(EGLDisplay display, EGLConfig config, EGLContext shareContext, std::mutex *updateThreadMutex, std::queue *updateThreadQueue, std::mutex *mainThreadMutex, std::queue *mainThreadQueue) { angle::RNG rng; EGLint contextAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; EGLContext context = eglCreateContext(display, config, shareContext, contextAttribs); EGLint surfaceAttribs[] = {EGL_NONE}; EGLSurface surface = eglCreatePbufferSurface(display, config, surfaceAttribs); eglMakeCurrent(display, surface, surface, context); for (;;) { bool hasUpdate = false; TextureAndFence textureAndFence; { std::lock_guard lock(*updateThreadMutex); if (!updateThreadQueue->empty()) { textureAndFence = updateThreadQueue->back(); hasUpdate = true; updateThreadQueue->pop(); } } if (hasUpdate) { if (textureAndFence.textureID == 0) { // Signal from the main thread to stop execution. break; } glClientWaitSync(textureAndFence.fenceSync, 0, kTimeout); glDeleteSync(textureAndFence.fenceSync); glBindTexture(GL_TEXTURE_2D, textureAndFence.textureID); std::vector bytes(3, 0); FillVectorWithRandomUBytes(&rng, &bytes); bytes.push_back(255); std::vector textureData; for (GLint x = 0; x < kTextureSize; ++x) { for (GLint y = 0; y < kTextureSize; ++y) { textureData.insert(textureData.end(), bytes.begin(), bytes.end()); } } glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE, textureData.data()); GLsync mainThreadSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); { std::lock_guard lock(*mainThreadMutex); mainThreadQueue->push({textureAndFence.textureID, mainThreadSync}); } } angle::Sleep(200); } eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(display, surface); eglDestroyContext(display, context); } class MultipleContextsSample : public SampleApplication { public: MultipleContextsSample(int argc, char **argv) : SampleApplication("MultipleContexts", argc, argv, 3, 0, kWindowWidth, kWindowHeight) {} bool initialize() override { // Initialize some textures and send them to the update thread. glGenTextures(kNumTextures, mTextures.data()); for (GLuint texture : mTextures) { glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); GLsync fenceSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); mUpdateThreadQueue.push({texture, fenceSync}); } mUpdateThread.reset(new std::thread(UpdateThreadLoop, getDisplay(), getConfig(), getContext(), &mUpdateThreadMutex, &mUpdateThreadQueue, &mMainThreadMutex, &mMainThreadQueue)); mProgram = CompileProgram(angle::essl1_shaders::vs::Texture2D(), angle::essl1_shaders::fs::Texture2D()); glUseProgram(mProgram); glEnableVertexAttribArray(0); glGenBuffers(1, &mVertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * 6, nullptr, GL_DYNAMIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); return true; } void destroy() override { { // Signal the worker thread to stop execution. std::lock_guard lock(mUpdateThreadMutex); mUpdateThreadQueue.push({0, 0}); } for (;;) { { std::lock_guard mainLock(mMainThreadMutex); if (mMainThreadQueue.empty()) { std::lock_guard updateLock(mUpdateThreadMutex); if (mUpdateThreadQueue.empty()) { break; } } else { TextureAndFence textureAndFence = mMainThreadQueue.back(); mMainThreadQueue.pop(); glClientWaitSync(textureAndFence.fenceSync, 0, kTimeout); glDeleteSync(textureAndFence.fenceSync); } } } glDeleteTextures(kNumTextures, mTextures.data()); glDeleteProgram(mProgram); glDeleteBuffers(1, &mVertexBuffer); } void draw() override { bool hasUpdate = false; TextureAndFence textureAndFence; while (!hasUpdate) { { std::lock_guard lock(mMainThreadMutex); if (!mMainThreadQueue.empty()) { hasUpdate = true; textureAndFence = mMainThreadQueue.back(); mMainThreadQueue.pop(); } } } glClientWaitSync(textureAndFence.fenceSync, 0, kTimeout); glDeleteSync(textureAndFence.fenceSync); glBindTexture(GL_TEXTURE_2D, textureAndFence.textureID); constexpr size_t kNumRows = 3; constexpr size_t kNumCols = 4; constexpr size_t kTileHeight = kWindowHeight / kNumRows; constexpr size_t kTileWidth = kWindowWidth / kNumCols; size_t tileX = mDrawCount % kNumCols; size_t tileY = (mDrawCount / kNumCols) % kNumRows; mDrawCount++; GLfloat tileX0 = static_cast(tileX) / static_cast(kNumCols); GLfloat tileY0 = static_cast(tileY) / static_cast(kNumRows); GLfloat tileX1 = tileX0 + static_cast(kTileWidth) / static_cast(kWindowWidth); GLfloat tileY1 = tileY0 + static_cast(kTileHeight) / static_cast(kWindowHeight); tileX0 = tileX0 * 2.0f - 1.0f; tileX1 = tileX1 * 2.0f - 1.0f; tileY0 = tileY0 * 2.0f - 1.0f; tileY1 = tileY1 * 2.0f - 1.0f; std::vector vertices = {tileX0, tileY0, tileX0, tileY1, tileX1, tileY0, tileX1, tileY0, tileX1, tileY1, tileX0, tileY1}; glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices[0]) * vertices.size(), vertices.data()); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 6); GLsync drawSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); { std::lock_guard lock(mUpdateThreadMutex); mUpdateThreadQueue.push({textureAndFence.textureID, drawSync}); } } private: std::unique_ptr mUpdateThread; static constexpr GLuint kNumTextures = 4; std::array mTextures; GLuint mProgram = 0; GLuint mVertexBuffer = 0; std::mutex mUpdateThreadMutex; std::queue mUpdateThreadQueue; std::mutex mMainThreadMutex; std::queue mMainThreadQueue; size_t mDrawCount = 0; }; int main(int argc, char **argv) { MultipleContextsSample app(argc, argv); return app.run(); }