| /* |
| * Copyright (C) 2023 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 "PostWorkerGl.h" |
| |
| #include "FrameBuffer.h" |
| #include "gl/DisplayGl.h" |
| #include "gl/DisplaySurfaceGl.h" |
| #include "host-common/GfxstreamFatalError.h" |
| #include "host-common/logging.h" |
| #include "host-common/misc.h" |
| |
| namespace gfxstream { |
| |
| namespace { |
| |
| using emugl::ABORT_REASON_OTHER; |
| using emugl::FatalError; |
| using gl::DisplayGl; |
| using gl::DisplaySurfaceGl; |
| using gl::EmulationGl; |
| using gl::s_egl; |
| |
| hwc_transform_t getTransformFromRotation(int rotation) { |
| switch (static_cast<int>(rotation / 90)) { |
| case 1: |
| return HWC_TRANSFORM_ROT_270; |
| case 2: |
| return HWC_TRANSFORM_ROT_180; |
| case 3: |
| return HWC_TRANSFORM_ROT_90; |
| default: |
| return HWC_TRANSFORM_NONE; |
| } |
| } |
| |
| } // namespace |
| |
| PostWorkerGl::PostWorkerGl(bool mainThreadPostingOnly, FrameBuffer* fb, Compositor* compositor, |
| DisplayGl* displayGl, gl::EmulationGl* emulationGl) |
| : PostWorker(mainThreadPostingOnly, fb, compositor), |
| m_displayGl(displayGl), |
| mEmulationGl(emulationGl) { |
| if (!m_displayGl) { |
| GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "PostWorker missing DisplayGl."; |
| } |
| } |
| |
| void PostWorkerGl::screenshot(ColorBuffer* cb, int screenwidth, int screenheight, GLenum format, |
| GLenum type, int skinRotation, void* pixels, Rect rect) { |
| // See b/292237104. |
| mFb->lock(); |
| cb->readToBytesScaled(screenwidth, screenheight, format, type, skinRotation, rect, pixels); |
| mFb->unlock(); |
| } |
| |
| std::shared_future<void> PostWorkerGl::postImpl(ColorBuffer* cb) { |
| if (!mContextBound || m_mainThreadPostingOnly) { |
| // This might happen on headless mode |
| // Also if posting on main thread, the context binding can get polluted easily, which |
| // requires frequent rebinds. |
| setupContext(); |
| } |
| std::shared_future<void> completedFuture = std::async(std::launch::deferred, [] {}).share(); |
| completedFuture.wait(); |
| |
| DisplayGl::Post post = {}; |
| |
| ComposeLayer postLayerOptions = { |
| .composeMode = HWC2_COMPOSITION_DEVICE, |
| .blendMode = HWC2_BLEND_MODE_NONE, |
| .alpha = 1.0f, |
| .transform = HWC_TRANSFORM_NONE, |
| }; |
| |
| const auto& multiDisplay = emugl::get_emugl_multi_display_operations(); |
| const bool pixel_fold = multiDisplay.isPixelFold(); |
| if (pixel_fold) { |
| #ifdef CONFIG_AEMU |
| if (emugl::shouldSkipDraw()) { |
| post.layers.clear(); |
| } else { |
| post.layers.push_back(postWithOverlay(cb)); |
| } |
| #endif |
| } |
| else if (multiDisplay.isMultiDisplayEnabled()) { |
| if (multiDisplay.isMultiDisplayWindow()) { |
| int32_t previousDisplayId = -1; |
| uint32_t currentDisplayId; |
| uint32_t currentDisplayColorBufferHandle; |
| while (multiDisplay.getNextMultiDisplay(previousDisplayId, ¤tDisplayId, |
| /*x=*/nullptr, |
| /*y=*/nullptr, |
| /*w=*/nullptr, |
| /*h=*/nullptr, |
| /*dpi=*/nullptr, |
| /*flags=*/nullptr, |
| ¤tDisplayColorBufferHandle)) { |
| previousDisplayId = currentDisplayId; |
| |
| if (currentDisplayColorBufferHandle == 0) { |
| continue; |
| } |
| emugl::get_emugl_window_operations().paintMultiDisplayWindow( |
| currentDisplayId, currentDisplayColorBufferHandle); |
| } |
| post.layers.push_back(postWithOverlay(cb)); |
| } else { |
| uint32_t combinedDisplayW = 0; |
| uint32_t combinedDisplayH = 0; |
| multiDisplay.getCombinedDisplaySize(&combinedDisplayW, &combinedDisplayH); |
| |
| post.frameWidth = combinedDisplayW; |
| post.frameHeight = combinedDisplayH; |
| |
| int32_t previousDisplayId = -1; |
| uint32_t currentDisplayId; |
| int32_t currentDisplayOffsetX; |
| int32_t currentDisplayOffsetY; |
| uint32_t currentDisplayW; |
| uint32_t currentDisplayH; |
| uint32_t currentDisplayColorBufferHandle; |
| while (multiDisplay.getNextMultiDisplay( |
| previousDisplayId, ¤tDisplayId, ¤tDisplayOffsetX, |
| ¤tDisplayOffsetY, ¤tDisplayW, ¤tDisplayH, |
| /*dpi=*/nullptr, |
| /*flags=*/nullptr, ¤tDisplayColorBufferHandle)) { |
| previousDisplayId = currentDisplayId; |
| |
| if (currentDisplayW == 0 || currentDisplayH == 0 || |
| (currentDisplayId != 0 && currentDisplayColorBufferHandle == 0)) { |
| continue; |
| } |
| |
| ColorBuffer* currentCb = |
| currentDisplayId == 0 |
| ? cb |
| : mFb->findColorBuffer(currentDisplayColorBufferHandle).get(); |
| if (!currentCb) { |
| continue; |
| } |
| |
| const auto transform = getTransformFromRotation(mFb->getZrot()); |
| postLayerOptions.transform = transform; |
| if ( transform == HWC_TRANSFORM_ROT_90 || transform == HWC_TRANSFORM_ROT_270) { |
| std::swap(currentDisplayW, currentDisplayH); |
| } |
| postLayerOptions.displayFrame = { |
| .left = static_cast<int>(currentDisplayOffsetX), |
| .top = static_cast<int>(currentDisplayOffsetY), |
| .right = static_cast<int>(currentDisplayOffsetX + currentDisplayW), |
| .bottom = static_cast<int>(currentDisplayOffsetY + currentDisplayH), |
| }; |
| postLayerOptions.crop = { |
| .left = 0.0f, |
| .top = static_cast<float>(currentCb->getHeight()), |
| .right = static_cast<float>(currentCb->getWidth()), |
| .bottom = 0.0f, |
| }; |
| |
| post.layers.push_back(DisplayGl::PostLayer{ |
| .colorBuffer = currentCb, |
| .layerOptions = postLayerOptions, |
| }); |
| } |
| } |
| } else if (emugl::get_emugl_window_operations().isFolded()) { |
| const float dpr = mFb->getDpr(); |
| |
| post.frameWidth = m_viewportWidth / dpr; |
| post.frameHeight = m_viewportHeight / dpr; |
| |
| int displayOffsetX; |
| int displayOffsetY; |
| int displayW; |
| int displayH; |
| emugl::get_emugl_window_operations().getFoldedArea(&displayOffsetX, &displayOffsetY, |
| &displayW, &displayH); |
| |
| postLayerOptions.displayFrame = { |
| .left = 0, |
| .top = 0, |
| .right = mFb->windowWidth(), |
| .bottom = mFb->windowHeight(), |
| }; |
| postLayerOptions.crop = { |
| .left = static_cast<float>(displayOffsetX), |
| .top = static_cast<float>(displayOffsetY + displayH), |
| .right = static_cast<float>(displayOffsetX + displayW), |
| .bottom = static_cast<float>(displayOffsetY), |
| }; |
| postLayerOptions.transform = getTransformFromRotation(mFb->getZrot()); |
| |
| post.layers.push_back(DisplayGl::PostLayer{ |
| .colorBuffer = cb, |
| .layerOptions = postLayerOptions, |
| }); |
| } else { |
| post.layers.push_back(postWithOverlay(cb)); |
| } |
| return m_displayGl->post(post); |
| } |
| |
| DisplayGl::PostLayer PostWorkerGl::postWithOverlay(ColorBuffer* cb) { |
| float dpr = mFb->getDpr(); |
| int windowWidth = mFb->windowWidth(); |
| int windowHeight = mFb->windowHeight(); |
| float px = mFb->getPx(); |
| float py = mFb->getPy(); |
| int zRot = mFb->getZrot(); |
| hwc_transform_t rotation = (hwc_transform_t)0; |
| |
| // Find the x and y values at the origin when "fully scrolled." |
| // Multiply by 2 because the texture goes from -1 to 1, not 0 to 1. |
| // Multiply the windowing coordinates by DPR because they ignore |
| // DPR, but the viewport includes DPR. |
| float fx = 2.f * (m_viewportWidth - windowWidth * dpr) / (float)m_viewportWidth; |
| float fy = 2.f * (m_viewportHeight - windowHeight * dpr) / (float)m_viewportHeight; |
| |
| // finally, compute translation values |
| float dx = px * fx; |
| float dy = py * fy; |
| |
| return DisplayGl::PostLayer{ |
| .colorBuffer = cb, |
| .overlayOptions = |
| DisplayGl::PostLayer::OverlayOptions{ |
| .rotation = static_cast<float>(zRot), |
| .dx = dx, |
| .dy = dy, |
| }, |
| }; |
| } |
| |
| // Called whenever the subwindow needs a refresh (FrameBuffer::setupSubWindow). |
| // This rebinds the subwindow context (to account for |
| // when the refresh is a display change, for instance) |
| // and resets the posting viewport. |
| void PostWorkerGl::viewportImpl(int width, int height) { |
| setupContext(); |
| const float dpr = mFb->getDpr(); |
| m_viewportWidth = width * dpr; |
| m_viewportHeight = height * dpr; |
| |
| if (!m_displayGl) { |
| GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "PostWorker missing DisplayGl."; |
| } |
| m_displayGl->viewport(m_viewportWidth, m_viewportHeight); |
| } |
| |
| // Called when the subwindow refreshes, but there is no |
| // last posted color buffer to show to the user. Instead of |
| // displaying whatever happens to be in the back buffer, |
| // clear() is useful for outputting consistent colors. |
| void PostWorkerGl::clearImpl() { |
| if (!mContextBound || m_mainThreadPostingOnly) { |
| // This might happen on headless mode |
| setupContext(); |
| } |
| m_displayGl->clear(); |
| } |
| |
| std::shared_future<void> PostWorkerGl::composeImpl(const FlatComposeRequest& composeRequest) { |
| if (!mContextBound || m_mainThreadPostingOnly) { |
| // This might happen on headless mode |
| setupContext(); |
| } |
| return PostWorker::composeImpl(composeRequest); |
| } |
| |
| void PostWorkerGl::setupContext() { |
| android::base::AutoLock lock(mMutex); |
| const auto* surface = getBoundSurface(); |
| const DisplaySurfaceGl* surfaceGl = nullptr; |
| if (surface) { |
| surfaceGl = static_cast<const DisplaySurfaceGl*>(surface->getImpl()); |
| } else { |
| // Create a fake context. |
| // This could happen in AEMU with -qt-hide-window. Also due to an Intel bug |
| // this needs to happen on post thread. |
| // b/274313125 |
| if (!mFakeWindowSurface) { |
| mFakeWindowSurface = mEmulationGl->createFakeWindowSurface(); |
| } |
| if (!mFakeWindowSurface) { |
| ERR("Post worker does not have a window surface."); |
| return; |
| } |
| surfaceGl = static_cast<const DisplaySurfaceGl*>(mFakeWindowSurface->getImpl()); |
| } |
| |
| // It will be no-op if it rebinds to the same context. |
| // We need to retry just in case the surface is changed. |
| |
| // Also we could not use the scope context helper here, |
| // because (1) binds and unbinds happen in very different places; |
| // (2) they both need to happen in post thread, but the d'tor |
| // of PostWorker can happen in a different thread. |
| if (!surfaceGl->bindContext()) { |
| ERR("Failed to bind to post worker context."); |
| return; |
| } |
| mContextBound = true; |
| } |
| |
| void PostWorkerGl::exitImpl() { |
| if (!mContextBound) { |
| return; |
| } |
| s_egl.eglMakeCurrent(s_egl.eglGetDisplay(EGL_DEFAULT_DISPLAY), nullptr, nullptr, nullptr); |
| mContextBound = false; |
| } |
| |
| } // namespace gfxstream |