/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ReadbackWorkerGl.h" #include #include "ColorBuffer.h" #include "ContextHelper.h" #include "OpenGLESDispatch/DispatchTables.h" #include "OpenGLESDispatch/EGLDispatch.h" #include "OpenGLESDispatch/GLESv2Dispatch.h" #include "gl/ColorBufferGl.h" #include "host-common/logging.h" #include "host-common/misc.h" namespace gfxstream { namespace gl { ReadbackWorkerGl::TrackedDisplay::TrackedDisplay(uint32_t displayId, uint32_t w, uint32_t h) : mBufferSize(4 * w * h /* RGBA8 (4 bpp) */), mBuffers(4 /* mailbox */, 0), // Note, last index is used for duplicating buffer on flush mDisplayId(displayId) {} ReadbackWorkerGl::ReadbackWorkerGl(std::unique_ptr surface, std::unique_ptr flushSurface) : mSurface(std::move(surface)), mFlushSurface(std::move(flushSurface)) {} void ReadbackWorkerGl::init() { if (!mFlushSurface->getContextHelper()->setupContext()) { ERR("Failed to make ReadbackWorkerGl flush surface current."); } } ReadbackWorkerGl::~ReadbackWorkerGl() { // Context not available on exit } void ReadbackWorkerGl::initReadbackForDisplay(uint32_t displayId, uint32_t w, uint32_t h) { android::base::AutoLock lock(mLock); auto [it, inserted] = mTrackedDisplays.emplace(displayId, TrackedDisplay(displayId, w, h)); if (!inserted) { ERR("Double init of TrackeDisplay for display:%d", displayId); return; } TrackedDisplay& display = it->second; s_gles2.glGenBuffers(display.mBuffers.size(), &display.mBuffers[0]); for (auto buffer : display.mBuffers) { s_gles2.glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer); s_gles2.glBufferData(GL_PIXEL_PACK_BUFFER, display.mBufferSize, nullptr, GL_STREAM_READ); } s_gles2.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); } void ReadbackWorkerGl::deinitReadbackForDisplay(uint32_t displayId) { android::base::AutoLock lock(mLock); auto it = mTrackedDisplays.find(displayId); if (it == mTrackedDisplays.end()) { ERR("Double deinit of TrackedDisplay for display:%d", displayId); return; } TrackedDisplay& display = it->second; s_gles2.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); s_gles2.glBindBuffer(GL_COPY_READ_BUFFER, 0); s_gles2.glDeleteBuffers(display.mBuffers.size(), &display.mBuffers[0]); mTrackedDisplays.erase(it); } ReadbackWorkerGl::DoNextReadbackResult ReadbackWorkerGl::doNextReadback(uint32_t displayId, ColorBuffer* cb, void* fbImage, bool repaint, bool readbackBgra) { // if |repaint|, make sure that the current frame is immediately sent down // the pipeline and made available to the consumer by priming async // readback; doing 4 consecutive reads in a row, which should be enough to // fill the 3 buffers in the triple buffering setup and on the 4th, trigger // a post callback. int numIter = repaint ? 4 : 1; DoNextReadbackResult ret = DoNextReadbackResult::OK_NOT_READY_FOR_READ; // Mailbox-style triple buffering setup: // We want to avoid glReadPixels while in the middle of doing // memcpy to the consumer, but also want to avoid latency while // that is going on. // // There are 3 buffer ids, A, B, and C. // If we are not in the middle of copying out a frame, // set glReadPixels to write to buffer A and copy from buffer B in // alternation, so the consumer always gets the latest frame // +1 frame of lag in order to not cause blocking on // glReadPixels / glMapBufferRange. // If we are in the middle of copying out a frame, reset A and B to // not be the buffer being copied out, and continue glReadPixels to // buffer A and B as before. // // The resulting invariants are: // - glReadPixels is called on a different buffer every time // so we avoid introducing sync points there. // - At no time are we mapping/copying a buffer and also doing // glReadPixels on it (avoid simultaneous map + glReadPixels) // - glReadPixels and then immediately map/copy the same buffer // doesn't happen either (avoid sync point in glMapBufferRange) for (int i = 0; i < numIter; i++) { android::base::AutoLock lock(mLock); TrackedDisplay& r = mTrackedDisplays[displayId]; if (r.mIsCopying) { switch (r.mMapCopyIndex) { // To keep double buffering effect on // glReadPixels, need to keep even/oddness of // mReadPixelsIndexEven and mReadPixelsIndexOdd. case 0: r.mReadPixelsIndexEven = 2; r.mReadPixelsIndexOdd = 1; break; case 1: r.mReadPixelsIndexEven = 0; r.mReadPixelsIndexOdd = 2; break; case 2: r.mReadPixelsIndexEven = 0; r.mReadPixelsIndexOdd = 1; break; } } else { r.mReadPixelsIndexEven = 0; r.mReadPixelsIndexOdd = 1; r.mMapCopyIndex = r.mPrevReadPixelsIndex; } // Double buffering on buffer A / B part uint32_t readAt; if (r.m_readbackCount % 2 == 0) { readAt = r.mReadPixelsIndexEven; } else { readAt = r.mReadPixelsIndexOdd; } r.m_readbackCount++; r.mPrevReadPixelsIndex = readAt; cb->glOpReadbackAsync(r.mBuffers[readAt], readbackBgra); // It's possible to post callback before any of the async readbacks // have written any data yet, which results in a black frame. Safer // option to avoid this glitch is to wait until all 3 potential // buffers in our triple buffering setup have had chances to readback. lock.unlock(); if (r.m_readbackCount > 3) { ret = DoNextReadbackResult::OK_READY_FOR_READ; } } return ret; } ReadbackWorkerGl::FlushResult ReadbackWorkerGl::flushPipeline(uint32_t displayId) { android::base::AutoLock lock(mLock); auto it = mTrackedDisplays.find(displayId); if (it == mTrackedDisplays.end()) { ERR("Failed to find TrackedDisplay for display:%d", displayId); return FlushResult::FAIL; } TrackedDisplay& display = it->second; if (display.mIsCopying) { // No need to make the last frame available, // we are currently being read. return FlushResult::OK_NOT_READY_FOR_READ; } auto src = display.mBuffers[display.mPrevReadPixelsIndex]; auto srcSize = display.mBufferSize; auto dst = display.mBuffers.back(); // This is not called from a renderthread, so let's activate the context. { RecursiveScopedContextBind contextBind(mSurface->getContextHelper()); if (!contextBind.isOk()) { ERR("Failed to make ReadbackWorkerGl surface current, skipping flush."); return FlushResult::FAIL; } // We now copy the last frame into slot 4, where no other thread // ever writes. s_gles2.glBindBuffer(GL_COPY_READ_BUFFER, src); s_gles2.glBindBuffer(GL_COPY_WRITE_BUFFER, dst); s_gles2.glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, srcSize); } display.mMapCopyIndex = display.mBuffers.size() - 1; return FlushResult::OK_READY_FOR_READ; } void ReadbackWorkerGl::getPixels(uint32_t displayId, void* buf, uint32_t bytes) { android::base::AutoLock lock(mLock); auto it = mTrackedDisplays.find(displayId); if (it == mTrackedDisplays.end()) { ERR("Failed to find TrackedDisplay for display:%d", displayId); return; } TrackedDisplay& display = it->second; display.mIsCopying = true; lock.unlock(); auto buffer = display.mBuffers[display.mMapCopyIndex]; s_gles2.glBindBuffer(GL_COPY_READ_BUFFER, buffer); void* pixels = s_gles2.glMapBufferRange(GL_COPY_READ_BUFFER, 0, bytes, GL_MAP_READ_BIT); memcpy(buf, pixels, bytes); s_gles2.glUnmapBuffer(GL_COPY_READ_BUFFER); lock.lock(); display.mIsCopying = false; lock.unlock(); } } // namespace gl } // namespace gfxstream