blob: 62db6d069f88a30a6f4ee7f24a0b7bf6030b71c3 [file] [log] [blame]
/*
* Copyright 2019 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 <cutils/compiler.h>
#include <gui/BufferQueue.h>
#include <surfacetexture/ImageConsumer.h>
#include <surfacetexture/SurfaceTexture.h>
#include <surfacetexture/surface_texture_platform.h>
#include <math/mat4.h>
#include <system/window.h>
#include <utils/Trace.h>
namespace android {
// Macros for including the SurfaceTexture name in log messages
#define SFT_LOGV(x, ...) ALOGV("[%s] " x, mName.string(), ##__VA_ARGS__)
#define SFT_LOGD(x, ...) ALOGD("[%s] " x, mName.string(), ##__VA_ARGS__)
#define SFT_LOGW(x, ...) ALOGW("[%s] " x, mName.string(), ##__VA_ARGS__)
#define SFT_LOGE(x, ...) ALOGE("[%s] " x, mName.string(), ##__VA_ARGS__)
static const mat4 mtxIdentity;
SurfaceTexture::SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t tex,
uint32_t texTarget, bool useFenceSync, bool isControlledByApp)
: ConsumerBase(bq, isControlledByApp),
mCurrentCrop(Rect::EMPTY_RECT),
mCurrentTransform(0),
mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
mCurrentFence(Fence::NO_FENCE),
mCurrentTimestamp(0),
mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
mCurrentFrameNumber(0),
mDefaultWidth(1),
mDefaultHeight(1),
mFilteringEnabled(true),
mTexName(tex),
mUseFenceSync(useFenceSync),
mTexTarget(texTarget),
mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
mOpMode(OpMode::attachedToGL) {
SFT_LOGV("SurfaceTexture");
memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix));
mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
}
SurfaceTexture::SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t texTarget,
bool useFenceSync, bool isControlledByApp)
: ConsumerBase(bq, isControlledByApp),
mCurrentCrop(Rect::EMPTY_RECT),
mCurrentTransform(0),
mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
mCurrentFence(Fence::NO_FENCE),
mCurrentTimestamp(0),
mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
mCurrentFrameNumber(0),
mDefaultWidth(1),
mDefaultHeight(1),
mFilteringEnabled(true),
mTexName(0),
mUseFenceSync(useFenceSync),
mTexTarget(texTarget),
mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
mOpMode(OpMode::detached) {
SFT_LOGV("SurfaceTexture");
memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix));
mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
}
status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h) {
Mutex::Autolock lock(mMutex);
if (mAbandoned) {
SFT_LOGE("setDefaultBufferSize: SurfaceTexture is abandoned!");
return NO_INIT;
}
mDefaultWidth = w;
mDefaultHeight = h;
return mConsumer->setDefaultBufferSize(w, h);
}
status_t SurfaceTexture::updateTexImage() {
ATRACE_CALL();
SFT_LOGV("updateTexImage");
Mutex::Autolock lock(mMutex);
if (mAbandoned) {
SFT_LOGE("updateTexImage: SurfaceTexture is abandoned!");
return NO_INIT;
}
return mEGLConsumer.updateTexImage(*this);
}
status_t SurfaceTexture::releaseTexImage() {
// releaseTexImage can be invoked even when not attached to a GL context.
ATRACE_CALL();
SFT_LOGV("releaseTexImage");
Mutex::Autolock lock(mMutex);
if (mAbandoned) {
SFT_LOGE("releaseTexImage: SurfaceTexture is abandoned!");
return NO_INIT;
}
return mEGLConsumer.releaseTexImage(*this);
}
status_t SurfaceTexture::acquireBufferLocked(BufferItem* item, nsecs_t presentWhen,
uint64_t maxFrameNumber) {
status_t err = ConsumerBase::acquireBufferLocked(item, presentWhen, maxFrameNumber);
if (err != NO_ERROR) {
return err;
}
switch (mOpMode) {
case OpMode::attachedToConsumer:
break;
case OpMode::attachedToGL:
mEGLConsumer.onAcquireBufferLocked(item, *this);
break;
case OpMode::detached:
break;
}
return NO_ERROR;
}
status_t SurfaceTexture::releaseBufferLocked(int buf, sp<GraphicBuffer> graphicBuffer,
EGLDisplay display, EGLSyncKHR eglFence) {
// release the buffer if it hasn't already been discarded by the
// BufferQueue. This can happen, for example, when the producer of this
// buffer has reallocated the original buffer slot after this buffer
// was acquired.
status_t err = ConsumerBase::releaseBufferLocked(buf, graphicBuffer, display, eglFence);
// We could be releasing an EGL/Vulkan buffer, even if not currently
// attached to a GL context.
mImageConsumer.onReleaseBufferLocked(buf);
mEGLConsumer.onReleaseBufferLocked(buf);
return err;
}
status_t SurfaceTexture::detachFromContext() {
ATRACE_CALL();
SFT_LOGV("detachFromContext");
Mutex::Autolock lock(mMutex);
if (mAbandoned) {
SFT_LOGE("detachFromContext: abandoned SurfaceTexture");
return NO_INIT;
}
if (mOpMode != OpMode::attachedToGL) {
SFT_LOGE("detachFromContext: SurfaceTexture is not attached to a GL context");
return INVALID_OPERATION;
}
status_t err = mEGLConsumer.detachFromContext(*this);
if (err == OK) {
mOpMode = OpMode::detached;
}
return err;
}
status_t SurfaceTexture::attachToContext(uint32_t tex) {
ATRACE_CALL();
SFT_LOGV("attachToContext");
Mutex::Autolock lock(mMutex);
if (mAbandoned) {
SFT_LOGE("attachToContext: abandoned SurfaceTexture");
return NO_INIT;
}
if (mOpMode != OpMode::detached) {
SFT_LOGE("attachToContext: SurfaceTexture is already attached to a "
"context");
return INVALID_OPERATION;
}
return mEGLConsumer.attachToContext(tex, *this);
}
void SurfaceTexture::takeConsumerOwnership() {
ATRACE_CALL();
Mutex::Autolock _l(mMutex);
if (mAbandoned) {
SFT_LOGE("attachToView: abandoned SurfaceTexture");
return;
}
if (mOpMode == OpMode::detached) {
mOpMode = OpMode::attachedToConsumer;
if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
// release possible EGLConsumer texture cache
mEGLConsumer.onFreeBufferLocked(mCurrentTexture);
mEGLConsumer.onAbandonLocked();
}
} else {
SFT_LOGE("attachToView: already attached");
}
}
void SurfaceTexture::releaseConsumerOwnership() {
ATRACE_CALL();
Mutex::Autolock _l(mMutex);
if (mAbandoned) {
SFT_LOGE("detachFromView: abandoned SurfaceTexture");
return;
}
if (mOpMode == OpMode::attachedToConsumer) {
mOpMode = OpMode::detached;
} else {
SFT_LOGE("detachFromView: not attached to View");
}
}
uint32_t SurfaceTexture::getCurrentTextureTarget() const {
return mTexTarget;
}
void SurfaceTexture::getTransformMatrix(float mtx[16]) {
Mutex::Autolock lock(mMutex);
memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix));
}
void SurfaceTexture::setFilteringEnabled(bool enabled) {
Mutex::Autolock lock(mMutex);
if (mAbandoned) {
SFT_LOGE("setFilteringEnabled: SurfaceTexture is abandoned!");
return;
}
bool needsRecompute = mFilteringEnabled != enabled;
mFilteringEnabled = enabled;
if (needsRecompute && mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT) {
SFT_LOGD("setFilteringEnabled called with no current item");
}
if (needsRecompute && mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
computeCurrentTransformMatrixLocked();
}
}
void SurfaceTexture::computeCurrentTransformMatrixLocked() {
SFT_LOGV("computeCurrentTransformMatrixLocked");
sp<GraphicBuffer> buf = (mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT)
? nullptr
: mSlots[mCurrentTexture].mGraphicBuffer;
if (buf == nullptr) {
SFT_LOGD("computeCurrentTransformMatrixLocked: no current item");
}
computeTransformMatrix(mCurrentTransformMatrix, buf, mCurrentCrop, mCurrentTransform,
mFilteringEnabled);
}
void SurfaceTexture::computeTransformMatrix(float outTransform[16], const sp<GraphicBuffer>& buf,
const Rect& cropRect, uint32_t transform,
bool filtering) {
// Transform matrices
static const mat4 mtxFlipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
static const mat4 mtxFlipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);
static const mat4 mtxRot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
mat4 xform;
if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
xform *= mtxFlipH;
}
if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
xform *= mtxFlipV;
}
if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
xform *= mtxRot90;
}
if (!cropRect.isEmpty() && buf.get()) {
float tx = 0.0f, ty = 0.0f, sx = 1.0f, sy = 1.0f;
float bufferWidth = buf->getWidth();
float bufferHeight = buf->getHeight();
float shrinkAmount = 0.0f;
if (filtering) {
// In order to prevent bilinear sampling beyond the edge of the
// crop rectangle we may need to shrink it by 2 texels in each
// dimension. Normally this would just need to take 1/2 a texel
// off each end, but because the chroma channels of YUV420 images
// are subsampled we may need to shrink the crop region by a whole
// texel on each side.
switch (buf->getPixelFormat()) {
case PIXEL_FORMAT_RGBA_8888:
case PIXEL_FORMAT_RGBX_8888:
case PIXEL_FORMAT_RGBA_FP16:
case PIXEL_FORMAT_RGBA_1010102:
case PIXEL_FORMAT_RGB_888:
case PIXEL_FORMAT_RGB_565:
case PIXEL_FORMAT_BGRA_8888:
// We know there's no subsampling of any channels, so we
// only need to shrink by a half a pixel.
shrinkAmount = 0.5;
break;
default:
// If we don't recognize the format, we must assume the
// worst case (that we care about), which is YUV420.
shrinkAmount = 1.0;
break;
}
}
// Only shrink the dimensions that are not the size of the buffer.
if (cropRect.width() < bufferWidth) {
tx = (float(cropRect.left) + shrinkAmount) / bufferWidth;
sx = (float(cropRect.width()) - (2.0f * shrinkAmount)) / bufferWidth;
}
if (cropRect.height() < bufferHeight) {
ty = (float(bufferHeight - cropRect.bottom) + shrinkAmount) / bufferHeight;
sy = (float(cropRect.height()) - (2.0f * shrinkAmount)) / bufferHeight;
}
mat4 crop(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1);
xform = crop * xform;
}
// SurfaceFlinger expects the top of its window textures to be at a Y
// coordinate of 0, so SurfaceTexture must behave the same way. We don't
// want to expose this to applications, however, so we must add an
// additional vertical flip to the transform after all the other transforms.
xform = mtxFlipV * xform;
memcpy(outTransform, xform.asArray(), sizeof(xform));
}
Rect SurfaceTexture::scaleDownCrop(const Rect& crop, uint32_t bufferWidth, uint32_t bufferHeight) {
Rect outCrop = crop;
uint32_t newWidth = static_cast<uint32_t>(crop.width());
uint32_t newHeight = static_cast<uint32_t>(crop.height());
if (newWidth * bufferHeight > newHeight * bufferWidth) {
newWidth = newHeight * bufferWidth / bufferHeight;
ALOGV("too wide: newWidth = %d", newWidth);
} else if (newWidth * bufferHeight < newHeight * bufferWidth) {
newHeight = newWidth * bufferHeight / bufferWidth;
ALOGV("too tall: newHeight = %d", newHeight);
}
uint32_t currentWidth = static_cast<uint32_t>(crop.width());
uint32_t currentHeight = static_cast<uint32_t>(crop.height());
// The crop is too wide
if (newWidth < currentWidth) {
uint32_t dw = currentWidth - newWidth;
auto halfdw = dw / 2;
outCrop.left += halfdw;
// Not halfdw because it would subtract 1 too few when dw is odd
outCrop.right -= (dw - halfdw);
// The crop is too tall
} else if (newHeight < currentHeight) {
uint32_t dh = currentHeight - newHeight;
auto halfdh = dh / 2;
outCrop.top += halfdh;
// Not halfdh because it would subtract 1 too few when dh is odd
outCrop.bottom -= (dh - halfdh);
}
ALOGV("getCurrentCrop final crop [%d,%d,%d,%d]", outCrop.left, outCrop.top, outCrop.right,
outCrop.bottom);
return outCrop;
}
nsecs_t SurfaceTexture::getTimestamp() {
SFT_LOGV("getTimestamp");
Mutex::Autolock lock(mMutex);
return mCurrentTimestamp;
}
android_dataspace SurfaceTexture::getCurrentDataSpace() {
SFT_LOGV("getCurrentDataSpace");
Mutex::Autolock lock(mMutex);
return mCurrentDataSpace;
}
uint64_t SurfaceTexture::getFrameNumber() {
SFT_LOGV("getFrameNumber");
Mutex::Autolock lock(mMutex);
return mCurrentFrameNumber;
}
Rect SurfaceTexture::getCurrentCrop() const {
Mutex::Autolock lock(mMutex);
return (mCurrentScalingMode == NATIVE_WINDOW_SCALING_MODE_SCALE_CROP)
? scaleDownCrop(mCurrentCrop, mDefaultWidth, mDefaultHeight)
: mCurrentCrop;
}
uint32_t SurfaceTexture::getCurrentTransform() const {
Mutex::Autolock lock(mMutex);
return mCurrentTransform;
}
uint32_t SurfaceTexture::getCurrentScalingMode() const {
Mutex::Autolock lock(mMutex);
return mCurrentScalingMode;
}
sp<Fence> SurfaceTexture::getCurrentFence() const {
Mutex::Autolock lock(mMutex);
return mCurrentFence;
}
std::shared_ptr<FenceTime> SurfaceTexture::getCurrentFenceTime() const {
Mutex::Autolock lock(mMutex);
return mCurrentFenceTime;
}
void SurfaceTexture::freeBufferLocked(int slotIndex) {
SFT_LOGV("freeBufferLocked: slotIndex=%d", slotIndex);
if (slotIndex == mCurrentTexture) {
mCurrentTexture = BufferQueue::INVALID_BUFFER_SLOT;
}
// The slotIndex buffer could have EGL cache, but there is no way to tell
// for sure. Buffers can be freed after SurfaceTexture has detached from GL
// context or View.
mEGLConsumer.onFreeBufferLocked(slotIndex);
ConsumerBase::freeBufferLocked(slotIndex);
}
void SurfaceTexture::abandonLocked() {
SFT_LOGV("abandonLocked");
mEGLConsumer.onAbandonLocked();
ConsumerBase::abandonLocked();
}
status_t SurfaceTexture::setConsumerUsageBits(uint64_t usage) {
return ConsumerBase::setConsumerUsageBits(usage | DEFAULT_USAGE_FLAGS);
}
void SurfaceTexture::dumpLocked(String8& result, const char* prefix) const {
result.appendFormat("%smTexName=%d mCurrentTexture=%d\n"
"%smCurrentCrop=[%d,%d,%d,%d] mCurrentTransform=%#x\n",
prefix, mTexName, mCurrentTexture, prefix, mCurrentCrop.left,
mCurrentCrop.top, mCurrentCrop.right, mCurrentCrop.bottom,
mCurrentTransform);
ConsumerBase::dumpLocked(result, prefix);
}
sp<GraphicBuffer> SurfaceTexture::dequeueBuffer(int* outSlotid, android_dataspace* outDataspace,
float* outTransformMatrix, bool* outQueueEmpty,
SurfaceTexture_createReleaseFence createFence,
SurfaceTexture_fenceWait fenceWait,
void* fencePassThroughHandle) {
Mutex::Autolock _l(mMutex);
sp<GraphicBuffer> buffer;
if (mAbandoned) {
SFT_LOGE("dequeueImage: SurfaceTexture is abandoned!");
return buffer;
}
if (mOpMode != OpMode::attachedToConsumer) {
SFT_LOGE("dequeueImage: SurfaceTexture is not attached to a View");
return buffer;
}
buffer = mImageConsumer.dequeueBuffer(outSlotid, outDataspace, outQueueEmpty, *this,
createFence, fenceWait, fencePassThroughHandle);
memcpy(outTransformMatrix, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix));
return buffer;
}
} // namespace android