gecko-dev/gfx/gl/SharedSurface.cpp
Markus Stange 2770f087b3 Bug 1479145 - Handle arbitrary strides for WebGL-to-SharedSurface readback on platforms that support it. r=jgilbert
The only platforms that do not support GL_PACK_ROW_LENGTH are platforms with
GLES 2. So on those platforms, trying to read back into buffers whose stride is
not width * 4 will assert.
That's fine because we usually don't encounter buffers with such large strides
on GLES 2 platforms. The only platform that really needs to handle the large
strides is macOS, and it always supports GL_PACK_ROW_LENGTH.
On macOS, we often run into large strides on surfaces that we intend to upload
as textures at some point, because large stride alignments are required for
efficient upload performance on some drivers.

Bug 1540209 tracks fixing the general case.

Differential Revision: https://phabricator.services.mozilla.com/D25464

--HG--
extra : moz-landing-system : lando
2019-03-29 20:18:53 +00:00

556 lines
16 KiB
C++

/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 4; -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SharedSurface.h"
#include "../2d/2D.h"
#include "GLBlitHelper.h"
#include "GLContext.h"
#include "GLReadTexImageHelper.h"
#include "GLScreenBuffer.h"
#include "nsThreadUtils.h"
#include "ScopedGLHelpers.h"
#include "SharedSurfaceGL.h"
#include "mozilla/layers/CompositorTypes.h"
#include "mozilla/layers/TextureClientSharedSurface.h"
#include "mozilla/layers/TextureForwarder.h"
#include "mozilla/Unused.h"
#include "VRManagerChild.h"
namespace mozilla {
namespace gl {
/*static*/
void SharedSurface::ProdCopy(SharedSurface* src, SharedSurface* dest,
SurfaceFactory* factory) {
GLContext* gl = src->mGL;
// If `src` begins locked, it must end locked, though we may
// temporarily unlock it if we need to.
MOZ_ASSERT((src == gl->GetLockedSurface()) == src->IsLocked());
gl->MakeCurrent();
if (src->mAttachType == AttachmentType::Screen &&
dest->mAttachType == AttachmentType::Screen) {
// Here, we actually need to blit through a temp surface, so let's make one.
UniquePtr<SharedSurface_Basic> tempSurf;
tempSurf = SharedSurface_Basic::Create(gl, factory->mFormats, src->mSize,
factory->mCaps.alpha);
ProdCopy(src, tempSurf.get(), factory);
ProdCopy(tempSurf.get(), dest, factory);
return;
}
if (src->mAttachType == AttachmentType::Screen) {
SharedSurface* origLocked = gl->GetLockedSurface();
bool srcNeedsUnlock = false;
bool origNeedsRelock = false;
if (origLocked != src) {
if (origLocked) {
origLocked->UnlockProd();
origNeedsRelock = true;
}
src->LockProd();
srcNeedsUnlock = true;
}
if (dest->mAttachType == AttachmentType::GLTexture) {
GLuint destTex = dest->ProdTexture();
GLenum destTarget = dest->ProdTextureTarget();
const ScopedBindFramebuffer bindFB(gl, 0);
gl->BlitHelper()->BlitFramebufferToTexture(destTex, src->mSize,
dest->mSize, destTarget);
} else if (dest->mAttachType == AttachmentType::GLRenderbuffer) {
GLuint destRB = dest->ProdRenderbuffer();
ScopedFramebufferForRenderbuffer destWrapper(gl, destRB);
gl->BlitHelper()->BlitFramebufferToFramebuffer(0, destWrapper.FB(),
src->mSize, dest->mSize);
} else {
MOZ_CRASH("GFX: Unhandled dest->mAttachType 1.");
}
if (srcNeedsUnlock) src->UnlockProd();
if (origNeedsRelock) origLocked->LockProd();
return;
}
if (dest->mAttachType == AttachmentType::Screen) {
SharedSurface* origLocked = gl->GetLockedSurface();
bool destNeedsUnlock = false;
bool origNeedsRelock = false;
if (origLocked != dest) {
if (origLocked) {
origLocked->UnlockProd();
origNeedsRelock = true;
}
dest->LockProd();
destNeedsUnlock = true;
}
if (src->mAttachType == AttachmentType::GLTexture) {
GLuint srcTex = src->ProdTexture();
GLenum srcTarget = src->ProdTextureTarget();
const ScopedBindFramebuffer bindFB(gl, 0);
gl->BlitHelper()->BlitTextureToFramebuffer(srcTex, src->mSize,
dest->mSize, srcTarget);
} else if (src->mAttachType == AttachmentType::GLRenderbuffer) {
GLuint srcRB = src->ProdRenderbuffer();
ScopedFramebufferForRenderbuffer srcWrapper(gl, srcRB);
gl->BlitHelper()->BlitFramebufferToFramebuffer(srcWrapper.FB(), 0,
src->mSize, dest->mSize);
} else {
MOZ_CRASH("GFX: Unhandled src->mAttachType 2.");
}
if (destNeedsUnlock) dest->UnlockProd();
if (origNeedsRelock) origLocked->LockProd();
return;
}
// Alright, done with cases involving Screen types.
// Only {src,dest}x{texture,renderbuffer} left.
if (src->mAttachType == AttachmentType::GLTexture) {
GLuint srcTex = src->ProdTexture();
GLenum srcTarget = src->ProdTextureTarget();
if (dest->mAttachType == AttachmentType::GLTexture) {
GLuint destTex = dest->ProdTexture();
GLenum destTarget = dest->ProdTextureTarget();
gl->BlitHelper()->BlitTextureToTexture(
srcTex, destTex, src->mSize, dest->mSize, srcTarget, destTarget);
return;
}
if (dest->mAttachType == AttachmentType::GLRenderbuffer) {
GLuint destRB = dest->ProdRenderbuffer();
ScopedFramebufferForRenderbuffer destWrapper(gl, destRB);
const ScopedBindFramebuffer bindFB(gl, destWrapper.FB());
gl->BlitHelper()->BlitTextureToFramebuffer(srcTex, src->mSize,
dest->mSize, srcTarget);
return;
}
MOZ_CRASH("GFX: Unhandled dest->mAttachType 3.");
}
if (src->mAttachType == AttachmentType::GLRenderbuffer) {
GLuint srcRB = src->ProdRenderbuffer();
ScopedFramebufferForRenderbuffer srcWrapper(gl, srcRB);
if (dest->mAttachType == AttachmentType::GLTexture) {
GLuint destTex = dest->ProdTexture();
GLenum destTarget = dest->ProdTextureTarget();
const ScopedBindFramebuffer bindFB(gl, srcWrapper.FB());
gl->BlitHelper()->BlitFramebufferToTexture(destTex, src->mSize,
dest->mSize, destTarget);
return;
}
if (dest->mAttachType == AttachmentType::GLRenderbuffer) {
GLuint destRB = dest->ProdRenderbuffer();
ScopedFramebufferForRenderbuffer destWrapper(gl, destRB);
gl->BlitHelper()->BlitFramebufferToFramebuffer(
srcWrapper.FB(), destWrapper.FB(), src->mSize, dest->mSize);
return;
}
MOZ_CRASH("GFX: Unhandled dest->mAttachType 4.");
}
MOZ_CRASH("GFX: Unhandled src->mAttachType 5.");
}
////////////////////////////////////////////////////////////////////////
// SharedSurface
SharedSurface::SharedSurface(SharedSurfaceType type, AttachmentType attachType,
GLContext* gl, const gfx::IntSize& size,
bool hasAlpha, bool canRecycle)
: mType(type),
mAttachType(attachType),
mGL(gl),
mSize(size),
mHasAlpha(hasAlpha),
mCanRecycle(canRecycle),
mIsLocked(false),
mIsProducerAcquired(false) {}
SharedSurface::~SharedSurface() = default;
layers::TextureFlags SharedSurface::GetTextureFlags() const {
return layers::TextureFlags::NO_FLAGS;
}
void SharedSurface::LockProd() {
MOZ_ASSERT(!mIsLocked);
LockProdImpl();
mGL->LockSurface(this);
mIsLocked = true;
}
void SharedSurface::UnlockProd() {
if (!mIsLocked) return;
UnlockProdImpl();
mGL->UnlockSurface(this);
mIsLocked = false;
}
////////////////////////////////////////////////////////////////////////
// SurfaceFactory
static void ChooseBufferBits(const SurfaceCaps& caps,
SurfaceCaps* const out_drawCaps,
SurfaceCaps* const out_readCaps) {
MOZ_ASSERT(out_drawCaps);
MOZ_ASSERT(out_readCaps);
SurfaceCaps screenCaps;
screenCaps.color = caps.color;
screenCaps.alpha = caps.alpha;
screenCaps.bpp16 = caps.bpp16;
screenCaps.depth = caps.depth;
screenCaps.stencil = caps.stencil;
screenCaps.antialias = caps.antialias;
screenCaps.preserve = caps.preserve;
if (caps.antialias) {
*out_drawCaps = screenCaps;
out_readCaps->Clear();
// Color caps need to be duplicated in readCaps.
out_readCaps->color = caps.color;
out_readCaps->alpha = caps.alpha;
out_readCaps->bpp16 = caps.bpp16;
} else {
out_drawCaps->Clear();
*out_readCaps = screenCaps;
}
}
SurfaceFactory::SurfaceFactory(
SharedSurfaceType type, GLContext* gl, const SurfaceCaps& caps,
const RefPtr<layers::LayersIPCChannel>& allocator,
const layers::TextureFlags& flags)
: mType(type),
mGL(gl),
mCaps(caps),
mAllocator(allocator),
mFlags(flags),
mFormats(gl->ChooseGLFormats(caps)),
mMutex("SurfaceFactor::mMutex") {
ChooseBufferBits(mCaps, &mDrawCaps, &mReadCaps);
}
SurfaceFactory::~SurfaceFactory() {
while (!mRecycleTotalPool.empty()) {
RefPtr<layers::SharedSurfaceTextureClient> tex = *mRecycleTotalPool.begin();
StopRecycling(tex);
tex->CancelWaitForRecycle();
}
MOZ_RELEASE_ASSERT(mRecycleTotalPool.empty(),
"GFX: Surface recycle pool not empty.");
// If we mRecycleFreePool.clear() before StopRecycling(), we may try to
// recycle it, fail, call StopRecycling(), then return here and call it again.
mRecycleFreePool.clear();
}
already_AddRefed<layers::SharedSurfaceTextureClient>
SurfaceFactory::NewTexClient(const gfx::IntSize& size) {
while (!mRecycleFreePool.empty()) {
RefPtr<layers::SharedSurfaceTextureClient> cur = mRecycleFreePool.front();
mRecycleFreePool.pop();
if (cur->Surf()->mSize == size) {
cur->Surf()->WaitForBufferOwnership();
return cur.forget();
}
StopRecycling(cur);
}
UniquePtr<SharedSurface> surf = CreateShared(size);
if (!surf) return nullptr;
RefPtr<layers::SharedSurfaceTextureClient> ret;
ret = layers::SharedSurfaceTextureClient::Create(std::move(surf), this,
mAllocator, mFlags);
StartRecycling(ret);
return ret.forget();
}
void SurfaceFactory::StartRecycling(layers::SharedSurfaceTextureClient* tc) {
tc->SetRecycleCallback(&SurfaceFactory::RecycleCallback,
static_cast<void*>(this));
bool didInsert = mRecycleTotalPool.insert(tc);
MOZ_RELEASE_ASSERT(
didInsert,
"GFX: Shared surface texture client was not inserted to recycle.");
mozilla::Unused << didInsert;
}
void SurfaceFactory::StopRecycling(layers::SharedSurfaceTextureClient* tc) {
MutexAutoLock autoLock(mMutex);
// Must clear before releasing ref.
tc->ClearRecycleCallback();
bool didErase = mRecycleTotalPool.erase(tc);
MOZ_RELEASE_ASSERT(didErase,
"GFX: Shared texture surface client was not erased.");
mozilla::Unused << didErase;
}
/*static*/
void SurfaceFactory::RecycleCallback(layers::TextureClient* rawTC,
void* rawFactory) {
RefPtr<layers::SharedSurfaceTextureClient> tc;
tc = static_cast<layers::SharedSurfaceTextureClient*>(rawTC);
SurfaceFactory* factory = static_cast<SurfaceFactory*>(rawFactory);
if (tc->Surf()->mCanRecycle) {
if (factory->Recycle(tc)) return;
}
// Did not recover the tex client. End the (re)cycle!
factory->StopRecycling(tc);
}
bool SurfaceFactory::Recycle(layers::SharedSurfaceTextureClient* texClient) {
MOZ_ASSERT(texClient);
MutexAutoLock autoLock(mMutex);
if (mRecycleFreePool.size() >= 2) {
return false;
}
RefPtr<layers::SharedSurfaceTextureClient> texClientRef = texClient;
mRecycleFreePool.push(texClientRef);
return true;
}
////////////////////////////////////////////////////////////////////////////////
// ScopedReadbackFB
ScopedReadbackFB::ScopedReadbackFB(SharedSurface* src)
: mGL(src->mGL), mAutoFB(mGL) {
switch (src->mAttachType) {
case AttachmentType::GLRenderbuffer: {
mGL->fGenFramebuffers(1, &mTempFB);
mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mTempFB);
GLuint rb = src->ProdRenderbuffer();
mGL->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
LOCAL_GL_COLOR_ATTACHMENT0,
LOCAL_GL_RENDERBUFFER, rb);
break;
}
case AttachmentType::GLTexture: {
mGL->fGenFramebuffers(1, &mTempFB);
mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mTempFB);
GLuint tex = src->ProdTexture();
GLenum texImageTarget = src->ProdTextureTarget();
mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
LOCAL_GL_COLOR_ATTACHMENT0, texImageTarget,
tex, 0);
break;
}
case AttachmentType::Screen: {
SharedSurface* origLocked = mGL->GetLockedSurface();
if (origLocked != src) {
if (origLocked) {
mSurfToLock = origLocked;
mSurfToLock->UnlockProd();
}
mSurfToUnlock = src;
mSurfToUnlock->LockProd();
}
// TODO: This should just be BindFB, but we don't have
// the patch for this yet. (bug 1045955)
MOZ_ASSERT(mGL->Screen());
mGL->Screen()->BindReadFB_Internal(0);
break;
}
default:
MOZ_CRASH("GFX: Unhandled `mAttachType`.");
}
if (src->NeedsIndirectReads()) {
mGL->fGenTextures(1, &mTempTex);
{
ScopedBindTexture autoTex(mGL, mTempTex);
GLenum format = src->mHasAlpha ? LOCAL_GL_RGBA : LOCAL_GL_RGB;
auto width = src->mSize.width;
auto height = src->mSize.height;
mGL->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, format, 0, 0, width, height,
0);
}
mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
LOCAL_GL_TEXTURE_2D, mTempTex, 0);
}
}
ScopedReadbackFB::~ScopedReadbackFB() {
if (mTempFB) {
mGL->fDeleteFramebuffers(1, &mTempFB);
}
if (mTempTex) {
mGL->fDeleteTextures(1, &mTempTex);
}
if (mSurfToUnlock) {
mSurfToUnlock->UnlockProd();
}
if (mSurfToLock) {
mSurfToLock->LockProd();
}
}
////////////////////////////////////////////////////////////////////////////////
class AutoLockBits {
gfx::DrawTarget* mDT;
uint8_t* mLockedBits;
public:
explicit AutoLockBits(gfx::DrawTarget* dt) : mDT(dt), mLockedBits(nullptr) {
MOZ_ASSERT(mDT);
}
bool Lock(uint8_t** data, gfx::IntSize* size, int32_t* stride,
gfx::SurfaceFormat* format) {
if (!mDT->LockBits(data, size, stride, format)) return false;
mLockedBits = *data;
return true;
}
~AutoLockBits() {
if (mLockedBits) mDT->ReleaseBits(mLockedBits);
}
};
bool ReadbackSharedSurface(SharedSurface* src, gfx::DrawTarget* dst) {
AutoLockBits lock(dst);
uint8_t* dstBytes;
gfx::IntSize dstSize;
int32_t dstStride;
gfx::SurfaceFormat dstFormat;
if (!lock.Lock(&dstBytes, &dstSize, &dstStride, &dstFormat)) return false;
const bool isDstRGBA = (dstFormat == gfx::SurfaceFormat::R8G8B8A8 ||
dstFormat == gfx::SurfaceFormat::R8G8B8X8);
MOZ_ASSERT_IF(!isDstRGBA, dstFormat == gfx::SurfaceFormat::B8G8R8A8 ||
dstFormat == gfx::SurfaceFormat::B8G8R8X8);
size_t width = src->mSize.width;
size_t height = src->mSize.height;
MOZ_ASSERT(width == (size_t)dstSize.width);
MOZ_ASSERT(height == (size_t)dstSize.height);
GLenum readGLFormat;
GLenum readType;
{
ScopedReadbackFB autoReadback(src);
// We have a source FB, now we need a format.
GLenum dstGLFormat = isDstRGBA ? LOCAL_GL_BGRA : LOCAL_GL_RGBA;
GLenum dstType = LOCAL_GL_UNSIGNED_BYTE;
// We actually don't care if they match, since we can handle
// any read{Format,Type} we get.
GLContext* gl = src->mGL;
GetActualReadFormats(gl, dstGLFormat, dstType, &readGLFormat, &readType);
MOZ_ASSERT(readGLFormat == LOCAL_GL_RGBA || readGLFormat == LOCAL_GL_BGRA);
MOZ_ASSERT(readType == LOCAL_GL_UNSIGNED_BYTE);
// ReadPixels from the current FB into lockedBits.
{
ScopedPackState scopedPackState(gl);
bool handled = scopedPackState.SetForWidthAndStrideRGBA(width, dstStride);
MOZ_RELEASE_ASSERT(handled, "Unhandled stride");
gl->raw_fReadPixels(0, 0, width, height, readGLFormat, readType,
dstBytes);
}
}
const bool isReadRGBA = readGLFormat == LOCAL_GL_RGBA;
if (isReadRGBA != isDstRGBA) {
for (size_t j = 0; j < height; ++j) {
uint8_t* rowItr = dstBytes + j * dstStride;
uint8_t* rowEnd = rowItr + 4 * width;
while (rowItr != rowEnd) {
Swap(rowItr[0], rowItr[2]);
rowItr += 4;
}
}
}
return true;
}
uint32_t ReadPixel(SharedSurface* src) {
GLContext* gl = src->mGL;
uint32_t pixel;
ScopedReadbackFB a(src);
{
ScopedPackState scopedPackState(gl);
UniquePtr<uint8_t[]> bytes(new uint8_t[4]);
gl->raw_fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
bytes.get());
memcpy(&pixel, bytes.get(), 4);
}
return pixel;
}
} // namespace gl
} /* namespace mozilla */