From 73ba2b9d64f0889bb9bde4607ed1161991ec162e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 11 May 2021 13:59:29 +0200 Subject: [PATCH] Shaders: benchmark all shader variants. Took me a while (several years?) to figure out a way to benchmark this without basically duplicating the testing effort and without new variants being too hard to add. --- package/ci/appveyor-desktop-gles.bat | 2 +- package/ci/appveyor-desktop-mingw.bat | 2 +- package/ci/appveyor-desktop.bat | 2 +- package/ci/emscripten.sh | 2 +- package/ci/travis-android-gles.sh | 2 +- package/ci/travis-ios-simulator.sh | 2 +- package/ci/unix-desktop.sh | 2 +- .../Shaders/Test/BenchmarkFiles/trivial.tga | Bin 0 -> 10258 bytes src/Magnum/Shaders/Test/CMakeLists.txt | 20 + .../Shaders/Test/ShadersGLBenchmark.cpp | 800 ++++++++++++++++++ 10 files changed, 827 insertions(+), 7 deletions(-) create mode 100644 src/Magnum/Shaders/Test/BenchmarkFiles/trivial.tga create mode 100644 src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp diff --git a/package/ci/appveyor-desktop-gles.bat b/package/ci/appveyor-desktop-gles.bat index 904df3606..15ab951c8 100644 --- a/package/ci/appveyor-desktop-gles.bat +++ b/package/ci/appveyor-desktop-gles.bat @@ -58,7 +58,7 @@ cmake --build . || exit /b rem Test set CORRADE_TEST_COLOR=ON -ctest -V -E GLTest || exit /b +ctest -V -E "GLTest|GLBenchmark" || exit /b rem Test install, after running the tests as for them it shouldn't be needed cmake --build . --target install || exit /b diff --git a/package/ci/appveyor-desktop-mingw.bat b/package/ci/appveyor-desktop-mingw.bat index 59d3a1edd..33811414f 100644 --- a/package/ci/appveyor-desktop-mingw.bat +++ b/package/ci/appveyor-desktop-mingw.bat @@ -60,7 +60,7 @@ cmake --build . || exit /b rem Test set CORRADE_TEST_COLOR=ON -ctest -V -E "(GL|Vk)Test" || exit /b +ctest -V -E "GLTest|GLBenchmark|VkTest" || exit /b rem Test install, after running the tests as for them it shouldn't be needed cmake --build . --target install || exit /b diff --git a/package/ci/appveyor-desktop.bat b/package/ci/appveyor-desktop.bat index 260f23b13..9972e9cdf 100644 --- a/package/ci/appveyor-desktop.bat +++ b/package/ci/appveyor-desktop.bat @@ -69,7 +69,7 @@ cmake --build . || exit /b rem Test set CORRADE_TEST_COLOR=ON -ctest -V -E "(GL|Vk)Test" || exit /b +ctest -V -E "GLTest|GLBenchmark|VkTest" || exit /b rem Test install, after running the tests as for them it shouldn't be needed cmake --build . --target install || exit /b diff --git a/package/ci/emscripten.sh b/package/ci/emscripten.sh index 25a9df853..66bc6a8f8 100755 --- a/package/ci/emscripten.sh +++ b/package/ci/emscripten.sh @@ -73,7 +73,7 @@ cmake .. \ ninja # Test -CORRADE_TEST_COLOR=ON ctest -V -E "(GL|AL)Test" +CORRADE_TEST_COLOR=ON ctest -V -E "GLTest|GLBenchmark|ALTest" # Test install, after running the tests as for them it shouldn't be needed ninja install diff --git a/package/ci/travis-android-gles.sh b/package/ci/travis-android-gles.sh index 9d9e062bf..4b4521e02 100755 --- a/package/ci/travis-android-gles.sh +++ b/package/ci/travis-android-gles.sh @@ -83,7 +83,7 @@ ninja -j4 echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a emulator -avd test -no-audio -no-window & android-wait-for-emulator -CORRADE_TEST_COLOR=ON ctest -V -E GLTest +CORRADE_TEST_COLOR=ON ctest -V -E "GLTest|GLBenchmark" # Test install, after running the tests as for them it shouldn't be needed ninja install diff --git a/package/ci/travis-ios-simulator.sh b/package/ci/travis-ios-simulator.sh index b887aecf4..896fe5225 100755 --- a/package/ci/travis-ios-simulator.sh +++ b/package/ci/travis-ios-simulator.sh @@ -70,7 +70,7 @@ set -o pipefail && cmake --build . --config Release | xcpretty # TODO: find a better way to avoid # Library not loaded: /System/Library/Frameworks/OpenGLES.framework/OpenGLES # error -DYLD_FALLBACK_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/OpenGLES.framework/ DYLD_FALLBACK_FRAMEWORK_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks CORRADE_TEST_COLOR=ON ctest -V -C Release -E GLTest +DYLD_FALLBACK_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/OpenGLES.framework/ DYLD_FALLBACK_FRAMEWORK_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks CORRADE_TEST_COLOR=ON ctest -V -C Release -E "GLTest|GLBenchmark" # Test install, after running the tests as for them it shouldn't be needed set -o pipefail && cmake --build . --config Release --target install | xcpretty diff --git a/package/ci/unix-desktop.sh b/package/ci/unix-desktop.sh index b3dfb30ad..84964def9 100755 --- a/package/ci/unix-desktop.sh +++ b/package/ci/unix-desktop.sh @@ -67,7 +67,7 @@ cmake .. \ -DBUILD_PLUGINS_STATIC=$BUILD_STATIC \ -G Ninja ninja $NINJA_JOBS -ASAN_OPTIONS="color=always" LSAN_OPTIONS="color=always suppressions=$(pwd)/../package/ci/leaksanitizer.conf" TSAN_OPTIONS="color=always" CORRADE_TEST_COLOR=ON ctest -V -E "(GL|Vk)Test" +ASAN_OPTIONS="color=always" LSAN_OPTIONS="color=always suppressions=$(pwd)/../package/ci/leaksanitizer.conf" TSAN_OPTIONS="color=always" CORRADE_TEST_COLOR=ON ctest -V -E "GLTest|GLBenchmark|VkTest" # Test install, after running the tests as for them it shouldn't be needed ninja install diff --git a/src/Magnum/Shaders/Test/BenchmarkFiles/trivial.tga b/src/Magnum/Shaders/Test/BenchmarkFiles/trivial.tga new file mode 100644 index 0000000000000000000000000000000000000000..5042eb5fc767b5f32f55af29a05706bad8daee8a GIT binary patch literal 10258 zcmeIuK@9*f2n4VTuVpIc-z59tfuxntYwAz<>b*1`HT5V8DO@0|pEjSU2ziI*;0b literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index 214681e51..cbdaa1e40 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -317,6 +317,25 @@ if(BUILD_GL_TESTS) endif() endif() + corrade_add_test(ShadersGLBenchmark ShadersGLBenchmark.cpp + LIBRARIES + MagnumDebugTools + MagnumMeshTools + MagnumPrimitives + MagnumShaders + MagnumOpenGLTester + FILES + BenchmarkFiles/trivial.tga) + target_include_directories(ShadersGLBenchmark PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) + if(BUILD_PLUGINS_STATIC) + if(WITH_ANYIMAGEIMPORTER) + target_link_libraries(ShadersGLBenchmark PRIVATE AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + target_link_libraries(ShadersGLBenchmark PRIVATE TgaImporter) + endif() + endif() + if(CORRADE_TARGET_IOS) set_source_files_properties( TestFiles @@ -336,5 +355,6 @@ if(BUILD_GL_TESTS) ShadersPhongGLTest ShadersVectorGLTest ShadersVertexColorGLTest + ShadersGLBenchmark PROPERTIES FOLDER "Magnum/Shaders/Test") endif() diff --git a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp new file mode 100644 index 000000000..b92d3f93d --- /dev/null +++ b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp @@ -0,0 +1,800 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Magnum/Image.h" +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/DebugTools/CompareImage.h" +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/Framebuffer.h" +#include "Magnum/GL/Mesh.h" +#include "Magnum/GL/OpenGLTester.h" +#include "Magnum/GL/Renderbuffer.h" +#include "Magnum/GL/RenderbufferFormat.h" +#include "Magnum/GL/Texture.h" +#include "Magnum/GL/TextureFormat.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/MeshTools/Compile.h" +#include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Primitives/Grid.h" +#include "Magnum/Shaders/DistanceFieldVectorGL.h" +#include "Magnum/Shaders/FlatGL.h" +#include "Magnum/Shaders/MeshVisualizerGL.h" +#include "Magnum/Shaders/PhongGL.h" +#include "Magnum/Shaders/VertexColorGL.h" +#include "Magnum/Shaders/VectorGL.h" +#include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/MeshData.h" + +#include "configure.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +/* + The goal of this is to not duplicate all testing work here, but instead + have a set of simple-to-setup benchmarks with trivial output that allow for + measuring cost of particular shader features or seeing performance + differences between different implementations of the same (e.g., using the + VertexColor shader vs Flat with vertex colors enabled). Thus: + + - all shaders render the same mesh, 2D shaders only ignore the Z + coordinate (so it should be possible to compare the perf between 2D and + 3D) + - the mesh contains all attributes the shader might ever need including + instanced ones, to avoid differences caused by different memory access + patterns + - transformation and projection is identity + - textures are always single-pixel to measure the sampler overhead, not + memory access overhead + - if texture transformation is enabled, it's identity + - if instancing features are enabled, there's exactly one instance + - if alpha mask is enabled, it's 0.0 + - uniforms / binding overhead is not included in the benchmark +*/ + +struct ShadersGLBenchmark: GL::OpenGLTester { + explicit ShadersGLBenchmark(); + + void renderSetup(); + void renderTeardown(); + + template void flat(); + void phong(); + template void vertexColor(); + template void vector(); + template void distanceFieldVector(); + void meshVisualizer2D(); + void meshVisualizer3D(); + /** @todo mesh visualizer TBN, how to verify output? */ + + private: + PluginManager::Manager _manager{"nonexistent"}; + + GL::Renderbuffer _color; + #ifndef MAGNUM_TARGET_GLES2 + GL::Renderbuffer _objectId{NoCreate}; + #endif + GL::Framebuffer _framebuffer; + + GL::Buffer _indices, _vertices; + GL::Mesh _mesh, _meshInstanced, _meshDuplicated; + + GL::Texture2D _textureWhite, _textureBlue; +}; + +using namespace Math::Literals; + +constexpr Vector2i GridSubdivisions{64, 64}; +constexpr Vector2i RenderSize{512, 512}; +constexpr std::size_t WarmupIterations{100}; +constexpr std::size_t BenchmarkIterations{1000}; +constexpr std::size_t BenchmarkRepeats{4}; + +const struct { + const char* name; + FlatGL2D::Flags flags; +} FlatData[] { + {"", {}}, + {"vertex color", FlatGL2D::Flag::VertexColor}, + #ifndef MAGNUM_TARGET_GLES2 + {"object ID", FlatGL2D::Flag::ObjectId}, + #endif + {"textured", FlatGL2D::Flag::Textured}, + {"textured + alpha mask", FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask}, + {"texture transformation", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation}, + {"instanced transformation", FlatGL2D::Flag::InstancedTransformation}, + {"instanced transformation + color", FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::VertexColor}, + #ifndef MAGNUM_TARGET_GLES2 + {"instanced transformation + object ID", FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedObjectId}, + #endif + {"instanced transformation + texture offset", FlatGL2D::Flag::Textured|FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedTextureOffset}, +}; + +const struct { + const char* name; + PhongGL::Flags flags; + UnsignedInt lights; +} PhongData[] { + {"", {}, 1}, + {"zero lights", {}, 0}, + {"five lights", {}, 5}, + {"vertex color", PhongGL::Flag::VertexColor, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"object ID", PhongGL::Flag::ObjectId, 1}, + #endif + {"diffuse texture", PhongGL::Flag::DiffuseTexture, 1}, + {"ADS textures", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, 1}, + {"ADS textures + alpha mask", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::AlphaMask, 1}, + {"ADS textures + transformation", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureTransformation, 1}, + {"normal texture", PhongGL::Flag::NormalTexture, 1}, + {"normal texture with separate bitangent", PhongGL::Flag::NormalTexture|PhongGL::Flag::Bitangent, 1}, + {"instanced transformation", PhongGL::Flag::InstancedTransformation, 1}, + {"instanced transformation + color", PhongGL::Flag::InstancedTransformation|PhongGL::Flag::VertexColor, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"instanced transformation + object ID", PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedObjectId, 1}, + #endif + {"instanced transformation + ADS texture offset", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedTextureOffset, 1}, +}; + +const struct { + const char* name; + VectorGL2D::Flags flags; +} VectorData[] { + {"", {}}, + {"texture transformation", VectorGL2D::Flag::TextureTransformation} +}; + +const struct { + const char* name; + DistanceFieldVectorGL2D::Flags flags; +} DistanceFieldVectorData[] { + {"", {}}, + {"texture transformation", DistanceFieldVectorGL2D::Flag::TextureTransformation} +}; + +const struct { + const char* name; + MeshVisualizerGL2D::Flags flags; +} MeshVisualizer2DData[] { + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + {"wireframe", MeshVisualizerGL2D::Flag::Wireframe}, + #endif + {"wireframe w/o a GS", MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader}, + #ifndef MAGNUM_TARGET_GLES2 + {"instanced object ID", MeshVisualizerGL2D::Flag::InstancedObjectId}, + {"vertex ID", MeshVisualizerGL2D::Flag::VertexId}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizerGL2D::Flag::PrimitiveId}, + {"primitive ID from vertex ID", MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId}, + #endif + #endif +}; + +const struct { + const char* name; + MeshVisualizerGL3D::Flags flags; +} MeshVisualizer3DData[] { + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + {"wireframe", MeshVisualizerGL3D::Flag::Wireframe}, + #endif + {"wireframe w/o a GS", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}, + #ifndef MAGNUM_TARGET_GLES2 + {"instanced object ID", MeshVisualizerGL3D::Flag::InstancedObjectId}, + {"vertex ID", MeshVisualizerGL3D::Flag::VertexId}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizerGL3D::Flag::PrimitiveId}, + {"primitive ID from vertex ID", MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId}, + #endif + #endif +}; + +ShadersGLBenchmark::ShadersGLBenchmark(): _framebuffer{{{}, RenderSize}} { + addInstancedBenchmarks({&ShadersGLBenchmark::flat<2>, + &ShadersGLBenchmark::flat<3>}, + BenchmarkRepeats, Containers::arraySize(FlatData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::phong}, + BenchmarkRepeats, Containers::arraySize(PhongData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addBenchmarks({&ShadersGLBenchmark::vertexColor<2>, + &ShadersGLBenchmark::vertexColor<3>}, + BenchmarkRepeats, + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::vector<2>, + &ShadersGLBenchmark::vector<3>}, + BenchmarkRepeats, Containers::arraySize(VectorData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::distanceFieldVector<2>, + &ShadersGLBenchmark::distanceFieldVector<3>}, + BenchmarkRepeats, Containers::arraySize(DistanceFieldVectorData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::meshVisualizer2D}, + BenchmarkRepeats, Containers::arraySize(MeshVisualizer2DData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::meshVisualizer3D}, + BenchmarkRepeats, Containers::arraySize(MeshVisualizer3DData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + /* Set up the framebuffer */ + _color.setStorage( + #if !defined(MAGNUM_TARGET_GLES2) || !defined(MAGNUM_TARGET_WEBGL) + GL::RenderbufferFormat::RGBA8, + #else + GL::RenderbufferFormat::RGBA4, + #endif + RenderSize); + _framebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _color) + .bind(); + #ifndef MAGNUM_TARGET_GLES2 + /* If we don't have EXT_gpu_shader4, we likely don't have integer + framebuffers either (Mesa's Zink), so skip setting up integer + attachments to avoid GL errors */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _objectId = GL::Renderbuffer{}; + _objectId.setStorage(GL::RenderbufferFormat::R32UI, RenderSize); + _framebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{1}, _objectId) + .mapForDraw({ + {FlatGL2D::ColorOutput, GL::Framebuffer::ColorAttachment{0}}, + {FlatGL2D::ObjectIdOutput, GL::Framebuffer::ColorAttachment{1}} + }); + } + #endif + + /* Set up the mesh */ + { + Trade::MeshData data = Primitives::grid3DSolid(GridSubdivisions, + Primitives::GridFlag::TextureCoordinates| + Primitives::GridFlag::Normals| + Primitives::GridFlag::Tangents); + Containers::Array vertexColors{DirectInit, data.vertexCount(), 0xffffffff_rgbaf}; + Containers::Array bitangents{DirectInit, data.vertexCount(), Vector3{0.0f, 1.0f, 0.0f}}; + Trade::MeshData dataWithVertexColors = MeshTools::interleave(std::move(data), { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, arrayView(vertexColors)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Bitangent, arrayView(bitangents)} + }); + _indices.setData(dataWithVertexColors.indexData()); + _vertices.setData(dataWithVertexColors.vertexData()); + _mesh = MeshTools::compile(dataWithVertexColors, _indices, _vertices); + + /* Instanced variant, if the divisor-related extension is supported */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #elif defined(MAGNUM_TARGET_GLES2) + #ifndef MAGNUM_TARGET_WEBGL + if(GL::Context::current().isExtensionSupported() || + GL::Context::current().isExtensionSupported() || + GL::Context::current().isExtensionSupported()) + #else + if(GL::Context::current().isExtensionSupported()) + #endif + #endif + { + _meshInstanced = MeshTools::compile(dataWithVertexColors, _indices, _vertices); + struct { + /* Given the way the matrix attribute is specified (column by + column), it should work for 2D as well */ + Matrix4 transformation{Math::IdentityInit}; + Matrix3x3 normalMatrix{Math::IdentityInit}; + Vector2 textureOffset{0.0f, 0.0f}; + Color4 color{0xffffffff_rgbaf}; + UnsignedInt objectId{0}; + } instanceData[1]; + _meshInstanced.addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, + GenericGL3D::TransformationMatrix{}, + GenericGL3D::NormalMatrix{}, + GenericGL3D::TextureOffset{}, + #ifndef MAGNUM_TARGET_GLES2 + GenericGL3D::ObjectId{} + #else + sizeof(UnsignedInt) + #endif + ); + /** @todo hmm, this doesn't really issue an instanced draw call, does + that matter? */ + _meshInstanced.setInstanceCount(1); + } + + /* Non-indexed variant for GS-less wireframe drawing */ + _meshDuplicated = MeshTools::compile(MeshTools::duplicate(dataWithVertexColors)); + } + + /* Set up the textures */ + { + const Color4ub white[1] { 0xffffffff_rgba }; + _textureWhite.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, + #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) + GL::TextureFormat::RGBA8 + #else + GL::TextureFormat::RGBA + #endif + , {1, 1}) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, white}); + } { + const Color4ub blue[1] { 0x0000ffff_rgba }; + _textureBlue.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, + #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) + GL::TextureFormat::RGBA8 + #else + GL::TextureFormat::RGBA + #endif + , {1, 1}) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, blue}); + } + + /* Load the plugins directly from the build tree. Otherwise they're either + static and already loaded or not present in the build tree */ + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + #ifdef TGAIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(TGAIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif +} + +void ShadersGLBenchmark::renderSetup() { + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer + .clearColor(0, Color4{}) + .clearColor(1, Vector4ui{}); + } + #ifndef MAGNUM_TARGET_GLES + else + #endif + #endif + { + _framebuffer.clear(GL::FramebufferClear::Color); + } +} + +void ShadersGLBenchmark::renderTeardown() { + /* Nothing to do here */ +} + +template void ShadersGLBenchmark::flat() { + auto&& data = FlatData[testCaseInstanceId()]; + setTestCaseTemplateName(Utility::formatString("{}", dimensions)); + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + FlatGL shader{data.flags}; + if(data.flags >= FlatGL2D::Flag::AlphaMask) + shader.setAlphaMask(0.0f); + if(data.flags >= FlatGL2D::Flag::Textured) + shader.bindTexture(_textureWhite); + + GL::Mesh* mesh; + /* InstancedTextureOffset is a superset of TextureTransformation, so + remove those bits first when deciding if instanced */ + if((data.flags & ~FlatGL2D::Flag::TextureTransformation) & (FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedTextureOffset + #ifndef MAGNUM_TARGET_GLES2 + |FlatGL2D::Flag::InstancedObjectId + #endif + )) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); + #elif defined(MAGNUM_TARGET_GLES2) + #ifndef MAGNUM_TARGET_WEBGL + if(!GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("Required extension is not available."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::instanced_arrays::string() << "is not supported."); + #endif + #endif + mesh = &_meshInstanced; + } else { + mesh = &_mesh; + } + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != 100; ++i) + shader.draw(*mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(*mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void ShadersGLBenchmark::phong() { + auto&& data = PhongData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + PhongGL shader{data.flags, data.lights}; + /* White ambient so we always have a white output */ + shader.setAmbientColor(0xffffffff_rgbaf); + if(data.flags >= PhongGL::Flag::AlphaMask) + shader.setAlphaMask(0.0f); + if(data.flags >= PhongGL::Flag::AmbientTexture) + shader.bindAmbientTexture(_textureWhite); + if(data.flags >= PhongGL::Flag::DiffuseTexture) + shader.bindDiffuseTexture(_textureWhite); + if(data.flags >= PhongGL::Flag::SpecularTexture) + shader.bindSpecularTexture(_textureWhite); + if(data.flags >= PhongGL::Flag::NormalTexture) + shader.bindNormalTexture(_textureBlue); + + GL::Mesh* mesh; + /* InstancedTextureOffset is a superset of TextureTransformation, so + remove those bits first when deciding if instanced */ + if((data.flags & ~PhongGL::Flag::TextureTransformation) & (PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedTextureOffset + #ifndef MAGNUM_TARGET_GLES2 + |PhongGL::Flag::InstancedObjectId + #endif + )) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::draw_instanced::string() << "is not supported."); + #elif defined(MAGNUM_TARGET_GLES2) + #ifndef MAGNUM_TARGET_WEBGL + if(!GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("Required extension is not available."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::instanced_arrays::string() << "is not supported."); + #endif + #endif + mesh = &_meshInstanced; + } else { + mesh = &_mesh; + } + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(*mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(*mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +template void ShadersGLBenchmark::vertexColor() { + setTestCaseTemplateName(Utility::formatString("{}", dimensions)); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + VertexColorGL shader; + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(_mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(_mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +template void ShadersGLBenchmark::vector() { + auto&& data = VectorData[testCaseInstanceId()]; + setTestCaseTemplateName(Utility::formatString("{}", dimensions)); + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + VectorGL shader{data.flags}; + shader.bindVectorTexture(_textureWhite); + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(_mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(_mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +template void ShadersGLBenchmark::distanceFieldVector() { + auto&& data = DistanceFieldVectorData[testCaseInstanceId()]; + setTestCaseTemplateName(Utility::formatString("{}", dimensions)); + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + DistanceFieldVectorGL shader{data.flags}; + shader.bindVectorTexture(_textureWhite); + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(_mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(_mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void ShadersGLBenchmark::meshVisualizer2D() { + auto&& data = MeshVisualizer2DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* Checks verbatim copied from MeshVisualizerGLTest::construct2D() */ + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL2D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags & MeshVisualizerGL2D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + + MeshVisualizerGL2D shader{data.flags}; + shader.setViewportSize(Vector2{RenderSize}); + if(data.flags >= MeshVisualizerGL2D::Flag::Wireframe) + shader.setWireframeColor(0xffffffff_rgbaf); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & (MeshVisualizerGL2D::Flag::InstancedObjectId|MeshVisualizerGL2D::Flag::VertexId|MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId + #ifndef MAGNUM_TARGET_WEBGL + |MeshVisualizerGL2D::Flag::PrimitiveId + #endif + )) + shader.bindColorMapTexture(_textureWhite); + #endif + + GL::Mesh* mesh; + if(data.flags >= MeshVisualizerGL2D::Flag::NoGeometryShader) { + mesh = &_meshDuplicated; + } + #ifndef MAGNUM_TARGET_GLES2 + else if(data.flags & MeshVisualizerGL2D::Flag::InstancedObjectId) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::draw_instanced::string() << "is not supported."); + #endif + mesh = &_meshInstanced; + } + #endif + else { + mesh = &_mesh; + } + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(*mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(*mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void ShadersGLBenchmark::meshVisualizer3D() { + auto&& data = MeshVisualizer3DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* Checks verbatim copied from MeshVisualizerGLTest:.construct3D() */ + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(((data.flags & MeshVisualizerGL3D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader)) || (data.flags & (MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection))) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + + MeshVisualizerGL3D shader{data.flags}; + shader.setViewportSize(Vector2{RenderSize}); + if(data.flags >= MeshVisualizerGL3D::Flag::Wireframe) + shader.setWireframeColor(0xffffffff_rgbaf); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & (MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId + #ifndef MAGNUM_TARGET_WEBGL + |MeshVisualizerGL3D::Flag::PrimitiveId + #endif + )) + shader.bindColorMapTexture(_textureWhite); + #endif + + GL::Mesh* mesh; + if(data.flags >= MeshVisualizerGL3D::Flag::NoGeometryShader) + mesh = &_meshDuplicated; + #ifndef MAGNUM_TARGET_GLES2 + else if(data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId) + mesh = &_meshInstanced; + #endif + else + mesh = &_mesh; + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(*mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(*mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::ShadersGLBenchmark)