/* * Copyright (C) 2013-2018 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 "MachPort.h" #import "PlatformCALayerRemote.h" #import "RemoteLayerBackingStoreCollection.h" #import "RemoteLayerTreeContext.h" #import "ShareableBitmap.h" #import "WebCoreArgumentCoders.h" #import #import #import #import #import #import #if HAVE(IOSURFACE) #import #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(WebCore::FloatSize size, float scale, bool acceleratesDrawing, bool deepColor, bool isOpaque) { #if !HAVE(IOSURFACE) acceleratesDrawing = false; #endif if (m_size == size && m_scale == scale && m_deepColor == deepColor && m_acceleratesDrawing == acceleratesDrawing && m_isOpaque == isOpaque) return; m_size = size; m_scale = scale; m_acceleratesDrawing = acceleratesDrawing; m_deepColor = deepColor; m_isOpaque = isOpaque; 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(); #if HAVE(IOSURFACE) m_secondaryBackBuffer.discard(); #endif } void RemoteLayerBackingStore::encode(IPC::Encoder& encoder) const { encoder << m_size; encoder << m_scale; encoder << m_acceleratesDrawing; encoder << m_isOpaque; #if HAVE(IOSURFACE) if (m_acceleratesDrawing) { if (m_frontBuffer.surface) encoder << m_frontBuffer.surface->createSendRight(); else encoder << WTF::MachSendRight(); return; } #endif ASSERT(!m_acceleratesDrawing); ShareableBitmap::Handle handle; m_frontBuffer.bitmap->createHandle(handle); encoder << handle; } bool RemoteLayerBackingStore::decode(IPC::Decoder& decoder, RemoteLayerBackingStore& result) { if (!decoder.decode(result.m_size)) return false; if (!decoder.decode(result.m_scale)) return false; if (!decoder.decode(result.m_acceleratesDrawing)) return false; if (!decoder.decode(result.m_isOpaque)) return false; #if HAVE(IOSURFACE) if (result.m_acceleratesDrawing) { MachSendRight sendRight; if (!decoder.decode(sendRight)) return false; result.m_frontBufferSendRight = WTFMove(sendRight); return true; } #endif ASSERT(!result.m_acceleratesDrawing); ShareableBitmap::Handle handle; if (!decoder.decode(handle)) return false; result.m_frontBuffer.bitmap = ShareableBitmap::create(handle); 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::IntSize RemoteLayerBackingStore::backingStoreSize() const { WebCore::FloatSize scaledSize = m_size; scaledSize.scale(m_scale); return roundedIntSize(scaledSize); } unsigned RemoteLayerBackingStore::bytesPerPixel() const { #if HAVE(IOSURFACE) switch (surfaceBufferFormat()) { case WebCore::IOSurface::Format::RGBA: return 4; case WebCore::IOSurface::Format::YUV422: return 2; #if HAVE(IOSURFACE_RGB10) case WebCore::IOSurface::Format::RGB10: return 4; case WebCore::IOSurface::Format::RGB10A8: return 5; #endif } #endif return 4; } void RemoteLayerBackingStore::swapToValidFrontBuffer() { WebCore::IntSize expandedScaledSize = backingStoreSize(); #if HAVE(IOSURFACE) if (m_acceleratesDrawing) { if (!m_backBuffer.surface || m_backBuffer.surface->isInUse()) { std::swap(m_backBuffer, m_secondaryBackBuffer); if (m_backBuffer.surface && m_backBuffer.surface->isInUse()) m_backBuffer.discard(); } std::swap(m_frontBuffer, m_backBuffer); if (!m_frontBuffer.surface) m_frontBuffer.surface = WebCore::IOSurface::create(expandedScaledSize, WebCore::sRGBColorSpaceRef(), surfaceBufferFormat()); setBufferVolatility(BufferType::Front, false); return; } #endif ASSERT(!m_acceleratesDrawing); std::swap(m_frontBuffer, m_backBuffer); if (!m_frontBuffer.bitmap) { ShareableBitmap::Configuration bitmapConfiguration; bitmapConfiguration.isOpaque = m_isOpaque; m_frontBuffer.bitmap = ShareableBitmap::createShareable(expandedScaledSize, bitmapConfiguration); } } bool RemoteLayerBackingStore::display() { ASSERT(!m_frontContextPendingFlush); 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); WebCore::IntSize expandedScaledSize = backingStoreSize(); if (m_dirtyRegion.isEmpty() || expandedScaledSize.isEmpty()) return needToEncodeBackingStore; WebCore::IntRect layerBounds(WebCore::IntPoint(), WebCore::expandedIntSize(m_size)); if (!hasFrontBuffer()) m_dirtyRegion.unite(layerBounds); if (m_layer->owner()->platformCALayerShowRepaintCounter(m_layer)) { WebCore::IntRect indicatorRect(0, 0, 52, 27); m_dirtyRegion.unite(indicatorRect); } WebCore::IntRect expandedScaledLayerBounds(WebCore::IntPoint(), expandedScaledSize); bool willPaintEntireBackingStore = m_dirtyRegion.contains(layerBounds); swapToValidFrontBuffer(); #if HAVE(IOSURFACE) if (m_acceleratesDrawing) { RetainPtr backImage; if (m_backBuffer.surface && !willPaintEntireBackingStore) backImage = m_backBuffer.surface->createImage(); if (m_frontBuffer.surface) { WebCore::GraphicsContext& context = m_frontBuffer.surface->ensureGraphicsContext(); context.scale(WebCore::FloatSize(1, -1)); context.translate(0, -expandedScaledSize.height()); drawInContext(context, backImage.get()); m_frontBuffer.surface->releaseGraphicsContext(); } } else #endif { ASSERT(!m_acceleratesDrawing); std::unique_ptr context = m_frontBuffer.bitmap->createGraphicsContext(); RetainPtr backImage; if (m_backBuffer.bitmap && !willPaintEntireBackingStore) backImage = m_backBuffer.bitmap->makeCGImage(); drawInContext(*context, backImage.get()); } m_layer->owner()->platformCALayerLayerDidDisplay(m_layer); return true; } void RemoteLayerBackingStore::drawInContext(WebCore::GraphicsContext& context, CGImageRef backImage) { WebCore::FloatSize scaledSize = m_size; scaledSize.scale(m_scale); WebCore::IntRect scaledLayerBounds(WebCore::IntPoint(), WebCore::roundedIntSize(scaledSize)); // 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(); Vector 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); } CGRect cgPaintingRects[WebCore::PlatformCALayer::webLayerMaxRectsToPaint]; for (size_t i = 0, dirtyRectCount = m_paintingRects.size(); i < dirtyRectCount; ++i) { WebCore::FloatRect scaledPaintingRect = m_paintingRects[i]; scaledPaintingRect.scale(m_scale); cgPaintingRects[i] = scaledPaintingRect; } CGContextRef cgContext = context.platformContext(); if (backImage) { WebCore::CGContextStateSaver stateSaver(cgContext); CGContextSetBlendMode(cgContext, kCGBlendModeCopy); CGContextTranslateCTM(cgContext, 0, scaledLayerBounds.height()); CGContextScaleCTM(cgContext, 1, -1); CGContextDrawImage(cgContext, scaledLayerBounds, backImage); } CGContextClipToRects(cgContext, cgPaintingRects, m_paintingRects.size()); if (!m_isOpaque) context.clearRect(scaledLayerBounds); #ifndef NDEBUG if (m_isOpaque) context.fillRect(scaledLayerBounds, WebCore::Color(255, 47, 146)); #endif context.scale(m_scale); auto flags = m_layer->context() && m_layer->context()->nextFlushIsForImmediatePaint() ? 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(cgContext, 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(cgContext, 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::LayerTypeScrollingLayer: case WebCore::PlatformCALayer::LayerTypeEditableImageLayer: case WebCore::PlatformCALayer::LayerTypeCustom: ASSERT_NOT_REACHED(); break; }; m_dirtyRegion = WebCore::Region(); m_paintingRects.clear(); m_frontContextPendingFlush = context.platformContext(); } void RemoteLayerBackingStore::enumerateRectsBeingDrawn(CGContextRef context, void (^block)(CGRect)) { CGAffineTransform inverseTransform = CGAffineTransformInvert(CGContextGetCTM(context)); // 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) { layer.contentsOpaque = m_isOpaque; #if HAVE(IOSURFACE) if (acceleratesDrawing()) { switch (contentsType) { case LayerContentsType::IOSurface: if (!m_frontBuffer.surface) { ASSERT(m_frontBufferSendRight); m_frontBuffer.surface = WebCore::IOSurface::createFromSendRight(WTFMove(m_frontBufferSendRight), WebCore::sRGBColorSpaceRef()); } layer.contents = m_frontBuffer.surface ? m_frontBuffer.surface->asLayerContents() : nil; break; case LayerContentsType::CAMachPort: ASSERT(m_frontBufferSendRight); layer.contents = (__bridge id)adoptCF(CAMachPortCreate(m_frontBufferSendRight.leakSendRight())).get(); break; } return; } #endif ASSERT(!acceleratesDrawing()); layer.contents = (__bridge id)m_frontBuffer.bitmap->makeCGImageCopy().get(); } RetainPtr RemoteLayerBackingStore::takeFrontContextPendingFlush() { return WTFMove(m_frontContextPendingFlush); } #if HAVE(IOSURFACE) bool RemoteLayerBackingStore::setBufferVolatility(BufferType type, bool isVolatile) { // Return value is true if we succeeded in making volatile. auto makeVolatile = [] (Buffer& buffer) -> bool { if (!buffer.surface || buffer.isVolatile) return true; buffer.surface->releaseGraphicsContext(); if (!buffer.surface->isInUse()) { buffer.surface->setIsVolatile(true); buffer.isVolatile = true; return true; } return false; }; // Return value is true if we need to repaint. auto makeNonVolatile = [] (Buffer& buffer) -> bool { if (!buffer.surface || !buffer.isVolatile) return false; auto previousState = buffer.surface->setIsVolatile(false); buffer.isVolatile = false; return previousState == WebCore::IOSurface::SurfaceState::Empty; }; switch (type) { 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; } #else bool RemoteLayerBackingStore::setBufferVolatility(BufferType, bool) { return true; } #endif void RemoteLayerBackingStore::Buffer::discard() { #if HAVE(IOSURFACE) if (surface) WebCore::IOSurface::moveToPool(WTFMove(surface)); isVolatile = false; #endif bitmap = nullptr; } #if HAVE(IOSURFACE) WebCore::IOSurface::Format RemoteLayerBackingStore::surfaceBufferFormat() const { #if HAVE(IOSURFACE_RGB10) if (m_deepColor) return m_isOpaque ? WebCore::IOSurface::Format::RGB10 : WebCore::IOSurface::Format::RGB10A8; #endif return WebCore::IOSurface::Format::RGBA; } #endif // HAVE(IOSURFACE) } // namespace WebKit