mirror of
https://github.com/doitsujin/dxvk.git
synced 2025-03-13 19:29:14 +01:00
[dxbc] Implemented switch-case instructions
This commit is contained in:
parent
006e1b25e6
commit
5332891748
@ -34,7 +34,6 @@ namespace dxvk {
|
||||
const std::string& semanticName,
|
||||
uint32_t semanticIndex) const {
|
||||
for (auto e = this->begin(); e != this->end(); e++) {
|
||||
// TODO case-insensitive compare
|
||||
if (e->semanticIndex == semanticIndex
|
||||
&& compareSemanticNames(semanticName, e->semanticName))
|
||||
return &(*e);
|
||||
|
@ -2280,6 +2280,109 @@ namespace dxvk {
|
||||
}
|
||||
|
||||
|
||||
void DxbcCompiler::emitControlFlowSwitch(const DxbcShaderInstruction& ins) {
|
||||
// Load the selector as a scalar unsigned integer
|
||||
const DxbcRegisterValue selector = emitRegisterLoad(
|
||||
ins.src[0], DxbcRegMask(true, false, false, false));
|
||||
|
||||
// Declare switch block. We cannot insert the switch
|
||||
// instruction itself yet because the number of case
|
||||
// statements and blocks is unknown at this point.
|
||||
DxbcCfgBlock block;
|
||||
block.type = DxbcCfgBlockType::Switch;
|
||||
block.b_switch.insertPtr = m_module.getInsertionPtr();
|
||||
block.b_switch.selectorId = selector.id;
|
||||
block.b_switch.labelBreak = m_module.allocateId();
|
||||
block.b_switch.labelCase = m_module.allocateId();
|
||||
block.b_switch.labelDefault = 0;
|
||||
block.b_switch.labelCases = nullptr;
|
||||
m_controlFlowBlocks.push_back(block);
|
||||
|
||||
// Define the first 'case' label
|
||||
m_module.opLabel(block.b_switch.labelCase);
|
||||
}
|
||||
|
||||
|
||||
void DxbcCompiler::emitControlFlowCase(const DxbcShaderInstruction& ins) {
|
||||
if (m_controlFlowBlocks.size() == 0
|
||||
|| m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch)
|
||||
throw DxvkError("DxbcCompiler: 'Case' without 'Switch' found");
|
||||
|
||||
// The source operand must be a 32-bit immediate.
|
||||
if (ins.src[0].type != DxbcOperandType::Imm32)
|
||||
throw DxvkError("DxbcCompiler: Invalid operand type for 'Case'");
|
||||
|
||||
// Use the last label allocated for 'case'. The block starting
|
||||
// with that label is guaranteed to be empty unless a previous
|
||||
// 'case' block was not properly closed in the DXBC shader.
|
||||
DxbcCfgBlockSwitch* block = &m_controlFlowBlocks.back().b_switch;
|
||||
|
||||
DxbcSwitchLabel label;
|
||||
label.desc.literal = ins.src[0].imm.u32_1;
|
||||
label.desc.labelId = block->labelCase;
|
||||
label.next = block->labelCases;
|
||||
block->labelCases = new DxbcSwitchLabel(label);
|
||||
}
|
||||
|
||||
|
||||
void DxbcCompiler::emitControlFlowDefault(const DxbcShaderInstruction& ins) {
|
||||
if (m_controlFlowBlocks.size() == 0
|
||||
|| m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch)
|
||||
throw DxvkError("DxbcCompiler: 'Default' without 'Switch' found");
|
||||
|
||||
// Set the last label allocated for 'case' as the default label.
|
||||
m_controlFlowBlocks.back().b_switch.labelDefault
|
||||
= m_controlFlowBlocks.back().b_switch.labelCase;
|
||||
}
|
||||
|
||||
|
||||
void DxbcCompiler::emitControlFlowEndSwitch(const DxbcShaderInstruction& ins) {
|
||||
if (m_controlFlowBlocks.size() == 0
|
||||
|| m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch)
|
||||
throw DxvkError("DxbcCompiler: 'EndSwitch' without 'Switch' found");
|
||||
|
||||
// Remove the block from the stack, it's closed
|
||||
DxbcCfgBlock block = m_controlFlowBlocks.back();
|
||||
m_controlFlowBlocks.pop_back();
|
||||
|
||||
// If no 'default' label was specified, use the last allocated
|
||||
// 'case' label. This is guaranteed to be an empty block unless
|
||||
// a previous 'case' block was not closed properly.
|
||||
if (block.b_switch.labelDefault == 0)
|
||||
block.b_switch.labelDefault = block.b_switch.labelCase;
|
||||
|
||||
// Close the current 'case' block
|
||||
m_module.opBranch(block.b_switch.labelBreak);
|
||||
m_module.opLabel (block.b_switch.labelBreak);
|
||||
|
||||
// Insert the 'switch' statement. For that, we need to
|
||||
// gather all the literal-label pairs for the construct.
|
||||
m_module.beginInsertion(block.b_switch.insertPtr);
|
||||
m_module.opSelectionMerge(
|
||||
block.b_switch.labelBreak,
|
||||
spv::SelectionControlMaskNone);
|
||||
|
||||
// We'll restore the original order of the case labels here
|
||||
std::vector<SpirvSwitchCaseLabel> jumpTargets;
|
||||
for (auto i = block.b_switch.labelCases; i != nullptr; i = i->next)
|
||||
jumpTargets.insert(jumpTargets.begin(), i->desc);
|
||||
|
||||
m_module.opSwitch(
|
||||
block.b_switch.selectorId,
|
||||
block.b_switch.labelDefault,
|
||||
jumpTargets.size(),
|
||||
jumpTargets.data());
|
||||
m_module.endInsertion();
|
||||
|
||||
// Destroy the list of case labels
|
||||
// FIXME we're leaking memory if compilation fails.
|
||||
DxbcSwitchLabel* caseLabel = block.b_switch.labelCases;
|
||||
|
||||
while (caseLabel != nullptr)
|
||||
delete std::exchange(caseLabel, caseLabel->next);
|
||||
}
|
||||
|
||||
|
||||
void DxbcCompiler::emitControlFlowLoop(const DxbcShaderInstruction& ins) {
|
||||
// Declare the 'loop' block
|
||||
DxbcCfgBlock block;
|
||||
@ -2325,25 +2428,42 @@ namespace dxvk {
|
||||
void DxbcCompiler::emitControlFlowBreak(const DxbcShaderInstruction& ins) {
|
||||
const bool isBreak = ins.op == DxbcOpcode::Break;
|
||||
|
||||
DxbcCfgBlock* loopBlock = cfgFindLoopBlock();
|
||||
DxbcCfgBlock* cfgBlock = isBreak
|
||||
? cfgFindBlock({ DxbcCfgBlockType::Loop, DxbcCfgBlockType::Switch })
|
||||
: cfgFindBlock({ DxbcCfgBlockType::Loop });
|
||||
|
||||
if (loopBlock == nullptr)
|
||||
throw DxvkError("DxbcCompiler: 'Break' or 'Continue' outside 'Loop' found");
|
||||
if (cfgBlock == nullptr)
|
||||
throw DxvkError("DxbcCompiler: 'Break' or 'Continue' outside 'Loop' or 'Switch' found");
|
||||
|
||||
m_module.opBranch(isBreak
|
||||
? loopBlock->b_loop.labelBreak
|
||||
: loopBlock->b_loop.labelContinue);
|
||||
m_module.opLabel (m_module.allocateId());
|
||||
if (cfgBlock->type == DxbcCfgBlockType::Loop) {
|
||||
m_module.opBranch(isBreak
|
||||
? cfgBlock->b_loop.labelBreak
|
||||
: cfgBlock->b_loop.labelContinue);
|
||||
} else /* if (cfgBlock->type == DxbcCfgBlockType::Switch) */ {
|
||||
m_module.opBranch(cfgBlock->b_switch.labelBreak);
|
||||
}
|
||||
|
||||
// Subsequent instructions assume that there is an open block
|
||||
const uint32_t labelId = m_module.allocateId();
|
||||
m_module.opLabel(labelId);
|
||||
|
||||
// If this is on the same level as a switch-case construct,
|
||||
// rather than being nested inside an 'if' statement, close
|
||||
// the current 'case' block.
|
||||
if (m_controlFlowBlocks.back().type == DxbcCfgBlockType::Switch)
|
||||
cfgBlock->b_switch.labelCase = labelId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DxbcCompiler::emitControlFlowBreakc(const DxbcShaderInstruction& ins) {
|
||||
const bool isBreak = ins.op == DxbcOpcode::Breakc;
|
||||
|
||||
DxbcCfgBlock* loopBlock = cfgFindLoopBlock();
|
||||
DxbcCfgBlock* cfgBlock = isBreak
|
||||
? cfgFindBlock({ DxbcCfgBlockType::Loop, DxbcCfgBlockType::Switch })
|
||||
: cfgFindBlock({ DxbcCfgBlockType::Loop });
|
||||
|
||||
if (loopBlock == nullptr)
|
||||
throw DxvkError("DxbcCompiler: 'Breakc' or 'Continuec' outside 'Loop' found");
|
||||
if (cfgBlock == nullptr)
|
||||
throw DxvkError("DxbcCompiler: 'Breakc' or 'Continuec' outside 'Loop' or 'Switch' found");
|
||||
|
||||
// Perform zero test on the first component of the condition
|
||||
const DxbcRegisterValue condition = emitRegisterLoad(
|
||||
@ -2363,18 +2483,26 @@ namespace dxvk {
|
||||
zeroTest.id, breakBlock, mergeBlock);
|
||||
|
||||
m_module.opLabel(breakBlock);
|
||||
m_module.opBranch(isBreak
|
||||
? loopBlock->b_loop.labelBreak
|
||||
: loopBlock->b_loop.labelContinue);
|
||||
|
||||
if (cfgBlock->type == DxbcCfgBlockType::Loop) {
|
||||
m_module.opBranch(isBreak
|
||||
? cfgBlock->b_loop.labelBreak
|
||||
: cfgBlock->b_loop.labelContinue);
|
||||
} else /* if (cfgBlock->type == DxbcCfgBlockType::Switch) */ {
|
||||
m_module.opBranch(cfgBlock->b_switch.labelBreak);
|
||||
}
|
||||
|
||||
m_module.opLabel(mergeBlock);
|
||||
}
|
||||
|
||||
|
||||
void DxbcCompiler::emitControlFlowRet(const DxbcShaderInstruction& ins) {
|
||||
// TODO implement properly
|
||||
m_module.opReturn();
|
||||
m_module.functionEnd();
|
||||
|
||||
if (m_controlFlowBlocks.size() == 0)
|
||||
m_module.functionEnd();
|
||||
else
|
||||
m_module.opLabel(m_module.allocateId());
|
||||
}
|
||||
|
||||
|
||||
@ -2416,6 +2544,18 @@ namespace dxvk {
|
||||
case DxbcOpcode::EndIf:
|
||||
return this->emitControlFlowEndIf(ins);
|
||||
|
||||
case DxbcOpcode::Switch:
|
||||
return this->emitControlFlowSwitch(ins);
|
||||
|
||||
case DxbcOpcode::Case:
|
||||
return this->emitControlFlowCase(ins);
|
||||
|
||||
case DxbcOpcode::Default:
|
||||
return this->emitControlFlowDefault(ins);
|
||||
|
||||
case DxbcOpcode::EndSwitch:
|
||||
return this->emitControlFlowEndSwitch(ins);
|
||||
|
||||
case DxbcOpcode::Loop:
|
||||
return this->emitControlFlowLoop(ins);
|
||||
|
||||
@ -3714,11 +3854,14 @@ namespace dxvk {
|
||||
}
|
||||
|
||||
|
||||
DxbcCfgBlock* DxbcCompiler::cfgFindLoopBlock() {
|
||||
DxbcCfgBlock* DxbcCompiler::cfgFindBlock(
|
||||
const std::initializer_list<DxbcCfgBlockType>& types) {
|
||||
for (auto cur = m_controlFlowBlocks.rbegin();
|
||||
cur != m_controlFlowBlocks.rend(); cur++) {
|
||||
if (cur->type == DxbcCfgBlockType::Loop)
|
||||
return &(*cur);
|
||||
for (auto type : types) {
|
||||
if (cur->type == type)
|
||||
return &(*cur);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
@ -145,7 +145,7 @@ namespace dxvk {
|
||||
|
||||
|
||||
enum class DxbcCfgBlockType : uint32_t {
|
||||
If, Loop,
|
||||
If, Loop, Switch,
|
||||
};
|
||||
|
||||
|
||||
@ -165,12 +165,29 @@ namespace dxvk {
|
||||
};
|
||||
|
||||
|
||||
struct DxbcSwitchLabel {
|
||||
SpirvSwitchCaseLabel desc;
|
||||
DxbcSwitchLabel* next;
|
||||
};
|
||||
|
||||
|
||||
struct DxbcCfgBlockSwitch {
|
||||
size_t insertPtr;
|
||||
uint32_t selectorId;
|
||||
uint32_t labelBreak;
|
||||
uint32_t labelCase;
|
||||
uint32_t labelDefault;
|
||||
DxbcSwitchLabel* labelCases;
|
||||
};
|
||||
|
||||
|
||||
struct DxbcCfgBlock {
|
||||
DxbcCfgBlockType type;
|
||||
|
||||
union {
|
||||
DxbcCfgBlockIf b_if;
|
||||
DxbcCfgBlockLoop b_loop;
|
||||
DxbcCfgBlockIf b_if;
|
||||
DxbcCfgBlockLoop b_loop;
|
||||
DxbcCfgBlockSwitch b_switch;
|
||||
};
|
||||
};
|
||||
|
||||
@ -433,6 +450,18 @@ namespace dxvk {
|
||||
void emitControlFlowEndIf(
|
||||
const DxbcShaderInstruction& ins);
|
||||
|
||||
void emitControlFlowSwitch(
|
||||
const DxbcShaderInstruction& ins);
|
||||
|
||||
void emitControlFlowCase(
|
||||
const DxbcShaderInstruction& ins);
|
||||
|
||||
void emitControlFlowDefault(
|
||||
const DxbcShaderInstruction& ins);
|
||||
|
||||
void emitControlFlowEndSwitch(
|
||||
const DxbcShaderInstruction& ins);
|
||||
|
||||
void emitControlFlowLoop(
|
||||
const DxbcShaderInstruction& ins);
|
||||
|
||||
@ -649,7 +678,8 @@ namespace dxvk {
|
||||
|
||||
////////////////
|
||||
// Misc methods
|
||||
DxbcCfgBlock* cfgFindLoopBlock();
|
||||
DxbcCfgBlock* cfgFindBlock(
|
||||
const std::initializer_list<DxbcCfgBlockType>& types);
|
||||
|
||||
DxbcBufferInfo getBufferInfo(
|
||||
const DxbcRegister& reg);
|
||||
|
@ -42,7 +42,7 @@ namespace dxvk {
|
||||
/* Cut */
|
||||
{ 0, DxbcInstClass::GeometryEmit },
|
||||
/* Default */
|
||||
{ },
|
||||
{ 0, DxbcInstClass::ControlFlow },
|
||||
/* DerivRtx */
|
||||
{ 2, DxbcInstClass::VectorDeriv, {
|
||||
{ DxbcOperandKind::DstReg, DxbcScalarType::Float32 },
|
||||
|
@ -9,7 +9,8 @@ namespace dxvk {
|
||||
SpirvCodeBuffer::~SpirvCodeBuffer() { }
|
||||
|
||||
|
||||
SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size, const uint32_t* data) {
|
||||
SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size, const uint32_t* data)
|
||||
: m_ptr(size) {
|
||||
m_code.resize(size);
|
||||
std::memcpy(m_code.data(), data, size * sizeof(uint32_t));
|
||||
}
|
||||
@ -28,6 +29,8 @@ namespace dxvk {
|
||||
m_code.resize(buffer.size() / sizeof(uint32_t));
|
||||
std::memcpy(reinterpret_cast<char*>(m_code.data()),
|
||||
buffer.data(), m_code.size() * sizeof(uint32_t));
|
||||
|
||||
m_ptr = m_code.size();
|
||||
}
|
||||
|
||||
|
||||
@ -40,12 +43,14 @@ namespace dxvk {
|
||||
const uint32_t* src = other.m_code.data();
|
||||
|
||||
std::memcpy(dst + size, src, other.size());
|
||||
m_ptr += other.m_code.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SpirvCodeBuffer::putWord(uint32_t word) {
|
||||
m_code.push_back(word);
|
||||
m_code.insert(m_code.begin() + m_ptr, word);
|
||||
m_ptr += 1;
|
||||
}
|
||||
|
||||
|
||||
|
@ -143,9 +143,46 @@ namespace dxvk {
|
||||
*/
|
||||
void store(std::ostream&& stream) const;
|
||||
|
||||
/**
|
||||
* \brief Retrieves current insertion pointer
|
||||
*
|
||||
* Sometimes it may be necessay to insert code into the
|
||||
* middle of the stream rather than appending it. This
|
||||
* retrieves the current function pointer. Note that the
|
||||
* pointer will become invalid if any code is inserted
|
||||
* before the current pointer location.
|
||||
* \returns Current instruction pointr
|
||||
*/
|
||||
size_t getInsertionPtr() const {
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Sets insertion pointer to a specific value
|
||||
*
|
||||
* Sets the insertion pointer to a value that was
|
||||
* previously retrieved by \ref getInsertionPtr.
|
||||
* \returns Current instruction pointr
|
||||
*/
|
||||
void beginInsertion(size_t ptr) {
|
||||
m_ptr = ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Sets insertion pointer to the end
|
||||
*
|
||||
* After this call, new instructions will be
|
||||
* appended to the stream. In other words,
|
||||
* this will restore default behaviour.
|
||||
*/
|
||||
void endInsertion() {
|
||||
m_ptr = m_code.size();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::vector<uint32_t> m_code;
|
||||
size_t m_ptr = 0;
|
||||
|
||||
};
|
||||
|
||||
|
@ -2099,6 +2099,22 @@ namespace dxvk {
|
||||
m_code.putWord(falseLabel);
|
||||
}
|
||||
|
||||
|
||||
void SpirvModule::opSwitch(
|
||||
uint32_t selector,
|
||||
uint32_t jumpDefault,
|
||||
uint32_t caseCount,
|
||||
const SpirvSwitchCaseLabel* caseLabels) {
|
||||
m_code.putIns (spv::OpSwitch, 3 + 2 * caseCount);
|
||||
m_code.putWord(selector);
|
||||
m_code.putWord(jumpDefault);
|
||||
|
||||
for (uint32_t i = 0; i < caseCount; i++) {
|
||||
m_code.putWord(caseLabels[i].literal);
|
||||
m_code.putWord(caseLabels[i].labelId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SpirvModule::opReturn() {
|
||||
m_code.putIns (spv::OpReturn, 1);
|
||||
|
@ -4,6 +4,11 @@
|
||||
|
||||
namespace dxvk {
|
||||
|
||||
struct SpirvSwitchCaseLabel {
|
||||
uint32_t literal = 0;
|
||||
uint32_t labelId = 0;
|
||||
};
|
||||
|
||||
struct SpirvImageOperands {
|
||||
uint32_t flags = 0;
|
||||
uint32_t sLodBias = 0;
|
||||
@ -34,6 +39,18 @@ namespace dxvk {
|
||||
|
||||
SpirvCodeBuffer compile() const;
|
||||
|
||||
size_t getInsertionPtr() {
|
||||
return m_code.getInsertionPtr();
|
||||
}
|
||||
|
||||
void beginInsertion(size_t ptr) {
|
||||
m_code.beginInsertion(ptr);
|
||||
}
|
||||
|
||||
void endInsertion() {
|
||||
m_code.endInsertion();
|
||||
}
|
||||
|
||||
uint32_t allocateId();
|
||||
|
||||
void enableCapability(
|
||||
@ -720,6 +737,13 @@ namespace dxvk {
|
||||
uint32_t trueLabel,
|
||||
uint32_t falseLabel);
|
||||
|
||||
void opSwitch(
|
||||
uint32_t selector,
|
||||
uint32_t jumpDefault,
|
||||
uint32_t caseCount,
|
||||
const SpirvSwitchCaseLabel* caseLabels);
|
||||
|
||||
|
||||
void opReturn();
|
||||
|
||||
void opKill();
|
||||
|
Loading…
x
Reference in New Issue
Block a user