/* -*- 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 "RenderCompositorOGLSWGL.h" #include "GLContext.h" #include "GLContextEGL.h" #include "mozilla/layers/BuildConstants.h" #include "mozilla/layers/CompositorOGL.h" #include "mozilla/layers/Effects.h" #include "mozilla/layers/TextureHostOGL.h" #include "mozilla/widget/CompositorWidget.h" #include "OGLShaderProgram.h" #ifdef MOZ_WIDGET_ANDROID # include "mozilla/java/GeckoSurfaceTextureWrappers.h" # include "mozilla/layers/AndroidHardwareBuffer.h" # include "mozilla/webrender/RenderAndroidHardwareBufferTextureHost.h" # include "mozilla/webrender/RenderAndroidSurfaceTextureHost.h" # include "mozilla/widget/AndroidCompositorWidget.h" # include # include #endif #ifdef MOZ_WIDGET_GTK # include "mozilla/widget/GtkCompositorWidget.h" # include # ifdef MOZ_X11 # include # endif #endif namespace mozilla { using namespace layers; using namespace gfx; namespace wr { extern LazyLogModule gRenderThreadLog; #define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) UniquePtr RenderCompositorOGLSWGL::Create( const RefPtr& aWidget, nsACString& aError) { if (!aWidget->GetCompositorOptions().AllowSoftwareWebRenderOGL()) { return nullptr; } RefPtr compositor; #ifdef MOZ_WIDGET_ANDROID RefPtr context = RenderThread::Get()->SingletonGLForCompositorOGL(); if (!context) { gfxCriticalNote << "SingletonGL does not exist for SWGL"; return nullptr; } auto programs = RenderThread::Get()->GetProgramsForCompositorOGL(); if (!programs) { gfxCriticalNote << "Failed to get Programs for CompositorOGL for SWGL"; return nullptr; } nsCString log; RefPtr compositorOGL; compositorOGL = new CompositorOGL(aWidget, /* aSurfaceWidth */ -1, /* aSurfaceHeight */ -1, /* aUseExternalSurfaceSize */ true); if (!compositorOGL->Initialize(context, programs, &log)) { gfxCriticalNote << "Failed to initialize CompositorOGL for SWGL: " << log.get(); return nullptr; } compositor = compositorOGL; #elif defined(MOZ_WIDGET_GTK) nsCString log; RefPtr compositorOGL; compositorOGL = new CompositorOGL(aWidget); if (!compositorOGL->Initialize(&log)) { gfxCriticalNote << "Failed to initialize CompositorOGL for SWGL: " << log.get(); return nullptr; } compositor = compositorOGL; #endif if (!compositor) { return nullptr; } void* ctx = wr_swgl_create_context(); if (!ctx) { gfxCriticalNote << "Failed SWGL context creation for WebRender"; return nullptr; } return MakeUnique(compositor, aWidget, ctx); } RenderCompositorOGLSWGL::RenderCompositorOGLSWGL( Compositor* aCompositor, const RefPtr& aWidget, void* aContext) : RenderCompositorLayersSWGL(aCompositor, aWidget, aContext) { LOG("RenderCompositorOGLSWGL::RenderCompositorOGLSWGL()"); } RenderCompositorOGLSWGL::~RenderCompositorOGLSWGL() { LOG("RRenderCompositorOGLSWGL::~RenderCompositorOGLSWGL()"); #ifdef MOZ_WIDGET_ANDROID java::GeckoSurfaceTexture::DestroyUnused((int64_t)GetGLContext()); DestroyEGLSurface(); #endif } gl::GLContext* RenderCompositorOGLSWGL::GetGLContext() { return mCompositor->AsCompositorOGL()->gl(); } bool RenderCompositorOGLSWGL::MakeCurrent() { GetGLContext()->MakeCurrent(); #ifdef MOZ_WIDGET_ANDROID if (GetGLContext()->GetContextType() == gl::GLContextType::EGL) { gl::GLContextEGL::Cast(GetGLContext())->SetEGLSurfaceOverride(mEGLSurface); } #endif RenderCompositorLayersSWGL::MakeCurrent(); return true; } EGLSurface RenderCompositorOGLSWGL::CreateEGLSurface() { MOZ_ASSERT(GetGLContext()->GetContextType() == gl::GLContextType::EGL); EGLSurface surface = EGL_NO_SURFACE; surface = gl::GLContextEGL::CreateEGLSurfaceForCompositorWidget( mWidget, gl::GLContextEGL::Cast(GetGLContext())->mSurfaceConfig); if (surface == EGL_NO_SURFACE) { const auto* renderThread = RenderThread::Get(); gfxCriticalNote << "Failed to create EGLSurface. " << renderThread->RendererCount() << " renderers, " << renderThread->ActiveRendererCount() << " active."; } // The subsequent render after creating a new surface must be a full render. mFullRender = true; return surface; } void RenderCompositorOGLSWGL::DestroyEGLSurface() { MOZ_ASSERT(GetGLContext()->GetContextType() == gl::GLContextType::EGL); const auto& gle = gl::GLContextEGL::Cast(GetGLContext()); const auto& egl = gle->mEgl; // Release EGLSurface of back buffer before calling ResizeBuffers(). if (mEGLSurface) { gle->SetEGLSurfaceOverride(EGL_NO_SURFACE); gl::GLContextEGL::DestroySurface(*egl, mEGLSurface); mEGLSurface = EGL_NO_SURFACE; } } bool RenderCompositorOGLSWGL::BeginFrame() { MOZ_ASSERT(!mInFrame); RenderCompositorLayersSWGL::BeginFrame(); #ifdef MOZ_WIDGET_ANDROID java::GeckoSurfaceTexture::DestroyUnused((int64_t)GetGLContext()); GetGLContext() ->MakeCurrent(); // DestroyUnused can change the current context! #endif return true; } RenderedFrameId RenderCompositorOGLSWGL::EndFrame( const nsTArray& aDirtyRects) { mFullRender = false; return RenderCompositorLayersSWGL::EndFrame(aDirtyRects); } void RenderCompositorOGLSWGL::HandleExternalImage( RenderTextureHost* aExternalImage, FrameSurface& aFrameSurface) { MOZ_ASSERT(aExternalImage); #ifdef MOZ_WIDGET_ANDROID GLenum target = LOCAL_GL_TEXTURE_EXTERNAL; // This is required by SurfaceTexture GLenum wrapMode = LOCAL_GL_CLAMP_TO_EDGE; if (auto* host = aExternalImage->AsRenderAndroidSurfaceTextureHost()) { host->UpdateTexImageIfNecessary(); // We need to hold the texture source separately from the effect, // since the effect doesn't hold a strong reference. RefPtr layer = new SurfaceTextureSource( (TextureSourceProvider*)mCompositor, host->mSurfTex, host->mFormat, target, wrapMode, host->mSize, host->mTransformOverride); RefPtr texturedEffect = CreateTexturedEffect(host->mFormat, layer, aFrameSurface.mFilter, /* isAlphaPremultiplied */ true); gfx::Rect drawRect(0, 0, host->mSize.width, host->mSize.height); EffectChain effect; effect.mPrimaryEffect = texturedEffect; mCompositor->DrawQuad(drawRect, aFrameSurface.mClipRect, effect, 1.0, aFrameSurface.mTransform, drawRect); } else if (auto* host = aExternalImage->AsRenderAndroidHardwareBufferTextureHost()) { // We need to hold the texture source separately from the effect, // since the effect doesn't hold a strong reference. RefPtr layer = new AndroidHardwareBufferTextureSource( (TextureSourceProvider*)mCompositor, host->GetAndroidHardwareBuffer(), host->GetAndroidHardwareBuffer()->mFormat, target, wrapMode, host->GetSize()); RefPtr texturedEffect = CreateTexturedEffect( host->GetAndroidHardwareBuffer()->mFormat, layer, aFrameSurface.mFilter, /* isAlphaPremultiplied */ true); gfx::Rect drawRect(0, 0, host->GetSize().width, host->GetSize().height); EffectChain effect; effect.mPrimaryEffect = texturedEffect; mCompositor->DrawQuad(drawRect, aFrameSurface.mClipRect, effect, 1.0, aFrameSurface.mTransform, drawRect); } #endif } void RenderCompositorOGLSWGL::GetCompositorCapabilities( CompositorCapabilities* aCaps) { RenderCompositor::GetCompositorCapabilities(aCaps); // max_update_rects are not yet handled properly aCaps->max_update_rects = 0; } bool RenderCompositorOGLSWGL::RequestFullRender() { return mFullRender; } void RenderCompositorOGLSWGL::Pause() { #ifdef MOZ_WIDGET_ANDROID DestroyEGLSurface(); #elif defined(MOZ_WIDGET_GTK) mCompositor->Pause(); #endif } bool RenderCompositorOGLSWGL::Resume() { #ifdef MOZ_WIDGET_ANDROID // Destroy EGLSurface if it exists. DestroyEGLSurface(); auto size = GetBufferSize(); GLint maxTextureSize = 0; GetGLContext()->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, (GLint*)&maxTextureSize); // When window size is too big, hardware buffer allocation could fail. if (maxTextureSize < size.width || maxTextureSize < size.height) { gfxCriticalNote << "Too big ANativeWindow size(" << size.width << ", " << size.height << ") MaxTextureSize " << maxTextureSize; return false; } mEGLSurface = CreateEGLSurface(); if (mEGLSurface == EGL_NO_SURFACE) { // Often when we fail to create an EGL surface it is because the // Java Surface we have been provided is invalid. Therefore the on // the first occurence we don't raise a WebRenderError and instead // just return failure. This allows the widget a chance to request // a new Java Surface. On subsequent failures, raising the // WebRenderError will result in the compositor being recreated, // falling back through webrender configurations, and eventually // crashing if we still do not succeed. if (!mHandlingNewSurfaceError) { mHandlingNewSurfaceError = true; } else { RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); } return false; } mHandlingNewSurfaceError = false; gl::GLContextEGL::Cast(GetGLContext())->SetEGLSurfaceOverride(mEGLSurface); mCompositor->SetDestinationSurfaceSize(size.ToUnknownSize()); #elif defined(MOZ_WIDGET_GTK) bool resumed = mCompositor->Resume(); if (!resumed) { RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); return false; } #endif return true; } bool RenderCompositorOGLSWGL::IsPaused() { #ifdef MOZ_WIDGET_ANDROID return mEGLSurface == EGL_NO_SURFACE; #endif return false; } LayoutDeviceIntSize RenderCompositorOGLSWGL::GetBufferSize() { return mWidget->GetClientSize(); } UniquePtr RenderCompositorOGLSWGL::DoCreateTile(Surface* aSurface) { auto source = MakeRefPtr( mCompositor->AsCompositorOGL(), layers::TextureFlags::NO_FLAGS); return MakeUnique(std::move(source), aSurface->TileSize()); } bool RenderCompositorOGLSWGL::MaybeReadback( const gfx::IntSize& aReadbackSize, const wr::ImageFormat& aReadbackFormat, const Range& aReadbackBuffer, bool* aNeedsYFlip) { #ifdef MOZ_WIDGET_ANDROID MOZ_ASSERT(aReadbackFormat == wr::ImageFormat::RGBA8); const GLenum format = LOCAL_GL_RGBA; #else MOZ_ASSERT(aReadbackFormat == wr::ImageFormat::BGRA8); const GLenum format = LOCAL_GL_BGRA; #endif GetGLContext()->fReadPixels(0, 0, aReadbackSize.width, aReadbackSize.height, format, LOCAL_GL_UNSIGNED_BYTE, &aReadbackBuffer[0]); if (aNeedsYFlip) { *aNeedsYFlip = true; } return true; } // This is a DataSourceSurface that represents a 0-based PBO for GLTextureImage. class PBOUnpackSurface : public gfx::DataSourceSurface { public: MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PBOUnpackSurface, override) explicit PBOUnpackSurface(const gfx::IntSize& aSize) : mSize(aSize) {} uint8_t* GetData() override { return nullptr; } int32_t Stride() override { return mSize.width * sizeof(uint32_t); } gfx::SurfaceType GetType() const override { return gfx::SurfaceType::DATA_ALIGNED; } gfx::IntSize GetSize() const override { return mSize; } gfx::SurfaceFormat GetFormat() const override { return gfx::SurfaceFormat::B8G8R8A8; } // PBO offsets need to start from a 0 address, but DataSourceSurface::Map // checks for failure by comparing the address against nullptr. Override Map // to work around this. bool Map(MapType, MappedSurface* aMappedSurface) override { aMappedSurface->mData = GetData(); aMappedSurface->mStride = Stride(); return true; } void Unmap() override {} private: gfx::IntSize mSize; }; RenderCompositorOGLSWGL::TileOGL::TileOGL( RefPtr&& aTexture, const gfx::IntSize& aSize) : mTexture(aTexture) { auto* gl = mTexture->gl(); if (gl && gl->HasPBOState() && gl->MakeCurrent()) { mSurface = new PBOUnpackSurface(aSize); // Create a PBO large enough to encompass any valid rects within the tile. gl->fGenBuffers(1, &mPBO); gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mPBO); gl->fBufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER, mSurface->Stride() * aSize.height, nullptr, LOCAL_GL_DYNAMIC_DRAW); gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); } else { // Couldn't allocate a PBO, so just use a memory surface instead. mSurface = gfx::Factory::CreateDataSourceSurface( aSize, gfx::SurfaceFormat::B8G8R8A8); } } RenderCompositorOGLSWGL::TileOGL::~TileOGL() { if (mPBO) { auto* gl = mTexture->gl(); if (gl && gl->MakeCurrent()) { gl->fDeleteBuffers(1, &mPBO); mPBO = 0; } } } layers::DataTextureSource* RenderCompositorOGLSWGL::TileOGL::GetTextureSource() { return mTexture.get(); } bool RenderCompositorOGLSWGL::TileOGL::Map(wr::DeviceIntRect aDirtyRect, wr::DeviceIntRect aValidRect, void** aData, int32_t* aStride) { if (mPBO) { auto* gl = mTexture->gl(); if (!gl) { return false; } // Map the PBO, but only within the range of the buffer that spans from the // linear start offset to the linear end offset. Since we don't care about // the previous contents of the buffer, we can just tell OpenGL to // invalidate the entire buffer, even though we're only mapping a sub-range. gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mPBO); size_t stride = mSurface->Stride(); size_t offset = stride * aValidRect.min.y + aValidRect.min.x * sizeof(uint32_t); size_t length = stride * (aValidRect.height() - 1) + (aValidRect.width()) * sizeof(uint32_t); void* data = gl->fMapBufferRange( LOCAL_GL_PIXEL_UNPACK_BUFFER, offset, length, LOCAL_GL_MAP_WRITE_BIT | LOCAL_GL_MAP_INVALIDATE_BUFFER_BIT); gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); if (!data) { return false; } *aData = data; *aStride = stride; } else { // No PBO is available, so just directly write to the memory surface. gfx::DataSourceSurface::MappedSurface map; if (!mSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) { return false; } // Verify that we're not somehow using a PBOUnpackSurface. MOZ_ASSERT(map.mData != nullptr); // glTex(Sub)Image on ES doesn't support arbitrary strides without // the EXT_unpack_subimage extension. To avoid needing to make a // copy of the data we'll always draw it with stride = bpp*width // unless we're uploading the entire texture. if (!mTexture->IsValid()) { // If we don't have a texture we need to position our // data in the correct spot because we're going to upload // the entire surface *aData = map.mData + aValidRect.min.y * map.mStride + aValidRect.min.x * sizeof(uint32_t); *aStride = map.mStride; mSubSurface = nullptr; } else { // Otherwise, we can just use the top left as a scratch space *aData = map.mData; *aStride = aDirtyRect.width() * BytesPerPixel(mSurface->GetFormat()); mSubSurface = Factory::CreateWrappingDataSourceSurface( (uint8_t*)*aData, *aStride, IntSize(aDirtyRect.width(), aDirtyRect.height()), mSurface->GetFormat()); } } return true; } void RenderCompositorOGLSWGL::TileOGL::Unmap(const gfx::IntRect& aDirtyRect) { nsIntRegion dirty(aDirtyRect); if (mPBO) { // If there is a PBO, it must be unmapped before it can be sourced from. // Leave the PBO bound before the call to Update so that the texture uploads // will source from it. auto* gl = mTexture->gl(); if (!gl) { return; } gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mPBO); gl->fUnmapBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER); mTexture->Update(mSurface, &dirty); gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); } else { if (mSubSurface) { mSurface->Unmap(); // Our subsurface has a stride = aDirtyRect.width // We use a negative offset to move it to match // the dirty rect's top-left. These two offsets // will cancel each other out by the time we reach // TexSubImage. IntPoint srcOffset = {0, 0}; IntPoint dstOffset = aDirtyRect.TopLeft(); // adjust the dirty region to be relative to the dstOffset dirty.MoveBy(-dstOffset); mTexture->Update(mSubSurface, &dirty, &srcOffset, &dstOffset); mSubSurface = nullptr; } else { mSurface->Unmap(); mTexture->Update(mSurface, &dirty); } } } } // namespace wr } // namespace mozilla