1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-01-18 02:52:10 +01:00

[dxbc] Shader decoder and compiler overhaul (2/2)

Removed the old decoder and the old shader compiler
and added documentation to the new structures.
This commit is contained in:
Philip Rebohle 2017-12-18 00:46:44 +01:00
parent 47347e38da
commit 858913ec0c
12 changed files with 1653 additions and 4607 deletions

View File

@ -2,7 +2,6 @@
#include "dxbc_common.h"
#include "dxbc_decoder.h"
#include "dxbc_decoder_2.h"
#include "dxbc_reader.h"
namespace dxvk {
@ -30,14 +29,6 @@ namespace dxvk {
m_code.data() + m_code.size());
}
DxbcDecoder begin() const {
return DxbcDecoder(m_code.data(), m_code.size());
}
DxbcDecoder end() const {
return DxbcDecoder();
}
private:
DxbcProgramVersion m_version;

View File

@ -1,6 +1,4 @@
#include "dxbc_compiler.h"
#include "dxbc_names.h"
#include "dxbc_util.h"
namespace dxvk {
@ -9,7 +7,6 @@ namespace dxvk {
constexpr uint32_t PerVertex_CullDist = 2;
constexpr uint32_t PerVertex_ClipDist = 3;
DxbcCompiler::DxbcCompiler(
const DxbcProgramVersion& version,
const Rc<DxbcIsgn>& isgn,
@ -26,19 +23,21 @@ namespace dxvk {
spv::AddressingModelLogical,
spv::MemoryModelGLSL450);
// Make sure our interface registers don't
// contain any valid IDs at the moment.
for (size_t i = 0; i < DxbcMaxInterfaceRegs; i++) {
m_vRegs[i] = 0;
m_oRegs[i] = 0;
// Make sure our interface registers are clear
for (uint32_t i = 0; i < DxbcMaxInterfaceRegs; i++) {
m_ps.oTypes.at(i).ctype = DxbcScalarType::Float32;
m_ps.oTypes.at(i).ccount = 0;
m_vRegs.at(i) = 0;
m_oRegs.at(i) = 0;
}
// Initialize the shader module with capabilities
// etc. Each shader type has its own peculiarities.
switch (m_version.type()) {
case DxbcProgramType::PixelShader: this->beginPixelShader (osgn); break;
case DxbcProgramType::VertexShader: this->beginVertexShader(isgn); break;
default: Logger::err("dxbc: Unsupported shader type");
case DxbcProgramType::VertexShader: this->emitVsInit(); break;
case DxbcProgramType::PixelShader: this->emitPsInit(); break;
default: throw DxvkError("DxbcCompiler: Unsupported program type");
}
}
@ -48,23 +47,86 @@ namespace dxvk {
}
DxbcError DxbcCompiler::processInstruction(const DxbcInstruction& ins) {
DxbcInst parsedInst;
DxbcError parseError = this->parseInstruction(ins, parsedInst);
if (parseError != DxbcError::sOk)
return parseError;
switch (parsedInst.format.instructionClass) {
case DxbcInstClass::Declaration: return this->handleDeclaration (parsedInst);
case DxbcInstClass::ControlFlow: return this->handleControlFlow (parsedInst);
case DxbcInstClass::TextureSample: return this->handleTextureSample(parsedInst);
case DxbcInstClass::VectorAlu: return this->handleVectorAlu (parsedInst);
case DxbcInstClass::VectorCmov: return this->handleVectorCmov (parsedInst);
case DxbcInstClass::VectorCmp: return this->handleVectorCmp (parsedInst);
case DxbcInstClass::VectorDot: return this->handleVectorDot (parsedInst);
case DxbcInstClass::VectorSinCos: return this->handleVectorSinCos (parsedInst);
default: return DxbcError::eUnhandledOpcode;
void DxbcCompiler::processInstruction(const DxbcShaderInstruction& ins) {
switch (ins.op) {
case DxbcOpcode::DclGlobalFlags:
return this->emitDclGlobalFlags(ins);
case DxbcOpcode::DclTemps:
return this->emitDclTemps(ins);
case DxbcOpcode::DclInput:
case DxbcOpcode::DclInputSgv:
case DxbcOpcode::DclInputSiv:
case DxbcOpcode::DclInputPs:
case DxbcOpcode::DclInputPsSgv:
case DxbcOpcode::DclInputPsSiv:
case DxbcOpcode::DclOutput:
case DxbcOpcode::DclOutputSgv:
case DxbcOpcode::DclOutputSiv:
return this->emitDclInterfaceReg(ins);
case DxbcOpcode::DclConstantBuffer:
return this->emitDclConstantBuffer(ins);
case DxbcOpcode::DclSampler:
return this->emitDclSampler(ins);
case DxbcOpcode::DclResource:
return this->emitDclResource(ins);
case DxbcOpcode::Add:
case DxbcOpcode::Div:
case DxbcOpcode::Exp:
case DxbcOpcode::Log:
case DxbcOpcode::Mad:
case DxbcOpcode::Max:
case DxbcOpcode::Min:
case DxbcOpcode::Mul:
case DxbcOpcode::Mov:
case DxbcOpcode::Rsq:
case DxbcOpcode::Sqrt:
case DxbcOpcode::IAdd:
case DxbcOpcode::IMad:
case DxbcOpcode::IMax:
case DxbcOpcode::IMin:
case DxbcOpcode::INeg:
return this->emitVectorAlu(ins);
case DxbcOpcode::Movc:
return this->emitVectorCmov(ins);
case DxbcOpcode::Eq:
case DxbcOpcode::Ge:
case DxbcOpcode::Lt:
case DxbcOpcode::Ne:
case DxbcOpcode::IEq:
case DxbcOpcode::IGe:
case DxbcOpcode::ILt:
case DxbcOpcode::INe:
return this->emitVectorCmp(ins);
case DxbcOpcode::Dp2:
case DxbcOpcode::Dp3:
case DxbcOpcode::Dp4:
return this->emitVectorDot(ins);
case DxbcOpcode::IMul:
return this->emitVectorImul(ins);
case DxbcOpcode::SinCos:
return this->emitVectorSinCos(ins);
case DxbcOpcode::Sample:
return this->emitSample(ins);
case DxbcOpcode::Ret:
return this->emitRet(ins);
default:
Logger::warn(
str::format("DxbcCompiler: Unhandled opcode: ",
ins.op));
}
}
@ -79,12 +141,13 @@ namespace dxvk {
spv::FunctionControlMaskNone);
m_module.opLabel(m_module.allocateId());
// Depending on the shader type, this will prepare input registers,
// call various shader functions and write back the output registers.
// Depending on the shader type, this will prepare
// input registers, call various shader functions
// and write back the output registers.
switch (m_version.type()) {
case DxbcProgramType::PixelShader: this->endPixelShader (); break;
case DxbcProgramType::VertexShader: this->endVertexShader(); break;
default: Logger::err("dxbc: Unsupported shader type");
case DxbcProgramType::VertexShader: this->emitVsFinalize(); break;
case DxbcProgramType::PixelShader: this->emitPsFinalize(); break;
default: throw DxvkError("DxbcCompiler: Unsupported program type");
}
// End main function
@ -108,195 +171,226 @@ namespace dxvk {
}
DxbcError DxbcCompiler::handleDeclaration(const DxbcInst& ins) {
switch (ins.opcode) {
case DxbcOpcode::DclGlobalFlags:
return this->declareGlobalFlags(ins);
case DxbcOpcode::DclTemps:
return this->declareTemps(ins);
case DxbcOpcode::DclInput:
case DxbcOpcode::DclInputSiv:
case DxbcOpcode::DclInputSgv:
case DxbcOpcode::DclInputPs:
case DxbcOpcode::DclInputPsSiv:
case DxbcOpcode::DclInputPsSgv:
case DxbcOpcode::DclOutput:
case DxbcOpcode::DclOutputSiv:
case DxbcOpcode::DclOutputSgv:
return this->declareInterfaceVar(ins);
case DxbcOpcode::DclConstantBuffer:
return this->declareConstantBuffer(ins);
case DxbcOpcode::DclSampler:
return this->declareSampler(ins);
case DxbcOpcode::DclResource:
return this->declareResource(ins);
default:
return DxbcError::eUnhandledOpcode;
}
void DxbcCompiler::emitDclGlobalFlags(const DxbcShaderInstruction& ins) {
// TODO implement properly
}
DxbcError DxbcCompiler::declareGlobalFlags(const DxbcInst& ins) {
// TODO add support for double-precision floats
// TODO add support for early depth-stencil
return DxbcError::sOk;
}
DxbcError DxbcCompiler::declareTemps(const DxbcInst& ins) {
if (ins.operands[0].type != DxbcOperandType::Imm32) {
Logger::err("dxbc: Number of temps not a contant");
return DxbcError::eInvalidOperand;
}
void DxbcCompiler::emitDclTemps(const DxbcShaderInstruction& ins) {
// dcl_temps has one operand:
// (imm0) Number of temp registers
const uint32_t oldCount = m_rRegs.size();
const uint32_t newCount = ins.imm[0].u32;
// Some shader program types use multiple sets of temps,
// so we'll just check if we need to create new ones.
const uint32_t newSize = ins.operands[0].immediates[0];
const uint32_t oldSize = m_rRegs.size();
if (newSize > oldSize) {
m_rRegs.resize(newSize);
if (newCount > oldCount) {
m_rRegs.resize(newCount);
// r# registers are always 4x32-bit float vectors
const uint32_t regTypeId = this->defineVectorType(
DxbcScalarType::Float32, 4);
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassPrivate;
const uint32_t ptrTypeId = m_module.defPointerType(
regTypeId, spv::StorageClassPrivate);
for (uint32_t i = oldSize; i < newSize; i++) {
m_rRegs.at(i) = m_module.newVar(
ptrTypeId, spv::StorageClassPrivate);
m_module.setDebugName(m_rRegs.at(i),
str::format("r", i).c_str());
for (uint32_t i = oldCount; i < newCount; i++) {
const uint32_t varId = this->emitNewVariable(info);
m_module.setDebugName(varId, str::format("r", i).c_str());
m_rRegs.at(i) = varId;
}
}
return DxbcError::sOk;
}
DxbcError DxbcCompiler::declareInterfaceVar(const DxbcInst& ins) {
const DxbcInstOp& op = ins.operands[0];
void DxbcCompiler::emitDclInterfaceReg(const DxbcShaderInstruction& ins) {
// dcl_input and dcl_output instructions
// have the following operands:
// (dst0) The register to declare
// (imm0) The system value (optional)
uint32_t regDim = 0;
uint32_t regIdx = 0;
// In the vertex and fragment shader stage, the
// operand indices will have the following format:
// (0) Register index
//
// In other stages, the input and output registers
// are declared as arrays of a fixed size:
// (0) Array size
// may be declared as arrays of a fixed size:
// (0) Array length
// (1) Register index
uint32_t regId = 0;
uint32_t regDim = 0;
if (op.indexDim == 1) {
if (op.index[0].type != DxbcIndexType::Immediate)
return DxbcError::eInvalidOperandIndex;
regId = op.index[0].immediate;
} else if (op.indexDim == 2) {
if (op.index[0].type != DxbcIndexType::Immediate
|| op.index[1].type != DxbcIndexType::Immediate)
return DxbcError::eInvalidOperandIndex;
regDim = op.index[0].immediate;
regId = op.index[1].immediate;
if (ins.dst[0].idxDim == 2) {
regDim = ins.dst[0].idx[0].offset;
regIdx = ins.dst[0].idx[1].offset;
} else if (ins.dst[0].idxDim == 1) {
regIdx = ins.dst[0].idx[0].offset;
} else {
Logger::err("dxbc: Invalid index dimension for v#/o# declaration");
return DxbcError::eInvalidOperandIndex;
Logger::err(str::format(
"DxbcCompiler: ", ins.op,
": Invalid index dimension"));
return;
}
// This declaration may map an output register to a system
// value. If that is the case, the system value type will
// be stored in the second operand.
const bool hasSv =
ins.opcode == DxbcOpcode::DclInputSgv
|| ins.opcode == DxbcOpcode::DclInputSiv
|| ins.opcode == DxbcOpcode::DclInputPsSgv
|| ins.opcode == DxbcOpcode::DclInputPsSiv
|| ins.opcode == DxbcOpcode::DclOutputSgv
|| ins.opcode == DxbcOpcode::DclOutputSiv;
ins.op == DxbcOpcode::DclInputSgv
|| ins.op == DxbcOpcode::DclInputSiv
|| ins.op == DxbcOpcode::DclInputPsSgv
|| ins.op == DxbcOpcode::DclInputPsSiv
|| ins.op == DxbcOpcode::DclOutputSgv
|| ins.op == DxbcOpcode::DclOutputSiv;
DxbcSystemValue sv = DxbcSystemValue::None;
if (hasSv) {
if (ins.operands[1].type != DxbcOperandType::Imm32) {
Logger::err("dxbc: Invalid system value in v#/o# declaration");
return DxbcError::eInstructionFormat;
}
sv = static_cast<DxbcSystemValue>(
ins.operands[1].immediates[0]);
}
if (hasSv)
sv = static_cast<DxbcSystemValue>(ins.imm[0].u32);
// In the pixel shader, inputs are declared with an
// interpolation mode that is part of the op token.
// const bool hasInterpolationMode =
// ins.opcode == DxbcOpcode::DclInputPs
// || ins.opcode == DxbcOpcode::DclInputPsSiv;
const bool hasInterpolationMode =
ins.op == DxbcOpcode::DclInputPs
|| ins.op == DxbcOpcode::DclInputPsSiv;
DxbcInterpolationMode im = DxbcInterpolationMode::Undefined;
// TODO implement this
// if (hasInterpolationMode) {
// im = static_cast<DxbcInterpolationMode>(
// bit::extract(ins.token().control(), 0, 3));
// }
if (hasInterpolationMode)
im = ins.controls.interpolation;
// Declare the actual variable
switch (op.type) {
case DxbcOperandType::Input:
return this->declareInputVar(
regId, regDim, op.mask, sv, im);
case DxbcOperandType::Output:
return this->declareOutputVar(
regId, regDim, op.mask, sv, im);
// Declare the actual input/output variable
switch (ins.op) {
case DxbcOpcode::DclInput:
case DxbcOpcode::DclInputSgv:
case DxbcOpcode::DclInputSiv:
case DxbcOpcode::DclInputPs:
case DxbcOpcode::DclInputPsSgv:
case DxbcOpcode::DclInputPsSiv:
this->emitDclInput(regIdx, regDim, ins.dst[0].mask, sv, im);
break;
case DxbcOpcode::DclOutput:
case DxbcOpcode::DclOutputSgv:
case DxbcOpcode::DclOutputSiv:
this->emitDclOutput(regIdx, regDim, ins.dst[0].mask, sv, im);
break;
default:
// We shouldn't ever be here
return DxbcError::eInternal;
Logger::err(str::format(
"DxbcCompiler: Unexpected opcode: ",
ins.op));
}
}
DxbcError DxbcCompiler::declareConstantBuffer(const DxbcInst& ins) {
const DxbcInstOp& op = ins.operands[0];
// This instruction has one operand with two indices:
// (1) Constant buffer register ID (cb#)
// (2) Number of constants in the buffer
if (op.indexDim != 2) {
Logger::err("dxbc: Constant buffer declaration requires two indices");
return DxbcError::eInvalidOperandIndex;
void DxbcCompiler::emitDclInput(
uint32_t regIdx,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im) {
if (regDim != 0) {
Logger::err("DxbcCompiler: Input arrays not yet supported");
return;
}
const uint32_t bufferId = op.index[0].immediate;
const uint32_t elementCount = op.index[1].immediate;
// Avoid declaring the same variable multiple times.
// This may happen when multiple system values are
// mapped to different parts of the same register.
if (m_vRegs.at(regIdx) == 0) {
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassInput;
const uint32_t varId = this->emitNewVariable(info);
m_module.decorateLocation(varId, regIdx);
m_module.setDebugName(varId, str::format("v", regIdx).c_str());
m_entryPointInterfaces.push_back(varId);
m_vRegs.at(regIdx) = varId;
// Interpolation mode, used in pixel shaders
if (im == DxbcInterpolationMode::Constant)
m_module.decorate(varId, spv::DecorationFlat);
if (im == DxbcInterpolationMode::LinearCentroid
|| im == DxbcInterpolationMode::LinearNoPerspectiveCentroid)
m_module.decorate(varId, spv::DecorationCentroid);
if (im == DxbcInterpolationMode::LinearNoPerspective
|| im == DxbcInterpolationMode::LinearNoPerspectiveCentroid
|| im == DxbcInterpolationMode::LinearNoPerspectiveSample)
m_module.decorate(varId, spv::DecorationNoPerspective);
if (im == DxbcInterpolationMode::LinearSample
|| im == DxbcInterpolationMode::LinearNoPerspectiveSample)
m_module.decorate(varId, spv::DecorationSample);
}
// Add a new system value mapping if needed
// TODO declare SV if necessary
if (sv != DxbcSystemValue::None)
m_vMappings.push_back({ regIdx, regMask, sv });
}
void DxbcCompiler::emitDclOutput(
uint32_t regIdx,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im) {
if (regDim != 0) {
Logger::err("DxbcCompiler: Output arrays not yet supported");
return;
}
// Avoid declaring the same variable multiple times.
// This may happen when multiple system values are
// mapped to different parts of the same register.
if (m_oRegs.at(regIdx) == 0) {
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassOutput;
const uint32_t varId = this->emitNewVariable(info);
m_module.decorateLocation(varId, regIdx);
m_module.setDebugName(varId, str::format("o", regIdx).c_str());
m_entryPointInterfaces.push_back(varId);
m_oRegs.at(regIdx) = varId;
}
// Add a new system value mapping if needed
// TODO declare SV if necessary
if (sv != DxbcSystemValue::None)
m_oMappings.push_back({ regIdx, regMask, sv });
}
void DxbcCompiler::emitDclConstantBuffer(const DxbcShaderInstruction& ins) {
// dcl_constant_buffer has one operand with two indices:
// (0) Constant buffer register ID (cb#)
// (1) Number of constants in the buffer
const uint32_t bufferId = ins.dst[0].idx[0].offset;
const uint32_t elementCount = ins.dst[0].idx[1].offset;
// Uniform buffer data is stored as a fixed-size array
// of 4x32-bit vectors. SPIR-V requires explicit strides.
uint32_t arrayType = m_module.defArrayTypeUnique(
this->defineVectorType(DxbcScalarType::Float32, 4),
const uint32_t arrayType = m_module.defArrayTypeUnique(
getVectorTypeId({ DxbcScalarType::Float32, 4 }),
m_module.constu32(elementCount));
m_module.decorateArrayStride(arrayType, 16);
// SPIR-V requires us to put that array into a
// struct and decorate that struct as a block.
uint32_t structType = m_module.defStructTypeUnique(1, &arrayType);
const uint32_t structType = m_module.defStructTypeUnique(1, &arrayType);
m_module.memberDecorateOffset(structType, 0, 0);
m_module.decorateBlock(structType);
// Variable that we'll use to access the buffer
uint32_t varId = m_module.newVar(
const uint32_t varId = m_module.newVar(
m_module.defPointerType(structType, spv::StorageClassUniform),
spv::StorageClassUniform);
@ -308,7 +402,7 @@ namespace dxvk {
// Compute the DXVK binding slot index for the buffer.
// D3D11 needs to bind the actual buffers to this slot.
uint32_t bindingId = computeResourceSlotId(
const uint32_t bindingId = computeResourceSlotId(
m_version.type(), DxbcBindingType::ConstantBuffer,
bufferId);
@ -320,31 +414,24 @@ namespace dxvk {
resource.slot = bindingId;
resource.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
m_resourceSlots.push_back(resource);
return DxbcError::sOk;
}
DxbcError DxbcCompiler::declareSampler(const DxbcInst& ins) {
void DxbcCompiler::emitDclSampler(const DxbcShaderInstruction& ins) {
// dclSampler takes one operand:
// (1) The sampler register ID
// (dst0) The sampler register to declare
// TODO implement sampler mode (default / comparison / mono)
if (ins.operands[0].indexDim != 1) {
Logger::err("dxbc: Invalid sampler index dimension");
return DxbcError::eInvalidOperandIndex;
}
uint32_t samplerId = ins.operands[0].index[0].immediate;
const uint32_t samplerId = ins.dst[0].idx[0].offset;
// The sampler type is opaque, but we still have to
// define a pointer and a variable in oder to use it
uint32_t samplerType = m_module.defSamplerType();
uint32_t samplerPtrType = m_module.defPointerType(
const uint32_t samplerType = m_module.defSamplerType();
const uint32_t samplerPtrType = m_module.defPointerType(
samplerType, spv::StorageClassUniformConstant);
// Define the sampler variable
uint32_t varId = m_module.newVar(samplerPtrType,
const uint32_t varId = m_module.newVar(samplerPtrType,
spv::StorageClassUniformConstant);
m_module.setDebugName(varId,
str::format("s", samplerId).c_str());
@ -352,7 +439,7 @@ namespace dxvk {
m_samplers.at(samplerId).typeId = samplerType;
// Compute binding slot index for the sampler
uint32_t bindingId = computeResourceSlotId(
const uint32_t bindingId = computeResourceSlotId(
m_version.type(), DxbcBindingType::ImageSampler, samplerId);
m_module.decorateDescriptorSet(varId, 0);
@ -363,40 +450,32 @@ namespace dxvk {
resource.slot = bindingId;
resource.type = VK_DESCRIPTOR_TYPE_SAMPLER;
m_resourceSlots.push_back(resource);
return DxbcError::sOk;
}
DxbcError DxbcCompiler::declareResource(const DxbcInst& ins) {
void DxbcCompiler::emitDclResource(const DxbcShaderInstruction& ins) {
// dclResource takes two operands:
// (1) The resource register ID
// (2) The resource return type
const DxbcInstOp op = ins.operands[0];
if (op.indexDim != 1) {
Logger::err("dxbc: dclResource: Invalid index dimension");
return DxbcError::eInvalidOperandIndex;
}
const uint32_t registerId = op.index[0].immediate;
// (dst0) The resource register ID
// (imm0) The resource return type
const uint32_t registerId = ins.dst[0].idx[0].offset;
// Defines the type of the resource (texture2D, ...)
const DxbcResourceDim resourceType = ins.control.resourceDim();
const DxbcResourceDim resourceType = ins.controls.resourceDim;
// Defines the type of a read operation. DXBC has the ability
// to define four different types whereas SPIR-V only allows
// one, but in practice this should not be much of a problem.
auto xType = static_cast<DxbcResourceReturnType>(
bit::extract(ins.operands[1].immediates[0], 0, 3));
bit::extract(ins.imm[0].u32, 0, 3));
auto yType = static_cast<DxbcResourceReturnType>(
bit::extract(ins.operands[1].immediates[0], 4, 7));
bit::extract(ins.imm[0].u32, 4, 7));
auto zType = static_cast<DxbcResourceReturnType>(
bit::extract(ins.operands[1].immediates[0], 8, 11));
bit::extract(ins.imm[0].u32, 8, 11));
auto wType = static_cast<DxbcResourceReturnType>(
bit::extract(ins.operands[1].immediates[0], 12, 15));
bit::extract(ins.imm[0].u32, 12, 15));
if ((xType != yType) || (xType != zType) || (xType != wType))
Logger::warn("DXBC: dclResource: Ignoring resource return types");
Logger::warn("DxbcCompiler: dcl_resource: Ignoring resource return types");
// Declare the actual sampled type
uint32_t sampledTypeId = 0;
@ -405,9 +484,7 @@ namespace dxvk {
case DxbcResourceReturnType::Float: sampledTypeId = m_module.defFloatType(32); break;
case DxbcResourceReturnType::Sint: sampledTypeId = m_module.defIntType (32, 1); break;
case DxbcResourceReturnType::Uint: sampledTypeId = m_module.defIntType (32, 0); break;
default:
Logger::err(str::format("dxbc: Invalid sampled type: ", xType));
return DxbcError::eInvalidOperand;
default: throw DxvkError(str::format("DxbcCompiler: Invalid sampled type: ", xType));
}
// Declare the resource type
@ -457,14 +534,13 @@ namespace dxvk {
break;
default:
Logger::err(str::format("dxbc: Unsupported resource type: ", resourceType));
return DxbcError::eUnsupported;
throw DxvkError(str::format("DxbcCompiler: Unsupported resource type: ", resourceType));
}
uint32_t resourcePtrType = m_module.defPointerType(
const uint32_t resourcePtrType = m_module.defPointerType(
textureTypeId, spv::StorageClassUniformConstant);
uint32_t varId = m_module.newVar(resourcePtrType,
const uint32_t varId = m_module.newVar(resourcePtrType,
spv::StorageClassUniformConstant);
m_module.setDebugName(varId,
@ -476,8 +552,8 @@ namespace dxvk {
// Compute the DXVK binding slot index for the resource.
// D3D11 needs to bind the actual resource to this slot.
uint32_t bindingId = computeResourceSlotId(m_version.type(),
DxbcBindingType::ShaderResource, registerId);
const uint32_t bindingId = computeResourceSlotId(
m_version.type(), DxbcBindingType::ShaderResource, registerId);
m_module.decorateDescriptorSet(varId, 0);
m_module.decorateBinding(varId, bindingId);
@ -487,257 +563,127 @@ namespace dxvk {
resource.slot = bindingId;
resource.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
m_resourceSlots.push_back(resource);
return DxbcError::sOk;
}
DxbcError DxbcCompiler::declareInputVar(
uint32_t regId,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im) {
if (regDim != 0) {
Logger::err("dxbc: Input arrays not yet supported");
return DxbcError::eUnsupported;
}
void DxbcCompiler::emitVectorAlu(const DxbcShaderInstruction& ins) {
std::array<DxbcRegisterValue, DxbcMaxOperandCount> src;
// A variable may be declared multiple
// times when system values are involved
if (m_vRegs.at(regId) == 0) {
uint32_t regTypeId = m_module.defVectorType(
m_module.defFloatType(32), 4);
uint32_t ptrTypeId = m_module.defPointerType(
regTypeId, spv::StorageClassInput);
uint32_t varId = m_module.newVar(
ptrTypeId, spv::StorageClassInput);
m_module.decorateLocation(varId, regId);
m_module.setDebugName(varId, str::format("v", regId).c_str());
m_entryPointInterfaces.push_back(varId);
m_vRegs.at(regId) = varId;
}
for (uint32_t i = 0; i < ins.srcCount; i++)
src.at(i) = emitRegisterLoad(ins.src[i], ins.dst[0].mask);
return DxbcError::sOk;
}
DxbcError DxbcCompiler::declareOutputVar(
uint32_t regId,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im) {
if (regDim != 0) {
Logger::err("dxbc: Output arrays not yet supported");
return DxbcError::eUnsupported;
}
DxbcRegisterValue dst;
dst.type.ctype = ins.dst[0].dataType;
dst.type.ccount = ins.dst[0].mask.setCount();
// Fragment shader outputs were defined earlier, based
// on their signature. We cannot add new output registers.
if (m_version.type() == DxbcProgramType::PixelShader)
return DxbcError::sOk;
const uint32_t typeId = getVectorTypeId(dst.type);
// Output variables may also be defined multiple times since
// multiple vector components may be mapped to system values.
if (m_oRegs.at(regId) == 0) {
uint32_t regTypeId = m_module.defVectorType(
m_module.defFloatType(32), 4);
uint32_t ptrTypeId = m_module.defPointerType(
regTypeId, spv::StorageClassOutput);
uint32_t varId = m_module.newVar(
ptrTypeId, spv::StorageClassOutput);
m_module.decorateLocation(varId, regId);
m_module.setDebugName(varId, str::format("o", regId).c_str());
m_entryPointInterfaces.push_back(varId);
m_oRegs.at(regId) = varId;
}
// Add a new system value mapping if needed
// TODO declare SV if necessary
if (sv != DxbcSystemValue::None)
m_oSvs.push_back({ regId, regMask, sv });
return DxbcError::sOk;
}
DxbcError DxbcCompiler::handleControlFlow(const DxbcInst& ins) {
switch (ins.opcode) {
case DxbcOpcode::Ret:
m_module.opReturn();
m_module.functionEnd();
return DxbcError::sOk;
default:
return DxbcError::eUnhandledOpcode;
}
}
DxbcError DxbcCompiler::handleTextureSample(const DxbcInst& ins) {
// TODO support address offset
// TODO support more sample ops
// sample has four operands:
// (1) The destination register
// (2) Texture coordinates
// (3) The texture itself
// (4) The sampler object
const DxbcInstOp destOp = ins.operands[0];
const DxbcInstOp coordOp = ins.operands[1];
const DxbcInstOp textureOp = ins.operands[2];
const DxbcInstOp samplerOp = ins.operands[3];
if (textureOp.indexDim != 1 || samplerOp.indexDim != 1) {
Logger::err("dxbc: Texture and Sampler registers require one index");
return DxbcError::eInvalidOperandIndex;
}
// Texture and sampler register IDs
const uint32_t textureId = textureOp.index[0].immediate;
const uint32_t samplerId = samplerOp.index[0].immediate;
// Load the texture coordinates. SPIR-V allows these
// to be float4 even if not all components are used.
const DxbcValue coord = this->loadOp(coordOp,
DxbcRegMask(true, true, true, true),
DxbcScalarType::Float32);
// Combine the texture and the sampler into a sampled image
uint32_t sampledImageType = m_module.defSampledImageType(
m_textures.at(textureId).textureTypeId);
uint32_t sampledImageId = m_module.opSampledImage(
sampledImageType,
m_module.opLoad(
m_textures.at(textureId).textureTypeId,
m_textures.at(textureId).varId),
m_module.opLoad(
m_samplers.at(samplerId).typeId,
m_samplers.at(samplerId).varId));
// Sampling an image in SPIR-V always returns a four-component
// vector, so we need to declare the corresponding type here
// TODO infer sampled type properly
DxbcValue result;
result.componentType = DxbcScalarType::Float32;
result.componentCount = 4;
result.valueId = m_module.opImageSampleImplicitLod(
this->defineVectorType(result.componentType, result.componentCount),
sampledImageId, coord.valueId);
// Swizzle components using the texture swizzle
// and the destination operand's write mask
result = this->swizzleReg(result, textureOp.swizzle, destOp.mask);
this->storeOp(destOp, result);
return DxbcError::sOk;
}
DxbcError DxbcCompiler::handleVectorAlu(const DxbcInst& ins) {
// Load input operands. Operands that are floating
// point types will be affected by modifiers.
DxbcValue arguments[DxbcMaxOperandCount - 1];
for (uint32_t i = 1; i < ins.format.operandCount; i++) {
arguments[i - 1] = this->loadOp(
ins.operands[i],
ins.operands[0].mask,
ins.format.operands[i].type);
}
// Result that we will write to the destination operand
DxbcValue result;
result.componentType = arguments[0].componentType;
result.componentCount = arguments[0].componentCount;
uint32_t resultTypeId = this->defineVectorType(
result.componentType, result.componentCount);
switch (ins.opcode) {
switch (ins.op) {
case DxbcOpcode::Add:
result.valueId = m_module.opFAdd(
resultTypeId,
arguments[0].valueId,
arguments[1].valueId);
dst.id = m_module.opFAdd(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Div:
dst.id = m_module.opFDiv(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Div:
result.valueId = m_module.opFDiv(
resultTypeId,
arguments[0].valueId,
arguments[1].valueId);
case DxbcOpcode::Exp:
dst.id = m_module.opExp2(
typeId, src.at(0).id);
break;
case DxbcOpcode::Log:
dst.id = m_module.opLog2(
typeId, src.at(0).id);
break;
case DxbcOpcode::Mad:
result.valueId = m_module.opFFma(
resultTypeId,
arguments[0].valueId,
arguments[1].valueId,
arguments[2].valueId);
dst.id = m_module.opFFma(typeId,
src.at(0).id, src.at(1).id, src.at(2).id);
break;
case DxbcOpcode::Max:
result.valueId = m_module.opFMax(
resultTypeId,
arguments[0].valueId,
arguments[1].valueId);
dst.id = m_module.opFMax(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Min:
result.valueId = m_module.opFMin(
resultTypeId,
arguments[0].valueId,
arguments[1].valueId);
break;
case DxbcOpcode::Mov:
result.valueId = arguments[0].valueId;
dst.id = m_module.opFMin(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Mul:
result.valueId = m_module.opFMul(
resultTypeId,
arguments[0].valueId,
arguments[1].valueId);
dst.id = m_module.opFMul(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Mov:
dst.id = src.at(0).id;
break;
case DxbcOpcode::Sqrt:
dst.id = m_module.opSqrt(
typeId, src.at(0).id);
break;
case DxbcOpcode::Rsq:
result.valueId = m_module.opInverseSqrt(
resultTypeId, arguments[0].valueId);
dst.id = m_module.opInverseSqrt(
typeId, src.at(0).id);
break;
case DxbcOpcode::IAdd:
dst.id = m_module.opIAdd(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::IMad:
dst.id = m_module.opIAdd(typeId,
m_module.opIMul(typeId,
src.at(0).id, src.at(1).id),
src.at(2).id);
break;
case DxbcOpcode::IMax:
dst.id = m_module.opSMax(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::IMin:
dst.id = m_module.opSMin(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::INeg:
dst.id = m_module.opSNegate(
typeId, src.at(0).id);
break;
default:
return DxbcError::eUnhandledOpcode;
Logger::warn(str::format(
"DxbcCompiler: Unhandled instruction: ",
ins.op));
return;
}
// Apply result modifiers to floating-point results
result = this->applyResultModifiers(result, ins.control);
this->storeOp(ins.operands[0], result);
return DxbcError::sOk;
// Store computed value
dst = emitDstOperandModifiers(dst, ins.modifiers);
emitRegisterStore(ins.dst[0], dst);
}
DxbcError DxbcCompiler::handleVectorCmov(const DxbcInst& ins) {
void DxbcCompiler::emitVectorCmov(const DxbcShaderInstruction& ins) {
// movc has four operands:
// (1) The destination register
// (2) The condition vector
// (3) Vector to select from if the condition is not 0
// (4) Vector to select from if the condition is 0
const DxbcValue condition = this->loadOp(ins.operands[1], ins.operands[0].mask, ins.format.operands[1].type);
const DxbcValue selectTrue = this->loadOp(ins.operands[2], ins.operands[0].mask, ins.format.operands[2].type);
const DxbcValue selectFalse = this->loadOp(ins.operands[3], ins.operands[0].mask, ins.format.operands[3].type);
// (dst0) The destination register
// (src0) The condition vector
// (src0) Vector to select from if the condition is not 0
// (src0) Vector to select from if the condition is 0
const DxbcRegisterValue condition = emitRegisterLoad(ins.src[0], ins.dst[0].mask);
const DxbcRegisterValue selectTrue = emitRegisterLoad(ins.src[1], ins.dst[0].mask);
const DxbcRegisterValue selectFalse = emitRegisterLoad(ins.src[2], ins.dst[0].mask);
const uint32_t componentCount = ins.operands[0].mask.setCount();
const uint32_t componentCount = ins.dst[0].mask.setCount();
// We'll compare against a vector of zeroes to generate a
// boolean vector, which in turn will be used by OpSelect
@ -754,40 +700,32 @@ namespace dxvk {
zero = m_module.constComposite(zeroType, componentCount, zeroVec.data());
}
// Use the component mask to select the vector components
DxbcValue result;
result.componentType = ins.format.operands[0].type;
result.componentCount = componentCount;
result.valueId = m_module.opSelect(
this->defineVectorType(result.componentType, result.componentCount),
m_module.opINotEqual(boolType, condition.valueId, zero),
selectTrue.valueId, selectFalse.valueId);
DxbcRegisterValue result;
result.type.ctype = ins.dst[0].dataType;
result.type.ccount = componentCount;
result.id = m_module.opSelect(
getVectorTypeId(result.type),
m_module.opINotEqual(boolType, condition.id, zero),
selectTrue.id, selectFalse.id);
// Apply result modifiers to floating-point results
result = this->applyResultModifiers(result, ins.control);
this->storeOp(ins.operands[0], result);
return DxbcError::sOk;
result = emitDstOperandModifiers(result, ins.modifiers);
emitRegisterStore(ins.dst[0], result);
}
DxbcError DxbcCompiler::handleVectorCmp(const DxbcInst& ins) {
void DxbcCompiler::emitVectorCmp(const DxbcShaderInstruction& ins) {
// Compare instructions have three operands:
// (1) The destination register
// (2) The first vector to compare
// (3) The second vector to compare
DxbcValue arguments[2];
// (dst0) The destination register
// (src0) The first vector to compare
// (src1) The second vector to compare
const std::array<DxbcRegisterValue, 2> src = {
emitRegisterLoad(ins.src[0], ins.dst[0].mask),
emitRegisterLoad(ins.src[1], ins.dst[0].mask),
};
if (ins.format.operandCount != 3)
return DxbcError::eInvalidOperand;
for (uint32_t i = 0; i < 2; i++) {
arguments[i] = this->loadOp(
ins.operands[i + 1],
ins.operands[0].mask,
ins.format.operands[i + 1].type);
}
const uint32_t componentCount = ins.operands[0].mask.setCount();
const uint32_t componentCount = ins.dst[0].mask.setCount();
// Condition, which is a boolean vector used
// to select between the ~0u and 0u vectors.
@ -797,268 +735,295 @@ namespace dxvk {
if (componentCount > 1)
conditionType = m_module.defVectorType(conditionType, componentCount);
switch (ins.opcode) {
switch (ins.op) {
case DxbcOpcode::Eq:
condition = m_module.opFOrdEqual(
conditionType,
arguments[0].valueId,
arguments[1].valueId);
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Ge:
condition = m_module.opFOrdGreaterThanEqual(
conditionType,
arguments[0].valueId,
arguments[1].valueId);
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Lt:
condition = m_module.opFOrdLessThan(
conditionType,
arguments[0].valueId,
arguments[1].valueId);
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Ne:
condition = m_module.opFOrdNotEqual(
conditionType,
arguments[0].valueId,
arguments[1].valueId);
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::IEq:
condition = m_module.opIEqual(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::IGe:
condition = m_module.opSGreaterThanEqual(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::ILt:
condition = m_module.opSLessThan(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::INe:
condition = m_module.opINotEqual(
conditionType, src.at(0).id, src.at(1).id);
break;
default:
return DxbcError::eUnhandledOpcode;
Logger::warn(str::format(
"DxbcCompiler: Unhandled instruction: ",
ins.op));
return;
}
// Generate constant vectors for selection
uint32_t sFalse = m_module.constu32( 0u);
uint32_t sTrue = m_module.constu32(~0u);
const uint32_t maskType = this->defineVectorType(
DxbcScalarType::Uint32, componentCount);
DxbcRegisterValue result;
result.type.ctype = DxbcScalarType::Uint32;
result.type.ccount = componentCount;
const uint32_t typeId = getVectorTypeId(result.type);
if (componentCount > 1) {
const std::array<uint32_t, 4> vFalse = { sFalse, sFalse, sFalse, sFalse };
const std::array<uint32_t, 4> vTrue = { sTrue, sTrue, sTrue, sTrue };
sFalse = m_module.constComposite(maskType, componentCount, vFalse.data());
sTrue = m_module.constComposite(maskType, componentCount, vTrue .data());
sFalse = m_module.constComposite(typeId, componentCount, vFalse.data());
sTrue = m_module.constComposite(typeId, componentCount, vTrue .data());
}
// Perform component-wise mask selection
// based on the condition evaluated above.
DxbcValue result;
result.componentType = DxbcScalarType::Uint32;
result.componentCount = componentCount;
result.valueId = m_module.opSelect(
maskType, condition, sTrue, sFalse);
result.id = m_module.opSelect(
typeId, condition, sTrue, sFalse);
this->storeOp(ins.operands[0], result);
return DxbcError::sOk;
emitRegisterStore(ins.dst[0], result);
}
DxbcError DxbcCompiler::handleVectorDot(const DxbcInst& ins) {
// Determine the component count and the source
// operand mask. Since the result is scalar, we
// cannot use the destination register mask.
uint32_t numComponents = 0;
void DxbcCompiler::emitVectorDot(const DxbcShaderInstruction& ins) {
const DxbcRegMask srcMask(true,
ins.op >= DxbcOpcode::Dp2,
ins.op >= DxbcOpcode::Dp3,
ins.op >= DxbcOpcode::Dp4);
switch (ins.opcode) {
case DxbcOpcode::Dp2: numComponents = 2; break;
case DxbcOpcode::Dp3: numComponents = 3; break;
case DxbcOpcode::Dp4: numComponents = 4; break;
default: return DxbcError::eUnhandledOpcode;
}
const std::array<DxbcRegisterValue, 2> src = {
emitRegisterLoad(ins.src[0], srcMask),
emitRegisterLoad(ins.src[1], srcMask),
};
// We'll use xyz for dp3, xy for dp2
const DxbcRegMask srcMask(
numComponents >= 1, numComponents >= 2,
numComponents >= 3, numComponents >= 4);
DxbcRegisterValue dst;
dst.type.ctype = ins.dst[0].dataType;
dst.type.ccount = 1;
// Load input operands as floatig point numbers
DxbcValue arguments[2];
dst.id = m_module.opDot(
getVectorTypeId(dst.type),
src.at(0).id,
src.at(1).id);
for (uint32_t i = 1; i <= 2; i++) {
arguments[i - 1] = this->loadOp(
ins.operands[i], srcMask,
DxbcScalarType::Float32);
}
DxbcValue result;
result.componentType = DxbcScalarType::Float32;
result.componentCount = 1;
result.valueId = m_module.opDot(
this->defineVectorType(
result.componentType,
result.componentCount),
arguments[0].valueId,
arguments[1].valueId);
// Apply result modifiers to floating-point results
result = this->applyResultModifiers(result, ins.control);
this->storeOp(ins.operands[0], result);
return DxbcError::sOk;
dst = emitDstOperandModifiers(dst, ins.modifiers);
emitRegisterStore(ins.dst[0], dst);
}
DxbcError DxbcCompiler::handleVectorSinCos(const DxbcInst& ins) {
void DxbcCompiler::emitVectorImul(const DxbcShaderInstruction& ins) {
// imul and umul have four operands:
// (dst0) High destination register
// (dst1) Low destination register
// (src0) The first vector to compare
// (src1) The second vector to compare
if (ins.dst[0].type == DxbcOperandType::Null) {
if (ins.dst[1].type == DxbcOperandType::Null)
return;
// If dst0 is NULL, this instruction behaves just
// like any other three -operand ALU instruction
const std::array<DxbcRegisterValue, 2> src = {
emitRegisterLoad(ins.src[0], ins.dst[1].mask),
emitRegisterLoad(ins.src[1], ins.dst[1].mask),
};
DxbcRegisterValue result;
result.type.ctype = ins.dst[1].dataType;
result.type.ccount = ins.dst[1].mask.setCount();
result.id = m_module.opIMul(
getVectorTypeId(result.type),
src.at(0).id, src.at(1).id);
result = emitDstOperandModifiers(result, ins.modifiers);
emitRegisterStore(ins.dst[1], result);
} else {
// TODO implement this
Logger::warn("DxbcCompiler: Extended Imul not yet supported");
}
}
void DxbcCompiler::emitVectorSinCos(const DxbcShaderInstruction& ins) {
// sincos has three operands:
// (1) Destination register for sin(x)
// (2) Destination register for cos(x)
// (3) Source operand x
const DxbcInstOp dstSinOp = ins.operands[0];
const DxbcInstOp dstCosOp = ins.operands[1];
const DxbcInstOp srcOp = ins.operands[2];
// (dst0) Destination register for sin(x)
// (dst1) Destination register for cos(x)
// (src0) Source operand x
// Load source operand as 32-bit float vector
const DxbcValue srcValue = this->loadOp(srcOp,
DxbcRegMask(true, true, true, true),
DxbcScalarType::Float32);
// Load source operand as 32-bit float vector.
const DxbcRegisterValue srcValue = emitRegisterLoad(
ins.src[0], DxbcRegMask(true, true, true, true));
// Either output may be DxbcOperandType::Null, in
// which case we don't have to generate any code
if (dstSinOp.type != DxbcOperandType::Null) {
const DxbcValue sinInput = this->extractReg(
srcValue, dstSinOp.mask);
// which case we don't have to generate any code.
if (ins.dst[0].type != DxbcOperandType::Null) {
const DxbcRegisterValue sinInput =
emitRegisterExtract(srcValue, ins.dst[0].mask);
DxbcValue sinValue;
sinValue.componentType = srcValue.componentType;
sinValue.componentCount = srcValue.componentCount;
sinValue.valueId = m_module.opSin(
this->defineVectorType(
sinInput.componentType,
sinInput.componentCount),
sinInput.valueId);
DxbcRegisterValue sin;
sin.type = sinInput.type;
sin.id = m_module.opSin(
getVectorTypeId(sin.type),
sinInput.id);
this->storeOp(dstSinOp, sinValue);
emitRegisterStore(ins.dst[0], sin);
}
if (dstCosOp.type != DxbcOperandType::Null) {
const DxbcValue cosInput = this->extractReg(
srcValue, dstCosOp.mask);
if (ins.dst[1].type != DxbcOperandType::Null) {
const DxbcRegisterValue cosInput =
emitRegisterExtract(srcValue, ins.dst[1].mask);
DxbcValue cosValue;
cosValue.componentType = srcValue.componentType;
cosValue.componentCount = srcValue.componentCount;
cosValue.valueId = m_module.opCos(
this->defineVectorType(
cosInput.componentType,
cosInput.componentCount),
cosInput.valueId);
DxbcRegisterValue cos;
cos.type = cosInput.type;
cos.id = m_module.opSin(
getVectorTypeId(cos.type),
cosInput.id);
this->storeOp(dstCosOp, cosValue);
emitRegisterStore(ins.dst[1], cos);
}
return DxbcError::sOk;
}
DxbcValue DxbcCompiler::bitcastReg(
const DxbcValue& src,
DxbcScalarType type) {
if (src.componentType == type)
return src;
void DxbcCompiler::emitSample(
const DxbcShaderInstruction& ins) {
// TODO support address offset
// TODO support more sample ops
// TODO support 64-bit types by adjusting the component count
uint32_t typeId = this->defineVectorType(type, src.componentCount);
// sample has four operands:
// (dst0) The destination register
// (src0) Texture coordinates
// (src1) The texture itself
// (src2) The sampler object
const DxbcRegister& texCoordReg = ins.src[0];
const DxbcRegister& textureReg = ins.src[1];
const DxbcRegister& samplerReg = ins.src[2];
DxbcValue result;
result.componentType = type;
result.componentCount = src.componentCount;
result.valueId = m_module.opBitcast(typeId, src.valueId);
// Texture and sampler register IDs
const uint32_t textureId = textureReg.idx[0].offset;
const uint32_t samplerId = samplerReg.idx[0].offset;
// Load the texture coordinates. SPIR-V allows these
// to be float4 even if not all components are used.
const DxbcRegisterValue coord = emitRegisterLoad(
texCoordReg, DxbcRegMask(true, true, true, true));
// Combine the texture and the sampler into a sampled image
const uint32_t sampledImageType = m_module.defSampledImageType(
m_textures.at(textureId).textureTypeId);
const uint32_t sampledImageId = m_module.opSampledImage(
sampledImageType,
m_module.opLoad(
m_textures.at(textureId).textureTypeId,
m_textures.at(textureId).varId),
m_module.opLoad(
m_samplers.at(samplerId).typeId,
m_samplers.at(samplerId).varId));
// Sampling an image in SPIR-V always returns a four-component
// vector, so we need to declare the corresponding type here
// TODO infer sampled type properly
DxbcRegisterValue result;
result.type.ctype = DxbcScalarType::Float32;
result.type.ccount = 4;
result.id = m_module.opImageSampleImplicitLod(
getVectorTypeId(result.type),
sampledImageId, coord.id);
// Swizzle components using the texture swizzle
// and the destination operand's write mask
result = emitRegisterSwizzle(result,
textureReg.swizzle, ins.dst[0].mask);
emitRegisterStore(ins.dst[0], result);
}
void DxbcCompiler::emitRet(const DxbcShaderInstruction& ins) {
// TODO implement properly
m_module.opReturn();
m_module.functionEnd();
}
DxbcRegisterValue DxbcCompiler::emitRegisterBitcast(
DxbcRegisterValue srcValue,
DxbcScalarType dstType) {
if (srcValue.type.ctype == dstType)
return srcValue;
// TODO support 64-bit values
DxbcRegisterValue result;
result.type.ctype = dstType;
result.type.ccount = srcValue.type.ccount;
result.id = m_module.opBitcast(
getVectorTypeId(result.type),
srcValue.id);
return result;
}
DxbcValue DxbcCompiler::insertReg(
const DxbcValue& dst,
const DxbcValue& src,
DxbcRegMask mask) {
DxbcValue result;
result.componentType = dst.componentType;
result.componentCount = dst.componentCount;
const uint32_t resultTypeId = this->defineVectorType(
result.componentType, result.componentCount);
if (dst.componentCount == 1) {
// Both values are scalar, so the first component
// of the write mask decides which one to take.
result.valueId = mask[0] ? src.valueId : dst.valueId;
} else if (src.componentCount == 1) {
// The source value is scalar. Since OpVectorShuffle
// requires both arguments to be vectors, we have to
// use OpCompositeInsert to modify the vector instead.
const uint32_t componentId = mask.firstSet();
result.valueId = m_module.opCompositeInsert(
resultTypeId, src.valueId, dst.valueId,
1, &componentId);
} else {
// Both arguments are vectors. We can determine which
// components to take from which vector and use the
// OpVectorShuffle instruction.
uint32_t components[4];
uint32_t srcComponentId = dst.componentCount;
for (uint32_t i = 0; i < dst.componentCount; i++)
components[i] = mask[i] ? srcComponentId++ : i;
result.valueId = m_module.opVectorShuffle(
resultTypeId, dst.valueId, src.valueId,
dst.componentCount, components);
}
return result;
}
DxbcValue DxbcCompiler::extractReg(
const DxbcValue& src,
DxbcRegMask mask) {
return this->swizzleReg(src,
DxbcRegSwizzle(0, 1, 2, 3), mask);
}
DxbcValue DxbcCompiler::swizzleReg(
const DxbcValue& src,
const DxbcRegSwizzle& swizzle,
DxbcRegMask mask) {
DxbcRegisterValue DxbcCompiler::emitRegisterSwizzle(
DxbcRegisterValue value,
DxbcRegSwizzle swizzle,
DxbcRegMask writeMask) {
std::array<uint32_t, 4> indices;
uint32_t dstIndex = 0;
for (uint32_t i = 0; i < src.componentCount; i++) {
if (mask[i])
for (uint32_t i = 0; i < value.type.ccount; i++) {
if (writeMask[i])
indices[dstIndex++] = swizzle[i];
}
// If the swizzle combined with the mask can be reduced
// to a no-op, we don't need to insert any instructions.
bool isIdentitySwizzle = dstIndex == src.componentCount;
bool isIdentitySwizzle = dstIndex == value.type.ccount;
for (uint32_t i = 0; i < dstIndex && isIdentitySwizzle; i++)
isIdentitySwizzle &= indices[i] == i;
if (isIdentitySwizzle)
return src;
return value;
// Use OpCompositeExtract if the resulting vector contains
// only one component, and OpVectorShuffle if it is a vector.
DxbcValue result;
result.componentType = src.componentType;
result.componentCount = dstIndex;
DxbcRegisterValue result;
result.type.ctype = value.type.ctype;
result.type.ccount = dstIndex;
const uint32_t typeId = getVectorTypeId(result.type);
const uint32_t resultTypeId = this->defineVectorType(
result.componentType, result.componentCount);
if (dstIndex == 1) {
result.valueId = m_module.opCompositeExtract(
resultTypeId, src.valueId, 1, indices.data());
result.id = m_module.opCompositeExtract(
typeId, value.id, 1, indices.data());
} else {
result.valueId = m_module.opVectorShuffle(
resultTypeId, src.valueId, src.valueId,
result.id = m_module.opVectorShuffle(
typeId, value.id, value.id,
dstIndex, indices.data());
}
@ -1066,334 +1031,435 @@ namespace dxvk {
}
DxbcValue DxbcCompiler::extendReg(
const DxbcValue& src,
uint32_t size) {
DxbcRegisterValue DxbcCompiler::emitRegisterExtract(
DxbcRegisterValue value,
DxbcRegMask mask) {
return emitRegisterSwizzle(value,
DxbcRegSwizzle(0, 1, 2, 3), mask);
}
DxbcRegisterValue DxbcCompiler::emitRegisterInsert(
DxbcRegisterValue dstValue,
DxbcRegisterValue srcValue,
DxbcRegMask srcMask) {
DxbcRegisterValue result;
result.type = dstValue.type;
const uint32_t typeId = getVectorTypeId(result.type);
if (srcMask.setCount() == 0) {
// Nothing to do if the insertion mask is empty
result.id = dstValue.id;
} else if (dstValue.type.ccount == 1) {
// Both values are scalar, so the first component
// of the write mask decides which one to take.
result.id = srcMask[0] ? srcValue.id : dstValue.id;
} else if (srcValue.type.ccount == 1) {
// The source value is scalar. Since OpVectorShuffle
// requires both arguments to be vectors, we have to
// use OpCompositeInsert to modify the vector instead.
const uint32_t componentId = srcMask.firstSet();
result.id = m_module.opCompositeInsert(typeId,
srcValue.id, dstValue.id, 1, &componentId);
} else {
// Both arguments are vectors. We can determine which
// components to take from which vector and use the
// OpVectorShuffle instruction.
std::array<uint32_t, 4> components;
uint32_t srcComponentId = dstValue.type.ccount;
for (uint32_t i = 0; i < dstValue.type.ccount; i++)
components.at(i) = srcMask[i] ? srcComponentId++ : i;
result.id = m_module.opVectorShuffle(
typeId, dstValue.id, srcValue.id,
dstValue.type.ccount, components.data());
}
return result;
}
DxbcRegisterValue DxbcCompiler::emitRegisterExtend(
DxbcRegisterValue value,
uint32_t size) {
if (size == 1)
return src;
return value;
std::array<uint32_t, 4> ids = {
src.valueId, src.valueId,
src.valueId, src.valueId,
value.id, value.id,
value.id, value.id,
};
uint32_t typeId = this->defineVectorType(
src.componentType, size);
DxbcValue result;
result.componentType = src.componentType;
result.componentCount = size;
result.valueId = m_module.opCompositeConstruct(
typeId, size, ids.data());
DxbcRegisterValue result;
result.type.ctype = value.type.ctype;
result.type.ccount = size;
result.id = m_module.opCompositeConstruct(
getVectorTypeId(result.type),
size, ids.data());
return result;
}
DxbcValue DxbcCompiler::applyOperandModifiers(
DxbcValue value,
DxbcOperandModifiers modifiers) {
uint32_t typeId = this->defineVectorType(
value.componentType, value.componentCount);
DxbcRegisterValue DxbcCompiler::emitRegisterAbsolute(
DxbcRegisterValue value) {
const uint32_t typeId = getVectorTypeId(value.type);
// Both modifiers can be applied to the same value.
// In that case, the absolute value is negated.
if (modifiers.test(DxbcOperandModifier::Abs))
value.valueId = m_module.opFAbs(typeId, value.valueId);
if (modifiers.test(DxbcOperandModifier::Neg))
value.valueId = m_module.opFNegate(typeId, value.valueId);
return value;
}
DxbcValue DxbcCompiler::applyResultModifiers(
DxbcValue value,
DxbcOpcodeControl control) {
uint32_t typeId = this->defineVectorType(
value.componentType, value.componentCount);
if (control.saturateBit()) {
value.valueId = m_module.opFClamp(
typeId, value.valueId,
m_module.constf32(0.0f),
m_module.constf32(1.0f));
switch (value.type.ctype) {
case DxbcScalarType::Float32: value.id = m_module.opFAbs(typeId, value.id); break;
case DxbcScalarType::Sint32: value.id = m_module.opSAbs(typeId, value.id); break;
default: Logger::warn("DxbcCompiler: Cannot get absolute value for given type");
}
return value;
}
DxbcValue DxbcCompiler::loadOp(
const DxbcInstOp& srcOp,
DxbcRegMask srcMask,
DxbcScalarType dstType) {
if (srcOp.type == DxbcOperandType::Imm32) {
return this->loadImm32(srcOp, srcMask, dstType);
} else {
// Load operand value from the operand pointer
DxbcValue result = this->loadRegister(srcOp, srcMask, dstType);
// Apply the component swizzle or the selection,
// depending on which mode the operand is in.
if (srcOp.componentCount == 4) {
switch (srcOp.componentMode) {
case DxbcRegMode::Swizzle:
result = this->swizzleReg(result, srcOp.swizzle, srcMask);
break;
case DxbcRegMode::Select1:
result = this->extractReg(result, 1u << srcOp.select1);
break;
default:
Logger::err("dxbc: Invalid component selection mode");
}
}
// Cast it to the requested type. We need to do
// this after the swizzling for 64-bit types.
if (result.componentType != dstType)
result = this->bitcastReg(result, dstType);
// Apply operand modifiers
return this->applyOperandModifiers(result, srcOp.modifiers);
DxbcRegisterValue DxbcCompiler::emitRegisterNegate(
DxbcRegisterValue value) {
const uint32_t typeId = getVectorTypeId(value.type);
switch (value.type.ctype) {
case DxbcScalarType::Float32: value.id = m_module.opFNegate(typeId, value.id); break;
case DxbcScalarType::Sint32: value.id = m_module.opSNegate(typeId, value.id); break;
default: Logger::warn("DxbcCompiler: Cannot negate given type");
}
return value;
}
DxbcValue DxbcCompiler::loadImm32(
const DxbcInstOp& srcOp,
DxbcRegMask srcMask,
DxbcScalarType dstType) {
// We will generate Uint32 constants because at this
// point we don't know how they are going to be used.
DxbcValue result;
result.componentType = DxbcScalarType::Uint32;
result.componentCount = srcMask.setCount();
DxbcRegisterValue DxbcCompiler::emitSrcOperandModifiers(
DxbcRegisterValue value,
DxbcRegModifiers modifiers) {
if (modifiers.test(DxbcRegModifier::Abs))
value = emitRegisterAbsolute(value);
uint32_t resultTypeId = this->defineVectorType(
result.componentType, result.componentCount);
if (modifiers.test(DxbcRegModifier::Neg))
value = emitRegisterNegate(value);
return value;
}
DxbcRegisterValue DxbcCompiler::emitDstOperandModifiers(
DxbcRegisterValue value,
DxbcOpModifiers modifiers) {
const uint32_t typeId = getVectorTypeId(value.type);
uint32_t constIds[4];
uint32_t constIdx = 0;
// Generate scalar constants for each component
// and pack them tightly in an array, so that we
// can use them to generate a constant vector.
if (srcOp.componentCount == 4) {
for (uint32_t i = 0; i < 4; i++) {
if (srcMask[i]) {
constIds[constIdx++] = m_module.constu32(
srcOp.immediates[i]);
}
if (value.type.ctype == DxbcScalarType::Float32) {
// Saturating only makes sense on floats
if (modifiers.saturate) {
value.id = m_module.opFClamp(
typeId, value.id,
m_module.constf32(0.0f),
m_module.constf32(1.0f));
}
} else if (srcOp.componentCount == 1) {
constIds[0] = m_module.constu32(srcOp.immediates[0]);
} else {
Logger::err("dxbc: Invalid imm32 component count");
}
// If the result is a vector, emit the final constant
if (result.componentCount == 1) {
result.valueId = constIds[0];
} else {
result.valueId = m_module.constComposite(
resultTypeId, result.componentCount, constIds);
}
// If necessary, cast the constant to the desired type
if (result.componentType != dstType)
result = this->bitcastReg(result, dstType);
return value;
}
DxbcRegisterPointer DxbcCompiler::emitGetTempPtr(
const DxbcRegister& operand) {
// r# regs are indexed as follows:
// (0) register index (immediate)
DxbcRegisterPointer result;
result.type.ctype = DxbcScalarType::Float32;
result.type.ccount = 4;
result.id = m_rRegs.at(operand.idx[0].offset);
return result;
}
DxbcValue DxbcCompiler::loadRegister(
const DxbcInstOp& srcOp,
DxbcRegMask srcMask,
DxbcScalarType dstType) {
return this->loadPtr(
this->getOperandPtr(srcOp));
}
void DxbcCompiler::storeOp(
const DxbcInstOp& dstOp,
const DxbcValue& srcValue) {
this->storePtr(
this->getOperandPtr(dstOp),
srcValue, dstOp.mask);
}
DxbcValue DxbcCompiler::loadPtr(const DxbcPointer& ptr) {
const uint32_t typeId = this->defineVectorType(
ptr.componentType, ptr.componentCount);
DxbcRegisterPointer DxbcCompiler::emitGetInputPtr(
const DxbcRegister& operand) {
// In the vertex and pixel stages,
// v# regs are indexed as follows:
// (0) register index (relative)
//
// In the tessellation and geometry
// stages, the index has two dimensions:
// (0) vertex index (relative)
// (1) register index (relative)
if (operand.idxDim != 1)
throw DxvkError("DxbcCompiler: 2D index for v# not yet supported");
DxbcValue result;
result.componentType = ptr.componentType;
result.componentCount = ptr.componentCount;
result.valueId = m_module.opLoad(typeId, ptr.pointerId);
// We don't support two-dimensional indices yet
const uint32_t registerId = operand.idx[0].offset;
DxbcRegisterPointer result;
result.type.ctype = DxbcScalarType::Float32;
result.type.ccount = 4;
result.id = m_vRegs.at(registerId);
return result;
}
void DxbcCompiler::storePtr(
const DxbcPointer& ptr,
const DxbcValue& value,
DxbcRegMask mask) {
DxbcValue srcValue = value;
DxbcRegisterPointer DxbcCompiler::emitGetOutputPtr(
const DxbcRegister& operand) {
// Same index format as input registers, except that
// outputs cannot be accessed with a relative index.
if (operand.idxDim != 1)
throw DxvkError("DxbcCompiler: 2D index for o# not yet supported");
// If the source value consists of only one component,
// it is stored in all destination register components.
if (srcValue.componentCount == 1)
srcValue = this->extendReg(srcValue, mask.setCount());
// We don't support two-dimensional indices yet
const uint32_t registerId = operand.idx[0].offset;
// In the pixel shader, output registers are typed,
// whereas they are float4 in all other stages.
if (m_version.type() == DxbcProgramType::PixelShader) {
DxbcRegisterPointer result;
result.type = m_ps.oTypes.at(registerId);
result.id = m_oRegs.at(registerId);
return result;
} else {
DxbcRegisterPointer result;
result.type.ctype = DxbcScalarType::Float32;
result.type.ccount = 4;
result.id = m_oRegs.at(registerId);
return result;
}
}
DxbcRegisterPointer DxbcCompiler::emitGetConstBufPtr(
const DxbcRegister& operand) {
// Constant buffers take a two-dimensional index:
// (0) register index (immediate)
// (1) constant offset (relative)
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassUniform;
const uint32_t regId = operand.idx[0].offset;
const DxbcRegisterValue constId = emitIndexLoad(operand.idx[1]);
const uint32_t ptrTypeId = getPointerTypeId(info);
const std::array<uint32_t, 2> indices = {
m_module.consti32(0), constId.id
};
DxbcRegisterPointer result;
result.type = info.type;
result.id = m_module.opAccessChain(ptrTypeId,
m_constantBuffers.at(regId).varId,
indices.size(), indices.data());
return result;
}
DxbcRegisterPointer DxbcCompiler::emitGetOperandPtr(
const DxbcRegister& operand) {
switch (operand.type) {
case DxbcOperandType::Temp:
return emitGetTempPtr(operand);
case DxbcOperandType::Input:
return emitGetInputPtr(operand);
case DxbcOperandType::Output:
return emitGetOutputPtr(operand);
case DxbcOperandType::ConstantBuffer:
return emitGetConstBufPtr(operand);
default:
throw DxvkError(str::format(
"DxbcCompiler: Unhandled operand type: ",
operand.type));
}
}
DxbcRegisterValue DxbcCompiler::emitIndexLoad(
DxbcRegIndex index) {
if (index.relReg != nullptr) {
DxbcRegisterValue result = emitRegisterLoad(
*index.relReg, DxbcRegMask(true, false, false, false));
if (index.offset != 0) {
result.id = m_module.opIAdd(
getVectorTypeId(result.type), result.id,
m_module.consti32(index.offset));
}
return result;
} else {
DxbcRegisterValue result;
result.type.ctype = DxbcScalarType::Sint32;
result.type.ccount = 1;
result.id = m_module.consti32(index.offset);
return result;
}
}
DxbcRegisterValue DxbcCompiler::emitValueLoad(
DxbcRegisterPointer ptr) {
DxbcRegisterValue result;
result.type = ptr.type;
result.id = m_module.opLoad(
getVectorTypeId(result.type),
ptr.id);
return result;
}
void DxbcCompiler::emitValueStore(
DxbcRegisterPointer ptr,
DxbcRegisterValue value,
DxbcRegMask writeMask) {
// If the component types are not compatible,
// we need to bit-cast the source variable.
if (ptr.componentType != srcValue.componentType)
srcValue = this->bitcastReg(srcValue, ptr.componentType);
if (value.type.ctype != ptr.type.ctype)
value = emitRegisterBitcast(value, ptr.type.ctype);
if (mask.setCount() == ptr.componentCount) {
// If the source value consists of only one component,
// it is stored in all components of the destination.
if (value.type.ccount == 1)
value = emitRegisterExtend(value, writeMask.setCount());
if (ptr.type.ccount == writeMask.setCount()) {
// Simple case: We write to the entire register
m_module.opStore(ptr.pointerId, srcValue.valueId);
m_module.opStore(ptr.id, value.id);
} else {
// We only write to part of the destination
// register, so we need to load and modify it
DxbcValue tmp = this->loadPtr(ptr);
tmp = this->insertReg(tmp, srcValue, mask);
DxbcRegisterValue tmp = emitValueLoad(ptr);
tmp = emitRegisterInsert(tmp, value, writeMask);
m_module.opStore(ptr.pointerId, tmp.valueId);
m_module.opStore(ptr.id, tmp.id);
}
}
DxbcValue DxbcCompiler::loadIndex(const DxbcInstOpIndex& idx) {
DxbcValue constantPart;
DxbcValue relativePart;
if ((idx.type == DxbcIndexType::Immediate) || (idx.immediate != 0)) {
constantPart.componentType = DxbcScalarType::Sint32;
constantPart.componentCount = 1;
constantPart.valueId = m_module.consti32(
static_cast<int32_t>(idx.immediate));
}
if (idx.type == DxbcIndexType::Relative) {
DxbcInstOp offsetOp;
offsetOp.type = DxbcOperandType::Temp;
offsetOp.indexDim = 1;
offsetOp.index[0].type = DxbcIndexType::Immediate;
offsetOp.index[0].immediate = idx.tempRegId;
offsetOp.componentCount = 4;
offsetOp.componentMode = DxbcRegMode::Select1;
offsetOp.select1 = idx.tempRegComponent;
DxbcRegisterValue DxbcCompiler::emitRegisterLoad(
const DxbcRegister& reg,
DxbcRegMask writeMask) {
if (reg.type == DxbcOperandType::Imm32) {
DxbcRegisterValue result;
relativePart = this->loadOp(offsetOp,
DxbcRegMask(true, false, false, false),
DxbcScalarType::Sint32);
}
if (relativePart.valueId == 0) return constantPart;
if (constantPart.valueId == 0) return relativePart;
DxbcValue result;
result.componentType = DxbcScalarType::Sint32;
result.componentCount = 1;
result.valueId = m_module.opIAdd(
this->defineScalarType(result.componentType),
relativePart.valueId, constantPart.valueId);
return result;
}
DxbcPointer DxbcCompiler::getOperandPtr(const DxbcInstOp& op) {
DxbcPointer result;
switch (op.type) {
case DxbcOperandType::Temp:
result.componentType = DxbcScalarType::Float32;
result.componentCount = 4;
result.pointerId = m_rRegs.at(op.index[0].immediate);
break;
if (reg.componentCount == DxbcComponentCount::Component1) {
// Create one single u32 constant
result.type.ctype = DxbcScalarType::Uint32;
result.type.ccount = 1;
result.id = m_module.constu32(reg.imm.u32_1);
} else if (reg.componentCount == DxbcComponentCount::Component4) {
// Create a four-component u32 vector
std::array<uint32_t, 4> indices = {
m_module.constu32(reg.imm.u32_4[0]),
m_module.constu32(reg.imm.u32_4[1]),
m_module.constu32(reg.imm.u32_4[2]),
m_module.constu32(reg.imm.u32_4[3]),
};
// TODO implement properly
case DxbcOperandType::Input:
result.componentType = DxbcScalarType::Float32;
result.componentCount = 4;
result.pointerId = m_vRegs.at(op.index[0].immediate);
break;
result.type.ctype = DxbcScalarType::Uint32;
result.type.ccount = 4;
result.id = m_module.constComposite(
getVectorTypeId(result.type),
indices.size(), indices.data());
} else {
// Something went horribly wrong in the decoder or the shader is broken
throw DxvkError("DxbcCompiler: Invalid component count for immediate operand");
}
// TODO implement properly
case DxbcOperandType::Output:
if (m_version.type() == DxbcProgramType::PixelShader)
return m_ps.oregs.at(op.index[0].immediate);
// Cast constants to the requested type
return emitRegisterBitcast(result, reg.dataType);
} else {
// Load operand from the operand pointer
DxbcRegisterPointer ptr = emitGetOperandPtr(reg);
DxbcRegisterValue result = emitValueLoad(ptr);
// Apply operand swizzle to the operand value
result = emitRegisterSwizzle(result, reg.swizzle, writeMask);
// Cast it to the requested type. We need to do
// this after the swizzling for 64-bit types.
result = emitRegisterBitcast(result, reg.dataType);
// Apply operand modifiers
result = emitSrcOperandModifiers(result, reg.modifiers);
return result;
}
}
void DxbcCompiler::emitRegisterStore(
const DxbcRegister& reg,
DxbcRegisterValue value) {
emitValueStore(emitGetOperandPtr(reg), value, reg.mask);
}
void DxbcCompiler::emitVsInputSetup() {
}
void DxbcCompiler::emitPsInputSetup() {
}
void DxbcCompiler::emitVsOutputSetup() {
for (const DxbcSvMapping& svMapping : m_oMappings) {
switch (svMapping.sv) {
case DxbcSystemValue::Position: {
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassOutput;
const uint32_t ptrTypeId = getPointerTypeId(info);
const uint32_t memberId = m_module.constu32(PerVertex_Position);
DxbcRegisterPointer dstPtr;
dstPtr.type = info.type;
dstPtr.id = m_module.opAccessChain(
ptrTypeId, m_perVertexOut, 1, &memberId);
DxbcRegisterPointer srcPtr;
srcPtr.type = info.type;
srcPtr.id = m_oRegs.at(svMapping.regId);
emitValueStore(dstPtr, emitValueLoad(srcPtr),
DxbcRegMask(true, true, true, true));
} break;
result.componentType = DxbcScalarType::Float32;
result.componentCount = 4;
result.pointerId = m_oRegs.at(op.index[0].immediate);
break;
case DxbcOperandType::ConstantBuffer:
return this->getConstantBufferPtr(op);
default:
Logger::err("dxbc: Unhandled operand type");
default:
Logger::warn(str::format(
"dxbc: Unhandled vertex sv output: ",
svMapping.sv));
}
}
return result;
}
DxbcPointer DxbcCompiler::getConstantBufferPtr(const DxbcInstOp& op) {
if (op.indexDim != 2) {
Logger::err("dxbc: Constant buffer reference needs two indices");
return DxbcPointer();
}
void DxbcCompiler::emitPsOutputSetup() {
// The operand itself has two indices:
// (1) The constant buffer ID (immediate)
// (2) The constant offset (relative)
const uint32_t bufferId = op.index[0].immediate;
const DxbcValue offset = this->loadIndex(op.index[1]);
// The first index selects the struct member,
// the second one selects the array element.
std::array<uint32_t, 2> indices = {
m_module.constu32(0), offset.valueId };
DxbcPointer result;
result.componentType = DxbcScalarType::Float32;
result.componentCount = 4;
result.pointerId = m_module.opAccessChain(
this->definePointerType(
result.componentType,
result.componentCount,
spv::StorageClassUniform),
m_constantBuffers.at(bufferId).varId,
2, indices.data());
return result;
}
void DxbcCompiler::beginVertexShader(const Rc<DxbcIsgn>& isgn) {
void DxbcCompiler::emitVsInit() {
m_module.enableCapability(spv::CapabilityShader);
m_module.enableCapability(spv::CapabilityCullDistance);
m_module.enableCapability(spv::CapabilityClipDistance);
m_module.enableCapability(spv::CapabilityCullDistance);
// Declare the per-vertex output block. This is where
// the vertex shader will write the vertex position.
uint32_t perVertexStruct = this->definePerVertexBlock();
uint32_t perVertexPointer = m_module.defPointerType(
const uint32_t perVertexStruct = this->getPerVertexBlockId();
const uint32_t perVertexPointer = m_module.defPointerType(
perVertexStruct, spv::StorageClassOutput);
m_perVertexOut = m_module.newVar(
perVertexPointer, spv::StorageClassOutput);
m_entryPointInterfaces.push_back(m_perVertexOut);
m_module.setDebugName(m_perVertexOut, "vs_block");
m_module.setDebugName(m_perVertexOut, "vs_vertex_out");
// Main function of the vertex shader
m_vs.functionId = m_module.allocateId();
@ -1409,7 +1475,7 @@ namespace dxvk {
}
void DxbcCompiler::beginPixelShader(const Rc<DxbcIsgn>& osgn) {
void DxbcCompiler::emitPsInit() {
m_module.enableCapability(spv::CapabilityShader);
m_module.setOriginUpperLeft(m_entryPointId);
@ -1418,22 +1484,19 @@ namespace dxvk {
// the render target.
for (auto e = m_osgn->begin(); e != m_osgn->end(); e++) {
if (e->systemValue == DxbcSystemValue::None) {
uint32_t regTypeId = this->defineVectorType(
e->componentType, e->componentMask.setCount());
DxbcRegisterInfo info;
info.type.ctype = e->componentType;
info.type.ccount = e->componentMask.setCount();
info.sclass = spv::StorageClassOutput;
uint32_t ptrTypeId = m_module.defPointerType(
regTypeId, spv::StorageClassOutput);
uint32_t varId = m_module.newVar(
ptrTypeId, spv::StorageClassOutput);
const uint32_t varId = emitNewVariable(info);
m_module.decorateLocation(varId, e->registerId);
m_module.setDebugName(varId, str::format("o", e->registerId).c_str());
m_entryPointInterfaces.push_back(varId);
m_ps.oregs.at(e->registerId).componentType = e->componentType;
m_ps.oregs.at(e->registerId).componentCount = e->componentMask.setCount();
m_ps.oregs.at(e->registerId).pointerId = varId;
m_oRegs.at(e->registerId) = varId;
m_ps.oTypes.at(e->registerId) = info.type;
}
}
@ -1451,77 +1514,62 @@ namespace dxvk {
}
void DxbcCompiler::prepareVertexInputs() {
// TODO implement
}
void DxbcCompiler::preparePixelInputs() {
// TODO implement
}
void DxbcCompiler::prepareVertexOutputs() {
for (const DxbcSvMapping& svMapping : m_oSvs) {
switch (svMapping.sv) {
case DxbcSystemValue::Position: {
DxbcPointer dstPtr;
dstPtr.componentType = DxbcScalarType::Float32;
dstPtr.componentCount = 4;
uint32_t regTypeId = this->defineVectorType(
dstPtr.componentType, dstPtr.componentCount);
uint32_t ptrTypeId = m_module.defPointerType(
regTypeId, spv::StorageClassOutput);
uint32_t memberId = m_module.constu32(PerVertex_Position);
dstPtr.pointerId = m_module.opAccessChain(
ptrTypeId, m_perVertexOut, 1, &memberId);
DxbcPointer srcPtr;
srcPtr.componentType = DxbcScalarType::Float32;
srcPtr.componentCount = 4;
srcPtr.pointerId = m_oRegs.at(svMapping.regId);
this->storePtr(dstPtr, this->loadPtr(srcPtr),
DxbcRegMask(true, true, true, true));
} break;
default:
Logger::err(str::format(
"dxbc: Unsupported vertex sv output: ",
svMapping.sv));
}
}
}
void DxbcCompiler::preparePixelOutputs() {
// TODO implement
}
void DxbcCompiler::endVertexShader() {
this->prepareVertexInputs();
void DxbcCompiler::emitVsFinalize() {
this->emitVsInputSetup();
m_module.opFunctionCall(
m_module.defVoidType(),
m_vs.functionId, 0, nullptr);
this->prepareVertexOutputs();
this->emitVsOutputSetup();
}
void DxbcCompiler::endPixelShader() {
this->preparePixelInputs();
void DxbcCompiler::emitPsFinalize() {
this->emitPsInputSetup();
m_module.opFunctionCall(
m_module.defVoidType(),
m_ps.functionId, 0, nullptr);
this->preparePixelOutputs();
this->emitPsOutputSetup();
}
uint32_t DxbcCompiler::definePerVertexBlock() {
uint32_t DxbcCompiler::emitNewVariable(const DxbcRegisterInfo& info) {
const uint32_t ptrTypeId = this->getPointerTypeId(info);
return m_module.newVar(ptrTypeId, info.sclass);
}
uint32_t DxbcCompiler::getScalarTypeId(DxbcScalarType type) {
switch (type) {
case DxbcScalarType::Uint32: return m_module.defIntType(32, 0);
case DxbcScalarType::Uint64: return m_module.defIntType(64, 0);
case DxbcScalarType::Sint32: return m_module.defIntType(32, 1);
case DxbcScalarType::Sint64: return m_module.defIntType(64, 1);
case DxbcScalarType::Float32: return m_module.defFloatType(32);
case DxbcScalarType::Float64: return m_module.defFloatType(64);
}
throw DxvkError("DxbcCompiler: Invalid scalar type");
}
uint32_t DxbcCompiler::getVectorTypeId(const DxbcVectorType& type) {
uint32_t typeId = this->getScalarTypeId(type.ctype);
if (type.ccount > 1)
typeId = m_module.defVectorType(typeId, type.ccount);
return typeId;
}
uint32_t DxbcCompiler::getPointerTypeId(const DxbcRegisterInfo& type) {
return m_module.defPointerType(
this->getVectorTypeId(type.type),
type.sclass);
}
uint32_t DxbcCompiler::getPerVertexBlockId() {
uint32_t t_f32 = m_module.defFloatType(32);
uint32_t t_f32_v4 = m_module.defVectorType(t_f32, 4);
uint32_t t_f32_a2 = m_module.defArrayType(t_f32, m_module.constu32(2));
@ -1549,155 +1597,4 @@ namespace dxvk {
return typeId;
}
uint32_t DxbcCompiler::defineScalarType(
DxbcScalarType componentType) {
switch (componentType) {
case DxbcScalarType::Float32: return m_module.defFloatType(32);
case DxbcScalarType::Float64: return m_module.defFloatType(64);
case DxbcScalarType::Uint32: return m_module.defIntType(32, 0);
case DxbcScalarType::Uint64: return m_module.defIntType(64, 0);
case DxbcScalarType::Sint32: return m_module.defIntType(32, 1);
case DxbcScalarType::Sint64: return m_module.defIntType(64, 1);
}
Logger::err("dxbc: Invalid scalar type");
return 0;
}
uint32_t DxbcCompiler::defineVectorType(
DxbcScalarType componentType,
uint32_t componentCount) {
uint32_t typeId = this->defineScalarType(componentType);
if (componentCount > 1) {
typeId = m_module.defVectorType(
typeId, componentCount);
}
return typeId;
}
uint32_t DxbcCompiler::definePointerType(
DxbcScalarType componentType,
uint32_t componentCount,
spv::StorageClass storageClass) {
return m_module.defPointerType(
this->defineVectorType(
componentType, componentCount),
storageClass);
}
DxbcError DxbcCompiler::parseInstruction(const DxbcInstruction& ins, DxbcInst& out) {
out.opcode = ins.token().opcode();
out.control = ins.token().control();
out.format = dxbcInstructionFormat(out.opcode);
// TODO implement extended opcodes
uint32_t argOffset = 0;
for (uint32_t i = 0; i < out.format.operandCount; i++) {
if (out.format.operands[i].kind == DxbcOperandKind::Imm32) {
// Some declarations use immediate DWORDs rather than the
// immediate operand tokens, but we'll treat them the same.
out.operands[i].type = DxbcOperandType::Imm32;
out.operands[i].immediates[0] = ins.arg(argOffset);
argOffset += 1;
} else {
// Most instructions use proper operand tokens, which either
// store an immediate value or references a register from one
// of the indexable register files.
const DxbcOperand op = ins.operand(argOffset);
out.operands[i].type = op.token().type();
// Parse operand modifiers
DxbcOperandTokenExt token;
if (op.queryOperandExt(DxbcOperandExt::OperandModifier, token))
out.operands[i].modifiers = DxbcOperandModifiers(token.data());
// Parse immediate values if applicable
if (op.token().type() == DxbcOperandType::Imm32) {
for (uint32_t j = 0; j < op.token().numComponents(); j++)
out.operands[i].immediates[j] = op.imm32(j);
}
if (op.token().type() == DxbcOperandType::Imm64) {
Logger::err("dxbc: 64-bit immediates not supported");
return DxbcError::eInstructionFormat;
}
// Parse the indices. Each index is either a constant value
// or the sum of a constant and a temporary register value.
out.operands[i].indexDim = op.token().indexDimension();
for (uint32_t j = 0; j < op.token().indexDimension(); j++) {
const DxbcOperandIndex index = op.index(j);
DxbcInstOpIndex& opIndex = out.operands[i].index[j];
opIndex.type = index.hasRelPart()
? DxbcIndexType::Relative
: DxbcIndexType::Immediate;
opIndex.immediate = index.immPart();
// If the index is relative, we have to parse another
// operand token which must reference a r# register.
if (index.hasRelPart()) {
const DxbcOperand relPart = index.relPart();
if (relPart.token().type() != DxbcOperandType::Temp) {
Logger::err("dxbc: Invalid index register type");
return DxbcError::eInstructionFormat;
}
if ((relPart.token().indexDimension() != 1)
|| (relPart.index(0).hasRelPart())) {
Logger::err("dxbc: Invalid index register index");
return DxbcError::eInstructionFormat;
}
if (relPart.token().selectionMode() != DxbcRegMode::Select1) {
Logger::err("dxbc: Invalid index component selection mode");
return DxbcError::eInstructionFormat;
}
// Assign the index and the component selection
opIndex.tempRegId = relPart.index(0).immPart();
opIndex.tempRegComponent = relPart.token().select1();
}
}
// Parse component mask, swizzle or selection
out.operands[i].componentCount = op.token().numComponents();
out.operands[i].componentMode = op.token().selectionMode();
if (op.token().numComponents() == 4) {
switch (op.token().selectionMode()) {
case DxbcRegMode::Mask:
out.operands[i].mask = op.token().mask();
break;
case DxbcRegMode::Swizzle:
out.operands[i].swizzle = op.token().swizzle();
break;
case DxbcRegMode::Select1:
out.operands[i].select1 = op.token().select1();
break;
}
}
// Move on to next operand
argOffset += op.length();
}
}
return DxbcError::sOk;
}
}

View File

@ -1,133 +1,84 @@
#pragma once
#include <array>
#include <vector>
#include "../spirv/spirv_module.h"
#include "dxbc_chunk_isgn.h"
#include "dxbc_decoder.h"
#include "dxbc_defs.h"
#include "dxbc_names.h"
#include "dxbc_util.h"
namespace dxvk {
/**
* \brief Expression value
* \brief Vector type
*
* Tracks the type and the SPIR-V variable
* ID when evaluating DXBC instructions.
* Convenience struct that stores a scalar
* type and a component count. The compiler
* can use this to generate SPIR-V types.
*/
struct DxbcValue {
DxbcScalarType componentType = DxbcScalarType::Float32;
uint32_t componentCount = 0;
uint32_t valueId = 0;
struct DxbcVectorType {
DxbcScalarType ctype;
uint32_t ccount;
};
/**
* \brief Variable pointer
* \brief Register info
*
* Stores the SPIR-V pointer ID and the
* type of the referenced variable. Used
* to access variables and resources.
* Stores the vector type of a register and
* its storage class. The compiler can use
* this to generate SPIR-V pointer types.
*/
struct DxbcPointer {
DxbcScalarType componentType = DxbcScalarType::Float32;
uint32_t componentCount = 0;
uint32_t pointerId = 0;
struct DxbcRegisterInfo {
DxbcVectorType type;
spv::StorageClass sclass;
};
/**
* \brief Compiler error code
* \brief Register value
*
* Helps identify the type of error
* that may occur during compilation.
* Stores a vector type and a SPIR-V ID that
* represents an intermediate value. This is
* used to track the type of such values.
*/
enum class DxbcError {
sOk,
eInternal,
eInstructionFormat,
eInvalidOperand,
eInvalidOperandIndex,
eTypeMismatch,
eUnhandledOpcode,
eUnsupported,
struct DxbcRegisterValue {
DxbcVectorType type;
uint32_t id;
};
/**
* \brief Operand index type
* \brief Register pointer
*
* Defines whether a register index
* is relative or constant.
* Stores a vector type and a SPIR-V ID that
* represents a pointer to such a vector. This
* can be used to load registers conveniently.
*/
enum class DxbcIndexType {
Immediate, ///< Index is a constant value
Relative, ///< Index depends on a r# register
struct DxbcRegisterPointer {
DxbcVectorType type;
uint32_t id;
};
/**
* \brief Instruction operand index
*
* Stores the type of the index as well as the
* register (if relative) and the constant offset.
* \brief Vertex shader-specific structure
*/
struct DxbcInstOpIndex {
DxbcIndexType type = DxbcIndexType::Immediate;
uint32_t immediate = 0;
uint32_t tempRegId = 0;
uint32_t tempRegComponent = 0;
};
/**
* \brief Instruction operand
*
* Stores all information about a single
* operand, including the register index.
*/
struct DxbcInstOp {
DxbcOperandType type = DxbcOperandType::Temp;
DxbcOperandModifiers modifiers = 0;
uint32_t immediates[4] = { 0u, 0u, 0u, 0u };
uint32_t indexDim = 0;
DxbcInstOpIndex index[3];
uint32_t componentCount = 0;
DxbcRegMode componentMode = DxbcRegMode::Mask;
DxbcRegMask mask = { false, false, false, false };
DxbcRegSwizzle swizzle = { 0, 0, 0, 0 };
uint32_t select1 = 0;
struct DxbcCompilerVsPart {
uint32_t functionId;
};
/**
* \brief Decoded instruction
*
* Stores all information about a single
* instruction, including its operands.
* \brief Pixel shader-specific structure
*/
struct DxbcInst {
DxbcOpcode opcode = DxbcOpcode::Nop;
DxbcOpcodeControl control = 0;
DxbcInstFormat format;
DxbcInstOp operands[DxbcMaxOperandCount];
};
/**
* \brief Vertex shader-specific data
*/
struct DxbcVsSpecifics {
uint32_t functionId = 0;
};
/**
* \brief Pixel shader-specific data
*/
struct DxbcPsSpecifics {
uint32_t functionId = 0;
std::array<DxbcPointer, DxbcMaxInterfaceRegs> oregs;
struct DxbcCompilerPsPart {
uint32_t functionId;
std::array<DxbcVectorType, DxbcMaxInterfaceRegs> oTypes;
};
@ -150,12 +101,10 @@ namespace dxvk {
/**
* \brief Processes a single instruction
*
* \param [in] ins The instruction
* \returns An error code, or \c sOK
*/
DxbcError processInstruction(
const DxbcInstruction& ins);
void processInstruction(
const DxbcShaderInstruction& ins);
/**
* \brief Finalizes the shader
@ -184,12 +133,14 @@ namespace dxvk {
// v# registers as defined by the shader. The type of each
// of these inputs is either float4 or an array of float4.
std::array<uint32_t, DxbcMaxInterfaceRegs> m_vRegs;
std::vector<DxbcSvMapping> m_vMappings;
//////////////////////////////////////////////////////////
// o# registers as defined by the shader. In the fragment
// shader stage, these registers are typed by the signature,
// in all other stages, they are float4 registers or arrays.
std::array<uint32_t, DxbcMaxInterfaceRegs> m_oRegs;
std::vector<DxbcSvMapping> m_oMappings;
//////////////////////////////////////////////////////
// Shader resource variables. These provide access to
@ -198,12 +149,6 @@ namespace dxvk {
std::array<DxbcSampler, 16> m_samplers;
std::array<DxbcShaderResource, 128> m_textures;
////////////////////////////////////////////////////////
// Input/Output system value mappings. These will need
// to be set up before or after the main function runs.
std::vector<DxbcSvMapping> m_vSvs;
std::vector<DxbcSvMapping> m_oSvs;
///////////////////////////////////////////////////////////
// Array of input values. Since v# registers are indexable
// in DXBC, we need to copy them into an array first.
@ -221,190 +166,185 @@ namespace dxvk {
std::vector<uint32_t> m_entryPointInterfaces;
uint32_t m_entryPointId = 0;
////////////////////////////////////////
// Data structures for each shader type
DxbcVsSpecifics m_vs;
DxbcPsSpecifics m_ps;
///////////////////////////////////
// Shader-specific data structures
DxbcCompilerVsPart m_vs;
DxbcCompilerPsPart m_ps;
/////////////////////////////////////////////////////
// Shader interface and metadata declaration methods
void emitDclGlobalFlags(
const DxbcShaderInstruction& ins);
void emitDclTemps(
const DxbcShaderInstruction& ins);
void emitDclInterfaceReg(
const DxbcShaderInstruction& ins);
void emitDclInput(
uint32_t regIdx,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im);
void emitDclOutput(
uint32_t regIdx,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im);
void emitDclConstantBuffer(
const DxbcShaderInstruction& ins);
void emitDclSampler(
const DxbcShaderInstruction& ins);
void emitDclResource(
const DxbcShaderInstruction& ins);
//////////////////////////////
// Instruction class handlers
DxbcError handleDeclaration(
const DxbcInst& ins);
void emitVectorAlu(
const DxbcShaderInstruction& ins);
DxbcError handleControlFlow(
const DxbcInst& ins);
void emitVectorCmov(
const DxbcShaderInstruction& ins);
DxbcError handleTextureSample(
const DxbcInst& ins);
void emitVectorCmp(
const DxbcShaderInstruction& ins);
DxbcError handleVectorAlu(
const DxbcInst& ins);
void emitVectorDot(
const DxbcShaderInstruction& ins);
DxbcError handleVectorCmov(
const DxbcInst& ins);
void emitVectorImul(
const DxbcShaderInstruction& ins);
DxbcError handleVectorCmp(
const DxbcInst& ins);
void emitVectorSinCos(
const DxbcShaderInstruction& ins);
DxbcError handleVectorDot(
const DxbcInst& ins);
void emitSample(
const DxbcShaderInstruction& ins);
DxbcError handleVectorSinCos(
const DxbcInst& ins);
void emitRet(
const DxbcShaderInstruction& ins);
///////////////////////
// Declaration methods
DxbcError declareGlobalFlags(
const DxbcInst& ins);
DxbcError declareTemps(
const DxbcInst& ins);
/////////////////////////////////////////
// Generic register manipulation methods
DxbcRegisterValue emitRegisterBitcast(
DxbcRegisterValue srcValue,
DxbcScalarType dstType);
DxbcError declareInterfaceVar(
const DxbcInst& ins);
DxbcRegisterValue emitRegisterSwizzle(
DxbcRegisterValue value,
DxbcRegSwizzle swizzle,
DxbcRegMask writeMask);
DxbcError declareConstantBuffer(
const DxbcInst& ins);
DxbcRegisterValue emitRegisterExtract(
DxbcRegisterValue value,
DxbcRegMask mask);
DxbcError declareSampler(
const DxbcInst& ins);
DxbcRegisterValue emitRegisterInsert(
DxbcRegisterValue dstValue,
DxbcRegisterValue srcValue,
DxbcRegMask srcMask);
DxbcError declareResource(
const DxbcInst& ins);
DxbcRegisterValue emitRegisterExtend(
DxbcRegisterValue value,
uint32_t size);
DxbcError declareInputVar(
uint32_t regId,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im);
DxbcRegisterValue emitRegisterAbsolute(
DxbcRegisterValue value);
DxbcError declareOutputVar(
uint32_t regId,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im);
DxbcRegisterValue emitRegisterNegate(
DxbcRegisterValue value);
////////////////////////////////////
// Register manipulation operations
DxbcValue bitcastReg(
const DxbcValue& src,
DxbcScalarType type);
DxbcRegisterValue emitSrcOperandModifiers(
DxbcRegisterValue value,
DxbcRegModifiers modifiers);
DxbcValue insertReg(
const DxbcValue& dst,
const DxbcValue& src,
DxbcRegMask mask);
DxbcRegisterValue emitDstOperandModifiers(
DxbcRegisterValue value,
DxbcOpModifiers modifiers);
DxbcValue extractReg(
const DxbcValue& src,
DxbcRegMask mask);
////////////////////////
// Address load methods
DxbcRegisterPointer emitGetTempPtr(
const DxbcRegister& operand);
DxbcValue swizzleReg(
const DxbcValue& src,
const DxbcRegSwizzle& swizzle,
DxbcRegMask mask);
DxbcRegisterPointer emitGetInputPtr(
const DxbcRegister& operand);
DxbcValue regVector(
const DxbcValue& src,
uint32_t size);
DxbcRegisterPointer emitGetOutputPtr(
const DxbcRegister& operand);
DxbcValue extendReg(
const DxbcValue& src,
uint32_t size);
DxbcRegisterPointer emitGetConstBufPtr(
const DxbcRegister& operand);
////////////////////////////
// Operand modifier methods
DxbcValue applyOperandModifiers(
DxbcValue value,
DxbcOperandModifiers modifiers);
DxbcRegisterPointer emitGetOperandPtr(
const DxbcRegister& operand);
DxbcValue applyResultModifiers(
DxbcValue value,
DxbcOpcodeControl control);
//////////////////////////////
// Operand load/store methods
DxbcRegisterValue emitIndexLoad(
DxbcRegIndex index);
/////////////////////////
// Load/Store operations
DxbcValue loadOp(
const DxbcInstOp& srcOp,
DxbcRegMask srcMask,
DxbcScalarType dstType);
DxbcRegisterValue emitValueLoad(
DxbcRegisterPointer ptr);
DxbcValue loadImm32(
const DxbcInstOp& srcOp,
DxbcRegMask srcMask,
DxbcScalarType dstType);
void emitValueStore(
DxbcRegisterPointer ptr,
DxbcRegisterValue value,
DxbcRegMask writeMask);
DxbcValue loadRegister(
const DxbcInstOp& srcOp,
DxbcRegMask srcMask,
DxbcScalarType dstType);
DxbcRegisterValue emitRegisterLoad(
const DxbcRegister& reg,
DxbcRegMask writeMask);
void storeOp(
const DxbcInstOp& dstOp,
const DxbcValue& srcValue);
DxbcValue loadPtr(
const DxbcPointer& ptr);
void storePtr(
const DxbcPointer& ptr,
const DxbcValue& value,
DxbcRegMask mask);
DxbcValue loadIndex(
const DxbcInstOpIndex& idx);
///////////////////////////
// Operand pointer methods
DxbcPointer getOperandPtr(
const DxbcInstOp& op);
DxbcPointer getConstantBufferPtr(
const DxbcInstOp& op);
/////////////////////////////////
// Shader initialization methods
void beginVertexShader(const Rc<DxbcIsgn>& isgn);
void beginPixelShader (const Rc<DxbcIsgn>& osgn);
void emitRegisterStore(
const DxbcRegister& reg,
DxbcRegisterValue value);
/////////////////////////////
// Input preparation methods
void prepareVertexInputs();
void preparePixelInputs();
void emitVsInputSetup();
void emitPsInputSetup();
//////////////////////////////
// Output preparation methods
void prepareVertexOutputs();
void preparePixelOutputs();
void emitVsOutputSetup();
void emitPsOutputSetup();
/////////////////////////////////
// Shader initialization methods
void emitVsInit();
void emitPsInit();
///////////////////////////////
// Shader finalization methods
void endVertexShader();
void endPixelShader();
void emitVsFinalize();
void emitPsFinalize();
///////////////////////////////
// Variable definition methods
uint32_t emitNewVariable(
const DxbcRegisterInfo& info);
///////////////////////////
// Type definition methods
uint32_t definePerVertexBlock();
uint32_t getScalarTypeId(
DxbcScalarType type);
uint32_t defineScalarType(
DxbcScalarType componentType);
uint32_t getVectorTypeId(
const DxbcVectorType& type);
uint32_t defineVectorType(
DxbcScalarType componentType,
uint32_t componentCount);
uint32_t getPointerTypeId(
const DxbcRegisterInfo& type);
uint32_t definePointerType(
DxbcScalarType componentType,
uint32_t componentCount,
spv::StorageClass storageClass);
/////////////////////////
// DXBC decoding methods
DxbcError parseInstruction(
const DxbcInstruction& ins,
DxbcInst& out);
uint32_t getPerVertexBlockId();
};

View File

@ -1,1600 +0,0 @@
#include "dxbc_compiler_2.h"
namespace dxvk {
constexpr uint32_t PerVertex_Position = 0;
constexpr uint32_t PerVertex_PointSize = 1;
constexpr uint32_t PerVertex_CullDist = 2;
constexpr uint32_t PerVertex_ClipDist = 3;
DxbcCompiler2::DxbcCompiler2(
const DxbcProgramVersion& version,
const Rc<DxbcIsgn>& isgn,
const Rc<DxbcIsgn>& osgn)
: m_version (version),
m_isgn (isgn),
m_osgn (osgn) {
// Declare an entry point ID. We'll need it during the
// initialization phase where the execution mode is set.
m_entryPointId = m_module.allocateId();
// Set the memory model. This is the same for all shaders.
m_module.setMemoryModel(
spv::AddressingModelLogical,
spv::MemoryModelGLSL450);
// Make sure our interface registers are clear
for (uint32_t i = 0; i < DxbcMaxInterfaceRegs; i++) {
m_ps.oTypes.at(i).ctype = DxbcScalarType::Float32;
m_ps.oTypes.at(i).ccount = 0;
m_vRegs.at(i) = 0;
m_oRegs.at(i) = 0;
}
// Initialize the shader module with capabilities
// etc. Each shader type has its own peculiarities.
switch (m_version.type()) {
case DxbcProgramType::VertexShader: this->emitVsInit(); break;
case DxbcProgramType::PixelShader: this->emitPsInit(); break;
default: throw DxvkError("DxbcCompiler: Unsupported program type");
}
}
DxbcCompiler2::~DxbcCompiler2() {
}
void DxbcCompiler2::processInstruction(const DxbcShaderInstruction& ins) {
switch (ins.op) {
case DxbcOpcode::DclGlobalFlags:
return this->emitDclGlobalFlags(ins);
case DxbcOpcode::DclTemps:
return this->emitDclTemps(ins);
case DxbcOpcode::DclInput:
case DxbcOpcode::DclInputSgv:
case DxbcOpcode::DclInputSiv:
case DxbcOpcode::DclInputPs:
case DxbcOpcode::DclInputPsSgv:
case DxbcOpcode::DclInputPsSiv:
case DxbcOpcode::DclOutput:
case DxbcOpcode::DclOutputSgv:
case DxbcOpcode::DclOutputSiv:
return this->emitDclInterfaceReg(ins);
case DxbcOpcode::DclConstantBuffer:
return this->emitDclConstantBuffer(ins);
case DxbcOpcode::DclSampler:
return this->emitDclSampler(ins);
case DxbcOpcode::DclResource:
return this->emitDclResource(ins);
case DxbcOpcode::Add:
case DxbcOpcode::Div:
case DxbcOpcode::Exp:
case DxbcOpcode::Log:
case DxbcOpcode::Mad:
case DxbcOpcode::Max:
case DxbcOpcode::Min:
case DxbcOpcode::Mul:
case DxbcOpcode::Mov:
case DxbcOpcode::Rsq:
case DxbcOpcode::Sqrt:
case DxbcOpcode::IAdd:
case DxbcOpcode::IMad:
case DxbcOpcode::IMax:
case DxbcOpcode::IMin:
case DxbcOpcode::INeg:
return this->emitVectorAlu(ins);
case DxbcOpcode::Movc:
return this->emitVectorCmov(ins);
case DxbcOpcode::Eq:
case DxbcOpcode::Ge:
case DxbcOpcode::Lt:
case DxbcOpcode::Ne:
case DxbcOpcode::IEq:
case DxbcOpcode::IGe:
case DxbcOpcode::ILt:
case DxbcOpcode::INe:
return this->emitVectorCmp(ins);
case DxbcOpcode::Dp2:
case DxbcOpcode::Dp3:
case DxbcOpcode::Dp4:
return this->emitVectorDot(ins);
case DxbcOpcode::IMul:
return this->emitVectorImul(ins);
case DxbcOpcode::SinCos:
return this->emitVectorSinCos(ins);
case DxbcOpcode::Sample:
return this->emitSample(ins);
case DxbcOpcode::Ret:
return this->emitRet(ins);
default:
Logger::warn(
str::format("DxbcCompiler: Unhandled opcode: ",
ins.op));
}
}
Rc<DxvkShader> DxbcCompiler2::finalize() {
// Define the actual 'main' function of the shader
m_module.functionBegin(
m_module.defVoidType(),
m_entryPointId,
m_module.defFunctionType(
m_module.defVoidType(), 0, nullptr),
spv::FunctionControlMaskNone);
m_module.opLabel(m_module.allocateId());
// Depending on the shader type, this will prepare
// input registers, call various shader functions
// and write back the output registers.
switch (m_version.type()) {
case DxbcProgramType::VertexShader: this->emitVsFinalize(); break;
case DxbcProgramType::PixelShader: this->emitPsFinalize(); break;
default: throw DxvkError("DxbcCompiler: Unsupported program type");
}
// End main function
m_module.opReturn();
m_module.functionEnd();
// Declare the entry point, we now have all the
// information we need, including the interfaces
m_module.addEntryPoint(m_entryPointId,
m_version.executionModel(), "main",
m_entryPointInterfaces.size(),
m_entryPointInterfaces.data());
m_module.setDebugName(m_entryPointId, "main");
// Create the shader module object
return new DxvkShader(
m_version.shaderStage(),
m_resourceSlots.size(),
m_resourceSlots.data(),
m_module.compile());
}
void DxbcCompiler2::emitDclGlobalFlags(const DxbcShaderInstruction& ins) {
// TODO implement properly
}
void DxbcCompiler2::emitDclTemps(const DxbcShaderInstruction& ins) {
// dcl_temps has one operand:
// (imm0) Number of temp registers
const uint32_t oldCount = m_rRegs.size();
const uint32_t newCount = ins.imm[0].u32;
if (newCount > oldCount) {
m_rRegs.resize(newCount);
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassPrivate;
for (uint32_t i = oldCount; i < newCount; i++) {
const uint32_t varId = this->emitNewVariable(info);
m_module.setDebugName(varId, str::format("r", i).c_str());
m_rRegs.at(i) = varId;
}
}
}
void DxbcCompiler2::emitDclInterfaceReg(const DxbcShaderInstruction& ins) {
// dcl_input and dcl_output instructions
// have the following operands:
// (dst0) The register to declare
// (imm0) The system value (optional)
uint32_t regDim = 0;
uint32_t regIdx = 0;
// In the vertex and fragment shader stage, the
// operand indices will have the following format:
// (0) Register index
//
// In other stages, the input and output registers
// may be declared as arrays of a fixed size:
// (0) Array length
// (1) Register index
if (ins.dst[0].idxDim == 2) {
regDim = ins.dst[0].idx[0].offset;
regIdx = ins.dst[0].idx[1].offset;
} else if (ins.dst[0].idxDim == 1) {
regIdx = ins.dst[0].idx[0].offset;
} else {
Logger::err(str::format(
"DxbcCompiler: ", ins.op,
": Invalid index dimension"));
return;
}
// This declaration may map an output register to a system
// value. If that is the case, the system value type will
// be stored in the second operand.
const bool hasSv =
ins.op == DxbcOpcode::DclInputSgv
|| ins.op == DxbcOpcode::DclInputSiv
|| ins.op == DxbcOpcode::DclInputPsSgv
|| ins.op == DxbcOpcode::DclInputPsSiv
|| ins.op == DxbcOpcode::DclOutputSgv
|| ins.op == DxbcOpcode::DclOutputSiv;
DxbcSystemValue sv = DxbcSystemValue::None;
if (hasSv)
sv = static_cast<DxbcSystemValue>(ins.imm[0].u32);
// In the pixel shader, inputs are declared with an
// interpolation mode that is part of the op token.
const bool hasInterpolationMode =
ins.op == DxbcOpcode::DclInputPs
|| ins.op == DxbcOpcode::DclInputPsSiv;
DxbcInterpolationMode im = DxbcInterpolationMode::Undefined;
if (hasInterpolationMode)
im = ins.controls.interpolation;
// Declare the actual input/output variable
switch (ins.op) {
case DxbcOpcode::DclInput:
case DxbcOpcode::DclInputSgv:
case DxbcOpcode::DclInputSiv:
case DxbcOpcode::DclInputPs:
case DxbcOpcode::DclInputPsSgv:
case DxbcOpcode::DclInputPsSiv:
this->emitDclInput(regIdx, regDim, ins.dst[0].mask, sv, im);
break;
case DxbcOpcode::DclOutput:
case DxbcOpcode::DclOutputSgv:
case DxbcOpcode::DclOutputSiv:
this->emitDclOutput(regIdx, regDim, ins.dst[0].mask, sv, im);
break;
default:
Logger::err(str::format(
"DxbcCompiler: Unexpected opcode: ",
ins.op));
}
}
void DxbcCompiler2::emitDclInput(
uint32_t regIdx,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im) {
if (regDim != 0) {
Logger::err("DxbcCompiler: Input arrays not yet supported");
return;
}
// Avoid declaring the same variable multiple times.
// This may happen when multiple system values are
// mapped to different parts of the same register.
if (m_vRegs.at(regIdx) == 0) {
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassInput;
const uint32_t varId = this->emitNewVariable(info);
m_module.decorateLocation(varId, regIdx);
m_module.setDebugName(varId, str::format("v", regIdx).c_str());
m_entryPointInterfaces.push_back(varId);
m_vRegs.at(regIdx) = varId;
// Interpolation mode, used in pixel shaders
if (im == DxbcInterpolationMode::Constant)
m_module.decorate(varId, spv::DecorationFlat);
if (im == DxbcInterpolationMode::LinearCentroid
|| im == DxbcInterpolationMode::LinearNoPerspectiveCentroid)
m_module.decorate(varId, spv::DecorationCentroid);
if (im == DxbcInterpolationMode::LinearNoPerspective
|| im == DxbcInterpolationMode::LinearNoPerspectiveCentroid
|| im == DxbcInterpolationMode::LinearNoPerspectiveSample)
m_module.decorate(varId, spv::DecorationNoPerspective);
if (im == DxbcInterpolationMode::LinearSample
|| im == DxbcInterpolationMode::LinearNoPerspectiveSample)
m_module.decorate(varId, spv::DecorationSample);
}
// Add a new system value mapping if needed
// TODO declare SV if necessary
if (sv != DxbcSystemValue::None)
m_vMappings.push_back({ regIdx, regMask, sv });
}
void DxbcCompiler2::emitDclOutput(
uint32_t regIdx,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im) {
if (regDim != 0) {
Logger::err("DxbcCompiler: Output arrays not yet supported");
return;
}
// Avoid declaring the same variable multiple times.
// This may happen when multiple system values are
// mapped to different parts of the same register.
if (m_oRegs.at(regIdx) == 0) {
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassOutput;
const uint32_t varId = this->emitNewVariable(info);
m_module.decorateLocation(varId, regIdx);
m_module.setDebugName(varId, str::format("o", regIdx).c_str());
m_entryPointInterfaces.push_back(varId);
m_oRegs.at(regIdx) = varId;
}
// Add a new system value mapping if needed
// TODO declare SV if necessary
if (sv != DxbcSystemValue::None)
m_oMappings.push_back({ regIdx, regMask, sv });
}
void DxbcCompiler2::emitDclConstantBuffer(const DxbcShaderInstruction& ins) {
// dcl_constant_buffer has one operand with two indices:
// (0) Constant buffer register ID (cb#)
// (1) Number of constants in the buffer
const uint32_t bufferId = ins.dst[0].idx[0].offset;
const uint32_t elementCount = ins.dst[0].idx[1].offset;
// Uniform buffer data is stored as a fixed-size array
// of 4x32-bit vectors. SPIR-V requires explicit strides.
const uint32_t arrayType = m_module.defArrayTypeUnique(
getVectorTypeId({ DxbcScalarType::Float32, 4 }),
m_module.constu32(elementCount));
m_module.decorateArrayStride(arrayType, 16);
// SPIR-V requires us to put that array into a
// struct and decorate that struct as a block.
const uint32_t structType = m_module.defStructTypeUnique(1, &arrayType);
m_module.memberDecorateOffset(structType, 0, 0);
m_module.decorateBlock(structType);
// Variable that we'll use to access the buffer
const uint32_t varId = m_module.newVar(
m_module.defPointerType(structType, spv::StorageClassUniform),
spv::StorageClassUniform);
m_module.setDebugName(varId,
str::format("cb", bufferId).c_str());
m_constantBuffers.at(bufferId).varId = varId;
m_constantBuffers.at(bufferId).size = elementCount;
// Compute the DXVK binding slot index for the buffer.
// D3D11 needs to bind the actual buffers to this slot.
const uint32_t bindingId = computeResourceSlotId(
m_version.type(), DxbcBindingType::ConstantBuffer,
bufferId);
m_module.decorateDescriptorSet(varId, 0);
m_module.decorateBinding(varId, bindingId);
// Store descriptor info for the shader interface
DxvkResourceSlot resource;
resource.slot = bindingId;
resource.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
m_resourceSlots.push_back(resource);
}
void DxbcCompiler2::emitDclSampler(const DxbcShaderInstruction& ins) {
// dclSampler takes one operand:
// (dst0) The sampler register to declare
// TODO implement sampler mode (default / comparison / mono)
const uint32_t samplerId = ins.dst[0].idx[0].offset;
// The sampler type is opaque, but we still have to
// define a pointer and a variable in oder to use it
const uint32_t samplerType = m_module.defSamplerType();
const uint32_t samplerPtrType = m_module.defPointerType(
samplerType, spv::StorageClassUniformConstant);
// Define the sampler variable
const uint32_t varId = m_module.newVar(samplerPtrType,
spv::StorageClassUniformConstant);
m_module.setDebugName(varId,
str::format("s", samplerId).c_str());
m_samplers.at(samplerId).varId = varId;
m_samplers.at(samplerId).typeId = samplerType;
// Compute binding slot index for the sampler
const uint32_t bindingId = computeResourceSlotId(
m_version.type(), DxbcBindingType::ImageSampler, samplerId);
m_module.decorateDescriptorSet(varId, 0);
m_module.decorateBinding(varId, bindingId);
// Store descriptor info for the shader interface
DxvkResourceSlot resource;
resource.slot = bindingId;
resource.type = VK_DESCRIPTOR_TYPE_SAMPLER;
m_resourceSlots.push_back(resource);
}
void DxbcCompiler2::emitDclResource(const DxbcShaderInstruction& ins) {
// dclResource takes two operands:
// (dst0) The resource register ID
// (imm0) The resource return type
const uint32_t registerId = ins.dst[0].idx[0].offset;
// Defines the type of the resource (texture2D, ...)
const DxbcResourceDim resourceType = ins.controls.resourceDim;
// Defines the type of a read operation. DXBC has the ability
// to define four different types whereas SPIR-V only allows
// one, but in practice this should not be much of a problem.
auto xType = static_cast<DxbcResourceReturnType>(
bit::extract(ins.imm[0].u32, 0, 3));
auto yType = static_cast<DxbcResourceReturnType>(
bit::extract(ins.imm[0].u32, 4, 7));
auto zType = static_cast<DxbcResourceReturnType>(
bit::extract(ins.imm[0].u32, 8, 11));
auto wType = static_cast<DxbcResourceReturnType>(
bit::extract(ins.imm[0].u32, 12, 15));
if ((xType != yType) || (xType != zType) || (xType != wType))
Logger::warn("DxbcCompiler: dcl_resource: Ignoring resource return types");
// Declare the actual sampled type
uint32_t sampledTypeId = 0;
switch (xType) {
case DxbcResourceReturnType::Float: sampledTypeId = m_module.defFloatType(32); break;
case DxbcResourceReturnType::Sint: sampledTypeId = m_module.defIntType (32, 1); break;
case DxbcResourceReturnType::Uint: sampledTypeId = m_module.defIntType (32, 0); break;
default: throw DxvkError(str::format("DxbcCompiler: Invalid sampled type: ", xType));
}
// Declare the resource type
uint32_t textureTypeId = 0;
switch (resourceType) {
case DxbcResourceDim::Texture1D:
textureTypeId = m_module.defImageType(
sampledTypeId, spv::Dim1D, 0, 0, 0, 1,
spv::ImageFormatUnknown);
break;
case DxbcResourceDim::Texture1DArr:
textureTypeId = m_module.defImageType(
sampledTypeId, spv::Dim1D, 0, 1, 0, 1,
spv::ImageFormatUnknown);
break;
case DxbcResourceDim::Texture2D:
textureTypeId = m_module.defImageType(
sampledTypeId, spv::Dim2D, 0, 0, 0, 1,
spv::ImageFormatUnknown);
break;
case DxbcResourceDim::Texture2DArr:
textureTypeId = m_module.defImageType(
sampledTypeId, spv::Dim2D, 0, 1, 0, 1,
spv::ImageFormatUnknown);
break;
case DxbcResourceDim::Texture3D:
textureTypeId = m_module.defImageType(
sampledTypeId, spv::Dim3D, 0, 0, 0, 1,
spv::ImageFormatUnknown);
break;
case DxbcResourceDim::TextureCube:
textureTypeId = m_module.defImageType(
sampledTypeId, spv::DimCube, 0, 0, 0, 1,
spv::ImageFormatUnknown);
break;
case DxbcResourceDim::TextureCubeArr:
textureTypeId = m_module.defImageType(
sampledTypeId, spv::DimCube, 0, 1, 0, 1,
spv::ImageFormatUnknown);
break;
default:
throw DxvkError(str::format("DxbcCompiler: Unsupported resource type: ", resourceType));
}
const uint32_t resourcePtrType = m_module.defPointerType(
textureTypeId, spv::StorageClassUniformConstant);
const uint32_t varId = m_module.newVar(resourcePtrType,
spv::StorageClassUniformConstant);
m_module.setDebugName(varId,
str::format("t", registerId).c_str());
m_textures.at(registerId).varId = varId;
m_textures.at(registerId).sampledTypeId = sampledTypeId;
m_textures.at(registerId).textureTypeId = textureTypeId;
// Compute the DXVK binding slot index for the resource.
// D3D11 needs to bind the actual resource to this slot.
const uint32_t bindingId = computeResourceSlotId(
m_version.type(), DxbcBindingType::ShaderResource, registerId);
m_module.decorateDescriptorSet(varId, 0);
m_module.decorateBinding(varId, bindingId);
// Store descriptor info for the shader interface
DxvkResourceSlot resource;
resource.slot = bindingId;
resource.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
m_resourceSlots.push_back(resource);
}
void DxbcCompiler2::emitVectorAlu(const DxbcShaderInstruction& ins) {
std::array<DxbcRegisterValue, DxbcMaxOperandCount> src;
for (uint32_t i = 0; i < ins.srcCount; i++)
src.at(i) = emitRegisterLoad(ins.src[i], ins.dst[0].mask);
DxbcRegisterValue dst;
dst.type.ctype = ins.dst[0].dataType;
dst.type.ccount = ins.dst[0].mask.setCount();
const uint32_t typeId = getVectorTypeId(dst.type);
switch (ins.op) {
case DxbcOpcode::Add:
dst.id = m_module.opFAdd(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Div:
dst.id = m_module.opFDiv(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Exp:
dst.id = m_module.opExp2(
typeId, src.at(0).id);
break;
case DxbcOpcode::Log:
dst.id = m_module.opLog2(
typeId, src.at(0).id);
break;
case DxbcOpcode::Mad:
dst.id = m_module.opFFma(typeId,
src.at(0).id, src.at(1).id, src.at(2).id);
break;
case DxbcOpcode::Max:
dst.id = m_module.opFMax(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Min:
dst.id = m_module.opFMin(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Mul:
dst.id = m_module.opFMul(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Mov:
dst.id = src.at(0).id;
break;
case DxbcOpcode::Sqrt:
dst.id = m_module.opSqrt(
typeId, src.at(0).id);
break;
case DxbcOpcode::Rsq:
dst.id = m_module.opInverseSqrt(
typeId, src.at(0).id);
break;
case DxbcOpcode::IAdd:
dst.id = m_module.opIAdd(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::IMad:
dst.id = m_module.opIAdd(typeId,
m_module.opIMul(typeId,
src.at(0).id, src.at(1).id),
src.at(2).id);
break;
case DxbcOpcode::IMax:
dst.id = m_module.opSMax(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::IMin:
dst.id = m_module.opSMin(typeId,
src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::INeg:
dst.id = m_module.opSNegate(
typeId, src.at(0).id);
break;
default:
Logger::warn(str::format(
"DxbcCompiler: Unhandled instruction: ",
ins.op));
return;
}
// Store computed value
dst = emitDstOperandModifiers(dst, ins.modifiers);
emitRegisterStore(ins.dst[0], dst);
}
void DxbcCompiler2::emitVectorCmov(const DxbcShaderInstruction& ins) {
// movc has four operands:
// (dst0) The destination register
// (src0) The condition vector
// (src0) Vector to select from if the condition is not 0
// (src0) Vector to select from if the condition is 0
const DxbcRegisterValue condition = emitRegisterLoad(ins.src[0], ins.dst[0].mask);
const DxbcRegisterValue selectTrue = emitRegisterLoad(ins.src[1], ins.dst[0].mask);
const DxbcRegisterValue selectFalse = emitRegisterLoad(ins.src[2], ins.dst[0].mask);
const uint32_t componentCount = ins.dst[0].mask.setCount();
// We'll compare against a vector of zeroes to generate a
// boolean vector, which in turn will be used by OpSelect
uint32_t zeroType = m_module.defIntType(32, 0);
uint32_t boolType = m_module.defBoolType();
uint32_t zero = m_module.constu32(0);
if (componentCount > 1) {
zeroType = m_module.defVectorType(zeroType, componentCount);
boolType = m_module.defVectorType(boolType, componentCount);
const std::array<uint32_t, 4> zeroVec = { zero, zero, zero, zero };
zero = m_module.constComposite(zeroType, componentCount, zeroVec.data());
}
// Use the component mask to select the vector components
DxbcRegisterValue result;
result.type.ctype = ins.dst[0].dataType;
result.type.ccount = componentCount;
result.id = m_module.opSelect(
getVectorTypeId(result.type),
m_module.opINotEqual(boolType, condition.id, zero),
selectTrue.id, selectFalse.id);
// Apply result modifiers to floating-point results
result = emitDstOperandModifiers(result, ins.modifiers);
emitRegisterStore(ins.dst[0], result);
}
void DxbcCompiler2::emitVectorCmp(const DxbcShaderInstruction& ins) {
// Compare instructions have three operands:
// (dst0) The destination register
// (src0) The first vector to compare
// (src1) The second vector to compare
const std::array<DxbcRegisterValue, 2> src = {
emitRegisterLoad(ins.src[0], ins.dst[0].mask),
emitRegisterLoad(ins.src[1], ins.dst[0].mask),
};
const uint32_t componentCount = ins.dst[0].mask.setCount();
// Condition, which is a boolean vector used
// to select between the ~0u and 0u vectors.
uint32_t condition = 0;
uint32_t conditionType = m_module.defBoolType();
if (componentCount > 1)
conditionType = m_module.defVectorType(conditionType, componentCount);
switch (ins.op) {
case DxbcOpcode::Eq:
condition = m_module.opFOrdEqual(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Ge:
condition = m_module.opFOrdGreaterThanEqual(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Lt:
condition = m_module.opFOrdLessThan(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::Ne:
condition = m_module.opFOrdNotEqual(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::IEq:
condition = m_module.opIEqual(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::IGe:
condition = m_module.opSGreaterThanEqual(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::ILt:
condition = m_module.opSLessThan(
conditionType, src.at(0).id, src.at(1).id);
break;
case DxbcOpcode::INe:
condition = m_module.opINotEqual(
conditionType, src.at(0).id, src.at(1).id);
break;
default:
Logger::warn(str::format(
"DxbcCompiler: Unhandled instruction: ",
ins.op));
return;
}
// Generate constant vectors for selection
uint32_t sFalse = m_module.constu32( 0u);
uint32_t sTrue = m_module.constu32(~0u);
DxbcRegisterValue result;
result.type.ctype = DxbcScalarType::Uint32;
result.type.ccount = componentCount;
const uint32_t typeId = getVectorTypeId(result.type);
if (componentCount > 1) {
const std::array<uint32_t, 4> vFalse = { sFalse, sFalse, sFalse, sFalse };
const std::array<uint32_t, 4> vTrue = { sTrue, sTrue, sTrue, sTrue };
sFalse = m_module.constComposite(typeId, componentCount, vFalse.data());
sTrue = m_module.constComposite(typeId, componentCount, vTrue .data());
}
// Perform component-wise mask selection
// based on the condition evaluated above.
result.id = m_module.opSelect(
typeId, condition, sTrue, sFalse);
emitRegisterStore(ins.dst[0], result);
}
void DxbcCompiler2::emitVectorDot(const DxbcShaderInstruction& ins) {
const DxbcRegMask srcMask(true,
ins.op >= DxbcOpcode::Dp2,
ins.op >= DxbcOpcode::Dp3,
ins.op >= DxbcOpcode::Dp4);
const std::array<DxbcRegisterValue, 2> src = {
emitRegisterLoad(ins.src[0], srcMask),
emitRegisterLoad(ins.src[1], srcMask),
};
DxbcRegisterValue dst;
dst.type.ctype = ins.dst[0].dataType;
dst.type.ccount = 1;
dst.id = m_module.opDot(
getVectorTypeId(dst.type),
src.at(0).id,
src.at(1).id);
dst = emitDstOperandModifiers(dst, ins.modifiers);
emitRegisterStore(ins.dst[0], dst);
}
void DxbcCompiler2::emitVectorImul(const DxbcShaderInstruction& ins) {
// imul and umul have four operands:
// (dst0) High destination register
// (dst1) Low destination register
// (src0) The first vector to compare
// (src1) The second vector to compare
if (ins.dst[0].type == DxbcOperandType::Null) {
if (ins.dst[1].type == DxbcOperandType::Null)
return;
// If dst0 is NULL, this instruction behaves just
// like any other three -operand ALU instruction
const std::array<DxbcRegisterValue, 2> src = {
emitRegisterLoad(ins.src[0], ins.dst[1].mask),
emitRegisterLoad(ins.src[1], ins.dst[1].mask),
};
DxbcRegisterValue result;
result.type.ctype = ins.dst[1].dataType;
result.type.ccount = ins.dst[1].mask.setCount();
result.id = m_module.opIMul(
getVectorTypeId(result.type),
src.at(0).id, src.at(1).id);
result = emitDstOperandModifiers(result, ins.modifiers);
emitRegisterStore(ins.dst[1], result);
} else {
// TODO implement this
Logger::warn("DxbcCompiler: Extended Imul not yet supported");
}
}
void DxbcCompiler2::emitVectorSinCos(const DxbcShaderInstruction& ins) {
// sincos has three operands:
// (dst0) Destination register for sin(x)
// (dst1) Destination register for cos(x)
// (src0) Source operand x
// Load source operand as 32-bit float vector.
const DxbcRegisterValue srcValue = emitRegisterLoad(
ins.src[0], DxbcRegMask(true, true, true, true));
// Either output may be DxbcOperandType::Null, in
// which case we don't have to generate any code.
if (ins.dst[0].type != DxbcOperandType::Null) {
const DxbcRegisterValue sinInput =
emitRegisterExtract(srcValue, ins.dst[0].mask);
DxbcRegisterValue sin;
sin.type = sinInput.type;
sin.id = m_module.opSin(
getVectorTypeId(sin.type),
sinInput.id);
emitRegisterStore(ins.dst[0], sin);
}
if (ins.dst[1].type != DxbcOperandType::Null) {
const DxbcRegisterValue cosInput =
emitRegisterExtract(srcValue, ins.dst[1].mask);
DxbcRegisterValue cos;
cos.type = cosInput.type;
cos.id = m_module.opSin(
getVectorTypeId(cos.type),
cosInput.id);
emitRegisterStore(ins.dst[1], cos);
}
}
void DxbcCompiler2::emitSample(
const DxbcShaderInstruction& ins) {
// TODO support address offset
// TODO support more sample ops
// sample has four operands:
// (dst0) The destination register
// (src0) Texture coordinates
// (src1) The texture itself
// (src2) The sampler object
const DxbcRegister& texCoordReg = ins.src[0];
const DxbcRegister& textureReg = ins.src[1];
const DxbcRegister& samplerReg = ins.src[2];
// Texture and sampler register IDs
const uint32_t textureId = textureReg.idx[0].offset;
const uint32_t samplerId = samplerReg.idx[0].offset;
// Load the texture coordinates. SPIR-V allows these
// to be float4 even if not all components are used.
const DxbcRegisterValue coord = emitRegisterLoad(
texCoordReg, DxbcRegMask(true, true, true, true));
// Combine the texture and the sampler into a sampled image
const uint32_t sampledImageType = m_module.defSampledImageType(
m_textures.at(textureId).textureTypeId);
const uint32_t sampledImageId = m_module.opSampledImage(
sampledImageType,
m_module.opLoad(
m_textures.at(textureId).textureTypeId,
m_textures.at(textureId).varId),
m_module.opLoad(
m_samplers.at(samplerId).typeId,
m_samplers.at(samplerId).varId));
// Sampling an image in SPIR-V always returns a four-component
// vector, so we need to declare the corresponding type here
// TODO infer sampled type properly
DxbcRegisterValue result;
result.type.ctype = DxbcScalarType::Float32;
result.type.ccount = 4;
result.id = m_module.opImageSampleImplicitLod(
getVectorTypeId(result.type),
sampledImageId, coord.id);
// Swizzle components using the texture swizzle
// and the destination operand's write mask
result = emitRegisterSwizzle(result,
textureReg.swizzle, ins.dst[0].mask);
emitRegisterStore(ins.dst[0], result);
}
void DxbcCompiler2::emitRet(const DxbcShaderInstruction& ins) {
// TODO implement properly
m_module.opReturn();
m_module.functionEnd();
}
DxbcRegisterValue DxbcCompiler2::emitRegisterBitcast(
DxbcRegisterValue srcValue,
DxbcScalarType dstType) {
if (srcValue.type.ctype == dstType)
return srcValue;
// TODO support 64-bit values
DxbcRegisterValue result;
result.type.ctype = dstType;
result.type.ccount = srcValue.type.ccount;
result.id = m_module.opBitcast(
getVectorTypeId(result.type),
srcValue.id);
return result;
}
DxbcRegisterValue DxbcCompiler2::emitRegisterSwizzle(
DxbcRegisterValue value,
DxbcRegSwizzle swizzle,
DxbcRegMask writeMask) {
std::array<uint32_t, 4> indices;
uint32_t dstIndex = 0;
for (uint32_t i = 0; i < value.type.ccount; i++) {
if (writeMask[i])
indices[dstIndex++] = swizzle[i];
}
// If the swizzle combined with the mask can be reduced
// to a no-op, we don't need to insert any instructions.
bool isIdentitySwizzle = dstIndex == value.type.ccount;
for (uint32_t i = 0; i < dstIndex && isIdentitySwizzle; i++)
isIdentitySwizzle &= indices[i] == i;
if (isIdentitySwizzle)
return value;
// Use OpCompositeExtract if the resulting vector contains
// only one component, and OpVectorShuffle if it is a vector.
DxbcRegisterValue result;
result.type.ctype = value.type.ctype;
result.type.ccount = dstIndex;
const uint32_t typeId = getVectorTypeId(result.type);
if (dstIndex == 1) {
result.id = m_module.opCompositeExtract(
typeId, value.id, 1, indices.data());
} else {
result.id = m_module.opVectorShuffle(
typeId, value.id, value.id,
dstIndex, indices.data());
}
return result;
}
DxbcRegisterValue DxbcCompiler2::emitRegisterExtract(
DxbcRegisterValue value,
DxbcRegMask mask) {
return emitRegisterSwizzle(value,
DxbcRegSwizzle(0, 1, 2, 3), mask);
}
DxbcRegisterValue DxbcCompiler2::emitRegisterInsert(
DxbcRegisterValue dstValue,
DxbcRegisterValue srcValue,
DxbcRegMask srcMask) {
DxbcRegisterValue result;
result.type = dstValue.type;
const uint32_t typeId = getVectorTypeId(result.type);
if (srcMask.setCount() == 0) {
// Nothing to do if the insertion mask is empty
result.id = dstValue.id;
} else if (dstValue.type.ccount == 1) {
// Both values are scalar, so the first component
// of the write mask decides which one to take.
result.id = srcMask[0] ? srcValue.id : dstValue.id;
} else if (srcValue.type.ccount == 1) {
// The source value is scalar. Since OpVectorShuffle
// requires both arguments to be vectors, we have to
// use OpCompositeInsert to modify the vector instead.
const uint32_t componentId = srcMask.firstSet();
result.id = m_module.opCompositeInsert(typeId,
srcValue.id, dstValue.id, 1, &componentId);
} else {
// Both arguments are vectors. We can determine which
// components to take from which vector and use the
// OpVectorShuffle instruction.
std::array<uint32_t, 4> components;
uint32_t srcComponentId = dstValue.type.ccount;
for (uint32_t i = 0; i < dstValue.type.ccount; i++)
components.at(i) = srcMask[i] ? srcComponentId++ : i;
result.id = m_module.opVectorShuffle(
typeId, dstValue.id, srcValue.id,
dstValue.type.ccount, components.data());
}
return result;
}
DxbcRegisterValue DxbcCompiler2::emitRegisterExtend(
DxbcRegisterValue value,
uint32_t size) {
if (size == 1)
return value;
std::array<uint32_t, 4> ids = {
value.id, value.id,
value.id, value.id,
};
DxbcRegisterValue result;
result.type.ctype = value.type.ctype;
result.type.ccount = size;
result.id = m_module.opCompositeConstruct(
getVectorTypeId(result.type),
size, ids.data());
return result;
}
DxbcRegisterValue DxbcCompiler2::emitRegisterAbsolute(
DxbcRegisterValue value) {
const uint32_t typeId = getVectorTypeId(value.type);
switch (value.type.ctype) {
case DxbcScalarType::Float32: value.id = m_module.opFAbs(typeId, value.id); break;
case DxbcScalarType::Sint32: value.id = m_module.opSAbs(typeId, value.id); break;
default: Logger::warn("DxbcCompiler: Cannot get absolute value for given type");
}
return value;
}
DxbcRegisterValue DxbcCompiler2::emitRegisterNegate(
DxbcRegisterValue value) {
const uint32_t typeId = getVectorTypeId(value.type);
switch (value.type.ctype) {
case DxbcScalarType::Float32: value.id = m_module.opFNegate(typeId, value.id); break;
case DxbcScalarType::Sint32: value.id = m_module.opSNegate(typeId, value.id); break;
default: Logger::warn("DxbcCompiler: Cannot negate given type");
}
return value;
}
DxbcRegisterValue DxbcCompiler2::emitSrcOperandModifiers(
DxbcRegisterValue value,
DxbcRegModifiers modifiers) {
if (modifiers.test(DxbcRegModifier::Abs))
value = emitRegisterAbsolute(value);
if (modifiers.test(DxbcRegModifier::Neg))
value = emitRegisterNegate(value);
return value;
}
DxbcRegisterValue DxbcCompiler2::emitDstOperandModifiers(
DxbcRegisterValue value,
DxbcOpModifiers modifiers) {
const uint32_t typeId = getVectorTypeId(value.type);
if (value.type.ctype == DxbcScalarType::Float32) {
// Saturating only makes sense on floats
if (modifiers.saturate) {
value.id = m_module.opFClamp(
typeId, value.id,
m_module.constf32(0.0f),
m_module.constf32(1.0f));
}
}
return value;
}
DxbcRegisterPointer DxbcCompiler2::emitGetTempPtr(
const DxbcRegister& operand) {
// r# regs are indexed as follows:
// (0) register index (immediate)
DxbcRegisterPointer result;
result.type.ctype = DxbcScalarType::Float32;
result.type.ccount = 4;
result.id = m_rRegs.at(operand.idx[0].offset);
return result;
}
DxbcRegisterPointer DxbcCompiler2::emitGetInputPtr(
const DxbcRegister& operand) {
// In the vertex and pixel stages,
// v# regs are indexed as follows:
// (0) register index (relative)
//
// In the tessellation and geometry
// stages, the index has two dimensions:
// (0) vertex index (relative)
// (1) register index (relative)
if (operand.idxDim != 1)
throw DxvkError("DxbcCompiler: 2D index for v# not yet supported");
// We don't support two-dimensional indices yet
const uint32_t registerId = operand.idx[0].offset;
DxbcRegisterPointer result;
result.type.ctype = DxbcScalarType::Float32;
result.type.ccount = 4;
result.id = m_vRegs.at(registerId);
return result;
}
DxbcRegisterPointer DxbcCompiler2::emitGetOutputPtr(
const DxbcRegister& operand) {
// Same index format as input registers, except that
// outputs cannot be accessed with a relative index.
if (operand.idxDim != 1)
throw DxvkError("DxbcCompiler: 2D index for o# not yet supported");
// We don't support two-dimensional indices yet
const uint32_t registerId = operand.idx[0].offset;
// In the pixel shader, output registers are typed,
// whereas they are float4 in all other stages.
if (m_version.type() == DxbcProgramType::PixelShader) {
DxbcRegisterPointer result;
result.type = m_ps.oTypes.at(registerId);
result.id = m_oRegs.at(registerId);
return result;
} else {
DxbcRegisterPointer result;
result.type.ctype = DxbcScalarType::Float32;
result.type.ccount = 4;
result.id = m_oRegs.at(registerId);
return result;
}
}
DxbcRegisterPointer DxbcCompiler2::emitGetConstBufPtr(
const DxbcRegister& operand) {
// Constant buffers take a two-dimensional index:
// (0) register index (immediate)
// (1) constant offset (relative)
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassUniform;
const uint32_t regId = operand.idx[0].offset;
const DxbcRegisterValue constId = emitIndexLoad(operand.idx[1]);
const uint32_t ptrTypeId = getPointerTypeId(info);
const std::array<uint32_t, 2> indices = {
m_module.consti32(0), constId.id
};
DxbcRegisterPointer result;
result.type = info.type;
result.id = m_module.opAccessChain(ptrTypeId,
m_constantBuffers.at(regId).varId,
indices.size(), indices.data());
return result;
}
DxbcRegisterPointer DxbcCompiler2::emitGetOperandPtr(
const DxbcRegister& operand) {
switch (operand.type) {
case DxbcOperandType::Temp:
return emitGetTempPtr(operand);
case DxbcOperandType::Input:
return emitGetInputPtr(operand);
case DxbcOperandType::Output:
return emitGetOutputPtr(operand);
case DxbcOperandType::ConstantBuffer:
return emitGetConstBufPtr(operand);
default:
throw DxvkError(str::format(
"DxbcCompiler: Unhandled operand type: ",
operand.type));
}
}
DxbcRegisterValue DxbcCompiler2::emitIndexLoad(
DxbcRegIndex index) {
if (index.relReg != nullptr) {
DxbcRegisterValue result = emitRegisterLoad(
*index.relReg, DxbcRegMask(true, false, false, false));
if (index.offset != 0) {
result.id = m_module.opIAdd(
getVectorTypeId(result.type), result.id,
m_module.consti32(index.offset));
}
return result;
} else {
DxbcRegisterValue result;
result.type.ctype = DxbcScalarType::Sint32;
result.type.ccount = 1;
result.id = m_module.consti32(index.offset);
return result;
}
}
DxbcRegisterValue DxbcCompiler2::emitValueLoad(
DxbcRegisterPointer ptr) {
DxbcRegisterValue result;
result.type = ptr.type;
result.id = m_module.opLoad(
getVectorTypeId(result.type),
ptr.id);
return result;
}
void DxbcCompiler2::emitValueStore(
DxbcRegisterPointer ptr,
DxbcRegisterValue value,
DxbcRegMask writeMask) {
// If the component types are not compatible,
// we need to bit-cast the source variable.
if (value.type.ctype != ptr.type.ctype)
value = emitRegisterBitcast(value, ptr.type.ctype);
// If the source value consists of only one component,
// it is stored in all components of the destination.
if (value.type.ccount == 1)
value = emitRegisterExtend(value, writeMask.setCount());
if (ptr.type.ccount == writeMask.setCount()) {
// Simple case: We write to the entire register
m_module.opStore(ptr.id, value.id);
} else {
// We only write to part of the destination
// register, so we need to load and modify it
DxbcRegisterValue tmp = emitValueLoad(ptr);
tmp = emitRegisterInsert(tmp, value, writeMask);
m_module.opStore(ptr.id, tmp.id);
}
}
DxbcRegisterValue DxbcCompiler2::emitRegisterLoad(
const DxbcRegister& reg,
DxbcRegMask writeMask) {
if (reg.type == DxbcOperandType::Imm32) {
DxbcRegisterValue result;
if (reg.componentCount == DxbcRegComponentCount::c1) {
// Create one single u32 constant
result.type.ctype = DxbcScalarType::Uint32;
result.type.ccount = 1;
result.id = m_module.constu32(reg.imm.u32_1);
} else if (reg.componentCount == DxbcRegComponentCount::c4) {
// Create a four-component u32 vector
std::array<uint32_t, 4> indices = {
m_module.constu32(reg.imm.u32_4[0]),
m_module.constu32(reg.imm.u32_4[1]),
m_module.constu32(reg.imm.u32_4[2]),
m_module.constu32(reg.imm.u32_4[3]),
};
result.type.ctype = DxbcScalarType::Uint32;
result.type.ccount = 4;
result.id = m_module.constComposite(
getVectorTypeId(result.type),
indices.size(), indices.data());
} else {
// Something went horribly wrong in the decoder or the shader is broken
throw DxvkError("DxbcCompiler: Invalid component count for immediate operand");
}
// Cast constants to the requested type
return emitRegisterBitcast(result, reg.dataType);
} else {
// Load operand from the operand pointer
DxbcRegisterPointer ptr = emitGetOperandPtr(reg);
DxbcRegisterValue result = emitValueLoad(ptr);
// Apply operand swizzle to the operand value
result = emitRegisterSwizzle(result, reg.swizzle, writeMask);
// Cast it to the requested type. We need to do
// this after the swizzling for 64-bit types.
result = emitRegisterBitcast(result, reg.dataType);
// Apply operand modifiers
result = emitSrcOperandModifiers(result, reg.modifiers);
return result;
}
}
void DxbcCompiler2::emitRegisterStore(
const DxbcRegister& reg,
DxbcRegisterValue value) {
emitValueStore(emitGetOperandPtr(reg), value, reg.mask);
}
void DxbcCompiler2::emitVsInputSetup() {
}
void DxbcCompiler2::emitPsInputSetup() {
}
void DxbcCompiler2::emitVsOutputSetup() {
for (const DxbcSvMapping& svMapping : m_oMappings) {
switch (svMapping.sv) {
case DxbcSystemValue::Position: {
DxbcRegisterInfo info;
info.type.ctype = DxbcScalarType::Float32;
info.type.ccount = 4;
info.sclass = spv::StorageClassOutput;
const uint32_t ptrTypeId = getPointerTypeId(info);
const uint32_t memberId = m_module.constu32(PerVertex_Position);
DxbcRegisterPointer dstPtr;
dstPtr.type = info.type;
dstPtr.id = m_module.opAccessChain(
ptrTypeId, m_perVertexOut, 1, &memberId);
DxbcRegisterPointer srcPtr;
srcPtr.type = info.type;
srcPtr.id = m_oRegs.at(svMapping.regId);
emitValueStore(dstPtr, emitValueLoad(srcPtr),
DxbcRegMask(true, true, true, true));
} break;
default:
Logger::warn(str::format(
"dxbc: Unhandled vertex sv output: ",
svMapping.sv));
}
}
}
void DxbcCompiler2::emitPsOutputSetup() {
}
void DxbcCompiler2::emitVsInit() {
m_module.enableCapability(spv::CapabilityShader);
m_module.enableCapability(spv::CapabilityClipDistance);
m_module.enableCapability(spv::CapabilityCullDistance);
// Declare the per-vertex output block. This is where
// the vertex shader will write the vertex position.
const uint32_t perVertexStruct = this->getPerVertexBlockId();
const uint32_t perVertexPointer = m_module.defPointerType(
perVertexStruct, spv::StorageClassOutput);
m_perVertexOut = m_module.newVar(
perVertexPointer, spv::StorageClassOutput);
m_entryPointInterfaces.push_back(m_perVertexOut);
m_module.setDebugName(m_perVertexOut, "vs_vertex_out");
// Main function of the vertex shader
m_vs.functionId = m_module.allocateId();
m_module.setDebugName(m_vs.functionId, "vs_main");
m_module.functionBegin(
m_module.defVoidType(),
m_vs.functionId,
m_module.defFunctionType(
m_module.defVoidType(), 0, nullptr),
spv::FunctionControlMaskNone);
m_module.opLabel(m_module.allocateId());
}
void DxbcCompiler2::emitPsInit() {
m_module.enableCapability(spv::CapabilityShader);
m_module.setOriginUpperLeft(m_entryPointId);
// Declare pixel shader outputs. According to the Vulkan
// documentation, they are required to match the type of
// the render target.
for (auto e = m_osgn->begin(); e != m_osgn->end(); e++) {
if (e->systemValue == DxbcSystemValue::None) {
DxbcRegisterInfo info;
info.type.ctype = e->componentType;
info.type.ccount = e->componentMask.setCount();
info.sclass = spv::StorageClassOutput;
const uint32_t varId = emitNewVariable(info);
m_module.decorateLocation(varId, e->registerId);
m_module.setDebugName(varId, str::format("o", e->registerId).c_str());
m_entryPointInterfaces.push_back(varId);
m_oRegs.at(e->registerId) = varId;
m_ps.oTypes.at(e->registerId) = info.type;
}
}
// Main function of the pixel shader
m_ps.functionId = m_module.allocateId();
m_module.setDebugName(m_ps.functionId, "ps_main");
m_module.functionBegin(
m_module.defVoidType(),
m_ps.functionId,
m_module.defFunctionType(
m_module.defVoidType(), 0, nullptr),
spv::FunctionControlMaskNone);
m_module.opLabel(m_module.allocateId());
}
void DxbcCompiler2::emitVsFinalize() {
this->emitVsInputSetup();
m_module.opFunctionCall(
m_module.defVoidType(),
m_vs.functionId, 0, nullptr);
this->emitVsOutputSetup();
}
void DxbcCompiler2::emitPsFinalize() {
this->emitPsInputSetup();
m_module.opFunctionCall(
m_module.defVoidType(),
m_ps.functionId, 0, nullptr);
this->emitPsOutputSetup();
}
uint32_t DxbcCompiler2::emitNewVariable(const DxbcRegisterInfo& info) {
const uint32_t ptrTypeId = this->getPointerTypeId(info);
return m_module.newVar(ptrTypeId, info.sclass);
}
uint32_t DxbcCompiler2::getScalarTypeId(DxbcScalarType type) {
switch (type) {
case DxbcScalarType::Uint32: return m_module.defIntType(32, 0);
case DxbcScalarType::Uint64: return m_module.defIntType(64, 0);
case DxbcScalarType::Sint32: return m_module.defIntType(32, 1);
case DxbcScalarType::Sint64: return m_module.defIntType(64, 1);
case DxbcScalarType::Float32: return m_module.defFloatType(32);
case DxbcScalarType::Float64: return m_module.defFloatType(64);
}
throw DxvkError("DxbcCompiler: Invalid scalar type");
}
uint32_t DxbcCompiler2::getVectorTypeId(const DxbcVectorType& type) {
uint32_t typeId = this->getScalarTypeId(type.ctype);
if (type.ccount > 1)
typeId = m_module.defVectorType(typeId, type.ccount);
return typeId;
}
uint32_t DxbcCompiler2::getPointerTypeId(const DxbcRegisterInfo& type) {
return m_module.defPointerType(
this->getVectorTypeId(type.type),
type.sclass);
}
uint32_t DxbcCompiler2::getPerVertexBlockId() {
uint32_t t_f32 = m_module.defFloatType(32);
uint32_t t_f32_v4 = m_module.defVectorType(t_f32, 4);
uint32_t t_f32_a2 = m_module.defArrayType(t_f32, m_module.constu32(2));
std::array<uint32_t, 4> members;
members[PerVertex_Position] = t_f32_v4;
members[PerVertex_PointSize] = t_f32;
members[PerVertex_CullDist] = t_f32_a2;
members[PerVertex_ClipDist] = t_f32_a2;
uint32_t typeId = m_module.defStructTypeUnique(
members.size(), members.data());
m_module.memberDecorateBuiltIn(typeId, PerVertex_Position, spv::BuiltInPosition);
m_module.memberDecorateBuiltIn(typeId, PerVertex_PointSize, spv::BuiltInPointSize);
m_module.memberDecorateBuiltIn(typeId, PerVertex_CullDist, spv::BuiltInCullDistance);
m_module.memberDecorateBuiltIn(typeId, PerVertex_ClipDist, spv::BuiltInClipDistance);
m_module.decorateBlock(typeId);
m_module.setDebugName(typeId, "per_vertex");
m_module.setDebugMemberName(typeId, PerVertex_Position, "position");
m_module.setDebugMemberName(typeId, PerVertex_PointSize, "point_size");
m_module.setDebugMemberName(typeId, PerVertex_CullDist, "cull_dist");
m_module.setDebugMemberName(typeId, PerVertex_ClipDist, "clip_dist");
return typeId;
}
}

View File

@ -1,311 +0,0 @@
#pragma once
#include <array>
#include <vector>
#include "../spirv/spirv_module.h"
#include "dxbc_chunk_isgn.h"
#include "dxbc_decoder_2.h"
#include "dxbc_defs.h"
#include "dxbc_names.h"
#include "dxbc_util.h"
namespace dxvk {
struct DxbcVectorType {
DxbcScalarType ctype;
uint32_t ccount;
};
struct DxbcRegisterInfo {
DxbcVectorType type;
spv::StorageClass sclass;
};
struct DxbcRegisterValue {
DxbcVectorType type;
uint32_t id;
};
struct DxbcRegisterPointer {
DxbcVectorType type;
uint32_t id;
};
struct DxbcCompilerVsPart {
uint32_t functionId;
};
struct DxbcCompilerPsPart {
uint32_t functionId;
std::array<DxbcVectorType, DxbcMaxInterfaceRegs> oTypes;
};
/**
* \brief DXBC to SPIR-V shader compiler
*
* Processes instructions from a DXBC shader and creates
* a DXVK shader object, which contains the SPIR-V module
* and information about the shader resource bindings.
*/
class DxbcCompiler2 {
public:
DxbcCompiler2(
const DxbcProgramVersion& version,
const Rc<DxbcIsgn>& isgn,
const Rc<DxbcIsgn>& osgn);
~DxbcCompiler2();
/**
* \brief Processes a single instruction
* \param [in] ins The instruction
*/
void processInstruction(
const DxbcShaderInstruction& ins);
/**
* \brief Finalizes the shader
* \returns The final shader object
*/
Rc<DxvkShader> finalize();
private:
DxbcProgramVersion m_version;
SpirvModule m_module;
Rc<DxbcIsgn> m_isgn;
Rc<DxbcIsgn> m_osgn;
///////////////////////////////////////////////////////
// Resource slot description for the shader. This will
// be used to map D3D11 bindings to DXVK bindings.
std::vector<DxvkResourceSlot> m_resourceSlots;
///////////////////////////////
// r# registers of type float4
std::vector<uint32_t> m_rRegs;
///////////////////////////////////////////////////////////
// v# registers as defined by the shader. The type of each
// of these inputs is either float4 or an array of float4.
std::array<uint32_t, DxbcMaxInterfaceRegs> m_vRegs;
std::vector<DxbcSvMapping> m_vMappings;
//////////////////////////////////////////////////////////
// o# registers as defined by the shader. In the fragment
// shader stage, these registers are typed by the signature,
// in all other stages, they are float4 registers or arrays.
std::array<uint32_t, DxbcMaxInterfaceRegs> m_oRegs;
std::vector<DxbcSvMapping> m_oMappings;
//////////////////////////////////////////////////////
// Shader resource variables. These provide access to
// constant buffers, samplers, textures, and UAVs.
std::array<DxbcConstantBuffer, 16> m_constantBuffers;
std::array<DxbcSampler, 16> m_samplers;
std::array<DxbcShaderResource, 128> m_textures;
///////////////////////////////////////////////////////////
// Array of input values. Since v# registers are indexable
// in DXBC, we need to copy them into an array first.
uint32_t m_vArray = 0;
////////////////////////////////////////////////////
// Per-vertex input and output blocks. Depending on
// the shader stage, these may be declared as arrays.
uint32_t m_perVertexIn = 0;
uint32_t m_perVertexOut = 0;
///////////////////////////////////////////////////
// Entry point description - we'll need to declare
// the function ID and all input/output variables.
std::vector<uint32_t> m_entryPointInterfaces;
uint32_t m_entryPointId = 0;
///////////////////////////////////
// Shader-specific data structures
DxbcCompilerVsPart m_vs;
DxbcCompilerPsPart m_ps;
/////////////////////////////////////////////////////
// Shader interface and metadata declaration methods
void emitDclGlobalFlags(
const DxbcShaderInstruction& ins);
void emitDclTemps(
const DxbcShaderInstruction& ins);
void emitDclInterfaceReg(
const DxbcShaderInstruction& ins);
void emitDclInput(
uint32_t regIdx,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im);
void emitDclOutput(
uint32_t regIdx,
uint32_t regDim,
DxbcRegMask regMask,
DxbcSystemValue sv,
DxbcInterpolationMode im);
void emitDclConstantBuffer(
const DxbcShaderInstruction& ins);
void emitDclSampler(
const DxbcShaderInstruction& ins);
void emitDclResource(
const DxbcShaderInstruction& ins);
//////////////////////////////
// Instruction class handlers
void emitVectorAlu(
const DxbcShaderInstruction& ins);
void emitVectorCmov(
const DxbcShaderInstruction& ins);
void emitVectorCmp(
const DxbcShaderInstruction& ins);
void emitVectorDot(
const DxbcShaderInstruction& ins);
void emitVectorImul(
const DxbcShaderInstruction& ins);
void emitVectorSinCos(
const DxbcShaderInstruction& ins);
void emitSample(
const DxbcShaderInstruction& ins);
void emitRet(
const DxbcShaderInstruction& ins);
/////////////////////////////////////////
// Generic register manipulation methods
DxbcRegisterValue emitRegisterBitcast(
DxbcRegisterValue srcValue,
DxbcScalarType dstType);
DxbcRegisterValue emitRegisterSwizzle(
DxbcRegisterValue value,
DxbcRegSwizzle swizzle,
DxbcRegMask writeMask);
DxbcRegisterValue emitRegisterExtract(
DxbcRegisterValue value,
DxbcRegMask mask);
DxbcRegisterValue emitRegisterInsert(
DxbcRegisterValue dstValue,
DxbcRegisterValue srcValue,
DxbcRegMask srcMask);
DxbcRegisterValue emitRegisterExtend(
DxbcRegisterValue value,
uint32_t size);
DxbcRegisterValue emitRegisterAbsolute(
DxbcRegisterValue value);
DxbcRegisterValue emitRegisterNegate(
DxbcRegisterValue value);
DxbcRegisterValue emitSrcOperandModifiers(
DxbcRegisterValue value,
DxbcRegModifiers modifiers);
DxbcRegisterValue emitDstOperandModifiers(
DxbcRegisterValue value,
DxbcOpModifiers modifiers);
////////////////////////
// Address load methods
DxbcRegisterPointer emitGetTempPtr(
const DxbcRegister& operand);
DxbcRegisterPointer emitGetInputPtr(
const DxbcRegister& operand);
DxbcRegisterPointer emitGetOutputPtr(
const DxbcRegister& operand);
DxbcRegisterPointer emitGetConstBufPtr(
const DxbcRegister& operand);
DxbcRegisterPointer emitGetOperandPtr(
const DxbcRegister& operand);
//////////////////////////////
// Operand load/store methods
DxbcRegisterValue emitIndexLoad(
DxbcRegIndex index);
DxbcRegisterValue emitValueLoad(
DxbcRegisterPointer ptr);
void emitValueStore(
DxbcRegisterPointer ptr,
DxbcRegisterValue value,
DxbcRegMask writeMask);
DxbcRegisterValue emitRegisterLoad(
const DxbcRegister& reg,
DxbcRegMask writeMask);
void emitRegisterStore(
const DxbcRegister& reg,
DxbcRegisterValue value);
/////////////////////////////
// Input preparation methods
void emitVsInputSetup();
void emitPsInputSetup();
//////////////////////////////
// Output preparation methods
void emitVsOutputSetup();
void emitPsOutputSetup();
/////////////////////////////////
// Shader initialization methods
void emitVsInit();
void emitPsInit();
///////////////////////////////
// Shader finalization methods
void emitVsFinalize();
void emitPsFinalize();
///////////////////////////////
// Variable definition methods
uint32_t emitNewVariable(
const DxbcRegisterInfo& info);
///////////////////////////
// Type definition methods
uint32_t getScalarTypeId(
DxbcScalarType type);
uint32_t getVectorTypeId(
const DxbcVectorType& type);
uint32_t getPointerTypeId(
const DxbcRegisterInfo& type);
uint32_t getPerVertexBlockId();
};
}

View File

@ -2,208 +2,332 @@
namespace dxvk {
DxbcCodeReader& DxbcCodeReader::operator ++ () {
return this->operator += (1);
uint32_t DxbcCodeSlice::at(uint32_t id) const {
if (m_ptr + id >= m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return m_ptr[id];
}
DxbcCodeReader& DxbcCodeReader::operator += (uint32_t n) {
if (n < m_size) {
m_code += n;
m_size -= n;
} else {
m_code = nullptr;
m_size = 0;
}
return *this;
uint32_t DxbcCodeSlice::read() {
if (m_ptr >= m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return *(m_ptr++);
}
DxbcCodeReader DxbcCodeReader::operator + (uint32_t n) const {
return n < m_size
? DxbcCodeReader(m_code + n, m_size - n)
: DxbcCodeReader();
DxbcCodeSlice DxbcCodeSlice::take(uint32_t n) const {
if (m_ptr + n > m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return DxbcCodeSlice(m_ptr, m_ptr + n);
}
bool DxbcCodeReader::operator == (const DxbcCodeReader& other) const {
return m_code == other.m_code && m_size == other.m_size;
DxbcCodeSlice DxbcCodeSlice::skip(uint32_t n) const {
if (m_ptr + n > m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return DxbcCodeSlice(m_ptr + n, m_end);
}
bool DxbcCodeReader::operator != (const DxbcCodeReader& other) const {
return !this->operator == (other);
}
uint32_t DxbcOperandIndex::length() const {
switch (m_rep) {
case DxbcOperandIndexRepresentation::Imm32: return 1;
case DxbcOperandIndexRepresentation::Imm64: return 2;
case DxbcOperandIndexRepresentation::Relative: return this->relPart().length();
case DxbcOperandIndexRepresentation::Imm32Relative: return this->relPart().length() + 1;
case DxbcOperandIndexRepresentation::Imm64Relative: return this->relPart().length() + 2;
}
void DxbcDecodeContext::decodeInstruction(DxbcCodeSlice& code) {
const uint32_t token0 = code.at(0);
throw DxvkError(str::format("DXBC: Unknown index representation: ", m_rep));
}
bool DxbcOperandIndex::hasImmPart() const {
return m_rep == DxbcOperandIndexRepresentation::Imm32
|| m_rep == DxbcOperandIndexRepresentation::Imm64
|| m_rep == DxbcOperandIndexRepresentation::Imm32Relative
|| m_rep == DxbcOperandIndexRepresentation::Imm64Relative;
}
bool DxbcOperandIndex::hasRelPart() const {
return m_rep == DxbcOperandIndexRepresentation::Relative
|| m_rep == DxbcOperandIndexRepresentation::Imm32Relative
|| m_rep == DxbcOperandIndexRepresentation::Imm64Relative;
}
uint64_t DxbcOperandIndex::immPart() const {
switch (m_rep) {
case DxbcOperandIndexRepresentation::Imm32:
case DxbcOperandIndexRepresentation::Imm32Relative:
return m_code.getWord(0);
case DxbcOperandIndexRepresentation::Imm64:
case DxbcOperandIndexRepresentation::Imm64Relative:
return (static_cast<uint64_t>(m_code.getWord(0)) << 32)
| (static_cast<uint64_t>(m_code.getWord(1)));
default:
return 0;
}
}
DxbcOperand DxbcOperandIndex::relPart() const {
switch (m_rep) {
case DxbcOperandIndexRepresentation::Relative:
return DxbcOperand(m_code);
case DxbcOperandIndexRepresentation::Imm32Relative:
return DxbcOperand(m_code + 1);
case DxbcOperandIndexRepresentation::Imm64Relative:
return DxbcOperand(m_code + 2);
default:
throw DxvkError("DXBC: Operand index is not relative");
}
}
DxbcOperand::DxbcOperand(const DxbcCodeReader& code)
: m_info(code) {
const DxbcOperandToken token(m_info.getWord(0));
// Initialize the instruction structure. Some of these values
// may not get written otherwise while decoding the instruction.
m_instruction.op = static_cast<DxbcOpcode>(bit::extract(token0, 0, 10));
m_instruction.sampleControls = { 0, 0, 0 };
m_instruction.dstCount = 0;
m_instruction.srcCount = 0;
m_instruction.immCount = 0;
m_instruction.dst = m_dstOperands.data();
m_instruction.src = m_srcOperands.data();
m_instruction.imm = m_immOperands.data();
uint32_t numTokens = 1;
// Reset the index pointer, which may still contain
// a non-zero value from the previous iteration
m_indexId = 0;
// Count extended operand tokens
if (token.isExtended()) {
while (DxbcOperandTokenExt(m_info.getWord(numTokens++)).isExtended())
continue;
}
m_data = m_info + numTokens;
// Immediate operands
// Instruction length, in DWORDs. This includes the token
// itself and any other prefix that an instruction may have.
uint32_t length = 0;
if (token.type() == DxbcOperandType::Imm32
|| token.type() == DxbcOperandType::Imm64)
length += token.numComponents();
// Indices into the register file, may contain additional operands
for (uint32_t i = 0; i < token.indexDimension(); i++) {
m_indexOffsets[i] = length;
length += this->index(i).length();
}
m_length = length + numTokens;
}
DxbcOperandIndex DxbcOperand::index(uint32_t dim) const {
return DxbcOperandIndex(
m_data + m_indexOffsets.at(dim),
this->token().indexRepresentation(dim));
}
bool DxbcOperand::queryOperandExt(DxbcOperandExt ext, DxbcOperandTokenExt& token) const {
if (!this->token().isExtended())
return false;
uint32_t extTokenId = 1;
DxbcOperandTokenExt extToken;
do {
extToken = m_info.getWord(extTokenId++);
if (extToken.type() == ext) {
token = extToken;
return true;
}
} while (extToken.isExtended());
return false;
}
DxbcInstruction::DxbcInstruction(const DxbcCodeReader& code)
: m_op(code) {
DxbcOpcodeToken token(m_op.getWord(0));
if (token.opcode() == DxbcOpcode::CustomData) {
// Custom data blocks have a special format,
// the length is stored in a separate DWORD
m_args = m_op + 2;
if (m_instruction.op == DxbcOpcode::CustomData) {
length = code.at(1);
this->decodeCustomData(code.take(length));
} else {
// For normal instructions, we just count
// the number of extended opcode tokens.
uint32_t numOpcodeTokens = 1;
length = bit::extract(token0, 24, 30);
this->decodeOperation(code.take(length));
}
// Advance the caller's slice to the next token so that
// they can make consecutive calls to decodeInstruction()
code = code.skip(length);
}
void DxbcDecodeContext::decodeCustomData(DxbcCodeSlice code) {
Logger::warn("DxbcDecodeContext::decodeCustomData: Not implemented");
}
void DxbcDecodeContext::decodeOperation(DxbcCodeSlice code) {
uint32_t token = code.read();
// Result modifiers, which are applied to common ALU ops
m_instruction.modifiers.saturate = !!bit::extract(token, 13, 13);
m_instruction.modifiers.precise = !!bit::extract(token, 19, 22);
// Opcode controls. It will depend on the opcode itself which ones are valid.
m_instruction.controls.zeroTest = static_cast<DxbcZeroTest> (bit::extract(token, 18, 18));
m_instruction.controls.syncFlags = static_cast<DxbcSyncFlags> (bit::extract(token, 11, 14));
m_instruction.controls.resourceDim = static_cast<DxbcResourceDim> (bit::extract(token, 11, 15));
m_instruction.controls.resinfoType = static_cast<DxbcResinfoType> (bit::extract(token, 11, 12));
m_instruction.controls.interpolation = static_cast<DxbcInterpolationMode>(bit::extract(token, 11, 14));
// Process extended opcode tokens
while (bit::extract(token, 31, 31)) {
token = code.read();
if (token.isExtended()) {
numOpcodeTokens += 1;
while (DxbcOpcodeTokenExt(m_op.getWord(numOpcodeTokens)).isExtended())
numOpcodeTokens += 1;
const DxbcExtOpcode extOpcode
= static_cast<DxbcExtOpcode>(bit::extract(token, 0, 5));
switch (extOpcode) {
case DxbcExtOpcode::SampleControls: {
struct {
int u : 4;
int v : 4;
int w : 4;
} aoffimmi;
aoffimmi.u = bit::extract(token, 9, 12);
aoffimmi.v = bit::extract(token, 13, 16);
aoffimmi.w = bit::extract(token, 17, 20);
// Four-bit signed numbers, sign-extend them
m_instruction.sampleControls.u = aoffimmi.u;
m_instruction.sampleControls.v = aoffimmi.v;
m_instruction.sampleControls.w = aoffimmi.w;
} break;
default:
Logger::warn(str::format(
"DxbcDecodeContext: Unhandled extended opcode: ",
extOpcode));
}
}
// Retrieve the instruction format in order to parse the
// operands. Doing this mostly automatically means that
// the compiler can rely on the operands being valid.
const DxbcInstFormat format = dxbcInstructionFormat(m_instruction.op);
for (uint32_t i = 0; i < format.operandCount; i++)
this->decodeOperand(code, format.operands[i]);
}
void DxbcDecodeContext::decodeComponentSelection(DxbcRegister& reg, uint32_t token) {
// Pick the correct component selection mode based on the
// component count. We'll simplify this here so that the
// compiler can assume that everything is a 4D vector.
reg.componentCount = static_cast<DxbcComponentCount>(bit::extract(token, 0, 1));
switch (reg.componentCount) {
// No components - used for samplers etc.
case DxbcComponentCount::Component0:
reg.mask = DxbcRegMask(false, false, false, false);
reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0);
break;
m_args = m_op + numOpcodeTokens;
// One component - used for immediates
// and a few built-in registers.
case DxbcComponentCount::Component1:
reg.mask = DxbcRegMask(true, false, false, false);
reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0);
break;
// Four components - everything else. This requires us
// to actually parse the component selection mode.
case DxbcComponentCount::Component4: {
const DxbcRegMode componentMode =
static_cast<DxbcRegMode>(bit::extract(token, 2, 3));
switch (componentMode) {
// Write mask for destination operands
case DxbcRegMode::Mask:
reg.mask = bit::extract(token, 4, 7);
reg.swizzle = DxbcRegSwizzle(0, 1, 2, 3);
break;
// Swizzle for source operands (including resources)
case DxbcRegMode::Swizzle:
reg.mask = DxbcRegMask(true, true, true, true);
reg.swizzle = DxbcRegSwizzle(
bit::extract(token, 4, 5),
bit::extract(token, 6, 7),
bit::extract(token, 8, 9),
bit::extract(token, 10, 11));
break;
// Selection of one component. We can generate both a
// mask and a swizzle for this so that the compiler
// won't have to deal with this case specifically.
case DxbcRegMode::Select1: {
const uint32_t n = bit::extract(token, 4, 5);
reg.mask = DxbcRegMask(n == 0, n == 1, n == 2, n == 3);
reg.swizzle = DxbcRegSwizzle(n, n, n, n);
} break;
default:
Logger::warn("DxbcDecodeContext: Invalid component selection mode");
}
} break;
default:
Logger::warn("DxbcDecodeContext: Invalid component count");
}
}
uint32_t DxbcInstruction::length() const {
auto token = this->token();
return token.opcode() != DxbcOpcode::CustomData
? token.length() : m_op.getWord(1);
void DxbcDecodeContext::decodeOperandExtensions(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) {
while (bit::extract(token, 31, 31)) {
token = code.read();
// Type of the extended operand token
const DxbcOperandExt extTokenType =
static_cast<DxbcOperandExt>(bit::extract(token, 0, 5));
switch (extTokenType) {
// Operand modifiers, which are used to manipulate the
// value of a source operand during the load operation
case DxbcOperandExt::OperandModifier:
reg.modifiers = bit::extract(token, 6, 13);
break;
default:
Logger::warn(str::format(
"DxbcDecodeContext: Unhandled extended operand token: ",
extTokenType));
}
}
}
bool DxbcInstruction::queryOpcodeExt(DxbcExtOpcode extOpcode, DxbcOpcodeTokenExt& token) const {
if (!this->token().isExtended())
return false;
uint32_t extTokenId = 1;
DxbcOpcodeTokenExt extToken;
do {
extToken = m_op.getWord(extTokenId++);
if (extToken.opcode() == extOpcode) {
token = extToken;
return true;
void DxbcDecodeContext::decodeOperandImmediates(DxbcCodeSlice& code, DxbcRegister& reg) {
if (reg.type == DxbcOperandType::Imm32) {
switch (reg.componentCount) {
// This is commonly used if only one vector
// component is involved in an operation
case DxbcComponentCount::Component1: {
reg.imm.u32_1 = code.read();
} break;
// Typical four-component vector
case DxbcComponentCount::Component4: {
reg.imm.u32_4[0] = code.read();
reg.imm.u32_4[1] = code.read();
reg.imm.u32_4[2] = code.read();
reg.imm.u32_4[3] = code.read();
} break;
default:
Logger::warn("DxbcDecodeContext: Invalid component count for immediate operand");
}
} while (extToken.isExtended());
} else if (reg.type == DxbcOperandType::Imm64) {
Logger::warn("DxbcDecodeContext: 64-bit immediates not supported");
}
}
void DxbcDecodeContext::decodeOperandIndex(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) {
reg.idxDim = bit::extract(token, 20, 21);
return false;
for (uint32_t i = 0; i < reg.idxDim; i++) {
// An index can be encoded in various different ways
const DxbcOperandIndexRepresentation repr =
static_cast<DxbcOperandIndexRepresentation>(
bit::extract(token, 22 + 3 * i, 24 + 3 * i));
switch (repr) {
case DxbcOperandIndexRepresentation::Imm32:
reg.idx[i].offset = static_cast<int32_t>(code.read());
reg.idx[i].relReg = nullptr;
break;
case DxbcOperandIndexRepresentation::Relative:
reg.idx[i].offset = 0;
reg.idx[i].relReg = &m_indices.at(m_indexId);
this->decodeRegister(code,
m_indices.at(m_indexId++),
DxbcScalarType::Sint32);
break;
case DxbcOperandIndexRepresentation::Imm32Relative:
reg.idx[i].offset = static_cast<int32_t>(code.read());
reg.idx[i].relReg = &m_indices.at(m_indexId);
this->decodeRegister(code,
m_indices.at(m_indexId++),
DxbcScalarType::Sint32);
break;
default:
Logger::warn(str::format(
"DxbcDecodeContext: Unhandled index representation: ",
repr));
}
}
}
void DxbcDecodeContext::decodeRegister(DxbcCodeSlice& code, DxbcRegister& reg, DxbcScalarType type) {
const uint32_t token = code.read();
reg.type = static_cast<DxbcOperandType>(bit::extract(token, 12, 19));
reg.dataType = type;
reg.modifiers = 0;
reg.idxDim = 0;
for (uint32_t i = 0; i < DxbcMaxRegIndexDim; i++) {
reg.idx[i].relReg = nullptr;
reg.idx[i].offset = 0;
}
this->decodeComponentSelection(reg, token);
this->decodeOperandExtensions(code, reg, token);
this->decodeOperandImmediates(code, reg);
this->decodeOperandIndex(code, reg, token);
}
void DxbcDecodeContext::decodeImm32(DxbcCodeSlice& code, DxbcImmediate& imm, DxbcScalarType type) {
imm.u32 = code.read();
}
void DxbcDecodeContext::decodeOperand(DxbcCodeSlice& code, const DxbcInstOperandFormat& format) {
switch (format.kind) {
case DxbcOperandKind::DstReg: {
const uint32_t operandId = m_instruction.dstCount++;
this->decodeRegister(code, m_dstOperands.at(operandId), format.type);
} break;
case DxbcOperandKind::SrcReg: {
const uint32_t operandId = m_instruction.srcCount++;
this->decodeRegister(code, m_srcOperands.at(operandId), format.type);
} break;
case DxbcOperandKind::Imm32: {
const uint32_t operandId = m_instruction.immCount++;
this->decodeImm32(code, m_immOperands.at(operandId), format.type);
} break;
default:
throw DxvkError("DxbcDecodeContext: Invalid operand format");
}
}
}

View File

@ -1,13 +1,32 @@
#pragma once
#include <cstring>
#include <array>
#include "dxbc_common.h"
#include "dxbc_decoder.h"
#include "dxbc_defs.h"
#include "dxbc_enums.h"
#include "dxbc_names.h"
namespace dxvk {
class DxbcOperand;
constexpr size_t DxbcMaxRegIndexDim = 3;
struct DxbcRegister;
/**
* \brief Source operand modifiers
*
* These are applied after loading
* an operand register.
*/
enum class DxbcRegModifier : uint32_t {
Neg = 0,
Abs = 1,
};
using DxbcRegModifiers = Flags<DxbcRegModifier>;
/**
* \brief Constant buffer binding
@ -121,671 +140,193 @@ namespace dxvk {
};
struct DxbcRegIndex {
DxbcRegister* relReg;
int32_t offset;
};
/**
* \brief Basic control info
* \brief Instruction operand
*
*
* Parses instruction-specific control bits. Whether
* these are well defined depends on the instruction.
*/
class DxbcOpcodeControl {
struct DxbcRegister {
DxbcOperandType type;
DxbcScalarType dataType;
DxbcComponentCount componentCount;
uint32_t idxDim;
DxbcRegIndex idx[DxbcMaxRegIndexDim];
DxbcRegMask mask;
DxbcRegSwizzle swizzle;
DxbcRegModifiers modifiers;
union {
uint32_t u32_4[4];
uint32_t u32_1;
} imm;
};
/**
* \brief Instruction result modifiers
*
* Modifiers that are applied
* to all destination operands.
*/
struct DxbcOpModifiers {
bool saturate;
bool precise;
};
/**
* \brief Opcode controls
*
* Instruction-specific controls. Usually,
* only one of the members will be valid.
*/
struct DxbcShaderOpcodeControls {
DxbcZeroTest zeroTest;
DxbcSyncFlags syncFlags;
DxbcResourceDim resourceDim;
DxbcResinfoType resinfoType;
DxbcInterpolationMode interpolation;
};
/**
* \brief Sample controls
*
* Constant texel offset with
* values raning from -8 to 7.
*/
struct DxbcShaderSampleControls {
int u, v, w;
};
/**
* \brief Immediate value
*
* Immediate argument represented either
* as a 32-bit or 64-bit unsigned integer.
*/
union DxbcImmediate {
uint32_t u32;
uint64_t u64;
};
/**
* \brief Shader instruction
*/
struct DxbcShaderInstruction {
DxbcOpcode op;
DxbcOpModifiers modifiers;
DxbcShaderOpcodeControls controls;
DxbcShaderSampleControls sampleControls;
uint32_t dstCount;
uint32_t srcCount;
uint32_t immCount;
const DxbcRegister* dst;
const DxbcRegister* src;
const DxbcImmediate* imm;
};
/**
* \brief DXBC code slice
*
* Convenient pointer pair that allows
* reading the code word stream safely.
*/
class DxbcCodeSlice {
public:
DxbcOpcodeControl() { }
DxbcOpcodeControl(uint32_t control)
: m_control(control) { }
DxbcCodeSlice(
const uint32_t* ptr,
const uint32_t* end)
: m_ptr(ptr), m_end(end) { }
/**
* \brief Saturation hint
*
* If set, the result of the given instruction
* is clamped to the [0..1] range.
*/
bool saturateBit() const {
return bit::extract(m_control, 2, 2) != 0;
}
uint32_t at(uint32_t id) const;
uint32_t read();
/**
* \brief Precision hint
*/
bool preciseBit() const {
return bit::extract(m_control, 8, 11) != 0;
}
DxbcCodeSlice take(uint32_t n) const;
DxbcCodeSlice skip(uint32_t n) const;
/**
* \brief Zero test
*
* For conditional instructions, this defines
* whether the test shall pass when the given
* operand is zero or non-zero.
*/
DxbcZeroTest zeroTest() const {
return static_cast<DxbcZeroTest>(
bit::extract(m_control, 7, 7));
}
/**
* \brief Resinfo return type
*
* Control bits specifically for
* the \c resinfo instruction.
*/
DxbcResinfoType resinfoType() const {
return static_cast<DxbcResinfoType>(
bit::extract(m_control, 0, 1));
}
/**
* \brief Sync flags
*
* Defines the exact operation of sync
* instructions in compute shaders.
*/
DxbcSyncFlags syncFlags() const {
return bit::extract(m_control, 0, 3);
}
/**
* \brief Resource dimension
*
* Defines the type of a resource.
* Only valid for dcl_resource etc.
*/
DxbcResourceDim resourceDim() const {
return static_cast<DxbcResourceDim>(
bit::extract(m_control, 0, 4));
bool atEnd() const {
return m_ptr == m_end;
}
private:
uint32_t m_control = 0;
const uint32_t* m_ptr = nullptr;
const uint32_t* m_end = nullptr;
};
/**
* \brief DXBC instruction token
* \brief Decode context
*
* Initial token at the beginning of each instruction.
* This encodes the actual op code, the length of the
* entire instruction in DWORDs, as well as some flags
* controlling the specific instruction.
* Stores data that is required to decode a single
* instruction. This data is not persistent, so it
* should be forwarded to the compiler right away.
*/
class DxbcOpcodeToken {
class DxbcDecodeContext {
public:
DxbcOpcodeToken() { }
DxbcOpcodeToken(uint32_t token)
: m_token(token) { }
/**
* \brief Opcode
* \returns Opcode
*/
DxbcOpcode opcode() const {
return static_cast<DxbcOpcode>(
bit::extract(m_token, 0, 10));
}
/**
* \brief Control info
* \brief Retrieves current instruction
*
* Instruction-specific control info. Undefined
* if the opcode is \c DxbcOpcode::CustomData.
* \returns Control info
* This is only valid after a call to \ref decode.
* \returns Reference to last decoded instruction
*/
uint32_t control() const {
return bit::extract(m_token, 11, 23);
const DxbcShaderInstruction& getInstruction() const {
return m_instruction;
}
/**
* \brief Instruction length
* \brief Decodes an instruction
*
* Undefind if the opcode is \c DxbcOpcode::CustomData.
* In that case, the instruction length will be stored
* in the DWORD immediately following the opcode token.
* \returns Instruction length, in DWORDs
* This also advances the given code slice by the
* number of dwords consumed by the instruction.
* \param [in] code Code slice
*/
uint32_t length() const {
return bit::extract(m_token, 24, 30);
}
/**
* \brief Checks whether the opcode is extended
*
* Additional information is encoded in extended
* opcode tokens if this flag is set. Note that
* multiple extended opcode tokens may be chained.
* \returns \c true if the opcode is extended.
*/
bool isExtended() const {
return !!bit::extract(m_token, 31, 31);
}
void decodeInstruction(DxbcCodeSlice& code);
private:
uint32_t m_token = 0;
DxbcShaderInstruction m_instruction;
};
/**
* \brief DXBC extended instruction token
*
* Some instruction may encode additional control
* modifiers in extended opcode tokens. The format
* of these tokens differs from that of the the
* initial opcode tokens.
*/
class DxbcOpcodeTokenExt {
std::array<DxbcRegister, 4> m_dstOperands;
std::array<DxbcRegister, 4> m_srcOperands;
std::array<DxbcImmediate, 4> m_immOperands;
std::array<DxbcRegister, 12> m_indices;
public:
// Index into the indices array. Used when decoding
// instruction operands with relative indexing.
uint32_t m_indexId = 0;
DxbcOpcodeTokenExt() { }
DxbcOpcodeTokenExt(uint32_t token)
: m_token(token) { }
void decodeCustomData(DxbcCodeSlice code);
void decodeOperation(DxbcCodeSlice code);
/**
* \brief Extended opcode
* \returns Extended opcode
*/
DxbcExtOpcode opcode() const {
return static_cast<DxbcExtOpcode>(
bit::extract(m_token, 0, 5));
}
void decodeComponentSelection(DxbcRegister& reg, uint32_t token);
void decodeOperandExtensions(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token);
void decodeOperandImmediates(DxbcCodeSlice& code, DxbcRegister& reg);
void decodeOperandIndex(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token);
/**
* \brief Control info
*
* Instruction-specific control info. Undefined
* if the opcode is \c DxbcOpcode::CustomData.
* \returns Control info
*/
uint32_t control() const {
return bit::extract(m_token, 6, 30);
}
void decodeRegister(DxbcCodeSlice& code, DxbcRegister& reg, DxbcScalarType type);
void decodeImm32(DxbcCodeSlice& code, DxbcImmediate& imm, DxbcScalarType type);
/**
* \brief Extended opcode length
*
* Number of DWORDs that belong to this extended
* opcode information. Currently, there are no
* extended opcodes with a length greater than 1.
* \returns Exteded opcode length, in DWORDs
*/
uint32_t length() const {
return 1;
}
/**
* \brief Checks whether there are additional modifiers
* \returns \c true if the extended opcode is extended
*/
bool isExtended() const {
return !!bit::extract(m_token, 31, 31);
}
private:
uint32_t m_token = 0;
};
/**
* \brief Operand token
*
* Stores general information about one operand of an
* instruction. Like opcode tokens, operand tokens may
* be extended.
*/
class DxbcOperandToken {
public:
DxbcOperandToken(uint32_t token)
: m_token(token) { }
/**
* \brief Number of operand components
*
* The number of components that the operand
* has. Can be zero, one, or four.
* \returns Number of components
*/
uint32_t numComponents() const {
std::array<uint32_t, 4> count = { 0, 1, 4, 0 };
return count.at(bit::extract(m_token, 0, 1));
}
/**
* \brief Component selection mode
*
* Operands can be either masked so that some components
* will not be used, or they can be swizzled so that only
* a given set of components is used.
* \returns Component selection mode
*/
DxbcRegMode selectionMode() const {
return static_cast<DxbcRegMode>(
bit::extract(m_token, 2, 3));
}
/**
* \brief Component mask
*
* Used when the component selection
* mode is \c DxbcRegMode::Mask.
* \returns The component mask
*/
DxbcRegMask mask() const {
return DxbcRegMask(bit::extract(m_token, 4, 7));
}
/**
* \brief Component swizzle
*
* Used when the component selection
* mode is \c DxbcRegMode::Swizzle.
* \returns The component swizzle
*/
DxbcRegSwizzle swizzle() const {
return DxbcRegSwizzle(
bit::extract(m_token, 4, 5),
bit::extract(m_token, 6, 7),
bit::extract(m_token, 8, 9),
bit::extract(m_token, 10, 11));
}
/**
* \brief Single component selection
*
* Used when the component selection
* mode is \c DxbcRegMode::Select1.
* \returns The component index
*/
uint32_t select1() const {
return bit::extract(m_token, 4, 5);
}
/**
* \brief Operand type
*
* Selects the type of the operand, i.e. whether the
* operand is a temporary register, a shader resource
* or a builtin interface variable.
* \returns Operand type
*/
DxbcOperandType type() const {
return static_cast<DxbcOperandType>(
bit::extract(m_token, 12, 19));
}
/**
* \brief Index dimension
*
* Number of indices. In DXBC, each register file has
* a dimensionality, e.g. the temporary registers are
* one-dimensional and therefore require one index.
* \returns Number of index dimensions
*/
uint32_t indexDimension() const {
return bit::extract(m_token, 20, 21);
}
/**
* \brief Index representation
*
* Stores whether an index is stored as an
* immediate value or as a relative value
* which requires another operand token.
* \param [in] dim Index dimension to query
* \returns Representation of that index
*/
DxbcOperandIndexRepresentation indexRepresentation(uint32_t dim) const {
return static_cast<DxbcOperandIndexRepresentation>(
bit::extract(m_token, 22 + 3 * dim, 24 + 3 * dim));
}
/**
* \brief Checks whether the operand is extended
*
* Operands can be modified with extended tokens.
* \returns \c true if the operand is extended
*/
bool isExtended() const {
return !!bit::extract(m_token, 31, 31);
}
private:
uint32_t m_token = 0;
};
/**
* \brief Extended operand token
*
* Stores additional properties for an operand that
* cannot be stored in the initial operand token.
*/
class DxbcOperandTokenExt {
public:
DxbcOperandTokenExt() { }
DxbcOperandTokenExt(uint32_t token)
: m_token(token) { }
/**
* \brief Operand extension type
* \returns Operand extension type
*/
DxbcOperandExt type() const {
return static_cast<DxbcOperandExt>(
bit::extract(m_token, 0, 5));
}
/**
* \brief Data flags
* \returns Data flags
*/
uint32_t data() const {
return bit::extract(m_token, 6, 30);
}
/**
* \brief Checks whether the operand is extended
*
* Like extended opcode tokens, extended
* operand tokens can be chained.
* \returns \c true if extended
*/
bool isExtended() const {
return !!bit::extract(m_token, 31, 31);
}
private:
uint32_t m_token = 0;
};
/**
* \brief DXBC code DxbcCodeReader
*
* Helper class that can read DWORDs from a sized slice.
* Returns undefined numbers on out-of-bounds access, but
* makes sure not to access memory locations outside the
* original code array.
*/
class DxbcCodeReader {
public:
DxbcCodeReader() { }
DxbcCodeReader(
const uint32_t* code,
uint32_t size)
: m_code(size != 0 ? code : nullptr),
m_size(size) { }
uint32_t getWord(uint32_t id) const {
return id < m_size ? m_code[id] : 0;
}
DxbcCodeReader& operator ++ ();
DxbcCodeReader& operator += (uint32_t n);
DxbcCodeReader operator + (uint32_t n) const;
bool operator == (const DxbcCodeReader& other) const;
bool operator != (const DxbcCodeReader& other) const;
private:
const uint32_t* m_code = nullptr;
uint32_t m_size = 0;
};
/**
* \brief DXBC operand index
*
* Represents an index into an indexable register file.
* For each register file dimension, one operand index
* must be read from the encoded instruction.
*/
class DxbcOperandIndex {
public:
DxbcOperandIndex() { }
DxbcOperandIndex(
const DxbcCodeReader& code,
DxbcOperandIndexRepresentation rep)
: m_code(code), m_rep(rep) { }
uint32_t length() const;
bool hasImmPart() const;
bool hasRelPart() const;
uint64_t immPart() const;
DxbcOperand relPart() const;
private:
DxbcCodeReader m_code;
DxbcOperandIndexRepresentation m_rep;
};
/**
* \brief DXBC operand
*
* Provides methods to query the operand token
* including extended operand tokens, which may
* modify the operand's return value.
*/
class DxbcOperand {
public:
DxbcOperand() { }
DxbcOperand(const DxbcCodeReader& code);
/**
* \brief Operand token
* \returns Operand token
*/
DxbcOperandToken token() const {
return DxbcOperandToken(m_info.getWord(0));
}
/**
* \brief Operand length, in DWORDs
* \returns Number of DWORDs
*/
uint32_t length() const {
return m_length;
}
/**
* \brief Operand index for a single dimension
*
* \param [in] dim Dimension to query
* \returns Operand index
*/
DxbcOperandIndex index(uint32_t dim) const;
/**
* \brief Queries an operand extension
*
* If an extended operand token with the given
* operand extension exists, return that token.
* \param [in] ext The operand extension
* \param [out] token The extended token
* \returns \c true if the token was defined
*/
bool queryOperandExt(
DxbcOperandExt ext,
DxbcOperandTokenExt& token) const;
/**
* \brief Reads 32-bit immediate integer
*
* \param [in] idx Component index
* \returns The immediate operand
*/
uint32_t imm32(uint32_t idx) const {
return m_data.getWord(idx);
}
/**
* \brief Reads 64-bit immediate integer
*
* \param [in] idx Component index
* \returns The immediate operand
*/
uint64_t imm64(uint32_t idx) const {
uint64_t hi = m_data.getWord(2 * idx + 0);
uint64_t lo = m_data.getWord(2 * idx + 1);
return (hi << 32) | (lo);
}
private:
DxbcCodeReader m_info;
DxbcCodeReader m_data;
uint32_t m_length = 0;
std::array<uint32_t, 3> m_indexOffsets = { 0, 0, 0 };
};
/**
* \brief DXBC instruction
*
* Provides methods to query the opcode token
* including extended opcode tokens, as well
* as convenience methods to read operands.
*/
class DxbcInstruction {
public:
DxbcInstruction() { }
DxbcInstruction(const DxbcCodeReader& code);
/**
* \brief Opcode token
* \returns Opcode token
*/
DxbcOpcodeToken token() const {
return DxbcOpcodeToken(m_op.getWord(0));
}
/**
* \brief Instruction length, in DWORDs
* \returns Instruction length, in DWORDs
*/
uint32_t length() const;
/**
* \brief Queries an opcode extension
*
* If an extended opcode token with the given
* opcode exists, the token will be returned.
* \param [in] extOpcode Extended opcode
* \param [out] token The extended token
* \returns \c true if the token was defined
*/
bool queryOpcodeExt(
DxbcExtOpcode extOpcode,
DxbcOpcodeTokenExt& token) const;
/**
* \brief Retrieves argument word
*
* Instruction arguments immediately follow the opcode
* tokens, including the extended opcodes. Argument 0
* will therefore be the first DWORD that is part of
* an instruction operand or an immediate number.
* \param [in] idx Argument word index
* \returns The word at the given index
*/
uint32_t arg(uint32_t idx) const {
return m_args.getWord(idx);
}
/**
* \brief Reads an argument enum
*
* Casts the word at the given location to an enum.
* Some instructions take name tokens as operands.
* \param [in] idx Argument word index
* \returns The enum value of the given word
*/
template<typename T>
T readEnum(uint32_t idx) const {
return static_cast<T>(arg(idx));
}
/**
* \brief Retrieves an operand
*
* \param [in] idx Argument word index
* \returns The operand object
*/
DxbcOperand operand(uint32_t idx) const {
return DxbcOperand(m_args + idx);
}
private:
DxbcCodeReader m_op;
DxbcCodeReader m_args;
};
/**
* \brief DXBC instruction decoder
*
* Iterator that walks over DXBC instructions.
* Instruction boundaries are easy to find as
* the length of each instruction is encoded
* in the opcode token, much like in SPIR-V.
*/
class DxbcDecoder {
public:
DxbcDecoder() { }
DxbcDecoder(const uint32_t* code, uint32_t size)
: m_code(code, size) { }
DxbcDecoder& operator ++ () {
m_code += DxbcInstruction(m_code).length();
return *this;
}
DxbcInstruction operator * () const {
return DxbcInstruction(m_code);
}
bool operator == (const DxbcDecoder& other) const { return m_code == other.m_code; }
bool operator != (const DxbcDecoder& other) const { return m_code != other.m_code; }
private:
DxbcCodeReader m_code;
void decodeOperand(DxbcCodeSlice& code, const DxbcInstOperandFormat& format);
};

View File

@ -1,333 +0,0 @@
#include "dxbc_decoder_2.h"
namespace dxvk {
uint32_t DxbcCodeSlice::at(uint32_t id) const {
if (m_ptr + id >= m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return m_ptr[id];
}
uint32_t DxbcCodeSlice::read() {
if (m_ptr >= m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return *(m_ptr++);
}
DxbcCodeSlice DxbcCodeSlice::take(uint32_t n) const {
if (m_ptr + n > m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return DxbcCodeSlice(m_ptr, m_ptr + n);
}
DxbcCodeSlice DxbcCodeSlice::skip(uint32_t n) const {
if (m_ptr + n > m_end)
throw DxvkError("DxbcCodeSlice: End of stream");
return DxbcCodeSlice(m_ptr + n, m_end);
}
void DxbcDecodeContext::decodeInstruction(DxbcCodeSlice& code) {
const uint32_t token0 = code.at(0);
// Initialize the instruction structure. Some of these values
// may not get written otherwise while decoding the instruction.
m_instruction.op = static_cast<DxbcOpcode>(bit::extract(token0, 0, 10));
m_instruction.sampleControls = { 0, 0, 0 };
m_instruction.dstCount = 0;
m_instruction.srcCount = 0;
m_instruction.immCount = 0;
m_instruction.dst = m_dstOperands.data();
m_instruction.src = m_srcOperands.data();
m_instruction.imm = m_immOperands.data();
// Reset the index pointer, which may still contain
// a non-zero value from the previous iteration
m_indexId = 0;
// Instruction length, in DWORDs. This includes the token
// itself and any other prefix that an instruction may have.
uint32_t length = 0;
if (m_instruction.op == DxbcOpcode::CustomData) {
length = code.at(1);
this->decodeCustomData(code.take(length));
} else {
length = bit::extract(token0, 24, 30);
this->decodeOperation(code.take(length));
}
// Advance the caller's slice to the next token so that
// they can make consecutive calls to decodeInstruction()
code = code.skip(length);
}
void DxbcDecodeContext::decodeCustomData(DxbcCodeSlice code) {
Logger::warn("DxbcDecodeContext::decodeCustomData: Not implemented");
}
void DxbcDecodeContext::decodeOperation(DxbcCodeSlice code) {
uint32_t token = code.read();
// Result modifiers, which are applied to common ALU ops
m_instruction.modifiers.saturate = !!bit::extract(token, 13, 13);
m_instruction.modifiers.precise = !!bit::extract(token, 19, 22);
// Opcode controls. It will depend on the opcode itself which ones are valid.
m_instruction.controls.zeroTest = static_cast<DxbcZeroTest> (bit::extract(token, 18, 18));
m_instruction.controls.syncFlags = static_cast<DxbcSyncFlags> (bit::extract(token, 11, 14));
m_instruction.controls.resourceDim = static_cast<DxbcResourceDim> (bit::extract(token, 11, 15));
m_instruction.controls.resinfoType = static_cast<DxbcResinfoType> (bit::extract(token, 11, 12));
m_instruction.controls.interpolation = static_cast<DxbcInterpolationMode>(bit::extract(token, 11, 14));
// Process extended opcode tokens
while (bit::extract(token, 31, 31)) {
token = code.read();
const DxbcExtOpcode extOpcode
= static_cast<DxbcExtOpcode>(bit::extract(token, 0, 5));
switch (extOpcode) {
case DxbcExtOpcode::SampleControls: {
struct {
int u : 4;
int v : 4;
int w : 4;
} aoffimmi;
aoffimmi.u = bit::extract(token, 9, 12);
aoffimmi.v = bit::extract(token, 13, 16);
aoffimmi.w = bit::extract(token, 17, 20);
// Four-bit signed numbers, sign-extend them
m_instruction.sampleControls.u = aoffimmi.u;
m_instruction.sampleControls.v = aoffimmi.v;
m_instruction.sampleControls.w = aoffimmi.w;
} break;
default:
Logger::warn(str::format(
"DxbcDecodeContext: Unhandled extended opcode: ",
extOpcode));
}
}
// Retrieve the instruction format in order to parse the
// operands. Doing this mostly automatically means that
// the compiler can rely on the operands being valid.
const DxbcInstFormat format = dxbcInstructionFormat(m_instruction.op);
for (uint32_t i = 0; i < format.operandCount; i++)
this->decodeOperand(code, format.operands[i]);
}
void DxbcDecodeContext::decodeComponentSelection(DxbcRegister& reg, uint32_t token) {
// Pick the correct component selection mode based on the
// component count. We'll simplify this here so that the
// compiler can assume that everything is a 4D vector.
reg.componentCount = static_cast<DxbcRegComponentCount>(bit::extract(token, 0, 1));
switch (reg.componentCount) {
// No components - used for samplers etc.
case DxbcRegComponentCount::c0:
reg.mask = DxbcRegMask(false, false, false, false);
reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0);
break;
// One component - used for immediates
// and a few built-in registers.
case DxbcRegComponentCount::c1:
reg.mask = DxbcRegMask(true, false, false, false);
reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0);
break;
// Four components - everything else. This requires us
// to actually parse the component selection mode.
case DxbcRegComponentCount::c4: {
const DxbcRegMode componentMode =
static_cast<DxbcRegMode>(bit::extract(token, 2, 3));
switch (componentMode) {
// Write mask for destination operands
case DxbcRegMode::Mask:
reg.mask = bit::extract(token, 4, 7);
reg.swizzle = DxbcRegSwizzle(0, 1, 2, 3);
break;
// Swizzle for source operands (including resources)
case DxbcRegMode::Swizzle:
reg.mask = DxbcRegMask(true, true, true, true);
reg.swizzle = DxbcRegSwizzle(
bit::extract(token, 4, 5),
bit::extract(token, 6, 7),
bit::extract(token, 8, 9),
bit::extract(token, 10, 11));
break;
// Selection of one component. We can generate both a
// mask and a swizzle for this so that the compiler
// won't have to deal with this case specifically.
case DxbcRegMode::Select1: {
const uint32_t n = bit::extract(token, 4, 5);
reg.mask = DxbcRegMask(n == 0, n == 1, n == 2, n == 3);
reg.swizzle = DxbcRegSwizzle(n, n, n, n);
} break;
default:
Logger::warn("DxbcDecodeContext: Invalid component selection mode");
}
} break;
default:
Logger::warn("DxbcDecodeContext: Invalid component count");
}
}
void DxbcDecodeContext::decodeOperandExtensions(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) {
while (bit::extract(token, 31, 31)) {
token = code.read();
// Type of the extended operand token
const DxbcOperandExt extTokenType =
static_cast<DxbcOperandExt>(bit::extract(token, 0, 5));
switch (extTokenType) {
// Operand modifiers, which are used to manipulate the
// value of a source operand during the load operation
case DxbcOperandExt::OperandModifier:
reg.modifiers = bit::extract(token, 6, 13);
break;
default:
Logger::warn(str::format(
"DxbcDecodeContext: Unhandled extended operand token: ",
extTokenType));
}
}
}
void DxbcDecodeContext::decodeOperandImmediates(DxbcCodeSlice& code, DxbcRegister& reg) {
if (reg.type == DxbcOperandType::Imm32) {
switch (reg.componentCount) {
// This is commonly used if only one vector
// component is involved in an operation
case DxbcRegComponentCount::c1: {
reg.imm.u32_1 = code.read();
} break;
// Typical four-component vector
case DxbcRegComponentCount::c4: {
reg.imm.u32_4[0] = code.read();
reg.imm.u32_4[1] = code.read();
reg.imm.u32_4[2] = code.read();
reg.imm.u32_4[3] = code.read();
} break;
default:
Logger::warn("DxbcDecodeContext: Invalid component count for immediate operand");
}
} else if (reg.type == DxbcOperandType::Imm64) {
Logger::warn("DxbcDecodeContext: 64-bit immediates not supported");
}
}
void DxbcDecodeContext::decodeOperandIndex(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) {
reg.idxDim = bit::extract(token, 20, 21);
for (uint32_t i = 0; i < reg.idxDim; i++) {
// An index can be encoded in various different ways
const DxbcOperandIndexRepresentation repr =
static_cast<DxbcOperandIndexRepresentation>(
bit::extract(token, 22 + 3 * i, 24 + 3 * i));
switch (repr) {
case DxbcOperandIndexRepresentation::Imm32:
reg.idx[i].offset = static_cast<int32_t>(code.read());
reg.idx[i].relReg = nullptr;
break;
case DxbcOperandIndexRepresentation::Relative:
reg.idx[i].offset = 0;
reg.idx[i].relReg = &m_indices.at(m_indexId);
this->decodeRegister(code,
m_indices.at(m_indexId++),
DxbcScalarType::Sint32);
break;
case DxbcOperandIndexRepresentation::Imm32Relative:
reg.idx[i].offset = static_cast<int32_t>(code.read());
reg.idx[i].relReg = &m_indices.at(m_indexId);
this->decodeRegister(code,
m_indices.at(m_indexId++),
DxbcScalarType::Sint32);
break;
default:
Logger::warn(str::format(
"DxbcDecodeContext: Unhandled index representation: ",
repr));
}
}
}
void DxbcDecodeContext::decodeRegister(DxbcCodeSlice& code, DxbcRegister& reg, DxbcScalarType type) {
const uint32_t token = code.read();
reg.type = static_cast<DxbcOperandType>(bit::extract(token, 12, 19));
reg.dataType = type;
reg.modifiers = 0;
reg.idxDim = 0;
for (uint32_t i = 0; i < DxbcMaxRegIndexDim; i++) {
reg.idx[i].relReg = nullptr;
reg.idx[i].offset = 0;
}
this->decodeComponentSelection(reg, token);
this->decodeOperandExtensions(code, reg, token);
this->decodeOperandImmediates(code, reg);
this->decodeOperandIndex(code, reg, token);
}
void DxbcDecodeContext::decodeImm32(DxbcCodeSlice& code, DxbcImmediate& imm, DxbcScalarType type) {
imm.u32 = code.read();
}
void DxbcDecodeContext::decodeOperand(DxbcCodeSlice& code, const DxbcInstOperandFormat& format) {
switch (format.kind) {
case DxbcOperandKind::DstReg: {
const uint32_t operandId = m_instruction.dstCount++;
this->decodeRegister(code, m_dstOperands.at(operandId), format.type);
} break;
case DxbcOperandKind::SrcReg: {
const uint32_t operandId = m_instruction.srcCount++;
this->decodeRegister(code, m_srcOperands.at(operandId), format.type);
} break;
case DxbcOperandKind::Imm32: {
const uint32_t operandId = m_instruction.immCount++;
this->decodeImm32(code, m_immOperands.at(operandId), format.type);
} break;
default:
throw DxvkError("DxbcDecodeContext: Invalid operand format");
}
}
}

View File

@ -1,192 +0,0 @@
#pragma once
#include <array>
#include "dxbc_common.h"
#include "dxbc_decoder.h"
#include "dxbc_defs.h"
#include "dxbc_enums.h"
#include "dxbc_names.h"
namespace dxvk {
constexpr size_t DxbcMaxRegIndexDim = 3;
struct DxbcRegister;
enum class DxbcRegComponentCount : uint32_t {
c0 = 0,
c1 = 1,
c4 = 2,
};
enum class DxbcRegModifier : uint32_t {
Neg = 0,
Abs = 1,
};
using DxbcRegModifiers = Flags<DxbcRegModifier>;
struct DxbcRegIndex {
DxbcRegister* relReg;
int32_t offset;
};
struct DxbcRegister {
DxbcOperandType type;
DxbcScalarType dataType;
DxbcRegComponentCount componentCount;
uint32_t idxDim;
DxbcRegIndex idx[DxbcMaxRegIndexDim];
DxbcRegMask mask;
DxbcRegSwizzle swizzle;
DxbcRegModifiers modifiers;
union {
uint32_t u32_4[4];
uint32_t u32_1;
} imm;
};
struct DxbcOpModifiers {
bool saturate;
bool precise;
};
struct DxbcShaderOpcodeControls {
DxbcZeroTest zeroTest;
DxbcSyncFlags syncFlags;
DxbcResourceDim resourceDim;
DxbcResinfoType resinfoType;
DxbcInterpolationMode interpolation;
};
struct DxbcShaderSampleControls {
int u, v, w;
};
union DxbcImmediate {
uint32_t u32;
uint64_t u64;
};
/**
* \brief Shader instruction
*/
struct DxbcShaderInstruction {
DxbcOpcode op;
DxbcOpModifiers modifiers;
DxbcShaderOpcodeControls controls;
DxbcShaderSampleControls sampleControls;
uint32_t dstCount;
uint32_t srcCount;
uint32_t immCount;
const DxbcRegister* dst;
const DxbcRegister* src;
const DxbcImmediate* imm;
};
/**
* \brief DXBC code slice
*
* Convenient pointer pair that allows
* reading the code word stream safely.
*/
class DxbcCodeSlice {
public:
DxbcCodeSlice(
const uint32_t* ptr,
const uint32_t* end)
: m_ptr(ptr), m_end(end) { }
uint32_t at(uint32_t id) const;
uint32_t read();
DxbcCodeSlice take(uint32_t n) const;
DxbcCodeSlice skip(uint32_t n) const;
bool atEnd() const {
return m_ptr == m_end;
}
private:
const uint32_t* m_ptr = nullptr;
const uint32_t* m_end = nullptr;
};
/**
* \brief Decode context
*
* Stores data that is required to decode a single
* instruction. This data is not persistent, so it
* should be forwarded to the compiler right away.
*/
class DxbcDecodeContext {
public:
/**
* \brief Retrieves current instruction
*
* This is only valid after a call to \ref decode.
* \returns Reference to last decoded instruction
*/
const DxbcShaderInstruction& getInstruction() const {
return m_instruction;
}
/**
* \brief Decodes an instruction
*
* This also advances the given code slice by the
* number of dwords consumed by the instruction.
* \param [in] code Code slice
*/
void decodeInstruction(DxbcCodeSlice& code);
private:
DxbcShaderInstruction m_instruction;
std::array<DxbcRegister, 4> m_dstOperands;
std::array<DxbcRegister, 4> m_srcOperands;
std::array<DxbcImmediate, 4> m_immOperands;
std::array<DxbcRegister, 12> m_indices;
// Index into the indices array. Used when decoding
// instruction operands with relative indexing.
uint32_t m_indexId = 0;
void decodeCustomData(DxbcCodeSlice code);
void decodeOperation(DxbcCodeSlice code);
void decodeComponentSelection(DxbcRegister& reg, uint32_t token);
void decodeOperandExtensions(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token);
void decodeOperandImmediates(DxbcCodeSlice& code, DxbcRegister& reg);
void decodeOperandIndex(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token);
void decodeRegister(DxbcCodeSlice& code, DxbcRegister& reg, DxbcScalarType type);
void decodeImm32(DxbcCodeSlice& code, DxbcImmediate& imm, DxbcScalarType type);
void decodeOperand(DxbcCodeSlice& code, const DxbcInstOperandFormat& format);
};
}

View File

@ -436,14 +436,6 @@ namespace dxvk {
using DxbcGlobalFlags = Flags<DxbcGlobalFlag>;
enum class DxbcOperandModifier : uint32_t {
Neg = 0,
Abs = 1,
};
using DxbcOperandModifiers = Flags<DxbcOperandModifier>;
enum class DxbcZeroTest : uint32_t {
TestZ = 0,
TestNz = 1,

View File

@ -1,5 +1,4 @@
#include "dxbc_compiler.h"
#include "dxbc_compiler_2.h"
#include "dxbc_module.h"
namespace dxvk {
@ -47,7 +46,7 @@ namespace dxvk {
DxbcCodeSlice slice = m_shexChunk->slice();
DxbcCompiler2 compiler(
DxbcCompiler compiler(
m_shexChunk->version(),
m_isgnChunk, m_osgnChunk);

View File

@ -3,10 +3,8 @@ dxbc_src = files([
'dxbc_chunk_shex.cpp',
'dxbc_common.cpp',
'dxbc_compiler.cpp',
'dxbc_compiler_2.cpp',
'dxbc_defs.cpp',
'dxbc_decoder.cpp',
'dxbc_decoder_2.cpp',
'dxbc_header.cpp',
'dxbc_module.cpp',
'dxbc_names.cpp',