blob: 8eb27da9d54825a3360ea82ef5cebffadc45148a [file] [log] [blame]
/*
* 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,
gfxstream::DisplaySurface* fakeWindowSurface, DisplayGl* displayGl)
: PostWorker(mainThreadPostingOnly, fb, compositor),
m_displayGl(displayGl),
mFakeWindowSurface(fakeWindowSurface) {
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) {
cb->readToBytesScaled(screenwidth, screenheight, format, type, skinRotation, rect, pixels);
}
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();
if (multiDisplay.isMultiDisplayEnabled()) {
if (multiDisplay.isMultiDisplayWindow()) {
int32_t previousDisplayId = -1;
uint32_t currentDisplayId;
uint32_t currentDisplayColorBufferHandle;
while (multiDisplay.getNextMultiDisplay(previousDisplayId, &currentDisplayId,
/*x=*/nullptr,
/*y=*/nullptr,
/*w=*/nullptr,
/*h=*/nullptr,
/*dpi=*/nullptr,
/*flags=*/nullptr,
&currentDisplayColorBufferHandle)) {
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, &currentDisplayId, &currentDisplayOffsetX,
&currentDisplayOffsetY, &currentDisplayW, &currentDisplayH,
/*dpi=*/nullptr,
/*flags=*/nullptr, &currentDisplayColorBufferHandle)) {
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<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 {
// This could happen in AEMU with -qt-hide-window.
// Create a fake context.
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