/* * 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 "PostWorker.h" #include #include #include "ColorBuffer.h" #include "FrameBuffer.h" #include "RenderThreadInfo.h" #include "aemu/base/Tracing.h" #include "gl/DisplayGl.h" #include "host-common/GfxstreamFatalError.h" #include "host-common/logging.h" #include "host-common/misc.h" #include "vulkan/DisplayVk.h" #include "vulkan/VkCommonOperations.h" #define POST_DEBUG 0 #if POST_DEBUG >= 1 #define DD(fmt, ...) \ fprintf(stderr, "%s:%d| " fmt, __func__, __LINE__, ##__VA_ARGS__) #else #define DD(fmt, ...) (void)0 #endif #define POST_ERROR(fmt, ...) \ do { \ fprintf(stderr, "%s(%s:%d): " fmt "\n", __func__, __FILE__, __LINE__, \ ##__VA_ARGS__); \ fflush(stderr); \ } while (0) static void sDefaultRunOnUiThread(UiUpdateFunc f, void* data, bool wait) { (void)f; (void)data; (void)wait; } namespace gfxstream { namespace { using emugl::ABORT_REASON_OTHER; using emugl::FatalError; using gl::DisplayGl; using vk::DisplayVk; 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 PostWorker::PostWorker(bool mainThreadPostingOnly, Compositor* compositor, DisplayGl* displayGl, DisplayVk* displayVk) : mFb(FrameBuffer::getFB()), m_mainThreadPostingOnly(mainThreadPostingOnly), m_runOnUiThread(m_mainThreadPostingOnly ? emugl::get_emugl_window_operations().runOnUiThread : sDefaultRunOnUiThread), m_compositor(compositor), m_displayGl(displayGl), m_displayVk(displayVk) {} DisplayGl::PostLayer PostWorker::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, }, }; } std::shared_future PostWorker::postImpl(ColorBuffer* cb) { std::shared_future completedFuture = std::async(std::launch::deferred, [] {}).share(); completedFuture.wait(); if (m_displayVk) { constexpr const int kMaxPostRetries = 2; for (int i = 0; i < kMaxPostRetries; i++) { const auto imageInfo = mFb->borrowColorBufferForDisplay(cb->getHndl()); auto result = m_displayVk->post(imageInfo.get()); if (result.success) { return result.postCompletedWaitable; } } ERR("Failed to post ColorBuffer after %d retries.", kMaxPostRetries); return completedFuture; } if (!m_displayGl) { GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "PostWorker missing DisplayGl."; } 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(); 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; } 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); } // 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 PostWorker::viewportImpl(int width, int height) { if (m_displayVk) { return; } 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 PostWorker::clearImpl() { if (m_displayVk) { GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "PostWorker with Vulkan doesn't support clear"; } if (!m_displayGl) { GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "PostWorker missing DisplayGl."; } m_displayGl->clear(); } std::shared_future PostWorker::composeImpl(const FlatComposeRequest& composeRequest) { std::shared_future completedFuture = std::async(std::launch::deferred, [] {}).share(); completedFuture.wait(); if (!isComposeTargetReady(composeRequest.targetHandle)) { ERR("The last composition on the target buffer hasn't completed."); } Compositor::CompositionRequest compositorRequest = {}; compositorRequest.target = mFb->borrowColorBufferForComposition(composeRequest.targetHandle, /*colorBufferIsTarget=*/true); if (!compositorRequest.target) { ERR("Compose target is null (cb=0x%x).", composeRequest.targetHandle); return completedFuture; } for (const ComposeLayer& guestLayer : composeRequest.layers) { // Skip the ColorBuffer whose id is 0. if (!guestLayer.cbHandle) { continue; } auto source = mFb->borrowColorBufferForComposition(guestLayer.cbHandle, /*colorBufferIsTarget=*/false); if (!source) { continue; } auto& compositorLayer = compositorRequest.layers.emplace_back(); compositorLayer.props = guestLayer; compositorLayer.source = std::move(source); } return m_compositor->compose(compositorRequest); } void PostWorker::screenshot(ColorBuffer* cb, int width, int height, GLenum format, GLenum type, int rotation, void* pixels, Rect rect) { if (m_displayVk) { GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Screenshot not supported with native Vulkan swapchain enabled."; } cb->readToBytesScaled(width, height, format, type, rotation, rect, pixels); } void PostWorker::block(std::promise scheduledSignal, std::future continueSignal) { // Do not block mainthread. if (m_mainThreadPostingOnly) { return; } // MSVC STL doesn't support not copyable std::packaged_task. As a workaround, we use the // copyable std::shared_ptr here. auto block = std::make_shared(Post::Block{ .scheduledSignal = std::move(scheduledSignal), .continueSignal = std::move(continueSignal), }); runTask(std::packaged_task([block] { block->scheduledSignal.set_value(); block->continueSignal.wait(); })); } PostWorker::~PostWorker() {} void PostWorker::post(ColorBuffer* cb, std::unique_ptr postCallback) { auto packagedPostCallback = std::shared_ptr(std::move(postCallback)); runTask( std::packaged_task([cb, packagedPostCallback, this] { auto completedFuture = postImpl(cb); (*packagedPostCallback)(completedFuture); })); } void PostWorker::viewport(int width, int height) { runTask(std::packaged_task( [width, height, this] { viewportImpl(width, height); })); } void PostWorker::compose(std::unique_ptr composeRequest, std::unique_ptr composeCallback) { // std::shared_ptr(std::move(...)) is WA for MSFT STL implementation bug: // https://developercommunity.visualstudio.com/t/unable-to-move-stdpackaged-task-into-any-stl-conta/108672 auto packagedComposeCallback = std::shared_ptr(std::move(composeCallback)); auto packagedComposeRequest = std::shared_ptr(std::move(composeRequest)); runTask( std::packaged_task([packagedComposeCallback, packagedComposeRequest, this] { auto completedFuture = composeImpl(*packagedComposeRequest); m_composeTargetToComposeFuture.emplace(packagedComposeRequest->targetHandle, completedFuture); (*packagedComposeCallback)(completedFuture); })); } void PostWorker::clear() { runTask(std::packaged_task([this] { clearImpl(); })); } void PostWorker::runTask(std::packaged_task task) { using Task = std::packaged_task; auto taskPtr = std::make_unique(std::move(task)); if (m_mainThreadPostingOnly) { m_runOnUiThread( [](void* data) { std::unique_ptr taskPtr(reinterpret_cast(data)); (*taskPtr)(); }, taskPtr.release(), false); } else { (*taskPtr)(); } } bool PostWorker::isComposeTargetReady(uint32_t targetHandle) { // Even if the target ColorBuffer has already been destroyed, the compose future should have // been waited and set to the ready state. for (auto i = m_composeTargetToComposeFuture.begin(); i != m_composeTargetToComposeFuture.end();) { auto& composeFuture = i->second; if (composeFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { i = m_composeTargetToComposeFuture.erase(i); } else { i++; } } if (m_composeTargetToComposeFuture.find(targetHandle) == m_composeTargetToComposeFuture.end()) { return true; } return false; } } // namespace gfxstream