SurfaceTexture: Fully refactored from BufferQueue

SurfaceTexture and BufferQueue are separate objects.

Change-Id: I230bc0ae6f78d0f9b2b5df902f40ab443ed5a055
This commit is contained in:
Daniel Lam 2012-02-23 14:35:13 -08:00
parent fddc28d871
commit b267579ba8
11 changed files with 204 additions and 112 deletions

View File

@ -203,6 +203,16 @@ public:
// when a new frame becomes available.
void setFrameAvailableListener(const sp<FrameAvailableListener>& listener);
// setDefaultBufferFormat allows the BufferQueue to create
// GraphicBuffers of a defaultFormat if no format is specified
// in dequeueBuffer
status_t setDefaultBufferFormat(uint32_t defaultFormat);
// setConsumerUsageBits will turn on additional usage bits for dequeueBuffer
status_t setConsumerUsageBits(uint32_t usage);
// setTransformHint bakes in rotation to buffers so overlays can be used
status_t setTransformHint(uint32_t hint);
private:
// freeBufferLocked frees the resources (both GraphicBuffer and EGLImage)
@ -417,7 +427,19 @@ private:
// with the surface Texture.
uint64_t mFrameCounter;
// mBufferHasBeenQueued is true once a buffer has been queued. It is reset
// by changing the buffer count.
bool mBufferHasBeenQueued;
// mDefaultBufferFormat can be set so it will override
// the buffer format when it isn't specified in dequeueBuffer
uint32_t mDefaultBufferFormat;
// mConsumerUsageBits contains flags the consumer wants for GraphicBuffers
uint32_t mConsumerUsageBits;
// mTransformHint is used to optimize for screen rotations
uint32_t mTransformHint;
};
// ----------------------------------------------------------------------------

View File

@ -39,8 +39,12 @@ namespace android {
class String8;
class SurfaceTexture : public BufferQueue {
class SurfaceTexture : public virtual RefBase {
public:
// This typedef allows external code to continue referencing
// SurfaceTexture::FrameAvailableListener during refactoring
typedef BufferQueue::FrameAvailableListener FrameAvailableListener;
// SurfaceTexture constructs a new SurfaceTexture object. tex indicates the
// name of the OpenGL ES texture to which images are to be streamed. This
@ -49,14 +53,15 @@ public:
// enabled. texTarget specifies the OpenGL ES texture target to which the
// texture will be bound in updateTexImage. useFenceSync specifies whether
// fences should be used to synchronize access to buffers if that behavior
// is enabled at compile-time.
// is enabled at compile-time. A custom bufferQueue can be specified
// if behavior for queue/dequeue/connect etc needs to be customized.
// Otherwise a default BufferQueue will be created and used.
SurfaceTexture(GLuint tex, bool allowSynchronousMode = true,
GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true);
GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true,
const sp<BufferQueue> &bufferQueue = 0);
virtual ~SurfaceTexture();
// updateTexImage sets the image contents of the target texture to that of
// the most recently queued buffer.
//
@ -152,6 +157,18 @@ public:
// log messages.
void setName(const String8& name);
// These functions call the corresponding BufferQueue implementation
// so the refactoring can proceed smoothly
status_t setDefaultBufferFormat(uint32_t defaultFormat);
status_t setConsumerUsageBits(uint32_t usage);
status_t setTransformHint(uint32_t hint);
virtual status_t setSynchronousMode(bool enabled);
virtual status_t setBufferCount(int bufferCount);
virtual status_t connect(int api,
uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform);
sp<BufferQueue> getBufferQueue() const;
// dump our state in a String
virtual void dump(String8& result) const;
virtual void dump(String8& result, const char* prefix, char* buffer, size_t SIZE) const;
@ -241,7 +258,7 @@ private:
EGLSyncKHR mFence;
};
EGLSlot mEGLSlots[NUM_BUFFER_SLOTS];
EGLSlot mEGLSlots[BufferQueue::NUM_BUFFER_SLOTS];
// mAbandoned indicates that the BufferQueue will no longer be used to
// consume images buffers pushed to it using the ISurfaceTexture interface.
@ -267,6 +284,10 @@ private:
// reset mCurrentTexture to INVALID_BUFFER_SLOT.
int mCurrentTexture;
// The SurfaceTexture has-a BufferQueue and is responsible for creating this object
// if none is supplied
sp<BufferQueue> mBufferQueue;
};
// ----------------------------------------------------------------------------

