From 45f115b931e530f15ee72ef0227c3472ebf06bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 31 May 2021 18:10:08 +0200 Subject: [PATCH] Shaders: separate instanced colored and textured test cases. With everything smashed together it was quite annoying to eyeball and too easy to miss issues. --- src/Magnum/Shaders/Test/CMakeLists.txt | 2 + src/Magnum/Shaders/Test/FlatGLTest.cpp | 236 +++++++++++------- .../FlatTestFiles/instanced-textured2D.tga | Bin 0 -> 2936 bytes .../FlatTestFiles/instanced-textured3D.tga | Bin 0 -> 2609 bytes .../Test/FlatTestFiles/instanced2D.tga | Bin 2630 -> 1058 bytes .../Test/FlatTestFiles/instanced3D.tga | Bin 2086 -> 1034 bytes src/Magnum/Shaders/Test/PhongGLTest.cpp | 204 ++++++++------- .../PhongTestFiles/instanced-textured.tga | Bin 0 -> 6512 bytes .../Shaders/Test/PhongTestFiles/instanced.tga | Bin 9748 -> 6448 bytes 9 files changed, 270 insertions(+), 172 deletions(-) create mode 100644 src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured2D.tga create mode 100644 src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured3D.tga create mode 100644 src/Magnum/Shaders/Test/PhongTestFiles/instanced-textured.tga diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index da3ceb234..277a3da9f 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -152,6 +152,8 @@ if(BUILD_GL_TESTS) FlatTestFiles/defaults.tga FlatTestFiles/instanced2D.tga FlatTestFiles/instanced3D.tga + FlatTestFiles/instanced-textured2D.tga + FlatTestFiles/instanced-textured3D.tga FlatTestFiles/textured2D.tga FlatTestFiles/textured3D.tga FlatTestFiles/textured2D-alpha.tga diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index 5b68c7978..5a50d4e87 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -166,21 +166,21 @@ struct FlatGLTest: GL::OpenGLTester { Mesa Intel BADIOM ES2 xx - ES3 BADIOx - Mesa AMD BADI - Mesa llvmpipe BADI - SwiftShader ES2 BADIxx - ES3 BADI + ES3 BAD Ox + Mesa AMD BAD + Mesa llvmpipe BAD + SwiftShader ES2 BAD xx + ES3 BAD ANGLE ES2 xx - ES3 BADIOM + ES3 BAD OM ARM Mali (Huawei P10) ES2 BAD xx - ES3 BADIOx + ES3 BAD Ox WebGL (on Mesa Intel) 1.0 BAD xx - 2.0 BADIOM + 2.0 BAD OM NVidia BAD Intel Windows BAD AMD macOS BAD - Intel macOS BADIOx + Intel macOS BAD Ox iPhone 6 w/ iOS 12.4 ES3 BAD x */ @@ -274,6 +274,23 @@ const struct { FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask, 1.0f} }; +constexpr struct { + const char* name; + const char* expected2D; + const char* expected3D; + FlatGL2D::Flags flags; + Float maxThreshold, meanThreshold; +} RenderInstancedData[] { + {"colored", "instanced2D.tga", "instanced3D.tga", + {}, + /* Minor differences on SwiftShader */ + 164.4f, 0.094f}, + {"textured", "instanced-textured2D.tga", "instanced-textured3D.tga", + FlatGL2D::Flag::InstancedTextureOffset|FlatGL2D::Flag::Textured, + /* Minor differences on SwiftShader */ + 192.67f, 0.140f}, +}; + #ifndef MAGNUM_TARGET_GLES2 constexpr struct { const char* name; @@ -462,7 +479,7 @@ FlatGLTest::FlatGLTest() { #endif /* MSVC needs explicit type due to default template args */ - addTests({ + addInstancedTests({ &FlatGLTest::renderInstanced2D, #ifndef MAGNUM_TARGET_GLES2 &FlatGLTest::renderInstanced2D, @@ -472,6 +489,7 @@ FlatGLTest::FlatGLTest() { &FlatGLTest::renderInstanced3D #endif }, + Containers::arraySize(RenderInstancedData), #ifndef MAGNUM_TARGET_GLES2 &FlatGLTest::renderObjectIdSetup, &FlatGLTest::renderObjectIdTeardown @@ -2090,6 +2108,9 @@ template void FlatGLTest::renderObjectId3D() { #endif template void FlatGLTest::renderInstanced2D() { + auto&& data = RenderInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 if(flag == FlatGL2D::Flag::UniformBuffers) { setTestCaseTemplateName("Flag::UniformBuffers"); @@ -2116,10 +2137,6 @@ template void FlatGLTest::renderInstanced2D() { #endif #endif - if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || - !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) - CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates)); @@ -2130,11 +2147,14 @@ template void FlatGLTest::renderInstanced2D() { Vector2 textureOffset; UnsignedInt objectId; } instanceData[] { - {Matrix3::translation({-1.25f, -1.25f}), 0xff3333_rgbf, + {Matrix3::translation({-1.25f, -1.25f}), + data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf, {0.0f, 0.0f}, 211}, - {Matrix3::translation({ 1.25f, -1.25f}), 0x33ff33_rgbf, + {Matrix3::translation({ 1.25f, -1.25f}), + data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0x00ffff_rgbf, {1.0f, 0.0f}, 4627}, - {Matrix3::translation({ 0.00f, 1.25f}), 0x9999ff_rgbf, + {Matrix3::translation({ 0.00f, 1.25f}), + data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xff00ff_rgbf, {0.5f, 1.0f}, 35363}, }; @@ -2151,22 +2171,9 @@ template void FlatGLTest::renderInstanced2D() { ) .setInstanceCount(3); - Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); - CORRADE_VERIFY(importer); - - GL::Texture2D texture; - Containers::Optional image; - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); - /* Enable also Object ID, if supported */ - FlatGL2D::Flags flags = FlatGL2D::Flag::Textured| - FlatGL2D::Flag::VertexColor|FlatGL2D::Flag::InstancedTransformation| - FlatGL2D::Flag::InstancedTextureOffset|flag; + FlatGL2D::Flags flags = FlatGL2D::Flag::VertexColor| + FlatGL2D::Flag::InstancedTransformation|data.flags|flag; #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES if(GL::Context::current().isExtensionSupported()) @@ -2176,14 +2183,36 @@ template void FlatGLTest::renderInstanced2D() { } #endif FlatGL2D shader{flags}; - shader.bindTexture(texture); + + GL::Texture2D texture; + if(data.flags & FlatGL3D::Flag::Textured) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + + shader.bindTexture(texture); + } if(flag == FlatGL2D::Flag{}) { - shader.setColor(0xffff99_rgbf) + shader + .setColor(data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf) .setTransformationProjectionMatrix( Matrix3::projection({2.1f, 2.1f})* - Matrix3::scaling(Vector2{0.4f})) - .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})); + Matrix3::scaling(Vector2{0.4f})); + + if(data.flags & FlatGL3D::Flag::Textured) + shader.setTextureMatrix(Matrix3::scaling(Vector2{0.5f})); #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES @@ -2215,11 +2244,12 @@ template void FlatGLTest::renderInstanced2D() { }}; GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { FlatMaterialUniform{} - .setColor(0xffff99_rgbf) + .setColor(data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf) }}; + if(data.flags & FlatGL3D::Flag::Textured) + shader.bindTextureTransformationBuffer(textureTransformationUniform); shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) - .bindTextureTransformationBuffer(textureTransformationUniform) .bindMaterialBuffer(materialUniform) .draw(circle); } @@ -2228,18 +2258,29 @@ template void FlatGLTest::renderInstanced2D() { MAGNUM_VERIFY_NO_GL_ERROR(); - #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) - /* Minor differences on AMD, SwiftShader a bit more */ - const Float maxThreshold = 3.0f, meanThreshold = 0.018f; - #else - /* WebGL 1 doesn't have 8bit renderbuffer storage */ - const Float maxThreshold = 3.0f, meanThreshold = 0.018f; - #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + Colored case: + + - First should be lower left, yellow with a yellow base color, so + yellow + - Second lower right, cyan with a yellow base color, so green + - Third up center, magenta with a yellow base color, so red + + Textured case: + + - Lower left has bottom left numbers, so light 7881 + - Lower light has bottom right, 1223 + - Up center has 6778 + */ CORRADE_COMPARE_WITH( /* Dropping the alpha channel, as it's always 1.0 */ Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), - Utility::Directory::join(_testDir, "FlatTestFiles/instanced2D.tga"), - (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); + Utility::Directory::join({_testDir, "FlatTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); #ifndef MAGNUM_TARGET_GLES2 /* Object ID -- no need to verify the whole image, just check that pixels @@ -2263,6 +2304,9 @@ template void FlatGLTest::renderInstanced2D() { } template void FlatGLTest::renderInstanced3D() { + auto&& data = RenderInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 if(flag == FlatGL2D::Flag::UniformBuffers) { setTestCaseTemplateName("Flag::UniformBuffers"); @@ -2289,10 +2333,6 @@ template void FlatGLTest::renderInstanced3D() { #endif #endif - if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || - !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) - CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); @@ -2303,11 +2343,17 @@ template void FlatGLTest::renderInstanced3D() { Vector2 textureOffset; UnsignedInt objectId; } instanceData[] { - {Matrix4::translation({-1.25f, -1.25f, 0.0f}), 0xff3333_rgbf, + {Matrix4::translation({-1.25f, -1.25f, 0.0f})* + /* To be consistent with Phong's output where it tests that the + normal matrix is applied properly */ + Matrix4::rotationX(90.0_degf), + data.flags & FlatGL3D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf, {0.0f, 0.0f}, 211}, - {Matrix4::translation({ 1.25f, -1.25f, 0.0f}), 0x33ff33_rgbf, + {Matrix4::translation({ 1.25f, -1.25f, 0.0f}), + data.flags & FlatGL3D::Flag::Textured ? 0xffffff_rgbf : 0x00ffff_rgbf, {1.0f, 0.0f}, 4627}, - {Matrix4::translation({ 0.0f, 1.0f, 1.0f}), 0x9999ff_rgbf, + {Matrix4::translation({ 0.0f, 1.0f, 1.0f}), + data.flags & FlatGL3D::Flag::Textured ? 0xffffff_rgbf : 0xff00ff_rgbf, {0.5f, 1.0f}, 35363} }; @@ -2324,22 +2370,9 @@ template void FlatGLTest::renderInstanced3D() { ) .setInstanceCount(3); - Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); - CORRADE_VERIFY(importer); - - GL::Texture2D texture; - Containers::Optional image; - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); - /* Enable also Object ID, if supported */ - FlatGL3D::Flags flags = FlatGL3D::Flag::Textured| - FlatGL3D::Flag::VertexColor|FlatGL3D::Flag::InstancedTransformation| - FlatGL3D::Flag::InstancedTextureOffset|flag; + FlatGL3D::Flags flags = FlatGL3D::Flag::VertexColor| + FlatGL3D::Flag::InstancedTransformation|data.flags|flag; #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES if(GL::Context::current().isExtensionSupported()) @@ -2349,15 +2382,37 @@ template void FlatGLTest::renderInstanced3D() { } #endif FlatGL3D shader{flags}; - shader.bindTexture(texture); + + GL::Texture2D texture; + if(data.flags & FlatGL2D::Flag::Textured) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + + shader.bindTexture(texture); + } if(flag == FlatGL3D::Flag{}) { - shader.setColor(0xffff99_rgbf) + shader + .setColor(data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf) .setTransformationProjectionMatrix( Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::scaling(Vector3{0.4f})) - .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})); + Matrix4::scaling(Vector3{0.4f})); + + if(data.flags & FlatGL3D::Flag::Textured) + shader.setTextureMatrix(Matrix3::scaling(Vector2{0.5f})); #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES @@ -2390,11 +2445,12 @@ template void FlatGLTest::renderInstanced3D() { }}; GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { FlatMaterialUniform{} - .setColor(0xffff99_rgbf) + .setColor(data.flags & FlatGL3D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf) }}; + if(data.flags & FlatGL3D::Flag::Textured) + shader.bindTextureTransformationBuffer(textureTransformationUniform); shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) - .bindTextureTransformationBuffer(textureTransformationUniform) .bindMaterialBuffer(materialUniform) .draw(sphere); } @@ -2403,18 +2459,30 @@ template void FlatGLTest::renderInstanced3D() { MAGNUM_VERIFY_NO_GL_ERROR(); - #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) - /* Minor differences on AMD, SwiftShader a bit more */ - const Float maxThreshold = 67.67f, meanThreshold = 0.062f; - #else - /* WebGL 1 doesn't have 8bit renderbuffer storage */ - const Float maxThreshold = 67.67f, meanThreshold = 0.062f; - #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + Colored case: + + - First should be lower left, yellow with a yellow base color, so + yellow + - Second lower right, cyan with a yellow base color, so green + - Third up center, magenta with a yellow base color, so red + + Textured case: + + - Lower left has bottom left numbers, so light 7881, rotated (78 + visible) + - Lower light has bottom right, 1223 + - Up center has 6778 + */ CORRADE_COMPARE_WITH( /* Dropping the alpha channel, as it's always 1.0 */ Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), - Utility::Directory::join(_testDir, "FlatTestFiles/instanced3D.tga"), - (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); + Utility::Directory::join({_testDir, "FlatTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); #ifndef MAGNUM_TARGET_GLES2 /* Object ID -- no need to verify the whole image, just check that pixels diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured2D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured2D.tga new file mode 100644 index 0000000000000000000000000000000000000000..01359db39146a6fbbef7cafa5293338ead551265 GIT binary patch literal 2936 zcmcIlTT@g=5Z-f^g{r*ymVa^B-I;UtqCX&(DTxx>XVfFkVnm z41~lOQ>GNbNWc|EZlWf!Dh2YAs^lfVBHuTAc0C&MkW{&~U8kp~`|F3XtZ!-n;!7xKi~+FIhTh0zP-qrZd`#*;b9Pk>U`OJVOUdF-XINBMD3274Lo zeL)_38SGJh4AhN1ZFO}OpCqUUYW)!R*br{x@k40iPoYg7D9+^Xp6bnxT)vc!bz)fz zP5X0p_}ZzqvUt-Td{!sV0}oa?P&puXj|R3ji^v%>a@wk!4nYzJ@f_uqOZTnhQF*4< z%5?k2t`$6HU`Jw?f=k*S zDoRHI6FBs7=~!u)kYaSwm(4tT_A9t-CQ(SsD^B8Y!sM|4`u3FEH_Kyil?I|-Id|9U zXaUy^aw3-@_IO8Y=aJ7whlhaAj5%U|2{!t3c<;U2)f`3ds`*F{fBcw^(a`?5XF zjUSGW-L#`uUREa0pFFzX;e~m2S7+pwsQ>*5R+j?exyA5>P_#DC(u4&YN&y2r2fQ2* zSc@E#bFX_3zUW8!gRh)dESF+=4SOn2zt0T)c6$KmTrnjcv{3ai^R1meHquj;gtvk{ z5Jym&@8;Fe&T4mfM-@Jmvr~{!o{?0$7l)53a~r1Lv`C7uw_UM?rqI!^X*2zjj1m5%b_YvuXq%cp8_uiD&A&d z>eC~sKOTGujw_Y%hNt1G7E!+(-hCyo_a|nf8?+8`8d$ntj0y1_xNaMnV@^fe)%czR zwxWI^RMXFt*5yOHzG4#ahi(?6O8FwY%x8sHEa8K{U)&O6w7^4`(X;&J0Ka;&wZDBo z{+*xfTALb#gyV;U(zBz_+#Z+PVzS|}?TE9R!%P>0r-RKLo2(>#l_NctOa)|XKz4$M z2)aB7Ulu+koHuUdyA*XFH(=faT`Dm>e46+yspkoM8SGK6o;4dpJ!{xwRVXJ8W9HPk zNFIBvJLT&=pHA6`L!bO*5T8q8lfMMw6UjKp6Q412-(5H+I0y%AQrR&UXMEScftoWr z0nrg;c3OR6s$j#no1{u7hB$PK1{HpB!z<>XhZ+Xt5yjBYd7C)aG%MZZu<`h0-Y@Pz z73H>MFBmqI(?Sn6HWZ#dH5Re!#tjUb2Sf5BEZEspKL=tYy7$ga@6tB4{eM_92ByR<<=L>TmEr;x6eIztFU5?^ z9s^tgR7?-M(j&xmsqUf_SA43(YDhs%i*8xZ!X1!p$RKR-*Zg7{pOQ&?jnjn1V}3kA zd05IppO{g%alv7s4;?U;;xvkZip@bIP3g|-QVx5?JOn&U3QGEPyK(V=(}Z2i-qMBm zUWlvI1Erx`mfLGAbW<09Xwd=h2DM!n5a_p)_!Q2=Q!qc}`F7)6l>h!ZqqojBr4!ya mnrekn#(wC50mbTSVx7U7yt=wrGh*_?AfA8@$UCpSlm8po#?W5? literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured3D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured3D.tga new file mode 100644 index 0000000000000000000000000000000000000000..9ec1a93c8cb12a94cff6eed7557f0bc1e6b33e66 GIT binary patch literal 2609 zcmb_dTT@e46wW?@7oX&<|AKSk2_Ya-hPEhlxL9u0Qb6QViaLW7MhBG;2!sFu3WApc zf)wioDz$Rc87{35637XC<)O7R?bu(@?>otXh|`&NrVMMc&-%Xg?X}lgYaf&8h{^cN zHRYP#GX>+~;{J^dfaj>*O%}zW^1pmjPS`ED+Tw#7W|x)^1pjdK!@LCW*a6ZI)*T@6 znf_i_JJxPs{aCw!i7TN@xSTM;JrRodEK~#*nT+frvoBZz7RAWwhPkj7IiV)7x?psB z$ei5y5(u{<%LD6R6ewH^b!-?cGU=wPi|+HANqD5D!R!I06VHTc=F%VRykaMukx(raF{d=uN2f zB^^YPD=G?3;-?KJg*OJ-6%i0$?51EB9*i0^6V@^1i+22+8M9SsOrG`Dwd%`HL&pJ& zg_eKD`_lu@>-BLwM?_u7>S+AW8}hVI_H4;lx8z#CeAchz2b7$Ek{PfEXOpzY$F=D+ z?eyaHXbb=4SJ>w`t>H_rhrFTYH%y8~=Dy$qHbPZWz1NI7kfCi$`pE*VK|H ztf$cfv9H(0$KCa7)9%1*{~O=bAFn22p^6_oyd^K>#x02U9uhV~tZc!4POlLgSF8ot zPQ9>p9INjTU&?7vOCBYHsL#ODZO*_yk=X#E^hYMFv&VL7z-E(+-*;D_&Ki5MgE*zN zMc>Fyj^z4WSPa)_D+0VG{!q ztb{K0& z*Ee)0Su5#+2{pI~geZ}_L@nw{1eGjmz#3ct$hSoB4hf372?DDurKc<>=x>?>NANZY zHme&TP5R)(l3kkt$gcrGeuTs^bq*k#3xvfmfmb_0onTR47=)~}IlJ}*pg~}9glc^X z0$WYWPM=NI9z?c~WZ!B+mlKp>7^!Ts15P)1^zI`$Mzn!2j$4QI&>Mw( z7oLg#1u4pyepgN4R7iq%nWPi$BMGJ`;^#Jz%gsnW=72MPpt3eI?HQPQL5niJP0`3^ z#m({0H|2|dU{Nuxh>2ajmNZaRd*PEl95dOz4hGu?vXVc&E!W*%yvQjOhVbksgl6pgSUJw}XmXZXydUCzR7v<0R0_3F;)W2qcdPM~vQ47^@|{XgSU@gl`XeIjZepBw5KI!fS%w W37C_}n!~18S^HKPz0tS9;q~7PSt+al literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/instanced2D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/instanced2D.tga index a130de40ebf0ee24b2176d9c04a294f2af228c87..11dec3675a0c0b9b796563b0a1652e2b165fa7f6 100644 GIT binary patch literal 1058 zcmd6l!D@s+5Jc;oo40({{#4?cAc=?=qb?!&pnQ`1DwBm~X^#sl6tAlsTBm2E2VI_n z4tmsYRaKeZ$Qy0jTqyh2A{z^3|B5=YzEJirs3+?RW#6EI%zdEjk7y)wA1M18O=Ru^ zWuIlw&Hs zERyusT9GO-^2-T0!#^M-@=5u!pg~>KPa=Pe&vUz5XM`9{Xx_Jd&-*^-InQ~|xwnd9 zDE?*oU!$*Rnpe7%%6r=P^+MJjxlN#)R;d7ucB5( z?Ni36RZ(O9LwFE1&+d?+2Gkj3YwHX_?dL4OFk)+P6{ybM?=~ks>X4V(VkgTOQ&Tve zS$W7k(Pr<*cdX|kQj``3k$KfLW)1VKX?|u}-&(%%uy#A6m%FsH3B4NAuBWgF7pTHD zPto1huDRIOKNvs!do;Vs?Q@Gtd;HxZe!0B@_ygtz)4XnS*X)Sa)Qmu%u^akHyTl0~ zQ6o;ZtM4~NkvK^2Y$WDh*c+-07!}i6waN&Wz!K1I_39r)3kb>NvxT6st!utDdb~qk zW;=Y1f1|Q~v0C0UNL9xCkoIFnA8*qOQSGOUPgntI5%WXQ?Duy1YeXH%m5qdDXk&}G z(|~!-v~GFLY-qdDtzB`8;3iFz%I>+*nYl@-hBi_JoXkpleA;I#f=xyLtThXfv1nMg zJ=%uOA2OiSZuV%W<34#A3s6=t;?y?TQ+E0qM;vJ|&@W?<|2Spv?*ws4b)gN>XYt^m3A#XjbRo8M`npz$}&1+9Qb2-*?p7*1S zrGtjk-?Dj|5zUn_;=!`Rs&*}h;RQIlMW zrcRNWT&kHKu(E@QtSv)WrMS+|hAaBXC|?d`%nxXJSIhM<^lyzv)#1!;pxUucl^HbM z?f0UpkV?xwMu`71pWv?0aHb?Md>mMH)6`B>EA7$4qW#&^N?a z3J<|=5=DhPvLqrHsd!Rh&~zl`aoJ(5_C&0AnJZAC3R)wUhe0RCd<}-cNn3XHS=;Zv zik9j5sGjd?aFf@2&4Q?giDxjbVO9-g=Dk-rYE{&{S2=1`)R_1F0#K`<=KTer#$#dr z6FA?O?D`jsQDc{xU;7vT%A315S4@Z#jIY2e&f;7JW5$=@Rg9|`GbVp+y&g=61*Boj zF2h5p(B_Z3QwK>?LNN?jQjDhLGcZ@oAVn~tiVgYCbFd{F) zOGGE1d)7y*t~@5hJ3t6=G%3dmh=^-qQ%^ijY#I5WloKiWRJYv4of2^YUPd*Lm_Cx2 zIm(b~RjBQj!>*VY;w>o;5>6qW!6p`{lpF=Z71LN?BbHlJlY4h}_iv-+A9dfJ z;s!i1LLNxVd`dpa70G-aUPrZ#G2{O=#@zbCUZ*+#Ka8n?TG*zv+#$szb(X;vSlHt{ zKj1L*+rVwALQF|<0Fe}L;K(?+0*o^J$Um_uIf70B0mB7oVn=dKEKh{zr>5l7Qck*J zHYrD4@xG4@ytd1Mk|LJykC^aK1106_z)~_b1ZGB@{X3gjUY`(3Fd+^L@#YeuCE!9Y z4m$ID97HgpcrC*XWKxbL<;(bnaB3(45peUeS21SnJzN-5B;yshPtN#$59^;rL7VIV diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/instanced3D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/instanced3D.tga index c1c4d964064b9bcf88aae8d73c376f36f49753c6..0aa0bb4d55178ea989f21f5ac0fd9e78161f4221 100644 GIT binary patch literal 1034 zcmcJNOK!qI5Cwakop(7FmqHfd7soipB!q|@h$A^wV}nL0ma+lqsb0Sx$0d_nJH z2E$slWQ&2fY)@8_!IatW#teq_RCeIpoIbprdvxX;_jmp)a}UnzY%uM`%)K~|d$D$h zJ2E{?J016%GWTrloA@4j-^2P9t;s65Axm$CUy{`W!`=?f$^0L{u)bva!Iu%e{6=fM zJfSUK9#M^#pQyph57gr20d@F9dw9R8u6~a{lFKRI(mQcRKH5*+Anf)J literal 2086 zcmbu9?{8C87{||T$SG_!T{D4gy|?YUb-U+ohDbUCnL{B40&Hwy#!QWg!Ktvx26N!r zu5Z@0x4#R5!KsJ=jlPg*Vq$z@Oic8JZ)n*oU--xP{od|w#Snv$+!K1f-_P@$AJ20p zM4NE`O^GSdCF+4d;7P`Z;5@qd6~1nDW4&kiuMH^cvTk}h8Uf#?4zEhd3;#nu8Tu8BEaJs}l`Dc_T#-zm-kAkMzl@P1vsYayfuU9+ z^YnwgIWY7~7&e$Pe6fG);T0aG? zUxL@ISka5LA zYRK%{^t4jHPpb;TSl?E@23`cMpSg&TSIydEm}_9@U;B@q z*kh%cpRvp~4he*yDE1$+TNmjK^f$ERn{&y0p__ z%}X42X-&o>-Vwb_RMum>Gh!UVH*nYnUgibp=9jz_UJ%pKXVbdAGl$1`>TGbQUTxM= zk?~#85*aMV&9OW@2U~+E-%+-?vy&{Z=+u{}6ZejE=kXs=Vtihrtt-9`oKN98=iZTh zHkd3J`qVN^*KVEV)7F9mw*%ehZ#2>FELZo|``I!?Z(?Y`@nt~s&qM}@1{)vUdds*+ z+X$yJ`sxS#{RM^yg_p9>a?m)xL(K>$bI@#PoZqI_7a7Y#6XDgxJalTGQ4fXRr>=#< zsmSU=0T$&?o7PH!mKnPQs|btoC$xGaXN$B{>{2v+p#+OE_b@ZfvS6*M89)1zNpF^(m% zVHaHkx@-A#-^|zxM-RS&Q}jizu%22oh^5}8UtQ7PT`^`73>`?VwS?OU_al7%#PGRe zBd;DgGClgz)Zr5ohfYn69`Ai^!5B{%Clki;gmE-!97-Aon84mj;9NBlXcxx^jpvd) z(Dqe7vCLwZNiU2K7$Zp**6o39Ile8kb4o59Px5*wvDb<_GTV(3yR$%faUy0sn`DDh zVn641WVSS=&oOu(+U1FU9F(V|#FvtFWh=!8MCmR@hVt^6UL%s?Tcgza2LJi{Z+{{c Af&c&j diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index 100e2a576..b4389bbc4 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -49,6 +49,7 @@ #include "Magnum/GL/TextureFormat.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix4.h" +#include "Magnum/Math/Swizzle.h" #include "Magnum/MeshTools/Compile.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/Primitives/Plane.h" @@ -173,21 +174,21 @@ struct PhongGLTest: GL::OpenGLTester { Mesa Intel BADLIOM ES2 xx - ES3 BADLIOx - Mesa AMD BAD I - Mesa llvmpipe BAD I - SwiftShader ES2 BADLIxx - ES3 BADLI + ES3 BADL Ox + Mesa AMD BAD + Mesa llvmpipe BAD + SwiftShader ES2 BADL xx + ES3 BADL ANGLE ES2 xx - ES3 BADLIOM + ES3 BADL OM ARM Mali (Huawei P10) ES2 BAD xx - ES3 BADLIOx + ES3 BADL Ox WebGL (on Mesa Intel) 1.0 BAD xx - 2.0 BADLIOM + 2.0 BADL OM NVidia BAD Intel Windows BAD AMD macOS BAD - Intel macOS BADLIOx + Intel macOS BADL Ox iPhone 6 w/ iOS 12.4 ES3 BAD x */ @@ -555,24 +556,14 @@ constexpr struct { PhongGL::Flags flags; Float maxThreshold, meanThreshold; } RenderInstancedData[] { - {"diffuse", "instanced.tga", {}, - #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) - /* AMD has one off pixel; SwiftShader a bit more */ - 96.34f, 0.113f, - #else - /* WebGL 1 doesn't have 8bit renderbuffer storage */ - 96.34f, 0.113f, - #endif - }, - {"diffuse + normal", "instanced-normal.tga", PhongGL::Flag::NormalTexture, - #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) - /* AMD has one off pixel, llvmpipe more */ - 96.0f, 0.333f, - #else - /* WebGL 1 doesn't have 8bit renderbuffer storage */ - 96.0f, 0.333f, - #endif - } + {"diffuse color", "instanced.tga", {}, + /* Minor differences on SwiftShader */ + 81.0f, 0.06f}, + {"diffuse texture", "instanced-textured.tga", + PhongGL::Flag::DiffuseTexture|PhongGL::Flag::InstancedTextureOffset, + /* Minor differences on SwiftShader */ + 112.0f, 0.09f}, + /** @todo test normal when there's usable texture */ }; #ifndef MAGNUM_TARGET_GLES2 @@ -614,6 +605,7 @@ constexpr struct { 4, 2, 3, 1, /* Minor differences on ARM Mali */ 4.67f, 0.02f}, + /** @todo test normal and per-draw scaling when there's usable texture */ }; #endif @@ -2833,16 +2825,13 @@ template void PhongGLTest::renderInstanced() { #endif #endif - if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || - !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) - CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates| Primitives::UVSphereFlag::Tangents)); - /* Three spheres, each in a different location, differently rotated to - ensure the normal matrix is properly used as well. */ + /* Three spheres, each in a different location. To test normal matrix + concatenation, everything is rotated 90° on Y, thus X is now -Z and Z is + now X. */ struct { Matrix4 transformation; Matrix3x3 normal; @@ -2850,18 +2839,22 @@ template void PhongGLTest::renderInstanced() { Vector2 textureOffset; UnsignedInt objectId; } instanceData[] { - {Matrix4::translation({-1.25f, -1.25f, 0.0f})* - Matrix4::rotationX(90.0_degf), - {}, 0xff3333_rgbf, {0.0f, 0.0f}, 211}, - {Matrix4::translation({ 1.25f, -1.25f, 0.0f})* - Matrix4::rotationY(90.0_degf), - {}, 0x33ff33_rgbf, {1.0f, 0.0f}, 4627}, - {Matrix4::translation({ 0.0f, 1.0f, 1.0f})* - Matrix4::rotationZ(90.0_degf), - {}, 0x9999ff_rgbf, {0.5f, 1.0f}, 35363} + {Matrix4::translation(Math::gather<'z', 'y', 'x'>(Vector3{-1.25f, -1.25f, 0.0f}))*Matrix4::rotationY(-90.0_degf)*Matrix4::rotationX(90.0_degf), + /* to test also per-instance normal matrix is applied properly -- + the texture should look the same as in the case of Flat 3D + instanced textured */ + (Matrix4::rotationY(-90.0_degf)*Matrix4::rotationX(90.0_degf)).normalMatrix(), + data.flags & PhongGL::Flag::DiffuseTexture ? 0xffffff_rgbf : 0xffff00_rgbf, + {0.0f, 0.0f}, 211}, + {Matrix4::translation(Math::gather<'z', 'y', 'x'>(Vector3{ 1.25f, -1.25f, 0.0f})), + {}, + data.flags & PhongGL::Flag::DiffuseTexture ? 0xffffff_rgbf : 0x00ffff_rgbf, + {1.0f, 0.0f}, 4627}, + {Matrix4::translation(Math::gather<'z', 'y', 'x'>(Vector3{ 0.0f, 1.0f, -1.0f})), + {}, + data.flags & PhongGL::Flag::DiffuseTexture ? 0xffffff_rgbf : 0xff00ff_rgbf, + {0.5f, 1.0f}, 35363} }; - for(auto& instance: instanceData) - instance.normal = instance.transformation.normalMatrix(); sphere .addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, @@ -2877,31 +2870,9 @@ template void PhongGLTest::renderInstanced() { ) .setInstanceCount(3); - Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); - CORRADE_VERIFY(importer); - - Containers::Optional image; - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - GL::Texture2D diffuse; - diffuse.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); - - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/normal-texture.tga")) && (image = importer->image2D(0))); - GL::Texture2D normal; - normal.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); - /* Enable also Object ID, if supported */ - PhongGL::Flags flags = PhongGL::Flag::DiffuseTexture| - PhongGL::Flag::VertexColor| - PhongGL::Flag::InstancedTransformation| - PhongGL::Flag::InstancedTextureOffset|data.flags|flag; + PhongGL::Flags flags = PhongGL::Flag::VertexColor| + PhongGL::Flag::InstancedTransformation|data.flags|flag; #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES if(GL::Context::current().isExtensionSupported()) @@ -2911,25 +2882,58 @@ template void PhongGLTest::renderInstanced() { } #endif PhongGL shader{flags, 2}; - shader.bindDiffuseTexture(diffuse); - if(data.flags & PhongGL::Flag::NormalTexture) - shader.bindNormalTexture(normal); + + GL::Texture2D diffuse; + GL::Texture2D normal; + if(data.flags & (PhongGL::Flag::DiffuseTexture|PhongGL::Flag::NormalTexture)) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + if(data.flags & PhongGL::Flag::DiffuseTexture) { + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + diffuse.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindDiffuseTexture(diffuse); + } + + if(data.flags & PhongGL::Flag::NormalTexture) { + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/normal-texture.tga")) && (image = importer->image2D(0))); + normal.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindNormalTexture(normal); + } + } if(flag == PhongGL::Flag{}) { shader .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setLightColors({0x999999_rgbf, 0x999999_rgbf}) + .setLightSpecularColors({0x0000ff_rgbf, 0x00ff00_rgbf}) .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-1.75f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(90.0_degf)* Matrix4::scaling(Vector3{0.4f})) - .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setNormalMatrix(Matrix4::rotationY(90.0_degf).normalMatrix()) .setProjectionMatrix( Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) - .setDiffuseColor(0xffff99_rgbf); + .setDiffuseColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0xffff00_rgbf); + + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.setTextureMatrix(Matrix3::scaling(Vector2{0.5f})); #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES @@ -2951,34 +2955,39 @@ template void PhongGLTest::renderInstanced() { }}; GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { TransformationUniform3D{}.setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-1.75f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(90.0_degf)* Matrix4::scaling(Vector3{0.4f}) ) }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { PhongDrawUniform{} - .setNormalMatrix( - (Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setNormalMatrix(Matrix4::rotationY(90.0_degf).normalMatrix()) .setObjectId(1000) /* gets added to the per-instance ID */ }}; GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { PhongMaterialUniform{} - .setDiffuseColor(0xffff99_rgbf) + .setDiffuseColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0xffff00_rgbf) }}; GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { TextureTransformationUniform{} .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) }}; GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { - PhongLightUniform{}.setPosition(Vector4{-3.0f, -3.0f, 2.0f, 0.0f}), - PhongLightUniform{}.setPosition(Vector4{3.0f, -3.0f, 2.0f, 0.0f}) + PhongLightUniform{} + .setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x999999_rgbf) + .setSpecularColor(0x0000ff_rgbf), + PhongLightUniform{} + .setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x999999_rgbf) + .setSpecularColor(0x00ff00_rgbf) }}; + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); shader.bindProjectionBuffer(projectionUniform) .bindTransformationBuffer(transformationUniform) - .bindTextureTransformationBuffer(textureTransformationUniform) .bindDrawBuffer(drawUniform) .bindMaterialBuffer(materialUniform) .bindLightBuffer(lightUniform) @@ -2987,6 +2996,25 @@ template void PhongGLTest::renderInstanced() { #endif else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + /* + Colored case: + + - First should be lower left, yellow with a blue and green highlight + on bottom left and right part + - Second lower right, cyan with a yellow light, so green, the same + highlight at the same position + - Third up center, magenta with a yellow light, so red, the same + highlight at the same position + + Textured case: + + - Lower left has bottom left numbers, so light 7881, rotated (78 + visible, should look the same as the multidraw case or as Flat) + - Lower light has bottom right, 1223, rotated (23 visible, looking at + the left side of the sphere in the equivalent Flat test) + - Up center has 6778, rotated (78 visible, looking at the left side + of the sphere in the equivalent Flat test) + */ MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_COMPARE_WITH( /* Dropping the alpha channel, as it's always 1.0 */ diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/instanced-textured.tga b/src/Magnum/Shaders/Test/PhongTestFiles/instanced-textured.tga new file mode 100644 index 0000000000000000000000000000000000000000..53b29b425f05c492d7750f0ae6d253bbcfa3fae6 GIT binary patch literal 6512 zcma)A`BPNck_L*}_kG{@TXpMJ0kQ~+0(GAT)vwEeF% zsWi!u^oLTZ{Qu}O;skYnms$5)=E>hG|Kr2jzyE3NuixMMw{KT|f4hjib+RA)_Dkx+ zH=&=u3;pta=+_@u`JTBmi@hc29jNr|H)&Nrq*s5)sQxjd=BJE*eSa77$2ZIW^7Yd1 z?-u{*t2=*wGynH5Ny-Wd_kZys@a|Rc{TIRSz6yT-mU{Op^u>$NyI1t)y;yf!WN{WX z5rEKzg!0PYM9;j9o&7our79rr(*QXJ<^SXBTmSHC4(PwVxcSS|sSl6G->nTHjWi%| zKfLFCw&{EM#Q*vks`|fr8F>3Sk$11gp{dlyGSku!e7Nd+^%U{96RJA#ls@?xbLw-Z z>=je?I*RJWuMhqDeEQdC)4x2M`uXY9PfsR4Jf8UB(b#tnM!sGj{&HpTvxQz{6T&Lz z@@@CVUC-v4XKTaz^r3I-esFYb}{8z_rTzgoTi=I+4fOV?g3^lZ&` zu8p^DW7ww09Jgnj%k!?)Mc4YW`~C{1K}c=h^E_VnJl^nbu9GF~=Nq1<>z;?J2mo={ zDmm0!3Dp(N`424y4&MzHuTpJm)RXm4+eYa4M(EAm{x{40ub2B@EnP$Xmy3O$E%rWN z?0!1m^?2s$gQ<3KSsK1H+jjv-b5OBmu-7^^1YY*JNyq$6=bc&4(!BfbZP&^I;dpP+ zwYD?{6}TQOxjrnp{(I5&{*H5X!MQZ=SeUWTOeBB^n0|MUYu|*oaK=}3%ilcj|F{tN zcqh;bqKkp8xlSUposVa)KAP#+oauPD)_(slSJu97U7c!K8g5z`yg1W)9_bk>e_R8Mx+PIlD-Y6dDX_g*zYy4y`XSFQcsb%T8$hx@-7 z8H|lyw_&6cH;7D*j6sFAsS(@!i0$_qwvRWge;u+;UPoh=fnHO02cWhY8e=uqNU=3~ z%9h{nsvB@!xb7|*a)a=mVfU?T^^m!17v}md%=VtY*>i5PulDrFp7D=0BmZ1A)Lk*q ze0sRGVx+BVw7q)xN(Er2p;BWhqN z2`tz9CZK%Fc&}xv*L=U*JlJLG=|CQm5)=td)2=o2{2^qkI)e+-R%3aiwyowc>iqne?dl+CX{Fz^Ts0(w>W_u3ai0Y&moN@>xiN<(RRp zMPj(pY`EO0Z)z}KJWn?@&@GLY)+S?HGl-)JlNC-PEmja2gG##_PIlFoc3vp$ICp$k z#NS$bthxGVYfW)`T}gfU0Z3QFNig0fYG`RRkZO$=boF(*x@uibrLL-4cf3wta9*EV zZ^&pc&=(CQ~F>T zk=oLN+S2_X2dO^3ud*b+qIfUJ<)w#m)2JBbK9ZktcyC6)sWm55>SHQ(u}WQ{)*Q!DttnM&PeR0M?Wwp$Tttq?Lt&#)oxTdl z+qc3TI%{y{YOS=|o~w2is$B=vu7eu)A&vX6#&cM6@<0xx^g#BBqU_^^StSLTNA{*? z(B6U^=4d`)TUC;ep`Slhgaf#E=Fo+*q6BTBCRVQ1Rgi&^;ev3fN>idzA62RjDwKs> zg|Yw-h4O$>1v#ix9U6=4K|Y-9)_!auAx?WIXAIU|pmpw3JNBz=c}h!;!kVkJ<|%D^ zh~(t@i*`o~AvsJzb_7>pe|F?RUJQiM!pVa3z||{mPDE^mM|Q#B!Y)n5@jXvzyhv_L=uzAGSSB; z?mLK^aV4zTN=z86!BiKPsQqGqjzYrt)R5eYcc!=9|DKR2Dq&!oZr zU|t&TcJcmQxID)SGK=zawx=!g<+10CTTp};0#-pnxWOyO=Or6?CSi2)w@@<_snq&RYN;^Z!1wqP0)OK6ce zC=xkELbX7k;Pa(?o`la6i}*Z<7$V@yghG`_0Z*NmF?ej#MmE$i!Ne+@x3A%{q_W7yxA-p^RbUs$|CkxGfOSPjEGY z7>=G5Fl5L$#gtIw!|VhCE1$0gFD_S{nkwON#H>hCQ&CaJ=bHrr2WHKZWq^=iN?x}N znxF&+uW$&ZW`S786DYYL$l<3XrzEAMqzL$2kX6a0dbI-c1zESvi1|_;8|`;6L1&C| zfwjZoNYFI!9d^_qg2NX1+4FYs1$I8)!s8jZTuo}KjKdM6q@*S%B_|~%r=;*X957XK zxjG)-gh*TokthH>a_oeoK;aU~Z33x@FVgY^ic~JzK;OcdlAa0uhIOK1ngfFlO1WpLc$%>LZo}Q(p01tGWoSd4#6@XrHG7qy% zO_d{r$20QzR-^)la3inITym~zE?<_)6>>;VNl8;sVQMNz$md~pN|{8Xlf=9GL7Wh!IVCB0ngWuu1BWCs~u9Hn(9f z;kke(d0i*=E!cMxPqHRIwg`3d(}Fz7`i~v@47R`@d>RbBp~7z|`fDQLw^+i7J)&Ds znb*G&2)&}{ccI9)VdjT0^C8UqiB1?lz=tKWV zu@e#I0UB@qJwzsik;1fo@1C> zXb%zbnIZ?7$Oj4l?|!KU*NHy5PcmCe@VE~Z=#XmbfzwyX=0eg2=ka` zwisqMBXTAZSq{-848cf2BGLf@D4>!kZ#ujlrTfz( zC+Ns}h#b#*A~fVpByuYn>54?I1;ZG@OQawX=>P!~0D(*XTY$y4+WH-?Ntb8A)sldZ%bsV1&O2NA5*(dYB{C!M*qV;pa#}BGLf@C;%q# zNe!FX+652lIz97l?_Koh4{R0&x1I+dyrvc^LKo6QO`*_QFhow5NceLq{DhA7Mb6ZR zzvvBow(L8&pca*Ob$J%k5ocd@Dn~wLkmsPXr!h2R4Gi`CxFE3Jy}iK8k|$!wyh4{kvCv)Azl9 zx$n8L=8mnnyo*Q&2%rdaYj>gw=|^TVUqN?dZuSAYbJXFQMzG&=C*QkV>|4$Bt@-^M zUjKc}+#7f}4#h|N0dWL3JlSiWl2!NqWfz4eP+=MXfQjP)82I-Rwb(kA4zuMdZiLl- z9UUXy<(~6;Zu>lo9`CZ-yW;V!;$U&#$DuOTT*ZvszGYWn(dEDG^vtnE#sB~qfSD~F ze^bY3zHG9zL9k`9_1Wx0cE_m0If;*u(>>?%%;Vl*=$Ixx_v26*OgP}iaU%qQxK~I* zDga0Y77xHX`bjpLnvCX_F{p@bs?Ta4#DGa(q}a5>HG_i%9wsvm6|v?l++@=^i9m!S ziLG%pfkj|{dUDZR0N94a)Ln#uI6ok+r^#CfvChHQ>u;Eh9ttT{xW?E~cA z0^Qn)B%~%#2;dHUN7Zg^VI=0VmFWz1I(@C)cmYhnhdrmOxG-!ne5(3LN%RNK30TciM3-BGaWaXLa${Y=xnV z3Bq0?5fB_gF0lcG*<9M11p3_;H)0t%J)GlHpvLeS^p6x!d zD|941{Xi_ID4JKmFx))9EI%kG z5|CpHOWtm;H0~{+h|{+FJ>2Kd!#vDA~3St z(p@fzB;6swBg-R2ey&fJ2l2DwlVJ%bwh$ml#JNf8Ck#L)DvY_yBJOcKww}AC$mQiUucJc1^n z-zfANgf5-HqZh#N8Hfi014}ru1p^)gBZU*s1Pss!SMtgvacTu_9T7x3w0xVIZ&C4# z3Z72JQ%i{$6l5th$jJkHh(H{o<7vnT3?azA#p&f-ladEPFl?=qtCFP3MI1ht!%O9e z`9!dU6I%#CSUetff)aTnR7nIJxsZrV$bl{5r|`Kc9QJh^uiUAeWGvyt76RgtuoEoE j8*CC{NdlXaG>v!R9UknHcUJPYJBtnS-1(OLxAy-6r!L1` literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/instanced.tga b/src/Magnum/Shaders/Test/PhongTestFiles/instanced.tga index a761d9413f319424c7c5e68e3d4a2cf1273fdc2b..8563565980b98545f9328f5c0593e90f05566135 100644 GIT binary patch literal 6448 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 literal 9748 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