blob: 2e34aca403ee6afe9892a0de47adb8bb6d1c81d3 [file] [log] [blame]
/*
* Copyright (C) 2019 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 "PlatformContextDirect2D.h"
#if USE(DIRECT2D)
#include "Direct2DOperations.h"
#include "Direct2DUtilities.h"
#include <d2d1.h>
namespace WebCore {
// Encapsulates the additional painting state information we store for each
// pushed graphics state.
class PlatformContextDirect2D::State {
public:
State() = default;
COMPtr<ID2D1DrawingStateBlock> m_drawingStateBlock;
COMPtr<ID2D1Layer> m_activeLayer;
Vector<Direct2DLayerType> m_clips;
};
PlatformContextDirect2D::PlatformContextDirect2D(ID2D1RenderTarget* renderTarget, WTF::Function<void()>&& preDrawHandler, WTF::Function<void()>&& postDrawHandler)
: m_renderTarget(renderTarget)
, m_preDrawHandler(WTFMove(preDrawHandler))
, m_postDrawHandler(WTFMove(postDrawHandler))
{
m_stateStack.append(State());
m_state = &m_stateStack.last();
m_deviceContext.query(m_renderTarget.get());
RELEASE_ASSERT(!!m_deviceContext);
}
void PlatformContextDirect2D::setRenderTarget(ID2D1RenderTarget* renderTarget)
{
m_renderTarget = renderTarget;
m_deviceContext.query(m_renderTarget.get());
RELEASE_ASSERT(!!m_deviceContext);
}
ID2D1Layer* PlatformContextDirect2D::clipLayer() const
{
return m_state->m_activeLayer.get();
}
void PlatformContextDirect2D::clearClips(Vector<Direct2DLayerType>& clips)
{
for (auto clipType = clips.rbegin(); clipType != clips.rend(); ++clipType) {
if (*clipType == AxisAlignedClip)
m_renderTarget->PopAxisAlignedClip();
else
m_renderTarget->PopLayer();
}
clips.clear();
}
void PlatformContextDirect2D::restore()
{
ASSERT(m_renderTarget);
// No need to restore if we don't have a saved element on the stack.
if (m_stateStack.size() == 1)
return;
auto& restoreState = m_stateStack.last();
if (restoreState.m_drawingStateBlock) {
m_renderTarget->RestoreDrawingState(restoreState.m_drawingStateBlock.get());
restoreState.m_drawingStateBlock = nullptr;
}
clearClips(restoreState.m_clips);
m_stateStack.removeLast();
ASSERT(!m_stateStack.isEmpty());
m_state = &m_stateStack.last();
}
PlatformContextDirect2D::~PlatformContextDirect2D() = default;
void PlatformContextDirect2D::save()
{
ASSERT(m_stateStack.size() >= 1); // There should always be one state on the stack.
ASSERT(m_renderTarget);
m_stateStack.append(State());
m_state = &m_stateStack.last();
GraphicsContext::systemFactory()->CreateDrawingStateBlock(&m_state->m_drawingStateBlock);
m_renderTarget->SaveDrawingState(m_state->m_drawingStateBlock.get());
}
void PlatformContextDirect2D::pushRenderClip(Direct2DLayerType clipType)
{
ASSERT(hasSavedState());
m_state->m_clips.append(clipType);
}
void PlatformContextDirect2D::setActiveLayer(COMPtr<ID2D1Layer>&& layer)
{
ASSERT(hasSavedState());
m_state->m_activeLayer = layer;
}
COMPtr<ID2D1SolidColorBrush> PlatformContextDirect2D::brushWithColor(const D2D1_COLOR_F& color)
{
RGBA32 colorKey = makeRGBA32FromFloats(color.r, color.g, color.b, color.a);
if (!colorKey) {
if (!m_zeroBrush)
m_renderTarget->CreateSolidColorBrush(color, &m_zeroBrush);
return m_zeroBrush;
}
if (colorKey == 0xFFFFFFFF) {
if (!m_whiteBrush)
m_renderTarget->CreateSolidColorBrush(color, &m_whiteBrush);
return m_whiteBrush;
}
auto existingBrush = m_solidColoredBrushCache.ensure(colorKey, [this, color] {
COMPtr<ID2D1SolidColorBrush> colorBrush;
m_renderTarget->CreateSolidColorBrush(color, &colorBrush);
return colorBrush;
});
return existingBrush.iterator->value;
}
void PlatformContextDirect2D::recomputeStrokeStyle()
{
if (!m_strokeSyleIsDirty)
return;
m_d2dStrokeStyle = nullptr;
DashArray dashes;
float patternOffset = 0;
auto dashStyle = D2D1_DASH_STYLE_SOLID;
if ((m_strokeStyle != SolidStroke) && (m_strokeStyle != NoStroke)) {
dashStyle = D2D1_DASH_STYLE_CUSTOM;
patternOffset = m_patternOffset / m_strokeThickness;
dashes = m_dashes;
// In Direct2D, dashes and dots are defined in terms of the ratio of the dash length to the line thickness.
for (auto& dash : dashes)
dash /= m_strokeThickness;
}
auto strokeStyleProperties = D2D1::StrokeStyleProperties(m_lineCap, m_lineCap, m_lineCap, m_lineJoin, m_miterLimit, dashStyle, patternOffset);
GraphicsContext::systemFactory()->CreateStrokeStyle(&strokeStyleProperties, dashes.data(), dashes.size(), &m_d2dStrokeStyle);
m_strokeSyleIsDirty = false;
}
ID2D1StrokeStyle* PlatformContextDirect2D::strokeStyle()
{
recomputeStrokeStyle();
return m_d2dStrokeStyle.get();
}
D2D1_STROKE_STYLE_PROPERTIES PlatformContextDirect2D::strokeStyleProperties() const
{
return D2D1::StrokeStyleProperties(m_lineCap, m_lineCap, m_lineCap, m_lineJoin, m_miterLimit, D2D1_DASH_STYLE_SOLID, 0.0f);
}
void PlatformContextDirect2D::setLineCap(D2D1_CAP_STYLE lineCap)
{
if (m_lineCap == lineCap)
return;
m_lineCap = lineCap;
m_strokeSyleIsDirty = true;
}
void PlatformContextDirect2D::setLineJoin(D2D1_LINE_JOIN join)
{
if (m_lineJoin == join)
return;
m_lineJoin = join;
m_strokeSyleIsDirty = true;
}
void PlatformContextDirect2D::setStrokeStyle(StrokeStyle strokeStyle)
{
if (m_strokeStyle == strokeStyle)
return;
m_strokeStyle = strokeStyle;
m_strokeSyleIsDirty = true;
}
void PlatformContextDirect2D::setStrokeThickness(float thickness)
{
if (WTF::areEssentiallyEqual(thickness, m_strokeThickness))
return;
m_strokeThickness = thickness;
m_strokeSyleIsDirty = true;
}
void PlatformContextDirect2D::setMiterLimit(float canvasMiterLimit)
{
// Direct2D miter limit is in terms of HALF the line thickness.
float miterLimit = 0.5f * canvasMiterLimit;
if (WTF::areEssentiallyEqual(miterLimit, m_miterLimit))
return;
m_miterLimit = miterLimit;
m_strokeSyleIsDirty = true;
}
void PlatformContextDirect2D::setDashOffset(float dashOffset)
{
if (WTF::areEssentiallyEqual(dashOffset, m_dashOffset))
return;
m_dashOffset = dashOffset;
m_strokeSyleIsDirty = true;
}
void PlatformContextDirect2D::setDashes(const DashArray& dashes)
{
if (m_dashes == dashes)
return;
m_dashes = dashes;
m_strokeSyleIsDirty = true;
}
void PlatformContextDirect2D::setPatternWidth(float patternWidth)
{
if (WTF::areEssentiallyEqual(patternWidth, m_patternWidth))
return;
m_patternWidth = patternWidth;
m_strokeSyleIsDirty = true;
}
void PlatformContextDirect2D::setPatternOffset(float patternOffset)
{
if (WTF::areEssentiallyEqual(patternOffset, m_patternOffset))
return;
m_patternOffset = patternOffset;
m_strokeSyleIsDirty = true;
}
void PlatformContextDirect2D::beginDraw()
{
ASSERT(m_renderTarget);
m_renderTarget->BeginDraw();
++beginDrawCount;
}
void PlatformContextDirect2D::endDraw()
{
ASSERT(m_renderTarget);
if (m_stateStack.size() > 1)
restore();
clearClips(m_state->m_clips);
ASSERT(m_stateStack.size() >= 1);
D2D1_TAG first, second;
HRESULT hr = m_renderTarget->EndDraw(&first, &second);
if (!SUCCEEDED(hr))
WTFLogAlways("Failed in PlatformContextDirect2D::endDraw: hr=%xd, first=%ld, second=%ld", hr, first, second);
--beginDrawCount;
compositeIfNeeded();
}
void PlatformContextDirect2D::setTags(D2D1_TAG tag1, D2D1_TAG tag2)
{
#if !ASSERT_DISABLED
m_renderTarget->SetTags(tag1, tag2);
#else
UNUSED_PARAM(tag1);
UNUSED_PARAM(tag2);
#endif
}
void PlatformContextDirect2D::notifyPreDrawObserver()
{
m_preDrawHandler();
}
void PlatformContextDirect2D::notifyPostDrawObserver()
{
m_postDrawHandler();
}
void PlatformContextDirect2D::pushClip(Direct2DLayerType clipType)
{
m_state->m_clips.append(clipType);
}
void PlatformContextDirect2D::compositeIfNeeded()
{
if (!m_compositeSource)
return;
COMPtr<ID2D1BitmapRenderTarget> bitmapTarget(Query, m_renderTarget.get());
if (!bitmapTarget)
return;
COMPtr<ID2D1Bitmap> currentCanvas = Direct2D::createBitmapCopyFromContext(bitmapTarget.get());
if (!currentCanvas)
return;
COMPtr<ID2D1Effect> effect;
if (m_compositeMode != D2D1_COMPOSITE_MODE_FORCE_DWORD) {
m_deviceContext->CreateEffect(CLSID_D2D1Composite, &effect);
effect->SetInput(0, m_compositeSource.get());
effect->SetInput(1, currentCanvas.get());
effect->SetValue(D2D1_COMPOSITE_PROP_MODE, m_compositeMode);
} else if (m_blendMode != D2D1_BLEND_MODE_FORCE_DWORD) {
m_deviceContext->CreateEffect(CLSID_D2D1Blend, &effect);
effect->SetInput(0, currentCanvas.get());
effect->SetInput(1, m_compositeSource.get());
effect->SetValue(D2D1_BLEND_PROP_MODE, m_blendMode);
}
if (effect) {
m_deviceContext->BeginDraw();
m_deviceContext->Clear(D2D1::ColorF(0, 0, 0, 0));
m_deviceContext->DrawImage(effect.get());
m_deviceContext->EndDraw();
}
m_compositeSource = nullptr;
}
void PlatformContextDirect2D::setBlendAndCompositeMode(D2D1_BLEND_MODE blend, D2D1_COMPOSITE_MODE mode)
{
if (mode == m_compositeMode && blend == m_blendMode)
return;
// Direct2D handles compositing based on bitmaps. If we are changing the
// compositing mode, we need to capture the current state of the rendering
// in a bitmap, perform drawing operations until we finish, then handle
// the composting of the two layers.
COMPtr<ID2D1BitmapRenderTarget> bitmapTarget(Query, m_renderTarget.get());
if (!bitmapTarget)
return;
endDraw();
m_compositeMode = mode;
m_blendMode = blend;
m_compositeSource = Direct2D::createBitmapCopyFromContext(bitmapTarget.get());
beginDraw();
m_deviceContext->Clear(D2D1::ColorF(0, 0, 0, 0));
}
} // namespace WebCore
#endif // USE(DIRECT2D)