View File

@ -19,6 +19,7 @@
#include <gui/ISurfaceTexture.h>
#include <gui/SurfaceTexture.h>
#include <gui/BufferQueue.h>
#include <ui/ANativeObjectBase.h>
#include <ui/Region.h>
@ -34,8 +35,15 @@ class SurfaceTextureClient
: public ANativeObjectBase<ANativeWindow, SurfaceTextureClient, RefBase>
{
public:
SurfaceTextureClient(const sp<ISurfaceTexture>& surfaceTexture);
// SurfaceTextureClient is overloaded to assist in refactoring ST and BQ.
// SurfaceTexture is no longer an ISurfaceTexture, so client code
// calling the original constructor will fail. Thus this convenience method
// passes in the surfaceTexture's bufferQueue to the init method.
SurfaceTextureClient(const sp<SurfaceTexture>& surfaceTexture);
sp<ISurfaceTexture> getISurfaceTexture() const;
protected:
@ -94,8 +102,8 @@ protected:
virtual int lock(ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds);
virtual int unlockAndPost();
enum { MIN_UNDEQUEUED_BUFFERS = SurfaceTexture::MIN_UNDEQUEUED_BUFFERS };
enum { NUM_BUFFER_SLOTS = SurfaceTexture::NUM_BUFFER_SLOTS };
enum { MIN_UNDEQUEUED_BUFFERS = BufferQueue::MIN_UNDEQUEUED_BUFFERS };
enum { NUM_BUFFER_SLOTS = BufferQueue::NUM_BUFFER_SLOTS };
enum { DEFAULT_FORMAT = PIXEL_FORMAT_RGBA_8888 };
private:

View File

@ -83,7 +83,10 @@ BufferQueue::BufferQueue( bool allowSynchronousMode ) :
mConnectedApi(NO_CONNECTED_API),
mAbandoned(false),
mFrameCounter(0),
mBufferHasBeenQueued(false)
mBufferHasBeenQueued(false),
mDefaultBufferFormat(0),
mConsumerUsageBits(0),
mTransformHint(0)
{
// Choose a name using the PID and a process-unique ID.
mConsumerName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
@ -128,6 +131,7 @@ status_t BufferQueue::setBufferCountServerLocked(int bufferCount) {
// dequeueBuffer.
mServerBufferCount = bufferCount;
mDequeueCondition.broadcast();
}
return OK;
}
@ -149,6 +153,24 @@ void BufferQueue::setFrameAvailableListener(
mFrameAvailableListener = listener;
}
status_t BufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) {
Mutex::Autolock lock(mMutex);
mDefaultBufferFormat = defaultFormat;
return OK;
}
status_t BufferQueue::setConsumerUsageBits(uint32_t usage) {
Mutex::Autolock lock(mMutex);
mConsumerUsageBits = usage;
return OK;
}
status_t BufferQueue::setTransformHint(uint32_t hint) {
Mutex::Autolock lock(mMutex);
mTransformHint = hint;
return OK;
}
status_t BufferQueue::setBufferCount(int bufferCount) {
ST_LOGV("setBufferCount: count=%d", bufferCount);
Mutex::Autolock lock(mMutex);
@ -263,6 +285,12 @@ status_t BufferQueue::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
{ // Scope for the lock
Mutex::Autolock lock(mMutex);
if (format == 0) {
format = mDefaultBufferFormat;
}
// turn on usage bits the consumer requested
usage |= mConsumerUsageBits;
int found = -1;
int foundSync = -1;
int dequeuedCount = 0;
@ -563,7 +591,7 @@ status_t BufferQueue::queueBuffer(int buf, int64_t timestamp,
*outWidth = mDefaultWidth;
*outHeight = mDefaultHeight;
*outTransform = 0;
*outTransform = mTransformHint;
ATRACE_INT(mConsumerName.string(), mQueue.size());
} // scope for the lock
@ -846,7 +874,8 @@ status_t BufferQueue::acquire(BufferItem *buffer) {
ATRACE_INT(mConsumerName.string(), mQueue.size());
}
else {
return -EINVAL; //should be a better return code
// should be a better return code?
return -EINVAL;
}
return OK;
@ -880,9 +909,10 @@ status_t BufferQueue::releaseBuffer(int buf, EGLDisplay display,
status_t BufferQueue::consumerDisconnect() {
Mutex::Autolock lock(mMutex);
// Once the SurfaceTexture disconnects, the BufferQueue
// is considered abandoned
mAbandoned = true;
mQueue.clear();
freeAllBuffersLocked();
mDequeueCondition.broadcast();
return OK;

View File

@ -105,8 +105,7 @@ static int32_t createProcessUniqueId() {
}
SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode,
GLenum texTarget, bool useFenceSync) :
BufferQueue(allowSynchronousMode),
GLenum texTarget, bool useFenceSync, const sp<BufferQueue> &bufferQueue) :
mCurrentTransform(0),
mCurrentTimestamp(0),
mTexName(tex),
@ -121,27 +120,37 @@ SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode,
{
// Choose a name using the PID and a process-unique ID.
mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
BufferQueue::setConsumerName(mName);
ST_LOGV("SurfaceTexture");
if (bufferQueue == 0) {
ST_LOGV("Creating a new BufferQueue");
mBufferQueue = new BufferQueue(allowSynchronousMode);
}
else {
mBufferQueue = bufferQueue;
}
mBufferQueue->setConsumerName(mName);
memcpy(mCurrentTransformMatrix, mtxIdentity,
sizeof(mCurrentTransformMatrix));
}
SurfaceTexture::~SurfaceTexture() {
ST_LOGV("~SurfaceTexture");
abandon();
}
status_t SurfaceTexture::setBufferCountServer(int bufferCount) {
Mutex::Autolock lock(mMutex);
return BufferQueue::setBufferCountServer(bufferCount);
return mBufferQueue->setBufferCountServer(bufferCount);
}
status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h)
{
return BufferQueue::setDefaultBufferSize(w, h);
Mutex::Autolock lock(mMutex);
return mBufferQueue->setDefaultBufferSize(w, h);
}
status_t SurfaceTexture::updateTexImage() {
@ -154,11 +163,11 @@ status_t SurfaceTexture::updateTexImage() {
return NO_INIT;
}
BufferItem item;
BufferQueue::BufferItem item;
// In asynchronous mode the list is guaranteed to be one buffer
// deep, while in synchronous mode we use the oldest buffer.
if (acquire(&item) == NO_ERROR) {
if (mBufferQueue->acquire(&item) == NO_ERROR) {
int buf = item.mBuf;
// This buffer was newly allocated, so we need to clean up on our side
if (item.mGraphicBuffer != NULL) {
@ -205,19 +214,19 @@ status_t SurfaceTexture::updateTexImage() {
failed = true;
}
if (failed) {
releaseBuffer(buf, mEGLSlots[buf].mEglDisplay,
mBufferQueue->releaseBuffer(buf, mEGLSlots[buf].mEglDisplay,
mEGLSlots[buf].mFence);
return -EINVAL;
}
if (mCurrentTexture != INVALID_BUFFER_SLOT) {
if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
if (mUseFenceSync) {
EGLSyncKHR fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR,
NULL);
if (fence == EGL_NO_SYNC_KHR) {
ALOGE("updateTexImage: error creating fence: %#x",
eglGetError());
releaseBuffer(buf, mEGLSlots[buf].mEglDisplay,
mBufferQueue->releaseBuffer(buf, mEGLSlots[buf].mEglDisplay,
mEGLSlots[buf].mFence);
return -EINVAL;
}
@ -232,7 +241,7 @@ status_t SurfaceTexture::updateTexImage() {
buf, item.mGraphicBuffer != NULL ? item.mGraphicBuffer->handle : 0);
// release old buffer
releaseBuffer(mCurrentTexture,
mBufferQueue->releaseBuffer(mCurrentTexture,
mEGLSlots[mCurrentTexture].mEglDisplay,
mEGLSlots[mCurrentTexture].mFence);
@ -385,7 +394,7 @@ void SurfaceTexture::setFrameAvailableListener(
const sp<FrameAvailableListener>& listener) {
ST_LOGV("setFrameAvailableListener");
Mutex::Autolock lock(mMutex);
BufferQueue::setFrameAvailableListener(listener);
mBufferQueue->setFrameAvailableListener(listener);
}
EGLImageKHR SurfaceTexture::createImage(EGLDisplay dpy,
@ -426,7 +435,7 @@ uint32_t SurfaceTexture::getCurrentScalingMode() const {
bool SurfaceTexture::isSynchronousMode() const {
Mutex::Autolock lock(mMutex);
return BufferQueue::isSynchronousMode();
return mBufferQueue->isSynchronousMode();
}
void SurfaceTexture::abandon() {
@ -435,7 +444,7 @@ void SurfaceTexture::abandon() {
mCurrentTextureBuf.clear();
// destroy all egl buffers
for (int i =0; i < NUM_BUFFER_SLOTS; i++) {
for (int i =0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
mEGLSlots[i].mGraphicBuffer = 0;
if (mEGLSlots[i].mEglImage != EGL_NO_IMAGE_KHR) {
eglDestroyImageKHR(mEGLSlots[i].mEglDisplay,
@ -446,13 +455,54 @@ void SurfaceTexture::abandon() {
}
// disconnect from the BufferQueue
BufferQueue::consumerDisconnect();
mBufferQueue->consumerDisconnect();
}
void SurfaceTexture::setName(const String8& name) {
Mutex::Autolock _l(mMutex);
mName = name;
BufferQueue::setConsumerName(name);
mBufferQueue->setConsumerName(name);
}
status_t SurfaceTexture::setDefaultBufferFormat(uint32_t defaultFormat) {
Mutex::Autolock lock(mMutex);
return mBufferQueue->setDefaultBufferFormat(defaultFormat);
}
status_t SurfaceTexture::setConsumerUsageBits(uint32_t usage) {
Mutex::Autolock lock(mMutex);
return mBufferQueue->setConsumerUsageBits(usage);
}
status_t SurfaceTexture::setTransformHint(uint32_t hint) {
Mutex::Autolock lock(mMutex);
return mBufferQueue->setTransformHint(hint);
}
// Used for refactoring BufferQueue from SurfaceTexture
// Should not be in final interface once users of SurfaceTexture are clean up.
status_t SurfaceTexture::setSynchronousMode(bool enabled) {
Mutex::Autolock lock(mMutex);
return mBufferQueue->setSynchronousMode(enabled);
}
// Used for refactoring, should not be in final interface
sp<BufferQueue> SurfaceTexture::getBufferQueue() const {
Mutex::Autolock lock(mMutex);
return mBufferQueue;
}
// Used for refactoring, should not be in final interface
status_t SurfaceTexture::setBufferCount(int bufferCount) {
Mutex::Autolock lock(mMutex);
return mBufferQueue->setBufferCount(bufferCount);
}
// Used for refactoring, should not be in final interface
status_t SurfaceTexture::connect(int api,
uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) {
Mutex::Autolock lock(mMutex);
return mBufferQueue->connect(api, outWidth, outHeight, outTransform);
}
void SurfaceTexture::dump(String8& result) const
@ -477,7 +527,7 @@ void SurfaceTexture::dump(String8& result, const char* prefix,
result.append(buffer);
BufferQueue::dump(result, prefix, buffer, SIZE);
mBufferQueue->dump(result, prefix, buffer, SIZE);
}
static void mtxMul(float out[16], const float a[16], const float b[16]) {

View File

@ -36,6 +36,14 @@ SurfaceTextureClient::SurfaceTextureClient(
SurfaceTextureClient::setISurfaceTexture(surfaceTexture);
}
// see SurfaceTextureClient.h
SurfaceTextureClient::SurfaceTextureClient(const
sp<SurfaceTexture>& surfaceTexture)
{
SurfaceTextureClient::init();
SurfaceTextureClient::setISurfaceTexture(surfaceTexture->getBufferQueue());
}
SurfaceTextureClient::SurfaceTextureClient() {
SurfaceTextureClient::init();
}

View File

@ -97,9 +97,19 @@ void Layer::onFirstRef()
}
}
};
mSurfaceTexture = new SurfaceTextureLayer(mTextureName, this);
// Creates a custom BufferQueue for SurfaceTexture to use
sp<BufferQueue> bq = new SurfaceTextureLayer();
mSurfaceTexture = new SurfaceTexture(mTextureName, true,
GL_TEXTURE_EXTERNAL_OES, false,bq);
mSurfaceTexture->setTransformHint(getTransformHint());
mSurfaceTexture->setConsumerUsageBits(getEffectiveUsage(0));
mSurfaceTexture->setFrameAvailableListener(new FrameQueuedListener(this));
mSurfaceTexture->setSynchronousMode(true);
#ifdef USE_TRIPLE_BUFFERING
#warning "using triple buffering"
mSurfaceTexture->setBufferCountServer(3);
@ -131,6 +141,14 @@ void Layer::setName(const String8& name) {
mSurfaceTexture->setName(name);
}
void Layer::validateVisibility(const Transform& globalTransform) {
LayerBase::validateVisibility(globalTransform);
// This optimization allows the SurfaceTexture to bake in
// the rotation so hardware overlays can be used
mSurfaceTexture->setTransformHint(getTransformHint());
}
sp<ISurface> Layer::createSurface()
{
class BSurface : public BnSurface, public LayerCleaner {
@ -139,7 +157,7 @@ sp<ISurface> Layer::createSurface()
sp<ISurfaceTexture> res;
sp<const Layer> that( mOwner.promote() );
if (that != NULL) {
res = that->mSurfaceTexture;
res = that->mSurfaceTexture->getBufferQueue();
}
return res;
}
@ -154,7 +172,7 @@ sp<ISurface> Layer::createSurface()
wp<IBinder> Layer::getSurfaceTextureBinder() const
{
return mSurfaceTexture->asBinder();
return mSurfaceTexture->getBufferQueue()->asBinder();
}
status_t Layer::setBuffers( uint32_t w, uint32_t h,
@ -193,6 +211,7 @@ status_t Layer::setBuffers( uint32_t w, uint32_t h,
mSurfaceTexture->setDefaultBufferSize(w, h);
mSurfaceTexture->setDefaultBufferFormat(format);
mSurfaceTexture->setConsumerUsageBits(getEffectiveUsage(0));
// we use the red index
int displayRedSize = displayInfo.getSize(PixelFormatInfo::INDEX_RED);

View File

@ -77,6 +77,7 @@ public:
virtual void onRemoved();
virtual sp<Layer> getLayer() const { return const_cast<Layer*>(this); }
virtual void setName(const String8& name);
virtual void validateVisibility(const Transform& globalTransform);
// LayerBaseClient interface
virtual wp<IBinder> getSurfaceTextureBinder() const;
@ -105,7 +106,7 @@ private:
// -----------------------------------------------------------------------
// constants
sp<SurfaceTextureLayer> mSurfaceTexture;
sp<SurfaceTexture> mSurfaceTexture;
GLuint mTextureName;
// thread-safe

View File

@ -1399,6 +1399,7 @@ status_t SurfaceFlinger::removeSurface(const sp<Client>& client, SurfaceID sid)
status_t err = NAME_NOT_FOUND;
Mutex::Autolock _l(mStateLock);
sp<LayerBaseClient> layer = client->getLayerUser(sid);
if (layer != 0) {
err = purgatorizeLayer_l(layer);
if (err == NO_ERROR) {

View File

@ -27,72 +27,18 @@ namespace android {
// ---------------------------------------------------------------------------
SurfaceTextureLayer::SurfaceTextureLayer(GLuint tex, const sp<Layer>& layer)
: SurfaceTexture(tex, true, GL_TEXTURE_EXTERNAL_OES, false), mLayer(layer) {
SurfaceTextureLayer::SurfaceTextureLayer()
: BufferQueue(true) {
}
SurfaceTextureLayer::~SurfaceTextureLayer() {
}
status_t SurfaceTextureLayer::setDefaultBufferSize(uint32_t w, uint32_t h)
{
//ALOGD("%s, w=%u, h=%u", __PRETTY_FUNCTION__, w, h);
return SurfaceTexture::setDefaultBufferSize(w, h);
}
status_t SurfaceTextureLayer::setDefaultBufferFormat(uint32_t format)
{
mDefaultFormat = format;
return NO_ERROR;
}
status_t SurfaceTextureLayer::setBufferCount(int bufferCount) {
status_t res = SurfaceTexture::setBufferCount(bufferCount);
return res;
}
status_t SurfaceTextureLayer::queueBuffer(int buf, int64_t timestamp,
uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) {
status_t res = SurfaceTexture::queueBuffer(buf, timestamp,
outWidth, outHeight, outTransform);
sp<Layer> layer(mLayer.promote());
if (layer != NULL) {
*outTransform = layer->getTransformHint();
}
return res;
}
status_t SurfaceTextureLayer::dequeueBuffer(int *buf,
uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
status_t res(NO_INIT);
sp<Layer> layer(mLayer.promote());
if (layer != NULL) {
if (format == 0)
format = mDefaultFormat;
uint32_t effectiveUsage = layer->getEffectiveUsage(usage);
//ALOGD("%s, w=%u, h=%u, format=%u, usage=%08x, effectiveUsage=%08x",
// __PRETTY_FUNCTION__, w, h, format, usage, effectiveUsage);
res = SurfaceTexture::dequeueBuffer(buf, w, h, format, effectiveUsage);
}
return res;
}
status_t SurfaceTextureLayer::connect(int api,
uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) {
status_t err = SurfaceTexture::connect(api,
status_t err = BufferQueue::connect(api,
outWidth, outHeight, outTransform);
if (err == NO_ERROR) {
sp<Layer> layer(mLayer.promote());
if (layer != NULL) {
uint32_t orientation = layer->getOrientation();
if (orientation & Transform::ROT_INVALID) {
orientation = 0;
}
*outTransform = orientation;
}
switch(api) {
case NATIVE_WINDOW_API_CPU:
// SurfaceTextureClient supports only 2 buffers for CPU connections
@ -110,7 +56,7 @@ status_t SurfaceTextureLayer::connect(int api,
#endif
// fall through to set synchronous mode when not defaulting to
// async mode.
deafult:
default:
err = setSynchronousMode(true);
break;
}

View File

@ -22,35 +22,21 @@
#include <sys/types.h>
#include <utils/Errors.h>
#include <gui/SurfaceTexture.h>
#include <gui/BufferQueue.h>
namespace android {
// ---------------------------------------------------------------------------
class Layer;
class SurfaceTextureLayer : public SurfaceTexture
// SurfaceTextureLayer is now a BufferQueue since SurfaceTexture has been
// refactored
class SurfaceTextureLayer : public BufferQueue
{
wp<Layer> mLayer;
uint32_t mDefaultFormat;
public:
SurfaceTextureLayer(GLuint tex, const sp<Layer>& layer);
SurfaceTextureLayer();
~SurfaceTextureLayer();
status_t setDefaultBufferSize(uint32_t w, uint32_t h);
status_t setDefaultBufferFormat(uint32_t format);
public:
virtual status_t setBufferCount(int bufferCount);
protected:
virtual status_t queueBuffer(int buf, int64_t timestamp,
uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform);
virtual status_t dequeueBuffer(int *buf, uint32_t w, uint32_t h,
uint32_t format, uint32_t usage);
virtual status_t connect(int api,
uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform);
};