| /* |
| * Copyright (C) 2019-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 "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, Function<void()>&& preDrawHandler, 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) |
| { |
| auto colorKey = colorConvert<SRGBA<uint8_t>>(SRGBA<float> { 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_ENABLED |
| 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) |