blob: 0c4b7ff94fe28b00d9720e666dbec5999723644b [file] [edit]
/*
* 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 "tests/Test.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
#include "include/effects/SkGradient.h"
#include "include/gpu/graphite/Context.h"
#include "include/gpu/graphite/Recorder.h"
#include "include/gpu/graphite/Surface.h"
#include "src/gpu/graphite/Caps.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/Renderer.h"
#include "src/gpu/graphite/ShaderInfo.h"
#include "src/gpu/graphite/StorageContext.h"
#include "src/gpu/graphite/Uniform.h"
namespace skgpu::graphite {
namespace {
class TestStep : public RenderStep {
public:
TestStep(std::initializer_list<Uniform> storageUniforms)
: RenderStep(
Layout::kStd430,
RenderStepID::kMesh,
Flags::kPerformsShading | Flags::kFsUsesStorage,
/*uniforms=*/{},
PrimitiveType::kTriangleStrip,
DepthStencilSettings{},
/*staticAttrs=*/{},
/*appendAttrs=*/{},
/*storageUniforms=*/SkSpan(storageUniforms.begin(), storageUniforms.size()),
/*varyings=*/{}) {
}
std::string vertexSkSL(const RootNodesInfo&) const override {
return "stepLocalCoords = float2(0.0);";
}
const char* fragmentCoverageSkSL() const override { return ""; }
void writeVertices(DrawWriter*, StorageContext*, const DrawParams&, uint32_t) const override {}
void writeUniformsAndTextures(const DrawParams&, PipelineDataGatherer*) const override {}
};
} // namespace
class TextureFallbackTest {
public:
static std::string emitFallback(const ResourceBindingRequirements& reqs,
const RenderStep* step) {
return ShaderInfo::EmitStorageFallbackTexture(reqs, step);
}
};
// 1. Primitive Scalar & Integer Bitcast Tests
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(TextureFallbackScalarsAndBitcastsTest,
reporter,
context,
CtsEnforcement::kApiLevel_202404) {
std::unique_ptr<Recorder> recorder = context->makeRecorder();
ResourceBindingRequirements reqs = recorder->priv().caps()->resourceBindingRequirements();
reqs.fUniformsSetIdx = 0;
reqs.fStorageBufferBinding = 0;
TestStep step({
{"f", SkSLType::kFloat},
{"h", SkSLType::kHalf},
{"i", SkSLType::kInt},
{"u", SkSLType::kUInt},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("float f;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("half h;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int i;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("uint u;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.f = t0.x;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.h = t0.y;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.i = floatBitsToInt(t0.z);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.u = floatBitsToUint(t0.w);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx0 = index * 1 + 0;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx1") == std::string::npos);
}
// 2. Exhaustive std430 Alignment & Padding Tests
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(TextureFallbackAlignmentAndPaddingTest,
reporter,
context,
CtsEnforcement::kApiLevel_202404) {
std::unique_ptr<Recorder> recorder = context->makeRecorder();
ResourceBindingRequirements reqs = recorder->priv().caps()->resourceBindingRequirements();
reqs.fUniformsSetIdx = 0;
reqs.fStorageBufferBinding = 0;
// Case A: float + float2 + float (8-byte alignment skips t0.y)
{
TestStep step({
{"a", SkSLType::kFloat},
{"b", SkSLType::kFloat2},
{"c", SkSLType::kFloat},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.a = t0.x;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.b = t0.zw;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.c = t1.x;") != std::string::npos);
}
// Case B: float + float + float2 (8-byte alignment packed tight: t0.xy, t0.zw)
{
TestStep step({
{"a", SkSLType::kFloat},
{"b", SkSLType::kFloat},
{"c", SkSLType::kFloat2},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.a = t0.x;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.b = t0.y;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.c = t0.zw;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx1") == std::string::npos);
}
// Case C: float + float3 (16-byte alignment skips t0.yzw)
{
TestStep step({
{"a", SkSLType::kFloat},
{"b", SkSLType::kFloat3},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.a = t0.x;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.b = t1.xyz;") != std::string::npos);
}
// Case D: float + float4 (16-byte alignment skips t0.yzw)
{
TestStep step({
{"a", SkSLType::kFloat},
{"b", SkSLType::kFloat4},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.a = t0.x;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.b = t1;") != std::string::npos);
}
// Case E: float2 + float3 (16-byte alignment skips t0.zw)
{
TestStep step({
{"a", SkSLType::kFloat2},
{"b", SkSLType::kFloat3},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.a = t0.xy;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.b = t1.xyz;") != std::string::npos);
}
// Case F: float2 + float4 (16-byte alignment skips t0.zw)
{
TestStep step({
{"a", SkSLType::kFloat2},
{"b", SkSLType::kFloat4},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.a = t0.xy;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.b = t1;") != std::string::npos);
}
// Case G: float3 + float (packed tight in t0: t0.xyz, t0.w)
{
TestStep step({
{"a", SkSLType::kFloat3},
{"b", SkSLType::kFloat},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.a = t0.xyz;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.b = t0.w;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx1") == std::string::npos);
}
// Case H: float3 + float2 (1 padding float t0.w, float2 in t1.xy)
{
TestStep step({
{"a", SkSLType::kFloat3},
{"b", SkSLType::kFloat2},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.a = t0.xyz;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.b = t1.xy;") != std::string::npos);
}
// Case I: float3 + float3 (1 padding float t0.w, float3 in t1.xyz)
{
TestStep step({
{"a", SkSLType::kFloat3},
{"b", SkSLType::kFloat3},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.a = t0.xyz;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.b = t1.xyz;") != std::string::npos);
}
}
// 3. Matrix Unpacking Tests (mat2x2, mat3x3, mat4x4)
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(TextureFallbackMatricesTest,
reporter,
context,
CtsEnforcement::kApiLevel_202404) {
std::unique_ptr<Recorder> recorder = context->makeRecorder();
ResourceBindingRequirements reqs = recorder->priv().caps()->resourceBindingRequirements();
reqs.fUniformsSetIdx = 0;
reqs.fStorageBufferBinding = 0;
// Case A: mat2x2 starting at component 0 (fits in 1 texel: t0.xy, t0.zw)
{
TestStep step({
{"m2", SkSLType::kFloat2x2},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter,
sksl.find("data.m2 = float2x2(t0.xy, t0.zw);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx1") == std::string::npos);
}
// Case B: mat2x2 starting at component 2 (spans across t0.zw and t1.xy)
{
TestStep step({
{"v2", SkSLType::kFloat2},
{"m2", SkSLType::kFloat2x2},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.v2 = t0.xy;") != std::string::npos);
REPORTER_ASSERT(reporter,
sksl.find("data.m2 = float2x2(t0.zw, t1.xy);") != std::string::npos);
}
// Case C: mat3x3 (3 columns of 4 floats each in std430: t0.xyz, t1.xyz, t2.xyz)
{
TestStep step({
{"m3", SkSLType::kFloat3x3},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(
reporter,
sksl.find("data.m3 = float3x3(t0.xyz, t1.xyz, t2.xyz);") != std::string::npos);
}
// Case D: mat4x4 (4 full texels: t0, t1, t2, t3)
{
TestStep step({
{"m4", SkSLType::kFloat4x4},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter,
sksl.find("data.m4 = float4x4(t0, t1, t2, t3);") != std::string::npos);
}
}
// 4. Vector Bitcast Tests for Signed and Unsigned Integers
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(TextureFallbackVectorBitcastsTest,
reporter,
context,
CtsEnforcement::kApiLevel_202404) {
std::unique_ptr<Recorder> recorder = context->makeRecorder();
ResourceBindingRequirements reqs = recorder->priv().caps()->resourceBindingRequirements();
reqs.fUniformsSetIdx = 0;
reqs.fStorageBufferBinding = 0;
TestStep step({
{"i2", SkSLType::kInt2},
{"u2", SkSLType::kUInt2},
{"i3", SkSLType::kInt3},
{"u3", SkSLType::kUInt3},
{"i4", SkSLType::kInt4},
{"u4", SkSLType::kUInt4},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.i2 = floatBitsToInt(t0.xy);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.u2 = floatBitsToUint(t0.zw);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.i3 = floatBitsToInt(t1.xyz);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.u3 = floatBitsToUint(t2.xyz);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.i4 = floatBitsToInt(t3);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.u4 = floatBitsToUint(t4);") != std::string::npos);
}
// 5. Multi-Instance Indexing & 2D Coordinates Math Tests
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(TextureFallbackMultiInstanceMathTest,
reporter,
context,
CtsEnforcement::kApiLevel_202404) {
std::unique_ptr<Recorder> recorder = context->makeRecorder();
ResourceBindingRequirements reqs = recorder->priv().caps()->resourceBindingRequirements();
reqs.fUniformsSetIdx = 2;
reqs.fStorageBufferBinding = 5;
// 3-texel struct: verifies linearIdx and coords for t0, t1, t2 with stride 3
TestStep step({
{"v4_0", SkSLType::kFloat4},
{"v4_1", SkSLType::kFloat4},
{"v4_2", SkSLType::kFloat4},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter,
sksl.find("layout(set=2, binding=5) readonly texture2D "
"storageFallbackTexture;") != std::string::npos);
const int maxAtlasWidth = reqs.fMaxFallbackTextureSize;
std::string expectedWidth =
"const int texWidth = " + std::to_string(maxAtlasWidth) + ";";
REPORTER_ASSERT(reporter, sksl.find(expectedWidth) != std::string::npos);
REPORTER_ASSERT(reporter, reqs.fMaxFallbackTextureBytes == maxAtlasWidth * maxAtlasWidth * 16);
// Verify custom maxAtlasWidth in ResourceBindingRequirements is respected
ResourceBindingRequirements customReqs = reqs;
customReqs.fMaxFallbackTextureSize = 4096;
std::string customSksl = TextureFallbackTest::emitFallback(customReqs, &step);
REPORTER_ASSERT(reporter, customSksl.find("const int texWidth = 4096;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx0 = index * 3 + 0;") != std::string::npos);
REPORTER_ASSERT(
reporter,
sksl.find("int2 coords0 = int2(linearIdx0 % texWidth, linearIdx0 / texWidth);") !=
std::string::npos);
REPORTER_ASSERT(
reporter,
sksl.find("float4 t0 = float4(textureRead(storageFallbackTexture, uint2(coords0)));") !=
std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx1 = index * 3 + 1;") != std::string::npos);
REPORTER_ASSERT(
reporter,
sksl.find("int2 coords1 = int2(linearIdx1 % texWidth, linearIdx1 / texWidth);") !=
std::string::npos);
REPORTER_ASSERT(
reporter,
sksl.find("float4 t1 = float4(textureRead(storageFallbackTexture, uint2(coords1)));") !=
std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx2 = index * 3 + 2;") != std::string::npos);
REPORTER_ASSERT(
reporter,
sksl.find("int2 coords2 = int2(linearIdx2 % texWidth, linearIdx2 / texWidth);") !=
std::string::npos);
REPORTER_ASSERT(
reporter,
sksl.find("float4 t2 = float4(textureRead(storageFallbackTexture, uint2(coords2)));") !=
std::string::npos);
REPORTER_ASSERT(reporter, step.storageUniformStride() == 48);
REPORTER_ASSERT(reporter, step.storageUniformAlignment() == 16);
// Verify RenderStep uses standard std430 alignment/stride without artificial padding
TestStep singleFloatStep({{"s", SkSLType::kFloat}});
REPORTER_ASSERT(reporter, singleFloatStep.storageUniformStride() == 4);
REPORTER_ASSERT(reporter, singleFloatStep.storageUniformAlignment() == 4);
TestStep float2Step({{"v2", SkSLType::kFloat2}});
REPORTER_ASSERT(reporter, float2Step.storageUniformStride() == 8);
REPORTER_ASSERT(reporter, float2Step.storageUniformAlignment() == 8);
TestStep float3Step({{"v3", SkSLType::kFloat3}});
REPORTER_ASSERT(reporter, float3Step.storageUniformStride() == 16);
REPORTER_ASSERT(reporter, float3Step.storageUniformAlignment() == 16);
// Verify StorageContext with storageBufferSupport=false bumps alignment and stride to 16
{
StorageContext fallbackStorageContext(reqs.fMaxFallbackTextureSize,
/*storageBufferSupport=*/false);
fallbackStorageContext.recordAlignment(singleFloatStep.storageUniformStride(),
singleFloatStep.storageUniformAlignment());
REPORTER_ASSERT(reporter, fallbackStorageContext.runningLCM() == 16);
fallbackStorageContext.recordAlignment(float2Step.storageUniformStride(),
float2Step.storageUniformAlignment());
REPORTER_ASSERT(reporter, fallbackStorageContext.runningLCM() == 16);
}
// Verify StorageContext with storageBufferSupport=true preserves natural std430 alignment/stride
{
StorageContext ssboStorageContext(reqs.fMaxFallbackTextureSize,
/*storageBufferSupport=*/true);
ssboStorageContext.recordAlignment(singleFloatStep.storageUniformStride(),
singleFloatStep.storageUniformAlignment());
REPORTER_ASSERT(reporter, ssboStorageContext.runningLCM() == 4);
ssboStorageContext.recordAlignment(float2Step.storageUniformStride(),
float2Step.storageUniformAlignment());
REPORTER_ASSERT(reporter, ssboStorageContext.runningLCM() == 8);
}
REPORTER_ASSERT(reporter,
sksl.find("inline StepStorageData readStepStorageData(uint index)") !=
std::string::npos);
}
// 6. Comprehensive Stress Test: Mixed Scalars, Integers, Vectors, and Matrices with Varied
// Alignments
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(TextureFallbackStressMixedTypesAndAlignmentTest,
reporter,
context,
CtsEnforcement::kApiLevel_202404) {
std::unique_ptr<Recorder> recorder = context->makeRecorder();
ResourceBindingRequirements reqs = recorder->priv().caps()->resourceBindingRequirements();
reqs.fUniformsSetIdx = 1;
reqs.fStorageBufferBinding = 3;
TestStep step({
{"s0", SkSLType::kFloat}, {"i0", SkSLType::kInt},
{"v2_0", SkSLType::kFloat2}, {"m2_0", SkSLType::kFloat2x2},
{"s1", SkSLType::kFloat}, {"m2_1", SkSLType::kFloat2x2},
{"u0", SkSLType::kUInt}, {"s2", SkSLType::kFloat},
{"v3_0", SkSLType::kFloat3}, {"i1", SkSLType::kInt},
{"m3_0", SkSLType::kFloat3x3}, {"h0", SkSLType::kHalf},
{"i2_0", SkSLType::kInt2}, {"v4_0", SkSLType::kFloat4},
{"u3_0", SkSLType::kUInt3}, {"m4_0", SkSLType::kFloat4x4},
{"u4_0", SkSLType::kUInt4}, {"s3", SkSLType::kFloat},
{"v3_1", SkSLType::kFloat3}, {"i4_0", SkSLType::kInt4},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
// Verify field unpacks and exact swizzles/casts
REPORTER_ASSERT(reporter, sksl.find("data.s0 = t0.x;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.i0 = floatBitsToInt(t0.y);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.v2_0 = t0.zw;") != std::string::npos);
REPORTER_ASSERT(reporter,
sksl.find("data.m2_0 = float2x2(t1.xy, t1.zw);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.s1 = t2.x;") != std::string::npos);
REPORTER_ASSERT(reporter,
sksl.find("data.m2_1 = float2x2(t2.zw, t3.xy);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.u0 = floatBitsToUint(t3.z);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.s2 = t3.w;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.v3_0 = t4.xyz;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.i1 = floatBitsToInt(t4.w);") != std::string::npos);
REPORTER_ASSERT(
reporter,
sksl.find("data.m3_0 = float3x3(t5.xyz, t6.xyz, t7.xyz);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.h0 = t8.x;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.i2_0 = floatBitsToInt(t8.zw);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.v4_0 = t9;") != std::string::npos);
REPORTER_ASSERT(reporter,
sksl.find("data.u3_0 = floatBitsToUint(t10.xyz);") != std::string::npos);
REPORTER_ASSERT(reporter,
sksl.find("data.m4_0 = float4x4(t11, t12, t13, t14);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.u4_0 = floatBitsToUint(t15);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.s3 = t16.x;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.v3_1 = t17.xyz;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.i4_0 = floatBitsToInt(t18);") != std::string::npos);
// Verify 19-texel linearIdx calculation across instances
REPORTER_ASSERT(reporter, sksl.find("int linearIdx0 = index * 19 + 0;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx18 = index * 19 + 18;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx19") == std::string::npos);
}
// 7. Stress Test: Back-to-Back Matrices and Boundary Crossings
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(TextureFallbackStressBackToBackComplexStructuresTest,
reporter,
context,
CtsEnforcement::kApiLevel_202404) {
std::unique_ptr<Recorder> recorder = context->makeRecorder();
ResourceBindingRequirements reqs = recorder->priv().caps()->resourceBindingRequirements();
reqs.fUniformsSetIdx = 0;
reqs.fStorageBufferBinding = 0;
TestStep step({
{"offset_pad", SkSLType::kFloat2},
{"m2", SkSLType::kFloat2x2},
{"m3", SkSLType::kFloat3x3},
{"m4", SkSLType::kFloat4x4},
{"v3_a", SkSLType::kFloat3},
{"v2_a", SkSLType::kFloat2},
{"v3_b", SkSLType::kFloat3},
{"v4_a", SkSLType::kFloat4},
});
std::string sksl = TextureFallbackTest::emitFallback(reqs, &step);
REPORTER_ASSERT(reporter, sksl.find("data.offset_pad = t0.xy;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.m2 = float2x2(t0.zw, t1.xy);") != std::string::npos);
REPORTER_ASSERT(reporter,
sksl.find("data.m3 = float3x3(t2.xyz, t3.xyz, t4.xyz);") != std::string::npos);
REPORTER_ASSERT(reporter,
sksl.find("data.m4 = float4x4(t5, t6, t7, t8);") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.v3_a = t9.xyz;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.v2_a = t10.xy;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.v3_b = t11.xyz;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("data.v4_a = t12;") != std::string::npos);
// Verify 13-texel stride
REPORTER_ASSERT(reporter, sksl.find("int linearIdx0 = index * 13 + 0;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx12 = index * 13 + 12;") != std::string::npos);
REPORTER_ASSERT(reporter, sksl.find("int linearIdx13") == std::string::npos);
}
// 8. End-to-end Draw Test with Multi-Stop (> 8 stops) Gradients
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(TextureFallbackMultiStopGradientsDrawTest,
reporter,
context,
CtsEnforcement::kApiLevel_202404) {
std::unique_ptr<Recorder> recorder = context->makeRecorder();
SkImageInfo ii = SkImageInfo::Make(SkISize::Make(100, 100),
SkColorType::kRGBA_8888_SkColorType,
SkAlphaType::kPremul_SkAlphaType);
sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(recorder.get(), ii);
REPORTER_ASSERT(reporter, surface != nullptr);
SkCanvas* canvas = surface->getCanvas();
constexpr int kNumStops = 12;
SkColor4f colors[kNumStops];
SkScalar pos[kNumStops];
for (int i = 0; i < kNumStops; ++i) {
float t = static_cast<float>(i) / (kNumStops - 1);
colors[i] = {t, 1.0f - t, 0.5f, 1.0f};
pos[i] = t;
}
SkPoint pts[2] = {{0.f, 0.f}, {100.f, 100.f}};
// 1. Linear gradient (> 8 stops)
{
SkPaint paint;
paint.setShader(SkShaders::LinearGradient(
pts, SkGradient{SkGradient::Colors{colors, pos, SkTileMode::kClamp}, {}}));
canvas->drawRect(SkRect::MakeWH(50, 50), paint);
}
// 2. Radial gradient (> 8 stops)
{
SkPaint paint;
paint.setShader(SkShaders::RadialGradient(
{50.f, 50.f}, 50.f,
SkGradient{SkGradient::Colors{colors, pos, SkTileMode::kRepeat}, {}}));
canvas->drawRect(SkRect::MakeXYWH(50, 0, 50, 50), paint);
}
// 3. Sweep gradient (> 8 stops)
{
SkPaint paint;
paint.setShader(SkShaders::SweepGradient(
{50.f, 50.f},
SkGradient{SkGradient::Colors{colors, pos, SkTileMode::kMirror}, {}}));
canvas->drawRect(SkRect::MakeXYWH(0, 50, 50, 50), paint);
}
// 4. Conical gradient (> 8 stops)
{
SkPaint paint;
paint.setShader(SkShaders::TwoPointConicalGradient(
pts[0], 10.f, pts[1], 50.f,
SkGradient{SkGradient::Colors{colors, pos, SkTileMode::kClamp}, {}}));
canvas->drawRect(SkRect::MakeXYWH(50, 50, 50, 50), paint);
}
std::unique_ptr<Recording> recording = recorder->snap();
REPORTER_ASSERT(reporter, recording != nullptr);
InsertRecordingInfo insertInfo;
insertInfo.fRecording = recording.get();
context->insertRecording(insertInfo);
context->submit(SyncToCpu::kYes);
}
} // namespace skgpu::graphite