Refactor SurfaceTexture a bit.
Rearranges updateTexImage() so that the SurfaceFlinger-specific behavior is in a new SurfaceFlingerConsumer subclass. SurfaceTexture behavior should not be altered. Instead of acquire-bind-release we now do acquire-release-bind, but since it's all done with the lock held there shouldn't be any externally-visible change. Change-Id: Ia566e4727945e2cfb9359fc6d2a8f8af64d7b7b7
This commit is contained in:
parent
0e1e53e376
commit
bf974abe92
|
@ -182,8 +182,9 @@ public:
|
|||
mBuf(INVALID_BUFFER_SLOT) {
|
||||
mCrop.makeInvalid();
|
||||
}
|
||||
// mGraphicBuffer points to the buffer allocated for this slot or is NULL
|
||||
// if no buffer has been allocated.
|
||||
// mGraphicBuffer points to the buffer allocated for this slot, or is NULL
|
||||
// if the buffer in this slot has been acquired in the past (see
|
||||
// BufferSlot.mAcquireCalled).
|
||||
sp<GraphicBuffer> mGraphicBuffer;
|
||||
|
||||
// mCrop is the current crop rectangle for this buffer slot.
|
||||
|
|
|
@ -74,14 +74,13 @@ public:
|
|||
GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true,
|
||||
const sp<BufferQueue> &bufferQueue = 0);
|
||||
|
||||
// updateTexImage sets the image contents of the target texture to that of
|
||||
// the most recently queued buffer.
|
||||
// updateTexImage acquires the most recently queued buffer, and sets the
|
||||
// image contents of the target texture to it.
|
||||
//
|
||||
// This call may only be made while the OpenGL ES context to which the
|
||||
// target texture belongs is bound to the calling thread.
|
||||
//
|
||||
// After calling this method the doGLFenceWait method must be called
|
||||
// before issuing OpenGL ES commands that access the texture contents.
|
||||
// This calls doGLFenceWait to ensure proper synchronization.
|
||||
status_t updateTexImage();
|
||||
|
||||
// setReleaseFence stores a fence file descriptor that will signal when the
|
||||
|
@ -161,8 +160,7 @@ public:
|
|||
|
||||
// doGLFenceWait inserts a wait command into the OpenGL ES command stream
|
||||
// to ensure that it is safe for future OpenGL ES commands to access the
|
||||
// current texture buffer. This must be called each time updateTexImage
|
||||
// is called before issuing OpenGL ES commands that access the texture.
|
||||
// current texture buffer.
|
||||
status_t doGLFenceWait() const;
|
||||
|
||||
// isSynchronousMode returns whether the SurfaceTexture is currently in
|
||||
|
@ -233,23 +231,33 @@ protected:
|
|||
virtual status_t releaseBufferLocked(int buf, EGLDisplay display,
|
||||
EGLSyncKHR eglFence);
|
||||
|
||||
status_t releaseBufferLocked(int buf, EGLSyncKHR eglFence) {
|
||||
return releaseBufferLocked(buf, mEglDisplay, eglFence);
|
||||
}
|
||||
|
||||
static bool isExternalFormat(uint32_t format);
|
||||
|
||||
private:
|
||||
// this version of updateTexImage() takes a functor used to reject or not
|
||||
// the newly acquired buffer.
|
||||
// this API is TEMPORARY and intended to be used by SurfaceFlinger only,
|
||||
// which is why class Layer is made a friend of SurfaceTexture below.
|
||||
class BufferRejecter {
|
||||
friend class SurfaceTexture;
|
||||
virtual bool reject(const sp<GraphicBuffer>& buf,
|
||||
const BufferQueue::BufferItem& item) = 0;
|
||||
protected:
|
||||
virtual ~BufferRejecter() { }
|
||||
};
|
||||
friend class Layer;
|
||||
status_t updateTexImage(BufferRejecter* rejecter, bool skipSync);
|
||||
// This releases the buffer in the slot referenced by mCurrentTexture,
|
||||
// then updates state to refer to the BufferItem, which must be a
|
||||
// newly-acquired buffer.
|
||||
status_t releaseAndUpdateLocked(const BufferQueue::BufferItem& item);
|
||||
|
||||
// Binds mTexName and the current buffer to mTexTarget. Uses
|
||||
// mCurrentTexture if it's set, mCurrentTextureBuf if not.
|
||||
status_t bindTextureImage();
|
||||
|
||||
// doGLFenceWaitLocked inserts a wait command into the OpenGL ES command
|
||||
// stream to ensure that it is safe for future OpenGL ES commands to
|
||||
// access the current texture buffer.
|
||||
status_t doGLFenceWaitLocked() const;
|
||||
|
||||
// Gets the current EGLDisplay and EGLContext values, and compares them
|
||||
// to mEglDisplay and mEglContext. If the fields have been previously
|
||||
// set, the values must match; if not, the fields are set to the current
|
||||
// values.
|
||||
status_t checkAndUpdateEglStateLocked();
|
||||
|
||||
private:
|
||||
// createImage creates a new EGLImage from a GraphicBuffer.
|
||||
EGLImageKHR createImage(EGLDisplay dpy,
|
||||
const sp<GraphicBuffer>& graphicBuffer);
|
||||
|
@ -267,19 +275,19 @@ private:
|
|||
// mCurrentTextureBuf must not be NULL.
|
||||
void computeCurrentTransformMatrixLocked();
|
||||
|
||||
// doGLFenceWaitLocked inserts a wait command into the OpenGL ES command
|
||||
// stream to ensure that it is safe for future OpenGL ES commands to
|
||||
// access the current texture buffer. This must be called each time
|
||||
// updateTexImage is called before issuing OpenGL ES commands that access
|
||||
// the texture.
|
||||
status_t doGLFenceWaitLocked() const;
|
||||
|
||||
// syncForReleaseLocked performs the synchronization needed to release the
|
||||
// current slot from an OpenGL ES context. If needed it will set the
|
||||
// current slot's fence to guard against a producer accessing the buffer
|
||||
// before the outstanding accesses have completed.
|
||||
status_t syncForReleaseLocked(EGLDisplay dpy);
|
||||
|
||||
// Normally, when we bind a buffer to a texture target, we bind a buffer
|
||||
// that is referenced by an entry in mEglSlots. In some situations we
|
||||
// have a buffer in mCurrentTextureBuf, but no corresponding entry for
|
||||
// it in our slot array. bindUnslottedBuffer handles that situation by
|
||||
// binding the buffer without touching the EglSlots.
|
||||
status_t bindUnslottedBufferLocked(EGLDisplay dpy);
|
||||
|
||||
// The default consumer usage flags that SurfaceTexture always sets on its
|
||||
// BufferQueue instance; these will be OR:d with any additional flags passed
|
||||
// from the SurfaceTexture user. In particular, SurfaceTexture will always
|
||||
|
@ -344,8 +352,8 @@ private:
|
|||
|
||||
// EGLSlot contains the information and object references that
|
||||
// SurfaceTexture maintains about a BufferQueue buffer slot.
|
||||
struct EGLSlot {
|
||||
EGLSlot()
|
||||
struct EglSlot {
|
||||
EglSlot()
|
||||
: mEglImage(EGL_NO_IMAGE_KHR),
|
||||
mEglFence(EGL_NO_SYNC_KHR) {
|
||||
}
|
||||
|
@ -379,7 +387,7 @@ private:
|
|||
// slot that has not yet been used. The buffer allocated to a slot will also
|
||||
// be replaced if the requested buffer usage or geometry differs from that
|
||||
// of the buffer allocated to a slot.
|
||||
EGLSlot mEglSlots[BufferQueue::NUM_BUFFER_SLOTS];
|
||||
EglSlot mEglSlots[BufferQueue::NUM_BUFFER_SLOTS];
|
||||
|
||||
// mCurrentTexture is the buffer slot index of the buffer that is currently
|
||||
// bound to the OpenGL texture. It is initialized to INVALID_BUFFER_SLOT,
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
#include <private/gui/ComposerService.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <gui/SurfaceTexture.h>
|
||||
#include <utils/Trace.h>
|
||||
|
||||
// Macros for including the BufferQueue name in log messages
|
||||
|
|
|
@ -154,7 +154,56 @@ status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h)
|
|||
}
|
||||
|
||||
status_t SurfaceTexture::updateTexImage() {
|
||||
return SurfaceTexture::updateTexImage(NULL, false);
|
||||
ATRACE_CALL();
|
||||
ST_LOGV("updateTexImage");
|
||||
Mutex::Autolock lock(mMutex);
|
||||
|
||||
if (mAbandoned) {
|
||||
ST_LOGE("updateTexImage: SurfaceTexture is abandoned!");
|
||||
return NO_INIT;
|
||||
}
|
||||
|
||||
// Make sure the EGL state is the same as in previous calls.
|
||||
status_t err = checkAndUpdateEglStateLocked();
|
||||
if (err != NO_ERROR) {
|
||||
return err;
|
||||
}
|
||||
|
||||
BufferQueue::BufferItem item;
|
||||
|
||||
// Acquire the next buffer.
|
||||
// In asynchronous mode the list is guaranteed to be one buffer
|
||||
// deep, while in synchronous mode we use the oldest buffer.
|
||||
err = acquireBufferLocked(&item);
|
||||
if (err != NO_ERROR) {
|
||||
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
|
||||
// We always bind the texture even if we don't update its contents.
|
||||
ST_LOGV("updateTexImage: no buffers were available");
|
||||
glBindTexture(mTexTarget, mTexName);
|
||||
err = NO_ERROR;
|
||||
} else {
|
||||
ST_LOGE("updateTexImage: acquire failed: %s (%d)",
|
||||
strerror(-err), err);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// Release the previous buffer.
|
||||
err = releaseAndUpdateLocked(item);
|
||||
if (err != NO_ERROR) {
|
||||
// We always bind the texture.
|
||||
glBindTexture(mTexTarget, mTexName);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Bind the new buffer to the GL texture.
|
||||
err = bindTextureImage();
|
||||
if (err != NO_ERROR) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// Wait for the new buffer to be ready.
|
||||
return doGLFenceWaitLocked();
|
||||
}
|
||||
|
||||
status_t SurfaceTexture::acquireBufferLocked(BufferQueue::BufferItem *item) {
|
||||
|
@ -165,161 +214,156 @@ status_t SurfaceTexture::acquireBufferLocked(BufferQueue::BufferItem *item) {
|
|||
|
||||
int slot = item->mBuf;
|
||||
if (item->mGraphicBuffer != NULL) {
|
||||
// This buffer has not been acquired before, so we must assume
|
||||
// that any EGLImage in mEglSlots is stale.
|
||||
if (mEglSlots[slot].mEglImage != EGL_NO_IMAGE_KHR) {
|
||||
eglDestroyImageKHR(mEglDisplay, mEglSlots[slot].mEglImage);
|
||||
if (!eglDestroyImageKHR(mEglDisplay, mEglSlots[slot].mEglImage)) {
|
||||
ST_LOGW("acquireBufferLocked: eglDestroyImageKHR failed for slot=%d",
|
||||
slot);
|
||||
// keep going
|
||||
}
|
||||
mEglSlots[slot].mEglImage = EGL_NO_IMAGE_KHR;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the GL texture object. We may have to do this even when
|
||||
// item.mGraphicBuffer == NULL, if we destroyed the EGLImage when
|
||||
// detaching from a context but the buffer has not been re-allocated.
|
||||
if (mEglSlots[slot].mEglImage == EGL_NO_IMAGE_KHR) {
|
||||
EGLImageKHR image = createImage(mEglDisplay, mSlots[slot].mGraphicBuffer);
|
||||
if (image == EGL_NO_IMAGE_KHR) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
mEglSlots[slot].mEglImage = image;
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
status_t SurfaceTexture::releaseBufferLocked(int buf, EGLDisplay display,
|
||||
EGLSyncKHR eglFence) {
|
||||
status_t err = ConsumerBase::releaseBufferLocked(buf, mEglDisplay,
|
||||
eglFence);
|
||||
status_t err = ConsumerBase::releaseBufferLocked(buf, display, eglFence);
|
||||
|
||||
mEglSlots[buf].mEglFence = EGL_NO_SYNC_KHR;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
status_t SurfaceTexture::updateTexImage(BufferRejecter* rejecter, bool skipSync) {
|
||||
ATRACE_CALL();
|
||||
ST_LOGV("updateTexImage");
|
||||
Mutex::Autolock lock(mMutex);
|
||||
|
||||
status_t SurfaceTexture::releaseAndUpdateLocked(const BufferQueue::BufferItem& item)
|
||||
{
|
||||
status_t err = NO_ERROR;
|
||||
|
||||
if (mAbandoned) {
|
||||
ST_LOGE("updateTexImage: SurfaceTexture is abandoned!");
|
||||
return NO_INIT;
|
||||
}
|
||||
|
||||
if (!mAttached) {
|
||||
ST_LOGE("updateTexImage: SurfaceTexture is not attached to an OpenGL "
|
||||
ST_LOGE("releaseAndUpdate: SurfaceTexture is not attached to an OpenGL "
|
||||
"ES context");
|
||||
return INVALID_OPERATION;
|
||||
}
|
||||
|
||||
// Confirm state.
|
||||
err = checkAndUpdateEglStateLocked();
|
||||
if (err != NO_ERROR) {
|
||||
return err;
|
||||
}
|
||||
|
||||
int buf = item.mBuf;
|
||||
|
||||
// If the mEglSlot entry is empty, create an EGLImage for the gralloc
|
||||
// buffer currently in the slot in ConsumerBase.
|
||||
//
|
||||
// We may have to do this even when item.mGraphicBuffer == NULL (which
|
||||
// means the buffer was previously acquired), if we destroyed the
|
||||
// EGLImage when detaching from a context but the buffer has not been
|
||||
// re-allocated.
|
||||
if (mEglSlots[buf].mEglImage == EGL_NO_IMAGE_KHR) {
|
||||
EGLImageKHR image = createImage(mEglDisplay, mSlots[buf].mGraphicBuffer);
|
||||
if (image == EGL_NO_IMAGE_KHR) {
|
||||
ST_LOGW("releaseAndUpdate: unable to createImage on display=%p slot=%d",
|
||||
mEglDisplay, buf);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
mEglSlots[buf].mEglImage = image;
|
||||
}
|
||||
|
||||
// Do whatever sync ops we need to do before releasing the old slot.
|
||||
err = syncForReleaseLocked(mEglDisplay);
|
||||
if (err != NO_ERROR) {
|
||||
// Release the buffer we just acquired. It's not safe to
|
||||
// release the old buffer, so instead we just drop the new frame.
|
||||
releaseBufferLocked(buf, mEglDisplay, EGL_NO_SYNC_KHR);
|
||||
return err;
|
||||
}
|
||||
|
||||
ST_LOGV("releaseAndUpdate: (slot=%d buf=%p) -> (slot=%d buf=%p)",
|
||||
mCurrentTexture,
|
||||
mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0,
|
||||
buf, mSlots[buf].mGraphicBuffer->handle);
|
||||
|
||||
// release old buffer
|
||||
if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
|
||||
status_t status = releaseBufferLocked(mCurrentTexture, mEglDisplay,
|
||||
mEglSlots[mCurrentTexture].mEglFence);
|
||||
if (status != NO_ERROR && status != BufferQueue::STALE_BUFFER_SLOT) {
|
||||
ST_LOGE("releaseAndUpdate: failed to release buffer: %s (%d)",
|
||||
strerror(-status), status);
|
||||
err = status;
|
||||
// keep going, with error raised [?]
|
||||
}
|
||||
}
|
||||
|
||||
// Update the SurfaceTexture state.
|
||||
mCurrentTexture = buf;
|
||||
mCurrentTextureBuf = mSlots[buf].mGraphicBuffer;
|
||||
mCurrentCrop = item.mCrop;
|
||||
mCurrentTransform = item.mTransform;
|
||||
mCurrentScalingMode = item.mScalingMode;
|
||||
mCurrentTimestamp = item.mTimestamp;
|
||||
mCurrentFence = item.mFence;
|
||||
|
||||
computeCurrentTransformMatrixLocked();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
status_t SurfaceTexture::bindTextureImage() {
|
||||
if (mEglDisplay == EGL_NO_DISPLAY) {
|
||||
ALOGE("bindTextureImage: invalid display");
|
||||
return INVALID_OPERATION;
|
||||
}
|
||||
|
||||
GLint error;
|
||||
while ((error = glGetError()) != GL_NO_ERROR) {
|
||||
ST_LOGW("bindTextureImage: clearing GL error: %#04x", error);
|
||||
}
|
||||
|
||||
glBindTexture(mTexTarget, mTexName);
|
||||
if (mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT) {
|
||||
if (mCurrentTextureBuf == NULL) {
|
||||
ST_LOGE("bindTextureImage: no currently-bound texture");
|
||||
return NO_INIT;
|
||||
}
|
||||
return bindUnslottedBufferLocked(mEglDisplay);
|
||||
} else {
|
||||
EGLImageKHR image = mEglSlots[mCurrentTexture].mEglImage;
|
||||
|
||||
glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image);
|
||||
|
||||
while ((error = glGetError()) != GL_NO_ERROR) {
|
||||
ST_LOGE("bindTextureImage: error binding external texture image %p"
|
||||
": %#04x", image, error);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
status_t SurfaceTexture::checkAndUpdateEglStateLocked() {
|
||||
EGLDisplay dpy = eglGetCurrentDisplay();
|
||||
EGLContext ctx = eglGetCurrentContext();
|
||||
|
||||
if ((mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) ||
|
||||
dpy == EGL_NO_DISPLAY) {
|
||||
ST_LOGE("updateTexImage: invalid current EGLDisplay");
|
||||
ST_LOGE("checkAndUpdateEglState: invalid current EGLDisplay");
|
||||
return INVALID_OPERATION;
|
||||
}
|
||||
|
||||
if ((mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) ||
|
||||
ctx == EGL_NO_CONTEXT) {
|
||||
ST_LOGE("updateTexImage: invalid current EGLContext");
|
||||
ST_LOGE("checkAndUpdateEglState: invalid current EGLContext");
|
||||
return INVALID_OPERATION;
|
||||
}
|
||||
|
||||
mEglDisplay = dpy;
|
||||
mEglContext = ctx;
|
||||
|
||||
BufferQueue::BufferItem item;
|
||||
|
||||
// In asynchronous mode the list is guaranteed to be one buffer
|
||||
// deep, while in synchronous mode we use the oldest buffer.
|
||||
err = acquireBufferLocked(&item);
|
||||
if (err == NO_ERROR) {
|
||||
int buf = item.mBuf;
|
||||
|
||||
// we call the rejecter here, in case the caller has a reason to
|
||||
// not accept this buffer. this is used by SurfaceFlinger to
|
||||
// reject buffers which have the wrong size
|
||||
if (rejecter && rejecter->reject(mSlots[buf].mGraphicBuffer, item)) {
|
||||
releaseBufferLocked(buf, dpy, EGL_NO_SYNC_KHR);
|
||||
glBindTexture(mTexTarget, mTexName);
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
GLint error;
|
||||
while ((error = glGetError()) != GL_NO_ERROR) {
|
||||
ST_LOGW("updateTexImage: clearing GL error: %#04x", error);
|
||||
}
|
||||
|
||||
EGLImageKHR image = mEglSlots[buf].mEglImage;
|
||||
glBindTexture(mTexTarget, mTexName);
|
||||
glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image);
|
||||
|
||||
while ((error = glGetError()) != GL_NO_ERROR) {
|
||||
ST_LOGE("updateTexImage: error binding external texture image %p "
|
||||
"(slot %d): %#04x", image, buf, error);
|
||||
err = UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (err == NO_ERROR) {
|
||||
err = syncForReleaseLocked(dpy);
|
||||
}
|
||||
|
||||
if (err != NO_ERROR) {
|
||||
// Release the buffer we just acquired. It's not safe to
|
||||
// release the old buffer, so instead we just drop the new frame.
|
||||
releaseBufferLocked(buf, dpy, EGL_NO_SYNC_KHR);
|
||||
return err;
|
||||
}
|
||||
|
||||
ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)",
|
||||
mCurrentTexture,
|
||||
mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0,
|
||||
buf, mSlots[buf].mGraphicBuffer->handle);
|
||||
|
||||
// release old buffer
|
||||
if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
|
||||
status_t status = releaseBufferLocked(mCurrentTexture, dpy,
|
||||
mEglSlots[mCurrentTexture].mEglFence);
|
||||
if (status != NO_ERROR && status != BufferQueue::STALE_BUFFER_SLOT) {
|
||||
ST_LOGE("updateTexImage: failed to release buffer: %s (%d)",
|
||||
strerror(-status), status);
|
||||
err = status;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the SurfaceTexture state.
|
||||
mCurrentTexture = buf;
|
||||
mCurrentTextureBuf = mSlots[buf].mGraphicBuffer;
|
||||
mCurrentCrop = item.mCrop;
|
||||
mCurrentTransform = item.mTransform;
|
||||
mCurrentScalingMode = item.mScalingMode;
|
||||
mCurrentTimestamp = item.mTimestamp;
|
||||
mCurrentFence = item.mFence;
|
||||
if (!skipSync) {
|
||||
// SurfaceFlinger needs to lazily perform GLES synchronization
|
||||
// only when it's actually going to use GLES for compositing.
|
||||
// Eventually SurfaceFlinger should have its own consumer class,
|
||||
// but for now we'll just hack it in to SurfaceTexture.
|
||||
// SurfaceFlinger is responsible for calling doGLFenceWait before
|
||||
// texturing from this SurfaceTexture.
|
||||
doGLFenceWaitLocked();
|
||||
}
|
||||
computeCurrentTransformMatrixLocked();
|
||||
} else {
|
||||
if (err < 0) {
|
||||
ST_LOGE("updateTexImage: acquire failed: %s (%d)",
|
||||
strerror(-err), err);
|
||||
return err;
|
||||
}
|
||||
// We always bind the texture even if we don't update its contents.
|
||||
glBindTexture(mTexTarget, mTexName);
|
||||
return OK;
|
||||
}
|
||||
|
||||
return err;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
void SurfaceTexture::setReleaseFence(int fenceFd) {
|
||||
|
@ -427,30 +471,8 @@ status_t SurfaceTexture::attachToContext(GLuint tex) {
|
|||
// The EGLImageKHR that was associated with the slot was destroyed when
|
||||
// the SurfaceTexture was detached from the old context, so we need to
|
||||
// recreate it here.
|
||||
EGLImageKHR image = createImage(dpy, mCurrentTextureBuf);
|
||||
if (image == EGL_NO_IMAGE_KHR) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// Attach the current buffer to the GL texture.
|
||||
glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image);
|
||||
|
||||
GLint error;
|
||||
status_t err = OK;
|
||||
while ((error = glGetError()) != GL_NO_ERROR) {
|
||||
ST_LOGE("attachToContext: error binding external texture image %p "
|
||||
"(slot %d): %#04x", image, mCurrentTexture, error);
|
||||
err = UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// We destroy the EGLImageKHR here because the current buffer may no
|
||||
// longer be associated with one of the buffer slots, so we have
|
||||
// nowhere to to store it. If the buffer is still associated with a
|
||||
// slot then another EGLImageKHR will be created next time that buffer
|
||||
// gets acquired in updateTexImage.
|
||||
eglDestroyImageKHR(dpy, image);
|
||||
|
||||
if (err != OK) {
|
||||
status_t err = bindUnslottedBufferLocked(dpy);
|
||||
if (err != NO_ERROR) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
@ -463,6 +485,38 @@ status_t SurfaceTexture::attachToContext(GLuint tex) {
|
|||
return OK;
|
||||
}
|
||||
|
||||
status_t SurfaceTexture::bindUnslottedBufferLocked(EGLDisplay dpy) {
|
||||
ST_LOGV("bindUnslottedBuffer ct=%d ctb=%p",
|
||||
mCurrentTexture, mCurrentTextureBuf.get());
|
||||
|
||||
// Create a temporary EGLImageKHR.
|
||||
EGLImageKHR image = createImage(dpy, mCurrentTextureBuf);
|
||||
if (image == EGL_NO_IMAGE_KHR) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// Attach the current buffer to the GL texture.
|
||||
glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image);
|
||||
|
||||
GLint error;
|
||||
status_t err = OK;
|
||||
while ((error = glGetError()) != GL_NO_ERROR) {
|
||||
ST_LOGE("bindUnslottedBuffer: error binding external texture image %p "
|
||||
"(slot %d): %#04x", image, mCurrentTexture, error);
|
||||
err = UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// We destroy the EGLImageKHR here because the current buffer may no
|
||||
// longer be associated with one of the buffer slots, so we have
|
||||
// nowhere to to store it. If the buffer is still associated with a
|
||||
// slot then another EGLImageKHR will be created next time that buffer
|
||||
// gets acquired in updateTexImage.
|
||||
eglDestroyImageKHR(dpy, image);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
status_t SurfaceTexture::syncForReleaseLocked(EGLDisplay dpy) {
|
||||
ST_LOGV("syncForReleaseLocked");
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ LOCAL_SRC_FILES:= \
|
|||
GLExtensions.cpp \
|
||||
MessageQueue.cpp \
|
||||
SurfaceFlinger.cpp \
|
||||
SurfaceFlingerConsumer.cpp \
|
||||
SurfaceTextureLayer.cpp \
|
||||
Transform.cpp \
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ void Layer::onLayerDisplayed(const sp<const DisplayDevice>& hw,
|
|||
HWComposer::HWCLayerInterface* layer) {
|
||||
LayerBaseClient::onLayerDisplayed(hw, layer);
|
||||
if (layer) {
|
||||
mSurfaceTexture->setReleaseFence(layer->getAndResetReleaseFenceFd());
|
||||
mSurfaceFlingerConsumer->setReleaseFence(layer->getAndResetReleaseFenceFd());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,20 +81,20 @@ void Layer::onFirstRef()
|
|||
{
|
||||
LayerBaseClient::onFirstRef();
|
||||
|
||||
// Creates a custom BufferQueue for SurfaceTexture to use
|
||||
// Creates a custom BufferQueue for SurfaceFlingerConsumer to use
|
||||
sp<BufferQueue> bq = new SurfaceTextureLayer();
|
||||
mSurfaceTexture = new SurfaceTexture(mTextureName, true,
|
||||
mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(mTextureName, true,
|
||||
GL_TEXTURE_EXTERNAL_OES, false, bq);
|
||||
|
||||
mSurfaceTexture->setConsumerUsageBits(getEffectiveUsage(0));
|
||||
mSurfaceTexture->setFrameAvailableListener(this);
|
||||
mSurfaceTexture->setSynchronousMode(true);
|
||||
mSurfaceFlingerConsumer->setConsumerUsageBits(getEffectiveUsage(0));
|
||||
mSurfaceFlingerConsumer->setFrameAvailableListener(this);
|
||||
mSurfaceFlingerConsumer->setSynchronousMode(true);
|
||||
|
||||
#ifdef TARGET_DISABLE_TRIPLE_BUFFERING
|
||||
#warning "disabling triple buffering"
|
||||
mSurfaceTexture->setDefaultMaxBufferCount(2);
|
||||
mSurfaceFlingerConsumer->setDefaultMaxBufferCount(2);
|
||||
#else
|
||||
mSurfaceTexture->setDefaultMaxBufferCount(3);
|
||||
mSurfaceFlingerConsumer->setDefaultMaxBufferCount(3);
|
||||
#endif
|
||||
|
||||
const sp<const DisplayDevice> hw(mFlinger->getDefaultDisplayDevice());
|
||||
|
@ -115,12 +115,12 @@ void Layer::onFrameAvailable() {
|
|||
// in the purgatory list
|
||||
void Layer::onRemoved()
|
||||
{
|
||||
mSurfaceTexture->abandon();
|
||||
mSurfaceFlingerConsumer->abandon();
|
||||
}
|
||||
|
||||
void Layer::setName(const String8& name) {
|
||||
LayerBase::setName(name);
|
||||
mSurfaceTexture->setName(name);
|
||||
mSurfaceFlingerConsumer->setName(name);
|
||||
}
|
||||
|
||||
sp<ISurface> Layer::createSurface()
|
||||
|
@ -131,7 +131,7 @@ sp<ISurface> Layer::createSurface()
|
|||
sp<ISurfaceTexture> res;
|
||||
sp<const Layer> that( mOwner.promote() );
|
||||
if (that != NULL) {
|
||||
res = that->mSurfaceTexture->getBufferQueue();
|
||||
res = that->mSurfaceFlingerConsumer->getBufferQueue();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ sp<ISurface> Layer::createSurface()
|
|||
|
||||
wp<IBinder> Layer::getSurfaceTextureBinder() const
|
||||
{
|
||||
return mSurfaceTexture->getBufferQueue()->asBinder();
|
||||
return mSurfaceFlingerConsumer->getBufferQueue()->asBinder();
|
||||
}
|
||||
|
||||
status_t Layer::setBuffers( uint32_t w, uint32_t h,
|
||||
|
@ -177,15 +177,15 @@ status_t Layer::setBuffers( uint32_t w, uint32_t h,
|
|||
mOpaqueLayer = (flags & ISurfaceComposerClient::eOpaque);
|
||||
mCurrentOpacity = getOpacityForFormat(format);
|
||||
|
||||
mSurfaceTexture->setDefaultBufferSize(w, h);
|
||||
mSurfaceTexture->setDefaultBufferFormat(format);
|
||||
mSurfaceTexture->setConsumerUsageBits(getEffectiveUsage(0));
|
||||
mSurfaceFlingerConsumer->setDefaultBufferSize(w, h);
|
||||
mSurfaceFlingerConsumer->setDefaultBufferFormat(format);
|
||||
mSurfaceFlingerConsumer->setConsumerUsageBits(getEffectiveUsage(0));
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
Rect Layer::computeBufferCrop() const {
|
||||
// Start with the SurfaceTexture's buffer crop...
|
||||
// Start with the SurfaceFlingerConsumer's buffer crop...
|
||||
Rect crop;
|
||||
if (!mCurrentCrop.isEmpty()) {
|
||||
crop = mCurrentCrop;
|
||||
|
@ -202,7 +202,7 @@ Rect Layer::computeBufferCrop() const {
|
|||
if (!s.active.crop.isEmpty()) {
|
||||
// Transform the window crop to match the buffer coordinate system,
|
||||
// which means using the inverse of the current transform set on the
|
||||
// SurfaceTexture.
|
||||
// SurfaceFlingerConsumer.
|
||||
uint32_t invTransform = mCurrentTransform;
|
||||
int winWidth = s.active.w;
|
||||
int winHeight = s.active.h;
|
||||
|
@ -284,7 +284,7 @@ void Layer::setAcquireFence(const sp<const DisplayDevice>& hw,
|
|||
// acquire fence the first time a new buffer is acquired on EACH display.
|
||||
|
||||
if (layer.getCompositionType() == HWC_OVERLAY) {
|
||||
sp<Fence> fence = mSurfaceTexture->getCurrentFence();
|
||||
sp<Fence> fence = mSurfaceFlingerConsumer->getCurrentFence();
|
||||
if (fence.get()) {
|
||||
fenceFd = fence->dup();
|
||||
if (fenceFd == -1) {
|
||||
|
@ -327,7 +327,15 @@ void Layer::onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const
|
|||
return;
|
||||
}
|
||||
|
||||
status_t err = mSurfaceTexture->doGLFenceWait();
|
||||
// Bind the current buffer to the GL texture.
|
||||
status_t err = mSurfaceFlingerConsumer->bindTextureImage();
|
||||
if (err != NO_ERROR) {
|
||||
ALOGW("Layer::onDraw: bindTextureImage failed");
|
||||
// keep going
|
||||
}
|
||||
|
||||
// Wait for the buffer to be ready for us to draw into.
|
||||
err = mSurfaceFlingerConsumer->doGLFenceWait();
|
||||
if (err != OK) {
|
||||
ALOGE("onDraw: failed waiting for fence: %d", err);
|
||||
// Go ahead and draw the buffer anyway; no matter what we do the screen
|
||||
|
@ -342,8 +350,8 @@ void Layer::onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const
|
|||
|
||||
// Query the texture matrix given our current filtering mode.
|
||||
float textureMatrix[16];
|
||||
mSurfaceTexture->setFilteringEnabled(useFiltering);
|
||||
mSurfaceTexture->getTransformMatrix(textureMatrix);
|
||||
mSurfaceFlingerConsumer->setFilteringEnabled(useFiltering);
|
||||
mSurfaceFlingerConsumer->getTransformMatrix(textureMatrix);
|
||||
|
||||
// Set things up for texturing.
|
||||
glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureName);
|
||||
|
@ -462,7 +470,7 @@ uint32_t Layer::doTransaction(uint32_t flags)
|
|||
|
||||
// record the new size, form this point on, when the client request
|
||||
// a buffer, it'll get the new size.
|
||||
mSurfaceTexture->setDefaultBufferSize(
|
||||
mSurfaceFlingerConsumer->setDefaultBufferSize(
|
||||
temp.requested.w, temp.requested.h);
|
||||
}
|
||||
|
||||
|
@ -507,13 +515,10 @@ bool Layer::onPreComposition() {
|
|||
|
||||
void Layer::onPostComposition() {
|
||||
if (mFrameLatencyNeeded) {
|
||||
nsecs_t desiredPresentTime = mSurfaceTexture->getTimestamp();
|
||||
nsecs_t desiredPresentTime = mSurfaceFlingerConsumer->getTimestamp();
|
||||
mFrameTracker.setDesiredPresentTime(desiredPresentTime);
|
||||
|
||||
sp<Fence> frameReadyFence = mSurfaceTexture->getCurrentFence();
|
||||
// XXX: Temporarily don't use the fence from the SurfaceTexture to
|
||||
// work around a driver bug.
|
||||
frameReadyFence.clear();
|
||||
sp<Fence> frameReadyFence = mSurfaceFlingerConsumer->getCurrentFence();
|
||||
if (frameReadyFence != NULL) {
|
||||
mFrameTracker.setFrameReadyFence(frameReadyFence);
|
||||
} else {
|
||||
|
@ -570,7 +575,7 @@ Region Layer::latchBuffer(bool& recomputeVisibleRegions)
|
|||
mFlinger->signalLayerUpdate();
|
||||
}
|
||||
|
||||
struct Reject : public SurfaceTexture::BufferRejecter {
|
||||
struct Reject : public SurfaceFlingerConsumer::BufferRejecter {
|
||||
Layer::State& front;
|
||||
Layer::State& current;
|
||||
bool& recomputeVisibleRegions;
|
||||
|
@ -655,14 +660,14 @@ Region Layer::latchBuffer(bool& recomputeVisibleRegions)
|
|||
|
||||
Reject r(mDrawingState, currentState(), recomputeVisibleRegions);
|
||||
|
||||
if (mSurfaceTexture->updateTexImage(&r, true) < NO_ERROR) {
|
||||
if (mSurfaceFlingerConsumer->updateTexImage(&r) != NO_ERROR) {
|
||||
// something happened!
|
||||
recomputeVisibleRegions = true;
|
||||
return outDirtyRegion;
|
||||
}
|
||||
|
||||
// update the active buffer
|
||||
mActiveBuffer = mSurfaceTexture->getCurrentBuffer();
|
||||
mActiveBuffer = mSurfaceFlingerConsumer->getCurrentBuffer();
|
||||
if (mActiveBuffer == NULL) {
|
||||
// this can only happen if the very first buffer was rejected.
|
||||
return outDirtyRegion;
|
||||
|
@ -676,9 +681,9 @@ Region Layer::latchBuffer(bool& recomputeVisibleRegions)
|
|||
recomputeVisibleRegions = true;
|
||||
}
|
||||
|
||||
Rect crop(mSurfaceTexture->getCurrentCrop());
|
||||
const uint32_t transform(mSurfaceTexture->getCurrentTransform());
|
||||
const uint32_t scalingMode(mSurfaceTexture->getCurrentScalingMode());
|
||||
Rect crop(mSurfaceFlingerConsumer->getCurrentCrop());
|
||||
const uint32_t transform(mSurfaceFlingerConsumer->getCurrentTransform());
|
||||
const uint32_t scalingMode(mSurfaceFlingerConsumer->getCurrentScalingMode());
|
||||
if ((crop != mCurrentCrop) ||
|
||||
(transform != mCurrentTransform) ||
|
||||
(scalingMode != mCurrentScalingMode))
|
||||
|
@ -737,8 +742,8 @@ void Layer::dump(String8& result, char* buffer, size_t SIZE) const
|
|||
|
||||
result.append(buffer);
|
||||
|
||||
if (mSurfaceTexture != 0) {
|
||||
mSurfaceTexture->dump(result, " ", buffer, SIZE);
|
||||
if (mSurfaceFlingerConsumer != 0) {
|
||||
mSurfaceFlingerConsumer->dump(result, " ", buffer, SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -780,7 +785,7 @@ void Layer::updateTransformHint(const sp<const DisplayDevice>& hw) const {
|
|||
orientation = 0;
|
||||
}
|
||||
}
|
||||
mSurfaceTexture->setTransformHint(orientation);
|
||||
mSurfaceFlingerConsumer->setTransformHint(orientation);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <gui/SurfaceTexture.h>
|
||||
|
||||
#include <utils/Timers.h>
|
||||
|
||||
#include <ui/GraphicBuffer.h>
|
||||
|
@ -34,6 +32,7 @@
|
|||
#include <GLES/gl.h>
|
||||
#include <GLES/glext.h>
|
||||
|
||||
#include "SurfaceFlingerConsumer.h"
|
||||
#include "FrameTracker.h"
|
||||
#include "LayerBase.h"
|
||||
#include "SurfaceTextureLayer.h"
|
||||
|
@ -49,7 +48,7 @@ class GLExtensions;
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
class Layer : public LayerBaseClient,
|
||||
public SurfaceTexture::FrameAvailableListener
|
||||
public SurfaceFlingerConsumer::FrameAvailableListener
|
||||
{
|
||||
public:
|
||||
Layer(SurfaceFlinger* flinger, const sp<Client>& client);
|
||||
|
@ -92,7 +91,7 @@ public:
|
|||
// only for debugging
|
||||
inline const sp<GraphicBuffer>& getActiveBuffer() const { return mActiveBuffer; }
|
||||
|
||||
// Updates the transform hint in our SurfaceTexture to match
|
||||
// Updates the transform hint in our SurfaceFlingerConsumer to match
|
||||
// the current orientation of the display device.
|
||||
virtual void updateTransformHint(const sp<const DisplayDevice>& hw) const;
|
||||
|
||||
|
@ -110,13 +109,13 @@ private:
|
|||
Rect computeBufferCrop() const;
|
||||
static bool getOpacityForFormat(uint32_t format);
|
||||
|
||||
// Interface implementation for SurfaceTexture::FrameAvailableListener
|
||||
// Interface implementation for SurfaceFlingerConsumer::FrameAvailableListener
|
||||
virtual void onFrameAvailable();
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// constants
|
||||
sp<SurfaceTexture> mSurfaceTexture;
|
||||
sp<SurfaceFlingerConsumer> mSurfaceFlingerConsumer;
|
||||
GLuint mTextureName;
|
||||
|
||||
// thread-safe
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (C) 2012 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 ATRACE_TAG ATRACE_TAG_GRAPHICS
|
||||
|
||||
#include "SurfaceFlingerConsumer.h"
|
||||
|
||||
#include <utils/Trace.h>
|
||||
#include <utils/Errors.h>
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
status_t SurfaceFlingerConsumer::updateTexImage(BufferRejecter* rejecter)
|
||||
{
|
||||
ATRACE_CALL();
|
||||
ALOGV("updateTexImage");
|
||||
Mutex::Autolock lock(mMutex);
|
||||
|
||||
if (mAbandoned) {
|
||||
ALOGE("updateTexImage: SurfaceTexture is abandoned!");
|
||||
return NO_INIT;
|
||||
}
|
||||
|
||||
// Make sure the EGL state is the same as in previous calls.
|
||||
status_t err = checkAndUpdateEglStateLocked();
|
||||
if (err != NO_ERROR) {
|
||||
return err;
|
||||
}
|
||||
|
||||
BufferQueue::BufferItem item;
|
||||
|
||||
// Acquire the next buffer.
|
||||
// In asynchronous mode the list is guaranteed to be one buffer
|
||||
// deep, while in synchronous mode we use the oldest buffer.
|
||||
err = acquireBufferLocked(&item);
|
||||
if (err != NO_ERROR) {
|
||||
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
|
||||
// This variant of updateTexImage does not guarantee that the
|
||||
// texture is bound, so no need to call glBindTexture.
|
||||
err = NO_ERROR;
|
||||
} else {
|
||||
ALOGE("updateTexImage: acquire failed: %s (%d)",
|
||||
strerror(-err), err);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
// We call the rejecter here, in case the caller has a reason to
|
||||
// not accept this buffer. This is used by SurfaceFlinger to
|
||||
// reject buffers which have the wrong size
|
||||
int buf = item.mBuf;
|
||||
if (rejecter && rejecter->reject(mSlots[buf].mGraphicBuffer, item)) {
|
||||
releaseBufferLocked(buf, EGL_NO_SYNC_KHR);
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// Release the previous buffer.
|
||||
err = releaseAndUpdateLocked(item);
|
||||
if (err != NO_ERROR) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// Bind the new buffer to the GL texture.
|
||||
// TODO: skip this on devices that support explicit sync
|
||||
// (glEGLImageTargetTexture2DOES provides required implicit sync;
|
||||
// without this we get wedged on older devices, but newer devices
|
||||
// don't need it.)
|
||||
return bindTextureImage();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
}; // namespace android
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2012 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.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_SURFACEFLINGERCONSUMER_H
|
||||
#define ANDROID_SURFACEFLINGERCONSUMER_H
|
||||
|
||||
#include <gui/SurfaceTexture.h>
|
||||
|
||||
namespace android {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* This is a thin wrapper around SurfaceTexture.
|
||||
*/
|
||||
class SurfaceFlingerConsumer : public SurfaceTexture {
|
||||
public:
|
||||
SurfaceFlingerConsumer(GLuint tex, bool allowSynchronousMode = true,
|
||||
GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true,
|
||||
const sp<BufferQueue> &bufferQueue = 0)
|
||||
: SurfaceTexture(tex, allowSynchronousMode, texTarget, useFenceSync,
|
||||
bufferQueue)
|
||||
{}
|
||||
|
||||
class BufferRejecter {
|
||||
friend class SurfaceFlingerConsumer;
|
||||
virtual bool reject(const sp<GraphicBuffer>& buf,
|
||||
const BufferQueue::BufferItem& item) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~BufferRejecter() { }
|
||||
};
|
||||
|
||||
// This version of updateTexImage() takes a functor that may be used to
|
||||
// reject the newly acquired buffer. Unlike the SurfaceTexture version,
|
||||
// this does not guarantee that the buffer has been bound to the GL
|
||||
// texture.
|
||||
status_t updateTexImage(BufferRejecter* rejecter);
|
||||
|
||||
// Pass-through to SurfaceTexture implementation.
|
||||
status_t bindTextureImage() { return SurfaceTexture::bindTextureImage(); }
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
}; // namespace android
|
||||
|
||||
#endif // ANDROID_SURFACEFLINGERCONSUMER_H
|
Loading…
Reference in New Issue