HandleExternalImage() has two nearly identical code paths for rendering either RenderAndroidSurfaceTextureHost or RenderAndroidHardwareBufferTextureHost using CompositorOGL. The next patch in this series will additionally add support for RenderEGLImageTextureHost. Rather than duplicate the code yet again, we abstract the differences in to a new virtual function RenderTextureHost::CreateTextureSource() and use a single common code path in HandleExternalImage(). Differential Revision: https://phabricator.services.mozilla.com/D211290
481 lines
16 KiB
C++
481 lines
16 KiB
C++
/* -*- 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 <android/native_window.h>
|
|
# include <android/native_window_jni.h>
|
|
#endif
|
|
|
|
#ifdef MOZ_WIDGET_GTK
|
|
# include "mozilla/widget/GtkCompositorWidget.h"
|
|
# include <gdk/gdk.h>
|
|
# ifdef MOZ_X11
|
|
# include <gdk/gdkx.h>
|
|
# 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<RenderCompositor> RenderCompositorOGLSWGL::Create(
|
|
const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) {
|
|
if (!aWidget->GetCompositorOptions().AllowSoftwareWebRenderOGL()) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<Compositor> compositor;
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
RefPtr<gl::GLContext> 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;
|
|
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;
|
|
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<RenderCompositorOGLSWGL>(compositor, aWidget, ctx);
|
|
}
|
|
|
|
RenderCompositorOGLSWGL::RenderCompositorOGLSWGL(
|
|
Compositor* aCompositor, const RefPtr<widget::CompositorWidget>& 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<DeviceIntRect>& aDirtyRects) {
|
|
mFullRender = false;
|
|
|
|
return RenderCompositorLayersSWGL::EndFrame(aDirtyRects);
|
|
}
|
|
|
|
void RenderCompositorOGLSWGL::HandleExternalImage(
|
|
RenderTextureHost* aExternalImage, FrameSurface& aFrameSurface) {
|
|
MOZ_ASSERT(aExternalImage);
|
|
|
|
// We need to hold the texture source separately from the effect,
|
|
// since the effect doesn't hold a strong reference.
|
|
RefPtr<TextureSource> layer =
|
|
aExternalImage->CreateTextureSource(mCompositor);
|
|
if (layer) {
|
|
RefPtr<TexturedEffect> texturedEffect = CreateTexturedEffect(
|
|
aExternalImage->GetFormat(), layer, aFrameSurface.mFilter,
|
|
/* isAlphaPremultiplied */ true);
|
|
|
|
auto size = layer->GetSize();
|
|
gfx::Rect drawRect(0.0, 0.0, float(size.width), float(size.height));
|
|
|
|
EffectChain effect;
|
|
effect.mPrimaryEffect = texturedEffect;
|
|
mCompositor->DrawQuad(drawRect, aFrameSurface.mClipRect, effect, 1.0,
|
|
aFrameSurface.mTransform, drawRect);
|
|
}
|
|
}
|
|
|
|
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<RenderCompositorLayersSWGL::Tile>
|
|
RenderCompositorOGLSWGL::DoCreateTile(Surface* aSurface) {
|
|
auto source = MakeRefPtr<TextureImageTextureSourceOGL>(
|
|
mCompositor->AsCompositorOGL(), layers::TextureFlags::NO_FLAGS);
|
|
|
|
return MakeUnique<TileOGL>(std::move(source), aSurface->TileSize());
|
|
}
|
|
|
|
bool RenderCompositorOGLSWGL::MaybeReadback(
|
|
const gfx::IntSize& aReadbackSize, const wr::ImageFormat& aReadbackFormat,
|
|
const Range<uint8_t>& 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<layers::TextureImageTextureSourceOGL>&& 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
|