blob: ca466bacd885393fc96abc19f67db7a16afb474a [file] [log] [blame]
/*
* Copyright (C) 2013-2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "RemoteLayerBackingStore.h"
#import "ArgumentCoders.h"
#import "CGDisplayListImageBufferBackend.h"
#import "MachPort.h"
#import "PlatformCALayerRemote.h"
#import "PlatformRemoteImageBufferProxy.h"
#import "RemoteLayerBackingStoreCollection.h"
#import "RemoteLayerTreeContext.h"
#import "RemoteLayerTreeLayers.h"
#import "ShareableBitmap.h"
#import "WebCoreArgumentCoders.h"
#import "WebProcess.h"
#import <QuartzCore/QuartzCore.h>
#import <WebCore/BifurcatedGraphicsContext.h>
#import <WebCore/GraphicsContextCG.h>
#import <WebCore/IOSurface.h>
#import <WebCore/ImageBuffer.h>
#import <WebCore/Model.h>
#import <WebCore/PlatformCALayerClient.h>
#import <WebCore/WebLayer.h>
#import <mach/mach_port.h>
#import <pal/spi/cocoa/QuartzCoreSPI.h>
#import <wtf/cocoa/TypeCastsCocoa.h>
#if ENABLE(CG_DISPLAY_LIST_BACKED_IMAGE_BUFFER)
#import <WebKitAdditions/CGDisplayListImageBufferAdditions.h>
#endif
namespace WebKit {
RemoteLayerBackingStore::RemoteLayerBackingStore(PlatformCALayerRemote* layer)
: m_layer(layer)
, m_isOpaque(false)
, m_lastDisplayTime(-MonotonicTime::infinity())
{
if (!m_layer)
return;
if (RemoteLayerTreeContext* context = m_layer->context())
context->backingStoreWasCreated(*this);
}
RemoteLayerBackingStore::~RemoteLayerBackingStore()
{
clearBackingStore();
if (!m_layer)
return;
if (RemoteLayerTreeContext* context = m_layer->context())
context->backingStoreWillBeDestroyed(*this);
}
void RemoteLayerBackingStore::ensureBackingStore(Type type, WebCore::FloatSize size, float scale, bool deepColor, bool isOpaque, IncludeDisplayList includeDisplayList)
{
if (m_type == type && m_size == size && m_scale == scale && m_deepColor == deepColor && m_isOpaque == isOpaque && m_includeDisplayList == includeDisplayList)
return;
m_type = type;
m_size = size;
m_scale = scale;
m_deepColor = deepColor;
m_isOpaque = isOpaque;
m_includeDisplayList = includeDisplayList;
if (m_frontBuffer) {
// If we have a valid backing store, we need to ensure that it gets completely
// repainted the next time display() is called.
setNeedsDisplay();
}
clearBackingStore();
}
void RemoteLayerBackingStore::clearBackingStore()
{
m_frontBuffer.discard();
m_backBuffer.discard();
m_secondaryBackBuffer.discard();
}
void RemoteLayerBackingStore::encode(IPC::Encoder& encoder) const
{
encoder << m_type;
encoder << m_size;
encoder << m_scale;
encoder << m_isOpaque;
encoder << m_includeDisplayList;
std::optional<ImageBufferBackendHandle> handle;
if (m_frontBuffer.imageBuffer) {
switch (m_type) {
case Type::IOSurface:
if (m_frontBuffer.imageBuffer->canMapBackingStore())
handle = static_cast<AcceleratedImageBufferShareableMappedBackend&>(*m_frontBuffer.imageBuffer->ensureBackendCreated()).createImageBufferBackendHandle();
else
handle = static_cast<AcceleratedImageBufferShareableBackend&>(*m_frontBuffer.imageBuffer->ensureBackendCreated()).createImageBufferBackendHandle();
break;
case Type::Bitmap:
handle = static_cast<UnacceleratedImageBufferShareableBackend&>(*m_frontBuffer.imageBuffer->ensureBackendCreated()).createImageBufferBackendHandle();
break;
}
}
encoder << handle;
#if ENABLE(CG_DISPLAY_LIST_BACKED_IMAGE_BUFFER)
std::optional<ImageBufferBackendHandle> displayListHandle;
if (m_frontBuffer.displayListImageBuffer)
displayListHandle = static_cast<CGDisplayListImageBufferBackend&>(*m_frontBuffer.displayListImageBuffer->ensureBackendCreated()).createImageBufferBackendHandle();
encoder << displayListHandle;
#endif
}
bool RemoteLayerBackingStore::decode(IPC::Decoder& decoder, RemoteLayerBackingStore& result)
{
if (!decoder.decode(result.m_type))
return false;
if (!decoder.decode(result.m_size))
return false;
if (!decoder.decode(result.m_scale))
return false;
if (!decoder.decode(result.m_isOpaque))
return false;
if (!decoder.decode(result.m_includeDisplayList))
return false;
if (!decoder.decode(result.m_bufferHandle))
return false;
#if ENABLE(CG_DISPLAY_LIST_BACKED_IMAGE_BUFFER)
if (!decoder.decode(result.m_displayListBufferHandle))
return false;
#endif
return true;
}
void RemoteLayerBackingStore::setNeedsDisplay(const WebCore::IntRect rect)
{
m_dirtyRegion.unite(rect);
}
void RemoteLayerBackingStore::setNeedsDisplay()
{
setNeedsDisplay(WebCore::IntRect(WebCore::IntPoint(), WebCore::expandedIntSize(m_size)));
}
WebCore::PixelFormat RemoteLayerBackingStore::pixelFormat() const
{
#if HAVE(IOSURFACE_RGB10)
if (m_type == Type::IOSurface && m_deepColor)
return m_isOpaque ? WebCore::PixelFormat::RGB10 : WebCore::PixelFormat::RGB10A8;
#endif
return WebCore::PixelFormat::BGRA8;
}
unsigned RemoteLayerBackingStore::bytesPerPixel() const
{
switch (pixelFormat()) {
case WebCore::PixelFormat::RGBA8: return 4;
case WebCore::PixelFormat::BGRA8: return 4;
case WebCore::PixelFormat::RGB10: return 4;
case WebCore::PixelFormat::RGB10A8: return 5;
}
return 4;
}
void RemoteLayerBackingStore::swapToValidFrontBuffer()
{
// Sometimes, we can get two swaps ahead of the render server.
// If we're using shared IOSurfaces, we must wait to modify
// a surface until it no longer has outstanding clients.
if (m_type == Type::IOSurface) {
if (!m_backBuffer.imageBuffer || m_backBuffer.imageBuffer->isInUse()) {
std::swap(m_backBuffer, m_secondaryBackBuffer);
// When pulling the secondary back buffer out of hibernation (to become
// the new front buffer), if it is somehow still in use (e.g. we got
// three swaps ahead of the render server), just give up and discard it.
if (m_backBuffer.imageBuffer && m_backBuffer.imageBuffer->isInUse())
m_backBuffer.discard();
}
}
std::swap(m_frontBuffer, m_backBuffer);
if (m_frontBuffer.imageBuffer) {
setBufferVolatility(BufferType::Front, false);
return;
}
bool shouldUseRemoteRendering = WebProcess::singleton().shouldUseRemoteRenderingFor(WebCore::RenderingPurpose::DOM);
switch (m_type) {
case Type::IOSurface:
if (shouldUseRemoteRendering)
m_frontBuffer.imageBuffer = m_layer->context()->ensureRemoteRenderingBackendProxy().createImageBuffer(m_size, WebCore::RenderingMode::Accelerated, m_scale, WebCore::DestinationColorSpace::SRGB(), pixelFormat());
else
m_frontBuffer.imageBuffer = WebCore::ConcreteImageBuffer<AcceleratedImageBufferShareableMappedBackend>::create(m_size, m_scale, WebCore::DestinationColorSpace::SRGB(), pixelFormat(), nullptr);
break;
case Type::Bitmap:
if (shouldUseRemoteRendering)
m_frontBuffer.imageBuffer = m_layer->context()->ensureRemoteRenderingBackendProxy().createImageBuffer(m_size, WebCore::RenderingMode::Unaccelerated, m_scale, WebCore::DestinationColorSpace::SRGB(), pixelFormat());
else
m_frontBuffer.imageBuffer = WebCore::ConcreteImageBuffer<UnacceleratedImageBufferShareableBackend>::create(m_size, m_scale, WebCore::DestinationColorSpace::SRGB(), pixelFormat(), nullptr);
break;
}
#if ENABLE(CG_DISPLAY_LIST_BACKED_IMAGE_BUFFER)
if (m_includeDisplayList == IncludeDisplayList::Yes)
m_frontBuffer.displayListImageBuffer = WebCore::ConcreteImageBuffer<CGDisplayListImageBufferBackend>::create(m_size, m_scale, WebCore::DestinationColorSpace::SRGB(), pixelFormat(), nullptr);
#endif
}
bool RemoteLayerBackingStore::supportsPartialRepaint()
{
// FIXME: Find a way to support partial repaint for backing store that
// includes a display list without allowing unbounded memory growth.
return m_includeDisplayList == IncludeDisplayList::No;
}
bool RemoteLayerBackingStore::display()
{
ASSERT(!m_frontBufferFlushers.size());
m_lastDisplayTime = MonotonicTime::now();
bool needToEncodeBackingStore = false;
if (RemoteLayerTreeContext* context = m_layer->context())
needToEncodeBackingStore = context->backingStoreWillBeDisplayed(*this);
// Make the previous front buffer non-volatile early, so that we can dirty the whole layer if it comes back empty.
setBufferVolatility(BufferType::Front, false);
if (m_dirtyRegion.isEmpty() || m_size.isEmpty())
return needToEncodeBackingStore;
WebCore::IntRect layerBounds(WebCore::IntPoint(), WebCore::expandedIntSize(m_size));
if (!hasFrontBuffer() || !supportsPartialRepaint())
m_dirtyRegion.unite(layerBounds);
if (m_layer->owner()->platformCALayerShowRepaintCounter(m_layer)) {
WebCore::IntRect indicatorRect(0, 0, 52, 27);
m_dirtyRegion.unite(indicatorRect);
}
swapToValidFrontBuffer();
if (!m_frontBuffer.imageBuffer)
return true;
if (m_includeDisplayList == IncludeDisplayList::Yes) {
#if ENABLE(CG_DISPLAY_LIST_BACKED_IMAGE_BUFFER)
auto& displayListContext = m_frontBuffer.displayListImageBuffer->context();
// FIXME: Remove this when <rdar://problem/80487697> is fixed.
static std::optional<bool> needsMissingFlipWorkaround;
WebCore::GraphicsContextStateSaver workaroundStateSaver(displayListContext, false);
if (!needsMissingFlipWorkaround) {
id defaultValue = [[NSUserDefaults standardUserDefaults] objectForKey:@"WebKitNeedsWorkaroundFor80487697"];
needsMissingFlipWorkaround = defaultValue ? [defaultValue boolValue] : true;
}
if (needsMissingFlipWorkaround.value()) {
workaroundStateSaver.save();
displayListContext.scale(WebCore::FloatSize(1, -1));
displayListContext.translate(0, -m_size.height());
}
WebCore::BifurcatedGraphicsContext context(m_frontBuffer.imageBuffer->context(), displayListContext);
#else
WebCore::GraphicsContext& context = m_frontBuffer.imageBuffer->context();
#endif
drawInContext(context);
} else {
WebCore::GraphicsContext& context = m_frontBuffer.imageBuffer->context();
drawInContext(context);
}
m_dirtyRegion = WebCore::Region();
m_paintingRects.clear();
m_layer->owner()->platformCALayerLayerDidDisplay(m_layer);
m_frontBuffer.imageBuffer->flushDrawingContextAsync();
m_frontBufferFlushers.append(m_frontBuffer.imageBuffer->createFlusher());
#if ENABLE(CG_DISPLAY_LIST_BACKED_IMAGE_BUFFER)
if (m_includeDisplayList == IncludeDisplayList::Yes)
m_frontBufferFlushers.append(m_frontBuffer.displayListImageBuffer->createFlusher());
#endif
return true;
}
void RemoteLayerBackingStore::drawInContext(WebCore::GraphicsContext& context)
{
WebCore::GraphicsContextStateSaver stateSaver(context);
// If we have less than webLayerMaxRectsToPaint rects to paint and they cover less
// than webLayerWastedSpaceThreshold of the total dirty area, we'll repaint each rect separately.
// Otherwise, repaint the entire bounding box of the dirty region.
WebCore::IntRect dirtyBounds = m_dirtyRegion.bounds();
auto dirtyRects = m_dirtyRegion.rects();
if (dirtyRects.size() > WebCore::PlatformCALayer::webLayerMaxRectsToPaint || m_dirtyRegion.totalArea() > WebCore::PlatformCALayer::webLayerWastedSpaceThreshold * dirtyBounds.width() * dirtyBounds.height()) {
dirtyRects.clear();
dirtyRects.append(dirtyBounds);
}
// FIXME: find a consistent way to scale and snap dirty and CG clip rects.
for (const auto& rect : dirtyRects) {
WebCore::FloatRect scaledRect(rect);
scaledRect.scale(m_scale);
scaledRect = WebCore::enclosingIntRect(scaledRect);
scaledRect.scale(1 / m_scale);
m_paintingRects.append(scaledRect);
}
WebCore::IntRect layerBounds(WebCore::IntPoint(), WebCore::expandedIntSize(m_size));
if (!m_dirtyRegion.contains(layerBounds)) {
ASSERT(m_backBuffer.imageBuffer);
context.drawImageBuffer(*m_backBuffer.imageBuffer, { {0, 0}, m_size }, { {0, 0}, m_size }, { WebCore::CompositeOperator::Copy });
}
if (m_paintingRects.size() == 1)
context.clip(m_paintingRects[0]);
else {
WebCore::Path clipPath;
for (auto rect : m_paintingRects)
clipPath.addRect(rect);
context.clipPath(clipPath);
}
if (!m_isOpaque)
context.clearRect(layerBounds);
#ifndef NDEBUG
if (m_isOpaque)
context.fillRect(layerBounds, WebCore::SRGBA<uint8_t> { 255, 47, 146 });
#endif
// FIXME: Clarify that WebCore::GraphicsLayerPaintSnapshotting is just about image decoding.
auto flags = m_layer->context() && m_layer->context()->nextRenderingUpdateRequiresSynchronousImageDecoding() ? WebCore::GraphicsLayerPaintSnapshotting : WebCore::GraphicsLayerPaintNormal;
// FIXME: This should be moved to PlatformCALayerRemote for better layering.
switch (m_layer->layerType()) {
case WebCore::PlatformCALayer::LayerTypeSimpleLayer:
case WebCore::PlatformCALayer::LayerTypeTiledBackingTileLayer:
m_layer->owner()->platformCALayerPaintContents(m_layer, context, dirtyBounds, flags);
break;
case WebCore::PlatformCALayer::LayerTypeWebLayer:
case WebCore::PlatformCALayer::LayerTypeBackdropLayer:
WebCore::PlatformCALayer::drawLayerContents(context, m_layer, m_paintingRects, flags);
break;
case WebCore::PlatformCALayer::LayerTypeDarkSystemBackdropLayer:
case WebCore::PlatformCALayer::LayerTypeLightSystemBackdropLayer:
// FIXME: These have a more complicated layer hierarchy. We need to paint into
// a child layer in order to see the rendered results.
WebCore::PlatformCALayer::drawLayerContents(context, m_layer, m_paintingRects, flags);
break;
case WebCore::PlatformCALayer::LayerTypeLayer:
case WebCore::PlatformCALayer::LayerTypeTransformLayer:
case WebCore::PlatformCALayer::LayerTypeTiledBackingLayer:
case WebCore::PlatformCALayer::LayerTypePageTiledBackingLayer:
case WebCore::PlatformCALayer::LayerTypeRootLayer:
case WebCore::PlatformCALayer::LayerTypeAVPlayerLayer:
case WebCore::PlatformCALayer::LayerTypeContentsProvidedLayer:
case WebCore::PlatformCALayer::LayerTypeShapeLayer:
case WebCore::PlatformCALayer::LayerTypeScrollContainerLayer:
#if ENABLE(MODEL_ELEMENT)
case WebCore::PlatformCALayer::LayerTypeModelLayer:
#endif
case WebCore::PlatformCALayer::LayerTypeCustom:
ASSERT_NOT_REACHED();
break;
};
}
void RemoteLayerBackingStore::enumerateRectsBeingDrawn(WebCore::GraphicsContext& context, void (^block)(WebCore::FloatRect))
{
CGAffineTransform inverseTransform = CGAffineTransformInvert(context.getCTM());
// We don't want to un-apply the flipping or contentsScale,
// because they're not applied to repaint rects.
inverseTransform = CGAffineTransformScale(inverseTransform, m_scale, -m_scale);
inverseTransform = CGAffineTransformTranslate(inverseTransform, 0, -m_size.height());
for (const auto& rect : m_paintingRects) {
CGRect rectToDraw = CGRectApplyAffineTransform(rect, inverseTransform);
block(rectToDraw);
}
}
void RemoteLayerBackingStore::applyBackingStoreToLayer(CALayer *layer, LayerContentsType contentsType)
{
ASSERT(m_bufferHandle);
layer.contentsOpaque = m_isOpaque;
RetainPtr<id> contents;
WTF::switchOn(*m_bufferHandle,
[&] (ShareableBitmap::Handle& handle) {
ASSERT(m_type == Type::Bitmap);
contents = bridge_id_cast(ShareableBitmap::create(handle)->makeCGImageCopy());
},
[&] (MachSendRight& machSendRight) {
ASSERT(m_type == Type::IOSurface);
switch (contentsType) {
case RemoteLayerBackingStore::LayerContentsType::IOSurface: {
auto surface = WebCore::IOSurface::createFromSendRight(WTFMove(machSendRight), WebCore::DestinationColorSpace::SRGB());
contents = surface ? surface->asLayerContents() : nil;
break;
}
case RemoteLayerBackingStore::LayerContentsType::CAMachPort:
contents = bridge_id_cast(adoptCF(CAMachPortCreate(machSendRight.leakSendRight())));
break;
}
}
#if ENABLE(CG_DISPLAY_LIST_BACKED_IMAGE_BUFFER)
, [&] (IPC::SharedBufferCopy& buffer) {
ASSERT_NOT_REACHED();
}
#endif
);
#if ENABLE(CG_DISPLAY_LIST_BACKED_IMAGE_BUFFER)
if (m_displayListBufferHandle) {
ASSERT([layer isKindOfClass:[WKCompositingLayer class]]);
if (![layer isKindOfClass:[WKCompositingLayer class]])
return;
[layer setValue:@1 forKeyPath:WKCGDisplayListEnabledKey];
[layer setValue:@1 forKeyPath:WKCGDisplayListBifurcationEnabledKey];
auto data = std::get<IPC::SharedBufferCopy>(*m_displayListBufferHandle).buffer()->createCFData();
[(WKCompositingLayer *)layer _setWKContents:contents.get() withDisplayList:data.get()];
return;
}
#endif
layer.contents = contents.get();
}
Vector<std::unique_ptr<WebCore::ThreadSafeImageBufferFlusher>> RemoteLayerBackingStore::takePendingFlushers()
{
return std::exchange(m_frontBufferFlushers, { });
}
bool RemoteLayerBackingStore::setBufferVolatility(BufferType bufferType, bool isVolatile)
{
if (m_type != Type::IOSurface)
return true;
// Return value is true if we succeeded in making volatile.
auto makeVolatile = [] (Buffer& buffer) -> bool {
if (!buffer.imageBuffer || buffer.isVolatile)
return true;
buffer.imageBuffer->releaseGraphicsContext();
if (!buffer.imageBuffer->isInUse()) {
buffer.imageBuffer->setVolatile(true);
buffer.isVolatile = true;
return true;
}
return false;
};
// Return value is true if we need to repaint.
auto makeNonVolatile = [] (Buffer& buffer) -> bool {
if (!buffer.imageBuffer || !buffer.isVolatile)
return false;
auto previousState = buffer.imageBuffer->setVolatile(false);
buffer.isVolatile = false;
return previousState == WebCore::VolatilityState::Empty;
};
switch (bufferType) {
case BufferType::Front:
if (isVolatile)
return makeVolatile(m_frontBuffer);
// Becoming non-volatile and the front buffer was purged, so we need to repaint.
if (makeNonVolatile(m_frontBuffer))
setNeedsDisplay();
break;
case BufferType::Back:
if (isVolatile)
return makeVolatile(m_backBuffer);
makeNonVolatile(m_backBuffer);
break;
case BufferType::SecondaryBack:
if (isVolatile)
return makeVolatile(m_secondaryBackBuffer);
makeNonVolatile(m_secondaryBackBuffer);
break;
}
return true;
}
void RemoteLayerBackingStore::Buffer::discard()
{
isVolatile = false;
if (imageBuffer)
imageBuffer->releaseBufferToPool();
imageBuffer = nullptr;
#if ENABLE(CG_DISPLAY_LIST_BACKED_IMAGE_BUFFER)
displayListImageBuffer = nullptr;
#endif
}
} // namespace WebKit