| /* | |
| * 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, | |
| /*stencilSettings=*/{}, | |
| /*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 |