/* * Copyright 2025 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" #if defined(SK_GRAPHITE) #include "include/core/SkCanvas.h" #include "include/core/SkColorSpace.h" #include "include/core/SkString.h" #include "include/effects/SkRuntimeEffect.h" #include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/PrecompileContext.h" #include "include/gpu/graphite/Surface.h" #include "include/gpu/graphite/precompile/PaintOptions.h" #include "include/gpu/graphite/precompile/Precompile.h" #include "include/gpu/graphite/precompile/PrecompileBlender.h" #include "include/gpu/graphite/precompile/PrecompileColorFilter.h" #include "include/gpu/graphite/precompile/PrecompileRuntimeEffect.h" #include "include/gpu/graphite/precompile/PrecompileShader.h" #include "src/core/SkRuntimeEffectPriv.h" #include "src/gpu/graphite/ContextPriv.h" #include "src/gpu/graphite/PrecompileContextPriv.h" #include "src/gpu/graphite/ShaderCodeDictionary.h" #include "tools/graphite/ContextFactory.h" #include "tools/graphite/GraphiteToolUtils.h" #include "tools/graphite/UniqueKeyUtils.h" #include "tools/graphite/precompile/PipelineCallbackHandler.h" #include "tools/graphite/precompile/PrecompileEffectFactories.h" using namespace::skgpu::graphite; using namespace skiatest::graphite; using namespace skiatools::graphite; namespace { std::pair create_paint_and_options(bool addBlenders) { SkPaint paint; PaintOptions paintOptions; auto [shader, shaderOption] = PrecompileFactories::CreateAnnulusRuntimeShader(); paint.setShader(std::move(shader)); paintOptions.setShaders({ std::move(shaderOption) }); auto [colorFilter, colorFilterOption] = PrecompileFactories::CreateComboRuntimeColorFilter(); paint.setColorFilter(std::move(colorFilter)); paintOptions.setColorFilters({ std::move(colorFilterOption) }); if (addBlenders) { auto [blender, blenderOption] = PrecompileFactories::CreateComboRuntimeBlender(); paint.setBlender(std::move(blender)); paintOptions.setBlenders({ std::move(blenderOption) }); } return { paint, paintOptions }; } bool draw_with_normal_api(skgpu::graphite::Context* context, skgpu::graphite::Recorder* recorder, const SkPaint& paint) { auto ii = SkImageInfo::Make({ 256, 256 }, kRGBA_8888_SkColorType, kPremul_SkAlphaType); sk_sp surface = SkSurfaces::RenderTarget(recorder, ii, skgpu::Mipmapped::kNo); if (!surface) { return false; } SkCanvas* canvas = surface->getCanvas(); canvas->drawRect({0, 0, 100, 100}, paint); std::unique_ptr recording = recorder->snap(); if (!recording) { return false; } if (!context->insertRecording({ recording.get() })) { return false; } if (!context->submit(skgpu::graphite::SyncToCpu::kYes)) { return false; } return true; } void fetch_keys_and_reset(skiatest::Reporter* reporter, PipelineCallBackHandler* handler, PrecompileContext* precompileContext, std::vector* uniqueKeys, std::vector>* serializedKeys, bool reset) { UniqueKeyUtils::FetchUniqueKeys(precompileContext, uniqueKeys); handler->retrieve(serializedKeys); if (reset) { GlobalCache* globalCache = precompileContext->priv().globalCache(); globalCache->resetGraphicsPipelines(); REPORTER_ASSERT(reporter, globalCache->numGraphicsPipelines() == 0); handler->reset(); REPORTER_ASSERT(reporter, handler->numKeys() == 0); } } // Get the existing keys, reset, and try recreating them all w/ the serialized pipeline keys void reset_and_recreate_pipelines_with_serialized_keys( skiatest::Reporter* reporter, Context* context, Recorder* recorder, PrecompileContext* precompileContext, PipelineCallBackHandler* handler) { GlobalCache* globalCache = precompileContext->priv().globalCache(); ShaderCodeDictionary* shaderCodeDictionary = context->priv().shaderCodeDictionary(); auto [paint, _] = create_paint_and_options(/* addBlenders= */ true); draw_with_normal_api(context, recorder, paint); // None of the user-defined stable runtime effects should've been transmuted to not-stable REPORTER_ASSERT(reporter, !shaderCodeDictionary->numUserDefinedRuntimeEffects()); std::vector origKeys; std::vector> androidStyleKeys; fetch_keys_and_reset(reporter, handler, precompileContext, &origKeys, &androidStyleKeys, /* reset= */ true); // Given 'draw_with_normal_api' we expect one serialized key - full of user-defined stable keys REPORTER_ASSERT(reporter, origKeys.size() == 1); REPORTER_ASSERT(reporter, androidStyleKeys.size() == 1); // Use the serialized keys to regenerate the Pipelines for (sk_sp& d : androidStyleKeys) { bool result = precompileContext->precompile(d); SkAssertResult(result); } // None of the user-defined stable runtime effects should've been transmuted to not-stable REPORTER_ASSERT(reporter, !shaderCodeDictionary->numUserDefinedRuntimeEffects()); std::vector recreatedKeys; std::vector> recreatedAndroidStyleKeys; fetch_keys_and_reset(reporter, handler, precompileContext, &recreatedKeys, &recreatedAndroidStyleKeys, /* reset= */ false); REPORTER_ASSERT(reporter, recreatedKeys.size() == 1); REPORTER_ASSERT(reporter, origKeys[0] == recreatedKeys[0]); REPORTER_ASSERT(reporter, recreatedAndroidStyleKeys.size() == 1); REPORTER_ASSERT(reporter, androidStyleKeys[0]->equals(recreatedAndroidStyleKeys[0].get())); int numBeforeSecondDraw = globalCache->numGraphicsPipelines(); draw_with_normal_api(context, recorder, paint); // None of the user-defined stable runtime effects should've been transmuted to not-stable REPORTER_ASSERT(reporter, !shaderCodeDictionary->numUserDefinedRuntimeEffects()); // Re-drawing shouldn't create any new pipelines REPORTER_ASSERT(reporter, numBeforeSecondDraw == globalCache->numGraphicsPipelines(), "%d != %d", numBeforeSecondDraw, globalCache->numGraphicsPipelines()); } // Get the existing keys, reset, and then try recreating them all using the normal precompile API void reset_and_recreate_pipelines_with_normal_precompile_api( skiatest::Reporter* reporter, Context* context, Recorder* recorder, PrecompileContext* precompileContext, PipelineCallBackHandler* handler) { // We don't attach runtime blenders to the SkPaint and PaintOptions in this case bc that will // force a dest read and complicate the normal-pipeline/Precompile-pipeline // comparison on Native Mac and Vulkan. This is bc, for those platforms, Precompile skips some // LoadOp combinations which don't matter for those platforms (please see 'numLoadOps' in // Precompile) but are serialized (for simplicity) in the serialized pipeline keys. auto [paint, paintOptions] = create_paint_and_options(/* addBlenders= */ false); draw_with_normal_api(context, recorder, paint); std::vector origKeys; std::vector> androidStyleKeys; fetch_keys_and_reset(reporter, handler, precompileContext, &origKeys, &androidStyleKeys, /* reset= */ true); // Given 'draw_with_normal_api' we expect one serialized key - full of user-defined stable keys REPORTER_ASSERT(reporter, origKeys.size() == 1); REPORTER_ASSERT(reporter, androidStyleKeys.size() == 1); RenderPassProperties renderPassProps; renderPassProps.fDSFlags = DepthStencilFlags::kDepth; Precompile(precompileContext, paintOptions, DrawTypeFlags::kSimpleShape, { renderPassProps }); std::vector recreatedKeys; std::vector> recreatedAndroidStyleKeys; fetch_keys_and_reset(reporter, handler, precompileContext, &recreatedKeys, &recreatedAndroidStyleKeys, /* reset= */ true); // The normal precompile API will overgenerate, so we need to search for a match { bool foundIt = false; for (const skgpu::UniqueKey& k : recreatedKeys) { if (origKeys[0] == k) { foundIt = true; break; } } REPORTER_ASSERT(reporter, foundIt); } { bool foundIt = false; for (const sk_sp& k : recreatedAndroidStyleKeys) { if (androidStyleKeys[0]->equals(k.get())) { foundIt = true; break; } } REPORTER_ASSERT(reporter, foundIt); } } // This helper creates a defective user-defined known runtime effect pair: // the blessed shader will have a user-defined stable key // the cursed shader has the same SkSL as the blessed one but no stable key std::pair, sk_sp> make_defective_annulus_shader_pair() { SkRuntimeEffect* blessed = PrecompileFactories::GetAnnulusShaderEffect(); sk_sp cursed(SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, PrecompileFactories::GetAnnulusShaderCode())); SkASSERT(blessed != cursed.get()); static const float kUniforms[4] = { 50.0f, 50.0f, 40.0f, 50.0f }; sk_sp uniforms = SkData::MakeWithCopy(kUniforms, sizeof(kUniforms)); return { blessed->makeShader(uniforms, /* children= */ {}), cursed->makeShader(uniforms, /* children= */ {}) }; } // Draw once with a registered runtime effect, reset, and then re-draw w/ an un-registered // runtime effect that uses the same SkSL. void test_sksl_reuse(skiatest::Reporter* reporter, Context* context, Recorder* recorder, PrecompileContext* precompileContext, PipelineCallBackHandler* handler) { auto [blessedShader, cursedShader] = make_defective_annulus_shader_pair(); SkASSERT(blessedShader != cursedShader); // The blessed paint is the static one from the PrecompileFactories which has been // registered as a user-defined known runtime effect. SkPaint blessedPaint; blessedPaint.setShader(std::move(blessedShader)); // The cursed paint uses the same SkSL as the blessed version but uses a wholly separate // runtime effect (which is not registered). SkPaint cursedPaint; cursedPaint.setShader(std::move(cursedShader)); draw_with_normal_api(context, recorder, blessedPaint); std::vector origKeys; std::vector> serializedKeys; fetch_keys_and_reset(reporter, handler, precompileContext, &origKeys, &serializedKeys, /* reset= */ true); // Given 'draw_with_normal_api' we expect one serialized key - full of user-defined stable keys REPORTER_ASSERT(reporter, origKeys.size() == 1); REPORTER_ASSERT(reporter, serializedKeys.size() == 1); draw_with_normal_api(context, recorder, cursedPaint); std::vector recreatedKeys; std::vector> recreatedSerializedKeys; fetch_keys_and_reset(reporter, handler, precompileContext, &recreatedKeys, &recreatedSerializedKeys, /* reset= */ true); REPORTER_ASSERT(reporter, recreatedKeys.size() == 1); REPORTER_ASSERT(reporter, origKeys[0] == recreatedKeys[0]); // The un-registered runtime effect should've been mapped back to the registered one // and successfully serialized. REPORTER_ASSERT(reporter, recreatedSerializedKeys.size() == 1); REPORTER_ASSERT(reporter, serializedKeys[0]->equals(recreatedSerializedKeys[0].get())); } void test_get_pipeline_label_api(skiatest::Reporter* reporter, Context* context, Recorder* recorder, PrecompileContext* precompileContext, PipelineCallBackHandler* handler) { auto [paint, paintOptions] = create_paint_and_options(/* addBlenders= */ true); draw_with_normal_api(context, recorder, paint); std::vector origKeys; std::vector> androidStyleKeys; fetch_keys_and_reset(reporter, handler, precompileContext, &origKeys, &androidStyleKeys, /* reset= */ true); // Given 'draw_with_normal_api' we expect one serialized key - full of user-defined stable keys REPORTER_ASSERT(reporter, origKeys.size() == 1); REPORTER_ASSERT(reporter, androidStyleKeys.size() == 1); std::string label = precompileContext->getPipelineLabel(androidStyleKeys[0]); REPORTER_ASSERT(reporter, std::string::npos != label.find("AnnulusShader")); REPORTER_ASSERT(reporter, std::string::npos != label.find("SrcBlender")); REPORTER_ASSERT(reporter, std::string::npos != label.find("DstBlender")); REPORTER_ASSERT(reporter, std::string::npos != label.find("ComboBlender")); REPORTER_ASSERT(reporter, std::string::npos != label.find("DoubleColorFilter")); REPORTER_ASSERT(reporter, std::string::npos != label.find("ComboColorFilter")); // We withheld the HalfColorFilter name to test the default name case REPORTER_ASSERT(reporter, std::string::npos == label.find("HalfColorFilter")); REPORTER_ASSERT(reporter, std::string::npos != label.find("UserDefinedKnownRuntimeEffect")); } } // anonymous namespace // This test adds some user-defined stably-keyed runtime effects and then verifies that // everything behaves as expected. For the purposes of this test, "behaves as expected" means: // 1) the user-defined stably keyed runtime effects appear as such in the ShaderCodeDictionary // 2) the user-defined stable keys appear in the serialized Pipeline // keys (i.e., from the PipelineCallBackHandler) // 3) said keys correctly (re)generate the desired pipelines // 4) the normal (non-serialized-key) Precompile API generates the same keys. // 5) if the blessed stable runtime-effects aren't used, the free-range runtime-effects are // mapped back to the stable runtime-effects (clients really shouldn't do this though!) DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS(UserDefinedStableKeyTest, skgpu::IsRenderingContext, reporter, origContext, origTestContext, origOptions, /* optionsProc= */ nullptr, /* condition= */ true, CtsEnforcement::kNever) { std::unique_ptr pipelineHandler(new PipelineCallBackHandler); TestOptions newOptions(origOptions); newOptions.fContextOptions.fPipelineCallbackContext = pipelineHandler.get(); newOptions.fContextOptions.fPipelineCallback = PipelineCallBackHandler::CallBack; // We're going to also use all these runtime effects via the normal API // (c.f. create_paint_and_options) static const int kNumUserDefinedStableKeys = 7; sk_sp userDefinedKnownRuntimeEffects[kNumUserDefinedStableKeys] = { sk_ref_sp(PrecompileFactories::GetAnnulusShaderEffect()), sk_ref_sp(PrecompileFactories::GetSrcBlenderEffect()), sk_ref_sp(PrecompileFactories::GetDstBlenderEffect()), sk_ref_sp(PrecompileFactories::GetComboBlenderEffect()), sk_ref_sp(PrecompileFactories::GetDoubleColorFilterEffect()), sk_ref_sp(PrecompileFactories::GetHalfColorFilterEffect()), sk_ref_sp(PrecompileFactories::GetComboColorFilterEffect()), }; for (const sk_sp& e: userDefinedKnownRuntimeEffects) { // The PrecompileFactories runtime effects are static so prior runs may have already // set their StableKeys. Reset the StableKeys so all our expectations/asserts will be met. SkRuntimeEffectPriv::ResetStableKey(e.get()); } newOptions.fContextOptions.fUserDefinedKnownRuntimeEffects = { userDefinedKnownRuntimeEffects }; ContextFactory workaroundFactory(newOptions); ContextInfo ctxInfo = workaroundFactory.getContextInfo(origTestContext->contextType()); Context* newContext = ctxInfo.fContext; std::unique_ptr precompileContext = newContext->makePrecompileContext(); GlobalCache* globalCache = precompileContext->priv().globalCache(); ShaderCodeDictionary* shaderCodeDictionary = newContext->priv().shaderCodeDictionary(); std::unique_ptr recorder = newContext->makeRecorder(ToolUtils::CreateTestingRecorderOptions()); REPORTER_ASSERT(reporter, !globalCache->numGraphicsPipelines()); // The next two lines check #1 above REPORTER_ASSERT(reporter, !shaderCodeDictionary->numUserDefinedRuntimeEffects()); REPORTER_ASSERT(reporter, shaderCodeDictionary->numUserDefinedKnownRuntimeEffects() == kNumUserDefinedStableKeys); // This verifies #2 and #3 above reset_and_recreate_pipelines_with_serialized_keys(reporter, newContext, recorder.get(), precompileContext.get(), pipelineHandler.get()); globalCache->resetGraphicsPipelines(); pipelineHandler->reset(); // This tests out #4 above reset_and_recreate_pipelines_with_normal_precompile_api(reporter, newContext, recorder.get(), precompileContext.get(), pipelineHandler.get()); globalCache->resetGraphicsPipelines(); pipelineHandler->reset(); // This tests out #5 above test_sksl_reuse(reporter, newContext, recorder.get(), precompileContext.get(), pipelineHandler.get()); globalCache->resetGraphicsPipelines(); pipelineHandler->reset(); // Extra little test to check on the getPipelineLabel API test_get_pipeline_label_api(reporter, newContext, recorder.get(), precompileContext.get(), pipelineHandler.get()); } // Test that the ShaderCodeDictionary can deduplicate the user-defined known runtime effect list DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS(UserDefinedStableKeyTest_Duplicates, skgpu::IsRenderingContext, reporter, context, testContext, origOptions, /* optionsProc= */ nullptr, /* condition= */ true, CtsEnforcement::kNever) { std::unique_ptr pipelineHandler(new PipelineCallBackHandler); TestOptions newOptions(origOptions); newOptions.fContextOptions.fPipelineCallbackContext = pipelineHandler.get(); newOptions.fContextOptions.fPipelineCallback = PipelineCallBackHandler::CallBack; sk_sp userDefinedKnownRuntimeEffects[] = { sk_ref_sp(PrecompileFactories::GetAnnulusShaderEffect()), sk_ref_sp(PrecompileFactories::GetAnnulusShaderEffect()), }; for (const sk_sp& e: userDefinedKnownRuntimeEffects) { // The PrecompileFactories runtime effects are static so prior runs may have already // set their StableKeys. Reset the StableKeys so all our expectations/asserts will be met. SkRuntimeEffectPriv::ResetStableKey(e.get()); } newOptions.fContextOptions.fUserDefinedKnownRuntimeEffects = { userDefinedKnownRuntimeEffects }; ContextFactory workaroundFactory(newOptions); ContextInfo ctxInfo = workaroundFactory.getContextInfo(testContext->contextType()); ShaderCodeDictionary* shaderCodeDictionary = ctxInfo.fContext->priv().shaderCodeDictionary(); REPORTER_ASSERT(reporter, shaderCodeDictionary->numUserDefinedKnownRuntimeEffects() == 1); } // Test that the ShaderCodeDictionary can handle nullptrs in the // user-defined known runtime effect list DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS(UserDefinedStableKeyTest_Nullptrs, skgpu::IsRenderingContext, reporter, context, testContext, origOptions, /* optionsProc= */ nullptr, /* condition= */ true, CtsEnforcement::kNever) { std::unique_ptr pipelineHandler(new PipelineCallBackHandler); TestOptions newOptions(origOptions); newOptions.fContextOptions.fPipelineCallbackContext = pipelineHandler.get(); newOptions.fContextOptions.fPipelineCallback = PipelineCallBackHandler::CallBack; sk_sp userDefinedKnownRuntimeEffects[] = { sk_ref_sp(PrecompileFactories::GetAnnulusShaderEffect()), nullptr, sk_ref_sp(PrecompileFactories::GetSrcBlenderEffect()), }; for (const sk_sp& e : userDefinedKnownRuntimeEffects) { // The PrecompileFactories runtime effects are static so prior runs may have already // set their StableKeys. Reset the StableKeys so all our expectations/asserts will be met. if (e) { SkRuntimeEffectPriv::ResetStableKey(e.get()); } } newOptions.fContextOptions.fUserDefinedKnownRuntimeEffects = { userDefinedKnownRuntimeEffects }; ContextFactory workaroundFactory(newOptions); ContextInfo ctxInfo = workaroundFactory.getContextInfo(testContext->contextType()); ShaderCodeDictionary* shaderCodeDictionary = ctxInfo.fContext->priv().shaderCodeDictionary(); REPORTER_ASSERT(reporter, shaderCodeDictionary->numUserDefinedKnownRuntimeEffects() == 2); } // Test that the ShaderCodeDictionary can handle excess user-defined known runtime effects DEF_CONDITIONAL_GRAPHITE_TEST_FOR_CONTEXTS(UserDefinedStableKeyTest_Overflow, skgpu::IsRenderingContext, reporter, context, testContext, origOptions, /* optionsProc= */ nullptr, /* condition= */ true, CtsEnforcement::kNever) { std::unique_ptr pipelineHandler(new PipelineCallBackHandler); TestOptions newOptions(origOptions); newOptions.fContextOptions.fPipelineCallbackContext = pipelineHandler.get(); newOptions.fContextOptions.fPipelineCallback = PipelineCallBackHandler::CallBack; std::vector> userDefinedKnownRuntimeEffects; for (int i = 0; i < 2*SkKnownRuntimeEffects::kUserDefinedKnownRuntimeEffectsReservedCnt; ++i) { SkString sksl; sksl.printf("half4 main(float2 xy) { return half4(%d/255.0, %d/255.0, %d/255.0, 1.0); }", i, i, i); userDefinedKnownRuntimeEffects.push_back(SkRuntimeEffect::MakeForShader(sksl).effect); } newOptions.fContextOptions.fUserDefinedKnownRuntimeEffects = { userDefinedKnownRuntimeEffects }; ContextFactory workaroundFactory(newOptions); ContextInfo ctxInfo = workaroundFactory.getContextInfo(testContext->contextType()); ShaderCodeDictionary* shaderCodeDictionary = ctxInfo.fContext->priv().shaderCodeDictionary(); REPORTER_ASSERT(reporter, shaderCodeDictionary->numUserDefinedKnownRuntimeEffects() == SkKnownRuntimeEffects::kUserDefinedKnownRuntimeEffectsReservedCnt); } #endif // SK_GRAPHITE