Initial checkin of new SkSL interpreter.
Not quite feature complete yet, but at a point where it's worth checking in. Bug: skia: Change-Id: I21141d30e8582a79e94450d84e56bacc067249e0 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/201685 Commit-Queue: Ethan Nicholas <ethannicholas@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com>
This commit is contained in:
parent
6463913201
commit
0e9401dafe
@ -33,7 +33,7 @@ DEF_SIMPLE_GPU_GM(runtimecolorfilter, context, rtc, canvas, 768, 256) {
|
||||
canvas->drawImage(img, 0, 0, nullptr);
|
||||
|
||||
float b = 0.75;
|
||||
sk_sp<SkData> data = SkData::MakeWithoutCopy(&b, sizeof(b));
|
||||
sk_sp<SkData> data = SkData::MakeWithCopy(&b, sizeof(b));
|
||||
auto cf1 = SkRuntimeColorFilterFactory(SkString(SKSL_TEST_SRC), runtimeCpuFunc).make(data);
|
||||
SkPaint p;
|
||||
p.setColorFilter(cf1);
|
||||
@ -49,3 +49,25 @@ DEF_SIMPLE_GPU_GM(runtimecolorfilter, context, rtc, canvas, 768, 256) {
|
||||
p.setColorFilter(cf2);
|
||||
canvas->drawImage(img, 512, 0, &p);
|
||||
}
|
||||
|
||||
DEF_SIMPLE_GM(runtimecolorfilter_interpreted, canvas, 768, 256) {
|
||||
auto img = GetResourceAsImage("images/mandrill_256.png");
|
||||
canvas->drawImage(img, 0, 0, nullptr);
|
||||
|
||||
float b = 0.75;
|
||||
sk_sp<SkData> data = SkData::MakeWithCopy(&b, sizeof(b));
|
||||
auto cf1 = SkRuntimeColorFilterFactory(SkString(SKSL_TEST_SRC), nullptr).make(data);
|
||||
SkPaint p;
|
||||
p.setColorFilter(cf1);
|
||||
canvas->drawImage(img, 256, 0, &p);
|
||||
|
||||
static constexpr size_t kBufferSize = 512;
|
||||
char buffer[kBufferSize];
|
||||
SkBinaryWriteBuffer wb(buffer, kBufferSize);
|
||||
wb.writeFlattenable(cf1.get());
|
||||
SkReadBuffer rb(buffer, kBufferSize);
|
||||
auto cf2 = rb.readColorFilter();
|
||||
SkASSERT(cf2);
|
||||
p.setColorFilter(cf2);
|
||||
canvas->drawImage(img, 512, 0, &p);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
_src = get_path_info("../src", "abspath")
|
||||
|
||||
skia_sksl_sources = [
|
||||
"$_src/sksl/SkSLByteCodeGenerator.cpp",
|
||||
"$_src/sksl/SkSLCFGGenerator.cpp",
|
||||
"$_src/sksl/SkSLCompiler.cpp",
|
||||
"$_src/sksl/SkSLCPPCodeGenerator.cpp",
|
||||
|
@ -239,6 +239,7 @@ tests_sources = [
|
||||
"$_tests/SkSLErrorTest.cpp",
|
||||
"$_tests/SkSLFPTest.cpp",
|
||||
"$_tests/SkSLGLSLTest.cpp",
|
||||
"$_tests/SkSLInterpreterTest.cpp",
|
||||
"$_tests/SkSLJITTest.cpp",
|
||||
"$_tests/SkSLMemoryLayoutTest.cpp",
|
||||
"$_tests/SkSLMetalTest.cpp",
|
||||
|
@ -382,6 +382,8 @@ sk_sp<SkColorFilter> SkColorFilter::MakeLerp(sk_sp<SkColorFilter> cf0,
|
||||
#if SK_SUPPORT_GPU
|
||||
#include "effects/GrSkSLFP.h"
|
||||
#include "GrRecordingContext.h"
|
||||
#include "SkSLByteCode.h"
|
||||
#include "SkSLInterpreter.h"
|
||||
|
||||
class SkRuntimeColorFilter : public SkColorFilter {
|
||||
public:
|
||||
@ -402,24 +404,51 @@ public:
|
||||
#endif
|
||||
|
||||
void onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override {
|
||||
// if this assert fails, it either means no CPU function was provided when this filter was
|
||||
// created, or we have flattened and unflattened the filter, which nulls out this pointer.
|
||||
// We don't currently have a means to flatten colorfilters containing CPU functions.
|
||||
SkASSERT(fCpuFunction);
|
||||
struct Ctx : public SkRasterPipeline_CallbackCtx {
|
||||
SkRuntimeColorFilterFn cpuFn;
|
||||
const void* inputs;
|
||||
};
|
||||
auto ctx = rec.fAlloc->make<Ctx>();
|
||||
ctx->inputs = fInputs->data();
|
||||
ctx->cpuFn = fCpuFunction;
|
||||
ctx->fn = [](SkRasterPipeline_CallbackCtx* arg, int active_pixels) {
|
||||
auto ctx = (Ctx*)arg;
|
||||
for (int i = 0; i < active_pixels; i++) {
|
||||
ctx->cpuFn(ctx->rgba + i * 4, ctx->inputs);
|
||||
if (fCpuFunction) {
|
||||
struct CpuFuncCtx : public SkRasterPipeline_CallbackCtx {
|
||||
SkRuntimeColorFilterFn cpuFn;
|
||||
const void* inputs;
|
||||
};
|
||||
auto ctx = rec.fAlloc->make<CpuFuncCtx>();
|
||||
ctx->inputs = fInputs->data();
|
||||
ctx->cpuFn = fCpuFunction;
|
||||
ctx->fn = [](SkRasterPipeline_CallbackCtx* arg, int active_pixels) {
|
||||
auto ctx = (CpuFuncCtx*)arg;
|
||||
for (int i = 0; i < active_pixels; i++) {
|
||||
ctx->cpuFn(ctx->rgba + i * 4, ctx->inputs);
|
||||
}
|
||||
};
|
||||
rec.fPipeline->append(SkRasterPipeline::callback, ctx);
|
||||
} else {
|
||||
struct InterpreterCtx : public SkRasterPipeline_CallbackCtx {
|
||||
SkSL::ByteCodeFunction* main;
|
||||
std::unique_ptr<SkSL::Interpreter> interpreter;
|
||||
const void* inputs;
|
||||
};
|
||||
auto ctx = rec.fAlloc->make<InterpreterCtx>();
|
||||
ctx->inputs = fInputs->data();
|
||||
SkSL::Compiler c;
|
||||
std::unique_ptr<SkSL::Program> prog =
|
||||
c.convertProgram(SkSL::Program::kPipelineStage_Kind,
|
||||
SkSL::String(fSkSL.c_str()),
|
||||
SkSL::Program::Settings());
|
||||
if (c.errorCount()) {
|
||||
SkDebugf("%s\n", c.errorText().c_str());
|
||||
SkASSERT(false);
|
||||
}
|
||||
};
|
||||
rec.fPipeline->append(SkRasterPipeline::callback, ctx);
|
||||
std::unique_ptr<SkSL::ByteCode> byteCode = c.toByteCode(*prog);
|
||||
ctx->main = byteCode->fFunctions[0].get();
|
||||
ctx->interpreter.reset(new SkSL::Interpreter(std::move(prog), std::move(byteCode)));
|
||||
ctx->fn = [](SkRasterPipeline_CallbackCtx* arg, int active_pixels) {
|
||||
auto ctx = (InterpreterCtx*)arg;
|
||||
for (int i = 0; i < active_pixels; i++) {
|
||||
ctx->interpreter->run(*ctx->main,
|
||||
(SkSL::Interpreter::Value*) (ctx->rgba + i * 4),
|
||||
(SkSL::Interpreter::Value*) ctx->inputs);
|
||||
}
|
||||
};
|
||||
rec.fPipeline->append(SkRasterPipeline::callback, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
115
src/sksl/SkSLByteCode.h
Normal file
115
src/sksl/SkSLByteCode.h
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SKSL_BYTECODE
|
||||
#define SKSL_BYTECODE
|
||||
|
||||
#include "ir/SkSLFunctionDeclaration.h"
|
||||
|
||||
namespace SkSL {
|
||||
|
||||
enum class ByteCodeInstruction : uint8_t {
|
||||
kInvalid,
|
||||
// B = bool, F = float, I = int, S = signed, U = unsigned
|
||||
kAddF,
|
||||
kAddI,
|
||||
kAndB,
|
||||
kAndI,
|
||||
kBranch,
|
||||
kCompareIEQ,
|
||||
kCompareINEQ,
|
||||
kCompareFEQ,
|
||||
kCompareFGT,
|
||||
kCompareFGTEQ,
|
||||
kCompareFLT,
|
||||
kCompareFLTEQ,
|
||||
kCompareFNEQ,
|
||||
kCompareSGT,
|
||||
kCompareSGTEQ,
|
||||
kCompareSLT,
|
||||
kCompareSLTEQ,
|
||||
kCompareUGT,
|
||||
kCompareUGTEQ,
|
||||
kCompareULT,
|
||||
kCompareULTEQ,
|
||||
// Followed by a 16 bit address
|
||||
kConditionalBranch,
|
||||
// Pops and prints the top value from the stack
|
||||
kDebugPrint,
|
||||
kDivideF,
|
||||
kDivideS,
|
||||
kDivideU,
|
||||
// Duplicates the top stack value
|
||||
kDup,
|
||||
// Followed by a byte indicating number of slots to copy below the underlying element.
|
||||
// dupdown 2 yields: ... value3 value2 value1 => .. value2 value1 value3 value2 value2
|
||||
kDupDown,
|
||||
kFloatToInt,
|
||||
kSignedToFloat,
|
||||
kUnsignedToFloat,
|
||||
kLoad,
|
||||
// Followed by a byte indicating global slot to load
|
||||
kLoadGlobal,
|
||||
// Followed by a count byte (1-4), and then one byte per swizzle component (0-3).
|
||||
kLoadSwizzle,
|
||||
kNegateF,
|
||||
kNegateS,
|
||||
kMultiplyF,
|
||||
kMultiplyS,
|
||||
kMultiplyU,
|
||||
kNot,
|
||||
kOrB,
|
||||
kOrI,
|
||||
// Followed by a byte indicating parameter slot to load
|
||||
kParameter,
|
||||
kPop,
|
||||
// Followed by a 32 bit value containing the value to push
|
||||
kPushImmediate,
|
||||
kRemainderS,
|
||||
kRemainderU,
|
||||
kStore,
|
||||
kStoreGlobal,
|
||||
// Followed by a count byte (1-4), and then one byte per swizzle component (0-3). Expects the
|
||||
// stack to look like: ... target v1 v2 v3 v4, where the number of 'v's is equal to the number
|
||||
// of swizzle components. After the store, the target and all v's are popped from the stack.
|
||||
kStoreSwizzle,
|
||||
// Followed by two count bytes (1-4), and then one byte per swizzle component (0-3). The first
|
||||
// count byte provides the current vector size (the vector is the top n stack elements), and the
|
||||
// second count byte provides the swizzle component count.
|
||||
kSwizzle,
|
||||
kSubtractF,
|
||||
kSubtractI,
|
||||
// Followed by a byte indicating vector count. Modifies the next instruction to operate on the
|
||||
// indicated number of columns, e.g. kVector 2 kMultiplyf performs a float2 * float2 operation.
|
||||
kVector,
|
||||
};
|
||||
|
||||
struct ByteCode;
|
||||
|
||||
struct ByteCodeFunction {
|
||||
ByteCodeFunction(const ByteCode* owner, const FunctionDeclaration* declaration)
|
||||
: fOwner(*owner)
|
||||
, fDeclaration(*declaration) {}
|
||||
|
||||
const ByteCode& fOwner;
|
||||
const FunctionDeclaration& fDeclaration;
|
||||
int fParameterCount = 0;
|
||||
int fLocalCount = 0;
|
||||
std::vector<uint8_t> fCode;
|
||||
};
|
||||
|
||||
struct ByteCode {
|
||||
int fGlobalCount = 0;
|
||||
int fInputCount = 0;
|
||||
// one entry per input slot, contains the global slot to which the input slot maps
|
||||
std::vector<uint8_t> fInputSlots;
|
||||
std::vector<std::unique_ptr<ByteCodeFunction>> fFunctions;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
787
src/sksl/SkSLByteCodeGenerator.cpp
Normal file
787
src/sksl/SkSLByteCodeGenerator.cpp
Normal file
@ -0,0 +1,787 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkSLByteCodeGenerator.h"
|
||||
|
||||
namespace SkSL {
|
||||
|
||||
static int slot_count(const Type& type) {
|
||||
return type.columns() * type.rows();
|
||||
}
|
||||
|
||||
bool ByteCodeGenerator::generateCode() {
|
||||
for (const auto& e : fProgram) {
|
||||
switch (e.fKind) {
|
||||
case ProgramElement::kFunction_Kind: {
|
||||
std::unique_ptr<ByteCodeFunction> f = this->writeFunction((FunctionDefinition&) e);
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
fOutput->fFunctions.push_back(std::move(f));
|
||||
break;
|
||||
}
|
||||
case ProgramElement::kVar_Kind: {
|
||||
VarDeclarations& decl = (VarDeclarations&) e;
|
||||
for (const auto& v : decl.fVars) {
|
||||
const Variable* declVar = ((VarDeclaration&) *v).fVar;
|
||||
if (declVar->fModifiers.fLayout.fBuiltin >= 0) {
|
||||
continue;
|
||||
}
|
||||
if (declVar->fModifiers.fFlags & Modifiers::kIn_Flag) {
|
||||
for (int i = slot_count(declVar->fType); i > 0; --i) {
|
||||
fOutput->fInputSlots.push_back(fOutput->fGlobalCount++);
|
||||
}
|
||||
} else {
|
||||
fOutput->fGlobalCount += slot_count(declVar->fType);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
; // ignore
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<ByteCodeFunction> ByteCodeGenerator::writeFunction(const FunctionDefinition& f) {
|
||||
fFunction = &f;
|
||||
std::unique_ptr<ByteCodeFunction> result(new ByteCodeFunction(fOutput, &f.fDeclaration));
|
||||
fParameterCount = 0;
|
||||
for (const auto& p : f.fDeclaration.fParameters) {
|
||||
fParameterCount += p->fType.columns() * p->fType.rows();
|
||||
}
|
||||
fCode = &result->fCode;
|
||||
this->writeStatement(*f.fBody);
|
||||
result->fParameterCount = fParameterCount;
|
||||
result->fLocalCount = fLocals.size();
|
||||
fLocals.clear();
|
||||
fFunction = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
enum class TypeCategory {
|
||||
kBool,
|
||||
kSigned,
|
||||
kUnsigned,
|
||||
kFloat,
|
||||
};
|
||||
|
||||
static TypeCategory type_category(const Type& type) {
|
||||
switch (type.kind()) {
|
||||
case Type::Kind::kVector_Kind:
|
||||
case Type::Kind::kMatrix_Kind:
|
||||
return type_category(type.componentType());
|
||||
default:
|
||||
if (type.fName == "bool") {
|
||||
return TypeCategory::kBool;
|
||||
} else if (type.fName == "int" || type.fName == "short") {
|
||||
return TypeCategory::kSigned;
|
||||
} else if (type.fName == "uint" || type.fName == "ushort") {
|
||||
return TypeCategory::kUnsigned;
|
||||
} else {
|
||||
SkASSERT(type.fName == "float" || type.fName == "half");
|
||||
return TypeCategory::kFloat;
|
||||
}
|
||||
ABORT("unsupported type: %s\n", type.description().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int ByteCodeGenerator::getLocation(const Variable& var) {
|
||||
// given that we seldom have more than a couple of variables, linear search is probably the most
|
||||
// efficient way to handle lookups
|
||||
switch (var.fStorage) {
|
||||
case Variable::kLocal_Storage: {
|
||||
for (int i = fLocals.size() - 1; i >= 0; --i) {
|
||||
if (fLocals[i] == &var) {
|
||||
return fParameterCount + i;
|
||||
}
|
||||
}
|
||||
int result = fParameterCount + fLocals.size();
|
||||
fLocals.push_back(&var);
|
||||
for (int i = 0; i < slot_count(var.fType) - 1; ++i) {
|
||||
fLocals.push_back(nullptr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case Variable::kParameter_Storage: {
|
||||
int offset = 0;
|
||||
for (const auto& p : fFunction->fDeclaration.fParameters) {
|
||||
if (p == &var) {
|
||||
return offset;
|
||||
}
|
||||
offset += slot_count(p->fType);
|
||||
}
|
||||
SkASSERT(false);
|
||||
return -1;
|
||||
}
|
||||
case Variable::kGlobal_Storage: {
|
||||
int offset = 0;
|
||||
for (const auto& e : fProgram) {
|
||||
if (e.fKind == ProgramElement::kVar_Kind) {
|
||||
VarDeclarations& decl = (VarDeclarations&) e;
|
||||
for (const auto& v : decl.fVars) {
|
||||
const Variable* declVar = ((VarDeclaration&) *v).fVar;
|
||||
if (declVar->fModifiers.fLayout.fBuiltin >= 0) {
|
||||
continue;
|
||||
}
|
||||
if (declVar == &var) {
|
||||
return offset;
|
||||
}
|
||||
offset += slot_count(declVar->fType);
|
||||
}
|
||||
}
|
||||
}
|
||||
SkASSERT(false);
|
||||
return -1;
|
||||
}
|
||||
default:
|
||||
SkASSERT(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::write8(uint8_t b) {
|
||||
fCode->push_back(b);
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::write16(uint16_t i) {
|
||||
this->write8(i >> 8);
|
||||
this->write8(i);
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::write32(uint32_t i) {
|
||||
this->write8(i >> 24);
|
||||
this->write8(i >> 16);
|
||||
this->write8(i >> 8);
|
||||
this->write8(i);
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::write(ByteCodeInstruction i) {
|
||||
this->write8((uint8_t) i);
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeTypedInstruction(const Type& type, ByteCodeInstruction s,
|
||||
ByteCodeInstruction u, ByteCodeInstruction f) {
|
||||
switch (type_category(type)) {
|
||||
case TypeCategory::kSigned:
|
||||
this->write(s);
|
||||
break;
|
||||
case TypeCategory::kUnsigned:
|
||||
this->write(u);
|
||||
break;
|
||||
case TypeCategory::kFloat:
|
||||
this->write(f);
|
||||
break;
|
||||
default:
|
||||
SkASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeBinaryExpression(const BinaryExpression& b) {
|
||||
if (b.fOperator == Token::Kind::EQ) {
|
||||
std::unique_ptr<LValue> lvalue = this->getLValue(*b.fLeft);
|
||||
this->writeExpression(*b.fRight);
|
||||
this->write(ByteCodeInstruction::kDupDown);
|
||||
this->write8(slot_count(b.fRight->fType));
|
||||
lvalue->store();
|
||||
return;
|
||||
}
|
||||
Token::Kind op;
|
||||
std::unique_ptr<LValue> lvalue;
|
||||
if (is_assignment(b.fOperator)) {
|
||||
lvalue = this->getLValue(*b.fLeft);
|
||||
lvalue->load();
|
||||
op = remove_assignment(b.fOperator);
|
||||
} else {
|
||||
this->writeExpression(*b.fLeft);
|
||||
op = b.fOperator;
|
||||
if (b.fLeft->fType.kind() == Type::kScalar_Kind &&
|
||||
b.fRight->fType.kind() == Type::kVector_Kind) {
|
||||
for (int i = b.fRight->fType.columns(); i > 1; --i) {
|
||||
this->write(ByteCodeInstruction::kDup);
|
||||
}
|
||||
}
|
||||
}
|
||||
this->writeExpression(*b.fRight);
|
||||
if (b.fLeft->fType.kind() == Type::kVector_Kind &&
|
||||
b.fRight->fType.kind() == Type::kScalar_Kind) {
|
||||
for (int i = b.fLeft->fType.columns(); i > 1; --i) {
|
||||
this->write(ByteCodeInstruction::kDup);
|
||||
}
|
||||
}
|
||||
int count = slot_count(b.fType);
|
||||
if (count > 1) {
|
||||
this->write(ByteCodeInstruction::kVector);
|
||||
this->write8(count);
|
||||
}
|
||||
switch (op) {
|
||||
case Token::Kind::EQEQ:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareIEQ,
|
||||
ByteCodeInstruction::kCompareIEQ,
|
||||
ByteCodeInstruction::kCompareFEQ);
|
||||
break;
|
||||
case Token::Kind::GT:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareSGT,
|
||||
ByteCodeInstruction::kCompareUGT,
|
||||
ByteCodeInstruction::kCompareFGT);
|
||||
break;
|
||||
case Token::Kind::GTEQ:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareSGTEQ,
|
||||
ByteCodeInstruction::kCompareUGTEQ,
|
||||
ByteCodeInstruction::kCompareFGTEQ);
|
||||
break;
|
||||
case Token::Kind::LT:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareSLT,
|
||||
ByteCodeInstruction::kCompareULT,
|
||||
ByteCodeInstruction::kCompareFLT);
|
||||
break;
|
||||
case Token::Kind::LTEQ:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareSLTEQ,
|
||||
ByteCodeInstruction::kCompareULTEQ,
|
||||
ByteCodeInstruction::kCompareFLTEQ);
|
||||
break;
|
||||
case Token::Kind::MINUS:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kSubtractI,
|
||||
ByteCodeInstruction::kSubtractI,
|
||||
ByteCodeInstruction::kSubtractF);
|
||||
break;
|
||||
case Token::Kind::NEQ:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kCompareINEQ,
|
||||
ByteCodeInstruction::kCompareINEQ,
|
||||
ByteCodeInstruction::kCompareFNEQ);
|
||||
break;
|
||||
case Token::Kind::PERCENT:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kRemainderS,
|
||||
ByteCodeInstruction::kRemainderU,
|
||||
ByteCodeInstruction::kInvalid);
|
||||
break;
|
||||
case Token::Kind::PLUS:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kAddI,
|
||||
ByteCodeInstruction::kAddI,
|
||||
ByteCodeInstruction::kAddF);
|
||||
break;
|
||||
case Token::Kind::SLASH:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kDivideS,
|
||||
ByteCodeInstruction::kDivideU,
|
||||
ByteCodeInstruction::kDivideF);
|
||||
break;
|
||||
case Token::Kind::STAR:
|
||||
this->writeTypedInstruction(b.fLeft->fType, ByteCodeInstruction::kMultiplyS,
|
||||
ByteCodeInstruction::kMultiplyU,
|
||||
ByteCodeInstruction::kMultiplyF);
|
||||
break;
|
||||
default:
|
||||
SkASSERT(false);
|
||||
}
|
||||
if (lvalue) {
|
||||
this->write(ByteCodeInstruction::kDupDown);
|
||||
this->write8(slot_count(b.fType));
|
||||
lvalue->store();
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeBoolLiteral(const BoolLiteral& b) {
|
||||
this->write(ByteCodeInstruction::kPushImmediate);
|
||||
this->write32(1);
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeConstructor(const Constructor& c) {
|
||||
if (c.fArguments.size() == 1 &&
|
||||
type_category(c.fType) == type_category(c.fArguments[0]->fType)) {
|
||||
// cast from float to half or similar no-op
|
||||
this->writeExpression(*c.fArguments[0]);
|
||||
return;
|
||||
}
|
||||
for (const auto& arg : c.fArguments) {
|
||||
this->writeExpression(*arg);
|
||||
}
|
||||
if (c.fArguments.size() == 1) {
|
||||
TypeCategory inCategory = type_category(c.fArguments[0]->fType);
|
||||
TypeCategory outCategory = type_category(c.fType);
|
||||
if (inCategory != outCategory) {
|
||||
int count = c.fType.columns();
|
||||
if (count > 1) {
|
||||
this->write(ByteCodeInstruction::kVector);
|
||||
this->write8(count);
|
||||
}
|
||||
if (inCategory == TypeCategory::kFloat) {
|
||||
SkASSERT(outCategory == TypeCategory::kSigned ||
|
||||
outCategory == TypeCategory::kUnsigned);
|
||||
this->write(ByteCodeInstruction::kFloatToInt);
|
||||
} else if (outCategory == TypeCategory::kFloat) {
|
||||
if (inCategory == TypeCategory::kSigned) {
|
||||
this->write(ByteCodeInstruction::kSignedToFloat);
|
||||
} else {
|
||||
SkASSERT(inCategory == TypeCategory::kUnsigned);
|
||||
this->write(ByteCodeInstruction::kUnsignedToFloat);
|
||||
}
|
||||
} else {
|
||||
SkASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeFieldAccess(const FieldAccess& f) {
|
||||
// not yet implemented
|
||||
abort();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeFloatLiteral(const FloatLiteral& f) {
|
||||
this->write(ByteCodeInstruction::kPushImmediate);
|
||||
union { float f; uint32_t u; } pun = { (float) f.fValue };
|
||||
this->write32(pun.u);
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeFunctionCall(const FunctionCall& f) {
|
||||
// not yet implemented
|
||||
abort();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeIndexExpression(const IndexExpression& i) {
|
||||
// not yet implemented
|
||||
abort();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeIntLiteral(const IntLiteral& i) {
|
||||
this->write(ByteCodeInstruction::kPushImmediate);
|
||||
this->write32(i.fValue);
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeNullLiteral(const NullLiteral& n) {
|
||||
// not yet implemented
|
||||
abort();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writePrefixExpression(const PrefixExpression& p) {
|
||||
switch (p.fOperator) {
|
||||
case Token::Kind::PLUSPLUS: // fall through
|
||||
case Token::Kind::MINUSMINUS: {
|
||||
std::unique_ptr<LValue> lvalue = this->getLValue(*p.fOperand);
|
||||
lvalue->load();
|
||||
this->write(ByteCodeInstruction::kPushImmediate);
|
||||
this->write32(1);
|
||||
if (p.fOperator == Token::Kind::PLUSPLUS) {
|
||||
this->writeTypedInstruction(p.fType,
|
||||
ByteCodeInstruction::kAddI,
|
||||
ByteCodeInstruction::kAddI,
|
||||
ByteCodeInstruction::kAddF);
|
||||
} else {
|
||||
this->writeTypedInstruction(p.fType,
|
||||
ByteCodeInstruction::kSubtractI,
|
||||
ByteCodeInstruction::kSubtractI,
|
||||
ByteCodeInstruction::kSubtractF);
|
||||
}
|
||||
this->write(ByteCodeInstruction::kDupDown);
|
||||
this->write8(slot_count(p.fType));
|
||||
lvalue->store();
|
||||
break;
|
||||
}
|
||||
case Token::Kind::MINUS:
|
||||
this->writeTypedInstruction(p.fType,
|
||||
ByteCodeInstruction::kNegateS,
|
||||
ByteCodeInstruction::kInvalid,
|
||||
ByteCodeInstruction::kNegateF);
|
||||
break;
|
||||
default:
|
||||
SkASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writePostfixExpression(const PostfixExpression& p) {
|
||||
// not yet implemented
|
||||
abort();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeSwizzle(const Swizzle& s) {
|
||||
switch (s.fBase->fKind) {
|
||||
case Expression::kVariableReference_Kind: {
|
||||
const Variable& var = ((VariableReference&) *s.fBase).fVariable;
|
||||
int location = this->getLocation(var);
|
||||
this->write(ByteCodeInstruction::kPushImmediate);
|
||||
this->write32(location);
|
||||
this->write(ByteCodeInstruction::kLoadSwizzle);
|
||||
this->write8(s.fComponents.size());
|
||||
for (int c : s.fComponents) {
|
||||
this->write8(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this->writeExpression(*s.fBase);
|
||||
this->write(ByteCodeInstruction::kSwizzle);
|
||||
this->write8(s.fBase->fType.columns());
|
||||
this->write8(s.fComponents.size());
|
||||
for (int c : s.fComponents) {
|
||||
this->write8(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeVariableReference(const VariableReference& v) {
|
||||
if (v.fVariable.fStorage == Variable::kGlobal_Storage) {
|
||||
this->write(ByteCodeInstruction::kLoadGlobal);
|
||||
int location = this->getLocation(v.fVariable);
|
||||
SkASSERT(location <= 255);
|
||||
this->write8(location);
|
||||
} else {
|
||||
this->write(ByteCodeInstruction::kPushImmediate);
|
||||
this->write32(this->getLocation(v.fVariable));
|
||||
int count = slot_count(v.fType);
|
||||
if (count > 1) {
|
||||
this->write(ByteCodeInstruction::kVector);
|
||||
this->write8(count);
|
||||
}
|
||||
this->write(ByteCodeInstruction::kLoad);
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeTernaryExpression(const TernaryExpression& t) {
|
||||
// not yet implemented
|
||||
abort();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeExpression(const Expression& e) {
|
||||
switch (e.fKind) {
|
||||
case Expression::kBinary_Kind:
|
||||
this->writeBinaryExpression((BinaryExpression&) e);
|
||||
break;
|
||||
case Expression::kBoolLiteral_Kind:
|
||||
this->writeBoolLiteral((BoolLiteral&) e);
|
||||
break;
|
||||
case Expression::kConstructor_Kind:
|
||||
this->writeConstructor((Constructor&) e);
|
||||
break;
|
||||
case Expression::kFieldAccess_Kind:
|
||||
this->writeFieldAccess((FieldAccess&) e);
|
||||
break;
|
||||
case Expression::kFloatLiteral_Kind:
|
||||
this->writeFloatLiteral((FloatLiteral&) e);
|
||||
break;
|
||||
case Expression::kFunctionCall_Kind:
|
||||
this->writeFunctionCall((FunctionCall&) e);
|
||||
break;
|
||||
case Expression::kIndex_Kind:
|
||||
this->writeIndexExpression((IndexExpression&) e);
|
||||
break;
|
||||
case Expression::kIntLiteral_Kind:
|
||||
this->writeIntLiteral((IntLiteral&) e);
|
||||
break;
|
||||
case Expression::kNullLiteral_Kind:
|
||||
this->writeNullLiteral((NullLiteral&) e);
|
||||
break;
|
||||
case Expression::kPrefix_Kind:
|
||||
this->writePrefixExpression((PrefixExpression&) e);
|
||||
break;
|
||||
case Expression::kPostfix_Kind:
|
||||
this->writePostfixExpression((PostfixExpression&) e);
|
||||
break;
|
||||
case Expression::kSwizzle_Kind:
|
||||
this->writeSwizzle((Swizzle&) e);
|
||||
break;
|
||||
case Expression::kVariableReference_Kind:
|
||||
this->writeVariableReference((VariableReference&) e);
|
||||
break;
|
||||
case Expression::kTernary_Kind:
|
||||
this->writeTernaryExpression((TernaryExpression&) e);
|
||||
break;
|
||||
default:
|
||||
printf("unsupported expression %s\n", e.description().c_str());
|
||||
SkASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeTarget(const Expression& e) {
|
||||
switch (e.fKind) {
|
||||
case Expression::kVariableReference_Kind:
|
||||
this->write(ByteCodeInstruction::kPushImmediate);
|
||||
this->write32(this->getLocation(((VariableReference&) e).fVariable));
|
||||
break;
|
||||
case Expression::kIndex_Kind:
|
||||
case Expression::kTernary_Kind:
|
||||
default:
|
||||
printf("unsupported target %s\n", e.description().c_str());
|
||||
SkASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
class ByteCodeSwizzleLValue : public ByteCodeGenerator::LValue {
|
||||
public:
|
||||
ByteCodeSwizzleLValue(ByteCodeGenerator* generator, const Swizzle& swizzle)
|
||||
: INHERITED(*generator)
|
||||
, fSwizzle(swizzle) {
|
||||
fGenerator.writeTarget(*swizzle.fBase);
|
||||
}
|
||||
|
||||
void load() override {
|
||||
fGenerator.write(ByteCodeInstruction::kDup);
|
||||
fGenerator.write(ByteCodeInstruction::kLoadSwizzle);
|
||||
fGenerator.write8(fSwizzle.fComponents.size());
|
||||
for (int c : fSwizzle.fComponents) {
|
||||
fGenerator.write8(c);
|
||||
}
|
||||
}
|
||||
|
||||
void store() override {
|
||||
fGenerator.write(ByteCodeInstruction::kStoreSwizzle);
|
||||
fGenerator.write8(fSwizzle.fComponents.size());
|
||||
for (int c : fSwizzle.fComponents) {
|
||||
fGenerator.write8(c);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const Swizzle& fSwizzle;
|
||||
|
||||
typedef LValue INHERITED;
|
||||
};
|
||||
|
||||
class ByteCodeVariableLValue : public ByteCodeGenerator::LValue {
|
||||
public:
|
||||
ByteCodeVariableLValue(ByteCodeGenerator* generator, const Variable& var)
|
||||
: INHERITED(*generator)
|
||||
, fCount(slot_count(var.fType))
|
||||
, fIsGlobal(var.fStorage == Variable::kGlobal_Storage) {
|
||||
fGenerator.write(ByteCodeInstruction::kPushImmediate);
|
||||
fGenerator.write32(generator->getLocation(var));
|
||||
}
|
||||
|
||||
void load() override {
|
||||
fGenerator.write(ByteCodeInstruction::kDup);
|
||||
if (fCount > 1) {
|
||||
fGenerator.write(ByteCodeInstruction::kVector);
|
||||
fGenerator.write8(fCount);
|
||||
}
|
||||
fGenerator.write(fIsGlobal ? ByteCodeInstruction::kLoadGlobal : ByteCodeInstruction::kLoad);
|
||||
}
|
||||
|
||||
void store() override {
|
||||
if (fCount > 1) {
|
||||
fGenerator.write(ByteCodeInstruction::kVector);
|
||||
fGenerator.write8(fCount);
|
||||
}
|
||||
fGenerator.write(fIsGlobal ? ByteCodeInstruction::kStoreGlobal
|
||||
: ByteCodeInstruction::kStore);
|
||||
}
|
||||
|
||||
private:
|
||||
typedef LValue INHERITED;
|
||||
|
||||
int fCount;
|
||||
|
||||
bool fIsGlobal;
|
||||
};
|
||||
|
||||
std::unique_ptr<ByteCodeGenerator::LValue> ByteCodeGenerator::getLValue(const Expression& e) {
|
||||
switch (e.fKind) {
|
||||
case Expression::kIndex_Kind:
|
||||
// not yet implemented
|
||||
abort();
|
||||
case Expression::kVariableReference_Kind:
|
||||
return std::unique_ptr<LValue>(new ByteCodeVariableLValue(this,
|
||||
((VariableReference&) e).fVariable));
|
||||
case Expression::kSwizzle_Kind:
|
||||
return std::unique_ptr<LValue>(new ByteCodeSwizzleLValue(this, (Swizzle&) e));
|
||||
case Expression::kTernary_Kind:
|
||||
default:
|
||||
printf("unsupported lvalue %s\n", e.description().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeBlock(const Block& b) {
|
||||
for (const auto& s : b.fStatements) {
|
||||
this->writeStatement(*s);
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::setBreakTargets() {
|
||||
std::vector<DeferredLocation>& breaks = fBreakTargets.top();
|
||||
for (DeferredLocation& b : breaks) {
|
||||
b.set();
|
||||
}
|
||||
fBreakTargets.pop();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::setContinueTargets() {
|
||||
std::vector<DeferredLocation>& continues = fContinueTargets.top();
|
||||
for (DeferredLocation& c : continues) {
|
||||
c.set();
|
||||
}
|
||||
fContinueTargets.pop();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeBreakStatement(const BreakStatement& b) {
|
||||
this->write(ByteCodeInstruction::kBranch);
|
||||
fBreakTargets.top().emplace_back(this);
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeContinueStatement(const ContinueStatement& c) {
|
||||
this->write(ByteCodeInstruction::kBranch);
|
||||
fContinueTargets.top().emplace_back(this);
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeDoStatement(const DoStatement& d) {
|
||||
fContinueTargets.emplace();
|
||||
fBreakTargets.emplace();
|
||||
size_t start = fCode->size();
|
||||
this->writeStatement(*d.fStatement);
|
||||
this->setContinueTargets();
|
||||
this->writeExpression(*d.fTest);
|
||||
this->write(ByteCodeInstruction::kConditionalBranch);
|
||||
this->write16(start);
|
||||
this->setBreakTargets();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeForStatement(const ForStatement& f) {
|
||||
fContinueTargets.emplace();
|
||||
fBreakTargets.emplace();
|
||||
if (f.fInitializer) {
|
||||
this->writeStatement(*f.fInitializer);
|
||||
}
|
||||
size_t start = fCode->size();
|
||||
if (f.fTest) {
|
||||
this->writeExpression(*f.fTest);
|
||||
this->write(ByteCodeInstruction::kNot);
|
||||
this->write(ByteCodeInstruction::kConditionalBranch);
|
||||
DeferredLocation endLocation(this);
|
||||
this->writeStatement(*f.fStatement);
|
||||
this->setContinueTargets();
|
||||
if (f.fNext) {
|
||||
this->writeExpression(*f.fNext);
|
||||
this->write(ByteCodeInstruction::kPop);
|
||||
this->write8(slot_count(f.fNext->fType));
|
||||
}
|
||||
this->write(ByteCodeInstruction::kBranch);
|
||||
this->write16(start);
|
||||
endLocation.set();
|
||||
} else {
|
||||
this->writeStatement(*f.fStatement);
|
||||
this->setContinueTargets();
|
||||
if (f.fNext) {
|
||||
this->writeExpression(*f.fNext);
|
||||
this->write(ByteCodeInstruction::kPop);
|
||||
this->write8(slot_count(f.fNext->fType));
|
||||
}
|
||||
this->write(ByteCodeInstruction::kBranch);
|
||||
this->write16(start);
|
||||
}
|
||||
this->setBreakTargets();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeIfStatement(const IfStatement& i) {
|
||||
this->writeExpression(*i.fTest);
|
||||
this->write(ByteCodeInstruction::kNot);
|
||||
this->write(ByteCodeInstruction::kConditionalBranch);
|
||||
DeferredLocation elseLocation(this);
|
||||
this->writeStatement(*i.fIfTrue);
|
||||
this->write(ByteCodeInstruction::kBranch);
|
||||
DeferredLocation endLocation(this);
|
||||
elseLocation.set();
|
||||
if (i.fIfFalse) {
|
||||
this->writeStatement(*i.fIfFalse);
|
||||
}
|
||||
endLocation.set();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeReturnStatement(const ReturnStatement& r) {
|
||||
// not yet implemented
|
||||
abort();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeSwitchStatement(const SwitchStatement& r) {
|
||||
// not yet implemented
|
||||
abort();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeVarDeclarations(const VarDeclarations& v) {
|
||||
for (const auto& declStatement : v.fVars) {
|
||||
const VarDeclaration& decl = (VarDeclaration&) *declStatement;
|
||||
// we need to grab the location even if we don't use it, to ensure it
|
||||
// has been allocated
|
||||
int location = getLocation(*decl.fVar);
|
||||
if (decl.fValue) {
|
||||
this->write(ByteCodeInstruction::kPushImmediate);
|
||||
this->write32(location);
|
||||
this->writeExpression(*decl.fValue);
|
||||
int count = slot_count(decl.fValue->fType);
|
||||
if (count > 1) {
|
||||
this->write(ByteCodeInstruction::kVector);
|
||||
this->write8(count);
|
||||
}
|
||||
this->write(ByteCodeInstruction::kStore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeWhileStatement(const WhileStatement& w) {
|
||||
fContinueTargets.emplace();
|
||||
fBreakTargets.emplace();
|
||||
size_t start = fCode->size();
|
||||
this->writeExpression(*w.fTest);
|
||||
this->write(ByteCodeInstruction::kNot);
|
||||
this->write(ByteCodeInstruction::kConditionalBranch);
|
||||
DeferredLocation endLocation(this);
|
||||
this->writeStatement(*w.fStatement);
|
||||
this->setContinueTargets();
|
||||
this->write(ByteCodeInstruction::kBranch);
|
||||
this->write16(start);
|
||||
endLocation.set();
|
||||
this->setBreakTargets();
|
||||
}
|
||||
|
||||
void ByteCodeGenerator::writeStatement(const Statement& s) {
|
||||
switch (s.fKind) {
|
||||
case Statement::kBlock_Kind:
|
||||
this->writeBlock((Block&) s);
|
||||
break;
|
||||
case Statement::kBreak_Kind:
|
||||
this->writeBreakStatement((BreakStatement&) s);
|
||||
break;
|
||||
case Statement::kContinue_Kind:
|
||||
this->writeContinueStatement((ContinueStatement&) s);
|
||||
break;
|
||||
case Statement::kDiscard_Kind:
|
||||
// not yet implemented
|
||||
abort();
|
||||
case Statement::kDo_Kind:
|
||||
this->writeDoStatement((DoStatement&) s);
|
||||
break;
|
||||
case Statement::kExpression_Kind: {
|
||||
const Expression& expr = *((ExpressionStatement&) s).fExpression;
|
||||
this->writeExpression(expr);
|
||||
this->write(ByteCodeInstruction::kPop);
|
||||
this->write8(slot_count(expr.fType));
|
||||
break;
|
||||
}
|
||||
case Statement::kFor_Kind:
|
||||
this->writeForStatement((ForStatement&) s);
|
||||
break;
|
||||
case Statement::kIf_Kind:
|
||||
this->writeIfStatement((IfStatement&) s);
|
||||
break;
|
||||
case Statement::kNop_Kind:
|
||||
break;
|
||||
case Statement::kReturn_Kind:
|
||||
this->writeReturnStatement((ReturnStatement&) s);
|
||||
break;
|
||||
case Statement::kSwitch_Kind:
|
||||
this->writeSwitchStatement((SwitchStatement&) s);
|
||||
break;
|
||||
case Statement::kVarDeclarations_Kind:
|
||||
this->writeVarDeclarations(*((VarDeclarationsStatement&) s).fDeclaration);
|
||||
break;
|
||||
case Statement::kWhile_Kind:
|
||||
this->writeWhileStatement((WhileStatement&) s);
|
||||
break;
|
||||
default:
|
||||
SkASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
241
src/sksl/SkSLByteCodeGenerator.h
Normal file
241
src/sksl/SkSLByteCodeGenerator.h
Normal file
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SKSL_BYTECODEGENERATOR
|
||||
#define SKSL_BYTECODEGENERATOR
|
||||
|
||||
#include <stack>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "SkSLByteCode.h"
|
||||
#include "SkSLCodeGenerator.h"
|
||||
#include "SkSLMemoryLayout.h"
|
||||
#include "ir/SkSLBinaryExpression.h"
|
||||
#include "ir/SkSLBoolLiteral.h"
|
||||
#include "ir/SkSLBlock.h"
|
||||
#include "ir/SkSLBreakStatement.h"
|
||||
#include "ir/SkSLConstructor.h"
|
||||
#include "ir/SkSLContinueStatement.h"
|
||||
#include "ir/SkSLDoStatement.h"
|
||||
#include "ir/SkSLExpressionStatement.h"
|
||||
#include "ir/SkSLFloatLiteral.h"
|
||||
#include "ir/SkSLIfStatement.h"
|
||||
#include "ir/SkSLIndexExpression.h"
|
||||
#include "ir/SkSLInterfaceBlock.h"
|
||||
#include "ir/SkSLIntLiteral.h"
|
||||
#include "ir/SkSLFieldAccess.h"
|
||||
#include "ir/SkSLForStatement.h"
|
||||
#include "ir/SkSLFunctionCall.h"
|
||||
#include "ir/SkSLFunctionDeclaration.h"
|
||||
#include "ir/SkSLFunctionDefinition.h"
|
||||
#include "ir/SkSLNullLiteral.h"
|
||||
#include "ir/SkSLPrefixExpression.h"
|
||||
#include "ir/SkSLPostfixExpression.h"
|
||||
#include "ir/SkSLProgramElement.h"
|
||||
#include "ir/SkSLReturnStatement.h"
|
||||
#include "ir/SkSLStatement.h"
|
||||
#include "ir/SkSLSwitchStatement.h"
|
||||
#include "ir/SkSLSwizzle.h"
|
||||
#include "ir/SkSLTernaryExpression.h"
|
||||
#include "ir/SkSLVarDeclarations.h"
|
||||
#include "ir/SkSLVarDeclarationsStatement.h"
|
||||
#include "ir/SkSLVariableReference.h"
|
||||
#include "ir/SkSLWhileStatement.h"
|
||||
#include "spirv.h"
|
||||
|
||||
namespace SkSL {
|
||||
|
||||
class ByteCodeGenerator : public CodeGenerator {
|
||||
public:
|
||||
class LValue {
|
||||
public:
|
||||
LValue(ByteCodeGenerator& generator)
|
||||
: fGenerator(generator) {}
|
||||
|
||||
virtual ~LValue() {}
|
||||
|
||||
/**
|
||||
* Stack before call: ... lvalue
|
||||
* Stack after call: ... lvalue load
|
||||
*/
|
||||
virtual void load() = 0;
|
||||
|
||||
/**
|
||||
* Stack before call: ... lvalue value
|
||||
* Stack after call: ...
|
||||
*/
|
||||
virtual void store() = 0;
|
||||
|
||||
protected:
|
||||
ByteCodeGenerator& fGenerator;
|
||||
};
|
||||
|
||||
ByteCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors,
|
||||
ByteCode* output)
|
||||
: INHERITED(program, errors, nullptr)
|
||||
, fContext(*context)
|
||||
, fOutput(output) {}
|
||||
|
||||
bool generateCode() override;
|
||||
|
||||
void write8(uint8_t b);
|
||||
|
||||
void write16(uint16_t b);
|
||||
|
||||
void write32(uint32_t b);
|
||||
|
||||
void write(ByteCodeInstruction inst);
|
||||
|
||||
/**
|
||||
* Based on 'type', writes the s (signed), u (unsigned), or f (float) instruction.
|
||||
*/
|
||||
void writeTypedInstruction(const Type& type, ByteCodeInstruction s, ByteCodeInstruction u,
|
||||
ByteCodeInstruction f);
|
||||
|
||||
/**
|
||||
* Pushes the storage location of an lvalue to the stack.
|
||||
*/
|
||||
void writeTarget(const Expression& expr);
|
||||
|
||||
private:
|
||||
// reserves 16 bits in the output code, to be filled in later with an address once we determine
|
||||
// it
|
||||
class DeferredLocation {
|
||||
public:
|
||||
DeferredLocation(ByteCodeGenerator* generator)
|
||||
: fGenerator(*generator)
|
||||
, fOffset(generator->fCode->size()) {
|
||||
generator->write16(0);
|
||||
}
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
~DeferredLocation() {
|
||||
SkASSERT(fSet);
|
||||
}
|
||||
#endif
|
||||
|
||||
void set() {
|
||||
int target = fGenerator.fCode->size();
|
||||
SkASSERT(target <= 65535);
|
||||
(*fGenerator.fCode)[fOffset] = target >> 8;
|
||||
(*fGenerator.fCode)[fOffset + 1] = target;
|
||||
#ifdef SK_DEBUG
|
||||
fSet = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
ByteCodeGenerator& fGenerator;
|
||||
size_t fOffset;
|
||||
#ifdef SK_DEBUG
|
||||
bool fSet = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the local slot into which var should be stored, allocating a new slot if it has not
|
||||
* already been assigned one. Compound variables (e.g. vectors) will consume more than one local
|
||||
* slot, with the getLocation return value indicating where the first element should be stored.
|
||||
*/
|
||||
int getLocation(const Variable& var);
|
||||
|
||||
std::unique_ptr<ByteCodeFunction> writeFunction(const FunctionDefinition& f);
|
||||
|
||||
void writeVarDeclarations(const VarDeclarations& decl);
|
||||
|
||||
void writeVariableReference(const VariableReference& ref);
|
||||
|
||||
void writeExpression(const Expression& expr);
|
||||
|
||||
/**
|
||||
* Pushes whatever values are required by the lvalue onto the stack, and returns an LValue
|
||||
* permitting loads and stores to it.
|
||||
*/
|
||||
std::unique_ptr<LValue> getLValue(const Expression& expr);
|
||||
|
||||
void writeFunctionCall(const FunctionCall& c);
|
||||
|
||||
void writeConstructor(const Constructor& c);
|
||||
|
||||
void writeFieldAccess(const FieldAccess& f);
|
||||
|
||||
void writeSwizzle(const Swizzle& swizzle);
|
||||
|
||||
void writeBinaryExpression(const BinaryExpression& b);
|
||||
|
||||
void writeTernaryExpression(const TernaryExpression& t);
|
||||
|
||||
void writeIndexExpression(const IndexExpression& expr);
|
||||
|
||||
void writeLogicalAnd(const BinaryExpression& b);
|
||||
|
||||
void writeLogicalOr(const BinaryExpression& o);
|
||||
|
||||
void writeNullLiteral(const NullLiteral& n);
|
||||
|
||||
void writePrefixExpression(const PrefixExpression& p);
|
||||
|
||||
void writePostfixExpression(const PostfixExpression& p);
|
||||
|
||||
void writeBoolLiteral(const BoolLiteral& b);
|
||||
|
||||
void writeIntLiteral(const IntLiteral& i);
|
||||
|
||||
void writeFloatLiteral(const FloatLiteral& f);
|
||||
|
||||
void writeStatement(const Statement& s);
|
||||
|
||||
void writeBlock(const Block& b);
|
||||
|
||||
void writeBreakStatement(const BreakStatement& b);
|
||||
|
||||
void writeContinueStatement(const ContinueStatement& c);
|
||||
|
||||
void writeIfStatement(const IfStatement& stmt);
|
||||
|
||||
void writeForStatement(const ForStatement& f);
|
||||
|
||||
void writeWhileStatement(const WhileStatement& w);
|
||||
|
||||
void writeDoStatement(const DoStatement& d);
|
||||
|
||||
void writeSwitchStatement(const SwitchStatement& s);
|
||||
|
||||
void writeReturnStatement(const ReturnStatement& r);
|
||||
|
||||
// updates the current set of breaks to branch to the current location
|
||||
void setBreakTargets();
|
||||
|
||||
// updates the current set of continues to branch to the current location
|
||||
void setContinueTargets();
|
||||
|
||||
const Context& fContext;
|
||||
|
||||
ByteCode* fOutput;
|
||||
|
||||
const FunctionDefinition* fFunction;
|
||||
|
||||
std::vector<uint8_t>* fCode;
|
||||
|
||||
std::vector<const Variable*> fLocals;
|
||||
|
||||
std::stack<std::vector<DeferredLocation>> fContinueTargets;
|
||||
|
||||
std::stack<std::vector<DeferredLocation>> fBreakTargets;
|
||||
|
||||
int fParameterCount;
|
||||
|
||||
friend class DeferredLocation;
|
||||
friend class ByteCodeVariableLValue;
|
||||
|
||||
typedef CodeGenerator INHERITED;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "SkSLCompiler.h"
|
||||
|
||||
#include "SkSLByteCodeGenerator.h"
|
||||
#include "SkSLCFGGenerator.h"
|
||||
#include "SkSLCPPCodeGenerator.h"
|
||||
#include "SkSLGLSLCodeGenerator.h"
|
||||
@ -1467,6 +1468,18 @@ bool Compiler::toPipelineStage(const Program& program, String* out,
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<ByteCode> Compiler::toByteCode(Program& program) {
|
||||
if (!this->optimize(program)) {
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<ByteCode> result(new ByteCode());
|
||||
ByteCodeGenerator cg(fContext.get(), &program, this, result.get());
|
||||
if (cg.generateCode()) {
|
||||
return result;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* Compiler::OperatorName(Token::Kind kind) {
|
||||
switch (kind) {
|
||||
case Token::PLUS: return "+";
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <vector>
|
||||
#include "ir/SkSLProgram.h"
|
||||
#include "ir/SkSLSymbolTable.h"
|
||||
#include "SkSLByteCode.h"
|
||||
#include "SkSLCFGGenerator.h"
|
||||
#include "SkSLContext.h"
|
||||
#include "SkSLErrorReporter.h"
|
||||
@ -111,6 +112,8 @@ public:
|
||||
|
||||
bool toH(Program& program, String name, OutputStream& out);
|
||||
|
||||
std::unique_ptr<ByteCode> toByteCode(Program& program);
|
||||
|
||||
bool toPipelineStage(const Program& program, String* out,
|
||||
std::vector<FormatArg>* outFormatArgs);
|
||||
|
||||
|
@ -27,189 +27,42 @@
|
||||
|
||||
namespace SkSL {
|
||||
|
||||
void Interpreter::run() {
|
||||
for (const auto& e : *fProgram) {
|
||||
if (ProgramElement::kFunction_Kind == e.fKind) {
|
||||
const FunctionDefinition& f = (const FunctionDefinition&) e;
|
||||
if ("appendStages" == f.fDeclaration.fName) {
|
||||
this->run(f);
|
||||
return;
|
||||
static constexpr int UNINITIALIZED = 0xDEADBEEF;
|
||||
|
||||
Interpreter::Value Interpreter::run(const ByteCodeFunction& f, Interpreter::Value args[],
|
||||
Interpreter::Value inputs[]) {
|
||||
fIP = 0;
|
||||
fCurrentFunction = &f;
|
||||
fStack.clear();
|
||||
fGlobals.clear();
|
||||
#ifdef TRACE
|
||||
this->disassemble(f);
|
||||
#endif
|
||||
for (int i = 0; i < f.fParameterCount; ++i) {
|
||||
this->push(args[i]);
|
||||
}
|
||||
for (int i = 0; i < f.fLocalCount; ++i) {
|
||||
this->push(Value((int) UNINITIALIZED));
|
||||
}
|
||||
for (int i = 0; i < f.fOwner.fGlobalCount; ++i) {
|
||||
fGlobals.push_back(Value((int) UNINITIALIZED));
|
||||
}
|
||||
for (int i = f.fOwner.fInputSlots.size() - 1; i >= 0; --i) {
|
||||
fGlobals[f.fOwner.fInputSlots[i]] = inputs[i];
|
||||
}
|
||||
run();
|
||||
int offset = 0;
|
||||
for (const auto& p : f.fDeclaration.fParameters) {
|
||||
if (p->fModifiers.fFlags & Modifiers::kOut_Flag) {
|
||||
for (int i = p->fType.columns() * p->fType.rows() - 1; i >= 0; --i) {
|
||||
args[offset] = fStack[offset];
|
||||
++offset;
|
||||
}
|
||||
} else {
|
||||
offset += p->fType.columns() * p->fType.rows();
|
||||
}
|
||||
}
|
||||
SkASSERT(false);
|
||||
}
|
||||
|
||||
static int SizeOf(const Type& type) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void Interpreter::run(const FunctionDefinition& f) {
|
||||
fVars.emplace_back();
|
||||
StackIndex current = (StackIndex) fStack.size();
|
||||
for (int i = f.fDeclaration.fParameters.size() - 1; i >= 0; --i) {
|
||||
current -= SizeOf(f.fDeclaration.fParameters[i]->fType);
|
||||
fVars.back()[f.fDeclaration.fParameters[i]] = current;
|
||||
}
|
||||
fCurrentIndex.push_back({ f.fBody.get(), 0 });
|
||||
while (fCurrentIndex.size()) {
|
||||
this->runStatement();
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::push(Value value) {
|
||||
fStack.push_back(value);
|
||||
}
|
||||
|
||||
Interpreter::Value Interpreter::pop() {
|
||||
auto iter = fStack.end() - 1;
|
||||
Value result = *iter;
|
||||
fStack.erase(iter);
|
||||
return result;
|
||||
}
|
||||
|
||||
Interpreter::StackIndex Interpreter::stackAlloc(int count) {
|
||||
int result = fStack.size();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
fStack.push_back(Value((int) 0xDEADBEEF));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Interpreter::runStatement() {
|
||||
const Statement& stmt = *fCurrentIndex.back().fStatement;
|
||||
const size_t index = fCurrentIndex.back().fIndex;
|
||||
fCurrentIndex.pop_back();
|
||||
switch (stmt.fKind) {
|
||||
case Statement::kBlock_Kind: {
|
||||
const Block& b = (const Block&) stmt;
|
||||
if (!b.fStatements.size()) {
|
||||
break;
|
||||
}
|
||||
SkASSERT(index < b.fStatements.size());
|
||||
if (index < b.fStatements.size() - 1) {
|
||||
fCurrentIndex.push_back({ &b, index + 1 });
|
||||
}
|
||||
fCurrentIndex.push_back({ b.fStatements[index].get(), 0 });
|
||||
break;
|
||||
}
|
||||
case Statement::kBreak_Kind:
|
||||
SkASSERT(index == 0);
|
||||
abort();
|
||||
case Statement::kContinue_Kind:
|
||||
SkASSERT(index == 0);
|
||||
abort();
|
||||
case Statement::kDiscard_Kind:
|
||||
SkASSERT(index == 0);
|
||||
abort();
|
||||
case Statement::kDo_Kind:
|
||||
abort();
|
||||
case Statement::kExpression_Kind:
|
||||
SkASSERT(index == 0);
|
||||
this->evaluate(*((const ExpressionStatement&) stmt).fExpression);
|
||||
break;
|
||||
case Statement::kFor_Kind: {
|
||||
ForStatement& f = (ForStatement&) stmt;
|
||||
switch (index) {
|
||||
case 0:
|
||||
// initializer
|
||||
fCurrentIndex.push_back({ &f, 1 });
|
||||
if (f.fInitializer) {
|
||||
fCurrentIndex.push_back({ f.fInitializer.get(), 0 });
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// test & body
|
||||
if (f.fTest && !evaluate(*f.fTest).fBool) {
|
||||
break;
|
||||
} else {
|
||||
fCurrentIndex.push_back({ &f, 2 });
|
||||
fCurrentIndex.push_back({ f.fStatement.get(), 0 });
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
// next
|
||||
if (f.fNext) {
|
||||
this->evaluate(*f.fNext);
|
||||
}
|
||||
fCurrentIndex.push_back({ &f, 1 });
|
||||
break;
|
||||
default:
|
||||
SkASSERT(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Statement::kGroup_Kind:
|
||||
abort();
|
||||
case Statement::kIf_Kind: {
|
||||
IfStatement& i = (IfStatement&) stmt;
|
||||
if (evaluate(*i.fTest).fBool) {
|
||||
fCurrentIndex.push_back({ i.fIfTrue.get(), 0 });
|
||||
} else if (i.fIfFalse) {
|
||||
fCurrentIndex.push_back({ i.fIfFalse.get(), 0 });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Statement::kNop_Kind:
|
||||
SkASSERT(index == 0);
|
||||
break;
|
||||
case Statement::kReturn_Kind:
|
||||
SkASSERT(index == 0);
|
||||
abort();
|
||||
case Statement::kSwitch_Kind:
|
||||
abort();
|
||||
case Statement::kVarDeclarations_Kind:
|
||||
SkASSERT(index == 0);
|
||||
for (const auto& decl :((const VarDeclarationsStatement&) stmt).fDeclaration->fVars) {
|
||||
const Variable* var = ((VarDeclaration&) *decl).fVar;
|
||||
StackIndex pos = this->stackAlloc(SizeOf(var->fType));
|
||||
fVars.back()[var] = pos;
|
||||
if (var->fInitialValue) {
|
||||
fStack[pos] = this->evaluate(*var->fInitialValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Statement::kWhile_Kind:
|
||||
abort();
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static Interpreter::TypeKind type_kind(const Type& type) {
|
||||
if (type.fName == "int") {
|
||||
return Interpreter::kInt_TypeKind;
|
||||
} else if (type.fName == "float") {
|
||||
return Interpreter::kFloat_TypeKind;
|
||||
}
|
||||
ABORT("unsupported type: %s\n", type.description().c_str());
|
||||
}
|
||||
|
||||
Interpreter::StackIndex Interpreter::getLValue(const Expression& expr) {
|
||||
switch (expr.fKind) {
|
||||
case Expression::kFieldAccess_Kind:
|
||||
break;
|
||||
case Expression::kIndex_Kind: {
|
||||
const IndexExpression& idx = (const IndexExpression&) expr;
|
||||
return this->evaluate(*idx.fBase).fInt + this->evaluate(*idx.fIndex).fInt;
|
||||
}
|
||||
case Expression::kSwizzle_Kind:
|
||||
break;
|
||||
case Expression::kVariableReference_Kind:
|
||||
SkASSERT(fVars.size());
|
||||
SkASSERT(fVars.back().find(&((VariableReference&) expr).fVariable) !=
|
||||
fVars.back().end());
|
||||
return fVars.back()[&((VariableReference&) expr).fVariable];
|
||||
case Expression::kTernary_Kind: {
|
||||
const TernaryExpression& t = (const TernaryExpression&) expr;
|
||||
return this->getLValue(this->evaluate(*t.fTest).fBool ? *t.fIfTrue : *t.fIfFalse);
|
||||
}
|
||||
case Expression::kTypeReference_Kind:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ABORT("unsupported lvalue");
|
||||
return fReturnValue;
|
||||
}
|
||||
|
||||
struct CallbackCtx : public SkRasterPipeline_CallbackCtx {
|
||||
@ -217,254 +70,421 @@ struct CallbackCtx : public SkRasterPipeline_CallbackCtx {
|
||||
const FunctionDefinition* fFunction;
|
||||
};
|
||||
|
||||
static void do_callback(SkRasterPipeline_CallbackCtx* raw, int activePixels) {
|
||||
CallbackCtx& ctx = (CallbackCtx&) *raw;
|
||||
for (int i = 0; i < activePixels; ++i) {
|
||||
ctx.fInterpreter->push(Interpreter::Value(ctx.rgba[i * 4 + 0]));
|
||||
ctx.fInterpreter->push(Interpreter::Value(ctx.rgba[i * 4 + 1]));
|
||||
ctx.fInterpreter->push(Interpreter::Value(ctx.rgba[i * 4 + 2]));
|
||||
ctx.fInterpreter->run(*ctx.fFunction);
|
||||
ctx.read_from[i * 4 + 2] = ctx.fInterpreter->pop().fFloat;
|
||||
ctx.read_from[i * 4 + 1] = ctx.fInterpreter->pop().fFloat;
|
||||
ctx.read_from[i * 4 + 0] = ctx.fInterpreter->pop().fFloat;
|
||||
uint8_t Interpreter::read8() {
|
||||
return fCurrentFunction->fCode[fIP++];
|
||||
}
|
||||
|
||||
uint16_t Interpreter::read16() {
|
||||
uint16_t result = (fCurrentFunction->fCode[fIP ] << 8) +
|
||||
fCurrentFunction->fCode[fIP + 1];
|
||||
fIP += 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t Interpreter::read32() {
|
||||
uint32_t result = (fCurrentFunction->fCode[fIP] << 24) +
|
||||
(fCurrentFunction->fCode[fIP + 1] << 16) +
|
||||
(fCurrentFunction->fCode[fIP + 2] << 8) +
|
||||
fCurrentFunction->fCode[fIP + 3];
|
||||
fIP += 4;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Interpreter::push(Value v) {
|
||||
fStack.push_back(v);
|
||||
}
|
||||
|
||||
Interpreter::Value Interpreter::pop() {
|
||||
Value v = fStack.back();
|
||||
fStack.pop_back();
|
||||
return v;
|
||||
}
|
||||
|
||||
static String value_string(uint32_t v) {
|
||||
union { uint32_t u; float f; } pun = { v };
|
||||
return to_string(v) + "(" + to_string(pun.f) + ")";
|
||||
}
|
||||
|
||||
void Interpreter::disassemble(const ByteCodeFunction& f) {
|
||||
SkASSERT(fIP == 0);
|
||||
while (fIP < (int) f.fCode.size()) {
|
||||
printf("%d: ", fIP);
|
||||
switch ((ByteCodeInstruction) this->read8()) {
|
||||
case ByteCodeInstruction::kAddF: printf("addf"); break;
|
||||
case ByteCodeInstruction::kAddI: printf("addi"); break;
|
||||
case ByteCodeInstruction::kAndB: printf("andb"); break;
|
||||
case ByteCodeInstruction::kAndI: printf("andi"); break;
|
||||
case ByteCodeInstruction::kBranch: printf("branch %d", this->read16()); break;
|
||||
case ByteCodeInstruction::kCompareIEQ: printf("comparei eq"); break;
|
||||
case ByteCodeInstruction::kCompareINEQ: printf("comparei neq"); break;
|
||||
case ByteCodeInstruction::kCompareFEQ: printf("comparef eq"); break;
|
||||
case ByteCodeInstruction::kCompareFGT: printf("comparef gt"); break;
|
||||
case ByteCodeInstruction::kCompareFGTEQ: printf("comparef gteq"); break;
|
||||
case ByteCodeInstruction::kCompareFLT: printf("comparef lt"); break;
|
||||
case ByteCodeInstruction::kCompareFLTEQ: printf("comparef lteq"); break;
|
||||
case ByteCodeInstruction::kCompareFNEQ: printf("comparef neq"); break;
|
||||
case ByteCodeInstruction::kCompareSGT: printf("compares sgt"); break;
|
||||
case ByteCodeInstruction::kCompareSGTEQ: printf("compares sgteq"); break;
|
||||
case ByteCodeInstruction::kCompareSLT: printf("compares lt"); break;
|
||||
case ByteCodeInstruction::kCompareSLTEQ: printf("compares lteq"); break;
|
||||
case ByteCodeInstruction::kCompareUGT: printf("compareu gt"); break;
|
||||
case ByteCodeInstruction::kCompareUGTEQ: printf("compareu gteq"); break;
|
||||
case ByteCodeInstruction::kCompareULT: printf("compareu lt"); break;
|
||||
case ByteCodeInstruction::kCompareULTEQ: printf("compareu lteq"); break;
|
||||
case ByteCodeInstruction::kConditionalBranch:
|
||||
printf("conditionalbranch %d", this->read16());
|
||||
break;
|
||||
case ByteCodeInstruction::kDebugPrint: printf("debugprint"); break;
|
||||
case ByteCodeInstruction::kDivideF: printf("dividef"); break;
|
||||
case ByteCodeInstruction::kDivideS: printf("divides"); break;
|
||||
case ByteCodeInstruction::kDivideU: printf("divideu"); break;
|
||||
case ByteCodeInstruction::kDup: printf("dup"); break;
|
||||
case ByteCodeInstruction::kDupDown: printf("dupdown %d", this->read8()); break;
|
||||
case ByteCodeInstruction::kFloatToInt: printf("floattoint"); break;
|
||||
case ByteCodeInstruction::kLoad: printf("load"); break;
|
||||
case ByteCodeInstruction::kLoadGlobal: printf("loadglobal"); break;
|
||||
case ByteCodeInstruction::kLoadSwizzle: {
|
||||
int count = this->read8();
|
||||
printf("loadswizzle %d", count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
printf(", %d", this->read8());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kMultiplyF: printf("multiplyf"); break;
|
||||
case ByteCodeInstruction::kMultiplyS: printf("multiplys"); break;
|
||||
case ByteCodeInstruction::kMultiplyU: printf("multiplyu"); break;
|
||||
case ByteCodeInstruction::kNegateF: printf("negatef"); break;
|
||||
case ByteCodeInstruction::kNegateS: printf("negates"); break;
|
||||
case ByteCodeInstruction::kNot: printf("not"); break;
|
||||
case ByteCodeInstruction::kOrB: printf("orb"); break;
|
||||
case ByteCodeInstruction::kOrI: printf("ori"); break;
|
||||
case ByteCodeInstruction::kParameter: printf("parameter"); break;
|
||||
case ByteCodeInstruction::kPop: printf("pop %d", this->read8()); break;
|
||||
case ByteCodeInstruction::kPushImmediate:
|
||||
printf("pushimmediate %s", value_string(this->read32()).c_str());
|
||||
break;
|
||||
case ByteCodeInstruction::kRemainderS: printf("remainders"); break;
|
||||
case ByteCodeInstruction::kRemainderU: printf("remainderu"); break;
|
||||
case ByteCodeInstruction::kSignedToFloat: printf("signedtofloat"); break;
|
||||
case ByteCodeInstruction::kStore: printf("store"); break;
|
||||
case ByteCodeInstruction::kStoreSwizzle: {
|
||||
int count = this->read8();
|
||||
printf("storeswizzle %d", count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
printf(", %d", this->read8());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kSubtractF: printf("subtractf"); break;
|
||||
case ByteCodeInstruction::kSubtractI: printf("subtracti"); break;
|
||||
case ByteCodeInstruction::kSwizzle: {
|
||||
printf("swizzle %d, ", this->read8());
|
||||
int count = this->read8();
|
||||
printf("%d", count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
printf(", %d", this->read8());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kUnsignedToFloat: printf("unsignedtofloat"); break;
|
||||
case ByteCodeInstruction::kVector: printf("vector%d", this->read8()); break;
|
||||
default: SkASSERT(false);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
fIP = 0;
|
||||
}
|
||||
|
||||
void Interpreter::appendStage(const AppendStage& a) {
|
||||
switch (a.fStage) {
|
||||
case SkRasterPipeline::matrix_4x5: {
|
||||
SkASSERT(a.fArguments.size() == 1);
|
||||
StackIndex transpose = evaluate(*a.fArguments[0]).fInt;
|
||||
fPipeline.append(SkRasterPipeline::matrix_4x5, &fStack[transpose]);
|
||||
break;
|
||||
}
|
||||
case SkRasterPipeline::callback: {
|
||||
SkASSERT(a.fArguments.size() == 1);
|
||||
CallbackCtx* ctx = new CallbackCtx();
|
||||
ctx->fInterpreter = this;
|
||||
ctx->fn = do_callback;
|
||||
for (const auto& e : *fProgram) {
|
||||
if (ProgramElement::kFunction_Kind == e.fKind) {
|
||||
const FunctionDefinition& f = (const FunctionDefinition&) e;
|
||||
if (&f.fDeclaration ==
|
||||
((const FunctionReference&) *a.fArguments[0]).fFunctions[0]) {
|
||||
ctx->fFunction = &f;
|
||||
}
|
||||
}
|
||||
}
|
||||
fPipeline.append(SkRasterPipeline::callback, ctx);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
fPipeline.append(a.fStage);
|
||||
void Interpreter::dumpStack() {
|
||||
printf("STACK:");
|
||||
for (size_t i = 0; i < fStack.size(); ++i) {
|
||||
printf(" %d(%f)", fStack[i].fSigned, fStack[i].fFloat);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
Interpreter::Value Interpreter::call(const FunctionCall& c) {
|
||||
abort();
|
||||
}
|
||||
#define BINARY_OP(inst, type, field, op) \
|
||||
case ByteCodeInstruction::inst: { \
|
||||
type b = this->pop().field; \
|
||||
type a = this->pop().field; \
|
||||
this->push(Value(a op b)); \
|
||||
break; \
|
||||
}
|
||||
|
||||
Interpreter::Value Interpreter::evaluate(const Expression& expr) {
|
||||
switch (expr.fKind) {
|
||||
case Expression::kAppendStage_Kind:
|
||||
this->appendStage((const AppendStage&) expr);
|
||||
return Value((int) 0xDEADBEEF);
|
||||
case Expression::kBinary_Kind: {
|
||||
#define ARITHMETIC(op) { \
|
||||
Value left = this->evaluate(*b.fLeft); \
|
||||
Value right = this->evaluate(*b.fRight); \
|
||||
switch (type_kind(b.fLeft->fType)) { \
|
||||
case kFloat_TypeKind: \
|
||||
return Value(left.fFloat op right.fFloat); \
|
||||
case kInt_TypeKind: \
|
||||
return Value(left.fInt op right.fInt); \
|
||||
default: \
|
||||
abort(); \
|
||||
} \
|
||||
}
|
||||
#define BITWISE(op) { \
|
||||
Value left = this->evaluate(*b.fLeft); \
|
||||
Value right = this->evaluate(*b.fRight); \
|
||||
switch (type_kind(b.fLeft->fType)) { \
|
||||
case kInt_TypeKind: \
|
||||
return Value(left.fInt op right.fInt); \
|
||||
default: \
|
||||
abort(); \
|
||||
} \
|
||||
}
|
||||
#define LOGIC(op) { \
|
||||
Value left = this->evaluate(*b.fLeft); \
|
||||
Value right = this->evaluate(*b.fRight); \
|
||||
switch (type_kind(b.fLeft->fType)) { \
|
||||
case kFloat_TypeKind: \
|
||||
return Value(left.fFloat op right.fFloat); \
|
||||
case kInt_TypeKind: \
|
||||
return Value(left.fInt op right.fInt); \
|
||||
default: \
|
||||
abort(); \
|
||||
} \
|
||||
}
|
||||
#define COMPOUND_ARITHMETIC(op) { \
|
||||
StackIndex left = this->getLValue(*b.fLeft); \
|
||||
Value right = this->evaluate(*b.fRight); \
|
||||
Value result = fStack[left]; \
|
||||
switch (type_kind(b.fLeft->fType)) { \
|
||||
case kFloat_TypeKind: \
|
||||
result.fFloat op right.fFloat; \
|
||||
break; \
|
||||
case kInt_TypeKind: \
|
||||
result.fInt op right.fInt; \
|
||||
break; \
|
||||
default: \
|
||||
abort(); \
|
||||
} \
|
||||
fStack[left] = result; \
|
||||
return result; \
|
||||
}
|
||||
#define COMPOUND_BITWISE(op) { \
|
||||
StackIndex left = this->getLValue(*b.fLeft); \
|
||||
Value right = this->evaluate(*b.fRight); \
|
||||
Value result = fStack[left]; \
|
||||
switch (type_kind(b.fLeft->fType)) { \
|
||||
case kInt_TypeKind: \
|
||||
result.fInt op right.fInt; \
|
||||
break; \
|
||||
default: \
|
||||
abort(); \
|
||||
} \
|
||||
fStack[left] = result; \
|
||||
return result; \
|
||||
}
|
||||
const BinaryExpression& b = (const BinaryExpression&) expr;
|
||||
switch (b.fOperator) {
|
||||
case Token::PLUS: ARITHMETIC(+)
|
||||
case Token::MINUS: ARITHMETIC(-)
|
||||
case Token::STAR: ARITHMETIC(*)
|
||||
case Token::SLASH: ARITHMETIC(/)
|
||||
case Token::BITWISEAND: BITWISE(&)
|
||||
case Token::BITWISEOR: BITWISE(|)
|
||||
case Token::BITWISEXOR: BITWISE(^)
|
||||
case Token::LT: LOGIC(<)
|
||||
case Token::GT: LOGIC(>)
|
||||
case Token::LTEQ: LOGIC(<=)
|
||||
case Token::GTEQ: LOGIC(>=)
|
||||
case Token::LOGICALAND: {
|
||||
Value result = this->evaluate(*b.fLeft);
|
||||
if (result.fBool) {
|
||||
result = this->evaluate(*b.fRight);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case Token::LOGICALOR: {
|
||||
Value result = this->evaluate(*b.fLeft);
|
||||
if (!result.fBool) {
|
||||
result = this->evaluate(*b.fRight);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case Token::EQ: {
|
||||
StackIndex left = this->getLValue(*b.fLeft);
|
||||
Value right = this->evaluate(*b.fRight);
|
||||
fStack[left] = right;
|
||||
return right;
|
||||
}
|
||||
case Token::PLUSEQ: COMPOUND_ARITHMETIC(+=)
|
||||
case Token::MINUSEQ: COMPOUND_ARITHMETIC(-=)
|
||||
case Token::STAREQ: COMPOUND_ARITHMETIC(*=)
|
||||
case Token::SLASHEQ: COMPOUND_ARITHMETIC(/=)
|
||||
case Token::BITWISEANDEQ: COMPOUND_BITWISE(&=)
|
||||
case Token::BITWISEOREQ: COMPOUND_BITWISE(|=)
|
||||
case Token::BITWISEXOREQ: COMPOUND_BITWISE(^=)
|
||||
default:
|
||||
ABORT("unsupported operator: %s\n", expr.description().c_str());
|
||||
void Interpreter::next() {
|
||||
#ifdef TRACE
|
||||
printf("at %d\n", fIP);
|
||||
#endif
|
||||
ByteCodeInstruction inst = (ByteCodeInstruction) this->read8();
|
||||
switch (inst) {
|
||||
BINARY_OP(kAddI, int32_t, fSigned, +)
|
||||
BINARY_OP(kAddF, float, fFloat, +)
|
||||
case ByteCodeInstruction::kBranch:
|
||||
fIP = this->read16();
|
||||
break;
|
||||
BINARY_OP(kCompareIEQ, int32_t, fSigned, ==)
|
||||
BINARY_OP(kCompareFEQ, float, fFloat, ==)
|
||||
BINARY_OP(kCompareINEQ, int32_t, fSigned, !=)
|
||||
BINARY_OP(kCompareFNEQ, float, fFloat, !=)
|
||||
BINARY_OP(kCompareSGT, int32_t, fSigned, >)
|
||||
BINARY_OP(kCompareUGT, uint32_t, fUnsigned, >)
|
||||
BINARY_OP(kCompareFGT, float, fFloat, >)
|
||||
BINARY_OP(kCompareSGTEQ, int32_t, fSigned, >=)
|
||||
BINARY_OP(kCompareUGTEQ, uint32_t, fUnsigned, >=)
|
||||
BINARY_OP(kCompareFGTEQ, float, fFloat, >=)
|
||||
BINARY_OP(kCompareSLT, int32_t, fSigned, <)
|
||||
BINARY_OP(kCompareULT, uint32_t, fUnsigned, <)
|
||||
BINARY_OP(kCompareFLT, float, fFloat, <)
|
||||
BINARY_OP(kCompareSLTEQ, int32_t, fSigned, <=)
|
||||
BINARY_OP(kCompareULTEQ, uint32_t, fUnsigned, <=)
|
||||
BINARY_OP(kCompareFLTEQ, float, fFloat, <=)
|
||||
case ByteCodeInstruction::kConditionalBranch: {
|
||||
int target = this->read16();
|
||||
if (this->pop().fBool) {
|
||||
fIP = target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::kBoolLiteral_Kind:
|
||||
return Value(((const BoolLiteral&) expr).fValue);
|
||||
case Expression::kConstructor_Kind:
|
||||
case ByteCodeInstruction::kDebugPrint: {
|
||||
Value v = this->pop();
|
||||
printf("Debug: %d(int), %d(uint), %f(float)\n", v.fSigned, v.fUnsigned, v.fFloat);
|
||||
break;
|
||||
case Expression::kIntLiteral_Kind:
|
||||
return Value((int) ((const IntLiteral&) expr).fValue);
|
||||
case Expression::kFieldAccess_Kind:
|
||||
break;
|
||||
case Expression::kFloatLiteral_Kind:
|
||||
return Value((float) ((const FloatLiteral&) expr).fValue);
|
||||
case Expression::kFunctionCall_Kind:
|
||||
return this->call((const FunctionCall&) expr);
|
||||
case Expression::kIndex_Kind: {
|
||||
const IndexExpression& idx = (const IndexExpression&) expr;
|
||||
StackIndex pos = this->evaluate(*idx.fBase).fInt +
|
||||
this->evaluate(*idx.fIndex).fInt;
|
||||
return fStack[pos];
|
||||
}
|
||||
case Expression::kPrefix_Kind: {
|
||||
const PrefixExpression& p = (const PrefixExpression&) expr;
|
||||
switch (p.fOperator) {
|
||||
case Token::MINUS: {
|
||||
Value base = this->evaluate(*p.fOperand);
|
||||
switch (type_kind(p.fType)) {
|
||||
case kFloat_TypeKind:
|
||||
return Value(-base.fFloat);
|
||||
case kInt_TypeKind:
|
||||
return Value(-base.fInt);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
case Token::LOGICALNOT: {
|
||||
Value base = this->evaluate(*p.fOperand);
|
||||
return Value(!base.fBool);
|
||||
}
|
||||
default:
|
||||
abort();
|
||||
BINARY_OP(kDivideS, int32_t, fSigned, /)
|
||||
BINARY_OP(kDivideU, uint32_t, fUnsigned, /)
|
||||
BINARY_OP(kDivideF, float, fFloat, /)
|
||||
case ByteCodeInstruction::kDup:
|
||||
this->push(fStack.back());
|
||||
break;
|
||||
case ByteCodeInstruction::kDupDown: {
|
||||
int count = this->read8();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
fStack.insert(fStack.end() - i - count - 1, fStack[fStack.size() - i - 1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::kPostfix_Kind: {
|
||||
const PostfixExpression& p = (const PostfixExpression&) expr;
|
||||
StackIndex lvalue = this->getLValue(*p.fOperand);
|
||||
Value result = fStack[lvalue];
|
||||
switch (type_kind(p.fType)) {
|
||||
case kFloat_TypeKind:
|
||||
if (Token::PLUSPLUS == p.fOperator) {
|
||||
++fStack[lvalue].fFloat;
|
||||
} else {
|
||||
SkASSERT(Token::MINUSMINUS == p.fOperator);
|
||||
--fStack[lvalue].fFloat;
|
||||
}
|
||||
break;
|
||||
case kInt_TypeKind:
|
||||
if (Token::PLUSPLUS == p.fOperator) {
|
||||
++fStack[lvalue].fInt;
|
||||
} else {
|
||||
SkASSERT(Token::MINUSMINUS == p.fOperator);
|
||||
--fStack[lvalue].fInt;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
case ByteCodeInstruction::kFloatToInt: {
|
||||
Value& top = fStack.back();
|
||||
top.fSigned = (int) top.fFloat;
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kSignedToFloat: {
|
||||
Value& top = fStack.back();
|
||||
top.fFloat = (float) top.fSigned;
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kUnsignedToFloat: {
|
||||
Value& top = fStack.back();
|
||||
top.fFloat = (float) top.fUnsigned;
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kLoad: {
|
||||
int target = this->pop().fSigned;
|
||||
SkASSERT(target < (int) fStack.size());
|
||||
this->push(fStack[target]);
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kLoadGlobal: {
|
||||
int target = this->read8();
|
||||
SkASSERT(target < (int) fGlobals.size());
|
||||
this->push(fGlobals[target]);
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kLoadSwizzle: {
|
||||
Value target = this->pop();
|
||||
int count = read8();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
SkASSERT(target.fSigned + fCurrentFunction->fCode[fIP + i] < (int) fStack.size());
|
||||
this->push(fStack[target.fSigned + fCurrentFunction->fCode[fIP + i]]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case Expression::kSetting_Kind:
|
||||
fIP += count;
|
||||
break;
|
||||
case Expression::kSwizzle_Kind:
|
||||
break;
|
||||
case Expression::kVariableReference_Kind:
|
||||
SkASSERT(fVars.size());
|
||||
SkASSERT(fVars.back().find(&((VariableReference&) expr).fVariable) !=
|
||||
fVars.back().end());
|
||||
return fStack[fVars.back()[&((VariableReference&) expr).fVariable]];
|
||||
case Expression::kTernary_Kind: {
|
||||
const TernaryExpression& t = (const TernaryExpression&) expr;
|
||||
return this->evaluate(this->evaluate(*t.fTest).fBool ? *t.fIfTrue : *t.fIfFalse);
|
||||
}
|
||||
case Expression::kTypeReference_Kind:
|
||||
BINARY_OP(kMultiplyS, int32_t, fSigned, *)
|
||||
BINARY_OP(kMultiplyU, uint32_t, fUnsigned, *)
|
||||
BINARY_OP(kMultiplyF, float, fFloat, *)
|
||||
case ByteCodeInstruction::kNot: {
|
||||
Value& top = fStack.back();
|
||||
top.fBool = !top.fBool;
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kNegateF:
|
||||
this->push(-this->pop().fFloat);
|
||||
case ByteCodeInstruction::kNegateS:
|
||||
this->push(-this->pop().fSigned);
|
||||
case ByteCodeInstruction::kPop:
|
||||
for (int i = read8(); i > 0; --i) {
|
||||
this->pop();
|
||||
}
|
||||
break;
|
||||
case ByteCodeInstruction::kPushImmediate:
|
||||
this->push(Value((int) read32()));
|
||||
break;
|
||||
BINARY_OP(kRemainderS, int32_t, fSigned, %)
|
||||
BINARY_OP(kRemainderU, uint32_t, fUnsigned, %)
|
||||
case ByteCodeInstruction::kStore: {
|
||||
Value value = this->pop();
|
||||
int target = this->pop().fSigned;
|
||||
SkASSERT(target < (int) fStack.size());
|
||||
fStack[target] = value;
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kStoreGlobal: {
|
||||
Value value = this->pop();
|
||||
int target = this->pop().fSigned;
|
||||
SkASSERT(target < (int) fGlobals.size());
|
||||
fGlobals[target] = value;
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kStoreSwizzle: {
|
||||
int count = read8();
|
||||
int target = fStack[fStack.size() - count - 1].fSigned;
|
||||
for (int i = count - 1; i >= 0; --i) {
|
||||
SkASSERT(target + fCurrentFunction->fCode[fIP + i] < (int) fStack.size());
|
||||
fStack[target + fCurrentFunction->fCode[fIP + i]] = this->pop();
|
||||
}
|
||||
this->pop();
|
||||
fIP += count;
|
||||
break;
|
||||
}
|
||||
BINARY_OP(kSubtractI, int32_t, fSigned, -)
|
||||
BINARY_OP(kSubtractF, float, fFloat, -)
|
||||
case ByteCodeInstruction::kSwizzle: {
|
||||
Value vec[4];
|
||||
for (int i = this->read8() - 1; i >= 0; --i) {
|
||||
vec[i] = this->pop();
|
||||
}
|
||||
for (int i = this->read8() - 1; i >= 0; --i) {
|
||||
this->push(vec[this->read8()]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kVector:
|
||||
this->nextVector(this->read8());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
printf("unsupported instruction %d\n", (int) inst);
|
||||
SkASSERT(false);
|
||||
}
|
||||
#ifdef TRACE
|
||||
this->dumpStack();
|
||||
#endif
|
||||
}
|
||||
|
||||
static constexpr int VECTOR_MAX = 16;
|
||||
|
||||
#define VECTOR_BINARY_OP(inst, type, field, op) \
|
||||
case ByteCodeInstruction::inst: { \
|
||||
Value result[VECTOR_MAX]; \
|
||||
for (int i = count - 1; i >= 0; --i) { \
|
||||
result[i] = this->pop(); \
|
||||
} \
|
||||
for (int i = count - 1; i >= 0; --i) { \
|
||||
result[i] = this->pop().field op result[i].field; \
|
||||
} \
|
||||
for (int i = 0; i < count; ++i) { \
|
||||
this->push(result[i]); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
|
||||
void Interpreter::nextVector(int count) {
|
||||
ByteCodeInstruction inst = (ByteCodeInstruction) this->read8();
|
||||
switch (inst) {
|
||||
VECTOR_BINARY_OP(kAddI, int32_t, fSigned, +)
|
||||
VECTOR_BINARY_OP(kAddF, float, fFloat, +)
|
||||
case ByteCodeInstruction::kBranch:
|
||||
fIP = this->read16();
|
||||
break;
|
||||
VECTOR_BINARY_OP(kCompareIEQ, int32_t, fSigned, ==)
|
||||
VECTOR_BINARY_OP(kCompareFEQ, float, fFloat, ==)
|
||||
VECTOR_BINARY_OP(kCompareINEQ, int32_t, fSigned, !=)
|
||||
VECTOR_BINARY_OP(kCompareFNEQ, float, fFloat, !=)
|
||||
VECTOR_BINARY_OP(kCompareSGT, int32_t, fSigned, >)
|
||||
VECTOR_BINARY_OP(kCompareUGT, uint32_t, fUnsigned, >)
|
||||
VECTOR_BINARY_OP(kCompareFGT, float, fFloat, >)
|
||||
VECTOR_BINARY_OP(kCompareSGTEQ, int32_t, fSigned, >=)
|
||||
VECTOR_BINARY_OP(kCompareUGTEQ, uint32_t, fUnsigned, >=)
|
||||
VECTOR_BINARY_OP(kCompareFGTEQ, float, fFloat, >=)
|
||||
VECTOR_BINARY_OP(kCompareSLT, int32_t, fSigned, <)
|
||||
VECTOR_BINARY_OP(kCompareULT, uint32_t, fUnsigned, <)
|
||||
VECTOR_BINARY_OP(kCompareFLT, float, fFloat, <)
|
||||
VECTOR_BINARY_OP(kCompareSLTEQ, int32_t, fSigned, <=)
|
||||
VECTOR_BINARY_OP(kCompareULTEQ, uint32_t, fUnsigned, <=)
|
||||
VECTOR_BINARY_OP(kCompareFLTEQ, float, fFloat, <=)
|
||||
case ByteCodeInstruction::kConditionalBranch: {
|
||||
int target = this->read16();
|
||||
if (this->pop().fBool) {
|
||||
fIP = target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
VECTOR_BINARY_OP(kDivideS, int32_t, fSigned, /)
|
||||
VECTOR_BINARY_OP(kDivideU, uint32_t, fUnsigned, /)
|
||||
VECTOR_BINARY_OP(kDivideF, float, fFloat, /)
|
||||
case ByteCodeInstruction::kFloatToInt: {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
Value& v = fStack[fStack.size() - i - 1];
|
||||
v.fSigned = (int) v.fFloat;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kSignedToFloat: {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
Value& v = fStack[fStack.size() - i - 1];
|
||||
v.fFloat = (float) v.fSigned;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kUnsignedToFloat: {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
Value& v = fStack[fStack.size() - i - 1];
|
||||
v.fFloat = (float) v.fUnsigned;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kLoad: {
|
||||
int target = this->pop().fSigned;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
SkASSERT(target < (int) fStack.size());
|
||||
this->push(fStack[target++]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ByteCodeInstruction::kLoadGlobal: {
|
||||
int target = this->read8();
|
||||
SkASSERT(target < (int) fGlobals.size());
|
||||
this->push(fGlobals[target]);
|
||||
break;
|
||||
}
|
||||
VECTOR_BINARY_OP(kMultiplyS, int32_t, fSigned, *)
|
||||
VECTOR_BINARY_OP(kMultiplyU, uint32_t, fUnsigned, *)
|
||||
VECTOR_BINARY_OP(kMultiplyF, float, fFloat, *)
|
||||
VECTOR_BINARY_OP(kRemainderS, int32_t, fSigned, %)
|
||||
VECTOR_BINARY_OP(kRemainderU, uint32_t, fUnsigned, %)
|
||||
case ByteCodeInstruction::kStore: {
|
||||
int target = fStack[fStack.size() - count - 1].fSigned + count;
|
||||
for (int i = count - 1; i >= 0; --i) {
|
||||
SkASSERT(target < (int) fStack.size());
|
||||
fStack[--target] = this->pop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
VECTOR_BINARY_OP(kSubtractI, int32_t, fSigned, -)
|
||||
VECTOR_BINARY_OP(kSubtractF, float, fFloat, -)
|
||||
case ByteCodeInstruction::kVector:
|
||||
this->nextVector(this->read8());
|
||||
default:
|
||||
printf("unsupported instruction %d\n", (int) inst);
|
||||
SkASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::run() {
|
||||
while (fIP < (int) fCurrentFunction->fCode.size()) {
|
||||
next();
|
||||
}
|
||||
ABORT("unsupported expression: %s\n", expr.description().c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -8,6 +8,7 @@
|
||||
#ifndef SKSL_INTERPRETER
|
||||
#define SKSL_INTERPRETER
|
||||
|
||||
#include "SkSLByteCode.h"
|
||||
#include "ir/SkSLAppendStage.h"
|
||||
#include "ir/SkSLExpression.h"
|
||||
#include "ir/SkSLFunctionCall.h"
|
||||
@ -17,31 +18,30 @@
|
||||
|
||||
#include <stack>
|
||||
|
||||
class SkRasterPipeline;
|
||||
|
||||
namespace SkSL {
|
||||
|
||||
class Interpreter {
|
||||
typedef int StackIndex;
|
||||
|
||||
struct StatementIndex {
|
||||
const Statement* fStatement;
|
||||
size_t fIndex;
|
||||
};
|
||||
|
||||
public:
|
||||
union Value {
|
||||
Value() {}
|
||||
|
||||
Value(float f)
|
||||
: fFloat(f) {}
|
||||
|
||||
Value(int i)
|
||||
: fInt(i) {}
|
||||
Value(int32_t s)
|
||||
: fSigned(s) {}
|
||||
|
||||
Value(uint32_t u)
|
||||
: fUnsigned(u) {}
|
||||
|
||||
Value(bool b)
|
||||
: fBool(b) {}
|
||||
|
||||
float fFloat;
|
||||
int fInt;
|
||||
int32_t fSigned;
|
||||
uint32_t fUnsigned;
|
||||
bool fBool;
|
||||
};
|
||||
|
||||
@ -51,37 +51,49 @@ public:
|
||||
kBool_TypeKind
|
||||
};
|
||||
|
||||
Interpreter(std::unique_ptr<Program> program, SkRasterPipeline* pipeline, std::vector<Value>* stack)
|
||||
Interpreter(std::unique_ptr<Program> program, std::unique_ptr<ByteCode> byteCode)
|
||||
: fProgram(std::move(program))
|
||||
, fPipeline(*pipeline)
|
||||
, fStack(*stack) {}
|
||||
, fByteCode(std::move(byteCode))
|
||||
, fReturnValue(0) {}
|
||||
|
||||
/**
|
||||
* Invokes the specified function with the given arguments, returning its return value. 'out'
|
||||
* and 'inout' parameters will result in the 'args' array being modified.
|
||||
*/
|
||||
Value run(const ByteCodeFunction& f, Value args[], Value inputs[]);
|
||||
|
||||
private:
|
||||
StackIndex stackAlloc(int count);
|
||||
|
||||
uint8_t read8();
|
||||
|
||||
uint16_t read16();
|
||||
|
||||
uint32_t read32();
|
||||
|
||||
void next();
|
||||
|
||||
void nextVector(int count);
|
||||
|
||||
void run();
|
||||
|
||||
void run(const FunctionDefinition& f);
|
||||
|
||||
void push(Value value);
|
||||
void push(Value v);
|
||||
|
||||
Value pop();
|
||||
|
||||
StackIndex stackAlloc(int count);
|
||||
void swizzle();
|
||||
|
||||
void runStatement();
|
||||
void disassemble(const ByteCodeFunction& f);
|
||||
|
||||
StackIndex getLValue(const Expression& expr);
|
||||
void dumpStack();
|
||||
|
||||
Value call(const FunctionCall& c);
|
||||
|
||||
void appendStage(const AppendStage& c);
|
||||
|
||||
Value evaluate(const Expression& expr);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Program> fProgram;
|
||||
SkRasterPipeline& fPipeline;
|
||||
std::vector<StatementIndex> fCurrentIndex;
|
||||
std::vector<std::unordered_map<const Variable*, StackIndex>> fVars;
|
||||
std::vector<Value> &fStack;
|
||||
std::unique_ptr<ByteCode> fByteCode;
|
||||
int fIP;
|
||||
const ByteCodeFunction* fCurrentFunction;
|
||||
std::vector<Value> fGlobals;
|
||||
std::vector<Value> fStack;
|
||||
Value fReturnValue;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -1915,28 +1915,6 @@ SpvId SPIRVCodeGenerator::writeBinaryOperation(const Type& resultType,
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_assignment(Token::Kind op) {
|
||||
switch (op) {
|
||||
case Token::EQ: // fall through
|
||||
case Token::PLUSEQ: // fall through
|
||||
case Token::MINUSEQ: // fall through
|
||||
case Token::STAREQ: // fall through
|
||||
case Token::SLASHEQ: // fall through
|
||||
case Token::PERCENTEQ: // fall through
|
||||
case Token::SHLEQ: // fall through
|
||||
case Token::SHREQ: // fall through
|
||||
case Token::BITWISEOREQ: // fall through
|
||||
case Token::BITWISEXOREQ: // fall through
|
||||
case Token::BITWISEANDEQ: // fall through
|
||||
case Token::LOGICALOREQ: // fall through
|
||||
case Token::LOGICALXOREQ: // fall through
|
||||
case Token::LOGICALANDEQ:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SpvId SPIRVCodeGenerator::foldToBool(SpvId id, const Type& operandType, SpvOp op,
|
||||
OutputStream& out) {
|
||||
if (operandType.kind() == Type::kVector_Kind) {
|
||||
|
@ -30,4 +30,45 @@ void write_stringstream(const StringStream& s, OutputStream& out) {
|
||||
out.write(s.str().c_str(), s.str().size());
|
||||
}
|
||||
|
||||
bool is_assignment(Token::Kind op) {
|
||||
switch (op) {
|
||||
case Token::EQ: // fall through
|
||||
case Token::PLUSEQ: // fall through
|
||||
case Token::MINUSEQ: // fall through
|
||||
case Token::STAREQ: // fall through
|
||||
case Token::SLASHEQ: // fall through
|
||||
case Token::PERCENTEQ: // fall through
|
||||
case Token::SHLEQ: // fall through
|
||||
case Token::SHREQ: // fall through
|
||||
case Token::BITWISEOREQ: // fall through
|
||||
case Token::BITWISEXOREQ: // fall through
|
||||
case Token::BITWISEANDEQ: // fall through
|
||||
case Token::LOGICALOREQ: // fall through
|
||||
case Token::LOGICALXOREQ: // fall through
|
||||
case Token::LOGICALANDEQ:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Token::Kind remove_assignment(Token::Kind op) {
|
||||
switch (op) {
|
||||
case Token::PLUSEQ: return Token::PLUS;
|
||||
case Token::MINUSEQ: return Token::MINUS;
|
||||
case Token::STAREQ: return Token::STAR;
|
||||
case Token::SLASHEQ: return Token::SLASH;
|
||||
case Token::PERCENTEQ: return Token::PERCENT;
|
||||
case Token::SHLEQ: return Token::SHL;
|
||||
case Token::SHREQ: return Token::SHR;
|
||||
case Token::BITWISEOREQ: return Token::BITWISEOR;
|
||||
case Token::BITWISEXOREQ: return Token::BITWISEXOR;
|
||||
case Token::BITWISEANDEQ: return Token::BITWISEAND;
|
||||
case Token::LOGICALOREQ: return Token::LOGICALOR;
|
||||
case Token::LOGICALXOREQ: return Token::LOGICALXOR;
|
||||
case Token::LOGICALANDEQ: return Token::LOGICALAND;
|
||||
default: return Token::INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <memory>
|
||||
#include "stdlib.h"
|
||||
#include "string.h"
|
||||
#include "SkSLLexer.h"
|
||||
#include "SkSLDefines.h"
|
||||
#include "SkSLString.h"
|
||||
#include "SkSLStringStream.h"
|
||||
@ -396,6 +397,13 @@ public:
|
||||
|
||||
void write_stringstream(const StringStream& d, OutputStream& out);
|
||||
|
||||
// Returns true if op is '=' or any compound assignment operator ('+=', '-=', etc.)
|
||||
bool is_assignment(Token::Kind op);
|
||||
|
||||
// Given a compound assignment operator, returns the non-assignment version of the operator (e.g.
|
||||
// '+=' becomes '+')
|
||||
Token::Kind remove_assignment(Token::Kind op);
|
||||
|
||||
NORETURN void sksl_abort();
|
||||
|
||||
} // namespace
|
||||
|
200
tests/SkSLInterpreterTest.cpp
Normal file
200
tests/SkSLInterpreterTest.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkSLCompiler.h"
|
||||
#include "SkSLInterpreter.h"
|
||||
|
||||
#include "Test.h"
|
||||
|
||||
void test(skiatest::Reporter* r, const char* src, float inR, float inG, float inB, float inA,
|
||||
float expectedR, float expectedG, float expectedB, float expectedA) {
|
||||
SkSL::Compiler compiler;
|
||||
SkSL::Program::Settings settings;
|
||||
std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
|
||||
SkSL::Program::kPipelineStage_Kind,
|
||||
SkSL::String(src), settings);
|
||||
REPORTER_ASSERT(r, program);
|
||||
if (program) {
|
||||
std::unique_ptr<SkSL::ByteCode> byteCode = compiler.toByteCode(*program);
|
||||
REPORTER_ASSERT(r, !compiler.errorCount());
|
||||
if (compiler.errorCount() > 0) {
|
||||
printf("%s\n%s", src, compiler.errorText().c_str());
|
||||
return;
|
||||
}
|
||||
SkSL::ByteCodeFunction* main = byteCode->fFunctions[0].get();
|
||||
SkSL::Interpreter interpreter(std::move(program), std::move(byteCode));
|
||||
float inoutColor[4] = { inR, inG, inB, inA };
|
||||
interpreter.run(*main, (SkSL::Interpreter::Value*) inoutColor, nullptr);
|
||||
if (inoutColor[0] != expectedR || inoutColor[1] != expectedG ||
|
||||
inoutColor[2] != expectedB || inoutColor[3] != expectedA) {
|
||||
printf("for program: %s\n", src);
|
||||
printf(" expected (%f, %f, %f, %f), but received (%f, %f, %f, %f)\n", expectedR,
|
||||
expectedG, expectedB, expectedA, inoutColor[0], inoutColor[1], inoutColor[2],
|
||||
inoutColor[3]);
|
||||
}
|
||||
REPORTER_ASSERT(r, inoutColor[0] == expectedR);
|
||||
REPORTER_ASSERT(r, inoutColor[1] == expectedG);
|
||||
REPORTER_ASSERT(r, inoutColor[2] == expectedB);
|
||||
REPORTER_ASSERT(r, inoutColor[3] == expectedA);
|
||||
} else {
|
||||
printf("%s\n%s", src, compiler.errorText().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterTEMP_TEST, r) {
|
||||
test(r, "void main(inout half4 color) { half4 c = color; color += c; }", 0.25, 0.5, 0.75, 1,
|
||||
0.5, 1, 1.5, 2);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterAdd, r) {
|
||||
test(r, "void main(inout half4 color) { color.r = color.r + color.g; }", 0.25, 0.75, 0, 0, 1,
|
||||
0.75, 0, 0);
|
||||
test(r, "void main(inout half4 color) { color += half4(1, 2, 3, 4); }", 4, 3, 2, 1, 5, 5, 5, 5);
|
||||
test(r, "void main(inout half4 color) { half4 c = color; color += c; }", 0.25, 0.5, 0.75, 1,
|
||||
0.5, 1, 1.5, 2);
|
||||
test(r, "void main(inout half4 color) { int a = 1; int b = 3; color.r = a + b; }", 1, 2, 3, 4,
|
||||
4, 2, 3, 4);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterSubtract, r) {
|
||||
test(r, "void main(inout half4 color) { color.r = color.r - color.g; }", 1, 0.75, 0, 0, 0.25,
|
||||
0.75, 0, 0);
|
||||
test(r, "void main(inout half4 color) { color -= half4(1, 2, 3, 4); }", 5, 5, 5, 5, 4, 3, 2, 1);
|
||||
test(r, "void main(inout half4 color) { half4 c = color; color -= c; }", 4, 3, 2, 1,
|
||||
0, 0, 0, 0);
|
||||
test(r, "void main(inout half4 color) { int a = 3; int b = 1; color.r = a - b; }", 0, 0, 0, 0,
|
||||
2, 0, 0, 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterMultiply, r) {
|
||||
test(r, "void main(inout half4 color) { color.r = color.r * color.g; }", 2, 3, 0, 0, 6, 3, 0,
|
||||
0);
|
||||
test(r, "void main(inout half4 color) { color *= half4(1, 2, 3, 4); }", 2, 3, 4, 5, 2, 6, 12,
|
||||
20);
|
||||
test(r, "void main(inout half4 color) { half4 c = color; color *= c; }", 4, 3, 2, 1,
|
||||
16, 9, 4, 1);
|
||||
test(r, "void main(inout half4 color) { int a = 3; int b = -2; color.r = a * b; }", 0, 0, 0, 0,
|
||||
-6, 0, 0, 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterDivide, r) {
|
||||
test(r, "void main(inout half4 color) { color.r = color.r / color.g; }", 1, 2, 0, 0, 0.5, 2, 0,
|
||||
0);
|
||||
test(r, "void main(inout half4 color) { color /= half4(1, 2, 3, 4); }", 12, 12, 12, 12, 12, 6,
|
||||
4, 3);
|
||||
test(r, "void main(inout half4 color) { half4 c = color; color /= c; }", 4, 3, 2, 1,
|
||||
1, 1, 1, 1);
|
||||
test(r, "void main(inout half4 color) { int a = 8; int b = -2; color.r = a / b; }", 0, 0, 0, 0,
|
||||
-4, 0, 0, 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterRemainder, r) {
|
||||
test(r, "void main(inout half4 color) { int a = 8; int b = 3; a %= b; color.r = a; }", 0, 0, 0,
|
||||
0, 2, 0, 0, 0);
|
||||
test(r, "void main(inout half4 color) { int a = 8; int b = 3; color.r = a % b; }", 0, 0, 0, 0,
|
||||
2, 0, 0, 0);
|
||||
test(r, "void main(inout half4 color) { int2 a = int2(8, 10); a %= 6; color.rg = a; }", 0, 0, 0,
|
||||
0, 2, 4, 0, 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterIf, r) {
|
||||
test(r, "void main(inout half4 color) { if (color.r > color.g) color.a = 1; }", 5, 3, 0, 0,
|
||||
5, 3, 0, 1);
|
||||
test(r, "void main(inout half4 color) { if (color.r > color.g) color.a = 1; }", 5, 5, 0, 0,
|
||||
5, 5, 0, 0);
|
||||
test(r, "void main(inout half4 color) { if (color.r > color.g) color.a = 1; }", 5, 6, 0, 0,
|
||||
5, 6, 0, 0);
|
||||
test(r, "void main(inout half4 color) { if (color.r < color.g) color.a = 1; }", 3, 5, 0, 0,
|
||||
3, 5, 0, 1);
|
||||
test(r, "void main(inout half4 color) { if (color.r < color.g) color.a = 1; }", 5, 5, 0, 0,
|
||||
5, 5, 0, 0);
|
||||
test(r, "void main(inout half4 color) { if (color.r < color.g) color.a = 1; }", 6, 5, 0, 0,
|
||||
6, 5, 0, 0);
|
||||
test(r, "void main(inout half4 color) { if (color.r >= color.g) color.a = 1; }", 5, 3, 0, 0,
|
||||
5, 3, 0, 1);
|
||||
test(r, "void main(inout half4 color) { if (color.r >= color.g) color.a = 1; }", 5, 5, 0, 0,
|
||||
5, 5, 0, 1);
|
||||
test(r, "void main(inout half4 color) { if (color.r >= color.g) color.a = 1; }", 5, 6, 0, 0,
|
||||
5, 6, 0, 0);
|
||||
test(r, "void main(inout half4 color) { if (color.r <= color.g) color.a = 1; }", 3, 5, 0, 0,
|
||||
3, 5, 0, 1);
|
||||
test(r, "void main(inout half4 color) { if (color.r <= color.g) color.a = 1; }", 5, 5, 0, 0,
|
||||
5, 5, 0, 1);
|
||||
test(r, "void main(inout half4 color) { if (color.r <= color.g) color.a = 1; }", 6, 5, 0, 0,
|
||||
6, 5, 0, 0);
|
||||
test(r, "void main(inout half4 color) { if (color.r == color.g) color.a = 1; }", 2, 2, 0, 0,
|
||||
2, 2, 0, 1);
|
||||
test(r, "void main(inout half4 color) { if (color.r == color.g) color.a = 1; }", 2, -2, 0, 0,
|
||||
2, -2, 0, 0);
|
||||
test(r, "void main(inout half4 color) { if (color.r != color.g) color.a = 1; }", 2, 2, 0, 0,
|
||||
2, 2, 0, 0);
|
||||
test(r, "void main(inout half4 color) { if (color.r != color.g) color.a = 1; }", 2, -2, 0, 0,
|
||||
2, -2, 0, 1);
|
||||
test(r, "void main(inout half4 color) { if (color.r == color.g) color.a = 1; else "
|
||||
"color.a = 2; }", 1, 1, 0, 0, 1, 1, 0, 1);
|
||||
test(r, "void main(inout half4 color) { if (color.r == color.g) color.a = 1; else "
|
||||
"color.a = 2; }", 2, -2, 0, 0, 2, -2, 0, 2);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterWhile, r) {
|
||||
test(r, "void main(inout half4 color) { while (color.r < 1) color.r += 0.25; }", 0, 0, 0, 0, 1,
|
||||
0, 0, 0);
|
||||
test(r, "void main(inout half4 color) { while (color.r > 1) color.r += 0.25; }", 0, 0, 0, 0, 0,
|
||||
0, 0, 0);
|
||||
test(r, "void main(inout half4 color) { while (true) { color.r += 0.5; "
|
||||
"if (color.r > 1) break; } }", 0, 0, 0, 0, 1.5, 0, 0, 0);
|
||||
test(r, "void main(inout half4 color) { while (color.r < 10) { color.r += 0.5; "
|
||||
"if (color.r < 5) continue; break; } }", 0, 0, 0, 0, 5, 0, 0, 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterDo, r) {
|
||||
test(r, "void main(inout half4 color) { do color.r += 0.25; while (color.r < 1); }", 0, 0, 0, 0,
|
||||
1, 0, 0, 0);
|
||||
test(r, "void main(inout half4 color) { do color.r += 0.25; while (color.r > 1); }", 0, 0, 0, 0,
|
||||
0.25, 0, 0, 0);
|
||||
test(r, "void main(inout half4 color) { do { color.r += 0.5; if (color.r > 1) break; } while "
|
||||
"(true); }", 0, 0, 0, 0, 1.5, 0, 0, 0);
|
||||
test(r, "void main(inout half4 color) {do { color.r += 0.5; if (color.r < 5) "
|
||||
"continue; if (color.r >= 5) break; } while (true); }", 0, 0, 0, 0, 5, 0, 0, 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterFor, r) {
|
||||
test(r, "void main(inout half4 color) { for (int i = 1; i <= 10; ++i) color.r += i; }", 0, 0, 0,
|
||||
0, 55, 0, 0, 0);
|
||||
test(r,
|
||||
"void main(inout half4 color) {"
|
||||
" for (int i = 1; i <= 10; ++i)"
|
||||
" for (int j = i; j <= 10; ++j)"
|
||||
" color.r += j;"
|
||||
"}",
|
||||
0, 0, 0, 0,
|
||||
385, 0, 0, 0);
|
||||
test(r,
|
||||
"void main(inout half4 color) {"
|
||||
" for (int i = 1; i <= 10; ++i)"
|
||||
" for (int j = 1; ; ++j) {"
|
||||
" if (i == j) continue;"
|
||||
" if (j > 10) break;"
|
||||
" color.r += j;"
|
||||
" }"
|
||||
"}",
|
||||
0, 0, 0, 0,
|
||||
495, 0, 0, 0);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterSwizzle, r) {
|
||||
test(r, "void main(inout half4 color) { color = color.abgr; }", 1, 2, 3, 4, 4, 3, 2, 1);
|
||||
test(r, "void main(inout half4 color) { color.rgb = half4(5, 6, 7, 8).bbg; }", 1, 2, 3, 4, 7, 7,
|
||||
6, 4);
|
||||
test(r, "void main(inout half4 color) { color.bgr = int3(5, 6, 7); }", 1, 2, 3, 4, 7, 6,
|
||||
5, 4);
|
||||
}
|
||||
|
||||
DEF_TEST(SkSLInterpreterGlobal, r) {
|
||||
test(r, "int x; void main(inout half4 color) { x = 10; color.b = x; }", 1, 2, 3, 4, 1, 2, 10,
|
||||
4);
|
||||
}
|
Loading…
Reference in New Issue
Block a user