static std::tuple<bool, SkString>
gather_uniforms_and_check_for_main(const SkSL::Program& program,
std::vector<Uniform>* uniforms,
std::vector<Child>* children,
SkMeshSpecification::Uniform::Flags stage,
size_t* offset) { bool foundMain = false; for (const SkSL::ProgramElement* elem : program.elements()) { if (elem->is<SkSL::FunctionDefinition>()) { const SkSL::FunctionDefinition& defn = elem->as<SkSL::FunctionDefinition>(); const SkSL::FunctionDeclaration& decl = defn.declaration(); if (decl.isMain()) {
foundMain = true;
}
} elseif (elem->is<SkSL::GlobalVarDeclaration>()) { const SkSL::GlobalVarDeclaration& global = elem->as<SkSL::GlobalVarDeclaration>(); const SkSL::VarDeclaration& varDecl = global.declaration()->as<SkSL::VarDeclaration>(); const SkSL::Variable& var = *varDecl.var(); if (var.modifierFlags().isUniform()) { if (var.type().isEffectChild()) { // This is a child effect; add it to our list of children.
children->push_back(SkRuntimeEffectPriv::VarAsChild(var, children->size()));
} else { // This is a uniform variable; make sure it exists in our list of uniforms, and // ensure that the type and layout matches between VS and FS. auto iter = find_uniform(*uniforms, var.name()); constauto& context = *program.fContext; if (iter == uniforms->end()) {
uniforms->push_back(SkRuntimeEffectPriv::VarAsUniform(var, context,
offset));
uniforms->back().flags |= stage;
} else { // Check that the two declarations are equivalent
size_t ignoredOffset = 0; auto uniform = SkRuntimeEffectPriv::VarAsUniform(var, context,
&ignoredOffset); if (uniform.isArray() != iter->isArray() ||
uniform.type != iter->type ||
uniform.count != iter->count) { return {false,
SkStringPrintf("Uniform %.*s declared with different types" " in vertex and fragment shaders.",
(int)var.name().size(), var.name().data())};
} if (uniform.isColor() != iter->isColor()) { return {false,
SkStringPrintf("Uniform %.*s declared with different color" " layout in vertex and fragment shaders.",
(int)var.name().size(), var.name().data())};
}
(*iter).flags |= stage;
}
}
}
}
} if (!foundMain) { return {false, SkString("No main function found.")};
} return {true, {}};
}
using ColorType = SkMeshSpecificationPriv::ColorType;
// This is a non-exhaustive check for the validity of a variable name. The SkSL compiler will // actually process the name. We're just guarding against having multiple tokens embedded in the // name before we put it into a struct definition. staticbool check_name(const SkString& name) { if (name.isEmpty()) { returnfalse;
} for (size_t i = 0; i < name.size(); ++i) { if (name[i] != '_' && !std::isalnum(name[i], std::locale::classic())) { returnfalse;
}
} returntrue;
}
static size_t attribute_type_size(Attribute::Type type) { switch (type) { case Attribute::Type::kFloat: return 4; case Attribute::Type::kFloat2: return 2*4; case Attribute::Type::kFloat3: return 3*4; case Attribute::Type::kFloat4: return 4*4; case Attribute::Type::kUByte4_unorm: return 4;
}
SkUNREACHABLE;
}
staticconstchar* attribute_type_string(Attribute::Type type) { switch (type) { case Attribute::Type::kFloat: return"float"; case Attribute::Type::kFloat2: return"float2"; case Attribute::Type::kFloat3: return"float3"; case Attribute::Type::kFloat4: return"float4"; case Attribute::Type::kUByte4_unorm: return"half4";
}
SkUNREACHABLE;
}
staticconstchar* varying_type_string(Varying::Type type) { switch (type) { case Varying::Type::kFloat: return"float"; case Varying::Type::kFloat2: return"float2"; case Varying::Type::kFloat3: return"float3"; case Varying::Type::kFloat4: return"float4"; case Varying::Type::kHalf: return"half"; case Varying::Type::kHalf2: return"half2"; case Varying::Type::kHalf3: return"half3"; case Varying::Type::kHalf4: return"half4";
}
SkUNREACHABLE;
}
std::tuple<bool, SkString>
check_vertex_offsets_and_stride(SkSpan<const Attribute> attributes,
size_t stride) { // Vulkan 1.0 has a minimum maximum attribute count of 2048.
static_assert(SkMeshSpecification::kMaxStride <= 2048); // ES 2 has a max of 8.
static_assert(SkMeshSpecification::kMaxAttributes <= 8); // Four bytes alignment is required by Metal.
static_assert(SkMeshSpecification::kStrideAlignment >= 4);
static_assert(SkMeshSpecification::kOffsetAlignment >= 4); // ES2 has a minimum maximum of 8. We may need one for a broken gl_FragCoord workaround and // one for local coords.
static_assert(SkMeshSpecification::kMaxVaryings <= 6);
if (attributes.empty()) {
RETURN_ERROR("At least 1 attribute is required.");
} if (attributes.size() > SkMeshSpecification::kMaxAttributes) {
RETURN_ERROR("A maximum of %zu attributes is allowed.",
SkMeshSpecification::kMaxAttributes);
}
static_assert(SkIsPow2(SkMeshSpecification::kStrideAlignment)); if (stride == 0 || stride & (SkMeshSpecification::kStrideAlignment - 1)) {
RETURN_ERROR("Vertex stride must be a non-zero multiple of %zu.",
SkMeshSpecification::kStrideAlignment);
} if (stride > SkMeshSpecification::kMaxStride) {
RETURN_ERROR("Stride cannot exceed %zu.", SkMeshSpecification::kMaxStride);
} for (constauto& a : attributes) { if (a.offset & (SkMeshSpecification::kOffsetAlignment - 1)) {
RETURN_ERROR("Attribute offset must be a multiple of %zu.",
SkMeshSpecification::kOffsetAlignment);
} // This equivalent to vertexAttributeAccessBeyondStride==VK_FALSE in // VK_KHR_portability_subset. First check is to avoid overflow in second check. if (a.offset >= stride || a.offset + attribute_type_size(a.type) > stride) {
RETURN_ERROR("Attribute offset plus size cannot exceed stride.");
}
}
RETURN_SUCCESS;
}
int check_for_passthrough_local_coords_and_dead_varyings(const SkSL::Program& fsProgram,
uint32_t* deadVaryingMask) {
SkASSERT(deadVaryingMask);
usingnamespace SkSL; static constexpr int kFailed = -2;
class Visitor final : public SkSL::ProgramVisitor { public:
Visitor(const Context& context) : fContext(context) {}
bool visitStatement(const Statement& s) override { if (!fInMain) { return ProgramVisitor::visitStatement(s);
} // We should only get here if are in main and therefore found the varyings parameter.
SkASSERT(fVaryings);
SkASSERT(fVaryingsType);
if (fPassthroughFieldIndex == kFailed) { // We've already determined there are return statements that aren't passthrough // or return different fields. return ProgramVisitor::visitStatement(s);
} if (!s.is<ReturnStatement>()) { return ProgramVisitor::visitStatement(s);
}
// We just detect simple cases like "return varyings.foo;" constauto& rs = s.as<ReturnStatement>();
SkASSERT(rs.expression()); if (!rs.expression()->is<FieldAccess>()) {
this->passthroughFailed(); return ProgramVisitor::visitStatement(s);
} constauto& fa = rs.expression()->as<FieldAccess>(); if (!fa.base()->is<VariableReference>()) {
this->passthroughFailed(); return ProgramVisitor::visitStatement(s);
} constauto& baseRef = fa.base()->as<VariableReference>(); if (baseRef.variable() != fVaryings) {
this->passthroughFailed(); return ProgramVisitor::visitStatement(s);
} if (fPassthroughFieldIndex >= 0) { // We already found an OK return statement. Check if this one returns the same // field. if (fa.fieldIndex() != fPassthroughFieldIndex) {
this->passthroughFailed(); return ProgramVisitor::visitStatement(s);
} // We don't call our base class here because we don't want to hit visitExpression // and mark the returned field as used. returnfalse;
} const Field& field = fVaryings->type().fields()[fa.fieldIndex()]; if (!field.fType->matches(*fContext.fTypes.fFloat2)) {
this->passthroughFailed(); return ProgramVisitor::visitStatement(s);
}
fPassthroughFieldIndex = fa.fieldIndex(); // We don't call our base class here because we don't want to hit visitExpression and // mark the returned field as used. returnfalse;
}
bool visitExpression(const Expression& e) override { // Anything before the Varyings struct is defined doesn't matter. if (!fVaryingsType) { returnfalse;
} if (!e.is<FieldAccess>()) { return ProgramVisitor::visitExpression(e);
} constauto& fa = e.as<FieldAccess>(); if (!fa.base()->type().matches(*fVaryingsType)) { return ProgramVisitor::visitExpression(e);
}
fFieldUseMask |= 1 << fa.fieldIndex(); returnfalse;
}
bool userProvidedPositionVarying = false; for (constauto& v : varyings) { if (v.name.equals("position")) { if (v.type != Varying::Type::kFloat2) { return {nullptr, SkString("Varying \"position\" must have type float2.")};
}
userProvidedPositionVarying = true;
}
}
STArray<kMaxVaryings, Varying> tempVaryings; if (!userProvidedPositionVarying) { // Even though we check the # of varyings in MakeFromSourceWithStructs we check here, too, // to avoid overflow with + 1. if (varyings.size() > kMaxVaryings - 1) {
RETURN_FAILURE("A maximum of %zu varyings is allowed.", kMaxVaryings);
} for (constauto& v : varyings) {
tempVaryings.push_back(v);
}
tempVaryings.push_back(Varying{Varying::Type::kFloat2, SkString("position")});
varyings = tempVaryings;
}
SkString varyingStruct("struct Varyings {\n"); for (constauto& v : varyings) {
varyingStruct.appendf(" %s %s;\n", varying_type_string(v.type), v.name.c_str());
}
varyingStruct.append("};\n");
for (constauto& a : attributes) { if (!check_name(a.name)) {
RETURN_FAILURE("\"%s\" is not a valid attribute name.", a.name.c_str());
}
}
if (varyings.size() > kMaxVaryings) {
RETURN_FAILURE("A maximum of %zu varyings is allowed.", kMaxVaryings);
}
for (constauto& v : varyings) { if (!check_name(v.name)) { return {nullptr, SkStringPrintf("\"%s\" is not a valid varying name.", v.name.c_str())};
}
}
// Disable memory pooling; this might slow down compilation slightly, but it will ensure that a // long-lived mesh specification doesn't waste memory.
SkSL::ProgramSettings settings;
settings.fUseMemoryPool = false;
// TODO(skia:11209): Add SkCapabilities to the API, check against required version.
std::unique_ptr<SkSL::Program> vsProgram = compiler.convertProgram(
SkSL::ProgramKind::kMeshVertex,
std::string(vs.c_str()),
settings); if (!vsProgram) {
RETURN_FAILURE("VS: %s", compiler.errorText().c_str());
}
if (SkSL::Analysis::CallsColorTransformIntrinsics(*vsProgram)) {
RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders");
}
if (SkSL::Analysis::CallsColorTransformIntrinsics(*fsProgram)) {
RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders");
}
ColorType ct = get_fs_color_type(*fsProgram);
if (ct == ColorType::kNone) {
cs = nullptr;
at = kPremul_SkAlphaType;
} else { if (!cs) { return {nullptr, SkString{"Must provide a color space if FS returns a color."}};
} if (at == kUnknown_SkAlphaType) { return {nullptr, SkString{"Must provide a valid alpha type if FS returns a color."}};
}
}
uint32_t deadVaryingMask; int passthroughLocalCoordsVaryingIndex =
check_for_passthrough_local_coords_and_dead_varyings(*fsProgram, &deadVaryingMask);
if (passthroughLocalCoordsVaryingIndex >= 0) {
SkASSERT(varyings[passthroughLocalCoordsVaryingIndex].type == Varying::Type::kFloat2);
}
// The attributes and varyings SkSL struct declarations are included in the program source. // However, the attribute offsets and types need to be included, the latter because the SkSL // struct definition has the GPU type but not the CPU data format. for (constauto& a : fAttributes) {
fHash = SkChecksum::Hash32(&a.offset, sizeof(a.offset), fHash);
fHash = SkChecksum::Hash32(&a.type, sizeof(a.type), fHash);
}
for (int index = 0; index < fChildren.size(); ++index) { const SkRuntimeEffect::Child& meshSpecChild = fSpec->children()[index]; if (fChildren[index].type().has_value()) { if (meshSpecChild.type != fChildren[index].type()) {
FAIL_MESH_VALIDATE("Child effect '%.*s' was specified as a %s, but passed as a %s.",
(int)meshSpecChild.name.size(), meshSpecChild.name.data(),
SkRuntimeEffectPriv::ChildTypeToStr(meshSpecChild.type),
SkRuntimeEffectPriv::ChildTypeToStr(*fChildren[index].type()));
}
}
}
auto vb = static_cast<SkMeshPriv::VB*>(fVB.get()); auto ib = static_cast<SkMeshPriv::IB*>(fIB.get());
SkSafeMath sm;
size_t vsize = sm.mul(fSpec->stride(), fVCount); if (sm.add(vsize, fVOffset) > vb->size()) {
FAIL_MESH_VALIDATE("The vertex buffer offset and vertex count reads beyond the end of the" " vertex buffer.");
}
if (fVOffset%fSpec->stride() != 0) {
FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
fVOffset,
fSpec->stride());
}
if (size_t uniformSize = fSpec->uniformSize()) { if (!fUniforms || fUniforms->size() < uniformSize) {
FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
fUniforms->size(),
uniformSize);
}
}
auto modeToStr = [](Mode m) { switch (m) { case Mode::kTriangles: return"triangles"; case Mode::kTriangleStrip: return"triangle-strip";
}
SkUNREACHABLE;
}; if (ib) { if (fICount < min_vcount_for_mode(fMode)) {
FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
modeToStr(fMode),
min_vcount_for_mode(fMode),
fICount);
}
size_t isize = sm.mul(sizeof(uint16_t), fICount); if (sm.add(isize, fIOffset) > ib->size()) {
FAIL_MESH_VALIDATE("The index buffer offset and index count reads beyond the end of the" " index buffer.");
} // If we allow 32 bit indices then this should enforce 4 byte alignment in that case. if (!SkIsAlign2(fIOffset)) {
FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
}
} else { if (fVCount < min_vcount_for_mode(fMode)) {
FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
modeToStr(fMode),
min_vcount_for_mode(fMode),
fICount);
}
SkASSERT(!fICount);
SkASSERT(!fIOffset);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.