diff options
Diffstat (limited to 'gfx/layers/opengl')
21 files changed, 7251 insertions, 0 deletions
diff --git a/gfx/layers/opengl/CompositingRenderTargetOGL.cpp b/gfx/layers/opengl/CompositingRenderTargetOGL.cpp new file mode 100644 index 0000000000..9fbc207a6b --- /dev/null +++ b/gfx/layers/opengl/CompositingRenderTargetOGL.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "CompositingRenderTargetOGL.h" +#include "GLContext.h" +#include "GLReadTexImageHelper.h" +#include "ScopedGLHelpers.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +CompositingRenderTargetOGL::~CompositingRenderTargetOGL() { + if (mGLResourceOwnership == GLResourceOwnership::OWNED_BY_RENDER_TARGET && + mGL && mGL->MakeCurrent()) { + mGL->fDeleteTextures(1, &mTextureHandle); + mGL->fDeleteFramebuffers(1, &mFBO); + } +} + +void CompositingRenderTargetOGL::BindTexture(GLenum aTextureUnit, + GLenum aTextureTarget) { + MOZ_ASSERT(!mNeedInitialization); + MOZ_ASSERT(mTextureHandle != 0); + mGL->fActiveTexture(aTextureUnit); + mGL->fBindTexture(aTextureTarget, mTextureHandle); +} + +void CompositingRenderTargetOGL::BindRenderTarget() { + bool needsClear = false; + + if (mNeedInitialization) { + Initialize(mNeedInitialization->mFBOTextureTarget); + if (mNeedInitialization->mInitMode == INIT_MODE_CLEAR) { + needsClear = true; + mClearOnBind = false; + } + mNeedInitialization = Nothing(); + } else { + GLuint fbo = GetFBO(); + mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fbo); + GLenum result = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { + // The main framebuffer (0) of non-offscreen contexts + // might be backed by a EGLSurface that needs to be renewed. + if (mFBO == 0 && !mGL->IsOffscreen()) { + mGL->RenewSurface(mCompositor->GetWidget()); + result = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + } + if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { + nsAutoCString msg; + msg.AppendPrintf( + "Framebuffer not complete -- CheckFramebufferStatus returned 0x%x, " + "GLContext=%p, IsOffscreen()=%d, mFBO=%d, " + "aRect.width=%d, aRect.height=%d", + result, mGL.get(), mGL->IsOffscreen(), mFBO, mSize.width, + mSize.height); + NS_WARNING(msg.get()); + } + } + + needsClear = mClearOnBind; + } + + if (needsClear) { + ScopedGLState scopedScissorTestState(mGL, LOCAL_GL_SCISSOR_TEST, true); + ScopedScissorRect autoScissorRect(mGL, 0, 0, mSize.width, mSize.height); + mGL->fClearColor(0.0, 0.0, 0.0, 0.0); + mGL->fClearDepth(0.0); + mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT); + } +} + +GLuint CompositingRenderTargetOGL::GetFBO() const { + MOZ_ASSERT(!mNeedInitialization); + return mFBO == 0 ? mGL->GetDefaultFramebuffer() : mFBO; +} + +#ifdef MOZ_DUMP_PAINTING +already_AddRefed<DataSourceSurface> CompositingRenderTargetOGL::Dump( + Compositor* aCompositor) { + MOZ_ASSERT(!mNeedInitialization); + CompositorOGL* compositorOGL = aCompositor->AsCompositorOGL(); + return ReadBackSurface(mGL, mTextureHandle, true, + compositorOGL->GetFBOFormat()); +} +#endif + +void CompositingRenderTargetOGL::Initialize(GLenum aFBOTextureTarget) { + // TODO: call mGL->GetBackbufferFB(), use that + GLuint fbo = mFBO == 0 ? mGL->GetDefaultFramebuffer() : mFBO; + mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fbo); + mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, + aFBOTextureTarget, mTextureHandle, 0); + + // Making this call to fCheckFramebufferStatus prevents a crash on + // PowerVR. See bug 695246. + GLenum result = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { + nsAutoCString msg; + msg.AppendPrintf( + "Framebuffer not complete -- error 0x%x, aFBOTextureTarget 0x%x, mFBO " + "%d, mTextureHandle %d, aRect.width %d, aRect.height %d", + result, aFBOTextureTarget, mFBO, mTextureHandle, mSize.width, + mSize.height); + NS_ERROR(msg.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/CompositingRenderTargetOGL.h b/gfx/layers/opengl/CompositingRenderTargetOGL.h new file mode 100644 index 0000000000..14e7265706 --- /dev/null +++ b/gfx/layers/opengl/CompositingRenderTargetOGL.h @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MOZILLA_GFX_COMPOSITINGRENDERTARGETOGL_H +#define MOZILLA_GFX_COMPOSITINGRENDERTARGETOGL_H + +#include "GLContextTypes.h" // for GLContext +#include "GLDefs.h" // for GLenum, LOCAL_GL_FRAMEBUFFER, etc +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/Point.h" // for IntSize, IntSizeTyped +#include "mozilla/gfx/Types.h" // for SurfaceFormat, etc +#include "mozilla/layers/Compositor.h" // for SurfaceInitMode, etc +#include "mozilla/layers/TextureHost.h" // for CompositingRenderTarget +#include "mozilla/layers/CompositorOGL.h" // for CompositorOGL +#include "mozilla/mozalloc.h" // for operator new +#include "nsAString.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ERROR, NS_WARNING +#include "nsString.h" // for nsAutoCString + +namespace mozilla { +namespace gl { +class BindableTexture; +} // namespace gl +namespace gfx { +class DataSourceSurface; +} // namespace gfx + +namespace layers { + +class TextureSource; + +class CompositingRenderTargetOGL : public CompositingRenderTarget { + typedef mozilla::gl::GLContext GLContext; + + friend class CompositorOGL; + + enum class GLResourceOwnership : uint8_t { + // Framebuffer and texture will be deleted when the RenderTarget is + // destroyed. + OWNED_BY_RENDER_TARGET, + + // Framebuffer and texture are only used by the RenderTarget, but never + // deleted. + EXTERNALLY_OWNED + }; + + struct InitParams { + GLenum mFBOTextureTarget; + SurfaceInitMode mInitMode; + }; + + public: + ~CompositingRenderTargetOGL(); + + const char* Name() const override { return "CompositingRenderTargetOGL"; } + + /** + * Create a render target around the default FBO, for rendering straight to + * the window. + */ + static already_AddRefed<CompositingRenderTargetOGL> CreateForWindow( + CompositorOGL* aCompositor, const gfx::IntSize& aSize) { + RefPtr<CompositingRenderTargetOGL> result = new CompositingRenderTargetOGL( + aCompositor, gfx::IntRect(gfx::IntPoint(), aSize), gfx::IntPoint(), + aSize, GLResourceOwnership::EXTERNALLY_OWNED, 0, 0, Nothing()); + return result.forget(); + } + + static already_AddRefed<CompositingRenderTargetOGL> + CreateForNewFBOAndTakeOwnership(CompositorOGL* aCompositor, GLuint aTexture, + GLuint aFBO, const gfx::IntRect& aRect, + const gfx::IntPoint& aClipSpaceOrigin, + const gfx::IntSize& aPhySize, + GLenum aFBOTextureTarget, + SurfaceInitMode aInit) { + RefPtr<CompositingRenderTargetOGL> result = new CompositingRenderTargetOGL( + aCompositor, aRect, aClipSpaceOrigin, aPhySize, + GLResourceOwnership::OWNED_BY_RENDER_TARGET, aTexture, aFBO, + Some(InitParams{aFBOTextureTarget, aInit})); + return result.forget(); + } + + static already_AddRefed<CompositingRenderTargetOGL> + CreateForExternallyOwnedFBO(CompositorOGL* aCompositor, GLuint aFBO, + const gfx::IntRect& aRect, + const gfx::IntPoint& aClipSpaceOrigin) { + RefPtr<CompositingRenderTargetOGL> result = new CompositingRenderTargetOGL( + aCompositor, aRect, aClipSpaceOrigin, aRect.Size(), + GLResourceOwnership::EXTERNALLY_OWNED, 0, aFBO, Nothing()); + return result.forget(); + } + + void BindTexture(GLenum aTextureUnit, GLenum aTextureTarget); + + /** + * Call when we want to draw into our FBO + */ + void BindRenderTarget(); + + bool IsWindow() { return mFBO == 0; } + + GLuint GetFBO() const; + + GLuint GetTextureHandle() const { + MOZ_ASSERT(!mNeedInitialization); + return mTextureHandle; + } + + // TextureSourceOGL + TextureSourceOGL* AsSourceOGL() override { + // XXX - Bug 900770 + MOZ_ASSERT( + false, + "CompositingRenderTargetOGL should not be used as a TextureSource"); + return nullptr; + } + gfx::IntSize GetSize() const override { return mSize; } + + // The point that DrawGeometry's aClipRect is relative to. Will be (0, 0) for + // root render targets and equal to GetOrigin() for non-root render targets. + gfx::IntPoint GetClipSpaceOrigin() const { return mClipSpaceOrigin; } + + gfx::SurfaceFormat GetFormat() const override { + // XXX - Should it be implemented ? is the above assert true ? + MOZ_ASSERT(false, "Not implemented"); + return gfx::SurfaceFormat::UNKNOWN; + } + + // In render target coordinates, i.e. the same space as GetOrigin(). + // NOT relative to mClipSpaceOrigin! + void SetClipRect(const Maybe<gfx::IntRect>& aRect) { mClipRect = aRect; } + const Maybe<gfx::IntRect>& GetClipRect() const { return mClipRect; } + +#ifdef MOZ_DUMP_PAINTING + already_AddRefed<gfx::DataSourceSurface> Dump( + Compositor* aCompositor) override; +#endif + + const gfx::IntSize& GetInitSize() const { return mSize; } + const gfx::IntSize& GetPhysicalSize() const { return mPhySize; } + + protected: + CompositingRenderTargetOGL(CompositorOGL* aCompositor, + const gfx::IntRect& aRect, + const gfx::IntPoint& aClipSpaceOrigin, + const gfx::IntSize& aPhySize, + GLResourceOwnership aGLResourceOwnership, + GLuint aTexure, GLuint aFBO, + const Maybe<InitParams>& aNeedInitialization) + : CompositingRenderTarget(aRect.TopLeft()), + mNeedInitialization(aNeedInitialization), + mSize(aRect.Size()), + mPhySize(aPhySize), + mCompositor(aCompositor), + mGL(aCompositor->gl()), + mClipSpaceOrigin(aClipSpaceOrigin), + mGLResourceOwnership(aGLResourceOwnership), + mTextureHandle(aTexure), + mFBO(aFBO) { + MOZ_ASSERT(mGL); + } + + /** + * Actually do the initialisation. + * We do this lazily so that when we first set this render target on the + * compositor we do not have to re-bind the FBO after unbinding it, or + * alternatively leave the FBO bound after creation. Note that we leave our + * FBO bound, and so calling this method is only suitable when about to use + * this render target. + */ + void Initialize(GLenum aFBOTextureTarget); + + /** + * Some() between construction and Initialize, if initialization was + * requested. + */ + Maybe<InitParams> mNeedInitialization; + + /* + * Users of render target would draw in logical size, but it is + * actually drawn to a surface in physical size. GL surfaces have + * a limitation on their size, a smaller surface would be + * allocated for the render target if the caller requests in a + * size too big. + */ + gfx::IntSize mSize; // Logical size, the expected by callers. + gfx::IntSize mPhySize; // Physical size, the real size of the surface. + + /** + * There is temporary a cycle between the compositor and the render target, + * each having a strong ref to the other. The compositor's reference to + * the target is always cleared at the end of a frame. + */ + RefPtr<CompositorOGL> mCompositor; + RefPtr<GLContext> mGL; + Maybe<gfx::IntRect> mClipRect; + gfx::IntPoint mClipSpaceOrigin; + GLResourceOwnership mGLResourceOwnership; + GLuint mTextureHandle; + GLuint mFBO; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_SURFACEOGL_H */ diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp new file mode 100644 index 0000000000..4cafbea88a --- /dev/null +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -0,0 +1,1725 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "CompositorOGL.h" +#include <stddef.h> // for size_t +#include <stdint.h> // for uint32_t, uint8_t +#include <stdlib.h> // for free, malloc +#include "GLContextProvider.h" // for GLContextProvider +#include "GLContext.h" // for GLContext +#include "GLUploadHelpers.h" +#include "gfxCrashReporterUtils.h" // for ScopedGfxFeatureReporter +#include "gfxEnv.h" // for gfxEnv +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for gfxUtils, etc +#include "mozilla/ArrayUtils.h" // for ArrayLength +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/ProfilerLabels.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_nglayout.h" +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/Matrix.h" // for Matrix4x4, Matrix +#include "mozilla/gfx/Triangle.h" // for Triangle +#include "mozilla/gfx/gfxVars.h" // for gfxVars +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/NativeLayer.h" +#include "mozilla/layers/CompositingRenderTargetOGL.h" +#include "mozilla/layers/Effects.h" // for EffectChain, TexturedEffect, etc +#include "mozilla/layers/TextureHost.h" // for TextureSource, etc +#include "mozilla/layers/TextureHostOGL.h" // for TextureSourceOGL, etc +#include "mozilla/layers/PTextureParent.h" // for OtherPid() on PTextureParent +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsAppRunner.h" +#include "nsAString.h" +#include "nsClassHashtable.h" +#include "nsIConsoleService.h" // for nsIConsoleService, etc +#include "nsIWidget.h" // for nsIWidget +#include "nsLiteralString.h" // for NS_LITERAL_STRING +#include "nsMathUtils.h" // for NS_roundf +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsServiceManagerUtils.h" // for do_GetService +#include "nsString.h" // for nsString, nsAutoCString, etc +#include "OGLShaderProgram.h" // for ShaderProgramOGL, etc +#include "ScopedGLHelpers.h" +#include "GLReadTexImageHelper.h" +#include "HeapCopyOfStackArray.h" +#include "GLBlitHelper.h" +#include "mozilla/gfx/Swizzle.h" +#ifdef MOZ_WAYLAND +# include "mozilla/widget/GtkCompositorWidget.h" +#endif +#if MOZ_WIDGET_ANDROID +# include "GLContextEGL.h" +# include "GLLibraryEGL.h" +# include "mozilla/java/GeckoSurfaceTextureWrappers.h" +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +using namespace mozilla::gl; + +static const GLuint kCoordinateAttributeIndex = 0; +static const GLuint kTexCoordinateAttributeIndex = 1; + +class AsyncReadbackBufferOGL final : public AsyncReadbackBuffer { + public: + AsyncReadbackBufferOGL(GLContext* aGL, const IntSize& aSize); + + bool MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) const override; + + void Bind() const { + mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, mBufferHandle); + mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1); + } + + protected: + virtual ~AsyncReadbackBufferOGL(); + + private: + GLContext* mGL; + GLuint mBufferHandle; +}; + +AsyncReadbackBufferOGL::AsyncReadbackBufferOGL(GLContext* aGL, + const IntSize& aSize) + : AsyncReadbackBuffer(aSize), mGL(aGL), mBufferHandle(0) { + size_t bufferByteCount = mSize.width * mSize.height * 4; + mGL->fGenBuffers(1, &mBufferHandle); + + ScopedPackState scopedPackState(mGL); + Bind(); + mGL->fBufferData(LOCAL_GL_PIXEL_PACK_BUFFER, bufferByteCount, nullptr, + LOCAL_GL_STREAM_READ); +} + +AsyncReadbackBufferOGL::~AsyncReadbackBufferOGL() { + if (mGL && mGL->MakeCurrent()) { + mGL->fDeleteBuffers(1, &mBufferHandle); + } +} + +bool AsyncReadbackBufferOGL::MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) const { + MOZ_RELEASE_ASSERT(aReadSize <= aSurface->GetSize()); + + if (!mGL || !mGL->MakeCurrent()) { + return false; + } + + ScopedPackState scopedPackState(mGL); + Bind(); + + const uint8_t* srcData = nullptr; + if (mGL->IsSupported(GLFeature::map_buffer_range)) { + srcData = static_cast<uint8_t*>(mGL->fMapBufferRange( + LOCAL_GL_PIXEL_PACK_BUFFER, 0, aReadSize.height * aReadSize.width * 4, + LOCAL_GL_MAP_READ_BIT)); + } else { + srcData = static_cast<uint8_t*>( + mGL->fMapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, LOCAL_GL_READ_ONLY)); + } + + if (!srcData) { + return false; + } + + int32_t srcStride = mSize.width * 4; // Bind() sets an alignment of 1 + DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::WRITE); + uint8_t* destData = map.GetData(); + int32_t destStride = map.GetStride(); + SurfaceFormat destFormat = aSurface->GetFormat(); + for (int32_t destRow = 0; destRow < aReadSize.height; destRow++) { + // Turn srcData upside down during the copy. + int32_t srcRow = aReadSize.height - 1 - destRow; + const uint8_t* src = &srcData[srcRow * srcStride]; + uint8_t* dest = &destData[destRow * destStride]; + SwizzleData(src, srcStride, SurfaceFormat::R8G8B8A8, dest, destStride, + destFormat, IntSize(aReadSize.width, 1)); + } + + mGL->fUnmapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER); + + return true; +} + +PerUnitTexturePoolOGL::PerUnitTexturePoolOGL(gl::GLContext* aGL) + : mTextureTarget(0), // zero is never a valid texture target + mGL(aGL) {} + +PerUnitTexturePoolOGL::~PerUnitTexturePoolOGL() { DestroyTextures(); } + +CompositorOGL::CompositorOGL(widget::CompositorWidget* aWidget, + int aSurfaceWidth, int aSurfaceHeight, + bool aUseExternalSurfaceSize) + : Compositor(aWidget), + mWidgetSize(-1, -1), + mSurfaceSize(aSurfaceWidth, aSurfaceHeight), + mFBOTextureTarget(0), + mWindowRenderTarget(nullptr), + mQuadVBO(0), + mTriangleVBO(0), + mPreviousFrameDoneSync(nullptr), + mThisFrameDoneSync(nullptr), + mHasBGRA(0), + mUseExternalSurfaceSize(aUseExternalSurfaceSize), + mFrameInProgress(false), + mDestroyed(false), + mViewportSize(0, 0) { + if (aWidget->GetNativeLayerRoot()) { + // We can only render into native layers, our GLContext won't have a usable + // default framebuffer. + mCanRenderToDefaultFramebuffer = false; + } + MOZ_COUNT_CTOR(CompositorOGL); +} + +CompositorOGL::~CompositorOGL() { MOZ_COUNT_DTOR(CompositorOGL); } + +already_AddRefed<mozilla::gl::GLContext> CompositorOGL::CreateContext() { + RefPtr<GLContext> context; + + // Used by mock widget to create an offscreen context + nsIWidget* widget = mWidget->RealWidget(); + void* widgetOpenGLContext = + widget ? widget->GetNativeData(NS_NATIVE_OPENGL_CONTEXT) : nullptr; + if (widgetOpenGLContext) { + GLContext* alreadyRefed = reinterpret_cast<GLContext*>(widgetOpenGLContext); + return already_AddRefed<GLContext>(alreadyRefed); + } + +#ifdef XP_WIN + if (gfxEnv::MOZ_LAYERS_PREFER_EGL()) { + printf_stderr("Trying GL layers...\n"); + context = gl::GLContextProviderEGL::CreateForCompositorWidget( + mWidget, /* aHardwareWebRender */ false, /* aForceAccelerated */ false); + } +#endif + + // Allow to create offscreen GL context for main Layer Manager + if (!context && gfxEnv::MOZ_LAYERS_PREFER_OFFSCREEN()) { + nsCString discardFailureId; + context = GLContextProvider::CreateHeadless( + {CreateContextFlags::REQUIRE_COMPAT_PROFILE}, &discardFailureId); + if (!context->CreateOffscreenDefaultFb(mSurfaceSize)) { + context = nullptr; + } + } + + if (!context) { + context = gl::GLContextProvider::CreateForCompositorWidget( + mWidget, + /* aHardwareWebRender */ false, + gfxVars::RequiresAcceleratedGLContextForCompositorOGL()); + } + + if (!context) { + NS_WARNING("Failed to create CompositorOGL context"); + } + + return context.forget(); +} + +void CompositorOGL::Destroy() { + Compositor::Destroy(); + + if (mTexturePool) { + mTexturePool->Clear(); + mTexturePool = nullptr; + } + + if (!mDestroyed) { + mDestroyed = true; + CleanupResources(); + } +} + +void CompositorOGL::CleanupResources() { + if (!mGLContext) return; + + if (mSurfacePoolHandle) { + mSurfacePoolHandle->Pool()->DestroyGLResourcesForContext(mGLContext); + mSurfacePoolHandle = nullptr; + } + + RefPtr<GLContext> ctx = mGLContext->GetSharedContext(); + if (!ctx) { + ctx = mGLContext; + } + + if (!ctx->MakeCurrent()) { + // Leak resources! + mQuadVBO = 0; + mTriangleVBO = 0; + mPreviousFrameDoneSync = nullptr; + mThisFrameDoneSync = nullptr; + mProgramsHolder = nullptr; + mGLContext = nullptr; + mNativeLayersReferenceRT = nullptr; + mFullWindowRenderTarget = nullptr; + return; + } + + mProgramsHolder = nullptr; + mNativeLayersReferenceRT = nullptr; + mFullWindowRenderTarget = nullptr; + +#ifdef MOZ_WIDGET_GTK + // TextureSources might hold RefPtr<gl::GLContext>. + // All of them needs to be released to destroy GLContext. + // GLContextGLX has to be destroyed before related gtk window is destroyed. + for (auto textureSource : mRegisteredTextureSources) { + textureSource->DeallocateDeviceData(); + } + mRegisteredTextureSources.clear(); +#endif + + ctx->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); + + if (mQuadVBO) { + ctx->fDeleteBuffers(1, &mQuadVBO); + mQuadVBO = 0; + } + + if (mTriangleVBO) { + ctx->fDeleteBuffers(1, &mTriangleVBO); + mTriangleVBO = 0; + } + + mGLContext->MakeCurrent(); + + if (mPreviousFrameDoneSync) { + mGLContext->fDeleteSync(mPreviousFrameDoneSync); + mPreviousFrameDoneSync = nullptr; + } + + if (mThisFrameDoneSync) { + mGLContext->fDeleteSync(mThisFrameDoneSync); + mThisFrameDoneSync = nullptr; + } + + if (mOwnsGLContext) { + // On the main thread the Widget will be destroyed soon and calling + // MakeCurrent after that could cause a crash (at least with GLX, see bug + // 1059793), unless context is marked as destroyed. There may be some + // textures still alive that will try to call MakeCurrent on the context so + // let's make sure it is marked destroyed now. + mGLContext->MarkDestroyed(); + } + + mGLContext = nullptr; +} + +bool CompositorOGL::Initialize(GLContext* aGLContext, + RefPtr<ShaderProgramOGLsHolder> aProgramsHolder, + nsCString* const out_failureReason) { + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(!mGLContext); + + mGLContext = aGLContext; + mProgramsHolder = aProgramsHolder; + mOwnsGLContext = false; + + return Initialize(out_failureReason); +} + +bool CompositorOGL::Initialize(nsCString* const out_failureReason) { + ScopedGfxFeatureReporter reporter("GL Layers"); + + // Do not allow double initialization + MOZ_ASSERT(mGLContext == nullptr || !mOwnsGLContext, + "Don't reinitialize CompositorOGL"); + + if (!mGLContext) { + MOZ_ASSERT(mOwnsGLContext); + mGLContext = CreateContext(); + } + +#ifdef MOZ_WIDGET_ANDROID + if (!mGLContext) { + *out_failureReason = "FEATURE_FAILURE_OPENGL_NO_ANDROID_CONTEXT"; + MOZ_CRASH("We need a context on Android"); + } +#endif + + if (!mGLContext) { + *out_failureReason = "FEATURE_FAILURE_OPENGL_CREATE_CONTEXT"; + return false; + } + + if (!mProgramsHolder) { + mProgramsHolder = new ShaderProgramOGLsHolder(mGLContext); + } + + MakeCurrent(); + + mHasBGRA = mGLContext->IsExtensionSupported( + gl::GLContext::EXT_texture_format_BGRA8888) || + mGLContext->IsExtensionSupported(gl::GLContext::EXT_bgra); + + mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, + LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); + mGLContext->fEnable(LOCAL_GL_BLEND); + + // initialise a common shader to check that we can actually compile a shader + RefPtr<EffectNV12> effect = + new EffectNV12(nullptr, YUVColorSpace::BT601, ColorRange::LIMITED, + ColorDepth::COLOR_8, SamplingFilter::GOOD); + ShaderConfigOGL config = GetShaderConfigFor(effect); + if (!GetShaderProgramFor(config)) { + *out_failureReason = "FEATURE_FAILURE_OPENGL_COMPILE_SHADER"; + return false; + } + + if (mGLContext->WorkAroundDriverBugs()) { + /** + * We'll test the ability here to bind NPOT textures to a framebuffer, if + * this fails we'll try ARB_texture_rectangle. + */ + + GLenum textureTargets[] = {LOCAL_GL_TEXTURE_2D, LOCAL_GL_NONE}; + + if (!mGLContext->IsGLES()) { + // No TEXTURE_RECTANGLE_ARB available on ES2 + textureTargets[1] = LOCAL_GL_TEXTURE_RECTANGLE_ARB; + } + + mFBOTextureTarget = LOCAL_GL_NONE; + + GLuint testFBO = 0; + mGLContext->fGenFramebuffers(1, &testFBO); + GLuint testTexture = 0; + + for (uint32_t i = 0; i < ArrayLength(textureTargets); i++) { + GLenum target = textureTargets[i]; + if (!target) continue; + + mGLContext->fGenTextures(1, &testTexture); + mGLContext->fBindTexture(target, testTexture); + mGLContext->fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_NEAREST); + mGLContext->fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_NEAREST); + mGLContext->fTexImage2D( + target, 0, LOCAL_GL_RGBA, 5, 3, /* sufficiently NPOT */ + 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, nullptr); + + // unbind this texture, in preparation for binding it to the FBO + mGLContext->fBindTexture(target, 0); + + mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, testFBO); + mGLContext->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, + LOCAL_GL_COLOR_ATTACHMENT0, target, + testTexture, 0); + + if (mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) == + LOCAL_GL_FRAMEBUFFER_COMPLETE) { + mFBOTextureTarget = target; + mGLContext->fDeleteTextures(1, &testTexture); + break; + } + + mGLContext->fDeleteTextures(1, &testTexture); + } + + if (testFBO) { + mGLContext->fDeleteFramebuffers(1, &testFBO); + } + + if (mFBOTextureTarget == LOCAL_GL_NONE) { + /* Unable to find a texture target that works with FBOs and NPOT textures + */ + *out_failureReason = "FEATURE_FAILURE_OPENGL_NO_TEXTURE_TARGET"; + return false; + } + } else { + // not trying to work around driver bugs, so TEXTURE_2D should just work + mFBOTextureTarget = LOCAL_GL_TEXTURE_2D; + } + + // back to default framebuffer, to avoid confusion + mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); + + if (mFBOTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB) { + /* If we're using TEXTURE_RECTANGLE, then we must have the ARB + * extension -- the EXT variant does not provide support for + * texture rectangle access inside GLSL (sampler2DRect, + * texture2DRect). + */ + if (!mGLContext->IsExtensionSupported( + gl::GLContext::ARB_texture_rectangle)) { + *out_failureReason = "FEATURE_FAILURE_OPENGL_ARB_EXT"; + return false; + } + } + + // Create a VBO for triangle vertices. + mGLContext->fGenBuffers(1, &mTriangleVBO); + + /* Create a simple quad VBO */ + mGLContext->fGenBuffers(1, &mQuadVBO); + + // 4 quads, with the number of the quad (vertexID) encoded in w. + GLfloat vertices[] = { + 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, + + 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, + + 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 2.0f, + 1.0f, 0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 2.0f, 1.0f, 1.0f, 0.0f, 2.0f, + + 0.0f, 0.0f, 0.0f, 3.0f, 1.0f, 0.0f, 0.0f, 3.0f, 0.0f, 1.0f, 0.0f, 3.0f, + 1.0f, 0.0f, 0.0f, 3.0f, 0.0f, 1.0f, 0.0f, 3.0f, 1.0f, 1.0f, 0.0f, 3.0f, + }; + HeapCopyOfStackArray<GLfloat> verticesOnHeap(vertices); + + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); + mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER, verticesOnHeap.ByteLength(), + verticesOnHeap.Data(), LOCAL_GL_STATIC_DRAW); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); + + nsCOMPtr<nsIConsoleService> console( + do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + + if (console) { + nsString msg; + msg += nsLiteralString( + u"OpenGL compositor Initialized Succesfully.\nVersion: "); + msg += NS_ConvertUTF8toUTF16(nsDependentCString( + (const char*)mGLContext->fGetString(LOCAL_GL_VERSION))); + msg += u"\nVendor: "_ns; + msg += NS_ConvertUTF8toUTF16(nsDependentCString( + (const char*)mGLContext->fGetString(LOCAL_GL_VENDOR))); + msg += u"\nRenderer: "_ns; + msg += NS_ConvertUTF8toUTF16(nsDependentCString( + (const char*)mGLContext->fGetString(LOCAL_GL_RENDERER))); + msg += u"\nFBO Texture Target: "_ns; + if (mFBOTextureTarget == LOCAL_GL_TEXTURE_2D) + msg += u"TEXTURE_2D"_ns; + else + msg += u"TEXTURE_RECTANGLE"_ns; + console->LogStringMessage(msg.get()); + } + + reporter.SetSuccessful(); + + return true; +} + +/* + * Returns a size that is equal to, or larger than and closest to, + * aSize where both width and height are powers of two. + * If the OpenGL setup is capable of using non-POT textures, + * then it will just return aSize. + */ +static IntSize CalculatePOTSize(const IntSize& aSize, GLContext* gl) { + if (CanUploadNonPowerOfTwo(gl)) return aSize; + + return IntSize(RoundUpPow2(aSize.width), RoundUpPow2(aSize.height)); +} + +gfx::Rect CompositorOGL::GetTextureCoordinates(gfx::Rect textureRect, + TextureSource* aTexture) { + // If the OpenGL setup does not support non-power-of-two textures then the + // texture's width and height will have been increased to the next + // power-of-two (unless already a power of two). In that case we must scale + // the texture coordinates to account for that. + if (!CanUploadNonPowerOfTwo(mGLContext)) { + const IntSize& textureSize = aTexture->GetSize(); + const IntSize potSize = CalculatePOTSize(textureSize, mGLContext); + if (potSize != textureSize) { + const float xScale = (float)textureSize.width / (float)potSize.width; + const float yScale = (float)textureSize.height / (float)potSize.height; + textureRect.Scale(xScale, yScale); + } + } + + return textureRect; +} + +void CompositorOGL::PrepareViewport(CompositingRenderTargetOGL* aRenderTarget) { + MOZ_ASSERT(aRenderTarget); + // Logical surface size. + const gfx::IntSize& size = aRenderTarget->GetSize(); + // Physical surface size. + const gfx::IntSize& phySize = aRenderTarget->GetPhysicalSize(); + + // Set the viewport correctly. + mGLContext->fViewport(mSurfaceOrigin.x, mSurfaceOrigin.y, phySize.width, + phySize.height); + + mViewportSize = size; + + if (!aRenderTarget->HasComplexProjection()) { + // We flip the view matrix around so that everything is right-side up; we're + // drawing directly into the window's back buffer, so this keeps things + // looking correct. + // XXX: We keep track of whether the window size changed, so we could skip + // this update if it hadn't changed since the last call. + + // Matrix to transform (0, 0, aWidth, aHeight) to viewport space (-1.0, 1.0, + // 2, 2) and flip the contents. + Matrix viewMatrix; + viewMatrix.PreTranslate(-1.0, 1.0); + viewMatrix.PreScale(2.0f / float(size.width), 2.0f / float(size.height)); + viewMatrix.PreScale(1.0f, -1.0f); + + MOZ_ASSERT(mCurrentRenderTarget, "No destination"); + + Matrix4x4 matrix3d = Matrix4x4::From2D(viewMatrix); + matrix3d._33 = 0.0f; + mProjMatrix = matrix3d; + mGLContext->fDepthRange(0.0f, 1.0f); + } else { + bool depthEnable; + float zNear, zFar; + aRenderTarget->GetProjection(mProjMatrix, depthEnable, zNear, zFar); + mGLContext->fDepthRange(zNear, zFar); + } +} + +already_AddRefed<CompositingRenderTarget> CompositorOGL::CreateRenderTarget( + const IntRect& aRect, SurfaceInitMode aInit) { + MOZ_ASSERT(!aRect.IsZeroArea(), + "Trying to create a render target of invalid size"); + + if (aRect.IsZeroArea()) { + return nullptr; + } + + if (!gl()) { + // CompositingRenderTargetOGL does not work without a gl context. + return nullptr; + } + + GLuint tex = 0; + GLuint fbo = 0; + IntRect rect = aRect; + IntSize fboSize; + CreateFBOWithTexture(rect, false, 0, &fbo, &tex, &fboSize); + return CompositingRenderTargetOGL::CreateForNewFBOAndTakeOwnership( + this, tex, fbo, aRect, aRect.TopLeft(), aRect.Size(), mFBOTextureTarget, + aInit); +} + +void CompositorOGL::SetRenderTarget(CompositingRenderTarget* aSurface) { + MOZ_ASSERT(aSurface); + CompositingRenderTargetOGL* surface = + static_cast<CompositingRenderTargetOGL*>(aSurface); + if (mCurrentRenderTarget != surface) { + mCurrentRenderTarget = surface; + surface->BindRenderTarget(); + } + + PrepareViewport(mCurrentRenderTarget); +} + +already_AddRefed<CompositingRenderTarget> +CompositorOGL::GetCurrentRenderTarget() const { + return do_AddRef(mCurrentRenderTarget); +} + +already_AddRefed<CompositingRenderTarget> CompositorOGL::GetWindowRenderTarget() + const { + return do_AddRef(mWindowRenderTarget); +} + +already_AddRefed<AsyncReadbackBuffer> CompositorOGL::CreateAsyncReadbackBuffer( + const IntSize& aSize) { + return MakeAndAddRef<AsyncReadbackBufferOGL>(mGLContext, aSize); +} + +bool CompositorOGL::ReadbackRenderTarget(CompositingRenderTarget* aSource, + AsyncReadbackBuffer* aDest) { + IntSize size = aSource->GetSize(); + MOZ_RELEASE_ASSERT(aDest->GetSize() == size); + + RefPtr<CompositingRenderTarget> previousTarget = GetCurrentRenderTarget(); + if (previousTarget != aSource) { + SetRenderTarget(aSource); + } + + ScopedPackState scopedPackState(mGLContext); + static_cast<AsyncReadbackBufferOGL*>(aDest)->Bind(); + + mGLContext->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA, + LOCAL_GL_UNSIGNED_BYTE, 0); + + if (previousTarget != aSource) { + SetRenderTarget(previousTarget); + } + return true; +} + +bool CompositorOGL::BlitRenderTarget(CompositingRenderTarget* aSource, + const gfx::IntSize& aSourceSize, + const gfx::IntSize& aDestSize) { + if (!mGLContext->IsSupported(GLFeature::framebuffer_blit)) { + return false; + } + CompositingRenderTargetOGL* source = + static_cast<CompositingRenderTargetOGL*>(aSource); + GLuint srcFBO = source->GetFBO(); + GLuint destFBO = mCurrentRenderTarget->GetFBO(); + mGLContext->BlitHelper()->BlitFramebufferToFramebuffer( + srcFBO, destFBO, IntRect(IntPoint(), aSourceSize), + IntRect(IntPoint(), aDestSize), LOCAL_GL_LINEAR); + return true; +} + +static GLenum GetFrameBufferInternalFormat( + GLContext* gl, GLuint aFrameBuffer, + mozilla::widget::CompositorWidget* aWidget) { + if (aFrameBuffer == 0) { // default framebuffer + return aWidget->GetGLFrameBufferFormat(); + } + return LOCAL_GL_RGBA; +} + +Maybe<IntRect> CompositorOGL::BeginFrameForWindow( + const nsIntRegion& aInvalidRegion, const Maybe<IntRect>& aClipRect, + const IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion) { + MOZ_RELEASE_ASSERT(!mTarget, "mTarget not cleared properly"); + return BeginFrame(aInvalidRegion, aClipRect, aRenderBounds, aOpaqueRegion); +} + +Maybe<IntRect> CompositorOGL::BeginFrame(const nsIntRegion& aInvalidRegion, + const Maybe<IntRect>& aClipRect, + const IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion) { + AUTO_PROFILER_LABEL("CompositorOGL::BeginFrame", GRAPHICS); + + MOZ_ASSERT(!mFrameInProgress, + "frame still in progress (should have called EndFrame"); + + IntRect rect; + if (mUseExternalSurfaceSize) { + rect = IntRect(IntPoint(), mSurfaceSize); + } else { + rect = aRenderBounds; + } + + // We can't draw anything to something with no area + // so just return + if (rect.IsZeroArea()) { + return Nothing(); + } + + // If the widget size changed, we have to force a MakeCurrent + // to make sure that GL sees the updated widget size. + if (mWidgetSize.ToUnknownSize() != rect.Size()) { + MakeCurrent(ForceMakeCurrent); + + mWidgetSize = LayoutDeviceIntSize::FromUnknownSize(rect.Size()); +#ifdef MOZ_WAYLAND + if (mWidget && mWidget->AsGTK()) { + mWidget->AsGTK()->SetEGLNativeWindowSize(mWidgetSize); + } +#endif + } else { + MakeCurrent(); + } + +#ifdef MOZ_WIDGET_ANDROID + java::GeckoSurfaceTexture::DestroyUnused((int64_t)mGLContext.get()); + mGLContext->MakeCurrent(); // DestroyUnused can change the current context! +#endif + + // Default blend function implements "OVER" + mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, + LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); + mGLContext->fEnable(LOCAL_GL_BLEND); + + RefPtr<CompositingRenderTarget> rt; + if (mCanRenderToDefaultFramebuffer) { + rt = CompositingRenderTargetOGL::CreateForWindow(this, rect.Size()); + } else if (mTarget) { + rt = CreateRenderTarget(rect, INIT_MODE_CLEAR); + } else { + MOZ_CRASH("Unexpected call"); + } + + if (!rt) { + return Nothing(); + } + + // We're about to actually draw a frame. + mFrameInProgress = true; + + SetRenderTarget(rt); + mWindowRenderTarget = mCurrentRenderTarget; + + for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + mCurrentFrameInvalidRegion.OrWith( + IntRect(r.X(), FlipY(r.YMost()), r.Width(), r.Height())); + } + // Check to see if there is any transparent dirty region that would require + // clearing. If not, just invalidate the framebuffer if supported. + // TODO: Currently we initialize the clear region to the widget bounds as + // SwapBuffers will update the entire framebuffer. On platforms that support + // damage regions, we could initialize this to mCurrentFrameInvalidRegion. + IntRegion regionToClear(rect); + regionToClear.SubOut(aOpaqueRegion); + GLbitfield clearBits = LOCAL_GL_DEPTH_BUFFER_BIT; + if (regionToClear.IsEmpty() && + mGLContext->IsSupported(GLFeature::invalidate_framebuffer)) { + GLenum attachments[] = {LOCAL_GL_COLOR}; + mGLContext->fInvalidateFramebuffer( + LOCAL_GL_FRAMEBUFFER, MOZ_ARRAY_LENGTH(attachments), attachments); + } else { + clearBits |= LOCAL_GL_COLOR_BUFFER_BIT; + } + +#if defined(MOZ_WIDGET_ANDROID) + if ((mSurfaceOrigin.x > 0) || (mSurfaceOrigin.y > 0)) { + mGLContext->fClearColor( + StaticPrefs::gfx_compositor_override_clear_color_r(), + StaticPrefs::gfx_compositor_override_clear_color_g(), + StaticPrefs::gfx_compositor_override_clear_color_b(), + StaticPrefs::gfx_compositor_override_clear_color_a()); + } else { + mGLContext->fClearColor(mClearColor.r, mClearColor.g, mClearColor.b, + mClearColor.a); + } +#else + mGLContext->fClearColor(mClearColor.r, mClearColor.g, mClearColor.b, + mClearColor.a); +#endif // defined(MOZ_WIDGET_ANDROID) + mGLContext->fClear(clearBits); + + return Some(rect); +} + +void CompositorOGL::CreateFBOWithTexture(const gfx::IntRect& aRect, + bool aCopyFromSource, + GLuint aSourceFrameBuffer, + GLuint* aFBO, GLuint* aTexture, + gfx::IntSize* aAllocSize) { + *aTexture = + CreateTexture(aRect, aCopyFromSource, aSourceFrameBuffer, aAllocSize); + mGLContext->fGenFramebuffers(1, aFBO); +} + +// Should be called after calls to fReadPixels or fCopyTexImage2D, and other +// GL read calls. +static void WorkAroundAppleIntelHD3000GraphicsGLDriverBug(GLContext* aGL) { +#ifdef XP_MACOSX + if (aGL->WorkAroundDriverBugs() && + aGL->Renderer() == GLRenderer::IntelHD3000) { + // Work around a bug in the Apple Intel HD Graphics 3000 driver (bug + // 1586627, filed with Apple as FB7379358). This bug has been found present + // on 10.9.3 and on 10.13.6, so it likely affects all shipped versions of + // this driver. (macOS 10.14 does not support this GPU.) + // The bug manifests as follows: Reading from a framebuffer puts that + // framebuffer into a state such that deleting that framebuffer can break + // other framebuffers in certain cases. More specifically, if you have two + // framebuffers A and B, the following sequence of events breaks subsequent + // drawing to B: + // 1. A becomes "most recently read-from framebuffer". + // 2. B is drawn to. + // 3. A is deleted, and other GL state (such as GL_SCISSOR enabled state) + // is touched. + // 4. B is drawn to again. + // Now all draws to framebuffer B, including the draw from step 4, will + // render at the wrong position and upside down. + // + // When AfterGLReadCall() is called, the currently bound framebuffer is the + // framebuffer that has been read from most recently. So in the presence of + // this bug, deleting this framebuffer has now become dangerous. We work + // around the bug by creating a new short-lived framebuffer, making that new + // framebuffer the most recently read-from framebuffer (using + // glCopyTexImage2D), and then deleting it under controlled circumstances. + // This deletion is not affected by the bug because our deletion call is not + // interleaved with draw calls to another framebuffer and a touching of the + // GL scissor enabled state. + + ScopedTexture texForReading(aGL); + { + // Initialize a 1x1 texture. + ScopedBindTexture autoBindTexForReading(aGL, texForReading); + aGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, 0, + LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, nullptr); + aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_LINEAR); + aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_LINEAR); + } + // Make a framebuffer around the texture. + ScopedFramebufferForTexture autoFBForReading(aGL, texForReading); + if (autoFBForReading.IsComplete()) { + // "Read" from the framebuffer, by initializing a new texture using + // glCopyTexImage2D. This flips the bad bit on autoFBForReading.FB(). + ScopedBindFramebuffer autoFB(aGL, autoFBForReading.FB()); + ScopedTexture texReadingDest(aGL); + ScopedBindTexture autoBindTexReadingDest(aGL, texReadingDest); + aGL->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 0, 0, 1, 1, + 0); + } + // When autoFBForReading goes out of scope, the "poisoned" framebuffer is + // deleted, and the bad state seems to go away along with it. + } +#endif +} + +GLuint CompositorOGL::CreateTexture(const IntRect& aRect, bool aCopyFromSource, + GLuint aSourceFrameBuffer, + IntSize* aAllocSize) { + // we're about to create a framebuffer backed by textures to use as an + // intermediate surface. What to do if its size (as given by aRect) would + // exceed the maximum texture size supported by the GL? The present code + // chooses the compromise of just clamping the framebuffer's size to the max + // supported size. This gives us a lower resolution rendering of the + // intermediate surface (children layers). See bug 827170 for a discussion. + IntRect clampedRect = aRect; + int32_t maxTexSize = GetMaxTextureSize(); + clampedRect.SetWidth(std::min(clampedRect.Width(), maxTexSize)); + clampedRect.SetHeight(std::min(clampedRect.Height(), maxTexSize)); + + auto clampedRectWidth = clampedRect.Width(); + auto clampedRectHeight = clampedRect.Height(); + + GLuint tex; + + mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); + mGLContext->fGenTextures(1, &tex); + mGLContext->fBindTexture(mFBOTextureTarget, tex); + + if (aCopyFromSource) { + GLuint curFBO = mCurrentRenderTarget->GetFBO(); + if (curFBO != aSourceFrameBuffer) { + mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, aSourceFrameBuffer); + } + + // We're going to create an RGBA temporary fbo. But to + // CopyTexImage() from the current framebuffer, the framebuffer's + // format has to be compatible with the new texture's. So we + // check the format of the framebuffer here and take a slow path + // if it's incompatible. + GLenum format = + GetFrameBufferInternalFormat(gl(), aSourceFrameBuffer, mWidget); + + bool isFormatCompatibleWithRGBA = + gl()->IsGLES() ? (format == LOCAL_GL_RGBA) : true; + + if (isFormatCompatibleWithRGBA) { + mGLContext->fCopyTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, + clampedRect.X(), FlipY(clampedRect.YMost()), + clampedRectWidth, clampedRectHeight, 0); + WorkAroundAppleIntelHD3000GraphicsGLDriverBug(mGLContext); + } else { + // Curses, incompatible formats. Take a slow path. + + // RGBA + size_t bufferSize = clampedRectWidth * clampedRectHeight * 4; + auto buf = MakeUnique<uint8_t[]>(bufferSize); + + mGLContext->fReadPixels(clampedRect.X(), clampedRect.Y(), + clampedRectWidth, clampedRectHeight, + LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, buf.get()); + WorkAroundAppleIntelHD3000GraphicsGLDriverBug(mGLContext); + mGLContext->fTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, + clampedRectWidth, clampedRectHeight, 0, + LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, buf.get()); + } + + GLenum error = mGLContext->fGetError(); + if (error != LOCAL_GL_NO_ERROR) { + nsAutoCString msg; + msg.AppendPrintf( + "Texture initialization failed! -- error 0x%x, Source %d, Source " + "format %d, RGBA Compat %d", + error, aSourceFrameBuffer, format, isFormatCompatibleWithRGBA); + NS_ERROR(msg.get()); + } + } else { + mGLContext->fTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, + clampedRectWidth, clampedRectHeight, 0, + LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, nullptr); + } + mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_LINEAR); + mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_LINEAR); + mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_WRAP_S, + LOCAL_GL_CLAMP_TO_EDGE); + mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_WRAP_T, + LOCAL_GL_CLAMP_TO_EDGE); + mGLContext->fBindTexture(mFBOTextureTarget, 0); + + if (aAllocSize) { + aAllocSize->width = clampedRectWidth; + aAllocSize->height = clampedRectHeight; + } + + return tex; +} + +ShaderConfigOGL CompositorOGL::GetShaderConfigFor(Effect* aEffect, + bool aDEAAEnabled) const { + ShaderConfigOGL config; + + switch (aEffect->mType) { + case EffectTypes::YCBCR: { + config.SetYCbCr(true); + EffectYCbCr* effectYCbCr = static_cast<EffectYCbCr*>(aEffect); + config.SetColorMultiplier( + RescalingFactorForColorDepth(effectYCbCr->mColorDepth)); + config.SetTextureTarget( + effectYCbCr->mTexture->AsSourceOGL()->GetTextureTarget()); + break; + } + case EffectTypes::NV12: + config.SetNV12(true); + if (gl()->IsExtensionSupported(gl::GLContext::ARB_texture_rectangle)) { + config.SetTextureTarget(LOCAL_GL_TEXTURE_RECTANGLE_ARB); + } else { + config.SetTextureTarget(LOCAL_GL_TEXTURE_2D); + } + break; + default: { + MOZ_ASSERT(aEffect->mType == EffectTypes::RGB); + TexturedEffect* texturedEffect = static_cast<TexturedEffect*>(aEffect); + TextureSourceOGL* source = texturedEffect->mTexture->AsSourceOGL(); + MOZ_ASSERT_IF( + source->GetTextureTarget() == LOCAL_GL_TEXTURE_EXTERNAL, + source->GetFormat() == gfx::SurfaceFormat::R8G8B8A8 || + source->GetFormat() == gfx::SurfaceFormat::R8G8B8X8 || + source->GetFormat() == gfx::SurfaceFormat::B8G8R8A8 || + source->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 || + source->GetFormat() == gfx::SurfaceFormat::R5G6B5_UINT16); + MOZ_ASSERT_IF( + source->GetTextureTarget() == LOCAL_GL_TEXTURE_RECTANGLE_ARB, + source->GetFormat() == gfx::SurfaceFormat::R8G8B8A8 || + source->GetFormat() == gfx::SurfaceFormat::R8G8B8X8 || + source->GetFormat() == gfx::SurfaceFormat::R5G6B5_UINT16 || + source->GetFormat() == gfx::SurfaceFormat::YUV422); + config = ShaderConfigFromTargetAndFormat(source->GetTextureTarget(), + source->GetFormat()); + if (!texturedEffect->mPremultiplied) { + config.SetNoPremultipliedAlpha(); + } + break; + } + } + config.SetDEAA(aDEAAEnabled); + return config; +} + +ShaderProgramOGL* CompositorOGL::GetShaderProgramFor( + const ShaderConfigOGL& aConfig) { + ShaderProgramOGL* shader = mProgramsHolder->GetShaderProgramFor(aConfig); + return shader; +} + +void CompositorOGL::ResetProgram() { mProgramsHolder->ResetCurrentProgram(); } + +static bool SetBlendMode(GLContext* aGL, gfx::CompositionOp aBlendMode, + bool aIsPremultiplied = true) { + if (BlendOpIsMixBlendMode(aBlendMode)) { + // Mix-blend modes require an extra step (or more) that cannot be expressed + // in the fixed-function blending capabilities of opengl. We handle them + // separately in shaders, and the shaders assume we will use our default + // blend function for compositing (premultiplied OP_OVER). + return false; + } + if (aBlendMode == gfx::CompositionOp::OP_OVER && aIsPremultiplied) { + return false; + } + + GLenum srcBlend; + GLenum dstBlend; + GLenum srcAlphaBlend = LOCAL_GL_ONE; + GLenum dstAlphaBlend = LOCAL_GL_ONE_MINUS_SRC_ALPHA; + + switch (aBlendMode) { + case gfx::CompositionOp::OP_OVER: + MOZ_ASSERT(!aIsPremultiplied); + srcBlend = LOCAL_GL_SRC_ALPHA; + dstBlend = LOCAL_GL_ONE_MINUS_SRC_ALPHA; + break; + case gfx::CompositionOp::OP_SOURCE: + srcBlend = aIsPremultiplied ? LOCAL_GL_ONE : LOCAL_GL_SRC_ALPHA; + dstBlend = LOCAL_GL_ZERO; + srcAlphaBlend = LOCAL_GL_ONE; + dstAlphaBlend = LOCAL_GL_ZERO; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported blend mode!"); + return false; + } + + aGL->fBlendFuncSeparate(srcBlend, dstBlend, srcAlphaBlend, dstAlphaBlend); + return true; +} + +gfx::Point3D CompositorOGL::GetLineCoefficients(const gfx::Point& aPoint1, + const gfx::Point& aPoint2) { + // Return standard coefficients for a line between aPoint1 and aPoint2 + // for standard line equation: + // + // Ax + By + C = 0 + // + // A = (p1.y – p2.y) + // B = (p2.x – p1.x) + // C = (p1.x * p2.y) – (p2.x * p1.y) + + gfx::Point3D coeffecients; + coeffecients.x = aPoint1.y - aPoint2.y; + coeffecients.y = aPoint2.x - aPoint1.x; + coeffecients.z = + aPoint1.x.value * aPoint2.y.value - aPoint2.x.value * aPoint1.y.value; + + coeffecients *= 1.0f / sqrtf(coeffecients.x * coeffecients.x + + coeffecients.y * coeffecients.y); + + // Offset outwards by 0.5 pixel as the edge is considered to be 1 pixel + // wide and included within the interior of the polygon + coeffecients.z += 0.5f; + + return coeffecients; +} + +void CompositorOGL::DrawQuad(const Rect& aRect, const IntRect& aClipRect, + const EffectChain& aEffectChain, Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + AUTO_PROFILER_LABEL("CompositorOGL::DrawQuad", GRAPHICS); + + DrawGeometry(aRect, aRect, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect); +} + +template <typename Geometry> +void CompositorOGL::DrawGeometry(const Geometry& aGeometry, + const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + MOZ_ASSERT(mFrameInProgress, "frame not started"); + MOZ_ASSERT(mCurrentRenderTarget, "No destination"); + + MakeCurrent(); + + // Convert aClipRect into render target space, and intersect it with the + // render target's clip. + IntRect clipRect = aClipRect + mCurrentRenderTarget->GetClipSpaceOrigin(); + if (Maybe<IntRect> rtClip = mCurrentRenderTarget->GetClipRect()) { + clipRect = clipRect.Intersect(*rtClip); + } + + Rect destRect = aTransform.TransformAndClipBounds( + aRect, Rect(mCurrentRenderTarget->GetRect().Intersect(clipRect))); + if (destRect.IsEmpty()) { + return; + } + + // Move clipRect into device space. + IntPoint offset = mCurrentRenderTarget->GetOrigin(); + clipRect -= offset; + + if (!mTarget && mCurrentRenderTarget->IsWindow()) { + clipRect.MoveBy(mSurfaceOrigin.x, -mSurfaceOrigin.y); + } + + ScopedGLState scopedScissorTestState(mGLContext, LOCAL_GL_SCISSOR_TEST, true); + ScopedScissorRect autoScissorRect(mGLContext, clipRect.X(), + FlipY(clipRect.Y() + clipRect.Height()), + clipRect.Width(), clipRect.Height()); + + // Only apply DEAA to quads that have been transformed such that aliasing + // could be visible + bool bEnableAA = StaticPrefs::layers_deaa_enabled() && + !aTransform.Is2DIntegerTranslation(); + + ShaderConfigOGL config = + GetShaderConfigFor(aEffectChain.mPrimaryEffect, bEnableAA); + + config.SetOpacity(aOpacity != 1.f); + ApplyPrimitiveConfig(config, aGeometry); + + ShaderProgramOGL* program = mProgramsHolder->ActivateProgram(config); + if (!program) { + return; + } + program->SetProjectionMatrix(mProjMatrix); + program->SetLayerTransform(aTransform); + program->SetRenderOffset(offset.x, offset.y); + + if (aOpacity != 1.f) program->SetLayerOpacity(aOpacity); + + if (config.mFeatures & ENABLE_TEXTURE_RECT) { + TextureSourceOGL* source = nullptr; + TexturedEffect* texturedEffect = + static_cast<TexturedEffect*>(aEffectChain.mPrimaryEffect.get()); + source = texturedEffect->mTexture->AsSourceOGL(); + // This is used by IOSurface that use 0,0...w,h coordinate rather then + // 0,0..1,1. + program->SetTexCoordMultiplier(source->GetSize().width, + source->GetSize().height); + } + + // XXX kip - These calculations could be performed once per layer rather than + // for every tile. This might belong in Compositor.cpp once DEAA + // is implemented for DirectX. + if (bEnableAA) { + // Calculate the transformed vertices of aVisibleRect in screen space + // pixels, mirroring the calculations in the vertex shader + Matrix4x4 flatTransform = aTransform; + flatTransform.PostTranslate(-offset.x, -offset.y, 0.0f); + flatTransform *= mProjMatrix; + + Rect viewportClip = Rect(-1.0f, -1.0f, 2.0f, 2.0f); + size_t edgeCount = 0; + Point3D coefficients[4]; + + Point points[Matrix4x4::kTransformAndClipRectMaxVerts]; + size_t pointCount = + flatTransform.TransformAndClipRect(aVisibleRect, viewportClip, points); + for (size_t i = 0; i < pointCount; i++) { + points[i] = Point((points[i].x * 0.5f + 0.5f) * mViewportSize.width, + (points[i].y * 0.5f + 0.5f) * mViewportSize.height); + } + if (pointCount > 2) { + // Use shoelace formula on a triangle in the clipped quad to determine if + // winding order is reversed. Iterate through the triangles until one is + // found with a non-zero area. + float winding = 0.0f; + size_t wp = 0; + while (winding == 0.0f && wp < pointCount) { + int wp1 = (wp + 1) % pointCount; + int wp2 = (wp + 2) % pointCount; + winding = (points[wp1].x - points[wp].x).value * + (points[wp1].y + points[wp].y).value + + (points[wp2].x - points[wp1].x).value * + (points[wp2].y + points[wp1].y).value + + (points[wp].x - points[wp2].x).value * + (points[wp].y + points[wp2].y).value; + wp++; + } + bool frontFacing = winding >= 0.0f; + + // Calculate the line coefficients used by the DEAA shader to determine + // the sub-pixel coverage of the edge pixels + for (size_t i = 0; i < pointCount; i++) { + const Point& p1 = points[i]; + const Point& p2 = points[(i + 1) % pointCount]; + // Create a DEAA edge for any non-straight lines, to a maximum of 4 + if (p1.x != p2.x && p1.y != p2.y && edgeCount < 4) { + if (frontFacing) { + coefficients[edgeCount++] = GetLineCoefficients(p2, p1); + } else { + coefficients[edgeCount++] = GetLineCoefficients(p1, p2); + } + } + } + } + + // The coefficients that are not needed must not cull any fragments. + // We fill these unused coefficients with a clipping plane that has no + // effect. + for (size_t i = edgeCount; i < 4; i++) { + coefficients[i] = Point3D(0.0f, 1.0f, mViewportSize.height); + } + + // Set uniforms required by DEAA shader + Matrix4x4 transformInverted = aTransform; + transformInverted.Invert(); + program->SetLayerTransformInverse(transformInverted); + program->SetDEAAEdges(coefficients); + program->SetVisibleCenter(aVisibleRect.Center()); + program->SetViewportSize(mViewportSize); + } + + bool didSetBlendMode = false; + + switch (aEffectChain.mPrimaryEffect->mType) { + case EffectTypes::RGB: { + TexturedEffect* texturedEffect = + static_cast<TexturedEffect*>(aEffectChain.mPrimaryEffect.get()); + TextureSource* source = texturedEffect->mTexture; + + didSetBlendMode = SetBlendMode(gl(), gfx::CompositionOp::OP_OVER, + texturedEffect->mPremultiplied); + + gfx::SamplingFilter samplingFilter = texturedEffect->mSamplingFilter; + + source->AsSourceOGL()->BindTexture(LOCAL_GL_TEXTURE0, samplingFilter); + + program->SetTextureUnit(0); + + Matrix4x4 textureTransform = source->AsSourceOGL()->GetTextureTransform(); + program->SetTextureTransform(textureTransform); + + BindAndDrawGeometryWithTextureRect( + program, aGeometry, texturedEffect->mTextureCoords, source); + source->AsSourceOGL()->MaybeFenceTexture(); + } break; + case EffectTypes::YCBCR: { + EffectYCbCr* effectYCbCr = + static_cast<EffectYCbCr*>(aEffectChain.mPrimaryEffect.get()); + TextureSource* sourceYCbCr = effectYCbCr->mTexture; + const int Y = 0, Cb = 1, Cr = 2; + TextureSourceOGL* sourceY = sourceYCbCr->GetSubSource(Y)->AsSourceOGL(); + TextureSourceOGL* sourceCb = sourceYCbCr->GetSubSource(Cb)->AsSourceOGL(); + TextureSourceOGL* sourceCr = sourceYCbCr->GetSubSource(Cr)->AsSourceOGL(); + + if (!sourceY || !sourceCb || !sourceCr) { + NS_WARNING("Invalid layer texture."); + return; + } + + sourceY->BindTexture(LOCAL_GL_TEXTURE0, effectYCbCr->mSamplingFilter); + sourceCb->BindTexture(LOCAL_GL_TEXTURE1, effectYCbCr->mSamplingFilter); + sourceCr->BindTexture(LOCAL_GL_TEXTURE2, effectYCbCr->mSamplingFilter); + + if (config.mFeatures & ENABLE_TEXTURE_RECT) { + // This is used by IOSurface that use 0,0...w,h coordinate rather then + // 0,0..1,1. + program->SetCbCrTexCoordMultiplier(sourceCb->GetSize().width, + sourceCb->GetSize().height); + } + + program->SetYCbCrTextureUnits(Y, Cb, Cr); + program->SetTextureTransform(Matrix4x4()); + program->SetYUVColorSpace(effectYCbCr->mYUVColorSpace); + + BindAndDrawGeometryWithTextureRect(program, aGeometry, + effectYCbCr->mTextureCoords, + sourceYCbCr->GetSubSource(Y)); + sourceY->MaybeFenceTexture(); + sourceCb->MaybeFenceTexture(); + sourceCr->MaybeFenceTexture(); + } break; + case EffectTypes::NV12: { + EffectNV12* effectNV12 = + static_cast<EffectNV12*>(aEffectChain.mPrimaryEffect.get()); + TextureSource* sourceNV12 = effectNV12->mTexture; + const int Y = 0, CbCr = 1; + TextureSourceOGL* sourceY = sourceNV12->GetSubSource(Y)->AsSourceOGL(); + TextureSourceOGL* sourceCbCr = + sourceNV12->GetSubSource(CbCr)->AsSourceOGL(); + + if (!sourceY || !sourceCbCr) { + NS_WARNING("Invalid layer texture."); + return; + } + + sourceY->BindTexture(LOCAL_GL_TEXTURE0, effectNV12->mSamplingFilter); + sourceCbCr->BindTexture(LOCAL_GL_TEXTURE1, effectNV12->mSamplingFilter); + + if (config.mFeatures & ENABLE_TEXTURE_RECT) { + // This is used by IOSurface that use 0,0...w,h coordinate rather then + // 0,0..1,1. + program->SetCbCrTexCoordMultiplier(sourceCbCr->GetSize().width, + sourceCbCr->GetSize().height); + } + + program->SetNV12TextureUnits(Y, CbCr); + program->SetTextureTransform(Matrix4x4()); + program->SetYUVColorSpace(effectNV12->mYUVColorSpace); + + BindAndDrawGeometryWithTextureRect(program, aGeometry, + effectNV12->mTextureCoords, + sourceNV12->GetSubSource(Y)); + sourceY->MaybeFenceTexture(); + sourceCbCr->MaybeFenceTexture(); + } break; + default: + MOZ_ASSERT(false, "Unhandled effect type"); + break; + } + + if (didSetBlendMode) { + gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, + LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); + } + + // in case rendering has used some other GL context + MakeCurrent(); +} + +void CompositorOGL::BindAndDrawGeometry(ShaderProgramOGL* aProgram, + const gfx::Rect& aRect) { + BindAndDrawQuad(aProgram, aRect); +} + +void CompositorOGL::BindAndDrawGeometry( + ShaderProgramOGL* aProgram, + const nsTArray<gfx::TexturedTriangle>& aTriangles) { + NS_ASSERTION(aProgram->HasInitialized(), + "Shader program not correctly initialized"); + + const nsTArray<TexturedVertex> vertices = + TexturedTrianglesToVertexArray(aTriangles); + + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTriangleVBO); + mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER, + vertices.Length() * sizeof(TexturedVertex), + vertices.Elements(), LOCAL_GL_STREAM_DRAW); + + const GLsizei stride = 4 * sizeof(GLfloat); + InitializeVAO(kCoordinateAttributeIndex, 2, stride, 0); + InitializeVAO(kTexCoordinateAttributeIndex, 2, stride, 2 * sizeof(GLfloat)); + + mGLContext->fDrawArrays(LOCAL_GL_TRIANGLES, 0, vertices.Length()); + + mGLContext->fDisableVertexAttribArray(kCoordinateAttributeIndex); + mGLContext->fDisableVertexAttribArray(kTexCoordinateAttributeIndex); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); +} + +// |aRect| is the rectangle we want to draw to. We will draw it with +// up to 4 draw commands if necessary to avoid wrapping. +// |aTexCoordRect| is the rectangle from the texture that we want to +// draw using the given program. +// |aTexture| is the texture we are drawing. Its actual size can be +// larger than the rectangle given by |texCoordRect|. +void CompositorOGL::BindAndDrawGeometryWithTextureRect( + ShaderProgramOGL* aProg, const Rect& aRect, const Rect& aTexCoordRect, + TextureSource* aTexture) { + Rect scaledTexCoordRect = GetTextureCoordinates(aTexCoordRect, aTexture); + Rect layerRects[4]; + Rect textureRects[4]; + size_t rects = DecomposeIntoNoRepeatRects(aRect, scaledTexCoordRect, + &layerRects, &textureRects); + + BindAndDrawQuads(aProg, rects, layerRects, textureRects); +} + +void CompositorOGL::BindAndDrawGeometryWithTextureRect( + ShaderProgramOGL* aProg, const nsTArray<gfx::TexturedTriangle>& aTriangles, + const gfx::Rect& aTexCoordRect, TextureSource* aTexture) { + BindAndDrawGeometry(aProg, aTriangles); +} + +void CompositorOGL::BindAndDrawQuads(ShaderProgramOGL* aProg, int aQuads, + const Rect* aLayerRects, + const Rect* aTextureRects) { + NS_ASSERTION(aProg->HasInitialized(), + "Shader program not correctly initialized"); + + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); + InitializeVAO(kCoordinateAttributeIndex, 4, 0, 0); + + aProg->SetLayerRects(aLayerRects); + if (aProg->GetTextureCount() > 0) { + aProg->SetTextureRects(aTextureRects); + } + + // We are using GL_TRIANGLES here because the Mac Intel drivers fail to + // properly process uniform arrays with GL_TRIANGLE_STRIP. Go figure. + mGLContext->fDrawArrays(LOCAL_GL_TRIANGLES, 0, 6 * aQuads); + mGLContext->fDisableVertexAttribArray(kCoordinateAttributeIndex); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); +} + +void CompositorOGL::InitializeVAO(const GLuint aAttrib, const GLint aComponents, + const GLsizei aStride, const size_t aOffset) { + mGLContext->fVertexAttribPointer(aAttrib, aComponents, LOCAL_GL_FLOAT, + LOCAL_GL_FALSE, aStride, + reinterpret_cast<GLvoid*>(aOffset)); + mGLContext->fEnableVertexAttribArray(aAttrib); +} + +#ifdef MOZ_DUMP_PAINTING +template <typename T> +void WriteSnapshotToDumpFile_internal(T* aObj, DataSourceSurface* aSurf) { + nsCString string(aObj->Name()); + string.Append('-'); + string.AppendInt((uint64_t)aObj); + if (gfxUtils::sDumpPaintFile != stderr) { + fprintf_stderr(gfxUtils::sDumpPaintFile, R"(array["%s"]=")", + string.BeginReading()); + } + gfxUtils::DumpAsDataURI(aSurf, gfxUtils::sDumpPaintFile); + if (gfxUtils::sDumpPaintFile != stderr) { + fprintf_stderr(gfxUtils::sDumpPaintFile, R"(";)"); + } +} + +void WriteSnapshotToDumpFile(Compositor* aCompositor, DrawTarget* aTarget) { + RefPtr<SourceSurface> surf = aTarget->Snapshot(); + RefPtr<DataSourceSurface> dSurf = surf->GetDataSurface(); + WriteSnapshotToDumpFile_internal(aCompositor, dSurf); +} +#endif + +void CompositorOGL::EndFrame() { + AUTO_PROFILER_LABEL("CompositorOGL::EndFrame", GRAPHICS); + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::MOZ_DISABLE_FORCE_PRESENT()) { + LayoutDeviceIntSize size; + if (mUseExternalSurfaceSize) { + size = LayoutDeviceIntSize(mSurfaceSize.width, mSurfaceSize.height); + } else { + size = mWidget->GetClientSize(); + } + RefPtr<DrawTarget> target = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(size.width, size.height), SurfaceFormat::B8G8R8A8); + if (target) { + CopyToTarget(target, nsIntPoint(), Matrix()); + WriteSnapshotToDumpFile(this, target); + } + } +#endif + + mFrameInProgress = false; + mShouldInvalidateWindow = false; + + if (mTarget) { + CopyToTarget(mTarget, mTargetBounds.TopLeft(), Matrix()); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); + mTarget = nullptr; + mWindowRenderTarget = nullptr; + mCurrentRenderTarget = nullptr; + Compositor::EndFrame(); + return; + } + + mWindowRenderTarget = nullptr; + mCurrentRenderTarget = nullptr; + + if (mTexturePool) { + mTexturePool->EndFrame(); + } + + InsertFrameDoneSync(); + + mGLContext->SetDamage(mCurrentFrameInvalidRegion); + mGLContext->SwapBuffers(); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); + + // Unbind all textures + for (GLuint i = 0; i <= 4; i++) { + mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0 + i); + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0); + if (!mGLContext->IsGLES()) { + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0); + } + } + + mCurrentFrameInvalidRegion.SetEmpty(); + + Compositor::EndFrame(); +} + +void CompositorOGL::InsertFrameDoneSync() { +#ifdef XP_MACOSX + // Only do this on macOS. + // On other platforms, SwapBuffers automatically applies back-pressure. + if (mThisFrameDoneSync) { + mGLContext->fDeleteSync(mThisFrameDoneSync); + } + mThisFrameDoneSync = + mGLContext->fFenceSync(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +#elif defined(MOZ_WIDGET_ANDROID) + const auto& gle = gl::GLContextEGL::Cast(mGLContext); + const auto& egl = gle->mEgl; + + EGLSync sync = nullptr; + if (AndroidHardwareBufferApi::Get()) { + sync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); + } + if (sync) { + int fenceFd = egl->fDupNativeFenceFDANDROID(sync); + if (fenceFd >= 0) { + mReleaseFenceFd = ipc::FileDescriptor(UniqueFileHandle(fenceFd)); + } + egl->fDestroySync(sync); + sync = nullptr; + } +#endif +} + +ipc::FileDescriptor CompositorOGL::GetReleaseFence() { return mReleaseFenceFd; } + +bool CompositorOGL::NeedToRecreateFullWindowRenderTarget() const { + if (!ShouldRecordFrames()) { + return false; + } + if (!mFullWindowRenderTarget) { + return true; + } + IntSize windowSize = mWidget->GetClientSize().ToUnknownSize(); + return mFullWindowRenderTarget->GetSize() != windowSize; +} + +void CompositorOGL::SetDestinationSurfaceSize(const IntSize& aSize) { + mSurfaceSize.width = aSize.width; + mSurfaceSize.height = aSize.height; +} + +void CompositorOGL::CopyToTarget(DrawTarget* aTarget, + const nsIntPoint& aTopLeft, + const gfx::Matrix& aTransform) { + MOZ_ASSERT(aTarget); + IntRect rect; + if (mUseExternalSurfaceSize) { + rect = IntRect(0, 0, mSurfaceSize.width, mSurfaceSize.height); + } else { + rect = IntRect(0, 0, mWidgetSize.width, mWidgetSize.height); + } + GLint width = rect.Width(); + GLint height = rect.Height(); + + if ((int64_t(width) * int64_t(height) * int64_t(4)) > INT32_MAX) { + NS_ERROR("Widget size too big - integer overflow!"); + return; + } + + RefPtr<DataSourceSurface> source = Factory::CreateDataSourceSurface( + rect.Size(), gfx::SurfaceFormat::B8G8R8A8); + if (NS_WARN_IF(!source)) { + return; + } + + ReadPixelsIntoDataSurface(mGLContext, source); + + // Map from GL space to Cairo space and reverse the world transform. + Matrix glToCairoTransform = aTransform; + glToCairoTransform.Invert(); + glToCairoTransform.PreScale(1.0, -1.0); + glToCairoTransform.PreTranslate(0.0, -height); + + glToCairoTransform.PostTranslate(-aTopLeft.x, -aTopLeft.y); + + Matrix oldMatrix = aTarget->GetTransform(); + aTarget->SetTransform(glToCairoTransform); + Rect floatRect = Rect(rect.X(), rect.Y(), width, height); + aTarget->DrawSurface(source, floatRect, floatRect, DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + aTarget->SetTransform(oldMatrix); + aTarget->Flush(); +} + +void CompositorOGL::Pause() { +#ifdef MOZ_WIDGET_ANDROID + if (!gl() || gl()->IsDestroyed()) return; + // ReleaseSurface internally calls MakeCurrent + gl()->ReleaseSurface(); +#elif defined(MOZ_WAYLAND) + // ReleaseSurface internally calls MakeCurrent + gl()->ReleaseSurface(); +#endif +} + +bool CompositorOGL::Resume() { +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT) || \ + defined(MOZ_WAYLAND) + if (!gl() || gl()->IsDestroyed()) return false; + + // RenewSurface internally calls MakeCurrent. + return gl()->RenewSurface(GetWidget()); +#else + return true; +#endif +} + +already_AddRefed<DataTextureSource> CompositorOGL::CreateDataTextureSource( + TextureFlags aFlags) { + if (!gl()) { + return nullptr; + } + + return MakeAndAddRef<TextureImageTextureSourceOGL>(this, aFlags); +} + +int32_t CompositorOGL::GetMaxTextureSize() const { + MOZ_ASSERT(mGLContext); + GLint texSize = 0; + mGLContext->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &texSize); + MOZ_ASSERT(texSize != 0); + return texSize; +} + +void CompositorOGL::MakeCurrent(MakeCurrentFlags aFlags) { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + mGLContext->MakeCurrent(aFlags & ForceMakeCurrent); +} + +GLuint CompositorOGL::GetTemporaryTexture(GLenum aTarget, GLenum aUnit) { + if (!mTexturePool) { + mTexturePool = new PerUnitTexturePoolOGL(gl()); + } + return mTexturePool->GetTexture(aTarget, aUnit); +} + +GLuint PerUnitTexturePoolOGL::GetTexture(GLenum aTarget, GLenum aTextureUnit) { + if (mTextureTarget == 0) { + mTextureTarget = aTarget; + } + MOZ_ASSERT(mTextureTarget == aTarget); + + size_t index = aTextureUnit - LOCAL_GL_TEXTURE0; + // lazily grow the array of temporary textures + if (mTextures.Length() <= index) { + size_t prevLength = mTextures.Length(); + mTextures.SetLength(index + 1); + for (unsigned int i = prevLength; i <= index; ++i) { + mTextures[i] = 0; + } + } + // lazily initialize the temporary textures + if (!mTextures[index]) { + if (!mGL->MakeCurrent()) { + return 0; + } + mGL->fGenTextures(1, &mTextures[index]); + mGL->fBindTexture(aTarget, mTextures[index]); + mGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_WRAP_S, + LOCAL_GL_CLAMP_TO_EDGE); + mGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_WRAP_T, + LOCAL_GL_CLAMP_TO_EDGE); + } + return mTextures[index]; +} + +void PerUnitTexturePoolOGL::DestroyTextures() { + if (mGL && mGL->MakeCurrent()) { + if (mTextures.Length() > 0) { + mGL->fDeleteTextures(mTextures.Length(), &mTextures[0]); + } + } + mTextures.SetLength(0); +} + +void CompositorOGL::RegisterTextureSource(TextureSource* aTextureSource) { +#ifdef MOZ_WIDGET_GTK + if (mDestroyed) { + return; + } + mRegisteredTextureSources.insert(aTextureSource); +#endif +} + +void CompositorOGL::UnregisterTextureSource(TextureSource* aTextureSource) { +#ifdef MOZ_WIDGET_GTK + if (mDestroyed) { + return; + } + mRegisteredTextureSources.erase(aTextureSource); +#endif +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h new file mode 100644 index 0000000000..878372aa64 --- /dev/null +++ b/gfx/layers/opengl/CompositorOGL.h @@ -0,0 +1,446 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MOZILLA_GFX_COMPOSITOROGL_H +#define MOZILLA_GFX_COMPOSITOROGL_H + +#include <map> +#include <unordered_map> +#include <unordered_set> + +#include "gfx2DGlue.h" +#include "GLContextTypes.h" // for GLContext, etc +#include "GLDefs.h" // for GLuint, LOCAL_GL_TEXTURE_2D, etc +#include "OGLShaderConfig.h" // for ShaderConfigOGL +#include "Units.h" // for ScreenPoint +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override, final +#include "mozilla/RefPtr.h" // for already_AddRefed, RefPtr +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect, IntRect +#include "mozilla/gfx/Triangle.h" // for Triangle +#include "mozilla/gfx/Types.h" // for Float, SurfaceFormat, etc +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/layers/Compositor.h" // for SurfaceInitMode, Compositor, etc +#include "mozilla/layers/CompositorTypes.h" // for MaskType::MaskType::NumMaskTypes, etc +#include "mozilla/layers/LayersTypes.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsTArray.h" // for AutoTArray, nsTArray, etc +#include "nsThreadUtils.h" // for nsRunnable +#include "nsXULAppAPI.h" // for XRE_GetProcessType +#include "nscore.h" // for NS_IMETHOD + +class nsIWidget; + +namespace mozilla { + +namespace layers { + +class CompositingRenderTarget; +class CompositingRenderTargetOGL; +class DataTextureSource; +class ShaderProgramOGL; +class ShaderProgramOGLsHolder; +class TextureSource; +class TextureSourceOGL; +class BufferTextureHost; +struct Effect; +struct EffectChain; + +/** + * Interface for pools of temporary gl textures for the compositor. + * The textures are fully owned by the pool, so the latter is responsible + * calling fDeleteTextures accordingly. + * Users of GetTexture receive a texture that is only valid for the duration + * of the current frame. + * This is primarily intended for direct texturing APIs that need to attach + * shared objects (such as an EGLImage) to a gl texture. + */ +class CompositorTexturePoolOGL { + protected: + virtual ~CompositorTexturePoolOGL() = default; + + public: + NS_INLINE_DECL_REFCOUNTING(CompositorTexturePoolOGL) + + virtual void Clear() = 0; + + virtual GLuint GetTexture(GLenum aTarget, GLenum aEnum) = 0; + + virtual void EndFrame() = 0; +}; + +/** + * Agressively reuses textures. One gl texture per texture unit in total. + * So far this hasn't shown the best results on b2g. + */ +class PerUnitTexturePoolOGL : public CompositorTexturePoolOGL { + public: + explicit PerUnitTexturePoolOGL(gl::GLContext* aGL); + virtual ~PerUnitTexturePoolOGL(); + + void Clear() override { DestroyTextures(); } + + GLuint GetTexture(GLenum aTarget, GLenum aUnit) override; + + void EndFrame() override {} + + protected: + void DestroyTextures(); + + GLenum mTextureTarget; + nsTArray<GLuint> mTextures; + RefPtr<gl::GLContext> mGL; +}; + +// If you want to make this class not final, first remove calls to virtual +// methods (Destroy) that are made in the destructor. +class CompositorOGL final : public Compositor { + typedef mozilla::gl::GLContext GLContext; + + friend class CompositingRenderTargetOGL; + + RefPtr<ShaderProgramOGLsHolder> mProgramsHolder; + + public: + explicit CompositorOGL(widget::CompositorWidget* aWidget, + int aSurfaceWidth = -1, int aSurfaceHeight = -1, + bool aUseExternalSurfaceSize = false); + + protected: + virtual ~CompositorOGL(); + + public: + CompositorOGL* AsCompositorOGL() override { return this; } + + already_AddRefed<DataTextureSource> CreateDataTextureSource( + TextureFlags aFlags = TextureFlags::NO_FLAGS) override; + + bool Initialize(GLContext* aGLContext, + RefPtr<ShaderProgramOGLsHolder> aProgramsHolder, + nsCString* const out_failureReason); + + bool Initialize(nsCString* const out_failureReason) override; + + void Destroy() override; + + // Returns a render target for the native layer. + // aInvalidRegion is in window coordinates, i.e. in the same space as + // aNativeLayer->GetPosition(). + already_AddRefed<CompositingRenderTargetOGL> RenderTargetForNativeLayer( + NativeLayer* aNativeLayer, const gfx::IntRegion& aInvalidRegion); + + already_AddRefed<CompositingRenderTarget> CreateRenderTarget( + const gfx::IntRect& aRect, SurfaceInitMode aInit) override; + + void SetRenderTarget(CompositingRenderTarget* aSurface) override; + already_AddRefed<CompositingRenderTarget> GetCurrentRenderTarget() + const override; + already_AddRefed<CompositingRenderTarget> GetWindowRenderTarget() + const override; + + bool ReadbackRenderTarget(CompositingRenderTarget* aSource, + AsyncReadbackBuffer* aDest) override; + + already_AddRefed<AsyncReadbackBuffer> CreateAsyncReadbackBuffer( + const gfx::IntSize& aSize) override; + + bool BlitRenderTarget(CompositingRenderTarget* aSource, + const gfx::IntSize& aSourceSize, + const gfx::IntSize& aDestSize) override; + + void DrawQuad(const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) override; + + void EndFrame() override; + + int32_t GetMaxTextureSize() const override; + + /** + * Set the size of the EGL surface we're rendering to, if we're rendering to + * an EGL surface. + */ + void SetDestinationSurfaceSize(const gfx::IntSize& aSize) override; + + typedef uint32_t MakeCurrentFlags; + static const MakeCurrentFlags ForceMakeCurrent = 0x1; + void MakeCurrent(MakeCurrentFlags aFlags = 0); + +#ifdef MOZ_DUMP_PAINTING + const char* Name() const override { return "OGL"; } +#endif // MOZ_DUMP_PAINTING + + void Pause() override; + bool Resume() override; + + GLContext* gl() const { return mGLContext; } + GLContext* GetGLContext() const override { return mGLContext; } + + /** + * Clear the program state. This must be called + * before operating on the GLContext directly. */ + void ResetProgram(); + + gfx::SurfaceFormat GetFBOFormat() const { + return gfx::SurfaceFormat::R8G8B8A8; + } + + /** + * The compositor provides with temporary textures for use with direct + * textruing. + */ + GLuint GetTemporaryTexture(GLenum aTarget, GLenum aUnit); + + const gfx::IntSize GetDestinationSurfaceSize() const { + return gfx::IntSize(mSurfaceSize.width, mSurfaceSize.height); + } + + /** + * Allow the origin of the surface to be offset so that content does not + * start at (0, 0) on the surface. + */ + void SetSurfaceOrigin(const ScreenIntPoint& aOrigin) { + mSurfaceOrigin = aOrigin; + } + + // Register TextureSource which own device data that have to be deleted before + // destroying this CompositorOGL. + void RegisterTextureSource(TextureSource* aTextureSource); + void UnregisterTextureSource(TextureSource* aTextureSource); + + ipc::FileDescriptor GetReleaseFence(); + + private: + template <typename Geometry> + void DrawGeometry(const Geometry& aGeometry, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect); + + void PrepareViewport(CompositingRenderTargetOGL* aRenderTarget); + + void InsertFrameDoneSync(); + + bool NeedToRecreateFullWindowRenderTarget() const; + + /** Widget associated with this compositor */ + LayoutDeviceIntSize mWidgetSize; + RefPtr<GLContext> mGLContext; + bool mOwnsGLContext = true; + RefPtr<SurfacePoolHandle> mSurfacePoolHandle; + gfx::Matrix4x4 mProjMatrix; + bool mCanRenderToDefaultFramebuffer = true; + + /** The size of the surface we are rendering to */ + gfx::IntSize mSurfaceSize; + + /** The origin of the content on the surface */ + ScreenIntPoint mSurfaceOrigin; + + already_AddRefed<mozilla::gl::GLContext> CreateContext(); + + /** Texture target to use for FBOs */ + GLenum mFBOTextureTarget; + + /** Currently bound render target */ + RefPtr<CompositingRenderTargetOGL> mCurrentRenderTarget; + + // The 1x1 dummy render target that's the "current" render target between + // BeginFrameForNativeLayers and EndFrame but outside pairs of + // Begin/EndRenderingToNativeLayer. Created on demand. + RefPtr<CompositingRenderTarget> mNativeLayersReferenceRT; + + // The render target that profiler screenshots / frame recording read from. + // This will be the actual window framebuffer when rendering to a window, and + // it will be mFullWindowRenderTarget when rendering to native layers. + RefPtr<CompositingRenderTargetOGL> mWindowRenderTarget; + + // Non-null when using native layers and frame recording is requested. + // EndNormalDrawing() maintains a copy of the entire window contents in this + // render target, by copying from the native layer render targets. + RefPtr<CompositingRenderTargetOGL> mFullWindowRenderTarget; + + /** + * VBO that has some basics in it for a textured quad, including vertex + * coords and texcoords. + */ + GLuint mQuadVBO; + + /** + * VBO that stores dynamic triangle geometry. + */ + GLuint mTriangleVBO; + + // Used to apply back-pressure in WaitForPreviousFrameDoneSync(). + GLsync mPreviousFrameDoneSync; + GLsync mThisFrameDoneSync; + + bool mHasBGRA; + + /** + * When rendering to some EGL surfaces (e.g. on Android), we rely on being + * told about size changes (via SetSurfaceSize) rather than pulling this + * information from the widget. + */ + bool mUseExternalSurfaceSize; + + /** + * Have we had DrawQuad calls since the last frame was rendered? + */ + bool mFrameInProgress; + + // Only true between BeginFromeForNativeLayers and EndFrame, and only if the + // full window render target needed to be recreated in the current frame. + bool mShouldInvalidateWindow = false; + + /* Start a new frame. + */ + Maybe<gfx::IntRect> BeginFrameForWindow( + const nsIntRegion& aInvalidRegion, const Maybe<gfx::IntRect>& aClipRect, + const gfx::IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion) override; + + Maybe<gfx::IntRect> BeginFrame(const nsIntRegion& aInvalidRegion, + const Maybe<gfx::IntRect>& aClipRect, + const gfx::IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion); + + ShaderConfigOGL GetShaderConfigFor(Effect* aEffect, + bool aDEAAEnabled = false) const; + + ShaderProgramOGL* GetShaderProgramFor(const ShaderConfigOGL& aConfig); + + void ApplyPrimitiveConfig(ShaderConfigOGL& aConfig, const gfx::Rect&) { + aConfig.SetDynamicGeometry(false); + } + + void ApplyPrimitiveConfig(ShaderConfigOGL& aConfig, + const nsTArray<gfx::TexturedTriangle>&) { + aConfig.SetDynamicGeometry(true); + } + + /** + * Create a FBO backed by a texture. + * Note that the texture target type will be + * of the type returned by FBOTextureTarget; different + * shaders are required to sample from the different + * texture types. + */ + void CreateFBOWithTexture(const gfx::IntRect& aRect, bool aCopyFromSource, + GLuint aSourceFrameBuffer, GLuint* aFBO, + GLuint* aTexture, + gfx::IntSize* aAllocSize = nullptr); + + GLuint CreateTexture(const gfx::IntRect& aRect, bool aCopyFromSource, + GLuint aSourceFrameBuffer, + gfx::IntSize* aAllocSize = nullptr); + + gfx::Point3D GetLineCoefficients(const gfx::Point& aPoint1, + const gfx::Point& aPoint2); + + void CleanupResources(); + + void BindAndDrawQuads(ShaderProgramOGL* aProg, int aQuads, + const gfx::Rect* aLayerRect, + const gfx::Rect* aTextureRect); + + void BindAndDrawQuad(ShaderProgramOGL* aProg, const gfx::Rect& aLayerRect, + const gfx::Rect& aTextureRect = gfx::Rect(0.0f, 0.0f, + 1.0f, 1.0f)) { + gfx::Rect layerRects[4]; + gfx::Rect textureRects[4]; + layerRects[0] = aLayerRect; + textureRects[0] = aTextureRect; + BindAndDrawQuads(aProg, 1, layerRects, textureRects); + } + + void BindAndDrawGeometry(ShaderProgramOGL* aProgram, const gfx::Rect& aRect); + + void BindAndDrawGeometry(ShaderProgramOGL* aProgram, + const nsTArray<gfx::TexturedTriangle>& aTriangles); + + void BindAndDrawGeometryWithTextureRect(ShaderProgramOGL* aProg, + const gfx::Rect& aRect, + const gfx::Rect& aTexCoordRect, + TextureSource* aTexture); + + void BindAndDrawGeometryWithTextureRect( + ShaderProgramOGL* aProg, + const nsTArray<gfx::TexturedTriangle>& aTriangles, + const gfx::Rect& aTexCoordRect, TextureSource* aTexture); + + void InitializeVAO(const GLuint aAttribIndex, const GLint aComponents, + const GLsizei aStride, const size_t aOffset); + + gfx::Rect GetTextureCoordinates(gfx::Rect textureRect, + TextureSource* aTexture); + + /** + * Copies the content of the current render target to the set transaction + * target. + */ + void CopyToTarget(gfx::DrawTarget* aTarget, const nsIntPoint& aTopLeft, + const gfx::Matrix& aWorldMatrix); + + /** + * Implements the flipping of the y-axis to convert from layers/compositor + * coordinates to OpenGL coordinates. + * + * Indeed, the only coordinate system that OpenGL knows has the y-axis + * pointing upwards, but the layers/compositor coordinate system has the + * y-axis pointing downwards, for good reason as Web pages are typically + * scrolled downwards. So, some flipping has to take place; FlippedY does it. + */ + GLint FlipY(GLint y) const { return mViewportSize.height - y; } + + // The DrawTarget from BeginFrameForTarget, which EndFrame needs to copy the + // window contents into. + // Only non-null between BeginFrameForTarget and EndFrame. + RefPtr<gfx::DrawTarget> mTarget; + gfx::IntRect mTargetBounds; + + RefPtr<CompositorTexturePoolOGL> mTexturePool; + + // The native layer that we're currently rendering to, if any. + // Non-null only between BeginFrame and EndFrame if BeginFrame has been called + // with a non-null aNativeLayer. + RefPtr<NativeLayer> mCurrentNativeLayer; + +#ifdef MOZ_WIDGET_GTK + // Hold TextureSources which own device data that have to be deleted before + // destroying this CompositorOGL. + std::unordered_set<TextureSource*> mRegisteredTextureSources; +#endif + + // FileDescriptor of release fence. + // Release fence is a fence that is used for waiting until usage/composite of + // AHardwareBuffer is ended. The fence is delivered to client side via + // ImageBridge. It is used only on android. + ipc::FileDescriptor mReleaseFenceFd; + + bool mDestroyed; + + /** + * Size of the OpenGL context's primary framebuffer in pixels. Used by + * FlipY for the y-flipping calculation and by the DEAA shader. + */ + gfx::IntSize mViewportSize; + + gfx::IntRegion mCurrentFrameInvalidRegion; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_COMPOSITOROGL_H */ diff --git a/gfx/layers/opengl/DMABUFTextureClientOGL.cpp b/gfx/layers/opengl/DMABUFTextureClientOGL.cpp new file mode 100644 index 0000000000..7dbd8fc599 --- /dev/null +++ b/gfx/layers/opengl/DMABUFTextureClientOGL.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "DMABUFTextureClientOGL.h" +#include "mozilla/widget/DMABufSurface.h" +#include "gfxPlatform.h" + +namespace mozilla::layers { + +using namespace gfx; + +DMABUFTextureData::DMABUFTextureData(DMABufSurface* aSurface, + BackendType aBackend) + : mSurface(aSurface), mBackend(aBackend) { + MOZ_ASSERT(mSurface); +} + +DMABUFTextureData::~DMABUFTextureData() = default; + +/* static */ DMABUFTextureData* DMABUFTextureData::Create( + const IntSize& aSize, SurfaceFormat aFormat, BackendType aBackend) { + if (aFormat != SurfaceFormat::B8G8R8A8 && + aFormat != SurfaceFormat::B8G8R8X8) { + // TODO + NS_WARNING("DMABUFTextureData::Create() - wrong surface format!"); + return nullptr; + } + + int flags = DMABUF_TEXTURE; + if (aFormat == SurfaceFormat::B8G8R8A8) { + flags |= DMABUF_ALPHA; + } + RefPtr<DMABufSurface> surf = + DMABufSurfaceRGBA::CreateDMABufSurface(aSize.width, aSize.height, flags); + if (!surf) { + NS_WARNING("DMABUFTextureData::Create() failed!"); + return nullptr; + } + return new DMABUFTextureData(surf, aBackend); +} + +TextureData* DMABUFTextureData::CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags) const { + return DMABUFTextureData::Create( + gfx::IntSize(mSurface->GetWidth(), mSurface->GetHeight()), + mSurface->GetFormat(), mBackend); +} + +bool DMABUFTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + return mSurface->Serialize(aOutDescriptor); +} + +void DMABUFTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = gfx::IntSize(mSurface->GetWidth(), mSurface->GetHeight()); + aInfo.format = mSurface->GetFormat(); + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = true; + aInfo.canExposeMappedData = false; +} + +bool DMABUFTextureData::Lock(OpenMode) { + auto surf = mSurface->GetAsDMABufSurfaceRGBA(); + MOZ_ASSERT(!surf->IsMapped(), "Already locked?"); + surf->Map(); + return true; +} + +void DMABUFTextureData::Unlock() { + auto surf = mSurface->GetAsDMABufSurfaceRGBA(); + MOZ_ASSERT(surf->IsMapped(), "Already unlocked?"); + surf->Unmap(); +} + +already_AddRefed<DataSourceSurface> DMABUFTextureData::GetAsSurface() { + // TODO: Update for debug purposes. + return nullptr; +} + +already_AddRefed<DrawTarget> DMABUFTextureData::BorrowDrawTarget() { + MOZ_ASSERT(mBackend != BackendType::NONE); + if (mBackend == BackendType::NONE) { + // shouldn't happen, but degrade gracefully + return nullptr; + } + auto surf = mSurface->GetAsDMABufSurfaceRGBA(); + if (!surf->GetMappedRegion()) { + return nullptr; + } + return Factory::CreateDrawTargetForData( + mBackend, (unsigned char*)surf->GetMappedRegion(), + IntSize(surf->GetWidth(), surf->GetHeight()), + surf->GetMappedRegionStride(), surf->GetFormat(), true); +} + +void DMABUFTextureData::Deallocate(LayersIPCChannel*) { mSurface = nullptr; } + +void DMABUFTextureData::Forget(LayersIPCChannel*) { mSurface = nullptr; } + +bool DMABUFTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface) { + RefPtr<DrawTarget> dt = BorrowDrawTarget(); + if (!dt) { + return false; + } + + dt->CopySurface(aSurface, IntRect(IntPoint(), aSurface->GetSize()), + IntPoint()); + return true; +} + +} // namespace mozilla::layers diff --git a/gfx/layers/opengl/DMABUFTextureClientOGL.h b/gfx/layers/opengl/DMABUFTextureClientOGL.h new file mode 100644 index 0000000000..42ed819b8c --- /dev/null +++ b/gfx/layers/opengl/DMABUFTextureClientOGL.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H +#define MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H + +#include "mozilla/layers/TextureClientOGL.h" + +class DMABufSurface; + +namespace mozilla { +namespace layers { + +class DMABUFTextureData : public TextureData { + public: + static DMABUFTextureData* Create(const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aBackend); + + static DMABUFTextureData* Create(DMABufSurface* aSurface, + gfx::BackendType aBackend) { + return new DMABUFTextureData(aSurface, aBackend); + } + + ~DMABUFTextureData(); + + virtual TextureData* CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override; + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Lock(OpenMode) override; + + void Unlock() override; + + already_AddRefed<gfx::DrawTarget> BorrowDrawTarget() override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + void Deallocate(LayersIPCChannel*) override; + + void Forget(LayersIPCChannel*) override; + + bool UpdateFromSurface(gfx::SourceSurface* aSurface) override; + + // For debugging purposes only. + already_AddRefed<gfx::DataSourceSurface> GetAsSurface(); + + protected: + DMABUFTextureData(DMABufSurface* aSurface, gfx::BackendType aBackend); + + RefPtr<DMABufSurface> mSurface; + gfx::BackendType mBackend; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H diff --git a/gfx/layers/opengl/DMABUFTextureHostOGL.cpp b/gfx/layers/opengl/DMABUFTextureHostOGL.cpp new file mode 100644 index 0000000000..19202038e8 --- /dev/null +++ b/gfx/layers/opengl/DMABUFTextureHostOGL.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "DMABUFTextureHostOGL.h" +#include "mozilla/widget/DMABufSurface.h" +#include "mozilla/webrender/RenderDMABUFTextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "GLContextEGL.h" + +namespace mozilla::layers { + +DMABUFTextureHostOGL::DMABUFTextureHostOGL(TextureFlags aFlags, + const SurfaceDescriptor& aDesc) + : TextureHost(TextureHostType::DMABUF, aFlags) { + MOZ_COUNT_CTOR(DMABUFTextureHostOGL); + + // DMABufSurface::CreateDMABufSurface() can fail, for instance when we're run + // out of file descriptors. + mSurface = + DMABufSurface::CreateDMABufSurface(aDesc.get_SurfaceDescriptorDMABuf()); +} + +DMABUFTextureHostOGL::~DMABUFTextureHostOGL() { + MOZ_COUNT_DTOR(DMABUFTextureHostOGL); +} + +gfx::SurfaceFormat DMABUFTextureHostOGL::GetFormat() const { + if (!mSurface) { + return gfx::SurfaceFormat::UNKNOWN; + } + return mSurface->GetFormat(); +} + +gfx::YUVColorSpace DMABUFTextureHostOGL::GetYUVColorSpace() const { + if (!mSurface) { + return gfx::YUVColorSpace::Identity; + } + return mSurface->GetYUVColorSpace(); +} + +gfx::ColorRange DMABUFTextureHostOGL::GetColorRange() const { + if (!mSurface) { + return gfx::ColorRange::LIMITED; + } + return mSurface->IsFullRange() ? gfx::ColorRange::FULL + : gfx::ColorRange::LIMITED; +} + +uint32_t DMABUFTextureHostOGL::NumSubTextures() { + return mSurface ? mSurface->GetTextureCount() : 0; +} + +gfx::IntSize DMABUFTextureHostOGL::GetSize() const { + if (!mSurface) { + return gfx::IntSize(); + } + return gfx::IntSize(mSurface->GetWidth(), mSurface->GetHeight()); +} + +gl::GLContext* DMABUFTextureHostOGL::gl() const { return nullptr; } + +void DMABUFTextureHostOGL::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + if (!mSurface) { + return; + } + RefPtr<wr::RenderTextureHost> texture = + new wr::RenderDMABUFTextureHost(mSurface); + wr::RenderThread::Get()->RegisterExternalImage(aExternalImageId, + texture.forget()); +} + +void DMABUFTextureHostOGL::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, const wr::ExternalImageId& aExtID) { + if (!mSurface) { + return; + } + + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = + wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D); + + switch (mSurface->GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8X8: + case gfx::SurfaceFormat::B8G8R8A8: { + MOZ_ASSERT(aImageKeys.length() == 1); + // XXX Add RGBA handling. Temporary hack to avoid crash + // With BGRA format setting, rendering works without problem. + wr::ImageDescriptor descriptor(GetSize(), mSurface->GetFormat()); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetTextureCount() == 2); + wr::ImageDescriptor descriptor0( + gfx::IntSize(mSurface->GetWidth(0), mSurface->GetHeight(0)), + gfx::SurfaceFormat::A8); + wr::ImageDescriptor descriptor1( + gfx::IntSize(mSurface->GetWidth(1), mSurface->GetHeight(1)), + gfx::SurfaceFormat::R8G8); + (aResources.*method)(aImageKeys[0], descriptor0, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], descriptor1, aExtID, imageType, 1); + break; + } + case gfx::SurfaceFormat::YUV: { + MOZ_ASSERT(aImageKeys.length() == 3); + MOZ_ASSERT(mSurface->GetTextureCount() == 3); + wr::ImageDescriptor descriptor0( + gfx::IntSize(mSurface->GetWidth(0), mSurface->GetHeight(0)), + gfx::SurfaceFormat::A8); + wr::ImageDescriptor descriptor1( + gfx::IntSize(mSurface->GetWidth(1), mSurface->GetHeight(1)), + gfx::SurfaceFormat::A8); + (aResources.*method)(aImageKeys[0], descriptor0, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], descriptor1, aExtID, imageType, 1); + (aResources.*method)(aImageKeys[2], descriptor1, aExtID, imageType, 2); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +void DMABUFTextureHostOGL::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, PushDisplayItemFlagSet aFlags) { + if (!mSurface) { + return; + } + bool preferCompositorSurface = + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE); + switch (mSurface->GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage(aBounds, aClip, true, false, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + preferCompositorSurface); + break; + } + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetTextureCount() == 2); + // Those images can only be generated at present by the VAAPI H264 decoder + // which only supports 8 bits color depth. + aBuilder.PushNV12Image(aBounds, aClip, true, aImageKeys[0], aImageKeys[1], + wr::ColorDepth::Color8, + wr::ToWrYuvColorSpace(GetYUVColorSpace()), + wr::ToWrColorRange(GetColorRange()), aFilter, + preferCompositorSurface); + break; + } + case gfx::SurfaceFormat::YUV: { + MOZ_ASSERT(aImageKeys.length() == 3); + MOZ_ASSERT(mSurface->GetTextureCount() == 3); + // Those images can only be generated at present by the VAAPI vp8 decoder + // which only supports 8 bits color depth. + aBuilder.PushYCbCrPlanarImage( + aBounds, aClip, true, aImageKeys[0], aImageKeys[1], aImageKeys[2], + wr::ColorDepth::Color8, wr::ToWrYuvColorSpace(GetYUVColorSpace()), + wr::ToWrColorRange(GetColorRange()), aFilter, + preferCompositorSurface); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +} // namespace mozilla::layers diff --git a/gfx/layers/opengl/DMABUFTextureHostOGL.h b/gfx/layers/opengl/DMABUFTextureHostOGL.h new file mode 100644 index 0000000000..13510a3c03 --- /dev/null +++ b/gfx/layers/opengl/DMABUFTextureHostOGL.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MOZILLA_GFX_DMABUFTEXTUREHOSTOGL_H +#define MOZILLA_GFX_DMABUFTEXTUREHOSTOGL_H + +#include "mozilla/gfx/2D.h" +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/TextureHostOGL.h" + +class DMABufSurface; + +namespace mozilla { +namespace layers { + +/** + * A TextureHost for shared class DMABufSurface; + */ +class DMABUFTextureHostOGL : public TextureHost { + public: + DMABUFTextureHostOGL(TextureFlags aFlags, const SurfaceDescriptor& aDesc); + virtual ~DMABUFTextureHostOGL(); + + bool IsValid() override { return !!mSurface; } + + gfx::SurfaceFormat GetFormat() const override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override; + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "DMABUFTextureHostOGL"; } +#endif + uint32_t NumSubTextures() override; + + gfx::YUVColorSpace GetYUVColorSpace() const override; + gfx::ColorRange GetColorRange() const override; + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + protected: + RefPtr<GLTextureSource> mTextureSource; + RefPtr<DMABufSurface> mSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_DMABUFTEXTUREHOSTOGL_H diff --git a/gfx/layers/opengl/EGLImageHelpers.cpp b/gfx/layers/opengl/EGLImageHelpers.cpp new file mode 100644 index 0000000000..a5abfe449f --- /dev/null +++ b/gfx/layers/opengl/EGLImageHelpers.cpp @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "EGLImageHelpers.h" +#include "GLContext.h" +#include "GLLibraryEGL.h" + +namespace mozilla { +namespace layers { + +using namespace gl; + +EGLImage EGLImageCreateFromNativeBuffer(GLContext* aGL, void* aBuffer, + const gfx::IntSize& aCropSize) { + EGLint attrs[] = { + LOCAL_EGL_IMAGE_PRESERVED, + LOCAL_EGL_TRUE, + LOCAL_EGL_NONE, + LOCAL_EGL_NONE, + }; + + EGLint cropAttrs[] = { + LOCAL_EGL_IMAGE_PRESERVED, + LOCAL_EGL_TRUE, + LOCAL_EGL_IMAGE_CROP_LEFT_ANDROID, + 0, + LOCAL_EGL_IMAGE_CROP_TOP_ANDROID, + 0, + LOCAL_EGL_IMAGE_CROP_RIGHT_ANDROID, + aCropSize.width, + LOCAL_EGL_IMAGE_CROP_BOTTOM_ANDROID, + aCropSize.height, + LOCAL_EGL_NONE, + LOCAL_EGL_NONE, + }; + + auto* egl = gl::GLLibraryEGL::Get(); + bool hasCropRect = (aCropSize.width != 0 && aCropSize.height != 0); + EGLint* usedAttrs = attrs; + if (hasCropRect && + egl->IsExtensionSupported(GLLibraryEGL::EGL_ANDROID_image_crop)) { + usedAttrs = cropAttrs; + } + + return egl->fCreateImage(egl->Display(), EGL_NO_CONTEXT, + LOCAL_EGL_NATIVE_BUFFER_ANDROID, aBuffer, usedAttrs); +} + +void EGLImageDestroy(GLContext* aGL, EGLImage aImage) { + auto* egl = gl::GLLibraryEGL::Get(); + egl->fDestroyImage(egl->Display(), aImage); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/EGLImageHelpers.h b/gfx/layers/opengl/EGLImageHelpers.h new file mode 100644 index 0000000000..59eb944c0d --- /dev/null +++ b/gfx/layers/opengl/EGLImageHelpers.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef EGLIMAGEHELPERS_H_ +#define EGLIMAGEHELPERS_H_ + +#include "mozilla/gfx/Point.h" + +typedef void* EGLImage; + +namespace mozilla { +namespace gl { +class GLContext; +} + +namespace layers { + +EGLImage EGLImageCreateFromNativeBuffer(gl::GLContext* aGL, void* aBuffer, + const gfx::IntSize& aCropSize); +void EGLImageDestroy(gl::GLContext* aGL, EGLImage aImage); + +} // namespace layers +} // namespace mozilla + +#endif // EGLIMAGEHELPERS_H_ diff --git a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp new file mode 100644 index 0000000000..9a14daacc7 --- /dev/null +++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "MacIOSurfaceTextureClientOGL.h" +#include "mozilla/gfx/MacIOSurface.h" +#include "MacIOSurfaceHelpers.h" +#include "gfxPlatform.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +MacIOSurfaceTextureData::MacIOSurfaceTextureData(MacIOSurface* aSurface, + BackendType aBackend) + : mSurface(aSurface), mBackend(aBackend) { + MOZ_ASSERT(mSurface); +} + +MacIOSurfaceTextureData::~MacIOSurfaceTextureData() = default; + +// static +MacIOSurfaceTextureData* MacIOSurfaceTextureData::Create(MacIOSurface* aSurface, + BackendType aBackend) { + MOZ_ASSERT(aSurface); + if (!aSurface) { + return nullptr; + } + return new MacIOSurfaceTextureData(aSurface, aBackend); +} + +MacIOSurfaceTextureData* MacIOSurfaceTextureData::Create(const IntSize& aSize, + SurfaceFormat aFormat, + BackendType aBackend) { + if (aFormat != SurfaceFormat::B8G8R8A8 && + aFormat != SurfaceFormat::B8G8R8X8) { + return nullptr; + } + + RefPtr<MacIOSurface> surf = MacIOSurface::CreateIOSurface( + aSize.width, aSize.height, aFormat == SurfaceFormat::B8G8R8A8); + if (!surf) { + return nullptr; + } + + return Create(surf, aBackend); +} + +bool MacIOSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + aOutDescriptor = SurfaceDescriptorMacIOSurface(mSurface->GetIOSurfaceID(), + !mSurface->HasAlpha(), + mSurface->GetYUVColorSpace()); + return true; +} + +void MacIOSurfaceTextureData::GetSubDescriptor( + RemoteDecoderVideoSubDescriptor* const aOutDesc) { + *aOutDesc = SurfaceDescriptorMacIOSurface(mSurface->GetIOSurfaceID(), + !mSurface->HasAlpha(), + mSurface->GetYUVColorSpace()); +} + +void MacIOSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = gfx::IntSize(mSurface->GetDevicePixelWidth(), + mSurface->GetDevicePixelHeight()); + aInfo.format = + mSurface->HasAlpha() ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = true; + aInfo.canExposeMappedData = false; +} + +bool MacIOSurfaceTextureData::Lock(OpenMode) { + mSurface->Lock(false); + return true; +} + +void MacIOSurfaceTextureData::Unlock() { mSurface->Unlock(false); } + +already_AddRefed<DataSourceSurface> MacIOSurfaceTextureData::GetAsSurface() { + RefPtr<SourceSurface> surf = CreateSourceSurfaceFromMacIOSurface(mSurface); + return surf->GetDataSurface(); +} + +already_AddRefed<DrawTarget> MacIOSurfaceTextureData::BorrowDrawTarget() { + MOZ_ASSERT(mBackend != BackendType::NONE); + if (mBackend == BackendType::NONE) { + // shouldn't happen, but degrade gracefully + return nullptr; + } + return Factory::CreateDrawTargetForData( + mBackend, (unsigned char*)mSurface->GetBaseAddress(), + IntSize(mSurface->GetWidth(), mSurface->GetHeight()), + mSurface->GetBytesPerRow(), + mSurface->HasAlpha() ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8, + true); +} + +void MacIOSurfaceTextureData::Deallocate(LayersIPCChannel*) { + mSurface = nullptr; +} + +void MacIOSurfaceTextureData::Forget(LayersIPCChannel*) { mSurface = nullptr; } + +bool MacIOSurfaceTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface) { + RefPtr<DrawTarget> dt = BorrowDrawTarget(); + if (!dt) { + return false; + } + + dt->CopySurface(aSurface, IntRect(IntPoint(), aSurface->GetSize()), + IntPoint()); + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h new file mode 100644 index 0000000000..f7206e0b1b --- /dev/null +++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H +#define MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H + +#include "mozilla/layers/TextureClientOGL.h" + +class MacIOSurface; + +namespace mozilla { +namespace layers { + +class MacIOSurfaceTextureData : public TextureData { + public: + static MacIOSurfaceTextureData* Create(MacIOSurface* aSurface, + gfx::BackendType aBackend); + + static MacIOSurfaceTextureData* Create(const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aBackend); + + ~MacIOSurfaceTextureData(); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Lock(OpenMode) override; + + void Unlock() override; + + already_AddRefed<gfx::DrawTarget> BorrowDrawTarget() override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + void GetSubDescriptor( + RemoteDecoderVideoSubDescriptor* const aOutDesc) override; + + void Deallocate(LayersIPCChannel*) override; + + void Forget(LayersIPCChannel*) override; + + bool UpdateFromSurface(gfx::SourceSurface* aSurface) override; + + // For debugging purposes only. + already_AddRefed<gfx::DataSourceSurface> GetAsSurface(); + + protected: + MacIOSurfaceTextureData(MacIOSurface* aSurface, gfx::BackendType aBackend); + + RefPtr<MacIOSurface> mSurface; + gfx::BackendType mBackend; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp new file mode 100644 index 0000000000..4e47f6ae8b --- /dev/null +++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "MacIOSurfaceTextureHostOGL.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/MacIOSurface.h" +#include "mozilla/webrender/RenderMacIOSurfaceTextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "GLContextCGL.h" + +namespace mozilla { +namespace layers { + +MacIOSurfaceTextureHostOGL::MacIOSurfaceTextureHostOGL( + TextureFlags aFlags, const SurfaceDescriptorMacIOSurface& aDescriptor) + : TextureHost(TextureHostType::MacIOSurface, aFlags) { + MOZ_COUNT_CTOR(MacIOSurfaceTextureHostOGL); + mSurface = MacIOSurface::LookupSurface(aDescriptor.surfaceId(), + !aDescriptor.isOpaque(), + aDescriptor.yUVColorSpace()); + if (!mSurface) { + gfxCriticalNote << "Failed to look up MacIOSurface"; + } +} + +MacIOSurfaceTextureHostOGL::~MacIOSurfaceTextureHostOGL() { + MOZ_COUNT_DTOR(MacIOSurfaceTextureHostOGL); +} + +gfx::SurfaceFormat MacIOSurfaceTextureHostOGL::GetFormat() const { + if (!mSurface) { + return gfx::SurfaceFormat::UNKNOWN; + } + return mSurface->GetFormat(); +} + +gfx::SurfaceFormat MacIOSurfaceTextureHostOGL::GetReadFormat() const { + if (!mSurface) { + return gfx::SurfaceFormat::UNKNOWN; + } + return mSurface->GetReadFormat(); +} + +gfx::IntSize MacIOSurfaceTextureHostOGL::GetSize() const { + if (!mSurface) { + return gfx::IntSize(); + } + return gfx::IntSize(mSurface->GetDevicePixelWidth(), + mSurface->GetDevicePixelHeight()); +} + +gl::GLContext* MacIOSurfaceTextureHostOGL::gl() const { return nullptr; } + +gfx::YUVColorSpace MacIOSurfaceTextureHostOGL::GetYUVColorSpace() const { + if (!mSurface) { + return gfx::YUVColorSpace::Identity; + } + return mSurface->GetYUVColorSpace(); +} + +gfx::ColorRange MacIOSurfaceTextureHostOGL::GetColorRange() const { + if (!mSurface) { + return gfx::ColorRange::LIMITED; + } + return mSurface->IsFullRange() ? gfx::ColorRange::FULL + : gfx::ColorRange::LIMITED; +} + +void MacIOSurfaceTextureHostOGL::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr<wr::RenderTextureHost> texture = + new wr::RenderMacIOSurfaceTextureHost(GetMacIOSurface()); + + bool isDRM = (bool)(mFlags & TextureFlags::DRM_SOURCE); + texture->SetIsFromDRMSource(isDRM); + + wr::RenderThread::Get()->RegisterExternalImage(aExternalImageId, + texture.forget()); +} + +uint32_t MacIOSurfaceTextureHostOGL::NumSubTextures() { + if (!mSurface) { + return 0; + } + + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: + case gfx::SurfaceFormat::YUV422: { + return 1; + } + case gfx::SurfaceFormat::NV12: + case gfx::SurfaceFormat::P010: { + return 2; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected format"); + return 1; + } + } +} + +void MacIOSurfaceTextureHostOGL::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, const wr::ExternalImageId& aExtID) { + MOZ_ASSERT(mSurface); + + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = + wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::TextureRect); + + switch (GetFormat()) { + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + MOZ_ASSERT(mSurface->GetPlaneCount() == 0); + // The internal pixel format of MacIOSurface is always BGRX or BGRA + // format. + auto format = GetFormat() == gfx::SurfaceFormat::B8G8R8A8 + ? gfx::SurfaceFormat::B8G8R8A8 + : gfx::SurfaceFormat::B8G8R8X8; + wr::ImageDescriptor descriptor(GetSize(), format); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + case gfx::SurfaceFormat::YUV422: { + // This is the special buffer format. The buffer contents could be a + // converted RGB interleaving data or a YCbCr interleaving data depending + // on the different platform setting. (e.g. It will be RGB at OpenGL 2.1 + // and YCbCr at OpenGL 3.1) + MOZ_ASSERT(aImageKeys.length() == 1); + MOZ_ASSERT(mSurface->GetPlaneCount() == 0); + wr::ImageDescriptor descriptor(GetSize(), gfx::SurfaceFormat::B8G8R8X8); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetPlaneCount() == 2); + wr::ImageDescriptor descriptor0( + gfx::IntSize(mSurface->GetDevicePixelWidth(0), + mSurface->GetDevicePixelHeight(0)), + gfx::SurfaceFormat::A8); + wr::ImageDescriptor descriptor1( + gfx::IntSize(mSurface->GetDevicePixelWidth(1), + mSurface->GetDevicePixelHeight(1)), + gfx::SurfaceFormat::R8G8); + (aResources.*method)(aImageKeys[0], descriptor0, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], descriptor1, aExtID, imageType, 1); + break; + } + case gfx::SurfaceFormat::P010: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetPlaneCount() == 2); + wr::ImageDescriptor descriptor0( + gfx::IntSize(mSurface->GetDevicePixelWidth(0), + mSurface->GetDevicePixelHeight(0)), + gfx::SurfaceFormat::A16); + wr::ImageDescriptor descriptor1( + gfx::IntSize(mSurface->GetDevicePixelWidth(1), + mSurface->GetDevicePixelHeight(1)), + gfx::SurfaceFormat::R16G16); + (aResources.*method)(aImageKeys[0], descriptor0, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], descriptor1, aExtID, imageType, 1); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +void MacIOSurfaceTextureHostOGL::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, PushDisplayItemFlagSet aFlags) { + bool preferCompositorSurface = + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE); + switch (GetFormat()) { + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + MOZ_ASSERT(mSurface->GetPlaneCount() == 0); + // We disable external compositing for RGB surfaces for now until + // we've tested support more thoroughly. Bug 1667917. + aBuilder.PushImage(aBounds, aClip, true, false, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + preferCompositorSurface, + /* aSupportsExternalCompositing */ true); + break; + } + case gfx::SurfaceFormat::YUV422: { + MOZ_ASSERT(aImageKeys.length() == 1); + MOZ_ASSERT(mSurface->GetPlaneCount() == 0); + // Those images can only be generated at present by the Apple H264 decoder + // which only supports 8 bits color depth. + aBuilder.PushYCbCrInterleavedImage( + aBounds, aClip, true, aImageKeys[0], wr::ColorDepth::Color8, + wr::ToWrYuvColorSpace(GetYUVColorSpace()), + wr::ToWrColorRange(GetColorRange()), aFilter, preferCompositorSurface, + /* aSupportsExternalCompositing */ true); + break; + } + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetPlaneCount() == 2); + aBuilder.PushNV12Image( + aBounds, aClip, true, aImageKeys[0], aImageKeys[1], + wr::ColorDepth::Color8, wr::ToWrYuvColorSpace(GetYUVColorSpace()), + wr::ToWrColorRange(GetColorRange()), aFilter, preferCompositorSurface, + /* aSupportsExternalCompositing */ true); + break; + } + case gfx::SurfaceFormat::P010: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetPlaneCount() == 2); + aBuilder.PushP010Image( + aBounds, aClip, true, aImageKeys[0], aImageKeys[1], + wr::ColorDepth::Color10, wr::ToWrYuvColorSpace(GetYUVColorSpace()), + wr::ToWrColorRange(GetColorRange()), aFilter, preferCompositorSurface, + /* aSupportsExternalCompositing */ true); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h new file mode 100644 index 0000000000..d4aaa630de --- /dev/null +++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H +#define MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H + +#include "MacIOSurfaceHelpers.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/TextureHostOGL.h" + +class MacIOSurface; + +namespace mozilla { +namespace layers { + +/** + * A TextureHost for shared MacIOSurface + * + * Most of the logic actually happens in MacIOSurfaceTextureSourceOGL. + */ +class MacIOSurfaceTextureHostOGL : public TextureHost { + public: + MacIOSurfaceTextureHostOGL(TextureFlags aFlags, + const SurfaceDescriptorMacIOSurface& aDescriptor); + virtual ~MacIOSurfaceTextureHostOGL(); + + // MacIOSurfaceTextureSourceOGL doesn't own any GL texture + void DeallocateDeviceData() override {} + + gfx::SurfaceFormat GetFormat() const override; + gfx::SurfaceFormat GetReadFormat() const override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override { + RefPtr<gfx::SourceSurface> surf = + CreateSourceSurfaceFromMacIOSurface(GetMacIOSurface()); + return surf->GetDataSurface(); + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override; + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "MacIOSurfaceTextureHostOGL"; } +#endif + + MacIOSurfaceTextureHostOGL* AsMacIOSurfaceTextureHost() override { + return this; + } + + MacIOSurface* GetMacIOSurface() override { return mSurface; } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + gfx::YUVColorSpace GetYUVColorSpace() const override; + gfx::ColorRange GetColorRange() const override; + + protected: + RefPtr<GLTextureSource> mTextureSource; + RefPtr<MacIOSurface> mSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H diff --git a/gfx/layers/opengl/OGLShaderConfig.h b/gfx/layers/opengl/OGLShaderConfig.h new file mode 100644 index 0000000000..f7c939aa98 --- /dev/null +++ b/gfx/layers/opengl/OGLShaderConfig.h @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef GFX_OGLSHADERCONFIG_H +#define GFX_OGLSHADERCONFIG_H + +#include "gfxTypes.h" +#include "ImageTypes.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" +#include "nsDebug.h" // for NS_ASSERTION +#include "nsPoint.h" // for nsIntPoint +#include "nsTArray.h" // for nsTArray +#include "mozilla/layers/CompositorTypes.h" + +namespace mozilla { +namespace layers { + +enum ShaderFeatures { + ENABLE_RENDER_COLOR = 0x01, + ENABLE_TEXTURE_RECT = 0x02, + ENABLE_TEXTURE_EXTERNAL = 0x04, + ENABLE_TEXTURE_YCBCR = 0x08, + ENABLE_TEXTURE_NV12 = 0x10, + ENABLE_TEXTURE_COMPONENT_ALPHA = 0x20, + ENABLE_TEXTURE_NO_ALPHA = 0x40, + ENABLE_TEXTURE_RB_SWAP = 0x80, + ENABLE_OPACITY = 0x100, + ENABLE_BLUR = 0x200, + ENABLE_COLOR_MATRIX = 0x400, + ENABLE_MASK = 0x800, + ENABLE_NO_PREMUL_ALPHA = 0x1000, + ENABLE_DEAA = 0x2000, + ENABLE_DYNAMIC_GEOMETRY = 0x4000, + ENABLE_MASK_TEXTURE_RECT = 0x8000, + ENABLE_TEXTURE_NV12_GA_SWITCH = 0x10000, +}; + +class KnownUniform { + public: + // this needs to be kept in sync with strings in 'AddUniforms' + enum KnownUniformName { + NotAKnownUniform = -1, + + LayerTransform = 0, + LayerTransformInverse, + MaskTransform, + BackdropTransform, + LayerRects, + MatrixProj, + TextureTransform, + TextureRects, + RenderTargetOffset, + LayerOpacity, + Texture, + YTexture, + CbTexture, + CrTexture, + RenderColor, + TexCoordMultiplier, + CbCrTexCoordMultiplier, + SSEdges, + ViewportSize, + VisibleCenter, + YuvColorMatrix, + YuvOffsetVector, + + KnownUniformCount + }; + + KnownUniform() { + mName = NotAKnownUniform; + mNameString = nullptr; + mLocation = -1; + memset(&mValue, 0, sizeof(mValue)); + } + + bool UpdateUniform(int32_t i1) { + if (mLocation == -1) return false; + if (mValue.i1 != i1) { + mValue.i1 = i1; + return true; + } + return false; + } + + bool UpdateUniform(float f1) { + if (mLocation == -1) return false; + if (mValue.f1 != f1) { + mValue.f1 = f1; + return true; + } + return false; + } + + bool UpdateUniform(float f1, float f2) { + if (mLocation == -1) return false; + if (mValue.f16v[0] != f1 || mValue.f16v[1] != f2) { + mValue.f16v[0] = f1; + mValue.f16v[1] = f2; + return true; + } + return false; + } + + bool UpdateUniform(float f1, float f2, float f3, float f4) { + if (mLocation == -1) return false; + if (mValue.f16v[0] != f1 || mValue.f16v[1] != f2 || mValue.f16v[2] != f3 || + mValue.f16v[3] != f4) { + mValue.f16v[0] = f1; + mValue.f16v[1] = f2; + mValue.f16v[2] = f3; + mValue.f16v[3] = f4; + return true; + } + return false; + } + + bool UpdateUniform(int cnt, const float* fp) { + if (mLocation == -1) return false; + switch (cnt) { + case 1: + case 2: + case 3: + case 4: + case 9: + case 16: + if (memcmp(mValue.f16v, fp, sizeof(float) * cnt) != 0) { + memcpy(mValue.f16v, fp, sizeof(float) * cnt); + return true; + } + return false; + } + + MOZ_ASSERT_UNREACHABLE("cnt must be 1 2 3 4 9 or 16"); + return false; + } + + bool UpdateArrayUniform(int cnt, const float* fp) { + if (mLocation == -1) return false; + if (cnt > 16) { + return false; + } + + if (memcmp(mValue.f16v, fp, sizeof(float) * cnt) != 0) { + memcpy(mValue.f16v, fp, sizeof(float) * cnt); + return true; + } + return false; + } + + bool UpdateArrayUniform(int cnt, const gfx::Point3D* points) { + if (mLocation == -1) return false; + if (cnt > 4) { + return false; + } + + float fp[12]; + float* d = fp; + for (int i = 0; i < cnt; i++) { + // Note: Do not want to make assumptions about .x, .y, .z member packing. + // If gfx::Point3D is updated to make this guarantee, SIMD optimizations + // may be possible + *d++ = points[i].x; + *d++ = points[i].y; + *d++ = points[i].z; + } + + if (memcmp(mValue.f16v, fp, sizeof(float) * cnt * 3) != 0) { + memcpy(mValue.f16v, fp, sizeof(float) * cnt * 3); + return true; + } + return false; + } + + KnownUniformName mName; + const char* mNameString; + int32_t mLocation; + + union { + int i1; + float f1; + float f16v[16]; + } mValue; +}; + +class ShaderConfigOGL { + public: + ShaderConfigOGL() + : mFeatures(0), + mMultiplier(1), + mCompositionOp(gfx::CompositionOp::OP_OVER) {} + + void SetRenderColor(bool aEnabled); + void SetTextureTarget(GLenum aTarget); + void SetMaskTextureTarget(GLenum aTarget); + void SetRBSwap(bool aEnabled); + void SetNoAlpha(bool aEnabled); + void SetOpacity(bool aEnabled); + void SetYCbCr(bool aEnabled); + void SetNV12(bool aEnabled); + void SetComponentAlpha(bool aEnabled); + void SetColorMatrix(bool aEnabled); + void SetBlur(bool aEnabled); + void SetMask(bool aEnabled); + void SetDEAA(bool aEnabled); + void SetCompositionOp(gfx::CompositionOp aOp); + void SetNoPremultipliedAlpha(); + void SetDynamicGeometry(bool aEnabled); + void SetColorMultiplier(uint32_t aMultiplier); + + bool operator<(const ShaderConfigOGL& other) const { + return mFeatures < other.mFeatures || + (mFeatures == other.mFeatures && + (int)mCompositionOp < (int)other.mCompositionOp) || + (mFeatures == other.mFeatures && + (int)mCompositionOp == (int)other.mCompositionOp && + mMultiplier < other.mMultiplier); + } + + public: + void SetFeature(int aBitmask, bool aState) { + if (aState) + mFeatures |= aBitmask; + else + mFeatures &= (~aBitmask); + } + + int mFeatures; + uint32_t mMultiplier; + gfx::CompositionOp mCompositionOp; +}; + +static inline ShaderConfigOGL ShaderConfigFromTargetAndFormat( + GLenum aTarget, gfx::SurfaceFormat aFormat) { + ShaderConfigOGL config; + config.SetTextureTarget(aTarget); + config.SetRBSwap(aFormat == gfx::SurfaceFormat::B8G8R8A8 || + aFormat == gfx::SurfaceFormat::B8G8R8X8); + config.SetNoAlpha(aFormat == gfx::SurfaceFormat::B8G8R8X8 || + aFormat == gfx::SurfaceFormat::R8G8B8X8 || + aFormat == gfx::SurfaceFormat::R5G6B5_UINT16); + return config; +} + +} // namespace layers +} // namespace mozilla + +#endif // GFX_OGLSHADERCONFIG_H diff --git a/gfx/layers/opengl/OGLShaderProgram.cpp b/gfx/layers/opengl/OGLShaderProgram.cpp new file mode 100644 index 0000000000..a5efa07f48 --- /dev/null +++ b/gfx/layers/opengl/OGLShaderProgram.cpp @@ -0,0 +1,1058 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "OGLShaderProgram.h" + +#include <stdint.h> // for uint32_t + +#include <sstream> // for std::ostringstream + +#include "GLContext.h" +#include "gfxEnv.h" +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" +#include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/gfx/Logging.h" +#include "mozilla/layers/Compositor.h" // for BlendOpIsMixBlendMode +#include "nsAString.h" +#include "nsString.h" // for nsAutoCString + +namespace mozilla { +namespace layers { + +using std::endl; + +#define GAUSSIAN_KERNEL_HALF_WIDTH 11 +#define GAUSSIAN_KERNEL_STEP 0.2 + +static void AddUniforms(ProgramProfileOGL& aProfile) { + // This needs to be kept in sync with the KnownUniformName enum + static const char* sKnownUniformNames[] = {"uLayerTransform", + "uLayerTransformInverse", + "uMaskTransform", + "uBackdropTransform", + "uLayerRects", + "uMatrixProj", + "uTextureTransform", + "uTextureRects", + "uRenderTargetOffset", + "uLayerOpacity", + "uTexture", + "uYTexture", + "uCbTexture", + "uCrTexture", + "uRenderColor", + "uTexCoordMultiplier", + "uCbCrTexCoordMultiplier", + "uSSEdges", + "uViewportSize", + "uVisibleCenter", + "uYuvColorMatrix", + "uYuvOffsetVector", + nullptr}; + + for (int i = 0; sKnownUniformNames[i] != nullptr; ++i) { + aProfile.mUniforms[i].mNameString = sKnownUniformNames[i]; + aProfile.mUniforms[i].mName = (KnownUniform::KnownUniformName)i; + } +} + +void ShaderConfigOGL::SetRenderColor(bool aEnabled) { + SetFeature(ENABLE_RENDER_COLOR, aEnabled); +} + +void ShaderConfigOGL::SetTextureTarget(GLenum aTarget) { + SetFeature(ENABLE_TEXTURE_EXTERNAL | ENABLE_TEXTURE_RECT, false); + switch (aTarget) { + case LOCAL_GL_TEXTURE_EXTERNAL: + SetFeature(ENABLE_TEXTURE_EXTERNAL, true); + break; + case LOCAL_GL_TEXTURE_RECTANGLE_ARB: + SetFeature(ENABLE_TEXTURE_RECT, true); + break; + } +} + +void ShaderConfigOGL::SetMaskTextureTarget(GLenum aTarget) { + if (aTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB) { + SetFeature(ENABLE_MASK_TEXTURE_RECT, true); + } else { + MOZ_ASSERT(aTarget == LOCAL_GL_TEXTURE_2D); + SetFeature(ENABLE_MASK_TEXTURE_RECT, false); + } +} + +void ShaderConfigOGL::SetRBSwap(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_RB_SWAP, aEnabled); +} + +void ShaderConfigOGL::SetNoAlpha(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_NO_ALPHA, aEnabled); +} + +void ShaderConfigOGL::SetOpacity(bool aEnabled) { + SetFeature(ENABLE_OPACITY, aEnabled); +} + +void ShaderConfigOGL::SetYCbCr(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_YCBCR, aEnabled); + MOZ_ASSERT(!(mFeatures & ENABLE_TEXTURE_NV12)); +} + +void ShaderConfigOGL::SetColorMultiplier(uint32_t aMultiplier) { + MOZ_ASSERT(mFeatures & ENABLE_TEXTURE_YCBCR, + "Multiplier only supported with YCbCr!"); + mMultiplier = aMultiplier; +} + +void ShaderConfigOGL::SetNV12(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_NV12, aEnabled); + MOZ_ASSERT(!(mFeatures & ENABLE_TEXTURE_YCBCR)); +#ifdef MOZ_WAYLAND + SetFeature(ENABLE_TEXTURE_NV12_GA_SWITCH, aEnabled); +#endif +} + +void ShaderConfigOGL::SetComponentAlpha(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_COMPONENT_ALPHA, aEnabled); +} + +void ShaderConfigOGL::SetColorMatrix(bool aEnabled) { + SetFeature(ENABLE_COLOR_MATRIX, aEnabled); +} + +void ShaderConfigOGL::SetBlur(bool aEnabled) { + SetFeature(ENABLE_BLUR, aEnabled); +} + +void ShaderConfigOGL::SetMask(bool aEnabled) { + SetFeature(ENABLE_MASK, aEnabled); +} + +void ShaderConfigOGL::SetNoPremultipliedAlpha() { + SetFeature(ENABLE_NO_PREMUL_ALPHA, true); +} + +void ShaderConfigOGL::SetDEAA(bool aEnabled) { + SetFeature(ENABLE_DEAA, aEnabled); +} + +void ShaderConfigOGL::SetCompositionOp(gfx::CompositionOp aOp) { + mCompositionOp = aOp; +} + +void ShaderConfigOGL::SetDynamicGeometry(bool aEnabled) { + SetFeature(ENABLE_DYNAMIC_GEOMETRY, aEnabled); +} + +/* static */ +ProgramProfileOGL ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig) { + ProgramProfileOGL result; + std::ostringstream fs, vs; + + AddUniforms(result); + + gfx::CompositionOp blendOp = aConfig.mCompositionOp; + + vs << "#ifdef GL_ES" << endl; + vs << "#define EDGE_PRECISION mediump" << endl; + vs << "#else" << endl; + vs << "#define EDGE_PRECISION" << endl; + vs << "#endif" << endl; + vs << "uniform mat4 uMatrixProj;" << endl; + vs << "uniform vec4 uLayerRects[4];" << endl; + vs << "uniform mat4 uLayerTransform;" << endl; + if (aConfig.mFeatures & ENABLE_DEAA) { + vs << "uniform mat4 uLayerTransformInverse;" << endl; + vs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl; + vs << "uniform vec2 uVisibleCenter;" << endl; + vs << "uniform vec2 uViewportSize;" << endl; + } + vs << "uniform vec2 uRenderTargetOffset;" << endl; + + if (!(aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY)) { + vs << "attribute vec4 aCoord;" << endl; + } else { + vs << "attribute vec2 aCoord;" << endl; + } + + result.mAttributes.AppendElement(std::pair<nsCString, GLuint>{"aCoord", 0}); + + if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + vs << "uniform mat4 uTextureTransform;" << endl; + vs << "uniform vec4 uTextureRects[4];" << endl; + vs << "varying vec2 vTexCoord;" << endl; + + if (aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY) { + vs << "attribute vec2 aTexCoord;" << endl; + result.mAttributes.AppendElement( + std::pair<nsCString, GLuint>{"aTexCoord", 1}); + } + } + + if (BlendOpIsMixBlendMode(blendOp)) { + vs << "uniform mat4 uBackdropTransform;" << endl; + vs << "varying vec2 vBackdropCoord;" << endl; + } + + if (aConfig.mFeatures & ENABLE_MASK) { + vs << "uniform mat4 uMaskTransform;" << endl; + vs << "varying vec3 vMaskCoord;" << endl; + } + + vs << "void main() {" << endl; + + if (aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY) { + vs << " vec4 finalPosition = vec4(aCoord.xy, 0.0, 1.0);" << endl; + } else { + vs << " int vertexID = int(aCoord.w);" << endl; + vs << " vec4 layerRect = uLayerRects[vertexID];" << endl; + vs << " vec4 finalPosition = vec4(aCoord.xy * layerRect.zw + " + "layerRect.xy, 0.0, 1.0);" + << endl; + } + + vs << " finalPosition = uLayerTransform * finalPosition;" << endl; + + if (aConfig.mFeatures & ENABLE_DEAA) { + // XXX kip - The DEAA shader could be made simpler if we switch to + // using dynamic vertex buffers instead of sending everything + // in through uniforms. This would enable passing information + // about how to dilate each vertex explicitly and eliminate the + // need to extrapolate this with the sub-pixel coverage + // calculation in the vertex shader. + + // Calculate the screen space position of this vertex, in screen pixels + vs << " vec4 ssPos = finalPosition;" << endl; + vs << " ssPos.xy -= uRenderTargetOffset * finalPosition.w;" << endl; + vs << " ssPos = uMatrixProj * ssPos;" << endl; + vs << " ssPos.xy = ((ssPos.xy/ssPos.w)*0.5+0.5)*uViewportSize;" << endl; + + if (aConfig.mFeatures & ENABLE_MASK || + !(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + vs << " vec4 coordAdjusted;" << endl; + vs << " coordAdjusted.xy = aCoord.xy;" << endl; + } + + // It is necessary to dilate edges away from uVisibleCenter to ensure that + // fragments with less than 50% sub-pixel coverage will be shaded. + // This offset is applied when the sub-pixel coverage of the vertex is + // less than 100%. Expanding by 0.5 pixels in screen space is sufficient + // to include these pixels. + vs << " if (dot(uSSEdges[0], vec3(ssPos.xy, 1.0)) < 1.5 ||" << endl; + vs << " dot(uSSEdges[1], vec3(ssPos.xy, 1.0)) < 1.5 ||" << endl; + vs << " dot(uSSEdges[2], vec3(ssPos.xy, 1.0)) < 1.5 ||" << endl; + vs << " dot(uSSEdges[3], vec3(ssPos.xy, 1.0)) < 1.5) {" << endl; + // If the shader reaches this branch, then this vertex is on the edge of + // the layer's visible rect and should be dilated away from the center of + // the visible rect. We don't want to hit this for inner facing + // edges between tiles, as the pixels may be covered twice without clipping + // against uSSEdges. If all edges were dilated, it would result in + // artifacts visible within semi-transparent layers with multiple tiles. + vs << " vec4 visibleCenter = uLayerTransform * vec4(uVisibleCenter, " + "0.0, 1.0);" + << endl; + vs << " vec2 dilateDir = finalPosition.xy / finalPosition.w - " + "visibleCenter.xy / visibleCenter.w;" + << endl; + vs << " vec2 offset = sign(dilateDir) * 0.5;" << endl; + vs << " finalPosition.xy += offset * finalPosition.w;" << endl; + if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + // We must adjust the texture coordinates to compensate for the dilation + vs << " coordAdjusted = uLayerTransformInverse * finalPosition;" + << endl; + vs << " coordAdjusted /= coordAdjusted.w;" << endl; + + if (!(aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY)) { + vs << " coordAdjusted.xy -= layerRect.xy;" << endl; + vs << " coordAdjusted.xy /= layerRect.zw;" << endl; + } + } + vs << " }" << endl; + + if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + if (aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY) { + vs << " vTexCoord = (uTextureTransform * vec4(aTexCoord, 0.0, " + "1.0)).xy;" + << endl; + } else { + vs << " vec4 textureRect = uTextureRects[vertexID];" << endl; + vs << " vec2 texCoord = coordAdjusted.xy * textureRect.zw + " + "textureRect.xy;" + << endl; + vs << " vTexCoord = (uTextureTransform * vec4(texCoord, 0.0, 1.0)).xy;" + << endl; + } + } + } else if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + if (aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY) { + vs << " vTexCoord = (uTextureTransform * vec4(aTexCoord, 0.0, 1.0)).xy;" + << endl; + } else { + vs << " vec4 textureRect = uTextureRects[vertexID];" << endl; + vs << " vec2 texCoord = aCoord.xy * textureRect.zw + textureRect.xy;" + << endl; + vs << " vTexCoord = (uTextureTransform * vec4(texCoord, 0.0, 1.0)).xy;" + << endl; + } + } + + if (aConfig.mFeatures & ENABLE_MASK) { + vs << " vMaskCoord.xy = (uMaskTransform * (finalPosition / " + "finalPosition.w)).xy;" + << endl; + // correct for perspective correct interpolation, see comment in D3D11 + // shader + vs << " vMaskCoord.z = 1.0;" << endl; + vs << " vMaskCoord *= finalPosition.w;" << endl; + } + vs << " finalPosition.xy -= uRenderTargetOffset * finalPosition.w;" << endl; + vs << " finalPosition = uMatrixProj * finalPosition;" << endl; + if (BlendOpIsMixBlendMode(blendOp)) { + // Translate from clip space (-1, 1) to (0..1), apply the backdrop + // transform, then invert the y-axis. + vs << " vBackdropCoord.x = (finalPosition.x + 1.0) / 2.0;" << endl; + vs << " vBackdropCoord.y = 1.0 - (finalPosition.y + 1.0) / 2.0;" << endl; + vs << " vBackdropCoord = (uBackdropTransform * vec4(vBackdropCoord.xy, " + "0.0, 1.0)).xy;" + << endl; + vs << " vBackdropCoord.y = 1.0 - vBackdropCoord.y;" << endl; + } + vs << " gl_Position = finalPosition;" << endl; + vs << "}" << endl; + + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << "#extension GL_ARB_texture_rectangle : require" << endl; + } + if (aConfig.mFeatures & ENABLE_TEXTURE_EXTERNAL) { + fs << "#extension GL_OES_EGL_image_external : require" << endl; + } + fs << "#ifdef GL_ES" << endl; + fs << "precision mediump float;" << endl; + fs << "#define COLOR_PRECISION lowp" << endl; + fs << "#define EDGE_PRECISION mediump" << endl; + fs << "#else" << endl; + fs << "#define COLOR_PRECISION" << endl; + fs << "#define EDGE_PRECISION" << endl; + fs << "#endif" << endl; + if (aConfig.mFeatures & ENABLE_RENDER_COLOR) { + fs << "uniform COLOR_PRECISION vec4 uRenderColor;" << endl; + } else { + // for tiling, texcoord can be greater than the lowfp range + fs << "varying vec2 vTexCoord;" << endl; + if (aConfig.mFeatures & ENABLE_BLUR) { + fs << "uniform bool uBlurAlpha;" << endl; + fs << "uniform vec2 uBlurRadius;" << endl; + fs << "uniform vec2 uBlurOffset;" << endl; + fs << "uniform float uBlurGaussianKernel[" << GAUSSIAN_KERNEL_HALF_WIDTH + << "];" << endl; + } + if (aConfig.mFeatures & ENABLE_COLOR_MATRIX) { + fs << "uniform mat4 uColorMatrix;" << endl; + fs << "uniform vec4 uColorMatrixVector;" << endl; + } + if (aConfig.mFeatures & ENABLE_OPACITY) { + fs << "uniform COLOR_PRECISION float uLayerOpacity;" << endl; + } + } + if (BlendOpIsMixBlendMode(blendOp)) { + fs << "varying vec2 vBackdropCoord;" << endl; + } + + const char* sampler2D = "sampler2D"; + const char* texture2D = "texture2D"; + + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << "uniform vec2 uTexCoordMultiplier;" << endl; + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR || + aConfig.mFeatures & ENABLE_TEXTURE_NV12) { + fs << "uniform vec2 uCbCrTexCoordMultiplier;" << endl; + } + sampler2D = "sampler2DRect"; + texture2D = "texture2DRect"; + } + + const char* maskSampler2D = "sampler2D"; + const char* maskTexture2D = "texture2D"; + + if (aConfig.mFeatures & ENABLE_MASK && + aConfig.mFeatures & ENABLE_MASK_TEXTURE_RECT) { + fs << "uniform vec2 uMaskCoordMultiplier;" << endl; + maskSampler2D = "sampler2DRect"; + maskTexture2D = "texture2DRect"; + } + + if (aConfig.mFeatures & ENABLE_TEXTURE_EXTERNAL) { + sampler2D = "samplerExternalOES"; + } + + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR) { + fs << "uniform " << sampler2D << " uYTexture;" << endl; + fs << "uniform " << sampler2D << " uCbTexture;" << endl; + fs << "uniform " << sampler2D << " uCrTexture;" << endl; + fs << "uniform mat3 uYuvColorMatrix;" << endl; + fs << "uniform vec3 uYuvOffsetVector;" << endl; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_NV12) { + fs << "uniform " << sampler2D << " uYTexture;" << endl; + fs << "uniform " << sampler2D << " uCbTexture;" << endl; + fs << "uniform mat3 uYuvColorMatrix;" << endl; + fs << "uniform vec3 uYuvOffsetVector;" << endl; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA) { + fs << "uniform " << sampler2D << " uBlackTexture;" << endl; + fs << "uniform " << sampler2D << " uWhiteTexture;" << endl; + fs << "uniform bool uTexturePass2;" << endl; + } else { + fs << "uniform " << sampler2D << " uTexture;" << endl; + } + + if (BlendOpIsMixBlendMode(blendOp)) { + // Component alpha should be flattened away inside blend containers. + MOZ_ASSERT(!(aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA)); + + fs << "uniform sampler2D uBackdropTexture;" << endl; + } + + if (aConfig.mFeatures & ENABLE_MASK) { + fs << "varying vec3 vMaskCoord;" << endl; + fs << "uniform " << maskSampler2D << " uMaskTexture;" << endl; + } + + if (aConfig.mFeatures & ENABLE_DEAA) { + fs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl; + } + + if (BlendOpIsMixBlendMode(blendOp)) { + BuildMixBlender(aConfig, fs); + } + + if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + fs << "vec4 sample(vec2 coord) {" << endl; + fs << " vec4 color;" << endl; + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR || + aConfig.mFeatures & ENABLE_TEXTURE_NV12) { + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR) { + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << " COLOR_PRECISION float y = " << texture2D + << "(uYTexture, coord * uTexCoordMultiplier).r;" << endl; + fs << " COLOR_PRECISION float cb = " << texture2D + << "(uCbTexture, coord * uCbCrTexCoordMultiplier).r;" << endl; + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCrTexture, coord * uCbCrTexCoordMultiplier).r;" << endl; + } else { + fs << " COLOR_PRECISION float y = " << texture2D + << "(uYTexture, coord).r;" << endl; + fs << " COLOR_PRECISION float cb = " << texture2D + << "(uCbTexture, coord).r;" << endl; + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCrTexture, coord).r;" << endl; + } + } else { + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << " COLOR_PRECISION float y = " << texture2D + << "(uYTexture, coord * uTexCoordMultiplier).r;" << endl; + fs << " COLOR_PRECISION float cb = " << texture2D + << "(uCbTexture, coord * uCbCrTexCoordMultiplier).r;" << endl; + if (aConfig.mFeatures & ENABLE_TEXTURE_NV12_GA_SWITCH) { + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCbTexture, coord * uCbCrTexCoordMultiplier).g;" << endl; + } else { + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCbTexture, coord * uCbCrTexCoordMultiplier).a;" << endl; + } + } else { + fs << " COLOR_PRECISION float y = " << texture2D + << "(uYTexture, coord).r;" << endl; + fs << " COLOR_PRECISION float cb = " << texture2D + << "(uCbTexture, coord).r;" << endl; + if (aConfig.mFeatures & ENABLE_TEXTURE_NV12_GA_SWITCH) { + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCbTexture, coord).g;" << endl; + } else { + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCbTexture, coord).a;" << endl; + } + } + } + fs << " vec3 yuv = vec3(y, cb, cr);" << endl; + if (aConfig.mMultiplier != 1) { + fs << " yuv *= " << aConfig.mMultiplier << ".0;" << endl; + } + fs << " yuv -= uYuvOffsetVector;" << endl; + fs << " color.rgb = uYuvColorMatrix * yuv;" << endl; + fs << " color.a = 1.0;" << endl; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA) { + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << " COLOR_PRECISION vec3 onBlack = " << texture2D + << "(uBlackTexture, coord * uTexCoordMultiplier).rgb;" << endl; + fs << " COLOR_PRECISION vec3 onWhite = " << texture2D + << "(uWhiteTexture, coord * uTexCoordMultiplier).rgb;" << endl; + } else { + fs << " COLOR_PRECISION vec3 onBlack = " << texture2D + << "(uBlackTexture, coord).rgb;" << endl; + fs << " COLOR_PRECISION vec3 onWhite = " << texture2D + << "(uWhiteTexture, coord).rgb;" << endl; + } + fs << " COLOR_PRECISION vec4 alphas = (1.0 - onWhite + onBlack).rgbg;" + << endl; + fs << " if (uTexturePass2)" << endl; + fs << " color = vec4(onBlack, alphas.a);" << endl; + fs << " else" << endl; + fs << " color = alphas;" << endl; + } else { + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << " color = " << texture2D + << "(uTexture, coord * uTexCoordMultiplier);" << endl; + } else { + fs << " color = " << texture2D << "(uTexture, coord);" << endl; + } + } + if (aConfig.mFeatures & ENABLE_TEXTURE_RB_SWAP) { + fs << " color = color.bgra;" << endl; + } + if (aConfig.mFeatures & ENABLE_TEXTURE_NO_ALPHA) { + fs << " color = vec4(color.rgb, 1.0);" << endl; + } + fs << " return color;" << endl; + fs << "}" << endl; + if (aConfig.mFeatures & ENABLE_BLUR) { + fs << "vec4 sampleAtRadius(vec2 coord, float radius) {" << endl; + fs << " coord += uBlurOffset;" << endl; + fs << " coord += radius * uBlurRadius;" << endl; + fs << " if (coord.x < 0. || coord.y < 0. || coord.x > 1. || coord.y > " + "1.)" + << endl; + fs << " return vec4(0, 0, 0, 0);" << endl; + fs << " return sample(coord);" << endl; + fs << "}" << endl; + fs << "vec4 blur(vec4 color, vec2 coord) {" << endl; + fs << " vec4 total = color * uBlurGaussianKernel[0];" << endl; + fs << " for (int i = 1; i < " << GAUSSIAN_KERNEL_HALF_WIDTH << "; ++i) {" + << endl; + fs << " float r = float(i) * " << GAUSSIAN_KERNEL_STEP << ";" << endl; + fs << " float k = uBlurGaussianKernel[i];" << endl; + fs << " total += sampleAtRadius(coord, r) * k;" << endl; + fs << " total += sampleAtRadius(coord, -r) * k;" << endl; + fs << " }" << endl; + fs << " if (uBlurAlpha) {" << endl; + fs << " color *= total.a;" << endl; + fs << " } else {" << endl; + fs << " color = total;" << endl; + fs << " }" << endl; + fs << " return color;" << endl; + fs << "}" << endl; + } + } + fs << "void main() {" << endl; + if (aConfig.mFeatures & ENABLE_RENDER_COLOR) { + fs << " vec4 color = uRenderColor;" << endl; + } else { + fs << " vec4 color = sample(vTexCoord);" << endl; + if (aConfig.mFeatures & ENABLE_BLUR) { + fs << " color = blur(color, vTexCoord);" << endl; + } + if (aConfig.mFeatures & ENABLE_COLOR_MATRIX) { + fs << " color = uColorMatrix * vec4(color.rgb / color.a, color.a) + " + "uColorMatrixVector;" + << endl; + fs << " color.rgb *= color.a;" << endl; + } + if (aConfig.mFeatures & ENABLE_OPACITY) { + fs << " color *= uLayerOpacity;" << endl; + } + } + if (aConfig.mFeatures & ENABLE_DEAA) { + // Calculate the sub-pixel coverage of the pixel and modulate its opacity + // by that amount to perform DEAA. + fs << " vec3 ssPos = vec3(gl_FragCoord.xy, 1.0);" << endl; + fs << " float deaaCoverage = clamp(dot(uSSEdges[0], ssPos), 0.0, 1.0);" + << endl; + fs << " deaaCoverage *= clamp(dot(uSSEdges[1], ssPos), 0.0, 1.0);" << endl; + fs << " deaaCoverage *= clamp(dot(uSSEdges[2], ssPos), 0.0, 1.0);" << endl; + fs << " deaaCoverage *= clamp(dot(uSSEdges[3], ssPos), 0.0, 1.0);" << endl; + fs << " color *= deaaCoverage;" << endl; + } + if (BlendOpIsMixBlendMode(blendOp)) { + fs << " vec4 backdrop = texture2D(uBackdropTexture, vBackdropCoord);" + << endl; + fs << " color = mixAndBlend(backdrop, color);" << endl; + } + if (aConfig.mFeatures & ENABLE_MASK) { + fs << " vec2 maskCoords = vMaskCoord.xy / vMaskCoord.z;" << endl; + if (aConfig.mFeatures & ENABLE_MASK_TEXTURE_RECT) { + fs << " COLOR_PRECISION float mask = " << maskTexture2D + << "(uMaskTexture, maskCoords * uMaskCoordMultiplier).r;" << endl; + } else { + fs << " COLOR_PRECISION float mask = " << maskTexture2D + << "(uMaskTexture, maskCoords).r;" << endl; + } + fs << " color *= mask;" << endl; + } else { + fs << " COLOR_PRECISION float mask = 1.0;" << endl; + fs << " color *= mask;" << endl; + } + fs << " gl_FragColor = color;" << endl; + fs << "}" << endl; + + result.mVertexShaderString = vs.str(); + result.mFragmentShaderString = fs.str(); + + if (aConfig.mFeatures & ENABLE_RENDER_COLOR) { + result.mTextureCount = 0; + } else { + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR) { + result.mTextureCount = 3; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_NV12) { + result.mTextureCount = 2; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA) { + result.mTextureCount = 2; + } else { + result.mTextureCount = 1; + } + } + if (aConfig.mFeatures & ENABLE_MASK) { + result.mTextureCount = 1; + } + if (BlendOpIsMixBlendMode(blendOp)) { + result.mTextureCount += 1; + } + + return result; +} + +void ProgramProfileOGL::BuildMixBlender(const ShaderConfigOGL& aConfig, + std::ostringstream& fs) { + // From the "Compositing and Blending Level 1" spec. + // Generate helper functions first. + switch (aConfig.mCompositionOp) { + case gfx::CompositionOp::OP_OVERLAY: + case gfx::CompositionOp::OP_HARD_LIGHT: + // Note: we substitute (2*src-1) into the screen formula below. + fs << "float hardlight(float dest, float src) {" << endl; + fs << " if (src <= 0.5) {" << endl; + fs << " return dest * (2.0 * src);" << endl; + fs << " } else {" << endl; + fs << " return 2.0*dest + 2.0*src - 1.0 - 2.0*dest*src;" << endl; + fs << " }" << endl; + fs << "}" << endl; + break; + case gfx::CompositionOp::OP_COLOR_DODGE: + fs << "float dodge(float dest, float src) {" << endl; + fs << " if (dest == 0.0) {" << endl; + fs << " return 0.0;" << endl; + fs << " } else if (src == 1.0) {" << endl; + fs << " return 1.0;" << endl; + fs << " } else {" << endl; + fs << " return min(1.0, dest / (1.0 - src));" << endl; + fs << " }" << endl; + fs << "}" << endl; + break; + case gfx::CompositionOp::OP_COLOR_BURN: + fs << "float burn(float dest, float src) {" << endl; + fs << " if (dest == 1.0) {" << endl; + fs << " return 1.0;" << endl; + fs << " } else if (src == 0.0) {" << endl; + fs << " return 0.0;" << endl; + fs << " } else {" << endl; + fs << " return 1.0 - min(1.0, (1.0 - dest) / src);" << endl; + fs << " }" << endl; + fs << "}" << endl; + break; + case gfx::CompositionOp::OP_SOFT_LIGHT: + fs << "float darken(float dest) {" << endl; + fs << " if (dest <= 0.25) {" << endl; + fs << " return ((16.0 * dest - 12.0) * dest + 4.0) * dest;" << endl; + fs << " } else {" << endl; + fs << " return sqrt(dest);" << endl; + fs << " }" << endl; + fs << "}" << endl; + fs << "float softlight(float dest, float src) {" << endl; + fs << " if (src <= 0.5) {" << endl; + fs << " return dest - (1.0 - 2.0 * src) * dest * (1.0 - dest);" + << endl; + fs << " } else {" << endl; + fs << " return dest + (2.0 * src - 1.0) * (darken(dest) - dest);" + << endl; + fs << " }" << endl; + fs << "}" << endl; + break; + case gfx::CompositionOp::OP_HUE: + case gfx::CompositionOp::OP_SATURATION: + case gfx::CompositionOp::OP_COLOR: + case gfx::CompositionOp::OP_LUMINOSITY: + fs << "float Lum(vec3 c) {" << endl; + fs << " return dot(vec3(0.3, 0.59, 0.11), c);" << endl; + fs << "}" << endl; + fs << "vec3 ClipColor(vec3 c) {" << endl; + fs << " float L = Lum(c);" << endl; + fs << " float n = min(min(c.r, c.g), c.b);" << endl; + fs << " float x = max(max(c.r, c.g), c.b);" << endl; + fs << " if (n < 0.0) {" << endl; + fs << " c = L + (((c - L) * L) / (L - n));" << endl; + fs << " }" << endl; + fs << " if (x > 1.0) {" << endl; + fs << " c = L + (((c - L) * (1.0 - L)) / (x - L));" << endl; + fs << " }" << endl; + fs << " return c;" << endl; + fs << "}" << endl; + fs << "vec3 SetLum(vec3 c, float L) {" << endl; + fs << " float d = L - Lum(c);" << endl; + fs << " return ClipColor(vec3(" << endl; + fs << " c.r + d," << endl; + fs << " c.g + d," << endl; + fs << " c.b + d));" << endl; + fs << "}" << endl; + fs << "float Sat(vec3 c) {" << endl; + fs << " return max(max(c.r, c.g), c.b) - min(min(c.r, c.g), c.b);" + << endl; + fs << "}" << endl; + + // To use this helper, re-arrange rgb such that r=min, g=mid, and b=max. + fs << "vec3 SetSatInner(vec3 c, float s) {" << endl; + fs << " if (c.b > c.r) {" << endl; + fs << " c.g = (((c.g - c.r) * s) / (c.b - c.r));" << endl; + fs << " c.b = s;" << endl; + fs << " } else {" << endl; + fs << " c.gb = vec2(0.0, 0.0);" << endl; + fs << " }" << endl; + fs << " return vec3(0.0, c.gb);" << endl; + fs << "}" << endl; + + fs << "vec3 SetSat(vec3 c, float s) {" << endl; + fs << " if (c.r <= c.g) {" << endl; + fs << " if (c.g <= c.b) {" << endl; + fs << " c.rgb = SetSatInner(c.rgb, s);" << endl; + fs << " } else if (c.r <= c.b) {" << endl; + fs << " c.rbg = SetSatInner(c.rbg, s);" << endl; + fs << " } else {" << endl; + fs << " c.brg = SetSatInner(c.brg, s);" << endl; + fs << " }" << endl; + fs << " } else if (c.r <= c.b) {" << endl; + fs << " c.grb = SetSatInner(c.grb, s);" << endl; + fs << " } else if (c.g <= c.b) {" << endl; + fs << " c.gbr = SetSatInner(c.gbr, s);" << endl; + fs << " } else {" << endl; + fs << " c.bgr = SetSatInner(c.bgr, s);" << endl; + fs << " }" << endl; + fs << " return c;" << endl; + fs << "}" << endl; + break; + default: + break; + } + + // Generate the main blending helper. + fs << "vec3 blend(vec3 dest, vec3 src) {" << endl; + switch (aConfig.mCompositionOp) { + case gfx::CompositionOp::OP_MULTIPLY: + fs << " return dest * src;" << endl; + break; + case gfx::CompositionOp::OP_SCREEN: + fs << " return dest + src - (dest * src);" << endl; + break; + case gfx::CompositionOp::OP_OVERLAY: + fs << " return vec3(" << endl; + fs << " hardlight(src.r, dest.r)," << endl; + fs << " hardlight(src.g, dest.g)," << endl; + fs << " hardlight(src.b, dest.b));" << endl; + break; + case gfx::CompositionOp::OP_DARKEN: + fs << " return min(dest, src);" << endl; + break; + case gfx::CompositionOp::OP_LIGHTEN: + fs << " return max(dest, src);" << endl; + break; + case gfx::CompositionOp::OP_COLOR_DODGE: + fs << " return vec3(" << endl; + fs << " dodge(dest.r, src.r)," << endl; + fs << " dodge(dest.g, src.g)," << endl; + fs << " dodge(dest.b, src.b));" << endl; + break; + case gfx::CompositionOp::OP_COLOR_BURN: + fs << " return vec3(" << endl; + fs << " burn(dest.r, src.r)," << endl; + fs << " burn(dest.g, src.g)," << endl; + fs << " burn(dest.b, src.b));" << endl; + break; + case gfx::CompositionOp::OP_HARD_LIGHT: + fs << " return vec3(" << endl; + fs << " hardlight(dest.r, src.r)," << endl; + fs << " hardlight(dest.g, src.g)," << endl; + fs << " hardlight(dest.b, src.b));" << endl; + break; + case gfx::CompositionOp::OP_SOFT_LIGHT: + fs << " return vec3(" << endl; + fs << " softlight(dest.r, src.r)," << endl; + fs << " softlight(dest.g, src.g)," << endl; + fs << " softlight(dest.b, src.b));" << endl; + break; + case gfx::CompositionOp::OP_DIFFERENCE: + fs << " return abs(dest - src);" << endl; + break; + case gfx::CompositionOp::OP_EXCLUSION: + fs << " return dest + src - 2.0*dest*src;" << endl; + break; + case gfx::CompositionOp::OP_HUE: + fs << " return SetLum(SetSat(src, Sat(dest)), Lum(dest));" << endl; + break; + case gfx::CompositionOp::OP_SATURATION: + fs << " return SetLum(SetSat(dest, Sat(src)), Lum(dest));" << endl; + break; + case gfx::CompositionOp::OP_COLOR: + fs << " return SetLum(src, Lum(dest));" << endl; + break; + case gfx::CompositionOp::OP_LUMINOSITY: + fs << " return SetLum(dest, Lum(src));" << endl; + break; + default: + MOZ_ASSERT_UNREACHABLE("unknown blend mode"); + } + fs << "}" << endl; + + // Generate the mix-blend function the fragment shader will call. + fs << "vec4 mixAndBlend(vec4 backdrop, vec4 color) {" << endl; + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // Infinity into the blend function and return incorrect results. + fs << " if (backdrop.a == 0.0) {" << endl; + fs << " return color;" << endl; + fs << " }" << endl; + fs << " if (color.a == 0.0) {" << endl; + fs << " return vec4(0.0, 0.0, 0.0, 0.0);" << endl; + fs << " }" << endl; + + // The spec assumes there is no premultiplied alpha. The backdrop is always + // premultiplied, so undo the premultiply. If the source is premultiplied we + // must fix that as well. + fs << " backdrop.rgb /= backdrop.a;" << endl; + if (!(aConfig.mFeatures & ENABLE_NO_PREMUL_ALPHA)) { + fs << " color.rgb /= color.a;" << endl; + } + fs << " vec3 blended = blend(backdrop.rgb, color.rgb);" << endl; + fs << " color.rgb = (1.0 - backdrop.a) * color.rgb + backdrop.a * " + "blended.rgb;" + << endl; + fs << " color.rgb *= color.a;" << endl; + fs << " return color;" << endl; + fs << "}" << endl; +} + +ShaderProgramOGL::ShaderProgramOGL(GLContext* aGL, + const ProgramProfileOGL& aProfile) + : mGL(aGL), mProgram(0), mProfile(aProfile), mProgramState(STATE_NEW) {} + +ShaderProgramOGL::~ShaderProgramOGL() { + if (mProgram <= 0) { + return; + } + + RefPtr<GLContext> ctx = mGL->GetSharedContext(); + if (!ctx) { + ctx = mGL; + } + ctx->MakeCurrent(); + ctx->fDeleteProgram(mProgram); +} + +bool ShaderProgramOGL::Initialize() { + NS_ASSERTION(mProgramState == STATE_NEW, + "Shader program has already been initialised"); + + std::ostringstream vs, fs; + for (uint32_t i = 0; i < mProfile.mDefines.Length(); ++i) { + vs << mProfile.mDefines[i] << endl; + fs << mProfile.mDefines[i] << endl; + } + vs << mProfile.mVertexShaderString << endl; + fs << mProfile.mFragmentShaderString << endl; + + if (!CreateProgram(vs.str().c_str(), fs.str().c_str())) { + mProgramState = STATE_ERROR; + return false; + } + + mProgramState = STATE_OK; + + for (uint32_t i = 0; i < KnownUniform::KnownUniformCount; ++i) { + mProfile.mUniforms[i].mLocation = + mGL->fGetUniformLocation(mProgram, mProfile.mUniforms[i].mNameString); + } + + return true; +} + +GLint ShaderProgramOGL::CreateShader(GLenum aShaderType, + const char* aShaderSource) { + GLint success, len = 0; + + GLint sh = mGL->fCreateShader(aShaderType); + mGL->fShaderSource(sh, 1, (const GLchar**)&aShaderSource, nullptr); + mGL->fCompileShader(sh); + mGL->fGetShaderiv(sh, LOCAL_GL_COMPILE_STATUS, &success); + mGL->fGetShaderiv(sh, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&len); + /* Even if compiling is successful, there may still be warnings. Print them + * in a debug build. The > 10 is to catch silly compilers that might put + * some whitespace in the log but otherwise leave it empty. + */ + if (!success +#ifdef DEBUG + || (len > 10 && gfxEnv::MOZ_DEBUG_SHADERS()) +#endif + ) { + nsAutoCString log; + log.SetLength(len); + mGL->fGetShaderInfoLog(sh, len, (GLint*)&len, (char*)log.BeginWriting()); + log.Truncate(len); + + if (!success) { + printf_stderr("=== SHADER COMPILATION FAILED ===\n"); + } else { + printf_stderr("=== SHADER COMPILATION WARNINGS ===\n"); + } + + printf_stderr("=== Source:\n%s\n", aShaderSource); + printf_stderr("=== Log:\n%s\n", log.get()); + printf_stderr("============\n"); + + if (!success) { + mGL->fDeleteShader(sh); + return 0; + } + } + + return sh; +} + +bool ShaderProgramOGL::CreateProgram(const char* aVertexShaderString, + const char* aFragmentShaderString) { + GLuint vertexShader = + CreateShader(LOCAL_GL_VERTEX_SHADER, aVertexShaderString); + GLuint fragmentShader = + CreateShader(LOCAL_GL_FRAGMENT_SHADER, aFragmentShaderString); + + if (!vertexShader || !fragmentShader) return false; + + GLint result = mGL->fCreateProgram(); + mGL->fAttachShader(result, vertexShader); + mGL->fAttachShader(result, fragmentShader); + + for (std::pair<nsCString, GLuint>& attribute : mProfile.mAttributes) { + mGL->fBindAttribLocation(result, attribute.second, attribute.first.get()); + } + + mGL->fLinkProgram(result); + + GLint success, len; + mGL->fGetProgramiv(result, LOCAL_GL_LINK_STATUS, &success); + mGL->fGetProgramiv(result, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&len); + /* Even if linking is successful, there may still be warnings. Print them + * in a debug build. The > 10 is to catch silly compilers that might put + * some whitespace in the log but otherwise leave it empty. + */ + if (!success +#ifdef DEBUG + || (len > 10 && gfxEnv::MOZ_DEBUG_SHADERS()) +#endif + ) { + nsAutoCString log; + log.SetLength(len); + mGL->fGetProgramInfoLog(result, len, (GLint*)&len, + (char*)log.BeginWriting()); + + if (!success) { + printf_stderr("=== PROGRAM LINKING FAILED ===\n"); + } else { + printf_stderr("=== PROGRAM LINKING WARNINGS ===\n"); + } + printf_stderr("=== Log:\n%s\n", log.get()); + printf_stderr("============\n"); + } + + // We can mark the shaders for deletion; they're attached to the program + // and will remain attached. + mGL->fDeleteShader(vertexShader); + mGL->fDeleteShader(fragmentShader); + + if (!success) { + mGL->fDeleteProgram(result); + return false; + } + + mProgram = result; + return true; +} + +GLuint ShaderProgramOGL::GetProgram() { + if (mProgramState == STATE_NEW) { + if (!Initialize()) { + NS_WARNING("Shader could not be initialised"); + } + } + MOZ_ASSERT(HasInitialized(), + "Attempting to get a program that's not been initialized!"); + return mProgram; +} + +void ShaderProgramOGL::SetYUVColorSpace(gfx::YUVColorSpace aYUVColorSpace) { + const float* yuvToRgb = + gfxUtils::YuvToRgbMatrix3x3ColumnMajor(aYUVColorSpace); + SetMatrix3fvUniform(KnownUniform::YuvColorMatrix, yuvToRgb); + if (aYUVColorSpace == gfx::YUVColorSpace::Identity) { + const float identity[] = {0.0, 0.0, 0.0}; + SetVec3fvUniform(KnownUniform::YuvOffsetVector, identity); + } else { + const float offset[] = {0.06275, 0.50196, 0.50196}; + SetVec3fvUniform(KnownUniform::YuvOffsetVector, offset); + } +} + +ShaderProgramOGLsHolder::ShaderProgramOGLsHolder(gl::GLContext* aGL) + : mGL(aGL) {} + +ShaderProgramOGLsHolder::~ShaderProgramOGLsHolder() { Clear(); } + +ShaderProgramOGL* ShaderProgramOGLsHolder::GetShaderProgramFor( + const ShaderConfigOGL& aConfig) { + auto iter = mPrograms.find(aConfig); + if (iter != mPrograms.end()) { + return iter->second.get(); + } + + ProgramProfileOGL profile = ProgramProfileOGL::GetProfileFor(aConfig); + auto shader = MakeUnique<ShaderProgramOGL>(mGL, profile); + if (!shader->Initialize()) { + gfxCriticalError() << "Shader compilation failure, cfg:" + << " features: " << gfx::hexa(aConfig.mFeatures) + << " multiplier: " << aConfig.mMultiplier + << " op: " << aConfig.mCompositionOp; + return nullptr; + } + + mPrograms.emplace(aConfig, std::move(shader)); + return mPrograms[aConfig].get(); +} + +void ShaderProgramOGLsHolder::Clear() { mPrograms.clear(); } + +ShaderProgramOGL* ShaderProgramOGLsHolder::ActivateProgram( + const ShaderConfigOGL& aConfig) { + ShaderProgramOGL* program = GetShaderProgramFor(aConfig); + MOZ_DIAGNOSTIC_ASSERT(program); + if (!program) { + return nullptr; + } + if (mCurrentProgram != program) { + mGL->fUseProgram(program->GetProgram()); + mCurrentProgram = program; + } + return program; +} + +void ShaderProgramOGLsHolder::ResetCurrentProgram() { + mCurrentProgram = nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/OGLShaderProgram.h b/gfx/layers/opengl/OGLShaderProgram.h new file mode 100644 index 0000000000..cfeb7dd23e --- /dev/null +++ b/gfx/layers/opengl/OGLShaderProgram.h @@ -0,0 +1,377 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef GFX_OGLSHADERPROGRAM_H +#define GFX_OGLSHADERPROGRAM_H + +#include <map> +#include <string> +#include <utility> + +#include "GLContext.h" // for fast inlines of glUniform* +#include "mozilla/UniquePtr.h" +#include "OGLShaderConfig.h" + +namespace mozilla { +namespace layers { + +#if defined(DEBUG) +# define CHECK_CURRENT_PROGRAM 1 +# define ASSERT_THIS_PROGRAM \ + do { \ + GLuint currentProgram; \ + mGL->GetUIntegerv(LOCAL_GL_CURRENT_PROGRAM, ¤tProgram); \ + MOZ_ASSERT(currentProgram == mProgram, \ + "SetUniform with wrong program active!"); \ + } while (0) +#else +# define ASSERT_THIS_PROGRAM \ + do { \ + } while (0) +#endif + +/** + * This struct represents the shaders that make up a program and the uniform + * and attribute parmeters that those shaders take. + * It is used by ShaderProgramOGL. + * Use the factory method GetProfileFor to create instances. + */ +struct ProgramProfileOGL { + /** + * Factory method; creates an instance of this class for the given + * ShaderConfigOGL + */ + static ProgramProfileOGL GetProfileFor(ShaderConfigOGL aConfig); + + // the source code for the program's shaders + std::string mVertexShaderString; + std::string mFragmentShaderString; + + // the vertex attributes + CopyableTArray<std::pair<nsCString, GLuint>> mAttributes; + + KnownUniform mUniforms[KnownUniform::KnownUniformCount]; + CopyableTArray<const char*> mDefines; + size_t mTextureCount; + + ProgramProfileOGL() : mTextureCount(0) {} + + private: + static void BuildMixBlender(const ShaderConfigOGL& aConfig, + std::ostringstream& fs); +}; + +/** + * Represents an OGL shader program. The details of a program are represented + * by a ProgramProfileOGL + */ +class ShaderProgramOGL { + public: + typedef mozilla::gl::GLContext GLContext; + + ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile); + + ~ShaderProgramOGL(); + + bool HasInitialized() { + NS_ASSERTION(mProgramState != STATE_OK || mProgram > 0, + "Inconsistent program state"); + return mProgramState == STATE_OK; + } + + GLuint GetProgram(); + + bool Initialize(); + + GLint CreateShader(GLenum aShaderType, const char* aShaderSource); + + /** + * Creates a program and stores its id. + */ + bool CreateProgram(const char* aVertexShaderString, + const char* aFragmentShaderString); + + /** + * The following set of methods set a uniform argument to the shader program. + * Not all uniforms may be set for all programs, and such uses will throw + * an assertion. + */ + void SetLayerTransform(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::LayerTransform, aMatrix); + } + + void SetLayerTransformInverse(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::LayerTransformInverse, aMatrix); + } + + void SetDEAAEdges(const gfx::Point3D* aEdges) { + SetArrayUniform(KnownUniform::SSEdges, 4, aEdges); + } + + void SetViewportSize(const gfx::IntSize& aSize) { + float vals[2] = {(float)aSize.width, (float)aSize.height}; + SetUniform(KnownUniform::ViewportSize, 2, vals); + } + + void SetVisibleCenter(const gfx::Point& aVisibleCenter) { + float vals[2] = {aVisibleCenter.x, aVisibleCenter.y}; + SetUniform(KnownUniform::VisibleCenter, 2, vals); + } + + void SetLayerRects(const gfx::Rect* aRects) { + float vals[16] = { + aRects[0].X(), aRects[0].Y(), aRects[0].Width(), aRects[0].Height(), + aRects[1].X(), aRects[1].Y(), aRects[1].Width(), aRects[1].Height(), + aRects[2].X(), aRects[2].Y(), aRects[2].Width(), aRects[2].Height(), + aRects[3].X(), aRects[3].Y(), aRects[3].Width(), aRects[3].Height()}; + SetUniform(KnownUniform::LayerRects, 16, vals); + } + + void SetProjectionMatrix(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::MatrixProj, aMatrix); + } + + // sets this program's texture transform, if it uses one + void SetTextureTransform(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::TextureTransform, aMatrix); + } + + void SetTextureRects(const gfx::Rect* aRects) { + float vals[16] = { + aRects[0].X(), aRects[0].Y(), aRects[0].Width(), aRects[0].Height(), + aRects[1].X(), aRects[1].Y(), aRects[1].Width(), aRects[1].Height(), + aRects[2].X(), aRects[2].Y(), aRects[2].Width(), aRects[2].Height(), + aRects[3].X(), aRects[3].Y(), aRects[3].Width(), aRects[3].Height()}; + SetUniform(KnownUniform::TextureRects, 16, vals); + } + + void SetRenderOffset(const nsIntPoint& aOffset) { + float vals[4] = {float(aOffset.x), float(aOffset.y)}; + SetUniform(KnownUniform::RenderTargetOffset, 2, vals); + } + + void SetRenderOffset(float aX, float aY) { + float vals[2] = {aX, aY}; + SetUniform(KnownUniform::RenderTargetOffset, 2, vals); + } + + void SetLayerOpacity(float aOpacity) { + SetUniform(KnownUniform::LayerOpacity, aOpacity); + } + + void SetTextureUnit(GLint aUnit) { SetUniform(KnownUniform::Texture, aUnit); } + void SetYTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::YTexture, aUnit); + } + + void SetCbTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::CbTexture, aUnit); + } + + void SetCrTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::CrTexture, aUnit); + } + + void SetYCbCrTextureUnits(GLint aYUnit, GLint aCbUnit, GLint aCrUnit) { + SetUniform(KnownUniform::YTexture, aYUnit); + SetUniform(KnownUniform::CbTexture, aCbUnit); + SetUniform(KnownUniform::CrTexture, aCrUnit); + } + + void SetNV12TextureUnits(GLint aYUnit, GLint aCbCrUnit) { + SetUniform(KnownUniform::YTexture, aYUnit); + SetUniform(KnownUniform::CbTexture, aCbCrUnit); + } + + void SetTexCoordMultiplier(float aWidth, float aHeight) { + float f[] = {aWidth, aHeight}; + SetUniform(KnownUniform::TexCoordMultiplier, 2, f); + } + + void SetCbCrTexCoordMultiplier(float aWidth, float aHeight) { + float f[] = {aWidth, aHeight}; + SetUniform(KnownUniform::CbCrTexCoordMultiplier, 2, f); + } + + void SetYUVColorSpace(gfx::YUVColorSpace aYUVColorSpace); + + size_t GetTextureCount() const { return mProfile.mTextureCount; } + + protected: + RefPtr<GLContext> mGL; + // the OpenGL id of the program + GLuint mProgram; + ProgramProfileOGL mProfile; + enum { STATE_NEW, STATE_OK, STATE_ERROR } mProgramState; + +#ifdef CHECK_CURRENT_PROGRAM + static int sCurrentProgramKey; +#endif + + void SetUniform(KnownUniform::KnownUniformName aKnownUniform, + float aFloatValue) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(aFloatValue)) { + mGL->fUniform1f(ku.mLocation, aFloatValue); + } + } + + void SetUniform(KnownUniform::KnownUniformName aKnownUniform, + const gfx::DeviceColor& aColor) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(aColor.r, aColor.g, aColor.b, aColor.a)) { + mGL->fUniform4fv(ku.mLocation, 1, ku.mValue.f16v); + } + } + + void SetUniform(KnownUniform::KnownUniformName aKnownUniform, int aLength, + const float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(aLength, aFloatValues)) { + switch (aLength) { + case 1: + mGL->fUniform1fv(ku.mLocation, 1, ku.mValue.f16v); + break; + case 2: + mGL->fUniform2fv(ku.mLocation, 1, ku.mValue.f16v); + break; + case 3: + mGL->fUniform3fv(ku.mLocation, 1, ku.mValue.f16v); + break; + case 4: + mGL->fUniform4fv(ku.mLocation, 1, ku.mValue.f16v); + break; + case 16: + mGL->fUniform4fv(ku.mLocation, 4, ku.mValue.f16v); + break; + default: + MOZ_ASSERT_UNREACHABLE("Bogus aLength param"); + } + } + } + + void SetArrayUniform(KnownUniform::KnownUniformName aKnownUniform, + int aLength, float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateArrayUniform(aLength, aFloatValues)) { + mGL->fUniform1fv(ku.mLocation, aLength, ku.mValue.f16v); + } + } + + void SetArrayUniform(KnownUniform::KnownUniformName aKnownUniform, + int aLength, const gfx::Point3D* aPointValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateArrayUniform(aLength, aPointValues)) { + mGL->fUniform3fv(ku.mLocation, aLength, ku.mValue.f16v); + } + } + + void SetUniform(KnownUniform::KnownUniformName aKnownUniform, + GLint aIntValue) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(aIntValue)) { + mGL->fUniform1i(ku.mLocation, aIntValue); + } + } + + void SetMatrixUniform(KnownUniform::KnownUniformName aKnownUniform, + const float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(16, aFloatValues)) { + mGL->fUniformMatrix4fv(ku.mLocation, 1, false, ku.mValue.f16v); + } + } + + void SetMatrix3fvUniform(KnownUniform::KnownUniformName aKnownUniform, + const float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(9, aFloatValues)) { + mGL->fUniformMatrix3fv(ku.mLocation, 1, false, ku.mValue.f16v); + } + } + + void SetVec3fvUniform(KnownUniform::KnownUniformName aKnownUniform, + const float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(3, aFloatValues)) { + mGL->fUniform3fv(ku.mLocation, 1, ku.mValue.f16v); + } + } + + void SetMatrixUniform(KnownUniform::KnownUniformName aKnownUniform, + const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(aKnownUniform, &aMatrix._11); + } +}; + +class ShaderProgramOGLsHolder final { + public: + NS_INLINE_DECL_REFCOUNTING(ShaderProgramOGLsHolder) + + explicit ShaderProgramOGLsHolder(gl::GLContext* aGL); + + ShaderProgramOGL* GetShaderProgramFor(const ShaderConfigOGL& aConfig); + void Clear(); + ShaderProgramOGL* ActivateProgram(const ShaderConfigOGL& aConfig); + void ResetCurrentProgram(); + + protected: + ~ShaderProgramOGLsHolder(); + + const RefPtr<gl::GLContext> mGL; + std::map<ShaderConfigOGL, UniquePtr<ShaderProgramOGL>> mPrograms; + ShaderProgramOGL* mCurrentProgram = nullptr; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_OGLSHADERPROGRAM_H diff --git a/gfx/layers/opengl/TextureClientOGL.cpp b/gfx/layers/opengl/TextureClientOGL.cpp new file mode 100644 index 0000000000..df3c97bc1d --- /dev/null +++ b/gfx/layers/opengl/TextureClientOGL.cpp @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "GLContext.h" // for GLContext, etc +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/TextureClientOGL.h" +#include "mozilla/gfx/2D.h" // for Factory +#include "mozilla/gfx/Point.h" // for IntSize +#include "GLLibraryEGL.h" + +#ifdef MOZ_WIDGET_ANDROID +# include <jni.h> +# include <android/native_window.h> +# include <android/native_window_jni.h> +# include <sys/socket.h> +# include "mozilla/ipc/FileDescriptor.h" +# include "mozilla/java/GeckoSurfaceWrappers.h" +# include "mozilla/java/SurfaceAllocatorWrappers.h" +# include "mozilla/layers/AndroidHardwareBuffer.h" +# include "mozilla/UniquePtrExtensions.h" +#endif + +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +class CompositableForwarder; + +//////////////////////////////////////////////////////////////////////// +// AndroidSurface + +#ifdef MOZ_WIDGET_ANDROID + +already_AddRefed<TextureClient> AndroidSurfaceTextureData::CreateTextureClient( + AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous, + gl::OriginPos aOriginPos, bool aHasAlpha, + Maybe<gfx::Matrix4x4> aTransformOverride, LayersIPCChannel* aAllocator, + TextureFlags aFlags) { + if (aOriginPos == gl::OriginPos::BottomLeft) { + aFlags |= TextureFlags::ORIGIN_BOTTOM_LEFT; + } + + return TextureClient::CreateWithData( + new AndroidSurfaceTextureData(aHandle, aSize, aContinuous, aHasAlpha, + aTransformOverride), + aFlags, aAllocator); +} + +AndroidSurfaceTextureData::AndroidSurfaceTextureData( + AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous, + bool aHasAlpha, Maybe<gfx::Matrix4x4> aTransformOverride) + : mHandle(aHandle), + mSize(aSize), + mContinuous(aContinuous), + mHasAlpha(aHasAlpha), + mTransformOverride(aTransformOverride) { + MOZ_ASSERT(mHandle); +} + +AndroidSurfaceTextureData::~AndroidSurfaceTextureData() {} + +void AndroidSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = gfx::SurfaceFormat::UNKNOWN; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = false; + aInfo.canExposeMappedData = false; +} + +bool AndroidSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + aOutDescriptor = SurfaceTextureDescriptor( + mHandle, mSize, + mHasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8, + mContinuous, mTransformOverride); + return true; +} + +#endif // MOZ_WIDGET_ANDROID + +//////////////////////////////////////////////////////////////////////// +// AndroidNativeWindow + +#ifdef MOZ_WIDGET_ANDROID + +AndroidNativeWindowTextureData* AndroidNativeWindowTextureData::Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat) { + if (aFormat != gfx::SurfaceFormat::R8G8B8A8 && + aFormat != gfx::SurfaceFormat::R8G8B8X8 && + aFormat != gfx::SurfaceFormat::B8G8R8A8 && + aFormat != gfx::SurfaceFormat::B8G8R8X8 && + aFormat != gfx::SurfaceFormat::R5G6B5_UINT16) { + return nullptr; + } + + auto surface = + java::GeckoSurface::LocalRef(java::SurfaceAllocator::AcquireSurface( + aSize.width, aSize.height, true /* single-buffer mode */)); + if (surface) { + return new AndroidNativeWindowTextureData(surface, aSize, aFormat); + } + + return nullptr; +} + +AndroidNativeWindowTextureData::AndroidNativeWindowTextureData( + java::GeckoSurface::Param aSurface, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat) + : mSurface(aSurface), mIsLocked(false), mSize(aSize), mFormat(aFormat) { + mNativeWindow = + ANativeWindow_fromSurface(jni::GetEnvForThread(), mSurface.Get()); + MOZ_ASSERT(mNativeWindow, "Failed to create NativeWindow."); + + // SurfaceTextures don't technically support BGR, but we can just pretend to + // be RGB. + int32_t format = WINDOW_FORMAT_RGBA_8888; + switch (aFormat) { + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + format = WINDOW_FORMAT_RGBA_8888; + break; + + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::B8G8R8X8: + format = WINDOW_FORMAT_RGBX_8888; + break; + + case gfx::SurfaceFormat::R5G6B5_UINT16: + format = WINDOW_FORMAT_RGB_565; + break; + + default: + MOZ_ASSERT(false, "Unsupported AndroidNativeWindowTextureData format."); + } + + DebugOnly<int32_t> r = ANativeWindow_setBuffersGeometry( + mNativeWindow, mSize.width, mSize.height, format); + MOZ_ASSERT(r == 0, "ANativeWindow_setBuffersGeometry failed."); + + // Ideally here we'd call ANativeWindow_setBuffersTransform() with the + // identity transform, but that is only available on api level >= 26. + // Instead use SurfaceDescriptor's transformOverride flag when serializing. +} + +void AndroidNativeWindowTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = true; + aInfo.canExposeMappedData = false; + aInfo.canConcurrentlyReadLock = false; +} + +bool AndroidNativeWindowTextureData::Serialize( + SurfaceDescriptor& aOutDescriptor) { + aOutDescriptor = SurfaceTextureDescriptor( + mSurface->GetHandle(), mSize, mFormat, false /* not continuous */, + Some(gfx::Matrix4x4()) /* always use identity transform */); + return true; +} + +bool AndroidNativeWindowTextureData::Lock(OpenMode) { + // ANativeWindows can only be locked and unlocked a single time, after which + // we must wait until they receive ownership back from the host. + // Therefore we must only actually call ANativeWindow_lock() once per cycle. + if (!mIsLocked) { + int32_t r = ANativeWindow_lock(mNativeWindow, &mBuffer, nullptr); + if (r == -ENOMEM) { + return false; + } else if (r < 0) { + MOZ_CRASH("ANativeWindow_lock failed."); + } + mIsLocked = true; + } + return true; +} + +void AndroidNativeWindowTextureData::Unlock() { + // The TextureClient may want to call Lock again before handing ownership + // to the host, so we cannot call ANativeWindow_unlockAndPost yet. +} + +void AndroidNativeWindowTextureData::Forget(LayersIPCChannel*) { + MOZ_ASSERT(!mIsLocked, + "ANativeWindow should not be released while locked.\n"); + ANativeWindow_release(mNativeWindow); + mNativeWindow = nullptr; + java::SurfaceAllocator::DisposeSurface(mSurface); + mSurface = nullptr; +} + +already_AddRefed<gfx::DrawTarget> +AndroidNativeWindowTextureData::BorrowDrawTarget() { + const int bpp = (mFormat == gfx::SurfaceFormat::R5G6B5_UINT16) ? 2 : 4; + + return gfx::Factory::CreateDrawTargetForData( + gfx::BackendType::SKIA, static_cast<unsigned char*>(mBuffer.bits), + gfx::IntSize(mBuffer.width, mBuffer.height), mBuffer.stride * bpp, + mFormat, true); +} + +void AndroidNativeWindowTextureData::OnForwardedToHost() { + if (mIsLocked) { + int32_t r = ANativeWindow_unlockAndPost(mNativeWindow); + if (r < 0) { + MOZ_CRASH("ANativeWindow_unlockAndPost failed\n."); + } + mIsLocked = false; + } +} + +AndroidHardwareBufferTextureData* AndroidHardwareBufferTextureData::Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat) { + RefPtr<AndroidHardwareBuffer> buffer = + AndroidHardwareBuffer::Create(aSize, aFormat); + if (!buffer) { + return nullptr; + } + return new AndroidHardwareBufferTextureData(buffer, aSize, aFormat); +} + +AndroidHardwareBufferTextureData::AndroidHardwareBufferTextureData( + AndroidHardwareBuffer* aAndroidHardwareBuffer, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat) + : mAndroidHardwareBuffer(aAndroidHardwareBuffer), + mSize(aSize), + mFormat(aFormat), + mAddress(nullptr), + mIsLocked(false) {} + +AndroidHardwareBufferTextureData::~AndroidHardwareBufferTextureData() {} + +void AndroidHardwareBufferTextureData::FillInfo( + TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.hasSynchronization = true; + aInfo.supportsMoz2D = true; + aInfo.canExposeMappedData = false; + aInfo.canConcurrentlyReadLock = true; +} + +bool AndroidHardwareBufferTextureData::Serialize( + SurfaceDescriptor& aOutDescriptor) { + int fd[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fd) != 0) { + aOutDescriptor = SurfaceDescriptorAndroidHardwareBuffer( + ipc::FileDescriptor(), mAndroidHardwareBuffer->mId, mSize, mFormat); + return false; + } + + UniqueFileHandle readerFd(fd[0]); + UniqueFileHandle writerFd(fd[1]); + + // Send the AHardwareBuffer to an AF_UNIX socket. It does not acquire or + // retain a reference to the buffer object. The caller is therefore + // responsible for ensuring that the buffer remains alive through the lifetime + // of this file descriptor. + int ret = mAndroidHardwareBuffer->SendHandleToUnixSocket(writerFd.get()); + if (ret < 0) { + aOutDescriptor = SurfaceDescriptorAndroidHardwareBuffer( + ipc::FileDescriptor(), mAndroidHardwareBuffer->mId, mSize, mFormat); + return false; + } + + aOutDescriptor = SurfaceDescriptorAndroidHardwareBuffer( + ipc::FileDescriptor(std::move(readerFd)), mAndroidHardwareBuffer->mId, + mSize, mFormat); + return true; +} + +bool AndroidHardwareBufferTextureData::Lock(OpenMode aMode) { + if (!mIsLocked) { + MOZ_ASSERT(!mAddress); + + mAndroidHardwareBuffer->WaitForBufferOwnership(); + + uint64_t usage = 0; + if (aMode & OpenMode::OPEN_READ) { + usage |= AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN; + } + if (aMode & OpenMode::OPEN_WRITE) { + usage |= AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN; + } + + int ret = mAndroidHardwareBuffer->Lock(usage, 0, &mAddress); + if (ret) { + mAddress = nullptr; + return false; + } + mIsLocked = true; + } + return true; +} + +void AndroidHardwareBufferTextureData::Unlock() { + // The TextureClient may want to call Lock again before handing ownership + // to the host, so we cannot call AHardwareBuffer_unlock yet. +} + +void AndroidHardwareBufferTextureData::Forget(LayersIPCChannel*) { + MOZ_ASSERT(!mIsLocked); + mAndroidHardwareBuffer = nullptr; + mAddress = nullptr; +} + +already_AddRefed<gfx::DrawTarget> +AndroidHardwareBufferTextureData::BorrowDrawTarget() { + MOZ_ASSERT(mIsLocked); + + const int bpp = (mFormat == gfx::SurfaceFormat::R5G6B5_UINT16) ? 2 : 4; + + return gfx::Factory::CreateDrawTargetForData( + gfx::BackendType::SKIA, static_cast<unsigned char*>(mAddress), + gfx::IntSize(mAndroidHardwareBuffer->mSize.width, + mAndroidHardwareBuffer->mSize.height), + mAndroidHardwareBuffer->mStride * bpp, mFormat, true); +} + +void AndroidHardwareBufferTextureData::OnForwardedToHost() { + if (mIsLocked) { + mAndroidHardwareBuffer->Unlock(); + mAddress = nullptr; + mIsLocked = false; + } +} + +TextureFlags AndroidHardwareBufferTextureData::GetTextureFlags() const { + return TextureFlags::WAIT_HOST_USAGE_END; +} + +Maybe<uint64_t> AndroidHardwareBufferTextureData::GetBufferId() const { + return Some(mAndroidHardwareBuffer->mId); +} + +mozilla::ipc::FileDescriptor +AndroidHardwareBufferTextureData::GetAcquireFence() { + if (!mAndroidHardwareBuffer) { + return ipc::FileDescriptor(); + } + + return mAndroidHardwareBuffer->GetAcquireFence(); +} + +#endif // MOZ_WIDGET_ANDROID + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/TextureClientOGL.h b/gfx/layers/opengl/TextureClientOGL.h new file mode 100644 index 0000000000..7e10e4611c --- /dev/null +++ b/gfx/layers/opengl/TextureClientOGL.h @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENTOGL_H +#define MOZILLA_GFX_TEXTURECLIENTOGL_H + +#include "GLContextTypes.h" // for SharedTextureHandle, etc +#include "GLImages.h" +#include "gfxTypes.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidSurfaceTexture.h" +# include "AndroidNativeWindow.h" +# include "mozilla/java/GeckoSurfaceWrappers.h" +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +namespace mozilla { + +namespace gfx { + +class DrawTarget; + +} // namespace gfx + +namespace layers { + +#ifdef MOZ_WIDGET_ANDROID + +class AndroidHardwareBuffer; + +class AndroidSurfaceTextureData : public TextureData { + public: + static already_AddRefed<TextureClient> CreateTextureClient( + AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous, + gl::OriginPos aOriginPos, bool aHasAlpha, + Maybe<gfx::Matrix4x4> aTransformOverride, LayersIPCChannel* aAllocator, + TextureFlags aFlags); + + virtual ~AndroidSurfaceTextureData(); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + // Useless functions. + bool Lock(OpenMode) override { return true; } + + void Unlock() override {} + + // Our data is always owned externally. + void Deallocate(LayersIPCChannel*) override {} + + protected: + AndroidSurfaceTextureData(AndroidSurfaceTextureHandle aHandle, + gfx::IntSize aSize, bool aContinuous, + bool aHasAlpha, + Maybe<gfx::Matrix4x4> aTransformOverride); + + const AndroidSurfaceTextureHandle mHandle; + const gfx::IntSize mSize; + const bool mContinuous; + const bool mHasAlpha; + const Maybe<gfx::Matrix4x4> mTransformOverride; +}; + +class AndroidNativeWindowTextureData : public TextureData { + public: + static AndroidNativeWindowTextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + bool Lock(OpenMode) override; + void Unlock() override; + + void Forget(LayersIPCChannel*) override; + void Deallocate(LayersIPCChannel*) override {} + + already_AddRefed<gfx::DrawTarget> BorrowDrawTarget() override; + + void OnForwardedToHost() override; + + protected: + AndroidNativeWindowTextureData(java::GeckoSurface::Param aSurface, + gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + private: + java::GeckoSurface::GlobalRef mSurface; + ANativeWindow* mNativeWindow; + ANativeWindow_Buffer mBuffer; + // Keeps track of whether the underlying NativeWindow is actually locked. + bool mIsLocked; + + const gfx::IntSize mSize; + const gfx::SurfaceFormat mFormat; +}; + +class AndroidHardwareBufferTextureData : public TextureData { + public: + static AndroidHardwareBufferTextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + virtual ~AndroidHardwareBufferTextureData(); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + bool Lock(OpenMode aMode) override; + void Unlock() override; + + void Forget(LayersIPCChannel*) override; + void Deallocate(LayersIPCChannel*) override {} + + already_AddRefed<gfx::DrawTarget> BorrowDrawTarget() override; + + void OnForwardedToHost() override; + + TextureFlags GetTextureFlags() const override; + + Maybe<uint64_t> GetBufferId() const override; + + mozilla::ipc::FileDescriptor GetAcquireFence() override; + + AndroidHardwareBufferTextureData* AsAndroidHardwareBufferTextureData() + override { + return this; + } + + AndroidHardwareBuffer* GetBuffer() { return mAndroidHardwareBuffer; } + + protected: + AndroidHardwareBufferTextureData( + AndroidHardwareBuffer* aAndroidHardwareBuffer, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + RefPtr<AndroidHardwareBuffer> mAndroidHardwareBuffer; + const gfx::IntSize mSize; + const gfx::SurfaceFormat mFormat; + + void* mAddress; + + // Keeps track of whether the underlying NativeWindow is actually locked. + bool mIsLocked; +}; + +#endif // MOZ_WIDGET_ANDROID + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/opengl/TextureHostOGL.cpp b/gfx/layers/opengl/TextureHostOGL.cpp new file mode 100644 index 0000000000..ee126d6e9b --- /dev/null +++ b/gfx/layers/opengl/TextureHostOGL.cpp @@ -0,0 +1,915 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "TextureHostOGL.h" + +#include "GLContextEGL.h" // for GLContext, etc +#include "GLLibraryEGL.h" // for GLLibraryEGL +#include "GLUploadHelpers.h" +#include "GLReadTexImageHelper.h" +#include "gfx2DGlue.h" // for ContentForFormat, etc +#include "mozilla/gfx/2D.h" // for DataSourceSurface +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Logging.h" // for gfxCriticalError +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/webrender/RenderEGLImageTextureHost.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsRegion.h" // for nsIntRegion +#include "GfxTexturesReporter.h" // for GfxTexturesReporter +#include "GeckoProfiler.h" + +#ifdef XP_MACOSX +# include "mozilla/layers/MacIOSurfaceTextureHostOGL.h" +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/AndroidHardwareBuffer.h" +# include "mozilla/webrender/RenderAndroidHardwareBufferTextureHost.h" +# include "mozilla/webrender/RenderAndroidSurfaceTextureHost.h" +#endif + +#ifdef MOZ_WAYLAND +# include "mozilla/layers/DMABUFTextureHostOGL.h" +#endif + +using namespace mozilla::gl; +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +class Compositor; + +void ApplySamplingFilterToBoundTexture(gl::GLContext* aGL, + gfx::SamplingFilter aSamplingFilter, + GLuint aTarget) { + GLenum filter = + (aSamplingFilter == gfx::SamplingFilter::POINT ? LOCAL_GL_NEAREST + : LOCAL_GL_LINEAR); + + aGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MIN_FILTER, filter); + aGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MAG_FILTER, filter); +} + +already_AddRefed<TextureHost> CreateTextureHostOGL( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags) { + RefPtr<TextureHost> result; + switch (aDesc.type()) { +#ifdef MOZ_WIDGET_ANDROID + case SurfaceDescriptor::TSurfaceTextureDescriptor: { + const SurfaceTextureDescriptor& desc = + aDesc.get_SurfaceTextureDescriptor(); + java::GeckoSurfaceTexture::LocalRef surfaceTexture = + java::GeckoSurfaceTexture::Lookup(desc.handle()); + + result = new SurfaceTextureHost(aFlags, surfaceTexture, desc.size(), + desc.format(), desc.continuous(), + desc.transformOverride()); + break; + } + case SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer: { + const SurfaceDescriptorAndroidHardwareBuffer& desc = + aDesc.get_SurfaceDescriptorAndroidHardwareBuffer(); + result = AndroidHardwareBufferTextureHost::Create(aFlags, desc); + break; + } +#endif + + case SurfaceDescriptor::TEGLImageDescriptor: { + const EGLImageDescriptor& desc = aDesc.get_EGLImageDescriptor(); + result = new EGLImageTextureHost(aFlags, (EGLImage)desc.image(), + (EGLSync)desc.fence(), desc.size(), + desc.hasAlpha()); + break; + } + +#ifdef MOZ_WAYLAND + case SurfaceDescriptor::TSurfaceDescriptorDMABuf: { + result = new DMABUFTextureHostOGL(aFlags, aDesc); + break; + } +#endif + +#ifdef XP_MACOSX + case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: { + const SurfaceDescriptorMacIOSurface& desc = + aDesc.get_SurfaceDescriptorMacIOSurface(); + result = new MacIOSurfaceTextureHostOGL(aFlags, desc); + break; + } +#endif + + case SurfaceDescriptor::TSurfaceDescriptorSharedGLTexture: { + const auto& desc = aDesc.get_SurfaceDescriptorSharedGLTexture(); + result = + new GLTextureHost(aFlags, desc.texture(), desc.target(), + (GLsync)desc.fence(), desc.size(), desc.hasAlpha()); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unsupported SurfaceDescriptor type"); + break; + } + } + return result.forget(); +} + +static gl::TextureImage::Flags FlagsToGLFlags(TextureFlags aFlags) { + uint32_t result = TextureImage::NoFlags; + + if (aFlags & TextureFlags::USE_NEAREST_FILTER) + result |= TextureImage::UseNearestFilter; + if (aFlags & TextureFlags::ORIGIN_BOTTOM_LEFT) + result |= TextureImage::OriginBottomLeft; + if (aFlags & TextureFlags::DISALLOW_BIGIMAGE) + result |= TextureImage::DisallowBigImage; + + return static_cast<gl::TextureImage::Flags>(result); +} + +TextureImageTextureSourceOGL::TextureImageTextureSourceOGL( + CompositorOGL* aCompositor, TextureFlags aFlags) + : mGL(aCompositor->gl()), + mCompositor(aCompositor), + mFlags(aFlags), + mIterating(false) { + if (mCompositor) { + mCompositor->RegisterTextureSource(this); + } +} + +TextureImageTextureSourceOGL::~TextureImageTextureSourceOGL() { + DeallocateDeviceData(); +} + +void TextureImageTextureSourceOGL::DeallocateDeviceData() { + mTexImage = nullptr; + mGL = nullptr; + if (mCompositor) { + mCompositor->UnregisterTextureSource(this); + } + SetUpdateSerial(0); +} + +bool TextureImageTextureSourceOGL::Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, + gfx::IntPoint* aSrcOffset, + gfx::IntPoint* aDstOffset) { + GLContext* gl = mGL; + MOZ_ASSERT(gl); + if (!gl || !gl->MakeCurrent()) { + NS_WARNING( + "trying to update TextureImageTextureSourceOGL without a GLContext"); + return false; + } + if (!aSurface) { + gfxCriticalError() << "Invalid surface for OGL update"; + return false; + } + MOZ_ASSERT(aSurface); + + IntSize size = aSurface->GetSize(); + if (!mTexImage || (mTexImage->GetSize() != size && !aSrcOffset) || + mTexImage->GetContentType() != + gfx::ContentForFormat(aSurface->GetFormat())) { + if (mFlags & TextureFlags::DISALLOW_BIGIMAGE) { + GLint maxTextureSize; + gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &maxTextureSize); + if (size.width > maxTextureSize || size.height > maxTextureSize) { + NS_WARNING("Texture exceeds maximum texture size, refusing upload"); + return false; + } + // Explicitly use CreateBasicTextureImage instead of CreateTextureImage, + // because CreateTextureImage might still choose to create a tiled + // texture image. + mTexImage = CreateBasicTextureImage( + gl, size, gfx::ContentForFormat(aSurface->GetFormat()), + LOCAL_GL_CLAMP_TO_EDGE, FlagsToGLFlags(mFlags)); + } else { + // XXX - clarify which size we want to use. IncrementalContentHost will + // require the size of the destination surface to be different from + // the size of aSurface. + // See bug 893300 (tracks the implementation of ContentHost for new + // textures). + mTexImage = CreateTextureImage( + gl, size, gfx::ContentForFormat(aSurface->GetFormat()), + LOCAL_GL_CLAMP_TO_EDGE, FlagsToGLFlags(mFlags), + SurfaceFormatToImageFormat(aSurface->GetFormat())); + } + ClearCachedFilter(); + + if (aDestRegion && !aSrcOffset && + !aDestRegion->IsEqual(gfx::IntRect(0, 0, size.width, size.height))) { + // UpdateFromDataSource will ignore our specified aDestRegion since the + // texture hasn't been allocated with glTexImage2D yet. Call Resize() to + // force the allocation (full size, but no upload), and then we'll only + // upload the pixels we care about below. + mTexImage->Resize(size); + } + } + + return mTexImage->UpdateFromDataSource(aSurface, aDestRegion, aSrcOffset, + aDstOffset); +} + +void TextureImageTextureSourceOGL::EnsureBuffer(const IntSize& aSize, + gfxContentType aContentType) { + if (!mTexImage || mTexImage->GetSize() != aSize || + mTexImage->GetContentType() != aContentType) { + mTexImage = + CreateTextureImage(mGL, aSize, aContentType, LOCAL_GL_CLAMP_TO_EDGE, + FlagsToGLFlags(mFlags)); + } + mTexImage->Resize(aSize); +} + +gfx::IntSize TextureImageTextureSourceOGL::GetSize() const { + if (mTexImage) { + if (mIterating) { + return mTexImage->GetTileRect().Size(); + } + return mTexImage->GetSize(); + } + NS_WARNING("Trying to query the size of an empty TextureSource."); + return gfx::IntSize(0, 0); +} + +gfx::SurfaceFormat TextureImageTextureSourceOGL::GetFormat() const { + if (mTexImage) { + return mTexImage->GetTextureFormat(); + } + NS_WARNING("Trying to query the format of an empty TextureSource."); + return gfx::SurfaceFormat::UNKNOWN; +} + +gfx::IntRect TextureImageTextureSourceOGL::GetTileRect() { + return mTexImage->GetTileRect(); +} + +void TextureImageTextureSourceOGL::BindTexture( + GLenum aTextureUnit, gfx::SamplingFilter aSamplingFilter) { + MOZ_ASSERT(mTexImage, + "Trying to bind a TextureSource that does not have an underlying " + "GL texture."); + mTexImage->BindTexture(aTextureUnit); + SetSamplingFilter(mGL, aSamplingFilter); +} + +//////////////////////////////////////////////////////////////////////// +// GLTextureSource + +GLTextureSource::GLTextureSource(TextureSourceProvider* aProvider, + GLuint aTextureHandle, GLenum aTarget, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat) + : GLTextureSource(aProvider->GetGLContext(), aTextureHandle, aTarget, aSize, + aFormat) {} + +GLTextureSource::GLTextureSource(GLContext* aGL, GLuint aTextureHandle, + GLenum aTarget, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat) + : mGL(aGL), + mTextureHandle(aTextureHandle), + mTextureTarget(aTarget), + mSize(aSize), + mFormat(aFormat) { + MOZ_COUNT_CTOR(GLTextureSource); +} + +GLTextureSource::~GLTextureSource() { + MOZ_COUNT_DTOR(GLTextureSource); + DeleteTextureHandle(); +} + +void GLTextureSource::DeallocateDeviceData() { DeleteTextureHandle(); } + +void GLTextureSource::DeleteTextureHandle() { + GLContext* gl = this->gl(); + if (mTextureHandle != 0 && gl && gl->MakeCurrent()) { + gl->fDeleteTextures(1, &mTextureHandle); + } + mTextureHandle = 0; +} + +void GLTextureSource::BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) { + MOZ_ASSERT(mTextureHandle != 0); + GLContext* gl = this->gl(); + if (!gl || !gl->MakeCurrent()) { + return; + } + gl->fActiveTexture(aTextureUnit); + gl->fBindTexture(mTextureTarget, mTextureHandle); + ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, mTextureTarget); +} + +bool GLTextureSource::IsValid() const { return !!gl() && mTextureHandle != 0; } + +//////////////////////////////////////////////////////////////////////// +// DirectMapTextureSource + +DirectMapTextureSource::DirectMapTextureSource(gl::GLContext* aContext, + gfx::DataSourceSurface* aSurface) + : GLTextureSource(aContext, 0, LOCAL_GL_TEXTURE_RECTANGLE_ARB, + aSurface->GetSize(), aSurface->GetFormat()), + mSync(0) { + MOZ_ASSERT(aSurface); + + UpdateInternal(aSurface, nullptr, nullptr, true); +} + +DirectMapTextureSource::DirectMapTextureSource(TextureSourceProvider* aProvider, + gfx::DataSourceSurface* aSurface) + : DirectMapTextureSource(aProvider->GetGLContext(), aSurface) {} + +DirectMapTextureSource::~DirectMapTextureSource() { + if (!mSync || !gl() || !gl()->MakeCurrent() || gl()->IsDestroyed()) { + return; + } + + gl()->fDeleteSync(mSync); + mSync = 0; +} + +bool DirectMapTextureSource::Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, + gfx::IntPoint* aSrcOffset, + gfx::IntPoint* aDstOffset) { + MOZ_RELEASE_ASSERT(aDstOffset == nullptr); + if (!aSurface) { + return false; + } + + return UpdateInternal(aSurface, aDestRegion, aSrcOffset, false); +} + +void DirectMapTextureSource::MaybeFenceTexture() { + if (!gl() || !gl()->MakeCurrent() || gl()->IsDestroyed()) { + return; + } + + if (mSync) { + gl()->fDeleteSync(mSync); + } + mSync = gl()->fFenceSync(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +} + +bool DirectMapTextureSource::Sync(bool aBlocking) { + if (!gl() || !gl()->MakeCurrent() || gl()->IsDestroyed()) { + // We use this function to decide whether we can unlock the texture + // and clean it up. If we return false here and for whatever reason + // the context is absent or invalid, the compositor will keep a + // reference to this texture forever. + return true; + } + + if (!mSync) { + return false; + } + + GLenum waitResult = + gl()->fClientWaitSync(mSync, LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT, + aBlocking ? LOCAL_GL_TIMEOUT_IGNORED : 0); + return waitResult == LOCAL_GL_ALREADY_SIGNALED || + waitResult == LOCAL_GL_CONDITION_SATISFIED; +} + +bool DirectMapTextureSource::UpdateInternal(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, + gfx::IntPoint* aSrcOffset, + bool aInit) { + if (!gl() || !gl()->MakeCurrent()) { + return false; + } + + if (aInit) { + gl()->fGenTextures(1, &mTextureHandle); + gl()->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, mTextureHandle); + + gl()->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, + LOCAL_GL_TEXTURE_STORAGE_HINT_APPLE, + LOCAL_GL_STORAGE_CACHED_APPLE); + gl()->fTextureRangeAPPLE(LOCAL_GL_TEXTURE_RECTANGLE_ARB, + aSurface->Stride() * aSurface->GetSize().height, + aSurface->GetData()); + + gl()->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, + LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE); + gl()->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, + LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE); + } + + MOZ_ASSERT(mTextureHandle); + + // APPLE_client_storage + gl()->fPixelStorei(LOCAL_GL_UNPACK_CLIENT_STORAGE_APPLE, LOCAL_GL_TRUE); + + nsIntRegion destRegion = aDestRegion + ? *aDestRegion + : IntRect(0, 0, aSurface->GetSize().width, + aSurface->GetSize().height); + gfx::IntPoint srcPoint = aSrcOffset ? *aSrcOffset : gfx::IntPoint(0, 0); + mFormat = gl::UploadSurfaceToTexture( + gl(), aSurface, destRegion, mTextureHandle, aSurface->GetSize(), nullptr, + aInit, srcPoint, gfx::IntPoint(0, 0), LOCAL_GL_TEXTURE0, + LOCAL_GL_TEXTURE_RECTANGLE_ARB); + + if (mSync) { + gl()->fDeleteSync(mSync); + mSync = 0; + } + + gl()->fPixelStorei(LOCAL_GL_UNPACK_CLIENT_STORAGE_APPLE, LOCAL_GL_FALSE); + return true; +} + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// SurfaceTextureHost + +#ifdef MOZ_WIDGET_ANDROID + +SurfaceTextureSource::SurfaceTextureSource( + TextureSourceProvider* aProvider, + mozilla::java::GeckoSurfaceTexture::Ref& aSurfTex, + gfx::SurfaceFormat aFormat, GLenum aTarget, GLenum aWrapMode, + gfx::IntSize aSize, Maybe<gfx::Matrix4x4> aTransformOverride) + : mGL(aProvider->GetGLContext()), + mSurfTex(aSurfTex), + mFormat(aFormat), + mTextureTarget(aTarget), + mWrapMode(aWrapMode), + mSize(aSize), + mTransformOverride(aTransformOverride) {} + +void SurfaceTextureSource::BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) { + MOZ_ASSERT(mSurfTex); + GLContext* gl = this->gl(); + if (!gl || !gl->MakeCurrent()) { + NS_WARNING("Trying to bind a texture without a GLContext"); + return; + } + + gl->fActiveTexture(aTextureUnit); + gl->fBindTexture(mTextureTarget, mSurfTex->GetTexName()); + + ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, mTextureTarget); +} + +bool SurfaceTextureSource::IsValid() const { return !!gl(); } + +gfx::Matrix4x4 SurfaceTextureSource::GetTextureTransform() { + MOZ_ASSERT(mSurfTex); + + gfx::Matrix4x4 ret; + + // GetTransformMatrix() returns the transform set by the producer side of the + // SurfaceTexture that must be applied to texture coordinates when + // sampling. In some cases we may have set an override value, such as in + // AndroidNativeWindowTextureData where we own the producer side, or for + // MediaCodec output on devices where where we know the value is incorrect. + if (mTransformOverride) { + ret = *mTransformOverride; + } else { + const auto& surf = java::sdk::SurfaceTexture::LocalRef( + java::sdk::SurfaceTexture::Ref::From(mSurfTex)); + AndroidSurfaceTexture::GetTransformMatrix(surf, &ret); + } + + return ret; +} + +void SurfaceTextureSource::DeallocateDeviceData() { mSurfTex = nullptr; } + +//////////////////////////////////////////////////////////////////////// + +SurfaceTextureHost::SurfaceTextureHost( + TextureFlags aFlags, mozilla::java::GeckoSurfaceTexture::Ref& aSurfTex, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, bool aContinuousUpdate, + Maybe<Matrix4x4> aTransformOverride) + : TextureHost(TextureHostType::AndroidSurfaceTexture, aFlags), + mSurfTex(aSurfTex), + mSize(aSize), + mFormat(aFormat), + mContinuousUpdate(aContinuousUpdate), + mTransformOverride(aTransformOverride) { + if (!mSurfTex) { + return; + } + + // Continuous update makes no sense with single buffer mode + MOZ_ASSERT(!mSurfTex->IsSingleBuffer() || !mContinuousUpdate); + + mSurfTex->IncrementUse(); +} + +SurfaceTextureHost::~SurfaceTextureHost() { + if (mSurfTex) { + mSurfTex->DecrementUse(); + mSurfTex = nullptr; + } +} + +gl::GLContext* SurfaceTextureHost::gl() const { return nullptr; } + +gfx::SurfaceFormat SurfaceTextureHost::GetFormat() const { return mFormat; } + +void SurfaceTextureHost::DeallocateDeviceData() { + if (mTextureSource) { + mTextureSource->DeallocateDeviceData(); + } + + if (mSurfTex) { + mSurfTex->DecrementUse(); + mSurfTex = nullptr; + } +} + +void SurfaceTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + const auto originPos = + NeedsYFlip() ? gl::OriginPos::BottomLeft : gl::OriginPos::TopLeft; + RefPtr<wr::RenderTextureHost> texture = + new wr::RenderAndroidSurfaceTextureHost(mSurfTex, mSize, mFormat, + mContinuousUpdate, originPos, + mTransformOverride); + wr::RenderThread::Get()->RegisterExternalImage(aExternalImageId, + texture.forget()); +} + +uint32_t SurfaceTextureHost::NumSubTextures() { return mSurfTex ? 1 : 0; } + +void SurfaceTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, const wr::ExternalImageId& aExtID) { + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + + // Prefer TextureExternal unless the backend requires TextureRect. + TextureHost::NativeTexturePolicy policy = + TextureHost::BackendNativeTexturePolicy(aResources.GetBackendType(), + GetSize()); + auto imageType = policy == TextureHost::NativeTexturePolicy::REQUIRE + ? wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureRect) + : wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureExternal); + + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: { + MOZ_ASSERT(aImageKeys.length() == 1); + + // XXX Add RGBA handling. Temporary hack to avoid crash + // With BGRA format setting, rendering works without problem. + auto format = GetFormat() == gfx::SurfaceFormat::R8G8B8A8 + ? gfx::SurfaceFormat::B8G8R8A8 + : gfx::SurfaceFormat::B8G8R8X8; + wr::ImageDescriptor descriptor(GetSize(), format); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +void SurfaceTextureHost::PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, + wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, + PushDisplayItemFlagSet aFlags) { + bool preferCompositorSurface = + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE); + bool supportsExternalCompositing = + SupportsExternalCompositing(aBuilder.GetBackendType()); + + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage(aBounds, aClip, true, false, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + preferCompositorSurface, supportsExternalCompositing); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +bool SurfaceTextureHost::SupportsExternalCompositing( + WebRenderBackend aBackend) { + return aBackend == WebRenderBackend::SOFTWARE; +} + +//////////////////////////////////////////////////////////////////////// +// AndroidHardwareBufferTextureHost + +/* static */ +already_AddRefed<AndroidHardwareBufferTextureHost> +AndroidHardwareBufferTextureHost::Create( + TextureFlags aFlags, const SurfaceDescriptorAndroidHardwareBuffer& aDesc) { + RefPtr<AndroidHardwareBuffer> buffer = + AndroidHardwareBuffer::FromFileDescriptor( + const_cast<ipc::FileDescriptor&>(aDesc.handle()), aDesc.bufferId(), + aDesc.size(), aDesc.format()); + if (!buffer) { + return nullptr; + } + RefPtr<AndroidHardwareBufferTextureHost> host = + new AndroidHardwareBufferTextureHost(aFlags, buffer); + return host.forget(); +} + +AndroidHardwareBufferTextureHost::AndroidHardwareBufferTextureHost( + TextureFlags aFlags, AndroidHardwareBuffer* aAndroidHardwareBuffer) + : TextureHost(TextureHostType::AndroidHardwareBuffer, aFlags), + mAndroidHardwareBuffer(aAndroidHardwareBuffer), + mEGLImage(EGL_NO_IMAGE) {} + +AndroidHardwareBufferTextureHost::~AndroidHardwareBufferTextureHost() { + DestroyEGLImage(); +} + +void AndroidHardwareBufferTextureHost::DestroyEGLImage() { + if (mEGLImage && gl()) { + const auto& gle = gl::GLContextEGL::Cast(gl()); + const auto& egl = gle->mEgl; + egl->fDestroyImage(mEGLImage); + mEGLImage = EGL_NO_IMAGE; + } +} + +gl::GLContext* AndroidHardwareBufferTextureHost::gl() const { return nullptr; } + +void AndroidHardwareBufferTextureHost::NotifyNotUsed() { + // XXX Add android fence handling + TextureHost::NotifyNotUsed(); +} + +gfx::SurfaceFormat AndroidHardwareBufferTextureHost::GetFormat() const { + if (mAndroidHardwareBuffer) { + return mAndroidHardwareBuffer->mFormat; + } + return gfx::SurfaceFormat::UNKNOWN; +} + +gfx::IntSize AndroidHardwareBufferTextureHost::GetSize() const { + if (mAndroidHardwareBuffer) { + return mAndroidHardwareBuffer->mSize; + } + return gfx::IntSize(); +} + +void AndroidHardwareBufferTextureHost::DeallocateDeviceData() { + if (mTextureSource) { + mTextureSource = nullptr; + } + DestroyEGLImage(); +} + +void AndroidHardwareBufferTextureHost::SetAcquireFence( + mozilla::ipc::FileDescriptor&& aFenceFd) { + if (!mAndroidHardwareBuffer) { + return; + } + mAndroidHardwareBuffer->SetAcquireFence(std::move(aFenceFd)); +} + +void AndroidHardwareBufferTextureHost::SetReleaseFence( + mozilla::ipc::FileDescriptor&& aFenceFd) { + if (!mAndroidHardwareBuffer) { + return; + } + mAndroidHardwareBuffer->SetReleaseFence(std::move(aFenceFd)); +} + +mozilla::ipc::FileDescriptor +AndroidHardwareBufferTextureHost::GetAndResetReleaseFence() { + if (!mAndroidHardwareBuffer) { + return mozilla::ipc::FileDescriptor(); + } + return mAndroidHardwareBuffer->GetAndResetReleaseFence(); +} + +void AndroidHardwareBufferTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr<wr::RenderTextureHost> texture = + new wr::RenderAndroidHardwareBufferTextureHost(mAndroidHardwareBuffer); + wr::RenderThread::Get()->RegisterExternalImage(aExternalImageId, + texture.forget()); +} + +uint32_t AndroidHardwareBufferTextureHost::NumSubTextures() { + return mAndroidHardwareBuffer ? 1 : 0; +} + +void AndroidHardwareBufferTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, const wr::ExternalImageId& aExtID) { + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureExternal); + + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: { + MOZ_ASSERT(aImageKeys.length() == 1); + + // XXX Add RGBA handling. Temporary hack to avoid crash + // With BGRA format setting, rendering works without problem. + auto format = GetFormat() == gfx::SurfaceFormat::R8G8B8A8 + ? gfx::SurfaceFormat::B8G8R8A8 + : gfx::SurfaceFormat::B8G8R8X8; + wr::ImageDescriptor descriptor(GetSize(), format); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +void AndroidHardwareBufferTextureHost::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, PushDisplayItemFlagSet aFlags) { + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage( + aBounds, aClip, true, false, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE)); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +#endif // MOZ_WIDGET_ANDROID + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// EGLImage + +EGLImageTextureSource::EGLImageTextureSource(TextureSourceProvider* aProvider, + EGLImage aImage, + gfx::SurfaceFormat aFormat, + GLenum aTarget, GLenum aWrapMode, + gfx::IntSize aSize) + : mImage(aImage), + mFormat(aFormat), + mTextureTarget(aTarget), + mWrapMode(aWrapMode), + mSize(aSize) { + MOZ_ASSERT(mTextureTarget == LOCAL_GL_TEXTURE_2D || + mTextureTarget == LOCAL_GL_TEXTURE_EXTERNAL); +} + +void EGLImageTextureSource::BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) { + GLContext* gl = this->gl(); + if (!gl || !gl->MakeCurrent()) { + NS_WARNING("Trying to bind a texture without a GLContext"); + return; + } + +#ifdef DEBUG + const bool supportsEglImage = [&]() { + const auto& gle = GLContextEGL::Cast(gl); + const auto& egl = gle->mEgl; + + return egl->HasKHRImageBase() && + egl->IsExtensionSupported(EGLExtension::KHR_gl_texture_2D_image) && + gl->IsExtensionSupported(GLContext::OES_EGL_image); + }(); + MOZ_ASSERT(supportsEglImage, "EGLImage not supported or disabled in runtime"); +#endif + + GLuint tex = mCompositor->GetTemporaryTexture(mTextureTarget, aTextureUnit); + + gl->fActiveTexture(aTextureUnit); + gl->fBindTexture(mTextureTarget, tex); + + gl->fEGLImageTargetTexture2D(mTextureTarget, mImage); + + ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, mTextureTarget); +} + +bool EGLImageTextureSource::IsValid() const { return !!gl(); } + +gfx::Matrix4x4 EGLImageTextureSource::GetTextureTransform() { + gfx::Matrix4x4 ret; + return ret; +} + +//////////////////////////////////////////////////////////////////////// + +EGLImageTextureHost::EGLImageTextureHost(TextureFlags aFlags, EGLImage aImage, + EGLSync aSync, gfx::IntSize aSize, + bool hasAlpha) + : TextureHost(TextureHostType::EGLImage, aFlags), + mImage(aImage), + mSync(aSync), + mSize(aSize), + mHasAlpha(hasAlpha) {} + +EGLImageTextureHost::~EGLImageTextureHost() = default; + +gl::GLContext* EGLImageTextureHost::gl() const { return nullptr; } + +gfx::SurfaceFormat EGLImageTextureHost::GetFormat() const { + MOZ_ASSERT(mTextureSource); + return mTextureSource ? mTextureSource->GetFormat() + : gfx::SurfaceFormat::UNKNOWN; +} + +void EGLImageTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr<wr::RenderTextureHost> texture = + new wr::RenderEGLImageTextureHost(mImage, mSync, mSize); + wr::RenderThread::Get()->RegisterExternalImage(aExternalImageId, + texture.forget()); +} + +void EGLImageTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, const wr::ExternalImageId& aExtID) { + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureExternal); + + gfx::SurfaceFormat format = + mHasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8; + + MOZ_ASSERT(aImageKeys.length() == 1); + // XXX Add RGBA handling. Temporary hack to avoid crash + // With BGRA format setting, rendering works without problem. + auto formatTmp = format == gfx::SurfaceFormat::R8G8B8A8 + ? gfx::SurfaceFormat::B8G8R8A8 + : gfx::SurfaceFormat::B8G8R8X8; + wr::ImageDescriptor descriptor(GetSize(), formatTmp); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); +} + +void EGLImageTextureHost::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, PushDisplayItemFlagSet aFlags) { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage( + aBounds, aClip, true, false, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE)); +} + +// + +GLTextureHost::GLTextureHost(TextureFlags aFlags, GLuint aTextureHandle, + GLenum aTarget, GLsync aSync, gfx::IntSize aSize, + bool aHasAlpha) + : TextureHost(TextureHostType::GLTexture, aFlags), + mTexture(aTextureHandle), + mTarget(aTarget), + mSync(aSync), + mSize(aSize), + mHasAlpha(aHasAlpha) {} + +GLTextureHost::~GLTextureHost() = default; + +gl::GLContext* GLTextureHost::gl() const { return nullptr; } + +gfx::SurfaceFormat GLTextureHost::GetFormat() const { + MOZ_ASSERT(mTextureSource); + return mTextureSource ? mTextureSource->GetFormat() + : gfx::SurfaceFormat::UNKNOWN; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/TextureHostOGL.h b/gfx/layers/opengl/TextureHostOGL.h new file mode 100644 index 0000000000..ea4042b0fc --- /dev/null +++ b/gfx/layers/opengl/TextureHostOGL.h @@ -0,0 +1,607 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MOZILLA_GFX_TEXTUREOGL_H +#define MOZILLA_GFX_TEXTUREOGL_H + +#include <stddef.h> // for size_t +#include <stdint.h> // for uint64_t +#include "CompositableHost.h" +#include "GLContextTypes.h" // for GLContext +#include "GLDefs.h" // for GLenum, LOCAL_GL_CLAMP_TO_EDGE, etc +#include "GLTextureImage.h" // for TextureImage +#include "gfxTypes.h" +#include "mozilla/GfxMessageUtils.h" // for gfxContentType +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize, IntPoint +#include "mozilla/gfx/Types.h" // for SurfaceFormat, etc +#include "mozilla/layers/CompositorOGL.h" // for CompositorOGL +#include "mozilla/layers/CompositorTypes.h" // for TextureFlags +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureHost.h" // for TextureHost, etc +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "mozilla/webrender/RenderThread.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING +#include "nsISupportsImpl.h" // for TextureImage::Release, etc +#include "nsRegionFwd.h" // for nsIntRegion + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidSurfaceTexture.h" +# include "mozilla/java/GeckoSurfaceTextureWrappers.h" +#endif + +namespace mozilla { +namespace gfx { +class DataSourceSurface; +} // namespace gfx + +namespace layers { + +class Compositor; +class CompositorOGL; +class AndroidHardwareBuffer; +class SurfaceDescriptorAndroidHardwareBuffer; +class TextureImageTextureSourceOGL; +class GLTextureSource; + +void ApplySamplingFilterToBoundTexture(gl::GLContext* aGL, + gfx::SamplingFilter aSamplingFilter, + GLuint aTarget = LOCAL_GL_TEXTURE_2D); + +already_AddRefed<TextureHost> CreateTextureHostOGL( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags); + +/* + * TextureHost implementations for the OpenGL backend. + * + * Note that it is important to be careful about the ownership model with + * the OpenGL backend, due to some widget limitation on Linux: before + * the nsBaseWidget associated with our OpenGL context has been completely + * deleted, every resource belonging to the OpenGL context MUST have been + * released. At the moment the teardown sequence happens in the middle of + * the nsBaseWidget's destructor, meaning that at a given moment we must be + * able to easily find and release all the GL resources. + * The point is: be careful about the ownership model and limit the number + * of objects sharing references to GL resources to make the tear down + * sequence as simple as possible. + */ + +/** + * TextureSourceOGL provides the necessary API for CompositorOGL to composite + * a TextureSource. + */ +class TextureSourceOGL { + public: + TextureSourceOGL() + : mCachedSamplingFilter(gfx::SamplingFilter::GOOD), + mHasCachedSamplingFilter(false) {} + + virtual bool IsValid() const = 0; + + virtual void BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) = 0; + + // To be overridden in textures that need this. This method will be called + // when the compositor has used the texture to draw. This allows us to set + // a fence with glFenceSync which we can wait on later to ensure the GPU + // is done with the draw calls using that texture. We would like to be able + // to simply use glFinishObjectAPPLE, but this returns earlier than + // expected with nvidia drivers. + virtual void MaybeFenceTexture() {} + + virtual gfx::IntSize GetSize() const = 0; + + virtual GLenum GetTextureTarget() const { return LOCAL_GL_TEXTURE_2D; } + + virtual gfx::SurfaceFormat GetFormat() const = 0; + + virtual GLenum GetWrapMode() const = 0; + + virtual gfx::Matrix4x4 GetTextureTransform() { return gfx::Matrix4x4(); } + + virtual TextureImageTextureSourceOGL* AsTextureImageTextureSource() { + return nullptr; + } + + virtual GLTextureSource* AsGLTextureSource() { return nullptr; } + + void SetSamplingFilter(gl::GLContext* aGL, + gfx::SamplingFilter aSamplingFilter) { + if (mHasCachedSamplingFilter && mCachedSamplingFilter == aSamplingFilter) { + return; + } + mHasCachedSamplingFilter = true; + mCachedSamplingFilter = aSamplingFilter; + ApplySamplingFilterToBoundTexture(aGL, aSamplingFilter, GetTextureTarget()); + } + + void ClearCachedFilter() { mHasCachedSamplingFilter = false; } + + private: + gfx::SamplingFilter mCachedSamplingFilter; + bool mHasCachedSamplingFilter; +}; + +/** + * A TextureSource backed by a TextureImage. + * + * Depending on the underlying TextureImage, may support texture tiling, so + * make sure to check AsBigImageIterator() and use the texture accordingly. + * + * This TextureSource can be used without a TextureHost and manage it's own + * GL texture(s). + */ +class TextureImageTextureSourceOGL final : public DataTextureSource, + public TextureSourceOGL, + public BigImageIterator { + public: + explicit TextureImageTextureSourceOGL( + CompositorOGL* aCompositor, TextureFlags aFlags = TextureFlags::DEFAULT); + + const char* Name() const override { return "TextureImageTextureSourceOGL"; } + // DataTextureSource + + bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr, + gfx::IntPoint* aDstOffset = nullptr) override; + + void EnsureBuffer(const gfx::IntSize& aSize, gfxContentType aContentType); + + TextureImageTextureSourceOGL* AsTextureImageTextureSource() override { + return this; + } + + // TextureSource + + void DeallocateDeviceData() override; + + TextureSourceOGL* AsSourceOGL() override { return this; } + + void BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) override; + + gfx::IntSize GetSize() const override; + + gfx::SurfaceFormat GetFormat() const override; + + bool IsValid() const override { return !!mTexImage; } + + GLenum GetWrapMode() const override { return mTexImage->GetWrapMode(); } + + // BigImageIterator + + BigImageIterator* AsBigImageIterator() override { return this; } + + void BeginBigImageIteration() override { + mTexImage->BeginBigImageIteration(); + mIterating = true; + } + + void EndBigImageIteration() override { mIterating = false; } + + gfx::IntRect GetTileRect() override; + + size_t GetTileCount() override { return mTexImage->GetTileCount(); } + + bool NextTile() override { return mTexImage->NextTile(); } + + gl::GLContext* gl() const { return mGL; } + + protected: + ~TextureImageTextureSourceOGL(); + + RefPtr<gl::TextureImage> mTexImage; + RefPtr<gl::GLContext> mGL; + RefPtr<CompositorOGL> mCompositor; + TextureFlags mFlags; + bool mIterating; +}; + +/** + * A texture source for GL textures. + * + * It does not own any GL texture, and attaches its shared handle to one of + * the compositor's temporary textures when binding. + * + * The shared texture handle is owned by the TextureHost. + */ +class GLTextureSource : public DataTextureSource, public TextureSourceOGL { + public: + GLTextureSource(TextureSourceProvider* aProvider, GLuint aTextureHandle, + GLenum aTarget, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + GLTextureSource(gl::GLContext* aGL, GLuint aTextureHandle, GLenum aTarget, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat); + + virtual ~GLTextureSource(); + + const char* Name() const override { return "GLTextureSource"; } + + GLTextureSource* AsGLTextureSource() override { return this; } + + TextureSourceOGL* AsSourceOGL() override { return this; } + + void BindTexture(GLenum activetex, + gfx::SamplingFilter aSamplingFilter) override; + + bool IsValid() const override; + + gfx::IntSize GetSize() const override { return mSize; } + + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + + GLenum GetTextureTarget() const override { return mTextureTarget; } + + GLenum GetWrapMode() const override { return LOCAL_GL_CLAMP_TO_EDGE; } + + void DeallocateDeviceData() override; + + void SetSize(gfx::IntSize aSize) { mSize = aSize; } + + void SetFormat(gfx::SurfaceFormat aFormat) { mFormat = aFormat; } + + GLuint GetTextureHandle() const { return mTextureHandle; } + + gl::GLContext* gl() const { return mGL; } + + bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr, + gfx::IntPoint* aDstOffset = nullptr) override { + return false; + } + + protected: + void DeleteTextureHandle(); + + RefPtr<gl::GLContext> mGL; + RefPtr<CompositorOGL> mCompositor; + GLuint mTextureHandle; + GLenum mTextureTarget; + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; +}; + +// This texture source try to wrap "aSurface" in ctor for compositor direct +// access. Since we can't know the timing for gpu buffer access, the surface +// should be alive until the ~ClientStorageTextureSource(). And if we try to +// update the surface we mapped before, we need to call Sync() to make sure +// the surface is not used by compositor. +class DirectMapTextureSource : public GLTextureSource { + public: + DirectMapTextureSource(gl::GLContext* aContext, + gfx::DataSourceSurface* aSurface); + DirectMapTextureSource(TextureSourceProvider* aProvider, + gfx::DataSourceSurface* aSurface); + ~DirectMapTextureSource(); + + bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr, + gfx::IntPoint* aDstOffset = nullptr) override; + + // If aBlocking is false, check if this texture is no longer being used + // by the GPU - if aBlocking is true, this will block until the GPU is + // done with it. + bool Sync(bool aBlocking) override; + + void MaybeFenceTexture() override; + + private: + bool UpdateInternal(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, gfx::IntPoint* aSrcOffset, + bool aInit); + + GLsync mSync; +}; + +class GLTextureHost : public TextureHost { + public: + GLTextureHost(TextureFlags aFlags, GLuint aTextureHandle, GLenum aTarget, + GLsync aSync, gfx::IntSize aSize, bool aHasAlpha); + + virtual ~GLTextureHost(); + + // We don't own anything. + void DeallocateDeviceData() override {} + + gfx::SurfaceFormat GetFormat() const override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override { return mSize; } + + const char* Name() override { return "GLTextureHost"; } + + protected: + const GLuint mTexture; + const GLenum mTarget; + GLsync mSync; + const gfx::IntSize mSize; + const bool mHasAlpha; + RefPtr<GLTextureSource> mTextureSource; +}; + +//////////////////////////////////////////////////////////////////////// +// SurfaceTexture + +#ifdef MOZ_WIDGET_ANDROID + +class SurfaceTextureSource : public TextureSource, public TextureSourceOGL { + public: + SurfaceTextureSource(TextureSourceProvider* aProvider, + java::GeckoSurfaceTexture::Ref& aSurfTex, + gfx::SurfaceFormat aFormat, GLenum aTarget, + GLenum aWrapMode, gfx::IntSize aSize, + Maybe<gfx::Matrix4x4> aTransformOverride); + + const char* Name() const override { return "SurfaceTextureSource"; } + + TextureSourceOGL* AsSourceOGL() override { return this; } + + void BindTexture(GLenum activetex, + gfx::SamplingFilter aSamplingFilter) override; + + bool IsValid() const override; + + gfx::IntSize GetSize() const override { return mSize; } + + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + + gfx::Matrix4x4 GetTextureTransform() override; + + GLenum GetTextureTarget() const override { return mTextureTarget; } + + GLenum GetWrapMode() const override { return mWrapMode; } + + void DeallocateDeviceData() override; + + gl::GLContext* gl() const { return mGL; } + + protected: + RefPtr<gl::GLContext> mGL; + mozilla::java::GeckoSurfaceTexture::GlobalRef mSurfTex; + const gfx::SurfaceFormat mFormat; + const GLenum mTextureTarget; + const GLenum mWrapMode; + const gfx::IntSize mSize; + const Maybe<gfx::Matrix4x4> mTransformOverride; +}; + +class SurfaceTextureHost : public TextureHost { + public: + SurfaceTextureHost(TextureFlags aFlags, + mozilla::java::GeckoSurfaceTexture::Ref& aSurfTex, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + bool aContinuousUpdate, + Maybe<gfx::Matrix4x4> aTransformOverride); + + virtual ~SurfaceTextureHost(); + + void DeallocateDeviceData() override; + + gfx::SurfaceFormat GetFormat() const override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override { return mSize; } + + const char* Name() override { return "SurfaceTextureHost"; } + + SurfaceTextureHost* AsSurfaceTextureHost() override { return this; } + + bool IsWrappingSurfaceTextureHost() override { return true; } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + bool SupportsExternalCompositing(WebRenderBackend aBackend) override; + + // gecko does not need deferred deletion with WebRender + // GPU/hardware task end could be checked by android fence. + // SurfaceTexture uses android fence internally, + bool NeedsDeferredDeletion() const override { return false; } + + protected: + mozilla::java::GeckoSurfaceTexture::GlobalRef mSurfTex; + const gfx::IntSize mSize; + const gfx::SurfaceFormat mFormat; + bool mContinuousUpdate; + const Maybe<gfx::Matrix4x4> mTransformOverride; + RefPtr<CompositorOGL> mCompositor; + RefPtr<SurfaceTextureSource> mTextureSource; +}; + +class AndroidHardwareBufferTextureHost : public TextureHost { + public: + static already_AddRefed<AndroidHardwareBufferTextureHost> Create( + TextureFlags aFlags, const SurfaceDescriptorAndroidHardwareBuffer& aDesc); + + AndroidHardwareBufferTextureHost( + TextureFlags aFlags, AndroidHardwareBuffer* aAndroidHardwareBuffer); + + virtual ~AndroidHardwareBufferTextureHost(); + + void DeallocateDeviceData() override; + + gfx::SurfaceFormat GetFormat() const override; + + gfx::IntSize GetSize() const override; + + void NotifyNotUsed() override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + const char* Name() override { return "AndroidHardwareBufferTextureHost"; } + + AndroidHardwareBufferTextureHost* AsAndroidHardwareBufferTextureHost() + override { + return this; + } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + void SetAcquireFence(mozilla::ipc::FileDescriptor&& aFenceFd) override; + + void SetReleaseFence(mozilla::ipc::FileDescriptor&& aFenceFd) override; + + mozilla::ipc::FileDescriptor GetAndResetReleaseFence() override; + + AndroidHardwareBuffer* GetAndroidHardwareBuffer() const override { + return mAndroidHardwareBuffer; + } + + // gecko does not need deferred deletion with WebRender + // GPU/hardware task end could be checked by android fence. + bool NeedsDeferredDeletion() const override { return false; } + + protected: + void DestroyEGLImage(); + + RefPtr<AndroidHardwareBuffer> mAndroidHardwareBuffer; + RefPtr<GLTextureSource> mTextureSource; + EGLImage mEGLImage; +}; + +#endif // MOZ_WIDGET_ANDROID + +//////////////////////////////////////////////////////////////////////// +// EGLImage + +class EGLImageTextureSource : public TextureSource, public TextureSourceOGL { + public: + EGLImageTextureSource(TextureSourceProvider* aProvider, EGLImage aImage, + gfx::SurfaceFormat aFormat, GLenum aTarget, + GLenum aWrapMode, gfx::IntSize aSize); + + const char* Name() const override { return "EGLImageTextureSource"; } + + TextureSourceOGL* AsSourceOGL() override { return this; } + + void BindTexture(GLenum activetex, + gfx::SamplingFilter aSamplingFilter) override; + + bool IsValid() const override; + + gfx::IntSize GetSize() const override { return mSize; } + + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + + gfx::Matrix4x4 GetTextureTransform() override; + + GLenum GetTextureTarget() const override { return mTextureTarget; } + + GLenum GetWrapMode() const override { return mWrapMode; } + + // We don't own anything. + void DeallocateDeviceData() override {} + + gl::GLContext* gl() const { return mGL; } + + protected: + RefPtr<gl::GLContext> mGL; + RefPtr<CompositorOGL> mCompositor; + const EGLImage mImage; + const gfx::SurfaceFormat mFormat; + const GLenum mTextureTarget; + const GLenum mWrapMode; + const gfx::IntSize mSize; +}; + +class EGLImageTextureHost final : public TextureHost { + public: + EGLImageTextureHost(TextureFlags aFlags, EGLImage aImage, EGLSync aSync, + gfx::IntSize aSize, bool hasAlpha); + + virtual ~EGLImageTextureHost(); + + // We don't own anything. + void DeallocateDeviceData() override {} + + gfx::SurfaceFormat GetFormat() const override; + + already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override { return mSize; } + + const char* Name() override { return "EGLImageTextureHost"; } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range<wr::ImageKey>& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range<wr::ImageKey>& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + protected: + const EGLImage mImage; + const EGLSync mSync; + const gfx::IntSize mSize; + const bool mHasAlpha; + RefPtr<EGLImageTextureSource> mTextureSource; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_TEXTUREOGL_H */ |