Implement GL_EXT_shader_16bit_storage and GL_EXT_shader_8bit_storage extensions.

These introduce limited support for 8/16-bit types such that they can only be accessed in buffer memory and converted to/from 32-bit types.

Contributed from Khronos-internal work.
This commit is contained in:
John Kessenich 2018-07-03 13:19:51 -06:00
parent eefab240f7
commit 312dcfb070
43 changed files with 6179 additions and 2765 deletions

View file

@ -354,96 +354,109 @@ TIntermTyped* TParseContext::handleVariable(const TSourceLoc& loc, TSymbol* symb
//
TIntermTyped* TParseContext::handleBracketDereference(const TSourceLoc& loc, TIntermTyped* base, TIntermTyped* index)
{
TIntermTyped* result = nullptr;
int indexValue = 0;
if (index->getQualifier().isFrontEndConstant())
indexValue = index->getAsConstantUnion()->getConstArray()[0].getIConst();
// basic type checks...
variableCheck(base);
if (! base->isArray() && ! base->isMatrix() && ! base->isVector()) {
if (base->getAsSymbolNode())
error(loc, " left of '[' is not of type array, matrix, or vector ", base->getAsSymbolNode()->getName().c_str(), "");
else
error(loc, " left of '[' is not of type array, matrix, or vector ", "expression", "");
} else if (base->getType().getQualifier().isFrontEndConstant() && index->getQualifier().isFrontEndConstant()) {
// Insert dummy error-recovery result
return intermediate.addConstantUnion(0.0, EbtFloat, loc);
}
if (!base->isArray() && base->isVector()) {
if (base->getType().containsBasicType(EbtFloat16)) {
requireFloat16Arithmetic(loc, "'[' does not operate on types containing float16");
}
if (base->getType().contains16BitInt()) {
requireInt16Arithmetic(loc, "'[' does not operate on types containing (u)int16");
}
if (base->getType().contains8BitInt()) {
requireInt8Arithmetic(loc, "'[' does not operate on types containing (u)int8");
}
}
// check for constant folding
if (base->getType().getQualifier().isFrontEndConstant() && index->getQualifier().isFrontEndConstant()) {
// both base and index are front-end constants
checkIndex(loc, base->getType(), indexValue);
return intermediate.foldDereference(base, indexValue, loc);
} else {
// at least one of base and index is not a front-end constant variable...
}
if (index->getQualifier().isFrontEndConstant())
// at least one of base and index is not a front-end constant variable...
TIntermTyped* result = nullptr;
if (index->getQualifier().isFrontEndConstant())
checkIndex(loc, base->getType(), indexValue);
if (base->getAsSymbolNode() && isIoResizeArray(base->getType()))
handleIoResizeArrayAccess(loc, base);
if (index->getQualifier().isFrontEndConstant()) {
if (base->getType().isUnsizedArray())
base->getWritableType().updateImplicitArraySize(indexValue + 1);
else
checkIndex(loc, base->getType(), indexValue);
if (base->getAsSymbolNode() && isIoResizeArray(base->getType()))
handleIoResizeArrayAccess(loc, base);
if (index->getQualifier().isFrontEndConstant()) {
if (base->getType().isUnsizedArray())
base->getWritableType().updateImplicitArraySize(indexValue + 1);
else
checkIndex(loc, base->getType(), indexValue);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
} else {
if (base->getType().isUnsizedArray()) {
// we have a variable index into an unsized array, which is okay,
// depending on the situation
if (base->getAsSymbolNode() && isIoResizeArray(base->getType()))
error(loc, "", "[", "array must be sized by a redeclaration or layout qualifier before being indexed with a variable");
else {
// it is okay for a run-time sized array
checkRuntimeSizable(loc, *base);
}
base->getWritableType().setArrayVariablyIndexed();
}
if (base->getBasicType() == EbtBlock) {
if (base->getQualifier().storage == EvqBuffer)
requireProfile(base->getLoc(), ~EEsProfile, "variable indexing buffer block array");
else if (base->getQualifier().storage == EvqUniform)
profileRequires(base->getLoc(), EEsProfile, 320, Num_AEP_gpu_shader5, AEP_gpu_shader5,
"variable indexing uniform block array");
else {
// input/output blocks either don't exist or can be variable indexed
}
} else if (language == EShLangFragment && base->getQualifier().isPipeOutput())
requireProfile(base->getLoc(), ~EEsProfile, "variable indexing fragment shader output array");
else if (base->getBasicType() == EbtSampler && version >= 130) {
const char* explanation = "variable indexing sampler array";
requireProfile(base->getLoc(), EEsProfile | ECoreProfile | ECompatibilityProfile, explanation);
profileRequires(base->getLoc(), EEsProfile, 320, Num_AEP_gpu_shader5, AEP_gpu_shader5, explanation);
profileRequires(base->getLoc(), ECoreProfile | ECompatibilityProfile, 400, nullptr, explanation);
}
result = intermediate.addIndex(EOpIndexIndirect, base, index, loc);
}
}
if (result == nullptr) {
// Insert dummy error-recovery result
result = intermediate.addConstantUnion(0.0, EbtFloat, loc);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
} else {
// Insert valid dereferenced result
TType newType(base->getType(), 0); // dereferenced type
if (base->getType().getQualifier().isConstant() && index->getQualifier().isConstant()) {
newType.getQualifier().storage = EvqConst;
// If base or index is a specialization constant, the result should also be a specialization constant.
if (base->getType().getQualifier().isSpecConstant() || index->getQualifier().isSpecConstant()) {
newType.getQualifier().makeSpecConstant();
if (base->getType().isUnsizedArray()) {
// we have a variable index into an unsized array, which is okay,
// depending on the situation
if (base->getAsSymbolNode() && isIoResizeArray(base->getType()))
error(loc, "", "[", "array must be sized by a redeclaration or layout qualifier before being indexed with a variable");
else {
// it is okay for a run-time sized array
checkRuntimeSizable(loc, *base);
}
} else {
newType.getQualifier().makePartialTemporary();
base->getWritableType().setArrayVariablyIndexed();
}
if (base->getBasicType() == EbtBlock) {
if (base->getQualifier().storage == EvqBuffer)
requireProfile(base->getLoc(), ~EEsProfile, "variable indexing buffer block array");
else if (base->getQualifier().storage == EvqUniform)
profileRequires(base->getLoc(), EEsProfile, 320, Num_AEP_gpu_shader5, AEP_gpu_shader5,
"variable indexing uniform block array");
else {
// input/output blocks either don't exist or can be variable indexed
}
} else if (language == EShLangFragment && base->getQualifier().isPipeOutput())
requireProfile(base->getLoc(), ~EEsProfile, "variable indexing fragment shader output array");
else if (base->getBasicType() == EbtSampler && version >= 130) {
const char* explanation = "variable indexing sampler array";
requireProfile(base->getLoc(), EEsProfile | ECoreProfile | ECompatibilityProfile, explanation);
profileRequires(base->getLoc(), EEsProfile, 320, Num_AEP_gpu_shader5, AEP_gpu_shader5, explanation);
profileRequires(base->getLoc(), ECoreProfile | ECompatibilityProfile, 400, nullptr, explanation);
}
result->setType(newType);
// Propagate nonuniform
if (base->getQualifier().isNonUniform() || index->getQualifier().isNonUniform())
result->getWritableType().getQualifier().nonUniform = true;
if (anyIndexLimits)
handleIndexLimits(loc, base, index);
result = intermediate.addIndex(EOpIndexIndirect, base, index, loc);
}
// Insert valid dereferenced result
TType newType(base->getType(), 0); // dereferenced type
if (base->getType().getQualifier().isConstant() && index->getQualifier().isConstant()) {
newType.getQualifier().storage = EvqConst;
// If base or index is a specialization constant, the result should also be a specialization constant.
if (base->getType().getQualifier().isSpecConstant() || index->getQualifier().isSpecConstant()) {
newType.getQualifier().makeSpecConstant();
}
} else {
newType.getQualifier().makePartialTemporary();
}
result->setType(newType);
// Propagate nonuniform
if (base->getQualifier().isNonUniform() || index->getQualifier().isNonUniform())
result->getWritableType().getQualifier().nonUniform = true;
if (anyIndexLimits)
handleIndexLimits(loc, base, index);
return result;
}
@ -615,6 +628,12 @@ TIntermTyped* TParseContext::handleBinaryMath(const TSourceLoc& loc, const char*
break;
}
if (((left->getType().containsBasicType(EbtFloat16) || right->getType().containsBasicType(EbtFloat16)) && !float16Arithmetic()) ||
((left->getType().contains16BitInt() || right->getType().contains16BitInt()) && !int16Arithmetic()) ||
((left->getType().contains8BitInt() || right->getType().contains8BitInt()) && !int8Arithmetic())) {
allowed = false;
}
TIntermTyped* result = nullptr;
if (allowed)
result = intermediate.addBinaryMath(op, left, right, loc);
@ -630,7 +649,17 @@ TIntermTyped* TParseContext::handleUnaryMath(const TSourceLoc& loc, const char*
{
rValueErrorCheck(loc, str, childNode);
TIntermTyped* result = intermediate.addUnaryMath(op, childNode, loc);
bool allowed = true;
if ((childNode->getType().containsBasicType(EbtFloat16) && !float16Arithmetic()) ||
(childNode->getType().contains16BitInt() && !int16Arithmetic()) ||
(childNode->getType().contains8BitInt() && !int8Arithmetic())) {
allowed = false;
}
TIntermTyped* result = nullptr;
if (allowed)
result = intermediate.addUnaryMath(op, childNode, loc);
if (result)
return result;
@ -692,6 +721,16 @@ TIntermTyped* TParseContext::handleDotDereference(const TSourceLoc& loc, TInterm
TSwizzleSelectors<TVectorSelector> selectors;
parseSwizzleSelector(loc, field, base->getVectorSize(), selectors);
if (base->isVector() && selectors.size() != 1 && base->getType().containsBasicType(EbtFloat16)) {
requireFloat16Arithmetic(loc, "can't swizzle types containing float16");
}
if (base->isVector() && selectors.size() != 1 && base->getType().contains16BitInt()) {
requireInt16Arithmetic(loc, "can't swizzle types containing (u)int16");
}
if (base->isVector() && selectors.size() != 1 && base->getType().contains8BitInt()) {
requireInt8Arithmetic(loc, "can't swizzle types containing (u)int8");
}
if (base->isScalar()) {
if (selectors.size() == 1)
return result;
@ -970,6 +1009,16 @@ TIntermTyped* TParseContext::handleFunctionCall(const TSourceLoc& loc, TFunction
if (builtIn && fnCandidate->getNumExtensions())
requireExtensions(loc, fnCandidate->getNumExtensions(), fnCandidate->getExtensions(), fnCandidate->getName().c_str());
if (builtIn && fnCandidate->getType().containsBasicType(EbtFloat16)) {
requireFloat16Arithmetic(loc, "float16 types can only be in uniform block or buffer storage");
}
if (builtIn && fnCandidate->getType().contains16BitInt()) {
requireInt16Arithmetic(loc, "(u)int16 types can only be in uniform block or buffer storage");
}
if (builtIn && fnCandidate->getType().contains8BitInt()) {
requireInt8Arithmetic(loc, "(u)int8 types can only be in uniform block or buffer storage");
}
if (arguments != nullptr) {
// Make sure qualifications work for these arguments.
TIntermAggregate* aggregate = arguments->getAsAggregate();
@ -995,6 +1044,17 @@ TIntermTyped* TParseContext::handleFunctionCall(const TSourceLoc& loc, TFunction
if (argQualifier.writeonly && ! formalQualifier.writeonly)
error(arguments->getLoc(), message, "writeonly", "");
}
if (builtIn && arg->getAsTyped()->getType().containsBasicType(EbtFloat16)) {
requireFloat16Arithmetic(arguments->getLoc(), "float16 types can only be in uniform block or buffer storage");
}
if (builtIn && arg->getAsTyped()->getType().contains16BitInt()) {
requireInt16Arithmetic(arguments->getLoc(), "(u)int16 types can only be in uniform block or buffer storage");
}
if (builtIn && arg->getAsTyped()->getType().contains8BitInt()) {
requireInt8Arithmetic(arguments->getLoc(), "(u)int8 types can only be in uniform block or buffer storage");
}
// TODO 4.5 functionality: A shader will fail to compile
// if the value passed to the memargument of an atomic memory function does not correspond to a buffer or
// shared variable. It is acceptable to pass an element of an array or a single component of a vector to the
@ -1179,6 +1239,8 @@ void TParseContext::computeBuiltinPrecisions(TIntermTyped& node, const TFunction
TIntermNode* TParseContext::handleReturnValue(const TSourceLoc& loc, TIntermTyped* value)
{
storage16BitAssignmentCheck(loc, value->getType(), "return");
functionReturnsValue = true;
if (currentFunctionType->getBasicType() == EbtVoid) {
error(loc, "void function cannot return a value", "return", "");
@ -2353,6 +2415,68 @@ bool TParseContext::constructorError(const TSourceLoc& loc, TIntermNode* node, T
specConstType = true;
if (function[arg].type->isFloatingDomain())
floatArgument = true;
if (type.isStruct()) {
if (function[arg].type->containsBasicType(EbtFloat16)) {
requireFloat16Arithmetic(loc, "Can't construct structure containing 16-bit type");
}
if (function[arg].type->containsBasicType(EbtUint16) ||
function[arg].type->containsBasicType(EbtInt16)) {
requireInt16Arithmetic(loc, "Can't construct structure containing 16-bit type");
}
if (function[arg].type->containsBasicType(EbtUint8) ||
function[arg].type->containsBasicType(EbtInt8)) {
requireInt8Arithmetic(loc, "Can't construct structure containing 8-bit type");
}
}
}
switch (op) {
case EOpConstructFloat16:
case EOpConstructF16Vec2:
case EOpConstructF16Vec3:
case EOpConstructF16Vec4:
if (type.isArray()) {
requireFloat16Arithmetic(loc, "16-bit array constructors not supported");
}
if (type.isVector() && function.getParamCount() != 1) {
requireFloat16Arithmetic(loc, "16-bit vector constructors only take vector types");
}
break;
case EOpConstructUint16:
case EOpConstructU16Vec2:
case EOpConstructU16Vec3:
case EOpConstructU16Vec4:
case EOpConstructInt16:
case EOpConstructI16Vec2:
case EOpConstructI16Vec3:
case EOpConstructI16Vec4:
if (type.isArray()) {
requireInt16Arithmetic(loc, "16-bit array constructors not supported");
}
if (type.isVector() && function.getParamCount() != 1) {
requireInt16Arithmetic(loc, "16-bit vector constructors only take vector types");
}
break;
case EOpConstructUint8:
case EOpConstructU8Vec2:
case EOpConstructU8Vec3:
case EOpConstructU8Vec4:
case EOpConstructInt8:
case EOpConstructI8Vec2:
case EOpConstructI8Vec3:
case EOpConstructI8Vec4:
if (type.isArray()) {
requireInt8Arithmetic(loc, "8-bit array constructors not supported");
}
if (type.isVector() && function.getParamCount() != 1) {
requireInt8Arithmetic(loc, "8-bit vector constructors only take vector types");
}
break;
default:
break;
}
// inherit constness from children
@ -3037,6 +3161,16 @@ void TParseContext::parameterTypeCheck(const TSourceLoc& loc, TStorageQualifier
{
if ((qualifier == EvqOut || qualifier == EvqInOut) && type.isOpaque())
error(loc, "samplers and atomic_uints cannot be output parameters", type.getBasicTypeString().c_str(), "");
if (!parsingBuiltins && type.containsBasicType(EbtFloat16)) {
requireFloat16Arithmetic(loc, "float16 types can only be in uniform block or buffer storage");
}
if (!parsingBuiltins && type.contains16BitInt()) {
requireInt16Arithmetic(loc, "(u)int16 types can only be in uniform block or buffer storage");
}
if (!parsingBuiltins && type.contains8BitInt()) {
requireInt8Arithmetic(loc, "(u)int8 types can only be in uniform block or buffer storage");
}
}
bool TParseContext::containsFieldWithBasicType(const TType& type, TBasicType basicType)
@ -3811,6 +3945,39 @@ void TParseContext::opaqueCheck(const TSourceLoc& loc, const TType& type, const
error(loc, "can't use with samplers or structs containing samplers", op, "");
}
void TParseContext::storage16BitAssignmentCheck(const TSourceLoc& loc, const TType& type, const char* op)
{
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtFloat16))
requireFloat16Arithmetic(loc, "can't use with structs containing float16");
if (type.isArray() && type.getBasicType() == EbtFloat16)
requireFloat16Arithmetic(loc, "can't use with arrays containing float16");
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtInt16))
requireInt16Arithmetic(loc, "can't use with structs containing int16");
if (type.isArray() && type.getBasicType() == EbtInt16)
requireInt16Arithmetic(loc, "can't use with arrays containing int16");
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtUint16))
requireInt16Arithmetic(loc, "can't use with structs containing uint16");
if (type.isArray() && type.getBasicType() == EbtUint16)
requireInt16Arithmetic(loc, "can't use with arrays containing uint16");
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtInt8))
requireInt8Arithmetic(loc, "can't use with structs containing int8");
if (type.isArray() && type.getBasicType() == EbtInt8)
requireInt8Arithmetic(loc, "can't use with arrays containing int8");
if (type.getBasicType() == EbtStruct && containsFieldWithBasicType(type, EbtUint8))
requireInt8Arithmetic(loc, "can't use with structs containing uint8");
if (type.isArray() && type.getBasicType() == EbtUint8)
requireInt8Arithmetic(loc, "can't use with arrays containing uint8");
}
void TParseContext::specializationCheck(const TSourceLoc& loc, const TType& type, const char* op)
{
if (type.containsSpecializationSize())
@ -5414,6 +5581,18 @@ TIntermNode* TParseContext::declareVariable(const TSourceLoc& loc, TString& iden
atomicUintCheck(loc, type, identifier);
transparentOpaqueCheck(loc, type, identifier);
if (type.getQualifier().storage != EvqUniform && type.getQualifier().storage != EvqBuffer) {
if (type.containsBasicType(EbtFloat16)) {
requireFloat16Arithmetic(loc, "float16 types can only be in uniform block or buffer storage");
}
if (type.contains16BitInt()) {
requireInt16Arithmetic(loc, "(u)int16 types can only be in uniform block or buffer storage");
}
if (type.contains8BitInt()) {
requireInt8Arithmetic(loc, "(u)int8 types can only be in uniform block or buffer storage");
}
}
if (identifier != "gl_FragCoord" && (publicType.shaderQualifiers.originUpperLeft || publicType.shaderQualifiers.pixelCenterInteger))
error(loc, "can only apply origin_upper_left and pixel_center_origin to gl_FragCoord", "layout qualifier", "");
if (identifier != "gl_FragDepth" && publicType.shaderQualifiers.layoutDepth != EldNone)