Switch implementation
Implemented switch/case for glsl in OpenGL ES 3.0.
For simplicity, it is implemented as a loop without a condition,
so break statements work properly like so:
begin switch
if(...) // 1st case
...
else if(...) // other cases
...
else // default case
...
end switch // Anchor point for break statements
All related dEQP tests pass, except 7 tests where vertex shaders
contain a switch or a loop within another switch. These 7 failures
have only about 5% of bad pixel and seem to be related to an issue
with int(floor(...)), since the equivalent tests inside the fragment
shader pass.
KNOWN ISSUE: If a switch is within a loop and one of the cases
contains a "continue" statement, this will not be
handled correctly at the moment. There are no dEQP
tests for this at the moment, AFAIK.
Change-Id: I3ba34ab06a759d07e8520f6a87d75036a5cdaef5
Reviewed-on: https://swiftshader-review.googlesource.com/5272
Tested-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <capn@google.com>
diff --git a/src/OpenGL/compiler/IntermTraverse.cpp b/src/OpenGL/compiler/IntermTraverse.cpp
index 09c8e6e..44b1c3d 100644
--- a/src/OpenGL/compiler/IntermTraverse.cpp
+++ b/src/OpenGL/compiler/IntermTraverse.cpp
@@ -232,23 +232,8 @@
if(visit)
{
it->incrementDepth(this);
- if(it->rightToLeft)
- {
- if(mStatementList)
- mStatementList->traverse(it);
- if(it->inVisit)
- visit = it->visitSwitch(InVisit, this);
- if(visit)
- mInit->traverse(it);
- }
- else
- {
- mInit->traverse(it);
- if(it->inVisit)
- visit = it->visitSwitch(InVisit, this);
- if(visit && mStatementList)
- mStatementList->traverse(it);
- }
+ if(it->inVisit)
+ visit = it->visitSwitch(InVisit, this);
it->decrementDepth();
}
diff --git a/src/OpenGL/compiler/OutputASM.cpp b/src/OpenGL/compiler/OutputASM.cpp
index 59949f1..a27b30e 100644
--- a/src/OpenGL/compiler/OutputASM.cpp
+++ b/src/OpenGL/compiler/OutputASM.cpp
@@ -1775,6 +1775,90 @@
return true;
}
+ bool OutputASM::visitSwitch(Visit visit, TIntermSwitch *node)
+ {
+ if(currentScope != emitScope)
+ {
+ return false;
+ }
+
+ TIntermTyped* switchValue = node->getInit();
+ TIntermAggregate* opList = node->getStatementList();
+
+ if(!switchValue || !opList)
+ {
+ return false;
+ }
+
+ switchValue->traverse(this);
+
+ emit(sw::Shader::OPCODE_SWITCH);
+
+ TIntermSequence& sequence = opList->getSequence();
+ TIntermSequence::iterator it = sequence.begin();
+ TIntermSequence::iterator defaultIt = sequence.end();
+ int nbCases = 0;
+ for(; it != sequence.end(); ++it)
+ {
+ TIntermCase* currentCase = (*it)->getAsCaseNode();
+ if(currentCase)
+ {
+ TIntermSequence::iterator caseIt = it;
+
+ TIntermTyped* condition = currentCase->getCondition();
+ if(condition) // non default case
+ {
+ if(nbCases != 0)
+ {
+ emit(sw::Shader::OPCODE_ELSE);
+ }
+
+ condition->traverse(this);
+ Temporary result(this);
+ emitBinary(sw::Shader::OPCODE_EQ, &result, switchValue, condition);
+ emit(sw::Shader::OPCODE_IF, 0, &result);
+ nbCases++;
+
+ for(++caseIt; caseIt != sequence.end(); ++caseIt)
+ {
+ (*caseIt)->traverse(this);
+ if((*caseIt)->getAsBranchNode()) // Kill, Break, Continue or Return
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ defaultIt = it; // The default case might not be the last case, keep it for last
+ }
+ }
+ }
+
+ // If there's a default case, traverse it here
+ if(defaultIt != sequence.end())
+ {
+ emit(sw::Shader::OPCODE_ELSE);
+ for(++defaultIt; defaultIt != sequence.end(); ++defaultIt)
+ {
+ (*defaultIt)->traverse(this);
+ if((*defaultIt)->getAsBranchNode()) // Kill, Break, Continue or Return
+ {
+ break;
+ }
+ }
+ }
+
+ for(int i = 0; i < nbCases; ++i)
+ {
+ emit(sw::Shader::OPCODE_ENDIF);
+ }
+
+ emit(sw::Shader::OPCODE_ENDSWITCH);
+
+ return false;
+ }
+
Instruction *OutputASM::emit(sw::Shader::Opcode op, TIntermTyped *dst, TIntermNode *src0, TIntermNode *src1, TIntermNode *src2, TIntermNode *src3, TIntermNode *src4)
{
return emit(op, dst, 0, src0, 0, src1, 0, src2, 0, src3, 0, src4, 0);
diff --git a/src/OpenGL/compiler/OutputASM.h b/src/OpenGL/compiler/OutputASM.h
index eff3c78..66d0ce9 100644
--- a/src/OpenGL/compiler/OutputASM.h
+++ b/src/OpenGL/compiler/OutputASM.h
@@ -258,6 +258,7 @@
virtual bool visitAggregate(Visit visit, TIntermAggregate*);
virtual bool visitLoop(Visit visit, TIntermLoop*);
virtual bool visitBranch(Visit visit, TIntermBranch*);
+ virtual bool visitSwitch(Visit, TIntermSwitch*);
sw::Shader::Opcode getOpcode(sw::Shader::Opcode op, TIntermTyped *in) const;
Instruction *emit(sw::Shader::Opcode op, TIntermTyped *dst = 0, TIntermNode *src0 = 0, TIntermNode *src1 = 0, TIntermNode *src2 = 0, TIntermNode *src3 = 0, TIntermNode *src4 = 0);
diff --git a/src/OpenGL/compiler/intermediate.h b/src/OpenGL/compiler/intermediate.h
index 3e0d876..a02df58 100644
--- a/src/OpenGL/compiler/intermediate.h
+++ b/src/OpenGL/compiler/intermediate.h
@@ -643,6 +643,7 @@
TIntermSwitch *getAsSwitchNode() { return this; }
+ TIntermTyped *getInit() { return mInit; }
TIntermAggregate *getStatementList() { return mStatementList; }
void setStatementList(TIntermAggregate *statementList) { mStatementList = statementList; }
diff --git a/src/Shader/PixelProgram.cpp b/src/Shader/PixelProgram.cpp
index d229a92..1c0ffb9 100644
--- a/src/Shader/PixelProgram.cpp
+++ b/src/Shader/PixelProgram.cpp
@@ -307,12 +307,14 @@
case Shader::OPCODE_ENDLOOP: ENDLOOP(); break;
case Shader::OPCODE_ENDREP: ENDREP(); break;
case Shader::OPCODE_ENDWHILE: ENDWHILE(); break;
+ case Shader::OPCODE_ENDSWITCH: ENDSWITCH(); break;
case Shader::OPCODE_IF: IF(src0); break;
case Shader::OPCODE_IFC: IFC(s0, s1, control); break;
case Shader::OPCODE_LABEL: LABEL(dst.index); break;
case Shader::OPCODE_LOOP: LOOP(src1); break;
case Shader::OPCODE_REP: REP(src0); break;
case Shader::OPCODE_WHILE: WHILE(src0); break;
+ case Shader::OPCODE_SWITCH: SWITCH(); break;
case Shader::OPCODE_RET: RET(); break;
case Shader::OPCODE_LEAVE: LEAVE(); break;
case Shader::OPCODE_CMP: cmp(d, s0, s1, control); break;
@@ -1475,6 +1477,19 @@
whileTest = false;
}
+ void PixelProgram::ENDSWITCH()
+ {
+ loopRepDepth--;
+
+ llvm::BasicBlock *endBlock = loopRepEndBlock[loopRepDepth];
+
+ Nucleus::createBr(loopRepEndBlock[loopRepDepth]);
+ Nucleus::setInsertBlock(endBlock);
+
+ enableIndex--;
+ enableBreak = Int4(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF);
+ }
+
void PixelProgram::IF(const Src &src)
{
if(src.type == Shader::PARAMETER_CONSTBOOL)
@@ -1674,6 +1689,20 @@
breakDepth = 0;
}
+ void PixelProgram::SWITCH()
+ {
+ enableIndex++;
+ enableStack[enableIndex] = Int4(0xFFFFFFFF);
+
+ llvm::BasicBlock *endBlock = Nucleus::createBasicBlock();
+
+ loopRepTestBlock[loopRepDepth] = nullptr;
+ loopRepEndBlock[loopRepDepth] = endBlock;
+
+ loopRepDepth++;
+ breakDepth = 0;
+ }
+
void PixelProgram::RET()
{
if(currentLabel == -1)
diff --git a/src/Shader/PixelProgram.hpp b/src/Shader/PixelProgram.hpp
index a518cc1..a8d60c8 100644
--- a/src/Shader/PixelProgram.hpp
+++ b/src/Shader/PixelProgram.hpp
@@ -136,6 +136,7 @@
void ENDLOOP();
void ENDREP();
void ENDWHILE();
+ void ENDSWITCH();
void IF(const Src &src);
void IFb(const Src &boolRegister);
void IFp(const Src &predicateRegister);
@@ -145,6 +146,7 @@
void LOOP(const Src &integerRegister);
void REP(const Src &integerRegister);
void WHILE(const Src &temporaryRegister);
+ void SWITCH();
void RET();
void LEAVE();
diff --git a/src/Shader/Shader.cpp b/src/Shader/Shader.cpp
index 8e92d2e..46858c9 100644
--- a/src/Shader/Shader.cpp
+++ b/src/Shader/Shader.cpp
@@ -971,6 +971,8 @@
case OPCODE_LEAVE: return "leave";
case OPCODE_CONTINUE: return "continue";
case OPCODE_TEST: return "test";
+ case OPCODE_SWITCH: return "switch";
+ case OPCODE_ENDSWITCH: return "endswitch";
default:
ASSERT(false);
}
@@ -1088,14 +1090,14 @@
return opcode == OPCODE_BREAK || opcode == OPCODE_BREAKC || opcode == OPCODE_BREAKP;
}
- bool Shader::Instruction::isLoop() const
+ bool Shader::Instruction::isLoopOrSwitch() const
{
- return opcode == OPCODE_LOOP || opcode == OPCODE_REP || opcode == OPCODE_WHILE;
+ return opcode == OPCODE_LOOP || opcode == OPCODE_REP || opcode == OPCODE_WHILE || opcode == OPCODE_SWITCH;
}
- bool Shader::Instruction::isEndLoop() const
+ bool Shader::Instruction::isEndLoopOrSwitch() const
{
- return opcode == OPCODE_ENDLOOP || opcode == OPCODE_ENDREP || opcode == OPCODE_ENDWHILE;
+ return opcode == OPCODE_ENDLOOP || opcode == OPCODE_ENDREP || opcode == OPCODE_ENDWHILE || opcode == OPCODE_ENDSWITCH;;
}
bool Shader::Instruction::isPredicated() const
@@ -1686,11 +1688,11 @@
if(breakDepth > 0)
{
- if(instruction[i]->isLoop()) // Nested loop, don't make the end of it disable the break execution mask
+ if(instruction[i]->isLoopOrSwitch()) // Nested loop or switch, don't make the end of it disable the break execution mask
{
breakDepth++;
}
- else if(instruction[i]->isEndLoop())
+ else if(instruction[i]->isEndLoopOrSwitch())
{
breakDepth--;
}
@@ -1711,11 +1713,11 @@
if(continueDepth > 0)
{
- if(instruction[i]->isLoop()) // Nested loop, don't make the end of it disable the break execution mask
+ if(instruction[i]->isLoopOrSwitch()) // Nested loop or switch, don't make the end of it disable the break execution mask
{
continueDepth++;
}
- else if(instruction[i]->isEndLoop())
+ else if(instruction[i]->isEndLoopOrSwitch())
{
continueDepth--;
}
diff --git a/src/Shader/Shader.hpp b/src/Shader/Shader.hpp
index d26a5b7..7e86360 100644
--- a/src/Shader/Shader.hpp
+++ b/src/Shader/Shader.hpp
@@ -244,6 +244,8 @@
OPCODE_LEAVE, // Return before the end of the function
OPCODE_CONTINUE,
OPCODE_TEST, // Marks the end of the code that can be skipped by 'continue'
+ OPCODE_SWITCH,
+ OPCODE_ENDSWITCH,
// Integer opcodes
OPCODE_INEG,
@@ -493,8 +495,8 @@
bool isBranch() const;
bool isCall() const;
bool isBreak() const;
- bool isLoop() const;
- bool isEndLoop() const;
+ bool isLoopOrSwitch() const;
+ bool isEndLoopOrSwitch() const;
bool isPredicated() const;
diff --git a/src/Shader/VertexProgram.cpp b/src/Shader/VertexProgram.cpp
index 5be2070..d59a33b 100644
--- a/src/Shader/VertexProgram.cpp
+++ b/src/Shader/VertexProgram.cpp
@@ -296,12 +296,14 @@
case Shader::OPCODE_ENDLOOP: ENDLOOP(); break;
case Shader::OPCODE_ENDREP: ENDREP(); break;
case Shader::OPCODE_ENDWHILE: ENDWHILE(); break;
+ case Shader::OPCODE_ENDSWITCH: ENDSWITCH(); break;
case Shader::OPCODE_IF: IF(src0); break;
case Shader::OPCODE_IFC: IFC(s0, s1, control); break;
case Shader::OPCODE_LABEL: LABEL(dst.index); break;
case Shader::OPCODE_LOOP: LOOP(src1); break;
case Shader::OPCODE_REP: REP(src0); break;
case Shader::OPCODE_WHILE: WHILE(src0); break;
+ case Shader::OPCODE_SWITCH: SWITCH(); break;
case Shader::OPCODE_RET: RET(); break;
case Shader::OPCODE_LEAVE: LEAVE(); break;
case Shader::OPCODE_CMP: cmp(d, s0, s1, control); break;
@@ -1256,6 +1258,19 @@
whileTest = false;
}
+ void VertexProgram::ENDSWITCH()
+ {
+ loopRepDepth--;
+
+ llvm::BasicBlock *endBlock = loopRepEndBlock[loopRepDepth];
+
+ Nucleus::createBr(loopRepEndBlock[loopRepDepth]);
+ Nucleus::setInsertBlock(endBlock);
+
+ enableIndex--;
+ enableBreak = Int4(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF);
+ }
+
void VertexProgram::IF(const Src &src)
{
if(src.type == Shader::PARAMETER_CONSTBOOL)
@@ -1456,6 +1471,20 @@
breakDepth = 0;
}
+ void VertexProgram::SWITCH()
+ {
+ enableIndex++;
+ enableStack[enableIndex] = Int4(0xFFFFFFFF);
+
+ llvm::BasicBlock *endBlock = Nucleus::createBasicBlock();
+
+ loopRepTestBlock[loopRepDepth] = nullptr;
+ loopRepEndBlock[loopRepDepth] = endBlock;
+
+ loopRepDepth++;
+ breakDepth = 0;
+ }
+
void VertexProgram::RET()
{
if(currentLabel == -1)
diff --git a/src/Shader/VertexProgram.hpp b/src/Shader/VertexProgram.hpp
index f4f1677..54335c2 100644
--- a/src/Shader/VertexProgram.hpp
+++ b/src/Shader/VertexProgram.hpp
@@ -93,6 +93,7 @@
void ENDLOOP();
void ENDREP();
void ENDWHILE();
+ void ENDSWITCH();
void IF(const Src &src);
void IFb(const Src &boolRegister);
void IFp(const Src &predicateRegister);
@@ -102,6 +103,7 @@
void LOOP(const Src &integerRegister);
void REP(const Src &integerRegister);
void WHILE(const Src &temporaryRegister);
+ void SWITCH();
void RET();
void LEAVE();
void TEXLDL(Vector4f &dst, Vector4f &src, const Src&);