| // | |
| // 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 <array> | |
| #include <mutex> | |
| #include <queue> | |
| #include <thread> | |
| 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<TextureAndFence> *updateThreadQueue, | |
| std::mutex *mainThreadMutex, | |
| std::queue<TextureAndFence> *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<std::mutex> 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<uint8_t> bytes(3, 0); | |
| FillVectorWithRandomUBytes(&rng, &bytes); | |
| bytes.push_back(255); | |
| std::vector<uint8_t> 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<std::mutex> 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, | |
| ClientType::ES3_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<std::mutex> lock(mUpdateThreadMutex); | |
| mUpdateThreadQueue.push({0, 0}); | |
| } | |
| for (;;) | |
| { | |
| { | |
| std::lock_guard<std::mutex> mainLock(mMainThreadMutex); | |
| if (mMainThreadQueue.empty()) | |
| { | |
| std::lock_guard<std::mutex> 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<std::mutex> 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<float>(tileX) / static_cast<float>(kNumCols); | |
| GLfloat tileY0 = static_cast<float>(tileY) / static_cast<float>(kNumRows); | |
| GLfloat tileX1 = tileX0 + static_cast<float>(kTileWidth) / static_cast<float>(kWindowWidth); | |
| GLfloat tileY1 = | |
| tileY0 + static_cast<float>(kTileHeight) / static_cast<float>(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<GLfloat> 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<std::mutex> lock(mUpdateThreadMutex); | |
| mUpdateThreadQueue.push({textureAndFence.textureID, drawSync}); | |
| } | |
| } | |
| private: | |
| std::unique_ptr<std::thread> mUpdateThread; | |
| static constexpr GLuint kNumTextures = 4; | |
| std::array<GLuint, kNumTextures> mTextures; | |
| GLuint mProgram = 0; | |
| GLuint mVertexBuffer = 0; | |
| std::mutex mUpdateThreadMutex; | |
| std::queue<TextureAndFence> mUpdateThreadQueue; | |
| std::mutex mMainThreadMutex; | |
| std::queue<TextureAndFence> mMainThreadQueue; | |
| size_t mDrawCount = 0; | |
| }; | |
| int main(int argc, char **argv) | |
| { | |
| MultipleContextsSample app(argc, argv); | |
| return app.run(); | |
| } |