/* * 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(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 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 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(currentDisplayOffsetX), .top = static_cast(currentDisplayOffsetY), .right = static_cast(currentDisplayOffsetX + currentDisplayW), .bottom = static_cast(currentDisplayOffsetY + currentDisplayH), }; postLayerOptions.crop = { .left = 0.0f, .top = static_cast(currentCb->getHeight()), .right = static_cast(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(displayOffsetX), .top = static_cast(displayOffsetY + displayH), .right = static_cast(displayOffsetX + displayW), .bottom = static_cast(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(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 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(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(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