Vulkan: Work around Qualcomm imprecision with dithering

On qualcomm, sometimes the output is ceil()ed instead of round()ed.
With ditering emulation affecting values, some dEQP tests fail due to
the excessive change in value when dithering bumps the value slightly
over to the next quantum.

In this change, a workaround is added to round() the value before
outputting it.

Bug: angleproject:6953
Change-Id: Iae7df5ca20055b4db3185c6153f3c0bf4ba07f68
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3611064
Reviewed-by: Yiwei Zhang <zzyiwei@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
This commit is contained in:
Shahbaz Youssefi 2022-04-27 11:04:22 -04:00 коммит произвёл Angle LUCI CQ
Родитель 6c24869183
Коммит 3b65b80379
12 изменённых файлов: 123 добавлений и 66 удалений

Просмотреть файл

@ -351,6 +351,10 @@ const ShCompileOptions SH_GENERATE_SPIRV_THROUGH_GLSLANG = UINT64_C(1) << 58;
// Insert explicit casts for float/double/unsigned/signed int on macOS 10.15 with Intel driver
const ShCompileOptions SH_ADD_EXPLICIT_BOOL_CASTS = UINT64_C(1) << 59;
// Add round() after applying dither. This works around a Qualcomm quirk where values can get
// ceil()ed instead.
const ShCompileOptions SH_ROUND_OUTPUT_AFTER_DITHERING = UINT64_C(1) << 60;
// The 64 bits hash function. The first parameter is the input string; the
// second parameter is the string length.
using ShHashFunction64 = khronos_uint64_t (*)(const char *, size_t);

Просмотреть файл

@ -569,6 +569,11 @@ struct FeaturesVk : FeatureSetBase
"Emulate OpenGL dithering", &members,
"http://anglebug.com/6755"};
FeatureInfo roundOutputAfterDithering = {
"roundOutputAfterDithering", FeatureCategory::VulkanWorkarounds,
"Round output after dithering to workaround a driver bug that rounds the output up",
&members, "http://anglebug.com/6953"};
FeatureInfo emulateAdvancedBlendEquations = {
"emulateAdvancedBlendEquations", FeatureCategory::VulkanFeatures,
"Emulate GL_KHR_blend_equation_advanced", &members, "http://anglebug.com/3586"};

Просмотреть файл

@ -844,6 +844,15 @@
"issue": "http://anglebug.com/6755"
},
{
"name": "round_output_after_dithering",
"category": "Workarounds",
"description": [
"Round output after dithering to workaround a driver bug that rounds the output up"
],
"issue": "http://anglebug.com/6953"
},
{
"name": "emulate_advanced_blend_equations",
"category": "Features",

Просмотреть файл

@ -6,7 +6,7 @@
"include/platform/FeaturesMtl_autogen.h":
"a71a6361a40f0c0471604a192c810d48",
"include/platform/FeaturesVk_autogen.h":
"76694d576e396463bb00390dbce9b3a9",
"fc192c3047b4649281e3552ae9982590",
"include/platform/FrontendFeatures_autogen.h":
"95ba012012ae6a2de2a9237ab9d081f7",
"include/platform/d3d_features.json":
@ -20,9 +20,9 @@
"include/platform/mtl_features.json":
"6703cf8790325cf33b98eb7dc0472e48",
"include/platform/vk_features.json":
"aaab06cd9b5cdefdaf093d659e747fa7",
"f4447cae3e9e90f53afb7a9d4f72ab15",
"util/angle_features_autogen.cpp":
"a033f798a05dc5863d214cc19abd1d8b",
"72d06f67c79931526fb75e290972c0ed",
"util/angle_features_autogen.h":
"9eacb6c51fcf74837915b5c2d0fe1134"
"57bcc3276d5543099f69d1fd4f29bd95"
}

Просмотреть файл

