/* -*- 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" #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 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& 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 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 SurfaceFactory::NewTexClient(const gfx::IntSize& size) { while (!mRecycleFreePool.empty()) { RefPtr cur = mRecycleFreePool.front(); mRecycleFreePool.pop(); if (cur->Surf()->mSize == size) { cur->Surf()->WaitForBufferOwnership(); return cur.forget(); } StopRecycling(cur); } UniquePtr surf = CreateShared(size); if (!surf) return nullptr; RefPtr 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(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 tc; tc = static_cast(rawTC); SurfaceFactory* factory = static_cast(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 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 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 */