Interpreter: Fix construction of Matrices

Per GLSL, constructing a matrix from a scalar produces a matrix
with the scalar value along the diagonal, and zero elsewhere.
Constructing a matrix from another matrix copies the overlapping
values, and fills in the remainder with the identity matrix.

Doing either of these with existing opcodes was going to be quite
verbose and tricky, so I just made new opcodes.

I've also got some (currently disabled) test cases for other
matrix behavior, all of which fail in various ways today.

Change-Id: Ia86a183395f1ac7e2f23ee1d6bb4af461f5ba93a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/215823
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2019-05-28 17:16:03 -04:00 committed by Skia Commit-Bot
parent c7e9f78d10
commit 29e013deb4
4 changed files with 138 additions and 7 deletions

View File

@ -74,6 +74,11 @@ enum class ByteCodeInstruction : uint16_t {
// count byte, and get the slot to load from the top of the stack.
kLoadExtended,
kLoadExtendedGlobal,
// Followed by four bytes: srcCols, srcRows, dstCols, dstRows. Consumes the src matrix from the
// stack, and replaces it with the dst matrix. Per GLSL rules, there are no restrictions on
// dimensions. Any overlapping values are copied, and any other values are filled in with the
// identity matrix.
kMatrixToMatrix,
VECTOR(kNegateF),
VECTOR(kNegateI),
VECTOR(kMix),
@ -94,6 +99,10 @@ enum class ByteCodeInstruction : uint16_t {
VECTOR(kRemainderU),
// Followed by a byte indicating the number of slots being returned
kReturn,
// Followed by two bytes indicating columns and rows of matrix (2, 3, or 4 each).
// Takes a single value from the top of the stack, and converts to a CxR matrix with that value
// replicated along the diagonal (and zero elsewhere), per the GLSL matrix construction rules.
kScalarToMatrix,
VECTOR(kSin),
VECTOR(kSqrt),
// kStore/kStoreGlobal are followed by a byte indicating the local/global slot to store

View File

@ -443,10 +443,12 @@ void ByteCodeGenerator::writeConstructor(const Constructor& c) {
this->writeExpression(*arg);
}
if (c.fArguments.size() == 1) {
TypeCategory inCategory = type_category(c.fArguments[0]->fType);
TypeCategory outCategory = type_category(c.fType);
int inCount = c.fArguments[0]->fType.columns();
int outCount = c.fType.columns();
const Type& inType = c.fArguments[0]->fType;
const Type& outType = c.fType;
TypeCategory inCategory = type_category(inType);
TypeCategory outCategory = type_category(outType);
int inCount = SlotCount(inType);
int outCount = SlotCount(outType);
if (inCategory != outCategory) {
SkASSERT(inCount == outCount);
if (inCategory == TypeCategory::kFloat) {
@ -464,10 +466,23 @@ void ByteCodeGenerator::writeConstructor(const Constructor& c) {
SkASSERT(false);
}
}
if (inCount != outCount) {
if (inType.kind() == Type::kMatrix_Kind && outType.kind() == Type::kMatrix_Kind) {
this->write(ByteCodeInstruction::kMatrixToMatrix);
this->write8(inType.columns());
this->write8(inType.rows());
this->write8(outType.columns());
this->write8(outType.rows());
} else if (inCount != outCount) {
SkASSERT(inCount == 1);
for (; inCount != outCount; ++inCount) {
this->write(ByteCodeInstruction::kDup);
if (outType.kind() == Type::kMatrix_Kind) {
this->write(ByteCodeInstruction::kScalarToMatrix);
this->write8(outType.columns());
this->write8(outType.rows());
} else {
SkASSERT(outType.kind() == Type::kVector_Kind);
for (; inCount != outCount; ++inCount) {
this->write(ByteCodeInstruction::kDup);
}
}
}
}

View File

@ -165,6 +165,14 @@ static const uint8_t* disassemble_instruction(const uint8_t* ip) {
case ByteCodeInstruction::kLoadExtended: printf("loadextended %d", READ8()); break;
case ByteCodeInstruction::kLoadExtendedGlobal: printf("loadextendedglobal %d", READ8());
break;
case ByteCodeInstruction::kMatrixToMatrix: {
int srcCols = READ8();
int srcRows = READ8();
int dstCols = READ8();
int dstRows = READ8();
printf("matrixtomatrix %dx%d %dx%d", srcCols, srcRows, dstCols, dstRows);
break;
}
VECTOR_DISASSEMBLE(kMix, "mix")
VECTOR_DISASSEMBLE(kMultiplyF, "multiplyf")
VECTOR_DISASSEMBLE(kMultiplyI, "multiplyi")
@ -189,6 +197,12 @@ static const uint8_t* disassemble_instruction(const uint8_t* ip) {
VECTOR_DISASSEMBLE(kRemainderS, "remainders")
VECTOR_DISASSEMBLE(kRemainderU, "remainderu")
case ByteCodeInstruction::kReturn: printf("return %d", READ8()); break;
case ByteCodeInstruction::kScalarToMatrix: {
int cols = READ8();
int rows = READ8();
printf("scalartomatrix %dx%d", cols, rows);
break;
}
VECTOR_DISASSEMBLE(kSin, "sin")
VECTOR_DISASSEMBLE(kSqrt, "sqrt")
case ByteCodeInstruction::kStore: printf("store %d", READ8()); break;
@ -517,6 +531,29 @@ void Interpreter::innerRun(const ByteCodeFunction& f, Value* stack, Value* outRe
break;
}
case ByteCodeInstruction::kMatrixToMatrix: {
int srcCols = READ8();
int srcRows = READ8();
int dstCols = READ8();
int dstRows = READ8();
SkASSERT(srcCols >= 2 && srcCols <= 4);
SkASSERT(srcRows >= 2 && srcRows <= 4);
SkASSERT(dstCols >= 2 && dstCols <= 4);
SkASSERT(dstRows >= 2 && dstRows <= 4);
SkMatrix44 m;
for (int c = srcCols - 1; c >= 0; --c) {
for (int r = srcRows - 1; r >= 0; --r) {
m.set(r, c, POP().fFloat);
}
}
for (int c = 0; c < dstCols; ++c) {
for (int r = 0; r < dstRows; ++r) {
PUSH(m.get(r, c));
}
}
break;
}
// stack looks like: X1 Y1 Z1 W1 X2 Y2 Z2 W2 T
case ByteCodeInstruction::kMix4:
sp[-5] = mix(sp[-5].fFloat, sp[-1].fFloat, sp[0].fFloat);
@ -612,6 +649,18 @@ void Interpreter::innerRun(const ByteCodeFunction& f, Value* stack, Value* outRe
}
}
case ByteCodeInstruction::kScalarToMatrix: {
int cols = READ8();
int rows = READ8();
Value v = POP();
for (int c = 0; c < cols; ++c) {
for (int r = 0; r < rows; ++r) {
PUSH(c == r ? v : 0.0f);
}
}
break;
}
VECTOR_UNARY_FN(kSin, sinf, fFloat)
VECTOR_UNARY_FN(kSqrt, sqrtf, fFloat)

View File

@ -49,6 +49,7 @@ void test(skiatest::Reporter* r, const char* src, SkSL::Interpreter::Value* in,
separator = ", ";
}
printf(")\n");
interpreter.disassemble(*main);
}
REPORTER_ASSERT(r, valid);
} else {
@ -149,6 +150,63 @@ DEF_TEST(SkSLInterpreterRemainder, r) {
0, 2, 4, 0, 0);
}
DEF_TEST(SkSLInterpreterMatrix, r) {
SkSL::Interpreter::Value in[4];
SkSL::Interpreter::Value expected[1];
// Constructing matrix from scalar produces a diagonal matrix
in[0] = 1.0f;
expected[0] = 2.0f;
test(r, "float main(float x) { float4x4 m = float4x4(x); return m[1][1] + m[1][2] + m[2][2]; }",
in, 1, expected);
// With non-square matrix
test(r, "float main(float x) { float3x2 m = float3x2(x); return m[0][0] + m[1][1] + m[2][1]; }",
in, 1, expected);
// Constructing from a different-sized matrix fills the remaining space with the identity matrix
test(r, "float main(float x) {"
"float3x2 m = float3x2(x);"
"float4x4 m2 = float4x4(m);"
"return m2[0][0] + m2[3][3]; }",
in, 1, expected);
// Constructing a matrix from vectors or scalars fills in values in column-major order
in[0] = 1.0f;
in[1] = 2.0f;
in[2] = 4.0f;
in[3] = 8.0f;
expected[0] = 6.0f;
test(r, "float main(float4 v) { float2x2 m = float2x2(v); return m[0][1] + m[1][0]; }",
in, 1, expected);
expected[0] = 10.0f;
test(r, "float main(float4 v) {"
"float2x2 m = float2x2(v.x, v.y, v.w, v.z);"
"return m[0][1] + m[1][0]; }",
in, 1, expected);
#if 0
// Addition of matrices
test(r, "void main(inout half4 color) {"
"half4x4 m = half4x4(color, color, color, color);"
"m += m; color = m[0]; }",
1, 2, 3, 4, 2, 4, 6, 8);
// Matrix * Vector multiplication
test(r, "void main(inout half4 color) {"
"half4x4 m = half4x4(color, color, color, color);"
"color = m * color; }",
1, 2, 3, 4, 10, 20, 30, 40);
// Matrix * Matrix multiplication
test(r, "void main(inout half4 color) {"
"half4x4 m = half4x4(color, color, color, color);"
"m = m * m; color = m[2]; }",
1, 2, 3, 4, 10, 20, 30, 40);
#endif
}
DEF_TEST(SkSLInterpreterTernary, r) {
test(r, "void main(inout half4 color) { color.r = color.g > color.b ? color.g : color.b; }",
0, 1, 2, 0, 2, 1, 2, 0);