fixed Metal matrix constructors

Bug: skia:8544
Change-Id: Ie1e96ab1ef6e8c032fa510be36c035f91a1f6851
Reviewed-on: https://skia-review.googlesource.com/c/185687
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
This commit is contained in:
Ethan Nicholas 2019-01-22 10:59:11 -05:00 committed by Skia Commit-Bot
parent 57796b340e
commit 842d31b141
9 changed files with 328 additions and 63 deletions

View File

@ -18,6 +18,7 @@ skia_sksl_sources = [
"$_src/sksl/SkSLJIT.cpp",
"$_src/sksl/SkSLLexer.cpp",
"$_src/sksl/SkSLMetalCodeGenerator.cpp",
"$_src/sksl/SkSLOutputStream.cpp",
"$_src/sksl/SkSLParser.cpp",
"$_src/sksl/SkSLPipelineStageCodeGenerator.cpp",
"$_src/sksl/SkSLSPIRVCodeGenerator.cpp",

View File

@ -241,6 +241,7 @@ tests_sources = [
"$_tests/SkSLGLSLTest.cpp",
"$_tests/SkSLJITTest.cpp",
"$_tests/SkSLMemoryLayoutTest.cpp",
"$_tests/SkSLMetalTest.cpp",
"$_tests/SkSLSPIRVTest.cpp",
"$_tests/SkUTFTest.cpp",
"$_tests/SortTest.cpp",

44
src/sksl/SkSLDefines.h Normal file
View File

@ -0,0 +1,44 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SKSL_DEFINES
#define SKSL_DEFINES
#ifdef SKSL_STANDALONE
#if defined(_WIN32) || defined(__SYMBIAN32__)
#define SKSL_BUILD_FOR_WIN
#endif
#else
#ifdef SK_BUILD_FOR_WIN
#define SKSL_BUILD_FOR_WIN
#endif // SK_BUILD_FOR_WIN
#endif // SKSL_STANDALONE
#ifdef SKSL_STANDALONE
#define SkASSERT(x)
#define SkAssertResult(x) x
#define SkDEBUGCODE(x)
#else
#include "SkTypes.h"
#endif
#define SKSL_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#if defined(__clang__) || defined(__GNUC__)
#define SKSL_PRINTF_LIKE(A, B) __attribute__((format(printf, (A), (B))))
#else
#define SKSL_PRINTF_LIKE(A, B)
#endif
#define ABORT(...) (printf(__VA_ARGS__), sksl_abort())
#if _MSC_VER
#define NORETURN __declspec(noreturn)
#else
#define NORETURN __attribute__((__noreturn__))
#endif
#endif

View File

@ -120,7 +120,7 @@ void MetalCodeGenerator::writeExpression(const Expression& expr, Precedence pare
this->writeBoolLiteral((BoolLiteral&) expr);
break;
case Expression::kConstructor_Kind:
this->writeConstructor((Constructor&) expr);
this->writeConstructor((Constructor&) expr, parentPrecedence);
break;
case Expression::kIntLiteral_Kind:
this->writeIntLiteral((IntLiteral&) expr);
@ -296,31 +296,109 @@ void MetalCodeGenerator::writeSpecialIntrinsic(const FunctionCall & c, SpecialIn
}
}
void MetalCodeGenerator::writeConstructor(const Constructor& c) {
this->writeType(c.fType);
this->write("(");
const char* separator = "";
int scalarCount = 0;
for (const auto& arg : c.fArguments) {
this->write(separator);
separator = ", ";
if (Type::kMatrix_Kind == c.fType.kind() && Type::kScalar_Kind == arg->fType.kind()) {
// float2x2(float, float, float, float) doesn't work in Metal 1, so we need to merge to
// float2x2(float2, float2).
if (!scalarCount) {
this->writeType(c.fType.componentType());
this->write(to_string(c.fType.rows()));
this->write("(");
}
++scalarCount;
}
this->writeExpression(*arg, kSequence_Precedence);
if (scalarCount && scalarCount == c.fType.rows()) {
this->write(")");
scalarCount = 0;
}
// If it hasn't already been written, writes a constructor for 'matrix' which takes a single value
// of type 'arg'.
String MetalCodeGenerator::getMatrixConstructHelper(const Type& matrix, const Type& arg) {
String key = matrix.name() + arg.name();
auto found = fMatrixConstructHelpers.find(key);
if (found != fMatrixConstructHelpers.end()) {
return found->second;
}
String name;
int columns = matrix.columns();
int rows = matrix.rows();
if (arg.isNumber()) {
// creating a matrix from a single scalar value
name = "float" + to_string(columns) + "x" + to_string(rows) + "_from_float";
fExtraFunctions.printf("float%dx%d %s(float x) {\n",
columns, rows, name.c_str());
fExtraFunctions.printf(" return float%dx%d(", columns, rows);
for (int i = 0; i < columns; ++i) {
if (i > 0) {
fExtraFunctions.writeText(", ");
}
fExtraFunctions.printf("float%d(", rows);
for (int j = 0; j < rows; ++j) {
if (j > 0) {
fExtraFunctions.writeText(", ");
}
if (i == j) {
fExtraFunctions.writeText("x");
} else {
fExtraFunctions.writeText("0");
}
}
fExtraFunctions.writeText(")");
}
fExtraFunctions.writeText(");\n}\n");
}
else if (matrix.rows() == 2 && matrix.columns() == 2) {
// float2x2(float4) doesn't work, need to split it into float2x2(float2, float2)
name = "float2x2_from_float4";
fExtraFunctions.printf(
"float2x2 %s(float4 v) {\n"
" return float2x2(float2(v[0], v[1]), float2(v[2], v[3]));\n"
"}\n",
name.c_str()
);
}
else {
SkASSERT(false);
name = "<error>";
}
fMatrixConstructHelpers[key] = name;
return name;
}
bool MetalCodeGenerator::canCoerce(const Type& t1, const Type& t2) {
if (t1.columns() != t2.columns() || t1.rows() != t2.rows()) {
return false;
}
if (t1.columns() > 1) {
return this->canCoerce(t1.componentType(), t2.componentType());
}
return ((t1 == *fContext.fFloat_Type || t1 == *fContext.fHalf_Type) &&
(t2 == *fContext.fFloat_Type || t2 == *fContext.fHalf_Type));
}
void MetalCodeGenerator::writeConstructor(const Constructor& c, Precedence parentPrecedence) {
if (c.fArguments.size() == 1 && this->canCoerce(c.fType, c.fArguments[0]->fType)) {
this->writeExpression(*c.fArguments[0], parentPrecedence);
return;
}
if (c.fType.kind() == Type::kMatrix_Kind && c.fArguments.size() == 1) {
const Expression& arg = *c.fArguments[0];
String name = this->getMatrixConstructHelper(c.fType, arg.fType);
this->write(name);
this->write("(");
this->writeExpression(arg, kSequence_Precedence);
this->write(")");
} else {
this->writeType(c.fType);
this->write("(");
const char* separator = "";
int scalarCount = 0;
for (const auto& arg : c.fArguments) {
this->write(separator);
separator = ", ";
if (Type::kMatrix_Kind == c.fType.kind() && Type::kScalar_Kind == arg->fType.kind()) {
// float2x2(float, float, float, float) doesn't work in Metal 1, so we need to merge
// to float2x2(float2, float2).
if (!scalarCount) {
this->writeType(c.fType.componentType());
this->write(to_string(c.fType.rows()));
this->write("(");
}
++scalarCount;
}
this->writeExpression(*arg, kSequence_Precedence);
if (scalarCount && scalarCount == c.fType.rows()) {
this->write(")");
scalarCount = 0;
}
}
this->write(")");
}
this->write(")");
}
void MetalCodeGenerator::writeFragCoord() {

View File

@ -184,9 +184,13 @@ protected:
void writeInverseHack(const Expression& mat);
String getMatrixConstructHelper(const Type& matrix, const Type& arg);
void writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind);
void writeConstructor(const Constructor& c);
bool canCoerce(const Type& t1, const Type& t2);
void writeConstructor(const Constructor& c, Precedence parentPrecedence);
void writeFieldAccess(const FieldAccess& f);
@ -268,6 +272,7 @@ protected:
std::unordered_map<const FunctionDeclaration*, Requirements> fRequirements;
bool fSetupFragPositionGlobal = false;
bool fSetupFragPositionLocal = false;
std::unordered_map<String, String> fMatrixConstructHelpers;
int fUniformBuffer = -1;
typedef CodeGenerator INHERITED;

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkSLOutputStream.h"
namespace SkSL {
void OutputStream::writeString(String s) {
this->write(s.c_str(), s.size());
}
void OutputStream::printf(const char format[], ...) {
va_list args;
va_start(args, format);
this->appendVAList(format, args);
va_end(args);
}
void OutputStream::appendVAList(const char format[], va_list args) {
char buffer[kBufferSize];
int length = vsnprintf(buffer, kBufferSize, format, args);
SkASSERT(length >= 0 && length < (int) kBufferSize);
this->write(buffer, length);
}
}

View File

@ -8,6 +8,7 @@
#ifndef SKSL_OUTPUTSTREAM
#define SKSL_OUTPUTSTREAM
#include "SkSLDefines.h"
#include "SkSLString.h"
namespace SkSL {
@ -24,11 +25,16 @@ public:
virtual void write(const void* s, size_t size) = 0;
void writeString(String s) {
this->write(s.c_str(), s.size());
}
void writeString(String s);
void printf(const char format[], ...) SKSL_PRINTF_LIKE(2, 3);
void appendVAList(const char format[], va_list args);
virtual ~OutputStream() {}
private:
static const int kBufferSize = 1024;
};
} // namespace

View File

@ -12,29 +12,25 @@
#include <memory>
#include "stdlib.h"
#include "string.h"
#include "assert.h"
#include "SkSLDefines.h"
#include "SkSLString.h"
#include "SkSLStringStream.h"
#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU
#ifndef SKSL_STANDALONE
#include "SkTypes.h"
#if SK_SUPPORT_GPU
#include "GrContextOptions.h"
#include "GrShaderCaps.h"
#endif
#ifdef SKSL_STANDALONE
#if defined(_WIN32) || defined(__SYMBIAN32__)
#define SKSL_BUILD_FOR_WIN
#endif
#else
#ifdef SK_BUILD_FOR_WIN
#define SKSL_BUILD_FOR_WIN
#endif // SK_BUILD_FOR_WIN
#endif // SK_SUPPORT_GPU
#endif // SKSL_STANDALONE
class GrShaderCaps;
namespace SkSL {
class OutputStream;
class StringStream;
#if defined(SKSL_STANDALONE) || !SK_SUPPORT_GPU
// we're being compiled standalone, so we don't have access to caps...
@ -392,30 +388,8 @@ public:
void write_stringstream(const StringStream& d, OutputStream& out);
#if _MSC_VER
#define NORETURN __declspec(noreturn)
#else
#define NORETURN __attribute__((__noreturn__))
#endif
NORETURN void sksl_abort();
} // namespace
#ifdef SKSL_STANDALONE
#define SkASSERT(x)
#define SkAssertResult(x) x
#define SkDEBUGCODE(x)
#endif
#define SKSL_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#if defined(__clang__) || defined(__GNUC__)
#define SKSL_PRINTF_LIKE(A, B) __attribute__((format(printf, (A), (B))))
#else
#define SKSL_PRINTF_LIKE(A, B)
#endif
#define ABORT(...) (printf(__VA_ARGS__), sksl_abort())
#endif

126
tests/SkSLMetalTest.cpp Normal file
View File

@ -0,0 +1,126 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkSLCompiler.h"
#include "Test.h"
static void test(skiatest::Reporter* r, const char* src, const SkSL::Program::Settings& settings,
const char* expected, SkSL::Program::Inputs* inputs,
SkSL::Program::Kind kind = SkSL::Program::kFragment_Kind) {
SkSL::Compiler compiler;
SkSL::String output;
std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, SkSL::String(src),
settings);
if (!program) {
SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
}
REPORTER_ASSERT(r, program);
*inputs = program->fInputs;
REPORTER_ASSERT(r, compiler.toMetal(*program, &output));
if (program) {
SkSL::String skExpected(expected);
if (output != skExpected) {
SkDebugf("MSL MISMATCH:\nsource:\n%s\n\nexpected:\n'%s'\n\nreceived:\n'%s'", src,
expected, output.c_str());
}
REPORTER_ASSERT(r, output == skExpected);
}
}
static void test(skiatest::Reporter* r, const char* src, const GrShaderCaps& caps,
const char* expected, SkSL::Program::Kind kind = SkSL::Program::kFragment_Kind) {
SkSL::Program::Settings settings;
settings.fCaps = &caps;
SkSL::Program::Inputs inputs;
test(r, src, settings, expected, &inputs, kind);
}
DEF_TEST(SkSLMetalHelloWorld, r) {
test(r,
"void main() { sk_FragColor = float4(0.75); }",
*SkSL::ShaderCapsFactory::Default(),
"#include <metal_stdlib>\n"
"#include <simd/simd.h>\n"
"using namespace metal;\n"
"struct Inputs {\n"
"};\n"
"struct Outputs {\n"
" float4 sk_FragColor [[color(0)]];\n"
"};\n"
"struct sksl_synthetic_uniforms {\n"
" float u_skRTHeight;\n"
"};\n"
"fragment Outputs fragmentMain(Inputs _in [[stage_in]], constant sksl_synthetic_uniforms& _anonInterface0 [[buffer(1)]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {\n"
" Outputs _outputStruct;\n"
" thread Outputs* _out = &_outputStruct;\n"
" _out->sk_FragColor = float4(0.75);\n"
" return *_out;\n"
"}\n");
}
DEF_TEST(SkSLMetalMatrices, r) {
test(r,
"void main() {"
"float2x2 m1 = float2x2(float4(1, 2, 3, 4));"
"float2x2 m2 = float2x2(float4(0));"
"float2x2 m3 = float2x2(m1);"
"float2x2 m4 = float2x2(1);"
"float2x2 m5 = float2x2(m1[0][0]);"
"float2x2 m6 = float2x2(1, 2, 3, 4);"
"float2x2 m7 = float2x2(5, 6, 7, 8);"
"float3x3 m8 = float3x3(1);"
"float3x3 m9 = float3x3(2);"
"float4x4 m10 = float4x4(1);"
"float4x4 m11 = float4x4(2);"
"sk_FragColor = half4(m1[0][0] + m2[0][0] + m3[0][0] + m4[0][0] + m5[0][0] + m6[0][0] + "
"m7[0][0] + m8[0][0] + m9[0][0] + m10[0][0] + m11[0][0]);"
"}",
*SkSL::ShaderCapsFactory::Default(),
"#include <metal_stdlib>\n"
"#include <simd/simd.h>\n"
"using namespace metal;\n"
"struct Inputs {\n"
"};\n"
"struct Outputs {\n"
" float4 sk_FragColor [[color(0)]];\n"
"};\n"
"struct sksl_synthetic_uniforms {\n"
" float u_skRTHeight;\n"
"};\n"
"float2x2 float2x2_from_float(float x) {\n"
" return float2x2(float2(x, 0), float2(0, x));\n"
"}\n"
"float2x2 float2x2_from_float4(float4 v) {\n"
" return float2x2(float2(v[0], v[1]), float2(v[2], v[3]));\n"
"}\n"
"float3x3 float3x3_from_float(float x) {\n"
" return float3x3(float3(x, 0, 0), float3(0, x, 0), float3(0, 0, x));\n"
"}\n"
"float4x4 float4x4_from_float(float x) {\n"
" return float4x4(float4(x, 0, 0, 0), float4(0, x, 0, 0), float4(0, 0, x, 0), float4(0,"
" 0, 0, x));\n"
"}\n"
"fragment Outputs fragmentMain(Inputs _in [[stage_in]], constant sksl_synthetic_uniforms& "
"_anonInterface0 [[buffer(1)]], bool _frontFacing [[front_facing]], float4 "
"_fragCoord [[position]]) {\n"
" Outputs _outputStruct;\n"
" thread Outputs* _out = &_outputStruct;\n"
" float2x2 m5 = float2x2_from_float(float2x2_from_float4(float4(1.0, 2.0, 3.0, "
"4.0))[0][0]);\n"
" _out->sk_FragColor = float4((((((((((float2x2_from_float4(float4(1.0, 2.0, 3.0, "
"4.0))[0][0] + float2x2_from_float4(float4(0.0))[0][0]) + "
"float2x2_from_float4(float4(1.0, 2.0, 3.0, 4.0))[0][0]) + "
"float2x2_from_float(1.0)[0][0]) + m5[0][0]) + float2x2(float2(1.0, 2.0), "
"float2(3.0, 4.0))[0][0]) + float2x2(float2(5.0, 6.0), float2(7.0, 8.0))[0][0]) + "
"float3x3_from_float(1.0)[0][0]) + float3x3_from_float(2.0)[0][0]) + "
"float4x4_from_float(1.0)[0][0]) + float4x4_from_float(2.0)[0][0]);\n"
" return *_out;\n"
"}\n"
);
}