blob: 2fd43f99ae354adad73bd85f14ff5ec0be4fd984 [file] [log] [blame]
/*
* Copyright (C) 2016-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.
*/
#include "config.h"
#include "DisplayListRecorder.h"
#include "DisplayList.h"
#include "DisplayListDrawingContext.h"
#include "DisplayListItems.h"
#include "GraphicsContext.h"
#include "ImageBuffer.h"
#include "Logging.h"
#include "MediaPlayer.h"
#include "NotImplemented.h"
#include <wtf/MathExtras.h>
#include <wtf/text/TextStream.h>
namespace WebCore {
namespace DisplayList {
Recorder::Recorder(const GraphicsContextState& state, const FloatRect& initialClip, const AffineTransform& initialCTM, DrawGlyphsRecorder::DeconstructDrawGlyphs deconstructDrawGlyphs)
: m_drawGlyphsRecorder(*this, deconstructDrawGlyphs)
{
m_stateStack.append({ state, initialCTM, initialCTM.mapRect(initialClip) });
}
Recorder::Recorder(Recorder& parent, const GraphicsContextState& state, const FloatRect& initialClip, const AffineTransform& initialCTM)
: m_drawGlyphsRecorder(*this, parent.m_drawGlyphsRecorder.deconstructDrawGlyphs())
{
m_stateStack.append({ state, initialCTM, initialCTM.mapRect(initialClip) });
}
Recorder::~Recorder()
{
ASSERT(m_stateStack.size() == 1); // If this fires, it indicates mismatched save/restore.
}
static bool containsOnlyInlineStateChanges(const GraphicsContextStateChange& changes, GraphicsContextState::StateChangeFlags changeFlags)
{
static constexpr GraphicsContextState::StateChangeFlags inlineStateChangeFlags {
GraphicsContextState::StrokeThicknessChange,
GraphicsContextState::StrokeColorChange,
GraphicsContextState::FillColorChange,
};
if (changeFlags != (changeFlags & inlineStateChangeFlags))
return false;
if (changeFlags.contains(GraphicsContextState::StrokeColorChange) && !changes.m_state.strokeColor.tryGetAsSRGBABytes())
return false;
if (changeFlags.contains(GraphicsContextState::FillColorChange) && !changes.m_state.fillColor.tryGetAsSRGBABytes())
return false;
return true;
}
void Recorder::appendStateChangeItem(const GraphicsContextStateChange& changes, GraphicsContextState::StateChangeFlags changeFlags)
{
if (!containsOnlyInlineStateChanges(changes, changeFlags)) {
if (auto pattern = changes.m_state.strokePattern)
recordResourceUse(pattern->tileImage());
if (auto pattern = changes.m_state.fillPattern)
recordResourceUse(pattern->tileImage());
recordSetState(changes.m_state, changeFlags);
return;
}
if (changeFlags.contains(GraphicsContextState::StrokeColorChange))
recordSetInlineStrokeColor(*changes.m_state.strokeColor.tryGetAsSRGBABytes());
if (changeFlags.contains(GraphicsContextState::StrokeThicknessChange))
recordSetStrokeThickness(changes.m_state.strokeThickness);
if (changeFlags.contains(GraphicsContextState::FillColorChange))
recordSetInlineFillColor(*changes.m_state.fillColor.tryGetAsSRGBABytes());
}
void Recorder::appendStateChangeItemIfNecessary()
{
// FIXME: This is currently invoked in an ad-hoc manner when recording drawing items. We should consider either
// splitting GraphicsContext state changes into individual display list items, or refactoring the code such that
// this method is automatically invoked when recording a drawing item.
auto& stateChanges = currentState().stateChange;
auto changesFromLastState = stateChanges.changesFromState(currentState().lastDrawingState);
if (!changesFromLastState)
return;
LOG_WITH_STREAM(DisplayLists, stream << "pre-drawing, saving state " << GraphicsContextStateChange(stateChanges.m_state, changesFromLastState));
appendStateChangeItem(stateChanges, changesFromLastState);
stateChanges.m_changeFlags = { };
currentState().lastDrawingState = stateChanges.m_state;
}
const GraphicsContextState& Recorder::state() const
{
return currentState().stateChange.m_state;
}
void Recorder::didUpdateState(const GraphicsContextState& state, GraphicsContextState::StateChangeFlags flags)
{
currentState().stateChange.accumulate(state, flags);
}
void Recorder::setLineCap(LineCap lineCap)
{
recordSetLineCap(lineCap);
}
void Recorder::setLineDash(const DashArray& dashArray, float dashOffset)
{
recordSetLineDash(dashArray, dashOffset);
}
void Recorder::setLineJoin(LineJoin lineJoin)
{
recordSetLineJoin(lineJoin);
}
void Recorder::setMiterLimit(float miterLimit)
{
recordSetMiterLimit(miterLimit);
}
void Recorder::drawGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
{
m_drawGlyphsRecorder.drawGlyphs(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
}
void Recorder::drawGlyphsAndCacheFont(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned count, const FloatPoint& localAnchor, FontSmoothingMode smoothingMode)
{
appendStateChangeItemIfNecessary();
recordResourceUse(const_cast<Font&>(font));
recordDrawGlyphs(font, glyphs, advances, count, localAnchor, smoothingMode);
}
void Recorder::drawImageBuffer(ImageBuffer& imageBuffer, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options)
{
appendStateChangeItemIfNecessary();
if (!canDrawImageBuffer(imageBuffer)) {
GraphicsContext::drawImageBuffer(imageBuffer, destRect, srcRect, options);
return;
}
recordResourceUse(imageBuffer);
recordDrawImageBuffer(imageBuffer.renderingResourceIdentifier(), destRect, srcRect, options);
}
void Recorder::drawNativeImage(NativeImage& image, const FloatSize& imageSize, const FloatRect& destRect, const FloatRect& srcRect, const ImagePaintingOptions& options)
{
appendStateChangeItemIfNecessary();
recordResourceUse(image);
recordDrawNativeImage(image.renderingResourceIdentifier(), imageSize, destRect, srcRect, options);
}
void Recorder::drawPattern(NativeImage& image, const FloatSize& imageSize, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, const ImagePaintingOptions& options)
{
appendStateChangeItemIfNecessary();
recordResourceUse(image);
recordDrawPattern(image.renderingResourceIdentifier(), imageSize, destRect, tileRect, patternTransform, phase, spacing, options);
}
void Recorder::save()
{
recordSave();
m_stateStack.append(m_stateStack.last().cloneForSave());
}
void Recorder::restore()
{
if (!m_stateStack.size())
return;
m_stateStack.removeLast();
recordRestore();
}
void Recorder::translate(float x, float y)
{
currentState().translate(x, y);
recordTranslate(x, y);
}
void Recorder::rotate(float angleInRadians)
{
currentState().rotate(angleInRadians);
recordRotate(angleInRadians);
}
void Recorder::scale(const FloatSize& size)
{
currentState().scale(size);
recordScale(size);
}
void Recorder::concatCTM(const AffineTransform& transform)
{
if (transform.isIdentity())
return;
currentState().concatCTM(transform);
recordConcatenateCTM(transform);
}
void Recorder::setCTM(const AffineTransform& transform)
{
currentState().setCTM(transform);
recordSetCTM(transform);
}
AffineTransform Recorder::getCTM(GraphicsContext::IncludeDeviceScale) const
{
return currentState().ctm;
}
void Recorder::beginTransparencyLayer(float opacity)
{
appendStateChangeItemIfNecessary();
recordBeginTransparencyLayer(opacity);
m_stateStack.append(m_stateStack.last().cloneForTransparencyLayer());
}
void Recorder::endTransparencyLayer()
{
appendStateChangeItemIfNecessary();
recordEndTransparencyLayer();
m_stateStack.removeLast();
}
void Recorder::drawRect(const FloatRect& rect, float borderThickness)
{
appendStateChangeItemIfNecessary();
recordDrawRect(rect, borderThickness);
}
void Recorder::drawLine(const FloatPoint& point1, const FloatPoint& point2)
{
appendStateChangeItemIfNecessary();
recordDrawLine(point1, point2);
}
void Recorder::drawLinesForText(const FloatPoint& point, float thickness, const DashArray& widths, bool printing, bool doubleLines, StrokeStyle)
{
appendStateChangeItemIfNecessary();
recordDrawLinesForText(FloatPoint(), toFloatSize(point), thickness, widths, printing, doubleLines);
}
void Recorder::drawDotsForDocumentMarker(const FloatRect& rect, DocumentMarkerLineStyle style)
{
appendStateChangeItemIfNecessary();
recordDrawDotsForDocumentMarker(rect, style);
}
void Recorder::drawEllipse(const FloatRect& rect)
{
appendStateChangeItemIfNecessary();
recordDrawEllipse(rect);
}
void Recorder::drawPath(const Path& path)
{
appendStateChangeItemIfNecessary();
recordDrawPath(path);
}
void Recorder::drawFocusRing(const Path& path, float width, float offset, const Color& color)
{
appendStateChangeItemIfNecessary();
recordDrawFocusRingPath(path, width, offset, color);
}
void Recorder::drawFocusRing(const Vector<FloatRect>& rects, float width, float offset, const Color& color)
{
appendStateChangeItemIfNecessary();
recordDrawFocusRingRects(rects, width, offset, color);
}
#if PLATFORM(MAC)
void Recorder::drawFocusRing(const Path&, double, bool&, const Color&)
{
notImplemented();
}
void Recorder::drawFocusRing(const Vector<FloatRect>&, double, bool&, const Color&)
{
notImplemented();
}
#endif
void Recorder::fillRect(const FloatRect& rect)
{
appendStateChangeItemIfNecessary();
recordFillRect(rect);
}
void Recorder::fillRect(const FloatRect& rect, const Color& color)
{
appendStateChangeItemIfNecessary();
recordFillRectWithColor(rect, color);
}
void Recorder::fillRect(const FloatRect& rect, Gradient& gradient)
{
appendStateChangeItemIfNecessary();
recordFillRectWithGradient(rect, gradient);
}
void Recorder::fillRect(const FloatRect& rect, const Color& color, CompositeOperator op, BlendMode blendMode)
{
appendStateChangeItemIfNecessary();
recordFillCompositedRect(rect, color, op, blendMode);
}
void Recorder::fillRoundedRect(const FloatRoundedRect& rect, const Color& color, BlendMode blendMode)
{
appendStateChangeItemIfNecessary();
recordFillRoundedRect(rect, color, blendMode);
}
void Recorder::fillRectWithRoundedHole(const FloatRect& rect, const FloatRoundedRect& roundedHoleRect, const Color& color)
{
appendStateChangeItemIfNecessary();
recordFillRectWithRoundedHole(rect, roundedHoleRect, color);
}
void Recorder::fillPath(const Path& path)
{
appendStateChangeItemIfNecessary();
#if ENABLE(INLINE_PATH_DATA)
if (path.hasInlineData()) {
if (path.hasInlineData<LineData>())
recordFillLine(path.inlineData<LineData>());
else if (path.hasInlineData<ArcData>())
recordFillArc(path.inlineData<ArcData>());
else if (path.hasInlineData<QuadCurveData>())
recordFillQuadCurve(path.inlineData<QuadCurveData>());
else if (path.hasInlineData<BezierCurveData>())
recordFillBezierCurve(path.inlineData<BezierCurveData>());
return;
}
#endif
recordFillPath(path);
}
void Recorder::fillEllipse(const FloatRect& rect)
{
appendStateChangeItemIfNecessary();
recordFillEllipse(rect);
}
void Recorder::strokeRect(const FloatRect& rect, float lineWidth)
{
appendStateChangeItemIfNecessary();
recordStrokeRect(rect, lineWidth);
}
void Recorder::strokePath(const Path& path)
{
appendStateChangeItemIfNecessary();
#if ENABLE(INLINE_PATH_DATA)
if (path.hasInlineData()) {
if (path.hasInlineData<LineData>())
recordStrokeLine(path.inlineData<LineData>());
else if (path.hasInlineData<ArcData>())
recordStrokeArc(path.inlineData<ArcData>());
else if (path.hasInlineData<QuadCurveData>())
recordStrokeQuadCurve(path.inlineData<QuadCurveData>());
else if (path.hasInlineData<BezierCurveData>())
recordStrokeBezierCurve(path.inlineData<BezierCurveData>());
return;
}
#endif
recordStrokePath(path);
}
void Recorder::strokeEllipse(const FloatRect& rect)
{
appendStateChangeItemIfNecessary();
recordStrokeEllipse(rect);
}
void Recorder::clearRect(const FloatRect& rect)
{
appendStateChangeItemIfNecessary();
recordClearRect(rect);
}
#if USE(CG)
void Recorder::applyStrokePattern()
{
appendStateChangeItemIfNecessary();
recordApplyStrokePattern();
}
void Recorder::applyFillPattern()
{
appendStateChangeItemIfNecessary();
recordApplyFillPattern();
}
#endif
void Recorder::clip(const FloatRect& rect)
{
currentState().clipBounds.intersect(currentState().ctm.mapRect(rect));
recordClip(rect);
}
void Recorder::clipOut(const FloatRect& rect)
{
recordClipOut(rect);
}
void Recorder::clipOut(const Path& path)
{
recordClipOutToPath(path);
}
void Recorder::clipPath(const Path& path, WindRule windRule)
{
currentState().clipBounds.intersect(currentState().ctm.mapRect(path.fastBoundingRect()));
recordClipPath(path, windRule);
}
IntRect Recorder::clipBounds() const
{
if (auto inverse = currentState().ctm.inverse())
return enclosingIntRect(inverse->mapRect(currentState().clipBounds));
// If the CTM is not invertible, return the original rect.
// This matches CGRectApplyInverseAffineTransform behavior.
return enclosingIntRect(currentState().clipBounds);
}
void Recorder::clipToImageBuffer(ImageBuffer& imageBuffer, const FloatRect& destRect)
{
recordResourceUse(imageBuffer);
recordClipToImageBuffer(imageBuffer.renderingResourceIdentifier(), destRect);
}
GraphicsContext::ClipToDrawingCommandsResult Recorder::clipToDrawingCommands(const FloatRect& destination, const DestinationColorSpace& colorSpace, Function<void(GraphicsContext&)>&& drawingFunction)
{
auto initialClip = FloatRect(FloatPoint(), destination.size());
// The initial CTM matches ImageBuffer's initial CTM.
AffineTransform transform = getCTM(GraphicsContext::DefinitelyIncludeDeviceScale);
FloatSize scaleFactor(transform.xScale(), transform.yScale());
auto scaledSize = expandedIntSize(destination.size() * scaleFactor);
AffineTransform initialCTM;
initialCTM.scale(1, -1);
initialCTM.translate(0, -scaledSize.height());
initialCTM.scale(scaledSize / destination.size());
auto nestedContext = createNestedContext(initialClip, initialCTM);
recordBeginClipToDrawingCommands(destination, colorSpace);
drawingFunction(*nestedContext);
recordEndClipToDrawingCommands(destination);
return ClipToDrawingCommandsResult::Success;
}
#if ENABLE(VIDEO)
void Recorder::paintFrameForMedia(MediaPlayer& player, const FloatRect& destination)
{
if (!player.identifier()) {
GraphicsContext::paintFrameForMedia(player, destination);
return;
}
ASSERT(player.identifier());
recordPaintFrameForMedia(player, destination);
}
#endif
void Recorder::applyDeviceScaleFactor(float deviceScaleFactor)
{
// We modify the state directly here instead of calling GraphicsContext::scale()
// because the recorded item will scale() when replayed.
currentState().scale({ deviceScaleFactor, deviceScaleFactor });
// FIXME: this changes the baseCTM, which will invalidate all of our cached extents.
// Assert that it's only called early on?
recordApplyDeviceScaleFactor(deviceScaleFactor);
}
FloatRect Recorder::roundToDevicePixels(const FloatRect& rect, GraphicsContext::RoundingMode)
{
WTFLogAlways("GraphicsContext::roundToDevicePixels() is not yet compatible with DisplayList::Recorder.");
return rect;
}
const Recorder::ContextState& Recorder::currentState() const
{
ASSERT(m_stateStack.size());
return m_stateStack.last();
}
Recorder::ContextState& Recorder::currentState()
{
ASSERT(m_stateStack.size());
return m_stateStack.last();
}
const AffineTransform& Recorder::ctm() const
{
return currentState().ctm;
}
void Recorder::ContextState::translate(float x, float y)
{
ctm.translate(x, y);
}
void Recorder::ContextState::rotate(float angleInRadians)
{
double angleInDegrees = rad2deg(static_cast<double>(angleInRadians));
ctm.rotate(angleInDegrees);
AffineTransform rotation;
rotation.rotate(angleInDegrees);
}
void Recorder::ContextState::scale(const FloatSize& size)
{
ctm.scale(size);
}
void Recorder::ContextState::setCTM(const AffineTransform& matrix)
{
ctm = matrix;
}
void Recorder::ContextState::concatCTM(const AffineTransform& matrix)
{
ctm *= matrix;
}
} // namespace DisplayList
} // namespace WebCore