1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Direct2D: Fix issue where contexts would not clear properly after pushing multiple clip layers

This issue could be seen when calling setBufferedToImage on a component
with a transparent background with a size different to the component's
size.

The details are unclear to me, but it seems like both calling Clear on
the device context, and using the COPY blend mode, ignore alpha values
and instead use a constant alpha of 1.0 when there is a geometric
clipping layer active.

As a workaround for this issue, when clearing a rectangle we now pop all
active layers, fill their intersection using the COPY blend mode while
there are no layers active, and then reinstate the layers.

The new implementation is likely to be very slow, however I think this
code path is unlikely to be used frequently in practice. The main
use-case for rendering clear transparent areas is the rendering of
buffered component images, but such cases normally use axis-aligned
clipping regions, which should be able to use the faster path.
This commit is contained in:
reuk 2024-07-01 15:35:15 +01:00
parent f3dfd0d9be
commit a4022df686
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C

View file

@ -39,6 +39,23 @@ namespace juce
JUCE_IMPLEMENT_SINGLETON (Direct2DMetricsHub)
#endif
struct ScopedBlendCopy
{
explicit ScopedBlendCopy (ComSmartPtr<ID2D1DeviceContext1> c)
: ctx (c)
{
ctx->SetPrimitiveBlend (D2D1_PRIMITIVE_BLEND_COPY);
}
~ScopedBlendCopy()
{
ctx->SetPrimitiveBlend (blend);
}
ComSmartPtr<ID2D1DeviceContext1> ctx;
D2D1_PRIMITIVE_BLEND blend = ctx->GetPrimitiveBlend();
};
class PushedLayers
{
public:
@ -142,10 +159,100 @@ public:
return pushedLayers.empty();
}
void fillGeometryWithNoLayersActive (ComSmartPtr<ID2D1DeviceContext1> ctx,
ComSmartPtr<ID2D1Geometry> geo,
ComSmartPtr<ID2D1Brush> brush)
{
ComSmartPtr<ID2D1Factory> factory;
ctx->GetFactory (factory.resetAndGetPointerAddress());
const auto hasGeoLayer = std::any_of (pushedLayers.begin(),
pushedLayers.end(),
[] (const auto& x) { return std::holds_alternative<OwningLayer> (x.var); });
const auto intersection = [&]() -> ComSmartPtr<ID2D1Geometry>
{
if (! hasGeoLayer)
return {};
const auto contextSize = ctx->GetPixelSize();
ComSmartPtr<ID2D1RectangleGeometry> rect;
factory->CreateRectangleGeometry (D2D1::RectF (0.0f,
0.0f,
(float) contextSize.width,
(float) contextSize.height),
rect.resetAndGetPointerAddress());
ComSmartPtr<ID2D1Geometry> clip = rect;
for (const auto& layer : pushedLayers)
{
ScopedGeometryWithSink scope { factory, D2D1_FILL_MODE_WINDING };
if (auto* l = std::get_if<OwningLayer> (&layer.var))
{
clip->CombineWithGeometry (l->geometry,
D2D1_COMBINE_MODE_INTERSECT,
l->params.maskTransform,
scope.sink);
}
else if (auto* r = std::get_if<Rectangle<float>> (&layer.var))
{
ComSmartPtr<ID2D1RectangleGeometry> temporaryRect;
factory->CreateRectangleGeometry (D2DUtilities::toRECT_F (*r),
temporaryRect.resetAndGetPointerAddress());
clip->CombineWithGeometry (temporaryRect,
D2D1_COMBINE_MODE_INTERSECT,
D2D1::Matrix3x2F::Identity(),
scope.sink);
}
clip = scope.geometry;
}
return clip;
}();
const auto clipWithGeo = [&]() -> ComSmartPtr<ID2D1Geometry>
{
if (intersection == nullptr)
return geo;
ScopedGeometryWithSink scope { factory, D2D1_FILL_MODE_WINDING };
intersection->CombineWithGeometry (geo,
D2D1_COMBINE_MODE_INTERSECT,
D2D1::Matrix3x2F::Identity(),
scope.sink);
return scope.geometry;
}();
if (intersection != nullptr)
{
std::for_each (pushedLayers.rbegin(),
pushedLayers.rend(),
[&] (const auto& layer) { layer.pop (ctx); });
}
{
const ScopedBlendCopy scope { ctx };
ctx->FillGeometry (clipWithGeo, brush);
}
if (intersection != nullptr)
{
pushedLayers.clear();
auto newLayer = D2D1::LayerParameters1();
newLayer.geometricMask = intersection;
push (ctx, newLayer);
}
}
private:
struct OwningLayer
{
explicit OwningLayer (D2D1_LAYER_PARAMETERS1 p) : params (p) {}
explicit OwningLayer (const D2D1_LAYER_PARAMETERS1& p) : params (p) {}
D2D1_LAYER_PARAMETERS1 params;
ComSmartPtr<ID2D1Geometry> geometry = params.geometricMask != nullptr ? addComSmartPtrOwner (params.geometricMask) : nullptr;
@ -1276,17 +1383,30 @@ void Direct2DGraphicsContext::fillRect (const Rectangle<int>& r, bool replaceExi
return;
if (replaceExistingContents)
clipToRectangle (r);
const auto clearColour = currentState->fillType.colour;
auto fill = [replaceExistingContents, clearColour] (Rectangle<float> rect,
ComSmartPtr<ID2D1DeviceContext1> deviceContext,
ComSmartPtr<ID2D1Brush> brush)
{
if (replaceExistingContents)
deviceContext->Clear (D2DUtilities::toCOLOR_F (clearColour));
else if (brush != nullptr)
applyPendingClipList();
const auto asRectF = D2DUtilities::toRECT_F (getPimpl()->getFrameSize().toFloat());
ComSmartPtr<ID2D1RectangleGeometry> rectGeometry;
getPimpl()->getDirect2DFactory()->CreateRectangleGeometry (asRectF,
rectGeometry.resetAndGetPointerAddress());
const auto matrix = D2DUtilities::transformToMatrix (currentState->currentTransform.getTransform());
ComSmartPtr<ID2D1TransformedGeometry> geo;
getPimpl()->getDirect2DFactory()->CreateTransformedGeometry (rectGeometry,
matrix,
geo.resetAndGetPointerAddress());
const auto brush = currentState->fillType.isInvisible() ? currentState->currentBrush : currentState->getBrush();
currentState->layers.fillGeometryWithNoLayersActive (getPimpl()->getDeviceContext(), geo, brush);
return;
}
const auto fill = [] (Rectangle<float> rect,
ComSmartPtr<ID2D1DeviceContext1> deviceContext,
ComSmartPtr<ID2D1Brush> brush)
{
if (brush != nullptr)
deviceContext->FillRectangle (D2DUtilities::toRECT_F (rect), brush);
};