summaryrefslogtreecommitdiffstats
path: root/gfx/gl/SharedSurface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/gl/SharedSurface.cpp')
-rw-r--r--gfx/gl/SharedSurface.cpp621
1 files changed, 621 insertions, 0 deletions
diff --git a/gfx/gl/SharedSurface.cpp b/gfx/gl/SharedSurface.cpp
new file mode 100644
index 000000000..fa1d19172
--- /dev/null
+++ b/gfx/gl/SharedSurface.cpp
@@ -0,0 +1,621 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
+/* 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"
+
+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();
+
+ gl->BlitHelper()->BlitFramebufferToTexture(0, destTex,
+ src->mSize,
+ dest->mSize,
+ destTarget,
+ true);
+ } else if (dest->mAttachType == AttachmentType::GLRenderbuffer) {
+ GLuint destRB = dest->ProdRenderbuffer();
+ ScopedFramebufferForRenderbuffer destWrapper(gl, destRB);
+
+ gl->BlitHelper()->BlitFramebufferToFramebuffer(0,
+ destWrapper.FB(),
+ src->mSize,
+ dest->mSize,
+ true);
+ } 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();
+
+ gl->BlitHelper()->BlitTextureToFramebuffer(srcTex, 0,
+ src->mSize,
+ dest->mSize,
+ srcTarget,
+ true);
+ } else if (src->mAttachType == AttachmentType::GLRenderbuffer) {
+ GLuint srcRB = src->ProdRenderbuffer();
+ ScopedFramebufferForRenderbuffer srcWrapper(gl, srcRB);
+
+ gl->BlitHelper()->BlitFramebufferToFramebuffer(srcWrapper.FB(),
+ 0,
+ src->mSize,
+ dest->mSize,
+ true);
+ } 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);
+
+ gl->BlitHelper()->BlitTextureToFramebuffer(srcTex, destWrapper.FB(),
+ 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();
+
+ gl->BlitHelper()->BlitFramebufferToTexture(srcWrapper.FB(), 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)
+#ifdef DEBUG
+ , mOwningThread(NS_GetCurrentThread())
+#endif
+{ }
+
+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 = Move(CreateShared(size));
+ if (!surf)
+ return nullptr;
+
+ RefPtr<layers::SharedSurfaceTextureClient> ret;
+ ret = layers::SharedSurfaceTextureClient::Create(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)
+ , mTempFB(0)
+ , mTempTex(0)
+ , mSurfToUnlock(nullptr)
+ , mSurfToLock(nullptr)
+{
+ 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.
+ {
+ size_t alignment = 8;
+ if (dstStride % 4 == 0)
+ alignment = 4;
+
+ ScopedPackState scopedPackState(gl);
+ if (alignment != 4) {
+ gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, alignment);
+ }
+
+ 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 */