blob: 5e74703ec0f60c70524f92de3334f4772ad33dfc [file]
/*
* Copyright 2026 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkPathBuilder.h"
#include "include/gpu/ganesh/GrDirectContext.h"
#include "src/gpu/ganesh/GrStyle.h"
#include "src/gpu/ganesh/SurfaceDrawContext.h"
#include "tests/Test.h"
#include "tools/gpu/ContextType.h"
static void only_allow_small(GrContextOptions* options) {
options->fGpuPathRenderers = GpuPathRenderers::kSmall;
}
DEF_GANESH_TEST_FOR_CONTEXTS(SmallPathRenderer_crbug505876830,
skgpu::IsRenderingContext,
reporter,
ctxInfo,
only_allow_small,
CtsEnforcement::kNever) {
auto ctx = ctxInfo.directContext();
auto sdc = skgpu::ganesh::SurfaceDrawContext::Make(
ctx, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kExact,
{100, 100}, SkSurfaceProps(), /*label=*/{});
if (!sdc) {
return;
}
sdc->clear(SK_PMColor4fBLACK);
GrStyle style(SkStrokeRec::kFill_InitStyle);
const SkPath smolpath = SkPathBuilder()
.moveTo(-0.25000017881393432617f, -0.25000017881393432617f)
.lineTo( 0.25000017881393432617f, -0.25000017881393432617f)
.lineTo( 0.25000017881393432617f, 0.25000017881393432617f)
.close()
.detach();
const SkMatrix m = SkMatrix::MakeAll(
-0.0f, 70368341861093281422320336896.0f, -590295810358705651712.0f,
2.0f, 17469300226849243136.0f, 0.0f,
868052350533632.0f, 4220791729285326409039872.0f, 3572.759765625f
);
// Passes if we don't assert.
sdc->drawPath(nullptr, GrPaint(), GrAA::kYes, m, smolpath, style);
ctx->flushAndSubmit();
}
#if !defined(SK_ENABLE_OPTIMIZE_SIZE)
#include "src/core/SkFloatBits.h"
#include "src/gpu/ganesh/geometry/GrStyledShape.h"
#include "src/gpu/ganesh/ops/SmallPathShapeData.h"
DEF_TEST(SmallPathShapeDataKey_DomainSeparation, reporter) {
SkPath path = SkPath::Polygon({{-1, -1}, {1, -1}, {1, 1}, {-1, 1}}, true);
GrStyledShape shape(path);
skgpu::ganesh::SmallPathShapeDataKey sdfKey(shape, 32);
skgpu::ganesh::SmallPathShapeDataKey bmKey(shape, SkMatrix::I());
REPORTER_ASSERT(reporter, !(sdfKey == bmKey));
REPORTER_ASSERT(reporter, sdfKey.data()[0] ==
static_cast<uint32_t>(skgpu::ganesh::SmallPathShapeDataKey::Type::kSDF));
REPORTER_ASSERT(reporter, bmKey.data()[0] ==
static_cast<uint32_t>(skgpu::ganesh::SmallPathShapeDataKey::Type::kBitmap));
}
DEF_TEST(SmallPathShapeDataKey_CollisionReproduction, reporter) {
// 1. Construct Shape B (Bitmap side)
const float p0_x = SkBits2Float(0x28);
const float p0_y = 0.45f;
const float p1_x = 0.2f; // "free"
const float p1_y = 0.45f;
const float p2_x = 0.49f;
const float p2_y = SkBits2Float(0x700000);
const float p3_x = 65.0f;
const float p3_y = SkBits2Float(0x6);
const float p4_x = -1.0f;
const float p4_y = 1.5f;
SkPath pathB = SkPathBuilder()
.moveTo(p0_x, p0_y)
.lineTo(p1_x, p1_y)
.lineTo(p2_x, p2_y)
.quadTo(p3_x, p3_y, p4_x, p4_y)
.detach();
pathB.setFillType(SkPathFillType::kWinding);
SkMatrix ctmB = SkMatrix::MakeAll(
SkBits2Float(162), -1.0f, 0.0f,
-1.0f, SkBits2Float(0x10C), 0.0f,
0.0f, 0.0f, 1.0f
);
GrStyledShape shapeB(pathB);
skgpu::ganesh::SmallPathShapeDataKey keyB(shapeB, ctmB);
// 2. Construct Shape A (DF side)
SkRect rectA = SkRect::MakeLTRB(-1.0f, -1.0f, 0.0f, SkBits2Float(0x110));
SkVector radiiA[4] = {
{ SkBits2Float(4), SkBits2Float(0x02010100) }, // UL
{ SkBits2Float(0x28), 0.45f }, // UR
{ 0.2f, 0.45f }, // LR
{ 0.49f, SkBits2Float(0x700000) } // LL
};
SkRRect rrectA;
rrectA.setRectRadii(rectA, radiiA);
SkPaint strokePaint;
strokePaint.setStyle(SkPaint::kStroke_Style);
strokePaint.setStrokeWidth(1.5f);
strokePaint.setStrokeJoin(SkPaint::kRound_Join);
strokePaint.setStrokeCap(SkPaint::kButt_Cap);
GrStyle styleA(strokePaint);
GrStyledShape parentShapeA(rrectA, styleA);
GrStyledShape shapeA = parentShapeA.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, 65.0f);
skgpu::ganesh::SmallPathShapeDataKey keyA(shapeA, 162);
// Both keys have identical lengths (19 words: 1 word tag + 18 payload words)
REPORTER_ASSERT(reporter, keyA.count32() == 19);
REPORTER_ASSERT(reporter, keyB.count32() == 19);
// Verify the 18 payload words match byte-for-byte!
// Without the domain tag, this would have been an exact collision.
int payloadDiff = memcmp(&keyA.data()[1], &keyB.data()[1], 18 * sizeof(uint32_t));
REPORTER_ASSERT(reporter, payloadDiff == 0);
// With the domain tag, keyA != keyB:
REPORTER_ASSERT(reporter, !(keyA == keyB));
REPORTER_ASSERT(reporter, keyA.data()[0] ==
static_cast<uint32_t>(skgpu::ganesh::SmallPathShapeDataKey::Type::kSDF));
REPORTER_ASSERT(reporter, keyB.data()[0] ==
static_cast<uint32_t>(skgpu::ganesh::SmallPathShapeDataKey::Type::kBitmap));
}
#endif // !defined(SK_ENABLE_OPTIMIZE_SIZE)