/* -*- 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 "RenderCompositorSWGL.h" #include "mozilla/gfx/Logging.h" #include "mozilla/widget/CompositorWidget.h" #ifdef MOZ_WIDGET_GTK # include "mozilla/WidgetUtilsGtk.h" #endif namespace mozilla { using namespace gfx; namespace wr { extern LazyLogModule gRenderThreadLog; #define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) /* static */ UniquePtr RenderCompositorSWGL::Create( const RefPtr& aWidget, nsACString& aError) { void* ctx = wr_swgl_create_context(); if (!ctx) { gfxCriticalNote << "Failed SWGL context creation for WebRender"; return nullptr; } return MakeUnique(aWidget, ctx); } RenderCompositorSWGL::RenderCompositorSWGL( const RefPtr& aWidget, void* aContext) : RenderCompositor(aWidget), mContext(aContext) { MOZ_ASSERT(mContext); LOG("RenderCompositorSWGL::RenderCompositorSWGL()"); } RenderCompositorSWGL::~RenderCompositorSWGL() { LOG("RenderCompositorSWGL::~RenderCompositorSWGL()"); wr_swgl_destroy_context(mContext); } void RenderCompositorSWGL::ClearMappedBuffer() { mMappedData = nullptr; mMappedStride = 0; mDT = nullptr; } bool RenderCompositorSWGL::MakeCurrent() { wr_swgl_make_current(mContext); return true; } bool RenderCompositorSWGL::BeginFrame() { mRenderWidgetSize = Some(mWidget->GetClientSize()); #ifdef MOZ_WAYLAND if (mLastRenderWidgetSize != mRenderWidgetSize.value()) { mLastRenderWidgetSize = mRenderWidgetSize.value(); mRequestFullRender = true; } #endif // Set up a temporary region representing the entire window surface in case a // dirty region is not supplied. ClearMappedBuffer(); mDirtyRegion = LayoutDeviceIntRect(LayoutDeviceIntPoint(), GetBufferSize()); wr_swgl_make_current(mContext); return true; } bool RenderCompositorSWGL::AllocateMappedBuffer( const wr::DeviceIntRect* aOpaqueRects, size_t aNumOpaqueRects) { // Request a new draw target to use from the widget... MOZ_ASSERT(!mDT); layers::BufferMode bufferMode = layers::BufferMode::BUFFERED; mDT = mWidget->StartRemoteDrawingInRegion(mDirtyRegion, &bufferMode); if (!mDT) { gfxCriticalNoteOnce << "RenderCompositorSWGL failed mapping default framebuffer, no dt"; return false; } // Attempt to lock the underlying buffer directly from the draw target. // Verify that the size at least matches what the widget claims and that // the format is BGRA8 as SWGL requires. uint8_t* data = nullptr; gfx::IntSize size; int32_t stride = 0; gfx::SurfaceFormat format = gfx::SurfaceFormat::UNKNOWN; if (bufferMode != layers::BufferMode::BUFFERED && !mSurface && mDT->LockBits(&data, &size, &stride, &format) && (format != gfx::SurfaceFormat::B8G8R8A8 && format != gfx::SurfaceFormat::B8G8R8X8)) { // We tried to lock the DT and it succeeded, but the size or format // of the data is not compatible, so just release it and fall back below... mDT->ReleaseBits(data); data = nullptr; } LayoutDeviceIntRect bounds = mDirtyRegion.GetBounds(); // If locking succeeded above, just use that. if (data) { mMappedData = data; mMappedStride = stride; // Disambiguate whether the widget's draw target has its origin at zero or // if it is offset to the dirty region origin. The DT might either enclose // only the region itself, the region including the origin, or the entire // widget. Thus, if the DT doesn't only enclose the region, we assume it // contains the origin. if (size != bounds.Size().ToUnknownSize()) { // Update the bounds to include zero if the origin is at zero. bounds.ExpandToEnclose(LayoutDeviceIntPoint(0, 0)); } // Sometimes we end up racing on the widget size, and it can shrink between // BeginFrame and StartCompositing. We calculated our dirty region based on // the previous widget size, so we need to clamp the bounds here to ensure // we remain within the buffer. bounds.IntersectRect( bounds, LayoutDeviceIntRect(bounds.TopLeft(), LayoutDeviceIntSize(size.width, size.height))); } else { // If we couldn't lock the DT above, then allocate a data surface and map // that for usage with SWGL. size = bounds.Size().ToUnknownSize(); if (!mSurface || mSurface->GetSize() != size) { mSurface = gfx::Factory::CreateDataSourceSurface( size, gfx::SurfaceFormat::B8G8R8A8); } gfx::DataSourceSurface::MappedSurface map = {nullptr, 0}; if (!mSurface || !mSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) { // We failed mapping the data surface, so need to cancel the frame. mWidget->EndRemoteDrawingInRegion(mDT, mDirtyRegion); ClearMappedBuffer(); gfxCriticalNoteOnce << "RenderCompositorSWGL failed mapping default framebuffer, no surf"; return false; } mMappedData = map.mData; mMappedStride = map.mStride; } MOZ_ASSERT(mMappedData != nullptr && mMappedStride > 0); wr_swgl_init_default_framebuffer(mContext, bounds.x, bounds.y, bounds.width, bounds.height, mMappedStride, mMappedData); LayoutDeviceIntRegion opaque; for (size_t i = 0; i < aNumOpaqueRects; i++) { const auto& rect = aOpaqueRects[i]; opaque.OrWith(LayoutDeviceIntRect(rect.min.x, rect.min.y, rect.width(), rect.height())); } LayoutDeviceIntRegion clear = mWidget->GetTransparentRegion(); clear.AndWith(mDirtyRegion); clear.SubOut(opaque); for (auto iter = clear.RectIter(); !iter.Done(); iter.Next()) { const auto& rect = iter.Get(); wr_swgl_clear_color_rect(mContext, 0, rect.x, rect.y, rect.width, rect.height, 0, 0, 0, 0); } return true; } void RenderCompositorSWGL::StartCompositing( wr::ColorF aClearColor, const wr::DeviceIntRect* aDirtyRects, size_t aNumDirtyRects, const wr::DeviceIntRect* aOpaqueRects, size_t aNumOpaqueRects) { if (mDT) { // Cancel any existing buffers that might accidentally be left from updates CommitMappedBuffer(false); // Reset the region to the widget bounds mDirtyRegion = LayoutDeviceIntRect(LayoutDeviceIntPoint(), GetBufferSize()); } if (aNumDirtyRects) { // Install the dirty rects into the bounds of the existing region auto bounds = mDirtyRegion.GetBounds(); mDirtyRegion.SetEmpty(); for (size_t i = 0; i < aNumDirtyRects; i++) { const auto& rect = aDirtyRects[i]; mDirtyRegion.OrWith(LayoutDeviceIntRect(rect.min.x, rect.min.y, rect.width(), rect.height())); } // Ensure the region lies within the widget bounds mDirtyRegion.AndWith(bounds); } // Now that the dirty rects have been supplied and the composition region // is known, allocate and install a framebuffer encompassing the composition // region. if (mDirtyRegion.IsEmpty() || !AllocateMappedBuffer(aOpaqueRects, aNumOpaqueRects)) { // If allocation of the mapped default framebuffer failed, then just install // a temporary framebuffer (with a minimum size of 2x2) so compositing can // still proceed. auto bounds = mDirtyRegion.GetBounds(); bounds.width = std::max(bounds.width, 2); bounds.height = std::max(bounds.height, 2); wr_swgl_init_default_framebuffer(mContext, bounds.x, bounds.y, bounds.width, bounds.height, 0, nullptr); } } void RenderCompositorSWGL::CommitMappedBuffer(bool aDirty) { if (!mDT) { mDirtyRegion.SetEmpty(); return; } // Force any delayed clears to resolve. if (aDirty) { wr_swgl_resolve_framebuffer(mContext, 0); } // Clear out the old framebuffer in case something tries to access it after // the frame. wr_swgl_init_default_framebuffer(mContext, 0, 0, 0, 0, 0, nullptr); // If we have a draw target at this point, mapping must have succeeded. MOZ_ASSERT(mMappedData != nullptr); if (mSurface) { // If we're using a data surface, unmap it and draw it to the DT if there // are any supplied dirty rects. mSurface->Unmap(); if (aDirty) { // The temporary source surface is always a partial region of the widget // that is offset from the origin to the actual bounds of the dirty // region. The destination DT may also be an offset partial region, but we // must check to see if its size matches the region bounds to verify this. LayoutDeviceIntRect bounds = mDirtyRegion.GetBounds(); gfx::IntPoint srcOffset = bounds.TopLeft().ToUnknownPoint(); gfx::IntPoint dstOffset = mDT->GetSize() == bounds.Size().ToUnknownSize() ? srcOffset : gfx::IntPoint(0, 0); for (auto iter = mDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { gfx::IntRect dirtyRect = iter.Get().ToUnknownRect(); mDT->CopySurface(mSurface, dirtyRect - srcOffset, dirtyRect.TopLeft() - dstOffset); } } } else { // Otherwise, we had locked the DT directly. Just release the data. mDT->ReleaseBits(mMappedData); } mDT->Flush(); // Done with the DT. Hand it back to the widget and clear out any trace of it. mWidget->EndRemoteDrawingInRegion(mDT, mDirtyRegion); mDirtyRegion.SetEmpty(); ClearMappedBuffer(); } void RenderCompositorSWGL::CancelFrame() { CommitMappedBuffer(false); mRenderWidgetSize = Nothing(); } RenderedFrameId RenderCompositorSWGL::EndFrame( const nsTArray& aDirtyRects) { // Dirty rects have already been set inside StartCompositing. We need to keep // those dirty rects exactly the same here so we supply the same exact region // to EndRemoteDrawingInRegion as for StartRemoteDrawingInRegion. RenderedFrameId frameId = GetNextRenderFrameId(); CommitMappedBuffer(); mRenderWidgetSize = Nothing(); return frameId; } bool RenderCompositorSWGL::RequestFullRender() { #ifdef MOZ_WIDGET_ANDROID // XXX Add partial present support. return true; #endif #ifdef MOZ_WAYLAND // We're requested to do full render after Resume() on Wayland. if (mRequestFullRender) { mRequestFullRender = false; return true; } #endif return false; } void RenderCompositorSWGL::Pause() {} bool RenderCompositorSWGL::Resume() { #ifdef MOZ_WAYLAND mRequestFullRender = true; #endif return true; } LayoutDeviceIntSize RenderCompositorSWGL::GetBufferSize() { // If we're between BeginFrame() and EndFrame()/CancelFrame() calls // return recent rendering size instead of actual underlying widget // size. It prevents possible rendering artifacts if widget size was changed. return mRenderWidgetSize ? mRenderWidgetSize.value() : mWidget->GetClientSize(); } void RenderCompositorSWGL::GetCompositorCapabilities( CompositorCapabilities* aCaps) { // Always support a single update rect for SwCompositor aCaps->max_update_rects = 1; // On uncomposited desktops such as X11 without compositor or Window 7 with // Aero disabled we need to force a full redraw when the window contents may // be damaged. #ifdef MOZ_WIDGET_GTK aCaps->redraw_on_invalidation = widget::GdkIsX11Display(); #else aCaps->redraw_on_invalidation = true; #endif } } // namespace wr } // namespace mozilla