1/*------------------------------------------------------------------------ 2 * Vulkan Conformance Tests 3 * ------------------------ 4 * 5 * Copyright (c) 2019 The Khronos Group Inc. 6 * Copyright (c) 2018 Google Inc. 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 * 20 *//*! 21 * \file 22 * \brief Invariant decoration tests. 23 *//*--------------------------------------------------------------------*/ 24 25#include "vktShaderRenderInvarianceTests.hpp" 26#include "vktShaderRender.hpp" 27#include "tcuImageCompare.hpp" 28#include "tcuStringTemplate.hpp" 29#include "tcuTextureUtil.hpp" 30#include "tcuTestLog.hpp" 31#include "vktDrawUtil.hpp" 32#include "deMath.h" 33#include "deRandom.hpp" 34 35using namespace vk; 36 37namespace vkt 38{ 39using namespace drawutil; 40 41namespace sr 42{ 43 44namespace 45{ 46 47class FormatArgument 48{ 49public: 50 FormatArgument (const char* name, const std::string& value); 51 52private: 53 friend class FormatArgumentList; 54 55 const char* const m_name; 56 const std::string m_value; 57}; 58 59FormatArgument::FormatArgument (const char* name, const std::string& value) 60 : m_name (name) 61 , m_value (value) 62{ 63} 64 65class FormatArgumentList 66{ 67public: 68 FormatArgumentList (void); 69 70 FormatArgumentList& operator<< (const FormatArgument&); 71 const std::map<std::string, std::string>& getArguments (void) const; 72 73private: 74 std::map<std::string, std::string> m_formatArguments; 75}; 76 77FormatArgumentList::FormatArgumentList (void) 78{ 79} 80 81FormatArgumentList& FormatArgumentList::operator<< (const FormatArgument& arg) 82{ 83 m_formatArguments[arg.m_name] = arg.m_value; 84 return *this; 85} 86 87const std::map<std::string, std::string>& FormatArgumentList::getArguments (void) const 88{ 89 return m_formatArguments; 90} 91 92static std::string formatGLSL(const char* templateString, const FormatArgumentList& args) 93{ 94 const std::map<std::string, std::string>& params = args.getArguments(); 95 96 return tcu::StringTemplate(std::string(templateString)).specialize(params); 97} 98 99class InvarianceTest : public vkt::TestCase 100{ 101public: 102 InvarianceTest(tcu::TestContext& ctx, const char* name, const std::string& vertexShader1, const std::string& vertexShader2, const std::string& fragmentShader = ""); 103 104 void initPrograms (SourceCollections& sourceCollections) const override; 105 vkt::TestInstance* createInstance (vkt::Context& context) const override; 106 107private: 108 const std::string m_vertexShader1; 109 const std::string m_vertexShader2; 110 const std::string m_fragmentShader; 111}; 112 113class InvarianceTestInstance : public vkt::TestInstance 114{ 115public: 116 InvarianceTestInstance(vkt::Context &context); 117 tcu::TestStatus iterate(void) override; 118 bool checkImage(const tcu::ConstPixelBufferAccess& image) const; 119 const int m_renderSize = 256; 120}; 121 122 InvarianceTest::InvarianceTest(tcu::TestContext& ctx, const char* name, const std::string& vertexShader1, const std::string& vertexShader2, const std::string& fragmentShader) 123 : vkt::TestCase(ctx, name) 124 , m_vertexShader1(vertexShader1) 125 , m_vertexShader2(vertexShader2) 126 , m_fragmentShader(fragmentShader) 127 128{ 129} 130 131void InvarianceTest::initPrograms(SourceCollections& sourceCollections) const 132{ 133 sourceCollections.glslSources.add("vertex1") << glu::VertexSource(m_vertexShader1); 134 sourceCollections.glslSources.add("vertex2") << glu::VertexSource(m_vertexShader2); 135 sourceCollections.glslSources.add("fragment") << glu::FragmentSource(m_fragmentShader); 136} 137 138vkt::TestInstance* InvarianceTest::createInstance(Context& context) const 139{ 140 return new InvarianceTestInstance(context); 141} 142 143InvarianceTestInstance::InvarianceTestInstance(vkt::Context &context) 144 : vkt::TestInstance(context) 145{ 146} 147 148static tcu::Vec4 genRandomVector(de::Random& rnd) 149{ 150 tcu::Vec4 retVal; 151 152 retVal.x() = rnd.getFloat(-1.0f, 1.0f); 153 retVal.y() = rnd.getFloat(-1.0f, 1.0f); 154 retVal.z() = rnd.getFloat(-1.0f, 1.0f); 155 retVal.w() = rnd.getFloat(0.2f, 1.0f); 156 157 return retVal; 158} 159 160struct ColorUniform 161{ 162 tcu::Vec4 color; 163}; 164 165tcu::TestStatus InvarianceTestInstance::iterate(void) 166{ 167 const VkDevice device = m_context.getDevice(); 168 const DeviceInterface& vk = m_context.getDeviceInterface(); 169 Allocator& allocator = m_context.getDefaultAllocator(); 170 tcu::TestLog& log = m_context.getTestContext().getLog(); 171 172 const int numTriangles = 72; 173 de::Random rnd (123); 174 std::vector<tcu::Vec4> vertices (numTriangles * 3 * 2); 175 176 { 177 // Narrow triangle pattern 178 for (int triNdx = 0; triNdx < numTriangles; ++triNdx) 179 { 180 const tcu::Vec4 vertex1 = genRandomVector(rnd); 181 const tcu::Vec4 vertex2 = genRandomVector(rnd); 182 const tcu::Vec4 vertex3 = vertex2 + genRandomVector(rnd) * 0.01f; // generate narrow triangles 183 184 vertices[triNdx * 3 + 0] = vertex1; 185 vertices[triNdx * 3 + 1] = vertex2; 186 vertices[triNdx * 3 + 2] = vertex3; 187 } 188 189 // Normal triangle pattern 190 for (int triNdx = 0; triNdx < numTriangles; ++triNdx) 191 { 192 vertices[(numTriangles + triNdx) * 3 + 0] = genRandomVector(rnd); 193 vertices[(numTriangles + triNdx) * 3 + 1] = genRandomVector(rnd); 194 vertices[(numTriangles + triNdx) * 3 + 2] = genRandomVector(rnd); 195 } 196 } 197 198 Move<VkDescriptorSetLayout> descriptorSetLayout; 199 Move<VkDescriptorPool> descriptorPool; 200 Move<VkBuffer> uniformBuffer[2]; 201 de::MovePtr<Allocation> uniformBufferAllocation[2]; 202 Move<VkDescriptorSet> descriptorSet[2]; 203 const tcu::Vec4 red = tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f); 204 const tcu::Vec4 green = tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f); 205 206 // Descriptors 207 { 208 DescriptorSetLayoutBuilder layoutBuilder; 209 layoutBuilder.addSingleBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT); 210 descriptorSetLayout = layoutBuilder.build(vk, device); 211 descriptorPool = DescriptorPoolBuilder() 212 .addType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2u) 213 .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 2u); 214 215 const VkDescriptorSetAllocateInfo descriptorSetAllocInfo = 216 { 217 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 218 DE_NULL, 219 *descriptorPool, 220 1u, 221 &descriptorSetLayout.get() 222 }; 223 224 const VkBufferCreateInfo uniformBufferCreateInfo = 225 { 226 VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType 227 DE_NULL, // const void* pNext 228 (VkBufferCreateFlags)0, // VkBufferCreateFlags flags 229 sizeof(ColorUniform), // VkDeviceSize size 230 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, // VkBufferUsageFlags usage 231 VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode 232 0u, // deUint32 queueFamilyIndexCount 233 DE_NULL // pQueueFamilyIndices 234 }; 235 236 for (deUint32 passNdx = 0; passNdx < 2; ++passNdx) 237 { 238 uniformBuffer[passNdx] = createBuffer(vk, device, &uniformBufferCreateInfo, DE_NULL); 239 uniformBufferAllocation[passNdx] = allocator.allocate(getBufferMemoryRequirements(vk, device, *uniformBuffer[passNdx]), MemoryRequirement::HostVisible); 240 VK_CHECK(vk.bindBufferMemory(device, *uniformBuffer[passNdx], uniformBufferAllocation[passNdx]->getMemory(), uniformBufferAllocation[passNdx]->getOffset())); 241 242 { 243 ColorUniform* bufferData = (ColorUniform*)(uniformBufferAllocation[passNdx]->getHostPtr()); 244 bufferData->color = (passNdx == 0) ? (red) : (green); 245 flushAlloc(vk, device, *uniformBufferAllocation[passNdx]); 246 } 247 descriptorSet[passNdx] = allocateDescriptorSet(vk, device, &descriptorSetAllocInfo); 248 249 const VkDescriptorBufferInfo bufferInfo = 250 { 251 *uniformBuffer[passNdx], 252 0u, 253 VK_WHOLE_SIZE 254 }; 255 256 DescriptorSetUpdateBuilder() 257 .writeSingle(*descriptorSet[passNdx], DescriptorSetUpdateBuilder::Location::binding(0u), VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, &bufferInfo) 258 .update(vk, device); 259 } 260 } 261 262 // pick first available depth buffer format 263 const std::vector<VkFormat> depthFormats { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_X8_D24_UNORM_PACK32, VK_FORMAT_D24_UNORM_S8_UINT }; 264 VkFormat depthFormat = VK_FORMAT_UNDEFINED; 265 const InstanceInterface& vki = m_context.getInstanceInterface(); 266 const VkPhysicalDevice vkPhysDevice = m_context.getPhysicalDevice(); 267 for (const auto& df : depthFormats) 268 { 269 const VkFormatProperties properties = getPhysicalDeviceFormatProperties(vki, vkPhysDevice, df); 270 if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) == VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) 271 { 272 depthFormat = df; 273 break; 274 } 275 } 276 if(depthFormat == VK_FORMAT_UNDEFINED) 277 return tcu::TestStatus::fail("There must be at least one depth depth format handled (Vulkan spec 37.3, table 65)"); 278 279 FrameBufferState frameBufferState(m_renderSize, m_renderSize); 280 frameBufferState.depthFormat = depthFormat; 281 PipelineState pipelineState(m_context.getDeviceProperties().limits.subPixelPrecisionBits); 282 DrawCallData drawCallData(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, vertices); 283 VulkanDrawContext vulkanDrawContext(m_context, frameBufferState); 284 285 const std::vector<std::string> vertexShaderNames = { "vertex1", "vertex2" }; 286 287 log << tcu::TestLog::Message << "Testing position invariance." << tcu::TestLog::EndMessage; 288 289 for (deUint32 passNdx = 0; passNdx < 2; ++passNdx) 290 { 291 std::vector<VulkanShader> shaders; 292 shaders.push_back(VulkanShader(VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get(vertexShaderNames[passNdx]))); 293 shaders.push_back(VulkanShader(VK_SHADER_STAGE_FRAGMENT_BIT, m_context.getBinaryCollection().get("fragment"))); 294 VulkanProgram vulkanProgram(shaders); 295 vulkanProgram.descriptorSetLayout = *descriptorSetLayout; 296 vulkanProgram.descriptorSet = *descriptorSet[passNdx]; 297 298 const char* const colorStr = (passNdx == 0) ? ("red - purple") : ("green"); 299 log << tcu::TestLog::Message << "Drawing position test pattern using shader " << (passNdx + 1) << ". Primitive color: " << colorStr << "." << tcu::TestLog::EndMessage; 300 301 vulkanDrawContext.registerDrawObject(pipelineState, vulkanProgram, drawCallData); 302 } 303 vulkanDrawContext.draw(); 304 305 tcu::ConstPixelBufferAccess resultImage( 306 tcu::TextureFormat(vulkanDrawContext.getColorPixels().getFormat()), 307 vulkanDrawContext.getColorPixels().getWidth(), 308 vulkanDrawContext.getColorPixels().getHeight(), 309 1, 310 vulkanDrawContext.getColorPixels().getDataPtr()); 311 312 log << tcu::TestLog::Message << "Verifying output. Expecting only green or background colored pixels." << tcu::TestLog::EndMessage; 313 if( !checkImage(resultImage) ) 314 return tcu::TestStatus::fail("Detected variance between two invariant values"); 315 316 return tcu::TestStatus::pass("Passed"); 317} 318 319bool InvarianceTestInstance::checkImage(const tcu::ConstPixelBufferAccess& image) const 320{ 321 const tcu::IVec4 okColor (0, 255, 0, 255); 322 const tcu::RGBA errColor (255, 0, 0, 255); 323 bool error = false; 324 tcu::Surface errorMask (image.getWidth(), image.getHeight()); 325 326 tcu::clear(errorMask.getAccess(), okColor); 327 328 for (int y = 0; y < m_renderSize; ++y) 329 for (int x = 0; x < m_renderSize; ++x) 330 { 331 const tcu::IVec4 col = image.getPixelInt(x, y); 332 333 if (col.x() != 0) 334 { 335 errorMask.setPixel(x, y, errColor); 336 error = true; 337 } 338 } 339 340 // report error 341 if (error) 342 { 343 m_context.getTestContext().getLog() << tcu::TestLog::Message << "Invalid pixels found (fragments from first render pass found). Variance detected." << tcu::TestLog::EndMessage; 344 m_context.getTestContext().getLog() 345 << tcu::TestLog::ImageSet("Results", "Result verification") 346 << tcu::TestLog::Image("Result", "Result", image) 347 << tcu::TestLog::Image("Error mask", "Error mask", errorMask) 348 << tcu::TestLog::EndImageSet; 349 350 return false; 351 } 352 else 353 { 354 m_context.getTestContext().getLog() << tcu::TestLog::Message << "No variance found." << tcu::TestLog::EndMessage; 355 m_context.getTestContext().getLog() 356 << tcu::TestLog::ImageSet("Results", "Result verification") 357 << tcu::TestLog::Image("Result", "Result", image) 358 << tcu::TestLog::EndImageSet; 359 360 return true; 361 } 362} 363 364} // namespace 365 366tcu::TestCaseGroup* createShaderInvarianceTests (tcu::TestContext& testCtx) 367{ 368 de::MovePtr<tcu::TestCaseGroup> invarianceGroup(new tcu::TestCaseGroup(testCtx, "invariance")); 369 370 static const struct PrecisionCase 371 { 372 glu::Precision prec; 373 const char* name; 374 375 // set literals in the glsl to be in the representable range 376 const char* highValue; // !< highValue < maxValue 377 const char* invHighValue; 378 const char* mediumValue; // !< mediumValue^2 < maxValue 379 const char* lowValue; // !< lowValue^4 < maxValue 380 const char* invlowValue; 381 int loopIterations; 382 int loopPartialIterations; 383 int loopNormalizationExponent; 384 const char* loopNormalizationConstantLiteral; 385 const char* loopMultiplier; 386 const char* sumLoopNormalizationConstantLiteral; 387 } precisions[] = 388 { 389 { glu::PRECISION_HIGHP, "highp", "1.0e20", "1.0e-20", "1.0e14", "1.0e9", "1.0e-9", 14, 11, 2, "1.0e4", "1.9", "1.0e3" }, 390 { glu::PRECISION_MEDIUMP, "mediump", "1.0e4", "1.0e-4", "1.0e2", "1.0e1", "1.0e-1", 13, 11, 2, "1.0e4", "1.9", "1.0e3" }, 391 { glu::PRECISION_LOWP, "lowp", "0.9", "1.1", "1.1", "1.15", "0.87", 6, 2, 0, "2.0", "1.1", "1.0" }, 392 }; 393 394 // gl_Position must always be invariant for comparisons on gl_Position to be valid. 395 static const std::string invariantDeclaration[] = { "invariant gl_Position;", "invariant gl_Position;\nlayout(location = 1) invariant highp out vec4 v_value;" }; 396 static const std::string invariantAssignment0[] = { "gl_Position", "v_value" }; 397 static const std::string invariantAssignment1[] = { "", "gl_Position = v_value;" }; 398 static const std::string fragDeclaration[] = { "", "layout(location = 1) highp in vec4 v_value;" }; 399 400 static const char* basicFragmentShader = "${VERSION}" 401 "precision mediump float;\n" 402 "${IN} vec4 v_unrelated;\n" 403 "${FRAG_DECLARATION}\n" 404 "layout(binding = 0) uniform ColorUniform\n" 405 "{\n" 406 " vec4 u_color;\n" 407 "} ucolor;\n" 408 "layout(location = 0) out vec4 fragColor;\n" 409 "void main ()\n" 410 "{\n" 411 " float blue = dot(v_unrelated, vec4(1.0, 1.0, 1.0, 1.0));\n" 412 " fragColor = vec4(ucolor.u_color.r, ucolor.u_color.g, blue, ucolor.u_color.a);\n" 413 "}\n"; 414 415 for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); ++precNdx) 416 { 417 const char* const precisionName = precisions[precNdx].name; 418 const glu::Precision precision = precisions[precNdx].prec; 419 // Invariance tests using the given precision. 420 tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(testCtx, precisionName); 421 422 const deUint32 VAR_GROUP_SIZE = 2u; 423 tcu::TestCaseGroup* varGroup[VAR_GROUP_SIZE]; 424 varGroup[0] = new tcu::TestCaseGroup(testCtx, "gl_position", "Invariance tests using gl_Position variable"); 425 varGroup[1] = new tcu::TestCaseGroup(testCtx, "user_defined", "Invariance tests using user defined variable"); 426 FormatArgumentList args[VAR_GROUP_SIZE]; 427 for (deUint32 groupNdx = 0u; groupNdx < VAR_GROUP_SIZE; ++groupNdx) 428 { 429 group->addChild(varGroup[groupNdx]); 430 args[groupNdx] = FormatArgumentList() 431 << FormatArgument("VERSION", "#version 450\n") 432 << FormatArgument("IN", "layout(location = 0) in") 433 << FormatArgument("OUT", "layout(location = 0) out") 434 << FormatArgument("IN_PREC", precisionName) 435 << FormatArgument("INVARIANT_DECLARATION", invariantDeclaration[groupNdx]) 436 << FormatArgument("INVARIANT_ASSIGN_0", invariantAssignment0[groupNdx]) 437 << FormatArgument("INVARIANT_ASSIGN_1", invariantAssignment1[groupNdx]) 438 << FormatArgument("FRAG_DECLARATION", fragDeclaration[groupNdx]) 439 << FormatArgument("HIGH_VALUE", de::toString(precisions[precNdx].highValue)) 440 << FormatArgument("HIGH_VALUE_INV", de::toString(precisions[precNdx].invHighValue)) 441 << FormatArgument("MEDIUM_VALUE", de::toString(precisions[precNdx].mediumValue)) 442 << FormatArgument("LOW_VALUE", de::toString(precisions[precNdx].lowValue)) 443 << FormatArgument("LOW_VALUE_INV", de::toString(precisions[precNdx].invlowValue)) 444 << FormatArgument("LOOP_ITERS", de::toString(precisions[precNdx].loopIterations)) 445 << FormatArgument("LOOP_ITERS_PARTIAL", de::toString(precisions[precNdx].loopPartialIterations)) 446 << FormatArgument("LOOP_NORM_FRACT_EXP", de::toString(precisions[precNdx].loopNormalizationExponent)) 447 << FormatArgument("LOOP_NORM_LITERAL", precisions[precNdx].loopNormalizationConstantLiteral) 448 << FormatArgument("LOOP_MULTIPLIER", precisions[precNdx].loopMultiplier) 449 << FormatArgument("SUM_LOOP_NORM_LITERAL", precisions[precNdx].sumLoopNormalizationConstantLiteral); 450 } 451 452 // subexpression cases 453 for (deUint32 groupNdx = 0u; groupNdx < VAR_GROUP_SIZE; ++groupNdx) 454 { 455 // First shader shares "${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy" with unrelated output variable. Reordering might result in accuracy loss 456 // due to the high exponent. In the second shader, the high exponent may be removed during compilation. 457 458 // Shader shares a subexpression with an unrelated variable. 459 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "common_subexpression_0", 460 formatGLSL("${VERSION}" 461 "${IN} ${IN_PREC} vec4 a_input;\n" 462 "${OUT} mediump vec4 v_unrelated;\n" 463 "${INVARIANT_DECLARATION}\n" 464 "void main ()\n" 465 "{\n" 466 " v_unrelated = a_input.xzxz + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * (1.08 * a_input.zyzy * a_input.xzxz) * ${HIGH_VALUE_INV} * (a_input.z * a_input.zzxz - a_input.z * a_input.zzxz) + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) / ${HIGH_VALUE};\n" 467 " ${INVARIANT_ASSIGN_0} = a_input + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * ${HIGH_VALUE_INV};\n" 468 " ${INVARIANT_ASSIGN_1}\n" 469 "}\n", args[groupNdx]), 470 formatGLSL("${VERSION}" 471 "${IN} ${IN_PREC} vec4 a_input;\n" 472 "${OUT} mediump vec4 v_unrelated;\n" 473 "${INVARIANT_DECLARATION}\n" 474 "void main ()\n" 475 "{\n" 476 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 477 " ${INVARIANT_ASSIGN_0} = a_input + (${HIGH_VALUE}*a_input.x*a_input.xxxx + ${HIGH_VALUE}*a_input.y*a_input.yyyy) * ${HIGH_VALUE_INV};\n" 478 " ${INVARIANT_ASSIGN_1}\n" 479 "}\n", args[groupNdx]), 480 formatGLSL(basicFragmentShader, args[groupNdx]))); 481 482 // In the first shader, the unrelated variable "d" has mathematically the same expression as "e", but the different 483 // order of calculation might cause different results. 484 485 // Shader shares a subexpression with an unrelated variable. 486 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "common_subexpression_1", 487 formatGLSL("${VERSION}" 488 "${IN} ${IN_PREC} vec4 a_input;\n" 489 "${OUT} mediump vec4 v_unrelated;\n" 490 "${INVARIANT_DECLARATION}\n" 491 "void main ()\n" 492 "{\n" 493 " ${IN_PREC} vec4 a = ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy - ${HIGH_VALUE} * a_input.zzxx;\n" 494 " ${IN_PREC} vec4 b = ${HIGH_VALUE} * a_input.zzxx;\n" 495 " ${IN_PREC} vec4 c = b - ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy;\n" 496 " ${IN_PREC} vec4 d = (${LOW_VALUE} * a_input.yzxx) * (${LOW_VALUE} * a_input.yzzw) * (1.1*${LOW_VALUE_INV} * a_input.yzxx) * (${LOW_VALUE_INV} * a_input.xzzy);\n" 497 " ${IN_PREC} vec4 e = ((${LOW_VALUE} * a_input.yzxx) * (1.1*${LOW_VALUE_INV} * a_input.yzxx)) * ((${LOW_VALUE_INV} * a_input.xzzy) * (${LOW_VALUE} * a_input.yzzw));\n" 498 " v_unrelated = a + b + c + d + e;\n" 499 " ${INVARIANT_ASSIGN_0} = a_input + fract(c) + e;\n" 500 " ${INVARIANT_ASSIGN_1}\n" 501 "}\n", args[groupNdx]), 502 formatGLSL("${VERSION}" 503 "${IN} ${IN_PREC} vec4 a_input;\n" 504 "${OUT} mediump vec4 v_unrelated;\n" 505 "${INVARIANT_DECLARATION}\n" 506 "void main ()\n" 507 "{\n" 508 " ${IN_PREC} vec4 b = ${HIGH_VALUE} * a_input.zzxx;\n" 509 " ${IN_PREC} vec4 c = b - ${HIGH_VALUE} * a_input.zzxx + a_input.xzxy;\n" 510 " ${IN_PREC} vec4 e = ((${LOW_VALUE} * a_input.yzxx) * (1.1*${LOW_VALUE_INV} * a_input.yzxx)) * ((${LOW_VALUE_INV} * a_input.xzzy) * (${LOW_VALUE} * a_input.yzzw));\n" 511 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 512 " ${INVARIANT_ASSIGN_0} = a_input + fract(c) + e;\n" 513 " ${INVARIANT_ASSIGN_1}\n" 514 "}\n", args[groupNdx]), 515 formatGLSL(basicFragmentShader, args[groupNdx]))); 516 517 // Intermediate values used by an unrelated output variable 518 519 // Shader shares a subexpression with an unrelated variable. 520 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "common_subexpression_2", 521 formatGLSL("${VERSION}" 522 "${IN} ${IN_PREC} vec4 a_input;\n" 523 "${OUT} mediump vec4 v_unrelated;\n" 524 "${INVARIANT_DECLARATION}\n" 525 "void main ()\n" 526 "{\n" 527 " ${IN_PREC} vec4 a = ${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy);\n" 528 " ${IN_PREC} vec4 b = (${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy)) * (${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy)) / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n" 529 " ${IN_PREC} vec4 c = a * a;\n" 530 " ${IN_PREC} vec4 d = c / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n" 531 " v_unrelated = a + b + c + d;\n" 532 " ${INVARIANT_ASSIGN_0} = a_input + d;\n" 533 " ${INVARIANT_ASSIGN_1}\n" 534 "}\n", args[groupNdx]), 535 formatGLSL("${VERSION}" 536 "${IN} ${IN_PREC} vec4 a_input;\n" 537 "${OUT} mediump vec4 v_unrelated;\n" 538 "${INVARIANT_DECLARATION}\n" 539 "void main ()\n" 540 "{\n" 541 " ${IN_PREC} vec4 a = ${MEDIUM_VALUE} * (a_input.xxxx + a_input.yyyy);\n" 542 " ${IN_PREC} vec4 c = a * a;\n" 543 " ${IN_PREC} vec4 d = c / ${MEDIUM_VALUE} / ${MEDIUM_VALUE};\n" 544 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 545 " ${INVARIANT_ASSIGN_0} = a_input + d;\n" 546 " ${INVARIANT_ASSIGN_1}\n" 547 "}\n", args[groupNdx]), 548 formatGLSL(basicFragmentShader, args[groupNdx]))); 549 550 // Invariant value can be calculated using unrelated value 551 552 // Shader shares a subexpression with an unrelated variable. 553 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "common_subexpression_3", 554 formatGLSL("${VERSION}" 555 "${IN} ${IN_PREC} vec4 a_input;\n" 556 "${OUT} mediump vec4 v_unrelated;\n" 557 "${INVARIANT_DECLARATION}\n" 558 "void main ()\n" 559 "{\n" 560 " ${IN_PREC} float x = a_input.x * 0.2;\n" 561 " ${IN_PREC} vec4 a = a_input.xxyx * 0.7;\n" 562 " ${IN_PREC} vec4 b = a_input.yxyz * 0.7;\n" 563 " ${IN_PREC} vec4 c = a_input.zxyx * 0.5;\n" 564 " ${IN_PREC} vec4 f = x*a + x*b + x*c;\n" 565 " v_unrelated = f;\n" 566 " ${IN_PREC} vec4 g = x * (a + b + c);\n" 567 " ${INVARIANT_ASSIGN_0} = a_input + g;\n" 568 " ${INVARIANT_ASSIGN_1}\n" 569 "}\n", args[groupNdx]), 570 formatGLSL("${VERSION}" 571 "${IN} ${IN_PREC} vec4 a_input;\n" 572 "${OUT} mediump vec4 v_unrelated;\n" 573 "${INVARIANT_DECLARATION}\n" 574 "void main ()\n" 575 "{\n" 576 " ${IN_PREC} float x = a_input.x * 0.2;\n" 577 " ${IN_PREC} vec4 a = a_input.xxyx * 0.7;\n" 578 " ${IN_PREC} vec4 b = a_input.yxyz * 0.7;\n" 579 " ${IN_PREC} vec4 c = a_input.zxyx * 0.5;\n" 580 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 581 " ${IN_PREC} vec4 g = x * (a + b + c);\n" 582 " ${INVARIANT_ASSIGN_0} = a_input + g;\n" 583 " ${INVARIANT_ASSIGN_1}\n" 584 "}\n", args[groupNdx]), 585 formatGLSL(basicFragmentShader, args[groupNdx]))); 586 } 587 588 // shared subexpression of different precision 589 for (deUint32 groupNdx = 0u; groupNdx < VAR_GROUP_SIZE; ++groupNdx) 590 { 591 for (int precisionOther = glu::PRECISION_LOWP; precisionOther != glu::PRECISION_LAST; ++precisionOther) 592 { 593 const char* const unrelatedPrec = glu::getPrecisionName((glu::Precision)precisionOther); 594 const glu::Precision minPrecision = (precisionOther < (int)precision) ? ((glu::Precision)precisionOther) : (precision); 595 const char* const multiplierStr = (minPrecision == glu::PRECISION_LOWP) ? ("0.8, 0.4, -0.2, 0.3") : ("1.0e1, 5.0e2, 2.0e2, 1.0"); 596 const char* const normalizationStrUsed = (minPrecision == glu::PRECISION_LOWP) ? ("vec4(fract(used2).xyz, 0.0)") : ("vec4(fract(used2 / 1.0e2).xyz - fract(used2 / 1.0e3).xyz, 0.0)"); 597 const char* const normalizationStrUnrelated = (minPrecision == glu::PRECISION_LOWP) ? ("vec4(fract(unrelated2).xyz, 0.0)") : ("vec4(fract(unrelated2 / 1.0e2).xyz - fract(unrelated2 / 1.0e3).xyz, 0.0)"); 598 599 // Shader shares subexpression of different precision with an unrelated variable. 600 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, ("subexpression_precision_" + std::string(unrelatedPrec)).c_str(), 601 formatGLSL("${VERSION}" 602 "${IN} ${IN_PREC} vec4 a_input;\n" 603 "${OUT} ${UNRELATED_PREC} vec4 v_unrelated;\n" 604 "${INVARIANT_DECLARATION}\n" 605 "void main ()\n" 606 "{\n" 607 " ${UNRELATED_PREC} vec4 unrelated0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n" 608 " ${UNRELATED_PREC} vec4 unrelated1 = vec4(${MULTIPLIER}) * unrelated0.xywz + unrelated0;\n" 609 " ${UNRELATED_PREC} vec4 unrelated2 = refract(unrelated1, unrelated0, distance(unrelated0, unrelated1));\n" 610 " v_unrelated = a_input + 0.02 * ${NORMALIZE_UNRELATED};\n" 611 " ${IN_PREC} vec4 used0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n" 612 " ${IN_PREC} vec4 used1 = vec4(${MULTIPLIER}) * used0.xywz + used0;\n" 613 " ${IN_PREC} vec4 used2 = refract(used1, used0, distance(used0, used1));\n" 614 " ${INVARIANT_ASSIGN_0} = a_input + 0.02 * ${NORMALIZE_USED};\n" 615 " ${INVARIANT_ASSIGN_1}\n" 616 "}\n", FormatArgumentList(args[groupNdx]) 617 << FormatArgument("UNRELATED_PREC", unrelatedPrec) 618 << FormatArgument("MULTIPLIER", multiplierStr) 619 << FormatArgument("NORMALIZE_USED", normalizationStrUsed) 620 << FormatArgument("NORMALIZE_UNRELATED", normalizationStrUnrelated)), 621 formatGLSL("${VERSION}" 622 "${IN} ${IN_PREC} vec4 a_input;\n" 623 "${OUT} ${UNRELATED_PREC} vec4 v_unrelated;\n" 624 "${INVARIANT_DECLARATION}\n" 625 "void main ()\n" 626 "{\n" 627 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 628 " ${IN_PREC} vec4 used0 = a_input + vec4(0.1, 0.2, 0.3, 0.4);\n" 629 " ${IN_PREC} vec4 used1 = vec4(${MULTIPLIER}) * used0.xywz + used0;\n" 630 " ${IN_PREC} vec4 used2 = refract(used1, used0, distance(used0, used1));\n" 631 " ${INVARIANT_ASSIGN_0} = a_input + 0.02 * ${NORMALIZE_USED};\n" 632 " ${INVARIANT_ASSIGN_1}\n" 633 "}\n", FormatArgumentList(args[groupNdx]) 634 << FormatArgument("UNRELATED_PREC", unrelatedPrec) 635 << FormatArgument("MULTIPLIER", multiplierStr) 636 << FormatArgument("NORMALIZE_USED", normalizationStrUsed) 637 << FormatArgument("NORMALIZE_UNRELATED", normalizationStrUnrelated)), 638 formatGLSL("${VERSION}" 639 "precision mediump float;\n" 640 "${IN} ${UNRELATED_PREC} vec4 v_unrelated;\n" 641 "${FRAG_DECLARATION}\n" 642 "layout(binding = 0) uniform ColorUniform\n" 643 "{\n" 644 " vec4 u_color;\n" 645 "} ucolor;\n" 646 "${OUT} vec4 fragColor;\n" 647 "void main ()\n" 648 "{\n" 649 " float blue = dot(v_unrelated, vec4(1.0, 1.0, 1.0, 1.0));\n" 650 " fragColor = vec4(ucolor.u_color.r, ucolor.u_color.g, blue, ucolor.u_color.a);\n" 651 "}\n", FormatArgumentList(args[groupNdx]) 652 << FormatArgument("UNRELATED_PREC", unrelatedPrec) 653 << FormatArgument("MULTIPLIER", multiplierStr) 654 << FormatArgument("NORMALIZE_USED", normalizationStrUsed) 655 << FormatArgument("NORMALIZE_UNRELATED", normalizationStrUnrelated)))); 656 } 657 } 658 659 // loops 660 for (deUint32 groupNdx = 0u; groupNdx < VAR_GROUP_SIZE; ++groupNdx) 661 { 662 // Invariant value set using a loop 663 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_0", 664 formatGLSL("${VERSION}" 665 "${IN} ${IN_PREC} vec4 a_input;\n" 666 "${OUT} highp vec4 v_unrelated;\n" 667 "${INVARIANT_DECLARATION}\n" 668 "void main ()\n" 669 "{\n" 670 " ${IN_PREC} vec4 value = a_input;\n" 671 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 672 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 673 " {\n" 674 " value *= ${LOOP_MULTIPLIER};\n" 675 " v_unrelated += value;\n" 676 " }\n" 677 " ${INVARIANT_ASSIGN_0} = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n" 678 " ${INVARIANT_ASSIGN_1}\n" 679 "}\n", args[groupNdx]), 680 formatGLSL("${VERSION}" 681 "${IN} ${IN_PREC} vec4 a_input;\n" 682 "${OUT} highp vec4 v_unrelated;\n" 683 "${INVARIANT_DECLARATION}\n" 684 "void main ()\n" 685 "{\n" 686 " ${IN_PREC} vec4 value = a_input;\n" 687 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 688 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 689 " {\n" 690 " value *= ${LOOP_MULTIPLIER};\n" 691 " }\n" 692 " ${INVARIANT_ASSIGN_0} = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n" 693 " ${INVARIANT_ASSIGN_1}\n" 694 "}\n", args[groupNdx]), 695 formatGLSL("${VERSION}" 696 "precision mediump float;\n" 697 "layout(location=0) in highp vec4 v_unrelated;\n" 698 "${FRAG_DECLARATION}\n" 699 "layout(binding = 0) uniform ColorUniform\n" 700 "{\n" 701 " vec4 u_color;\n" 702 "} ucolor;\n" 703 "layout(location = 0) out vec4 fragColor;\n" 704 "void main ()\n" 705 "{\n" 706 " float blue = dot(v_unrelated, vec4(1.0, 1.0, 1.0, 1.0));\n" 707 " fragColor = vec4(ucolor.u_color.r, ucolor.u_color.g, blue, ucolor.u_color.a);\n" 708 "}\n", args[groupNdx]))); 709 710 // Invariant value set using a loop 711 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_1", 712 formatGLSL("${VERSION}" 713 "${IN} ${IN_PREC} vec4 a_input;\n" 714 "${OUT} mediump vec4 v_unrelated;\n" 715 "${INVARIANT_DECLARATION}\n" 716 "void main ()\n" 717 "{\n" 718 " ${IN_PREC} vec4 value = a_input;\n" 719 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 720 " {\n" 721 " value *= ${LOOP_MULTIPLIER};\n" 722 " if (i == ${LOOP_ITERS_PARTIAL})\n" 723 " v_unrelated = value;\n" 724 " }\n" 725 " ${INVARIANT_ASSIGN_0} = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n" 726 " ${INVARIANT_ASSIGN_1}\n" 727 "}\n", args[groupNdx]), 728 formatGLSL("${VERSION}" 729 "${IN} ${IN_PREC} vec4 a_input;\n" 730 "${OUT} mediump vec4 v_unrelated;\n" 731 "${INVARIANT_DECLARATION}\n" 732 "void main ()\n" 733 "{\n" 734 " ${IN_PREC} vec4 value = a_input;\n" 735 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 736 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 737 " {\n" 738 " value *= ${LOOP_MULTIPLIER};\n" 739 " }\n" 740 " ${INVARIANT_ASSIGN_0} = vec4(value.xyz / ${LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n" 741 " ${INVARIANT_ASSIGN_1}\n" 742 "}\n", args[groupNdx]), 743 formatGLSL(basicFragmentShader, args[groupNdx]))); 744 745 // Invariant value set using a loop 746 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_2", 747 formatGLSL("${VERSION}" 748 "${IN} ${IN_PREC} vec4 a_input;\n" 749 "${OUT} mediump vec4 v_unrelated;\n" 750 "${INVARIANT_DECLARATION}\n" 751 "void main ()\n" 752 "{\n" 753 " ${IN_PREC} vec4 value = a_input;\n" 754 " v_unrelated = vec4(0.0, 0.0, -1.0, 1.0);\n" 755 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 756 " {\n" 757 " value *= ${LOOP_MULTIPLIER};\n" 758 " if (i == ${LOOP_ITERS_PARTIAL})\n" 759 " ${INVARIANT_ASSIGN_0} = a_input + 0.05 * vec4(fract(value.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n" 760 " else\n" 761 " v_unrelated = value + a_input;\n" 762 " ${INVARIANT_ASSIGN_1}\n" 763 " }\n" 764 "}\n", args[groupNdx]), 765 formatGLSL("${VERSION}" 766 "${IN} ${IN_PREC} vec4 a_input;\n" 767 "${OUT} mediump vec4 v_unrelated;\n" 768 "${INVARIANT_DECLARATION}\n" 769 "void main ()\n" 770 "{\n" 771 " ${IN_PREC} vec4 value = a_input;\n" 772 " v_unrelated = vec4(0.0, 0.0, -1.0, 1.0);\n" 773 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 774 " {\n" 775 " value *= ${LOOP_MULTIPLIER};\n" 776 " if (i == ${LOOP_ITERS_PARTIAL})\n" 777 " ${INVARIANT_ASSIGN_0} = a_input + 0.05 * vec4(fract(value.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n" 778 " else\n" 779 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 780 " ${INVARIANT_ASSIGN_1}\n" 781 " }\n" 782 "}\n", args[groupNdx]), 783 formatGLSL(basicFragmentShader, args[groupNdx]))); 784 785 // Invariant value set using a loop 786 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_3", 787 formatGLSL("${VERSION}" 788 "${IN} ${IN_PREC} vec4 a_input;\n" 789 "${OUT} mediump vec4 v_unrelated;\n" 790 "${INVARIANT_DECLARATION}\n" 791 "void main ()\n" 792 "{\n" 793 " ${IN_PREC} vec4 value = a_input;\n" 794 " ${INVARIANT_ASSIGN_0} = vec4(0.0, 0.0, 0.0, 0.0);\n" 795 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 796 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 797 " {\n" 798 " value *= ${LOOP_MULTIPLIER};\n" 799 " ${INVARIANT_ASSIGN_0} += vec4(value.xyz / ${SUM_LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n" 800 " v_unrelated = ${INVARIANT_ASSIGN_0}.xyzx * a_input;\n" 801 " }\n" 802 " ${INVARIANT_ASSIGN_1}\n" 803 "}\n", args[groupNdx]), 804 formatGLSL("${VERSION}" 805 "${IN} ${IN_PREC} vec4 a_input;\n" 806 "${OUT} mediump vec4 v_unrelated;\n" 807 "${INVARIANT_DECLARATION}\n" 808 "void main ()\n" 809 "{\n" 810 " ${IN_PREC} vec4 value = a_input;\n" 811 " ${INVARIANT_ASSIGN_0} = vec4(0.0, 0.0, 0.0, 0.0);\n" 812 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 813 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 814 " {\n" 815 " value *= ${LOOP_MULTIPLIER};\n" 816 " ${INVARIANT_ASSIGN_0} += vec4(value.xyz / ${SUM_LOOP_NORM_LITERAL} + a_input.xyz * 0.1, 1.0);\n" 817 " }\n" 818 " ${INVARIANT_ASSIGN_1}\n" 819 "}\n", args[groupNdx]), 820 formatGLSL(basicFragmentShader, args[groupNdx]))); 821 822 // Invariant value set using a loop 823 varGroup[groupNdx]->addChild(new InvarianceTest(testCtx, "loop_4", 824 formatGLSL("${VERSION}" 825 "${IN} ${IN_PREC} vec4 a_input;\n" 826 "${OUT} mediump vec4 v_unrelated;\n" 827 "${INVARIANT_DECLARATION}\n" 828 "void main ()\n" 829 "{\n" 830 " ${IN_PREC} vec4 position = vec4(0.0, 0.0, 0.0, 0.0);\n" 831 " ${IN_PREC} vec4 value1 = a_input;\n" 832 " ${IN_PREC} vec4 value2 = a_input;\n" 833 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 834 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 835 " {\n" 836 " value1 *= ${LOOP_MULTIPLIER};\n" 837 " v_unrelated = v_unrelated*1.3 + a_input.xyzx * value1.xyxw;\n" 838 " }\n" 839 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 840 " {\n" 841 " value2 *= ${LOOP_MULTIPLIER};\n" 842 " position = position*1.3 + a_input.xyzx * value2.xyxw;\n" 843 " }\n" 844 " ${INVARIANT_ASSIGN_0} = a_input + 0.05 * vec4(fract(position.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n" 845 " ${INVARIANT_ASSIGN_1}\n" 846 "}\n", args[groupNdx]), 847 formatGLSL("${VERSION}" 848 "${IN} ${IN_PREC} vec4 a_input;\n" 849 "${OUT} mediump vec4 v_unrelated;\n" 850 "${INVARIANT_DECLARATION}\n" 851 "void main ()\n" 852 "{\n" 853 " ${IN_PREC} vec4 position = vec4(0.0, 0.0, 0.0, 0.0);\n" 854 " ${IN_PREC} vec4 value2 = a_input;\n" 855 " v_unrelated = vec4(0.0, 0.0, 0.0, 0.0);\n" 856 " for (mediump int i = 0; i < ${LOOP_ITERS}; ++i)\n" 857 " {\n" 858 " value2 *= ${LOOP_MULTIPLIER};\n" 859 " position = position*1.3 + a_input.xyzx * value2.xyxw;\n" 860 " }\n" 861 " ${INVARIANT_ASSIGN_0} = a_input + 0.05 * vec4(fract(position.xyz / 1.0e${LOOP_NORM_FRACT_EXP}), 1.0);\n" 862 " ${INVARIANT_ASSIGN_1}\n" 863 "}\n", args[groupNdx]), 864 formatGLSL(basicFragmentShader, args[groupNdx]))); 865 } 866 invarianceGroup->addChild(group); 867 } 868 return invarianceGroup.release(); 869} 870 871} // namespace sr 872 873} // namespace vkt 874