Commit ae09e889 by Courtney Goeltzenleuchter Committed by Commit Bot

Fix mismatch issue with precision qualifiers.

GLSL allows varyings passed from one stage to another to not match in precision (e.g. float & half-float). Vulkan doesn't allow that so adjust those mismatches to use the higher precision. To fix we keep track of the precision of varyings and in the Vulkan backend if we see they are different patch up the SPIR-V to make them match. Bug: angleproject:3078 Change-Id: I385d31e082da46ccdd4817b6612f5f9d9cbce17c Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2337755 Commit-Queue: Courtney Goeltzenleuchter <courtneygo@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarTim Van Patten <timvp@google.com>
parent c5b5cf6c
...@@ -994,12 +994,17 @@ class SpirvTransformer final : angle::NonCopyable ...@@ -994,12 +994,17 @@ class SpirvTransformer final : angle::NonCopyable
bool transformEntryPoint(const uint32_t *instruction, size_t wordCount); bool transformEntryPoint(const uint32_t *instruction, size_t wordCount);
bool transformDecorate(const uint32_t *instruction, size_t wordCount); bool transformDecorate(const uint32_t *instruction, size_t wordCount);
bool transformTypePointer(const uint32_t *instruction, size_t wordCount); bool transformTypePointer(const uint32_t *instruction, size_t wordCount);
bool transformReturn(const uint32_t *instruction, size_t wordCount);
bool transformVariable(const uint32_t *instruction, size_t wordCount); bool transformVariable(const uint32_t *instruction, size_t wordCount);
bool transformExecutionMode(const uint32_t *instruction, size_t wordCount); bool transformExecutionMode(const uint32_t *instruction, size_t wordCount);
// Any other instructions: // Any other instructions:
void writeInputPreamble();
size_t copyInstruction(const uint32_t *instruction, size_t wordCount); size_t copyInstruction(const uint32_t *instruction, size_t wordCount);
uint32_t getNewId(); uint32_t getNewId();
void writeOpLoad(uint32_t id, uint32_t typeId, uint32_t tempVarId);
void writeOpStore(uint32_t tempVarId, uint32_t destId);
void writeOpVariable(uint32_t id, uint32_t typeId, uint32_t storageClassId);
// SPIR-V to transform: // SPIR-V to transform:
const std::vector<uint32_t> &mSpirvBlobIn; const std::vector<uint32_t> &mSpirvBlobIn;
...@@ -1019,6 +1024,9 @@ class SpirvTransformer final : angle::NonCopyable ...@@ -1019,6 +1024,9 @@ class SpirvTransformer final : angle::NonCopyable
// Traversal state: // Traversal state:
size_t mCurrentWord = 0; size_t mCurrentWord = 0;
bool mIsInFunctionSection = false; bool mIsInFunctionSection = false;
bool mInsertFunctionVariables = false;
uint32_t mEntryPointId = 0;
uint32_t mOpFunctionId = 0;
// Transformation state: // Transformation state:
...@@ -1035,7 +1043,14 @@ class SpirvTransformer final : angle::NonCopyable ...@@ -1035,7 +1043,14 @@ class SpirvTransformer final : angle::NonCopyable
// duplicated with a similar instruction but which defines a type with the Private storage // duplicated with a similar instruction but which defines a type with the Private storage
// class. If inactive varyings are encountered, its type is changed to the Private one. The // class. If inactive varyings are encountered, its type is changed to the Private one. The
// following vector maps the Output type id to the corresponding Private one. // following vector maps the Output type id to the corresponding Private one.
std::vector<uint32_t> mTypePointerTransformedId; struct TransformedIDs
{
uint32_t privateID;
uint32_t typeID;
};
std::vector<TransformedIDs> mTypePointerTransformedId;
std::vector<uint32_t> mFixedVaryingId;
std::vector<uint32_t> mFixedVaryingTypeId;
}; };
bool SpirvTransformer::transform() bool SpirvTransformer::transform()
...@@ -1053,13 +1068,14 @@ bool SpirvTransformer::transform() ...@@ -1053,13 +1068,14 @@ bool SpirvTransformer::transform()
// Make sure the SpirvBlob is not reused. // Make sure the SpirvBlob is not reused.
ASSERT(mSpirvBlobOut->empty()); ASSERT(mSpirvBlobOut->empty());
// Copy the header to SpirvBlob, we need that to be defined for SpirvTransformer::getNewId to
// work.
mSpirvBlobOut->assign(mSpirvBlobIn.begin(), mSpirvBlobIn.begin() + kHeaderIndexInstructions);
// First, find all necessary ids and associate them with the information required to transform // First, find all necessary ids and associate them with the information required to transform
// their decorations. // their decorations.
resolveVariableIds(); resolveVariableIds();
// Copy the header to SpirvBlob
mSpirvBlobOut->assign(mSpirvBlobIn.begin(), mSpirvBlobIn.begin() + kHeaderIndexInstructions);
mCurrentWord = kHeaderIndexInstructions; mCurrentWord = kHeaderIndexInstructions;
while (mCurrentWord < mSpirvBlobIn.size()) while (mCurrentWord < mSpirvBlobIn.size())
{ {
...@@ -1113,7 +1129,10 @@ void SpirvTransformer::resolveVariableIds() ...@@ -1113,7 +1129,10 @@ void SpirvTransformer::resolveVariableIds()
// Allocate storage for Output type pointer map. At index i, this vector holds the identical // Allocate storage for Output type pointer map. At index i, this vector holds the identical
// type as %i except for its storage class turned to Private. // type as %i except for its storage class turned to Private.
mTypePointerTransformedId.resize(indexBound + 1, 0); // Also store a FunctionID and TypeID for when we need to fix a precision mismatch
mTypePointerTransformedId.resize(indexBound + 1, {0, 0});
mFixedVaryingId.resize(indexBound + 1, {0});
mFixedVaryingTypeId.resize(indexBound + 1, {0});
size_t currentWord = kHeaderIndexInstructions; size_t currentWord = kHeaderIndexInstructions;
...@@ -1155,6 +1174,7 @@ void SpirvTransformer::resolveVariableIds() ...@@ -1155,6 +1174,7 @@ void SpirvTransformer::resolveVariableIds()
void SpirvTransformer::transformInstruction() void SpirvTransformer::transformInstruction()
{ {
ASSERT(mCurrentWord < mSpirvBlobIn.size());
const uint32_t *instruction = &mSpirvBlobIn[mCurrentWord]; const uint32_t *instruction = &mSpirvBlobIn[mCurrentWord];
const uint32_t wordCount = GetSpirvInstructionLength(instruction); const uint32_t wordCount = GetSpirvInstructionLength(instruction);
...@@ -1165,9 +1185,16 @@ void SpirvTransformer::transformInstruction() ...@@ -1165,9 +1185,16 @@ void SpirvTransformer::transformInstruction()
if (opCode == spv::OpFunction) if (opCode == spv::OpFunction)
{ {
constexpr size_t kFunctionIdIndex = 2;
mOpFunctionId = instruction[kFunctionIdIndex];
// SPIR-V is structured in sections. Function declarations come last. Only Op*Access* // SPIR-V is structured in sections. Function declarations come last. Only Op*Access*
// opcodes inside functions need to be inspected. // opcodes inside functions need to be inspected.
mIsInFunctionSection = true; mIsInFunctionSection = true;
// Only write function variables for the EntryPoint function for non-compute shaders
mInsertFunctionVariables =
mOpFunctionId == mEntryPointId && mShaderType != gl::ShaderType::Compute;
} }
// Only look at interesting instructions. // Only look at interesting instructions.
...@@ -1175,6 +1202,18 @@ void SpirvTransformer::transformInstruction() ...@@ -1175,6 +1202,18 @@ void SpirvTransformer::transformInstruction()
if (mIsInFunctionSection) if (mIsInFunctionSection)
{ {
// After we process an OpFunction instruction and any instructions that must come
// immediately after OpFunction we need to check if there are any precision mismatches that
// need to be handled. If so, output OpVariable for each variable that needed to change from
// a StorageClassOutput to a StorageClassFunction.
if (mInsertFunctionVariables && opCode != spv::OpFunction &&
opCode != spv::OpFunctionParameter && opCode != spv::OpLabel &&
opCode != spv::OpVariable)
{
writeInputPreamble();
mInsertFunctionVariables = false;
}
// Look at in-function opcodes. // Look at in-function opcodes.
switch (opCode) switch (opCode)
{ {
...@@ -1184,6 +1223,10 @@ void SpirvTransformer::transformInstruction() ...@@ -1184,6 +1223,10 @@ void SpirvTransformer::transformInstruction()
case spv::OpInBoundsPtrAccessChain: case spv::OpInBoundsPtrAccessChain:
transformed = transformAccessChain(instruction, wordCount); transformed = transformAccessChain(instruction, wordCount);
break; break;
case spv::OpReturn:
transformed = transformReturn(instruction, wordCount);
break;
default: default:
break; break;
} }
...@@ -1241,6 +1284,29 @@ void SpirvTransformer::transformInstruction() ...@@ -1241,6 +1284,29 @@ void SpirvTransformer::transformInstruction()
mCurrentWord += wordCount; mCurrentWord += wordCount;
} }
// Called by transformInstruction to insert necessary instructions for casting varying
void SpirvTransformer::writeInputPreamble()
{
for (uint32_t id = 0; id < mVariableInfoById.size(); id++)
{
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
if (info && info->useRelaxedPrecision && info->activeStages[mShaderType] &&
!info->varyingIsOutput)
{
// This is an input varying, need to cast the mediump value that came from
// the previous stage into a highp value that the code wants to work with.
// Build OpLoad instruction to load the mediump value into a temporary
uint32_t tempVar = getNewId();
writeOpLoad(mFixedVaryingId[id], mFixedVaryingTypeId[id], tempVar);
// Build OpStore instruction to cast the mediump value to highp for use in
// the function
writeOpStore(tempVar, id);
}
}
}
void SpirvTransformer::visitName(const uint32_t *instruction) void SpirvTransformer::visitName(const uint32_t *instruction)
{ {
// We currently don't have any big-endian devices in the list of supported platforms. Literal // We currently don't have any big-endian devices in the list of supported platforms. Literal
...@@ -1361,6 +1427,13 @@ void SpirvTransformer::visitVariable(const uint32_t *instruction) ...@@ -1361,6 +1427,13 @@ void SpirvTransformer::visitVariable(const uint32_t *instruction)
// Associate the id of this name with its info. // Associate the id of this name with its info.
mVariableInfoById[id] = info; mVariableInfoById[id] = info;
if (info && info->useRelaxedPrecision && info->activeStages[mShaderType] &&
mFixedVaryingId[id] == 0)
{
mFixedVaryingId[id] = getNewId();
mFixedVaryingTypeId[id] = typeId;
}
// Note if the variable is captured by transform feedback. In that case, the TransformFeedback // Note if the variable is captured by transform feedback. In that case, the TransformFeedback
// capability needs to be added. // capability needs to be added.
if (mShaderType != gl::ShaderType::Fragment && if (mShaderType != gl::ShaderType::Fragment &&
...@@ -1380,6 +1453,7 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor ...@@ -1380,6 +1453,7 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor
uint32_t id = instruction[kIdIndex]; uint32_t id = instruction[kIdIndex];
uint32_t decoration = instruction[kDecorationIndex]; uint32_t decoration = instruction[kDecorationIndex];
ASSERT(id < mVariableInfoById.size());
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
// If variable is not a shader interface variable that needs modification, there's nothing to // If variable is not a shader interface variable that needs modification, there's nothing to
...@@ -1408,6 +1482,16 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor ...@@ -1408,6 +1482,16 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor
case spv::DecorationDescriptorSet: case spv::DecorationDescriptorSet:
newDecorationValue = info->descriptorSet; newDecorationValue = info->descriptorSet;
break; break;
case spv::DecorationFlat:
if (info->useRelaxedPrecision)
{
// Change the id to replacement variable
ASSERT(mFixedVaryingId[id] != 0);
const size_t instructionOffset = copyInstruction(instruction, wordCount);
(*mSpirvBlobOut)[instructionOffset + kIdIndex] = mFixedVaryingId[id];
return true;
}
break;
default: default:
break; break;
} }
...@@ -1429,6 +1513,23 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor ...@@ -1429,6 +1513,23 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor
return true; return true;
} }
if (info->useRelaxedPrecision)
{
// Change the id of the location decoration to replacement variable
ASSERT(mFixedVaryingId[id] != 0);
(*mSpirvBlobOut)[instructionOffset + kIdIndex] = mFixedVaryingId[id];
// The replacement variable is always reduced precision so add that decoration to
// fixedVaryingId
constexpr size_t kInstDecorateRelaxedPrecisionWordCount = 3;
uint32_t inst[kInstDecorateRelaxedPrecisionWordCount];
SetSpirvInstructionLength(inst, kInstDecorateRelaxedPrecisionWordCount);
SetSpirvInstructionOp(inst, spv::OpDecorate);
inst[kIdIndex] = mFixedVaryingId[id];
inst[kDecorationIndex] = spv::DecorationRelaxedPrecision;
copyInstruction(inst, kInstDecorateRelaxedPrecisionWordCount);
}
// Add component decoration, if any. // Add component decoration, if any.
if (info->component != ShaderInterfaceVariableInfo::kInvalid) if (info->component != ShaderInterfaceVariableInfo::kInvalid)
{ {
...@@ -1463,6 +1564,11 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor ...@@ -1463,6 +1564,11 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor
for (size_t i = 0; i < kXfbDecorationCount; ++i) for (size_t i = 0; i < kXfbDecorationCount; ++i)
{ {
const size_t xfbInstructionOffset = copyInstruction(instruction, wordCount); const size_t xfbInstructionOffset = copyInstruction(instruction, wordCount);
if (info->useRelaxedPrecision)
{
// Change the id to replacement variable
(*mSpirvBlobOut)[xfbInstructionOffset + kIdIndex] = mFixedVaryingId[id];
}
(*mSpirvBlobOut)[xfbInstructionOffset + kDecorationIndex] = xfbDecorations[i]; (*mSpirvBlobOut)[xfbInstructionOffset + kDecorationIndex] = xfbDecorations[i];
(*mSpirvBlobOut)[xfbInstructionOffset + kDecorationValueIndex] = xfbDecorationValues[i]; (*mSpirvBlobOut)[xfbInstructionOffset + kDecorationValueIndex] = xfbDecorationValues[i];
} }
...@@ -1518,6 +1624,7 @@ bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t w ...@@ -1518,6 +1624,7 @@ bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t w
// Remove inactive varyings from the shader interface declaration. // Remove inactive varyings from the shader interface declaration.
// SPIR-V 1.0 Section 3.32 Instructions, OpEntryPoint // SPIR-V 1.0 Section 3.32 Instructions, OpEntryPoint
constexpr size_t kEntryPointIdIndex = 2;
constexpr size_t kNameIndex = 3; constexpr size_t kNameIndex = 3;
// Calculate the length of entry point name in words. Note that endianness of the string // Calculate the length of entry point name in words. Note that endianness of the string
...@@ -1529,6 +1636,10 @@ bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t w ...@@ -1529,6 +1636,10 @@ bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t w
const size_t interfaceStart = kNameIndex + nameLength; const size_t interfaceStart = kNameIndex + nameLength;
const size_t interfaceCount = instructionLength - interfaceStart; const size_t interfaceCount = instructionLength - interfaceStart;
// Should only have one EntryPoint
ASSERT(mEntryPointId == 0);
mEntryPointId = instruction[kEntryPointIdIndex];
// Create a copy of the entry point for modification. // Create a copy of the entry point for modification.
std::vector<uint32_t> filteredEntryPoint(instruction, instruction + wordCount); std::vector<uint32_t> filteredEntryPoint(instruction, instruction + wordCount);
...@@ -1546,6 +1657,11 @@ bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t w ...@@ -1546,6 +1657,11 @@ bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t w
continue; continue;
} }
// If ID is one we had to replace due to varying mismatch, use the fixed ID.
if (mFixedVaryingId[id] != 0)
{
id = mFixedVaryingId[id];
}
filteredEntryPoint[writeIndex] = id; filteredEntryPoint[writeIndex] = id;
++writeIndex; ++writeIndex;
} }
...@@ -1563,9 +1679,6 @@ bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t w ...@@ -1563,9 +1679,6 @@ bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t w
return true; return true;
} }
// SPIR-V 1.0 Section 3.32 Instructions, OpEntryPoint
constexpr size_t kEntryPointIdIndex = 2;
// SPIR-V 1.0 Section 3.32 Instructions, OpExecutionMode // SPIR-V 1.0 Section 3.32 Instructions, OpExecutionMode
constexpr size_t kExecutionModeInstructionLength = 3; constexpr size_t kExecutionModeInstructionLength = 3;
constexpr size_t kExecutionModeIdIndex = 1; constexpr size_t kExecutionModeIdIndex = 1;
...@@ -1599,38 +1712,66 @@ bool SpirvTransformer::transformTypePointer(const uint32_t *instruction, size_t ...@@ -1599,38 +1712,66 @@ bool SpirvTransformer::transformTypePointer(const uint32_t *instruction, size_t
// inactive varying, or if that varying is a struct, an Op*AccessChain retrieving a field of // inactive varying, or if that varying is a struct, an Op*AccessChain retrieving a field of
// that inactive varying. // that inactive varying.
// //
// Unfortunately, SPIR-V specifies the storage class both on the type and the variable // SPIR-V specifies the storage class both on the type and the variable declaration. Otherwise
// declaration. Otherwise it would have been sufficient to modify the OpVariable instruction. // it would have been sufficient to modify the OpVariable instruction. For simplicity, copy
// For simplicty, copy every "OpTypePointer Output" instruction except with the Private storage // every "OpTypePointer Output" and "OpTypePointer Input" instruction except with the Private
// class, in case it may be necessary later. // storage class, in case it may be necessary later.
if (storageClass != spv::StorageClassOutput) // Cannot create a Private type declaration from builtins such as gl_PerVertex.
if (mNamesById[typeId] != nullptr && angle::BeginsWith(mNamesById[typeId], "gl_"))
{ {
return false; return false;
} }
// Cannot create a Private type declaration from builtins such as gl_PerVertex. // Precision fixup needs this typeID
if (mNamesById[typeId] != nullptr && angle::BeginsWith(mNamesById[typeId], "gl_")) mTypePointerTransformedId[id].typeID = typeId;
if (storageClass != spv::StorageClassOutput && storageClass != spv::StorageClassInput)
{ {
return false; return false;
} }
// Copy the type declaration for modification. // Insert OpTypePointer definition for new PrivateType.
const size_t instructionOffset = copyInstruction(instruction, wordCount); const size_t instructionOffset = copyInstruction(instruction, wordCount);
const uint32_t newTypeId = getNewId(); const uint32_t newPrivateTypeId = getNewId();
(*mSpirvBlobOut)[instructionOffset + kIdIndex] = newTypeId; (*mSpirvBlobOut)[instructionOffset + kIdIndex] = newPrivateTypeId;
(*mSpirvBlobOut)[instructionOffset + kStorageClassIndex] = spv::StorageClassPrivate; (*mSpirvBlobOut)[instructionOffset + kStorageClassIndex] = spv::StorageClassPrivate;
// Remember the id of the replacement. // Remember the id of the replacement.
ASSERT(id < mTypePointerTransformedId.size()); ASSERT(id < mTypePointerTransformedId.size());
mTypePointerTransformedId[id] = newTypeId; mTypePointerTransformedId[id].privateID = newPrivateTypeId;
// The original instruction should still be present as well. At this point, we don't know // The original instruction should still be present as well. At this point, we don't know
// whether we will need the Output or Private type. // whether we will need the Output or Private type.
return false; return false;
} }
bool SpirvTransformer::transformReturn(const uint32_t *instruction, size_t wordCount)
{
if (mOpFunctionId != mEntryPointId)
{
// We only need to process the precision info when returning from the entry point function
return false;
}
for (uint32_t id = 0; id < mVariableInfoById.size(); id++)
{
const ShaderInterfaceVariableInfo *info = mVariableInfoById[id];
if (info && info->useRelaxedPrecision && info->activeStages[mShaderType] &&
info->varyingIsOutput)
{
// Build OpLoad instruction to load the highp value into a temporary
uint32_t tempVar = getNewId();
writeOpLoad(id, mFixedVaryingTypeId[id], tempVar);
// Build OpStore instruction to cast the highp value to mediump for output
writeOpStore(tempVar, mFixedVaryingId[id]);
}
}
return false;
}
bool SpirvTransformer::transformVariable(const uint32_t *instruction, size_t wordCount) bool SpirvTransformer::transformVariable(const uint32_t *instruction, size_t wordCount)
{ {
// SPIR-V 1.0 Section 3.32 Instructions, OpVariable // SPIR-V 1.0 Section 3.32 Instructions, OpVariable
...@@ -1656,6 +1797,21 @@ bool SpirvTransformer::transformVariable(const uint32_t *instruction, size_t wor ...@@ -1656,6 +1797,21 @@ bool SpirvTransformer::transformVariable(const uint32_t *instruction, size_t wor
ASSERT(storageClass != spv::StorageClassInput || info->activeStages[mShaderType]); ASSERT(storageClass != spv::StorageClassInput || info->activeStages[mShaderType]);
if (info->activeStages[mShaderType]) if (info->activeStages[mShaderType])
{ {
if (info->useRelaxedPrecision &&
(storageClass == spv::StorageClassOutput || storageClass == spv::StorageClassInput))
{
// Change existing OpVariable to use fixedVaryingId
const size_t instructionOffset = copyInstruction(instruction, wordCount);
ASSERT(mFixedVaryingId[id] != 0);
(*mSpirvBlobOut)[instructionOffset + kIdIndex] = mFixedVaryingId[id];
// Make original variable a private global
ASSERT(mTypePointerTransformedId[typeId].privateID != 0);
writeOpVariable(id, mTypePointerTransformedId[typeId].privateID,
spv::StorageClassPrivate);
return true;
}
return false; return false;
} }
...@@ -1667,9 +1823,10 @@ bool SpirvTransformer::transformVariable(const uint32_t *instruction, size_t wor ...@@ -1667,9 +1823,10 @@ bool SpirvTransformer::transformVariable(const uint32_t *instruction, size_t wor
const size_t instructionOffset = copyInstruction(instruction, wordCount); const size_t instructionOffset = copyInstruction(instruction, wordCount);
ASSERT(typeId < mTypePointerTransformedId.size()); ASSERT(typeId < mTypePointerTransformedId.size());
ASSERT(mTypePointerTransformedId[typeId] != 0); ASSERT(mTypePointerTransformedId[typeId].privateID != 0);
(*mSpirvBlobOut)[instructionOffset + kTypeIdIndex] = mTypePointerTransformedId[typeId]; (*mSpirvBlobOut)[instructionOffset + kTypeIdIndex] =
mTypePointerTransformedId[typeId].privateID;
(*mSpirvBlobOut)[instructionOffset + kStorageClassIndex] = spv::StorageClassPrivate; (*mSpirvBlobOut)[instructionOffset + kStorageClassIndex] = spv::StorageClassPrivate;
return true; return true;
...@@ -1687,18 +1844,23 @@ bool SpirvTransformer::transformAccessChain(const uint32_t *instruction, size_t ...@@ -1687,18 +1844,23 @@ bool SpirvTransformer::transformAccessChain(const uint32_t *instruction, size_t
// If not accessing an inactive output varying, nothing to do. // If not accessing an inactive output varying, nothing to do.
const ShaderInterfaceVariableInfo *info = mVariableInfoById[baseId]; const ShaderInterfaceVariableInfo *info = mVariableInfoById[baseId];
if (info == nullptr || info->activeStages[mShaderType]) if (info == nullptr)
{ {
return false; return false;
} }
if (info->activeStages[mShaderType] && !info->useRelaxedPrecision)
{
return false;
}
// Copy the instruction for modification. // Copy the instruction for modification.
const size_t instructionOffset = copyInstruction(instruction, wordCount); const size_t instructionOffset = copyInstruction(instruction, wordCount);
ASSERT(typeId < mTypePointerTransformedId.size()); ASSERT(typeId < mTypePointerTransformedId.size());
ASSERT(mTypePointerTransformedId[typeId] != 0); ASSERT(mTypePointerTransformedId[typeId].privateID != 0);
(*mSpirvBlobOut)[instructionOffset + kTypeIdIndex] = mTypePointerTransformedId[typeId]; (*mSpirvBlobOut)[instructionOffset + kTypeIdIndex] =
mTypePointerTransformedId[typeId].privateID;
return true; return true;
} }
...@@ -1730,6 +1892,57 @@ uint32_t SpirvTransformer::getNewId() ...@@ -1730,6 +1892,57 @@ uint32_t SpirvTransformer::getNewId()
{ {
return (*mSpirvBlobOut)[kHeaderIndexIndexBound]++; return (*mSpirvBlobOut)[kHeaderIndexIndexBound]++;
} }
void SpirvTransformer::writeOpStore(uint32_t tempVarId, uint32_t destId)
{
constexpr size_t kInstIndex = 0;
constexpr size_t kIdIndex = 1;
constexpr size_t kObjectIndex = 2;
constexpr size_t kOpStoreInstructionLength = 3;
uint32_t opStore[kOpStoreInstructionLength];
SetSpirvInstructionLength(&opStore[kInstIndex], kOpStoreInstructionLength);
SetSpirvInstructionOp(&opStore[kInstIndex], spv::OpStore);
opStore[kIdIndex] = destId;
opStore[kObjectIndex] = tempVarId;
copyInstruction(opStore, kOpStoreInstructionLength);
}
void SpirvTransformer::writeOpLoad(uint32_t id, uint32_t typeId, uint32_t tempVarId)
{
constexpr size_t kResultTypeIndex = 1;
constexpr size_t kResultIdIndex = 2;
constexpr size_t kLoadIdIndex = 3;
constexpr size_t kOpLoadInstructionLength = 4;
// Build OpLoad instruction
ASSERT(typeId != 0);
ASSERT(mTypePointerTransformedId[typeId].typeID != 0);
uint32_t opLoad[kOpLoadInstructionLength];
SetSpirvInstructionOp(opLoad, spv::OpLoad);
SetSpirvInstructionLength(opLoad, kOpLoadInstructionLength);
opLoad[kResultTypeIndex] = mTypePointerTransformedId[typeId].typeID;
opLoad[kResultIdIndex] = tempVarId;
opLoad[kLoadIdIndex] = id;
copyInstruction(opLoad, kOpLoadInstructionLength);
}
void SpirvTransformer::writeOpVariable(uint32_t id, uint32_t typeId, uint32_t storageClassId)
{
constexpr size_t kResultTypeIndex = 1;
constexpr size_t kResultIdIndex = 2;
constexpr size_t kStorageClassIdIndex = 3;
constexpr size_t kOpVariableInstructionLength = 4;
uint32_t opVariable[kOpVariableInstructionLength];
SetSpirvInstructionOp(opVariable, spv::OpVariable);
SetSpirvInstructionLength(opVariable, kOpVariableInstructionLength);
opVariable[kResultTypeIndex] = typeId;
opVariable[kResultIdIndex] = id;
opVariable[kStorageClassIdIndex] = storageClassId;
copyInstruction(opVariable, kOpVariableInstructionLength);
}
} // anonymous namespace } // anonymous namespace
const uint32_t ShaderInterfaceVariableInfo::kInvalid; const uint32_t ShaderInterfaceVariableInfo::kInvalid;
......
...@@ -81,6 +81,13 @@ struct ShaderInterfaceVariableInfo ...@@ -81,6 +81,13 @@ struct ShaderInterfaceVariableInfo
uint32_t xfbBuffer = kInvalid; uint32_t xfbBuffer = kInvalid;
uint32_t xfbOffset = kInvalid; uint32_t xfbOffset = kInvalid;
uint32_t xfbStride = kInvalid; uint32_t xfbStride = kInvalid;
// Indicates that the precision needs to be modified in the generated SPIR-V
// to support only transferring medium precision data when there's a precision
// mismatch between the shaders. For example, either the VS casts highp->mediump
// or the FS casts mediump->highp.
bool useRelaxedPrecision = false;
// Indicate if varying is input or output
bool varyingIsOutput = false;
}; };
// TODO: http://anglebug.com/4524: Need a different hash key than a string, since // TODO: http://anglebug.com/4524: Need a different hash key than a string, since
......
...@@ -223,6 +223,8 @@ std::unique_ptr<rx::LinkEvent> ProgramExecutableVk::load(gl::BinaryInputStream * ...@@ -223,6 +223,8 @@ std::unique_ptr<rx::LinkEvent> ProgramExecutableVk::load(gl::BinaryInputStream *
info->xfbBuffer = stream->readInt<uint32_t>(); info->xfbBuffer = stream->readInt<uint32_t>();
info->xfbOffset = stream->readInt<uint32_t>(); info->xfbOffset = stream->readInt<uint32_t>();
info->xfbStride = stream->readInt<uint32_t>(); info->xfbStride = stream->readInt<uint32_t>();
info->useRelaxedPrecision = stream->readBool();
info->varyingIsOutput = stream->readBool();
} }
} }
...@@ -246,6 +248,8 @@ void ProgramExecutableVk::save(gl::BinaryOutputStream *stream) ...@@ -246,6 +248,8 @@ void ProgramExecutableVk::save(gl::BinaryOutputStream *stream)
stream->writeInt<uint32_t>(it.second.xfbBuffer); stream->writeInt<uint32_t>(it.second.xfbBuffer);
stream->writeInt<uint32_t>(it.second.xfbOffset); stream->writeInt<uint32_t>(it.second.xfbOffset);
stream->writeInt<uint32_t>(it.second.xfbStride); stream->writeInt<uint32_t>(it.second.xfbStride);
stream->writeInt<uint8_t>(it.second.useRelaxedPrecision);
stream->writeInt<uint8_t>(it.second.varyingIsOutput);
} }
} }
} }
...@@ -866,6 +870,40 @@ angle::Result ProgramExecutableVk::createPipelineLayout(const gl::Context *glCon ...@@ -866,6 +870,40 @@ angle::Result ProgramExecutableVk::createPipelineLayout(const gl::Context *glCon
return angle::Result::Continue; return angle::Result::Continue;
} }
void ProgramExecutableVk::resolvePrecisionMismatch(const gl::ProgramMergedVaryings &mergedVaryings)
{
for (const gl::ProgramVaryingRef &mergedVarying : mergedVaryings)
{
if (mergedVarying.frontShader && mergedVarying.backShader)
{
GLenum frontPrecision = mergedVarying.frontShader->precision;
GLenum backPrecision = mergedVarying.backShader->precision;
if (frontPrecision != backPrecision)
{
ShaderInterfaceVariableInfo *info =
&mVariableInfoMap[mergedVarying.frontShaderStage]
[mergedVarying.frontShader->mappedName];
ASSERT(frontPrecision >= GL_LOW_FLOAT && frontPrecision <= GL_HIGH_INT);
ASSERT(backPrecision >= GL_LOW_FLOAT && backPrecision <= GL_HIGH_INT);
if (frontPrecision > backPrecision)
{
// The output is higher precision than the input
info->varyingIsOutput = true;
info->useRelaxedPrecision = true;
}
else if (backPrecision > frontPrecision)
{
// The output is lower precision than the input, adjust the input
info = &mVariableInfoMap[mergedVarying.backShaderStage]
[mergedVarying.backShader->mappedName];
info->varyingIsOutput = false;
info->useRelaxedPrecision = true;
}
}
}
}
}
void ProgramExecutableVk::updateDefaultUniformsDescriptorSet( void ProgramExecutableVk::updateDefaultUniformsDescriptorSet(
const gl::ShaderType shaderType, const gl::ShaderType shaderType,
const DefaultUniformBlock &defaultUniformBlock, const DefaultUniformBlock &defaultUniformBlock,
......
...@@ -197,6 +197,7 @@ class ProgramExecutableVk ...@@ -197,6 +197,7 @@ class ProgramExecutableVk
const gl::ActiveTextureArray<vk::TextureUnit> *activeTextures, const gl::ActiveTextureArray<vk::TextureUnit> *activeTextures,
vk::DescriptorSetLayoutDesc *descOut); vk::DescriptorSetLayoutDesc *descOut);
void resolvePrecisionMismatch(const gl::ProgramMergedVaryings &mergedVaryings);
void updateDefaultUniformsDescriptorSet(const gl::ShaderType shaderType, void updateDefaultUniformsDescriptorSet(const gl::ShaderType shaderType,
const DefaultUniformBlock &defaultUniformBlock, const DefaultUniformBlock &defaultUniformBlock,
vk::BufferHelper *defaultUniformBuffer, vk::BufferHelper *defaultUniformBuffer,
......
...@@ -53,7 +53,7 @@ void ProgramPipelineVk::fillProgramStateMap( ...@@ -53,7 +53,7 @@ void ProgramPipelineVk::fillProgramStateMap(
} }
angle::Result ProgramPipelineVk::link(const gl::Context *glContext, angle::Result ProgramPipelineVk::link(const gl::Context *glContext,
const gl::ProgramMergedVaryings & /*mergedVaryings*/) const gl::ProgramMergedVaryings &mergedVaryings)
{ {
ContextVk *contextVk = vk::GetImpl(glContext); ContextVk *contextVk = vk::GetImpl(glContext);
const gl::State &glState = glContext->getState(); const gl::State &glState = glContext->getState();
...@@ -87,6 +87,11 @@ angle::Result ProgramPipelineVk::link(const gl::Context *glContext, ...@@ -87,6 +87,11 @@ angle::Result ProgramPipelineVk::link(const gl::Context *glContext,
} }
} }
if (contextVk->getFeatures().enablePrecisionQualifiers.enabled)
{
mExecutable.resolvePrecisionMismatch(mergedVaryings);
}
return mExecutable.createPipelineLayout(glContext); return mExecutable.createPipelineLayout(glContext);
} }
......
...@@ -255,7 +255,7 @@ void ProgramVk::fillProgramStateMap(gl::ShaderMap<const gl::ProgramState *> *pro ...@@ -255,7 +255,7 @@ void ProgramVk::fillProgramStateMap(gl::ShaderMap<const gl::ProgramState *> *pro
std::unique_ptr<LinkEvent> ProgramVk::link(const gl::Context *context, std::unique_ptr<LinkEvent> ProgramVk::link(const gl::Context *context,
const gl::ProgramLinkedResources &resources, const gl::ProgramLinkedResources &resources,
gl::InfoLog &infoLog, gl::InfoLog &infoLog,
const gl::ProgramMergedVaryings & /*mergedVaryings*/) const gl::ProgramMergedVaryings &mergedVaryings)
{ {
ANGLE_TRACE_EVENT0("gpu.angle", "ProgramVk::link"); ANGLE_TRACE_EVENT0("gpu.angle", "ProgramVk::link");
...@@ -288,6 +288,11 @@ std::unique_ptr<LinkEvent> ProgramVk::link(const gl::Context *context, ...@@ -288,6 +288,11 @@ std::unique_ptr<LinkEvent> ProgramVk::link(const gl::Context *context,
return std::make_unique<LinkEventDone>(status); return std::make_unique<LinkEventDone>(status);
} }
if (contextVk->getFeatures().enablePrecisionQualifiers.enabled)
{
mExecutable.resolvePrecisionMismatch(mergedVaryings);
}
// TODO(jie.a.chen@intel.com): Parallelize linking. // TODO(jie.a.chen@intel.com): Parallelize linking.
// http://crbug.com/849576 // http://crbug.com/849576
status = mExecutable.createPipelineLayout(context); status = mExecutable.createPipelineLayout(context);
......
...@@ -5932,6 +5932,220 @@ void main() ...@@ -5932,6 +5932,220 @@ void main()
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
} }
// Test mismatched precision in varying is handled correctly.
TEST_P(GLSLTest_ES3, MismatchPrecisionFloat)
{
constexpr char kVS[] = R"(#version 300 es
in vec4 position;
uniform highp float inVal;
out highp float myVarying;
void main()
{
myVarying = inVal;
gl_Position = position;
})";
constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
in mediump float myVarying;
void main()
{
my_FragColor = vec4(1, 0, 0, 1);
if (myVarying > 1.0)
{
my_FragColor = vec4(0, 1, 0, 1);
}
})";
ANGLE_GL_PROGRAM(program, kVS, kFS);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
glUseProgram(program.get());
GLint positionLocation = glGetAttribLocation(program.get(), "position");
std::array<Vector3, 6> quadVertices = GetQuadVertices();
for (Vector3 &vertex : quadVertices)
{
vertex.z() = 0.5f;
}
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
glEnableVertexAttribArray(positionLocation);
GLint inValLoc = glGetUniformLocation(program, "inVal");
ASSERT_NE(-1, inValLoc);
glUniform1f(inValLoc, static_cast<GLfloat>(1.003));
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
// Test mismatched precision in varying is handled correctly.
TEST_P(GLSLTest_ES3, MismatchPrecisionlowpFloat)
{
// Note: SPIRV only has relaxed precision so both lowp and mediump turn into "relaxed
// precision", thus this is the same test as MismatchPrecisionFloat but including it for
// completeness in case something changes.
constexpr char kVS[] = R"(#version 300 es
in vec4 position;
uniform highp float inVal;
out highp float myVarying;
void main()
{
myVarying = inVal;
gl_Position = position;
})";
constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
in lowp float myVarying;
void main()
{
my_FragColor = vec4(1, 0, 0, 1);
if (myVarying > 1.0)
{
my_FragColor = vec4(0, 1, 0, 1);
}
})";
ANGLE_GL_PROGRAM(program, kVS, kFS);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
glUseProgram(program.get());
GLint positionLocation = glGetAttribLocation(program.get(), "position");
std::array<Vector3, 6> quadVertices = GetQuadVertices();
for (Vector3 &vertex : quadVertices)
{
vertex.z() = 0.5f;
}
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
glEnableVertexAttribArray(positionLocation);
GLint inValLoc = glGetUniformLocation(program, "inVal");
ASSERT_NE(-1, inValLoc);
glUniform1f(inValLoc, static_cast<GLfloat>(1.003));
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
// Test mismatched precision in varying is handled correctly.
TEST_P(GLSLTest_ES3, MismatchPrecisionVec2UnusedVarying)
{
constexpr char kVS[] = R"(#version 300 es
in vec2 position;
uniform highp float inVal;
out highp float myVarying;
out highp vec2 texCoord;
void main()
{
myVarying = inVal;
gl_Position = vec4(position, 0, 1);
texCoord = position * 0.5 + vec2(0.5);
})";
constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
in mediump float myVarying;
in mediump vec2 texCoord;
void main()
{
my_FragColor = vec4(1, 0, 0, 1);
if (myVarying > 1.0)
{
my_FragColor = vec4(0, 1, 0, 1);
}
})";
ANGLE_GL_PROGRAM(program, kVS, kFS);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
glUseProgram(program.get());
GLint positionLocation = glGetAttribLocation(program.get(), "position");
std::array<Vector3, 6> quadVertices = GetQuadVertices();
for (Vector3 &vertex : quadVertices)
{
vertex.z() = 0.5f;
}
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
glEnableVertexAttribArray(positionLocation);
GLint inValLoc = glGetUniformLocation(program, "inVal");
ASSERT_NE(-1, inValLoc);
glUniform1f(inValLoc, static_cast<GLfloat>(1.003));
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
// Test mismatched precision in varying is handled correctly.
TEST_P(GLSLTest_ES3, MismatchPrecisionMedToHigh)
{
constexpr char kVS[] = R"(#version 300 es
in vec2 position;
uniform highp float inVal;
out mediump float myVarying;
void main()
{
myVarying = inVal;
gl_Position = vec4(position, 0, 1);
})";
constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
in highp float myVarying;
void main()
{
my_FragColor = vec4(1, 0, 0, 1);
if (myVarying > 1.0)
{
my_FragColor = vec4(0, 1, 0, 1);
}
})";
ANGLE_GL_PROGRAM(program, kVS, kFS);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
glUseProgram(program.get());
GLint positionLocation = glGetAttribLocation(program.get(), "position");
std::array<Vector3, 6> quadVertices = GetQuadVertices();
for (Vector3 &vertex : quadVertices)
{
vertex.z() = 0.5f;
}
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
glEnableVertexAttribArray(positionLocation);
GLint inValLoc = glGetUniformLocation(program, "inVal");
ASSERT_NE(-1, inValLoc);
glUniform1f(inValLoc, static_cast<GLfloat>(1.003));
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
// Test vector/scalar arithmetic (in this case multiplication and addition). Meant to reproduce a // Test vector/scalar arithmetic (in this case multiplication and addition). Meant to reproduce a
// bug that appeared in NVIDIA OpenGL drivers and that is worked around by // bug that appeared in NVIDIA OpenGL drivers and that is worked around by
// VectorizeVectorScalarArithmetic AST transform. // VectorizeVectorScalarArithmetic AST transform.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment