blob: 369d2719e6d97beb504dbd2d32c1de79396d4156 [file] [log] [blame]
/*
* Copyright 2014 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.
*/
#define LOG_TAG "VNC-VirtualDisplay"
//#define LOG_NDEBUG 0
#include <utils/Log.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <gui/SurfaceComposerClient.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <ui/Rect.h>
#include "VirtualDisplay.h"
using namespace android;
static const int kGlBytesPerPixel = 4; // GL_RGBA
/*
* Returns "true" if the device is rotated 90 degrees.
*/
bool VirtualDisplay::isDeviceRotated(int orientation) {
return orientation != DISPLAY_ORIENTATION_0 &&
orientation != DISPLAY_ORIENTATION_180;
}
/*
* Sets the display projection, based on the display dimensions, video size,
* and device orientation.
*/
status_t VirtualDisplay::setDisplayProjection(const sp<IBinder>& dpy,
const DisplayInfo& mMainDpyInfo) {
status_t err;
// Set the region of the layer stack we're interested in, which in our
// case is "all of it". If the app is rotated (so that the width of the
// app is based on the height of the display), reverse width/height.
bool deviceRotated = isDeviceRotated(mMainDpyInfo.orientation);
uint32_t sourceWidth, sourceHeight;
if (!deviceRotated) {
sourceWidth = mMainDpyInfo.w;
sourceHeight = mMainDpyInfo.h;
} else {
ALOGV("using rotated width/height");
sourceHeight = mMainDpyInfo.w;
sourceWidth = mMainDpyInfo.h;
}
Rect layerStackRect(sourceWidth, sourceHeight);
// We need to preserve the aspect ratio of the display.
float displayAspect = (float) sourceHeight / (float) sourceWidth;
// Set the way we map the output onto the display surface (which will
// be e.g. 1280x720 for a 720p video). The rect is interpreted
// post-rotation, so if the display is rotated 90 degrees we need to
// "pre-rotate" it by flipping width/height, so that the orientation
// adjustment changes it back.
//
// We might want to encode a portrait display as landscape to use more
// of the screen real estate. (If players respect a 90-degree rotation
// hint, we can essentially get a 720x1280 video instead of 1280x720.)
// In that case, we swap the configured video width/height and then
// supply a rotation value to the display projection.
uint32_t videoWidth, videoHeight;
uint32_t outWidth, outHeight;
if (!mRotate) {
videoWidth = mWidth;
videoHeight = mHeight;
} else {
videoWidth = mHeight;
videoHeight = mWidth;
}
if (videoHeight > (uint32_t)(videoWidth * displayAspect)) {
// limited by narrow width; reduce height
outWidth = videoWidth;
outHeight = (uint32_t)(videoWidth * displayAspect);
} else {
// limited by short height; restrict width
outHeight = videoHeight;
outWidth = (uint32_t)(videoHeight / displayAspect);
}
uint32_t offX, offY;
offX = (videoWidth - outWidth) / 2;
offY = (videoHeight - outHeight) / 2;
Rect displayRect(offX, offY, offX + outWidth, offY + outHeight);
if (mRotate) {
printf("Rotated content area is %ux%u at offset x=%d y=%d\n",
outHeight, outWidth, offY, offX);
} else {
printf("Content area is %ux%u at offset x=%d y=%d\n",
outWidth, outHeight, offX, offY);
}
SurfaceComposerClient::setDisplayProjection(dpy,
mRotate ? DISPLAY_ORIENTATION_90 : DISPLAY_ORIENTATION_0,
layerStackRect, displayRect);
return NO_ERROR;
}
status_t VirtualDisplay::start(const DisplayInfo& mainDpyInfo, EventQueue *queue) {
Mutex::Autolock _l(mMutex);
mQueue = queue;
mRotate = isDeviceRotated(mainDpyInfo.orientation);
mWidth = mRotate ? mainDpyInfo.h : mainDpyInfo.w;
mHeight = mRotate ? mainDpyInfo.w : mainDpyInfo.h;
sp<ProcessState> self = ProcessState::self();
self->startThreadPool();
run("vnc-virtualdisplay");
mState = INIT;
while (mState == INIT) {
mStartCond.wait(mMutex);
}
if (mThreadResult != NO_ERROR) {
ALOGE("Failed to start VDS thread: err=%d", mThreadResult);
return mThreadResult;
}
assert(mState == RUNNING);
mDpy = SurfaceComposerClient::createDisplay(
String8("VNCFlinger"), false /*secure*/);
SurfaceComposerClient::openGlobalTransaction();
SurfaceComposerClient::setDisplaySurface(mDpy, mProducer);
setDisplayProjection(mDpy, mainDpyInfo);
SurfaceComposerClient::setDisplayLayerStack(mDpy, 0); // default stack
SurfaceComposerClient::closeGlobalTransaction();
ALOGV("VirtualDisplay::start successful");
return NO_ERROR;
}
status_t VirtualDisplay::stop() {
Mutex::Autolock _l(mMutex);
mState = STOPPING;
mEventCond.signal();
return NO_ERROR;
}
bool VirtualDisplay::threadLoop() {
Mutex::Autolock _l(mMutex);
mThreadResult = setup_l();
if (mThreadResult != NO_ERROR) {
ALOGW("Aborting VDS thread");
mState = STOPPED;
release_l();
mStartCond.broadcast();
return false;
}
ALOGV("VDS thread running");
mState = RUNNING;
mStartCond.broadcast();
while (mState == RUNNING) {
mEventCond.wait(mMutex);
ALOGD("Awake, frame available");
void* ptr = processFrame_l();
const Event ev(EVENT_BUFFER_READY, ptr);
mQueue->enqueue(ev);
}
ALOGV("VDS thread stopping");
release_l();
mState = STOPPED;
return false; // stop
}
status_t VirtualDisplay::setup_l() {
status_t err;
err = mEglWindow.createPbuffer(mWidth, mHeight);
if (err != NO_ERROR) {
return err;
}
mEglWindow.makeCurrent();
glViewport(0, 0, mWidth, mHeight);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
// Shader for rendering the external texture.
err = mExtTexProgram.setup(Program::PROGRAM_EXTERNAL_TEXTURE);
if (err != NO_ERROR) {
return err;
}
// Input side (buffers from virtual display).
glGenTextures(1, &mExtTextureName);
if (mExtTextureName == 0) {
ALOGE("glGenTextures failed: %#x", glGetError());
return UNKNOWN_ERROR;
}
mBufSize = mWidth * mHeight * kGlBytesPerPixel;
// pixel buffer for image copy
mPBO = new GLuint[NUM_PBO];
glGenBuffers(NUM_PBO, mPBO);
for (unsigned int i = 0; i < NUM_PBO; i++) {
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO[i]);
glBufferData(GL_PIXEL_PACK_BUFFER, mBufSize, 0, GL_DYNAMIC_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&mProducer, &consumer);
mGlConsumer = new GLConsumer(consumer, mExtTextureName,
GL_TEXTURE_EXTERNAL_OES, true, false);
mGlConsumer->setName(String8("virtual display"));
mGlConsumer->setDefaultBufferSize(mWidth, mHeight);
mProducer->setMaxDequeuedBufferCount(4);
mGlConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_TEXTURE);
mGlConsumer->setFrameAvailableListener(this);
ALOGD("VirtualDisplay::setup_l OK");
return NO_ERROR;
}
void* VirtualDisplay::processFrame_l() {
ALOGD("processFrame_l\n");
float texMatrix[16];
mGlConsumer->updateTexImage();
mGlConsumer->getTransformMatrix(texMatrix);
// The data is in an external texture, so we need to render it to the
// pbuffer to get access to RGB pixel data. We also want to flip it
// upside-down for easy conversion to a bitmap.
int width = mEglWindow.getWidth();
int height = mEglWindow.getHeight();
mExtTexProgram.blit(mExtTextureName, texMatrix, 0, 0, mWidth, mHeight, true);
GLenum glErr;
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO[mIndex]);
glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, 0);
if ((glErr = glGetError()) != GL_NO_ERROR) {
ALOGE("glReadPixels failed: %#x", glErr);
return NULL;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO[mIndex]);
void* ptr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, mBufSize, GL_MAP_READ_BIT);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
mIndex = (mIndex + 1) % NUM_PBO;
return ptr;
}
void VirtualDisplay::release_l() {
ALOGD("release_l");
mGlConsumer.clear();
mProducer.clear();
mExtTexProgram.release();
mEglWindow.release();
SurfaceComposerClient::destroyDisplay(mDpy);
}
// Callback; executes on arbitrary thread.
void VirtualDisplay::onFrameAvailable(const BufferItem& item) {
Mutex::Autolock _l(mMutex);
mEventCond.signal();
ALOGD("mTimestamp=%ld mFrameNumber=%ld", item.mTimestamp, item.mFrameNumber);
}