// // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // TextureUploadBenchmark: // Performance test for uploading texture data. // #include "ANGLEPerfTest.h" #include #include #include #include "test_utils/gl_raii.h" #include "util/shader_utils.h" using namespace angle; namespace { constexpr unsigned int kIterationsPerStep = 2; struct TextureUploadParams final : public RenderTestParams { TextureUploadParams() { iterationsPerStep = kIterationsPerStep; trackGpuTime = true; baseSize = 1024; subImageSize = 64; webgl = false; } std::string story() const override; GLsizei baseSize; GLsizei subImageSize; bool webgl; }; std::ostream &operator<<(std::ostream &os, const TextureUploadParams ¶ms) { os << params.backendAndStory().substr(1); return os; } std::string TextureUploadParams::story() const { std::stringstream strstr; strstr << RenderTestParams::story(); if (webgl) { strstr << "_webgl"; } return strstr.str(); } class TextureUploadBenchmarkBase : public ANGLERenderTest, public ::testing::WithParamInterface { public: TextureUploadBenchmarkBase(const char *benchmarkName); void initializeBenchmark() override; void destroyBenchmark() override; protected: void initShaders(); GLuint mProgram = 0; GLint mPositionLoc = -1; GLint mSamplerLoc = -1; GLuint mTexture = 0; std::vector mTextureData; }; class TextureUploadSubImageBenchmark : public TextureUploadBenchmarkBase { public: TextureUploadSubImageBenchmark() : TextureUploadBenchmarkBase("TexSubImage") { addExtensionPrerequisite("GL_EXT_texture_storage"); } void initializeBenchmark() override { TextureUploadBenchmarkBase::initializeBenchmark(); const auto ¶ms = GetParam(); glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, params.baseSize, params.baseSize); } void drawBenchmark() override; }; class TextureUploadFullMipBenchmark : public TextureUploadBenchmarkBase { public: TextureUploadFullMipBenchmark() : TextureUploadBenchmarkBase("TextureUpload") {} void drawBenchmark() override; }; class PBOSubImageBenchmark : public TextureUploadBenchmarkBase { public: PBOSubImageBenchmark() : TextureUploadBenchmarkBase("PBO") {} void initializeBenchmark() override { TextureUploadBenchmarkBase::initializeBenchmark(); const auto ¶ms = GetParam(); glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, params.baseSize, params.baseSize); glGenBuffers(1, &mPBO); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mPBO); glBufferData(GL_PIXEL_UNPACK_BUFFER, params.baseSize * params.baseSize * 4, mTextureData.data(), GL_STREAM_DRAW); } void destroyBenchmark() { TextureUploadBenchmarkBase::destroyBenchmark(); glDeleteBuffers(1, &mPBO); } void drawBenchmark() override; private: GLuint mPBO; }; class PBOCompressedSubImageBenchmark : public TextureUploadBenchmarkBase { public: PBOCompressedSubImageBenchmark() : TextureUploadBenchmarkBase("PBOCompressed") {} void initializeBenchmark() override { TextureUploadBenchmarkBase::initializeBenchmark(); const auto ¶ms = GetParam(); glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGB8_ETC2, params.baseSize, params.baseSize); glGenBuffers(1, &mPBO); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mPBO); glBufferData(GL_PIXEL_UNPACK_BUFFER, params.subImageSize * params.subImageSize / 2, mTextureData.data(), GL_STREAM_DRAW); } void destroyBenchmark() { TextureUploadBenchmarkBase::destroyBenchmark(); glDeleteBuffers(1, &mPBO); } void drawBenchmark() override; private: GLuint mPBO; }; TextureUploadBenchmarkBase::TextureUploadBenchmarkBase(const char *benchmarkName) : ANGLERenderTest(benchmarkName, GetParam()) { setWebGLCompatibilityEnabled(GetParam().webgl); setRobustResourceInit(GetParam().webgl); // Crashes on nvidia+d3d11. http://crbug.com/945415 if (GetParam().getRenderer() == EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE) { mSkipTest = true; } } void TextureUploadBenchmarkBase::initializeBenchmark() { const auto ¶ms = GetParam(); initShaders(); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight()); if (mIsTimestampQueryAvailable && params.webgl) { glRequestExtensionANGLE("GL_EXT_disjoint_timer_query"); } glActiveTexture(GL_TEXTURE0); glGenTextures(1, &mTexture); glBindTexture(GL_TEXTURE_2D, mTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); ASSERT_TRUE(params.baseSize >= params.subImageSize); mTextureData.resize(params.baseSize * params.baseSize * 4, 0.5); ASSERT_GL_NO_ERROR(); } void TextureUploadBenchmarkBase::initShaders() { constexpr char kVS[] = R"(attribute vec4 a_position; void main() { gl_Position = a_position; })"; constexpr char kFS[] = R"(precision mediump float; uniform sampler2D s_texture; void main() { gl_FragColor = texture2D(s_texture, vec2(0, 0)); })"; mProgram = CompileProgram(kVS, kFS); ASSERT_NE(0u, mProgram); mPositionLoc = glGetAttribLocation(mProgram, "a_position"); mSamplerLoc = glGetUniformLocation(mProgram, "s_texture"); glUseProgram(mProgram); glUniform1i(mSamplerLoc, 0); glDisable(GL_DEPTH_TEST); ASSERT_GL_NO_ERROR(); } void TextureUploadBenchmarkBase::destroyBenchmark() { glDeleteTextures(1, &mTexture); glDeleteProgram(mProgram); } void TextureUploadSubImageBenchmark::drawBenchmark() { const auto ¶ms = GetParam(); startGpuTimer(); for (unsigned int iteration = 0; iteration < params.iterationsPerStep; ++iteration) { glTexSubImage2D(GL_TEXTURE_2D, 0, rand() % (params.baseSize - params.subImageSize), rand() % (params.baseSize - params.subImageSize), params.subImageSize, params.subImageSize, GL_RGBA, GL_UNSIGNED_BYTE, mTextureData.data()); // Perform a draw just so the texture data is flushed. With the position attributes not // set, a constant default value is used, resulting in a very cheap draw. glDrawArrays(GL_TRIANGLES, 0, 3); } stopGpuTimer(); ASSERT_GL_NO_ERROR(); } void TextureUploadFullMipBenchmark::drawBenchmark() { const auto ¶ms = GetParam(); startGpuTimer(); for (size_t it = 0; it < params.iterationsPerStep; ++it) { // Stage data for all mips GLint mip = 0; for (GLsizei levelSize = params.baseSize; levelSize > 0; levelSize >>= 1) { glTexImage2D(GL_TEXTURE_2D, mip++, GL_RGBA, levelSize, levelSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, mTextureData.data()); } // Perform a draw just so the texture data is flushed. With the position attributes not // set, a constant default value is used, resulting in a very cheap draw. glDrawArrays(GL_TRIANGLES, 0, 3); } stopGpuTimer(); ASSERT_GL_NO_ERROR(); } void PBOSubImageBenchmark::drawBenchmark() { const auto ¶ms = GetParam(); startGpuTimer(); for (unsigned int iteration = 0; iteration < params.iterationsPerStep; ++iteration) { glTexSubImage2D(GL_TEXTURE_2D, 0, rand() % (params.baseSize - params.subImageSize), rand() % (params.baseSize - params.subImageSize), params.subImageSize, params.subImageSize, GL_RGBA, GL_UNSIGNED_BYTE, 0); // Perform a draw just so the texture data is flushed. With the position attributes not // set, a constant default value is used, resulting in a very cheap draw. glDrawArrays(GL_TRIANGLES, 0, 3); } stopGpuTimer(); ASSERT_GL_NO_ERROR(); } void PBOCompressedSubImageBenchmark::drawBenchmark() { const auto ¶ms = GetParam(); startGpuTimer(); for (unsigned int iteration = 0; iteration < params.iterationsPerStep; ++iteration) { glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, params.subImageSize, params.subImageSize, GL_COMPRESSED_RGB8_ETC2, params.subImageSize * params.subImageSize / 2, 0); // Perform a draw just so the texture data is flushed. With the position attributes not // set, a constant default value is used, resulting in a very cheap draw. glDrawArrays(GL_TRIANGLES, 0, 3); } stopGpuTimer(); ASSERT_GL_NO_ERROR(); } TextureUploadParams D3D11Params(bool webglCompat) { TextureUploadParams params; params.eglParameters = egl_platform::D3D11(); params.webgl = webglCompat; return params; } TextureUploadParams OpenGLOrGLESParams(bool webglCompat) { TextureUploadParams params; params.eglParameters = egl_platform::OPENGL_OR_GLES(); params.webgl = webglCompat; return params; } TextureUploadParams VulkanParams(bool webglCompat) { TextureUploadParams params; params.eglParameters = egl_platform::VULKAN(); params.webgl = webglCompat; return params; } TextureUploadParams VulkanPBOParams(GLsizei baseSize, GLsizei subImageSize) { TextureUploadParams params; params.eglParameters = egl_platform::VULKAN(); params.webgl = false; params.trackGpuTime = false; params.baseSize = baseSize; params.subImageSize = subImageSize; return params; } TextureUploadParams ES3OpenGLPBOParams(GLsizei baseSize, GLsizei subImageSize) { TextureUploadParams params; params.eglParameters = egl_platform::OPENGL(); params.majorVersion = 3; params.minorVersion = 0; params.webgl = false; params.trackGpuTime = false; params.baseSize = baseSize; params.subImageSize = subImageSize; return params; } } // anonymous namespace TEST_P(TextureUploadSubImageBenchmark, Run) { run(); } TEST_P(TextureUploadFullMipBenchmark, Run) { run(); } TEST_P(PBOSubImageBenchmark, Run) { run(); } TEST_P(PBOCompressedSubImageBenchmark, Run) { run(); } using namespace params; ANGLE_INSTANTIATE_TEST(TextureUploadSubImageBenchmark, D3D11Params(false), D3D11Params(true), OpenGLOrGLESParams(false), OpenGLOrGLESParams(true), VulkanParams(false), NullDevice(VulkanParams(false)), VulkanParams(true)); ANGLE_INSTANTIATE_TEST(TextureUploadFullMipBenchmark, D3D11Params(false), D3D11Params(true), OpenGLOrGLESParams(false), OpenGLOrGLESParams(true), VulkanParams(false), VulkanParams(true)); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PBOSubImageBenchmark); ANGLE_INSTANTIATE_TEST(PBOSubImageBenchmark, ES3OpenGLPBOParams(1024, 128), VulkanPBOParams(1024, 128)); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PBOCompressedSubImageBenchmark); ANGLE_INSTANTIATE_TEST(PBOCompressedSubImageBenchmark, ES3OpenGLPBOParams(128, 128), VulkanPBOParams(128, 128));