| /* |
| * Copyright 2021 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/SkSpan.h" |
| #include "include/core/SkTypes.h" |
| #include "src/core/SkTHash.h" |
| #include "src/sksl/SkSLBuiltinTypes.h" |
| #include "src/sksl/SkSLCompiler.h" |
| #include "src/sksl/SkSLContext.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/analysis/SkSLProgramUsage.h" |
| #include "src/sksl/ir/SkSLFunctionDeclaration.h" |
| #include "src/sksl/ir/SkSLFunctionDefinition.h" |
| #include "src/sksl/ir/SkSLIRNode.h" |
| #include "src/sksl/ir/SkSLInterfaceBlock.h" |
| #include "src/sksl/ir/SkSLLayout.h" |
| #include "src/sksl/ir/SkSLProgram.h" |
| #include "src/sksl/ir/SkSLProgramElement.h" |
| #include "src/sksl/ir/SkSLSymbol.h" |
| #include "src/sksl/ir/SkSLSymbolTable.h" |
| #include "src/sksl/ir/SkSLType.h" |
| #include "src/sksl/ir/SkSLVarDeclarations.h" |
| #include "src/sksl/ir/SkSLVariable.h" |
| #include "src/sksl/transform/SkSLTransform.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string_view> |
| #include <vector> |
| |
| namespace SkSL { |
| namespace Transform { |
| namespace { |
| |
| class BuiltinVariableScanner { |
| public: |
| BuiltinVariableScanner(const Context& context, const SymbolTable& symbols) |
| : fContext(context) |
| , fSymbols(symbols) {} |
| |
| void addDeclaringElement(const ProgramElement* decl) { |
| // Make sure we only add a built-in variable once. We only have a small handful of built-in |
| // variables to declare, so linear search here is good enough. |
| if (std::find(fNewElements.begin(), fNewElements.end(), decl) == fNewElements.end()) { |
| fNewElements.push_back(decl); |
| } |
| } |
| |
| void addDeclaringElement(const Symbol* symbol) { |
| if (!symbol || !symbol->is<Variable>()) { |
| return; |
| } |
| const Variable& var = symbol->as<Variable>(); |
| if (const GlobalVarDeclaration* decl = var.globalVarDeclaration()) { |
| this->addDeclaringElement(decl); |
| } else if (const InterfaceBlock* block = var.interfaceBlock()) { |
| this->addDeclaringElement(block); |
| } else { |
| // Double-check that this variable isn't associated with a global or an interface block. |
| // (Locals and parameters will come along naturally as part of the associated function.) |
| SkASSERTF(var.storage() != VariableStorage::kGlobal && |
| var.storage() != VariableStorage::kInterfaceBlock, |
| "%.*s", (int)var.name().size(), var.name().data()); |
| } |
| } |
| |
| void addImplicitFragColorWrite(SkSpan<const std::unique_ptr<ProgramElement>> elements) { |
| for (const std::unique_ptr<ProgramElement>& pe : elements) { |
| if (!pe->is<FunctionDefinition>()) { |
| continue; |
| } |
| const FunctionDefinition& funcDef = pe->as<FunctionDefinition>(); |
| if (funcDef.declaration().isMain()) { |
| if (funcDef.declaration().returnType().matches(*fContext.fTypes.fHalf4)) { |
| // We synthesize writes to sk_FragColor if main() returns a color, even if it's |
| // otherwise unreferenced. |
| this->addDeclaringElement(fSymbols.findBuiltinSymbol(Compiler::FRAGCOLOR_NAME)); |
| } |
| // Now that main() has been found, we can stop scanning. |
| break; |
| } |
| } |
| } |
| |
| static std::string_view GlobalVarBuiltinName(const ProgramElement& elem) { |
| return elem.as<GlobalVarDeclaration>().varDeclaration().var()->name(); |
| } |
| |
| static std::string_view InterfaceBlockName(const ProgramElement& elem) { |
| return elem.as<InterfaceBlock>().instanceName(); |
| } |
| |
| void sortNewElements() { |
| std::sort(fNewElements.begin(), |
| fNewElements.end(), |
| [](const ProgramElement* a, const ProgramElement* b) { |
| if (a->kind() != b->kind()) { |
| return a->kind() < b->kind(); |
| } |
| switch (a->kind()) { |
| case ProgramElement::Kind::kGlobalVar: |
| SkASSERT(a == b || |
| GlobalVarBuiltinName(*a) != GlobalVarBuiltinName(*b)); |
| return GlobalVarBuiltinName(*a) < GlobalVarBuiltinName(*b); |
| |
| case ProgramElement::Kind::kInterfaceBlock: |
| SkASSERT(a == b || InterfaceBlockName(*a) != InterfaceBlockName(*b)); |
| return InterfaceBlockName(*a) < InterfaceBlockName(*b); |
| |
| default: |
| SkUNREACHABLE; |
| } |
| }); |
| } |
| |
| const Context& fContext; |
| const SymbolTable& fSymbols; |
| std::vector<const ProgramElement*> fNewElements; |
| }; |
| |
| } // namespace |
| |
| void FindAndDeclareBuiltinVariables(Program& program) { |
| using Interface = Program::Interface; |
| const Context& context = *program.fContext; |
| const SymbolTable& symbols = *program.fSymbols; |
| BuiltinVariableScanner scanner(context, symbols); |
| |
| if (ProgramConfig::IsFragment(program.fConfig->fKind)) { |
| // Find main() in the program and check its return type. |
| // If it's half4, we treat that as an implicit write to sk_FragColor and add a reference. |
| scanner.addImplicitFragColorWrite(program.fOwnedElements); |
| } |
| |
| // Scan all the variables used by the program and declare any built-ins. |
| for (const auto& [var, counts] : program.fUsage->fVariableCounts) { |
| if (var->isBuiltin()) { |
| scanner.addDeclaringElement(var); |
| |
| switch (var->layout().fBuiltin) { |
| // Set the RTFlip program input if we find sk_FragCoord or sk_Clockwise. |
| case SK_FRAGCOORD_BUILTIN: |
| if (!context.fConfig->fSettings.fForceNoRTFlip) { |
| program.fInterface.fRTFlipUniform |= Interface::kRTFlip_FragCoord; |
| } |
| break; |
| |
| case SK_CLOCKWISE_BUILTIN: |
| if (!context.fConfig->fSettings.fForceNoRTFlip) { |
| program.fInterface.fRTFlipUniform |= Interface::kRTFlip_Clockwise; |
| } |
| break; |
| |
| // Set the UseLastFragColor program input if we find sk_LastFragColor. |
| // Metal and Dawn define this as a program input, rather than a global variable. |
| case SK_LASTFRAGCOLOR_BUILTIN: |
| program.fInterface.fUseLastFragColor = true; |
| break; |
| |
| // Set secondary color output if we find sk_SecondaryFragColor. |
| case SK_SECONDARYFRAGCOLOR_BUILTIN: |
| program.fInterface.fOutputSecondaryColor = true; |
| break; |
| } |
| } |
| } |
| |
| // Sort the referenced builtin functions into a consistent order; otherwise our output will |
| // become non-deterministic. The exact order isn't particularly important. |
| scanner.sortNewElements(); |
| |
| // Add all the newly-declared elements to the program, and update ProgramUsage to match. |
| program.fSharedElements.insert(program.fSharedElements.begin(), |
| scanner.fNewElements.begin(), |
| scanner.fNewElements.end()); |
| |
| for (const ProgramElement* element : scanner.fNewElements) { |
| program.fUsage->add(*element); |
| } |
| } |
| |
| } // namespace Transform |
| } // namespace SkSL |