@ -159,8 +159,8 @@ bool DeclareDefaultUniforms(TCompiler *compiler,
TLayoutQualifier layoutQualifier = TLayoutQualifier::Create();
layoutQualifier.blockStorage = EbsStd140;
const TVariable *uniformBlock = DeclareInterfaceBlock(
root, symbolTable, uniformList, EvqUniform, layoutQualifier, TMemoryQualifier::Create(), 0,
ImmutableString(kDefaultUniformNames[shaderType]), ImmutableString(""));
root, symbolTable, uniformList, EvqUniform, layoutQualifier, TMemoryQualifier::Create(), 0,
ImmutableString(kDefaultUniformNames[shaderType]), ImmutableString(""));
// Create a map from the uniform variables to new variables that reference the fields of the
// block.
@ -1245,7 +1245,8 @@ bool TranslatorVulkan::translateImpl(TInfoSinkBase &sink,
}
}
if (!EmulateDithering(this, root, &getSymbolTable(), specConst, driverUniforms))
if (!EmulateDithering(this, compileOptions, root, &getSymbolTable(), specConst,
driverUniforms))
{
return false;
}

Просмотреть файл

@ -9,7 +9,6 @@
#include "compiler/translator/tree_ops/vulkan/EmulateDithering.h"
#include "compiler/translator/Compiler.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/DriverUniform.h"
@ -70,6 +69,7 @@ TIntermTyped *CreateDitherValue(const TType &type, TIntermSequence *ditherValueE
}
void EmitFragmentOutputDither(TCompiler *compiler,
ShCompileOptions compileOptions,
TSymbolTable *symbolTable,
TIntermBlock *ditherBlock,
TIntermTyped *ditherControl,
@ -77,6 +77,8 @@ void EmitFragmentOutputDither(TCompiler *compiler,
TIntermTyped *fragmentOutput,
uint32_t location)
{
bool roundOutputAfterDithering = (compileOptions | SH_ROUND_OUTPUT_AFTER_DITHERING) != 0;
// dither >> 2*location
TIntermBinary *ditherControlShifted = new TIntermBinary(
EOpBitShiftRight, ditherControl->deepCopy(), CreateUIntNode(location * 2));
@ -104,6 +106,23 @@ void EmitFragmentOutputDither(TCompiler *compiler,
CreateTempInitDeclarationNode(&ditherValue->variable(), CreateZeroNode(*outputType));
ditherBlock->appendStatement(ditherValueDecl);
// If a workaround is enabled, the bit-range of each channel is also tracked, so a round() can
// be applied.
TIntermSymbol *roundMultiplier = nullptr;
if (roundOutputAfterDithering)
{
roundMultiplier = new TIntermSymbol(
CreateTempVariable(symbolTable, StaticType::GetBasic<EbtFloat, EbpMedium, 3>()));
constexpr std::array<float, 3> kDefaultMultiplier = {255, 255, 255};
TIntermConstantUnion *defaultMultiplier =
CreateVecNode(kDefaultMultiplier.data(), 3, EbpMedium);
TIntermDeclaration *roundMultiplierDecl =
CreateTempInitDeclarationNode(&roundMultiplier->variable(), defaultMultiplier);
ditherBlock->appendStatement(roundMultiplierDecl);
}
TIntermBlock *switchBody = new TIntermBlock;
// case kDitherControlDither4444:
@ -118,6 +137,16 @@ void EmitFragmentOutputDither(TCompiler *compiler,
switchBody->appendStatement(new TIntermCase(CreateUIntNode(vk::kDitherControlDither4444)));
switchBody->appendStatement(setDitherValue);
if (roundOutputAfterDithering)
{
constexpr std::array<float, 3> kMultiplier = {15, 15, 15};
TIntermConstantUnion *roundMultiplierValue =
CreateVecNode(kMultiplier.data(), 3, EbpMedium);
switchBody->appendStatement(
new TIntermBinary(EOpAssign, roundMultiplier->deepCopy(), roundMultiplierValue));
}
switchBody->appendStatement(new TIntermBranch(EOpBreak, nullptr));
}
@ -133,6 +162,16 @@ void EmitFragmentOutputDither(TCompiler *compiler,
switchBody->appendStatement(new TIntermCase(CreateUIntNode(vk::kDitherControlDither5551)));
switchBody->appendStatement(setDitherValue);
if (roundOutputAfterDithering)
{
constexpr std::array<float, 3> kMultiplier = {31, 31, 31};
TIntermConstantUnion *roundMultiplierValue =
CreateVecNode(kMultiplier.data(), 3, EbpMedium);
switchBody->appendStatement(
new TIntermBinary(EOpAssign, roundMultiplier->deepCopy(), roundMultiplierValue));
}
switchBody->appendStatement(new TIntermBranch(EOpBreak, nullptr));
}
@ -150,6 +189,16 @@ void EmitFragmentOutputDither(TCompiler *compiler,
switchBody->appendStatement(new TIntermCase(CreateUIntNode(vk::kDitherControlDither565)));
switchBody->appendStatement(setDitherValue);
if (roundOutputAfterDithering)
{
constexpr std::array<float, 3> kMultiplier = {31, 63, 31};
TIntermConstantUnion *roundMultiplierValue =
CreateVecNode(kMultiplier.data(), 3, EbpMedium);
switchBody->appendStatement(
new TIntermBinary(EOpAssign, roundMultiplier->deepCopy(), roundMultiplierValue));
}
switchBody->appendStatement(new TIntermBranch(EOpBreak, nullptr));
}
@ -166,9 +215,26 @@ void EmitFragmentOutputDither(TCompiler *compiler,
fragmentOutput = new TIntermSwizzle(fragmentOutput, {0, 1, 2});
}
ditherBlock->appendStatement(new TIntermBinary(EOpAddAssign, fragmentOutput, ditherValue));
// round() the output if workaround is enabled
if (roundOutputAfterDithering)
{
TVector<int> swizzle = {0, 1, 2};
swizzle.resize(fragmentOutput->getNominalSize());
TIntermTyped *multiplier = new TIntermSwizzle(roundMultiplier, swizzle);
// fragmentOutput.rgb = round(fragmentOutput.rgb * roundMultiplier) / roundMultiplier
TIntermTyped *scaledUp = new TIntermBinary(EOpMul, fragmentOutput->deepCopy(), multiplier);
TIntermTyped *rounded =
CreateBuiltInUnaryFunctionCallNode("round", scaledUp, *symbolTable, 300);
TIntermTyped *scaledDown = new TIntermBinary(EOpDiv, rounded, multiplier->deepCopy());
ditherBlock->appendStatement(
new TIntermBinary(EOpAssign, fragmentOutput->deepCopy(), scaledDown));
}
}
void EmitFragmentVariableDither(TCompiler *compiler,
ShCompileOptions compileOptions,
TSymbolTable *symbolTable,
TIntermBlock *ditherBlock,
TIntermTyped *ditherControl,
@ -192,8 +258,8 @@ void EmitFragmentVariableDither(TCompiler *compiler,
TIntermSymbol *fragmentOutputSymbol = new TIntermSymbol(&fragmentVariable);
if (!type.isArray())
{
EmitFragmentOutputDither(compiler, symbolTable, ditherBlock, ditherControl, ditherParam,
fragmentOutputSymbol, location);
EmitFragmentOutputDither(compiler, compileOptions, symbolTable, ditherBlock, ditherControl,
ditherParam, fragmentOutputSymbol, location);
return;
}
@ -201,12 +267,13 @@ void EmitFragmentVariableDither(TCompiler *compiler,
{
TIntermBinary *element = new TIntermBinary(EOpIndexDirect, fragmentOutputSymbol->deepCopy(),
CreateIndexNode(index));
EmitFragmentOutputDither(compiler, symbolTable, ditherBlock, ditherControl, ditherParam,
element, location + static_cast<uint32_t>(index));
EmitFragmentOutputDither(compiler, compileOptions, symbolTable, ditherBlock, ditherControl,
ditherParam, element, location + static_cast<uint32_t>(index));
}
}
TIntermNode *EmitDitheringBlock(TCompiler *compiler,
ShCompileOptions compileOptions,
TSymbolTable *symbolTable,
SpecConst *specConst,
DriverUniform *driverUniforms,
@ -358,8 +425,8 @@ TIntermNode *EmitDitheringBlock(TCompiler *compiler,
// Dither blocks for each fragment output
for (const TVariable *fragmentVariable : fragmentOutputVariables)
{
EmitFragmentVariableDither(compiler, symbolTable, ditherBlock, ditherControl, ditherParam,
*fragmentVariable);
EmitFragmentVariableDither(compiler, compileOptions, symbolTable, ditherBlock,
ditherControl, ditherParam, *fragmentVariable);
}
return new TIntermIfElse(ifAnyDitherCondition, ditherBlock, nullptr);
@ -367,6 +434,7 @@ TIntermNode *EmitDitheringBlock(TCompiler *compiler,
} // anonymous namespace
bool EmulateDithering(TCompiler *compiler,
ShCompileOptions compileOptions,
TIntermBlock *root,
TSymbolTable *symbolTable,
SpecConst *specConst,
@ -375,8 +443,8 @@ bool EmulateDithering(TCompiler *compiler,
FragmentOutputVariableList fragmentOutputVariables;
GatherFragmentOutputs(root, &fragmentOutputVariables);
TIntermNode *ditherCode = EmitDitheringBlock(compiler, symbolTable, specConst, driverUniforms,
fragmentOutputVariables);
TIntermNode *ditherCode = EmitDitheringBlock(compiler, compileOptions, symbolTable, specConst,
driverUniforms, fragmentOutputVariables);
return RunAtTheEndOfShader(compiler, root, ditherCode, symbolTable);
}

Просмотреть файл

@ -11,6 +11,7 @@
#define COMPILER_TRANSLATOR_TREEOPS_VULKAN_EMULATEDITHERING_H_
#include "common/angleutils.h"
#include "compiler/translator/Compiler.h"
namespace sh
{
@ -21,6 +22,7 @@ class SpecConst;
class DriverUniform;
ANGLE_NO_DISCARD bool EmulateDithering(TCompiler *compiler,
ShCompileOptions compileOptions,
TIntermBlock *root,
TSymbolTable *symbolTable,
SpecConst *specConst,

Просмотреть файл

@ -959,7 +959,7 @@ bool CompressAndStorePipelineCacheVk(VkPhysicalDeviceProperties physicalDevicePr
const size_t numChunks = UnsignedCeilDivide(static_cast<unsigned int>(compressedData.size()),
kMaxBlobCacheSize - kBlobHeaderSize);
size_t chunkSize = UnsignedCeilDivide(static_cast<unsigned int>(compressedData.size()),
static_cast<unsigned int>(numChunks));
static_cast<unsigned int>(numChunks));
for (size_t chunkIndex = 0; chunkIndex < numChunks; ++chunkIndex)
{
@ -2590,27 +2590,27 @@ angle::Result RendererVk::initializeDevice(DisplayVk *displayVk, uint32_t queueF
#else
if (getFeatures().supportsHostQueryReset.enabled)
{
InitHostQueryResetFunctions(mInstance);
InitHostQueryResetFunctions(mInstance);
}
if (hasGetMemoryRequirements2KHR)
{
InitGetMemoryRequirements2KHRFunctions(mDevice);
InitGetMemoryRequirements2KHRFunctions(mDevice);
}
if (hasBindMemory2KHR)
{
InitBindMemory2KHRFunctions(mDevice);
InitBindMemory2KHRFunctions(mDevice);
}
if (getFeatures().supportsTransformFeedbackExtension.enabled)
{
InitTransformFeedbackEXTFunctions(mDevice);
InitTransformFeedbackEXTFunctions(mDevice);
}
if (getFeatures().supportsYUVSamplerConversion.enabled)
{
InitSamplerYcbcrKHRFunctions(mDevice);
InitSamplerYcbcrKHRFunctions(mDevice);
}
if (getFeatures().supportsRenderpass2.enabled)
{
InitRenderPass2KHRFunctions(mDevice);
InitRenderPass2KHRFunctions(mDevice);
}
#endif // !defined(ANGLE_SHARED_LIBVULKAN)
@ -3417,6 +3417,9 @@ void RendererVk::initFeatures(DisplayVk *displayVk,
// (which would require Chromium and Capture/Replay test expectations updates).
ANGLE_FEATURE_CONDITION(&mFeatures, emulateDithering, IsAndroid());
// Workaround a Qualcomm imprecision with dithering
ANGLE_FEATURE_CONDITION(&mFeatures, roundOutputAfterDithering, isQualcomm);
// GL_KHR_blend_equation_advanced is emulated when the equivalent Vulkan extension is not
// usable. Additionally, the following platforms don't support INPUT_ATTACHMENT usage for the
// swapchain, so they are excluded:
@ -3644,7 +3647,7 @@ angle::Result RendererVk::syncPipelineCacheVk(DisplayVk *displayVk, const gl::Co
// kMaxTotalSize to 64k.
constexpr size_t kMaxTotalSize = 64 * 1024;
bool compressResult = CompressAndStorePipelineCacheVk(
mPhysicalDeviceProperties, displayVk, contextVk, pipelineCacheData, kMaxTotalSize);
mPhysicalDeviceProperties, displayVk, contextVk, pipelineCacheData, kMaxTotalSize);
if (compressResult)
{

Просмотреть файл

@ -109,6 +109,11 @@ std::shared_ptr<WaitableCompileEvent> ShaderVk::compile(const gl::Context *conte
compileOptions |= SH_GENERATE_SPIRV_THROUGH_GLSLANG;
}
if (contextVk->getFeatures().roundOutputAfterDithering.enabled)
{
compileOptions |= SH_ROUND_OUTPUT_AFTER_DITHERING;
}
return compileImpl(context, compilerInstance, mState.getSource(), compileOptions | options);
}

Просмотреть файл

@ -120,48 +120,6 @@
6277 ANDROID VULKAN : dEQP-EGL.functional.image.render_multiple_contexts.gles2_android_native_rgb8_read_pixels = FAIL
6277 ANDROID VULKAN : dEQP-EGL.functional.image.render_multiple_contexts.gles2_android_native_rgb8_texture = FAIL
// Pixel 4 Vulkan (still failing March 16, 2022)
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_clear_render_even_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_clear_render_even_clear_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_clear_render_even_clear_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_clear_render_even_none = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_clear_render_even_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_clear_render_even_render_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_clear_even_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_clear_even_clear_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_clear_even_clear_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_clear_even_none = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_clear_even_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_clear_even_render_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_clear_even_render_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_even_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_even_none = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_even_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_render_even_clear_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_render_even_none = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_render_even_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.buffer_age.no_preserve.*.odd_render_render_even_render_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_clear_render_even_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_clear_render_even_clear_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_clear_render_even_clear_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_clear_render_even_none = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_clear_render_even_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_clear_render_even_render_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_clear_even_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_clear_even_clear_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_clear_even_clear_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_clear_even_none = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_clear_even_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_clear_even_render_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_clear_even_render_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_even_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_even_none = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_even_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_render_even_clear_clear = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_render_even_none = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_render_even_render = FAIL
6953 PIXEL4ORXL VULKAN : dEQP-EGL.functional.partial_update.odd_render_render_even_render_render = FAIL
// Pixel 6 Vulkan
// These fail on the Android S bots, but pass on pre-release Android T drivers:
6899 PIXEL6 VULKAN : dEQP-EGL.functional.query_context.*.rgb888_window = FAIL

Просмотреть файл

@ -191,6 +191,7 @@ constexpr PackedEnumMap<Feature, const char *> kFeatureNames = {{
{Feature::RewriteUnaryMinusOperator, "rewriteUnaryMinusOperator"},
{Feature::RGBA4IsNotSupportedForColorRendering, "RGBA4IsNotSupportedForColorRendering"},
{Feature::RGBDXT1TexturesSampleZeroAlpha, "RGBDXT1TexturesSampleZeroAlpha"},
{Feature::RoundOutputAfterDithering, "roundOutputAfterDithering"},
{Feature::SanitizeAMDGPURendererString, "sanitizeAMDGPURendererString"},
{Feature::ScalarizeVecAndMatConstructorArgs, "scalarizeVecAndMatConstructorArgs"},
{Feature::SelectViewInGeometryShader, "selectViewInGeometryShader"},

Просмотреть файл

@ -180,6 +180,7 @@ enum class Feature
RewriteUnaryMinusOperator,
RGBA4IsNotSupportedForColorRendering,
RGBDXT1TexturesSampleZeroAlpha,
RoundOutputAfterDithering,
SanitizeAMDGPURendererString,
ScalarizeVecAndMatConstructorArgs,
SelectViewInGeometryShader,