480 lines
16 KiB
C++
480 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
|