| /*------------------------------------------------------------------------- |
| * drawElements Quality Program OpenGL ES 2.0 Module |
| * ------------------------------------------------- |
| * |
| * Copyright 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *//*! |
| * \file |
| * \brief Texture upload performance tests. |
| * |
| * \todo [2012-10-01 pyry] |
| * - Test different pixel unpack alignments |
| * - Use multiple textures |
| * - Trash cache prior to uploading from data ptr |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "es2pTextureUploadTests.hpp" |
| #include "tcuTexture.hpp" |
| #include "tcuTextureUtil.hpp" |
| #include "tcuTestLog.hpp" |
| #include "tcuSurface.hpp" |
| #include "gluTextureUtil.hpp" |
| #include "gluShaderProgram.hpp" |
| #include "gluPixelTransfer.hpp" |
| #include "deStringUtil.hpp" |
| #include "deRandom.hpp" |
| #include "deClock.h" |
| #include "deString.h" |
| |
| #include "glsCalibration.hpp" |
| |
| #include "glwEnums.hpp" |
| #include "glwFunctions.hpp" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| namespace deqp |
| { |
| namespace gles2 |
| { |
| namespace Performance |
| { |
| |
| using tcu::Vec2; |
| using tcu::Vec3; |
| using tcu::Vec4; |
| using tcu::IVec4; |
| using std::string; |
| using std::vector; |
| using tcu::TestLog; |
| using tcu::TextureFormat; |
| using namespace glw; // GL types |
| |
| static const int VIEWPORT_SIZE = 64; |
| static const float quadCoords[] = |
| { |
| -1.0f, -1.0f, |
| 1.0f, -1.0f, |
| -1.0f, 1.0f, |
| 1.0f, 1.0f |
| }; |
| |
| class TextureUploadCase : public TestCase |
| { |
| public: |
| TextureUploadCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize); |
| ~TextureUploadCase (void); |
| |
| virtual void init (void); |
| void deinit (void); |
| |
| virtual IterateResult iterate (void) = 0; |
| void logResults (void); |
| |
| protected: |
| UploadFunction m_uploadFunction; |
| deUint32 m_format; |
| deUint32 m_type; |
| int m_texSize; |
| int m_alignment; |
| |
| gls::TheilSenCalibrator m_calibrator; |
| glu::ShaderProgram* m_program; |
| deUint32 m_texture; |
| de::Random m_rnd; |
| TestLog& m_log; |
| |
| vector<deUint8> m_texData; |
| }; |
| |
| TextureUploadCase::TextureUploadCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize) |
| : TestCase (context, tcu::NODETYPE_PERFORMANCE, name, description) |
| , m_uploadFunction (uploadFunction) |
| , m_format (format) |
| , m_type (type) |
| , m_texSize (texSize) |
| , m_alignment (4) |
| , m_calibrator () |
| , m_program (DE_NULL) |
| , m_texture (0) |
| , m_rnd (deStringHash(name)) |
| , m_log (context.getTestContext().getLog()) |
| { |
| } |
| |
| TextureUploadCase::~TextureUploadCase (void) |
| { |
| TextureUploadCase::deinit(); |
| } |
| |
| void TextureUploadCase::deinit (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| |
| if (m_program) |
| { |
| delete m_program; |
| m_program = DE_NULL; |
| } |
| |
| gl.deleteTextures(1, &m_texture); |
| m_texture = 0; |
| |
| m_texData.clear(); |
| } |
| |
| void TextureUploadCase::init (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| int maxTextureSize; |
| gl.getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); |
| |
| if (m_texSize > maxTextureSize) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Unsupported texture size"); |
| return; |
| } |
| |
| // Create program |
| |
| string vertexShaderSource = ""; |
| string fragmentShaderSource = ""; |
| |
| vertexShaderSource.append( "precision mediump float;\n" |
| "attribute vec2 a_pos;\n" |
| "varying vec2 v_texCoord;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " v_texCoord = a_pos;\n" |
| " gl_Position = vec4(a_pos, 0.5, 1.0);\n" |
| "}\n"); |
| |
| fragmentShaderSource.append("precision mediump float;\n" |
| "uniform lowp sampler2D u_sampler;\n" |
| "varying vec2 v_texCoord;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " gl_FragColor = texture2D(u_sampler, v_texCoord.xy);\n" |
| "}\n"); |
| |
| DE_ASSERT(!m_program); |
| m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource)); |
| |
| if (!m_program->isOk()) |
| { |
| m_log << *m_program; |
| TCU_FAIL("Failed to create shader program (m_programRender)"); |
| } |
| |
| gl.useProgram (m_program->getProgram()); |
| |
| // Init GL state |
| |
| gl.viewport (0, 0, VIEWPORT_SIZE, VIEWPORT_SIZE); |
| gl.disable (GL_DEPTH_TEST); |
| gl.disable (GL_CULL_FACE); |
| gl.enable (GL_BLEND); |
| gl.blendFunc (GL_ONE, GL_ONE); |
| gl.clearColor (0.0f, 0.0f, 0.0f, 1.0f); |
| gl.clear (GL_COLOR_BUFFER_BIT); |
| |
| deUint32 uSampler = gl.getUniformLocation(m_program->getProgram(), "u_sampler"); |
| deUint32 aPos = gl.getAttribLocation (m_program->getProgram(), "a_pos"); |
| gl.enableVertexAttribArray (aPos); |
| gl.vertexAttribPointer (aPos, 2, GL_FLOAT, GL_FALSE, 0, &quadCoords[0]); |
| gl.uniform1i (uSampler, 0); |
| |
| // Create texture |
| |
| gl.activeTexture (GL_TEXTURE0); |
| gl.genTextures (1, &m_texture); |
| gl.bindTexture (GL_TEXTURE_2D, m_texture); |
| gl.pixelStorei (GL_UNPACK_ALIGNMENT, m_alignment); |
| gl.texParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gl.texParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| gl.texParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| gl.texParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| |
| // Prepare texture data |
| |
| { |
| const tcu::TextureFormat& texFmt = glu::mapGLTransferFormat(m_format, m_type); |
| int pixelSize = texFmt.getPixelSize(); |
| int stride = deAlign32(pixelSize*m_texSize, m_alignment); |
| |
| m_texData.resize(stride*m_texSize); |
| |
| tcu::PixelBufferAccess access (texFmt, m_texSize, m_texSize, 1, stride, 0, &m_texData[0]); |
| |
| tcu::fillWithComponentGradients(access, tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); |
| } |
| |
| // Do a dry-run to ensure the pipes are hot |
| |
| gl.texImage2D (GL_TEXTURE_2D, 0, m_format, m_texSize, m_texSize, 0, m_format, m_type, &m_texData[0]); |
| gl.drawArrays (GL_TRIANGLE_STRIP, 0, 4); |
| gl.finish (); |
| } |
| |
| void TextureUploadCase::logResults (void) |
| { |
| const gls::MeasureState& measureState = m_calibrator.getMeasureState(); |
| |
| // Log measurement details |
| |
| m_log << TestLog::Section("Measurement details", "Measurement details"); |
| m_log << TestLog::Message << "Uploading texture with " << (m_uploadFunction == UPLOAD_TEXIMAGE2D ? "glTexImage2D" : "glTexSubImage2D") << "." << TestLog::EndMessage; // \todo [arttu] Change enum to struct with name included |
| m_log << TestLog::Message << "Texture size = " << m_texSize << "x" << m_texSize << "." << TestLog::EndMessage; |
| m_log << TestLog::Message << "Viewport size = " << VIEWPORT_SIZE << "x" << VIEWPORT_SIZE << "." << TestLog::EndMessage; |
| m_log << TestLog::Message << measureState.numDrawCalls << " upload calls / iteration" << TestLog::EndMessage; |
| m_log << TestLog::EndSection; |
| |
| // Log results |
| |
| TestLog& log = m_testCtx.getLog(); |
| log << TestLog::Section("Results", "Results"); |
| |
| // Log individual frame durations |
| //for (int i = 0; i < m_calibrator.measureState.numFrames; i++) |
| // m_log << TestLog::Message << "Frame " << i+1 << " duration: \t" << m_calibrator.measureState.frameTimes[i] << " us."<< TestLog::EndMessage; |
| |
| std::vector<deUint64> sortedFrameTimes(measureState.frameTimes.begin(), measureState.frameTimes.end()); |
| std::sort(sortedFrameTimes.begin(), sortedFrameTimes.end()); |
| vector<deUint64>::const_iterator first = sortedFrameTimes.begin(); |
| vector<deUint64>::const_iterator last = sortedFrameTimes.end(); |
| vector<deUint64>::const_iterator middle = first + (last - first) / 2; |
| |
| deUint64 medianFrameTime = *middle; |
| double medianMTexelsPerSeconds = (double)(m_texSize*m_texSize*measureState.numDrawCalls) / (double)medianFrameTime; |
| double medianTexelDrawDurationNs = (double)medianFrameTime * 1000.0 / (double)(m_texSize*m_texSize*measureState.numDrawCalls); |
| |
| deUint64 totalTime = measureState.getTotalTime(); |
| int numFrames = (int)measureState.frameTimes.size(); |
| deInt64 numTexturesDrawn = measureState.numDrawCalls * numFrames; |
| deInt64 numPixels = (deInt64)m_texSize * (deInt64)m_texSize * numTexturesDrawn; |
| |
| double framesPerSecond = (double)numFrames / ((double)totalTime / 1000000.0); |
| double avgFrameTime = (double)totalTime / (double)numFrames; |
| double avgMTexelsPerSeconds = (double)numPixels / (double)totalTime; |
| double avgTexelDrawDurationNs = (double)totalTime * 1000.0 / (double)numPixels; |
| |
| log << TestLog::Float("FramesPerSecond", "Frames per second in measurement\t\t", "Frames/s", QP_KEY_TAG_PERFORMANCE, (float)framesPerSecond); |
| log << TestLog::Float("AverageFrameTime", "Average frame duration in measurement\t", "us", QP_KEY_TAG_PERFORMANCE, (float)avgFrameTime); |
| log << TestLog::Float("AverageTexelPerf", "Average texel upload performance\t\t", "MTex/s", QP_KEY_TAG_PERFORMANCE, (float)avgMTexelsPerSeconds); |
| log << TestLog::Float("AverageTexelTime", "Average texel upload duration\t\t", "ns", QP_KEY_TAG_PERFORMANCE, (float)avgTexelDrawDurationNs); |
| log << TestLog::Float("MedianTexelPerf", "Median texel upload performance\t\t", "MTex/s", QP_KEY_TAG_PERFORMANCE, (float)medianMTexelsPerSeconds); |
| log << TestLog::Float("MedianTexelTime", "Median texel upload duration\t\t", "ns", QP_KEY_TAG_PERFORMANCE, (float)medianTexelDrawDurationNs); |
| |
| log << TestLog::EndSection; |
| |
| gls::logCalibrationInfo(log, m_calibrator); // Log calibration details |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)avgMTexelsPerSeconds, 2).c_str()); |
| } |
| |
| // Texture upload call case |
| |
| class TextureUploadCallCase : public TextureUploadCase |
| { |
| public: |
| TextureUploadCallCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize); |
| ~TextureUploadCallCase (void); |
| |
| IterateResult iterate (void); |
| void render (void); |
| }; |
| |
| TextureUploadCallCase::TextureUploadCallCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize) |
| : TextureUploadCase (context, name, description, uploadFunction, format, type, texSize) |
| { |
| } |
| |
| TextureUploadCallCase::~TextureUploadCallCase (void) |
| { |
| TextureUploadCase::deinit(); |
| } |
| |
| void TextureUploadCallCase::render (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| |
| // Draw multiple quads to ensure enough workload |
| |
| switch (m_uploadFunction) |
| { |
| case UPLOAD_TEXIMAGE2D: |
| gl.texImage2D(GL_TEXTURE_2D, 0, m_format, m_texSize, m_texSize, 0, m_format, m_type, &m_texData[0]); |
| break; |
| case UPLOAD_TEXSUBIMAGE2D: |
| gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_texSize, m_texSize, m_format, m_type, &m_texData[0]); |
| break; |
| default: |
| DE_ASSERT(false); |
| } |
| } |
| |
| tcu::TestNode::IterateResult TextureUploadCallCase::iterate (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| |
| if (m_testCtx.getTestResult() == QP_TEST_RESULT_NOT_SUPPORTED) |
| return STOP; |
| |
| for (;;) |
| { |
| gls::TheilSenCalibrator::State state = m_calibrator.getState(); |
| |
| if (state == gls::TheilSenCalibrator::STATE_MEASURE) |
| { |
| int numCalls = m_calibrator.getCallCount(); |
| deUint64 startTime = deGetMicroseconds(); |
| |
| for (int i = 0; i < numCalls; i++) |
| render(); |
| |
| gl.finish(); |
| |
| deUint64 endTime = deGetMicroseconds(); |
| deUint64 duration = endTime-startTime; |
| |
| m_calibrator.recordIteration(duration); |
| } |
| else if (state == gls::TheilSenCalibrator::STATE_RECOMPUTE_PARAMS) |
| { |
| m_calibrator.recomputeParameters(); |
| } |
| else |
| { |
| DE_ASSERT(state == gls::TheilSenCalibrator::STATE_FINISHED); |
| break; |
| } |
| |
| // Touch watchdog between iterations to avoid timeout. |
| { |
| qpWatchDog* dog = m_testCtx.getWatchDog(); |
| if (dog) |
| qpWatchDog_touch(dog); |
| } |
| } |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "iterate"); |
| logResults(); |
| return STOP; |
| } |
| |
| // Texture upload and draw case |
| |
| class TextureUploadAndDrawCase : public TextureUploadCase |
| { |
| public: |
| TextureUploadAndDrawCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize); |
| ~TextureUploadAndDrawCase (void); |
| |
| IterateResult iterate (void); |
| void render (void); |
| |
| private: |
| bool m_lastIterationRender; |
| deUint64 m_renderStart; |
| }; |
| |
| TextureUploadAndDrawCase::TextureUploadAndDrawCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize) |
| : TextureUploadCase (context, name, description, uploadFunction, format, type, texSize) |
| , m_lastIterationRender (false) |
| , m_renderStart (0) |
| { |
| } |
| |
| TextureUploadAndDrawCase::~TextureUploadAndDrawCase (void) |
| { |
| TextureUploadCase::deinit(); |
| } |
| |
| void TextureUploadAndDrawCase::render (void) |
| { |
| const glw::Functions& gl = m_context.getRenderContext().getFunctions(); |
| |
| // Draw multiple quads to ensure enough workload |
| |
| switch (m_uploadFunction) |
| { |
| case UPLOAD_TEXIMAGE2D: |
| gl.texImage2D(GL_TEXTURE_2D, 0, m_format, m_texSize, m_texSize, 0, m_format, m_type, &m_texData[0]); |
| break; |
| case UPLOAD_TEXSUBIMAGE2D: |
| gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_texSize, m_texSize, m_format, m_type, &m_texData[0]); |
| break; |
| default: |
| DE_ASSERT(false); |
| } |
| |
| gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| } |
| |
| tcu::TestNode::IterateResult TextureUploadAndDrawCase::iterate (void) |
| { |
| if (m_testCtx.getTestResult() == QP_TEST_RESULT_NOT_SUPPORTED) |
| return STOP; |
| |
| if (m_lastIterationRender && (m_calibrator.getState() == gls::TheilSenCalibrator::STATE_MEASURE)) |
| { |
| deUint64 curTime = deGetMicroseconds(); |
| m_calibrator.recordIteration(curTime - m_renderStart); |
| } |
| |
| gls::TheilSenCalibrator::State state = m_calibrator.getState(); |
| |
| if (state == gls::TheilSenCalibrator::STATE_MEASURE) |
| { |
| // Render |
| int numCalls = m_calibrator.getCallCount(); |
| |
| m_renderStart = deGetMicroseconds(); |
| m_lastIterationRender = true; |
| |
| for (int i = 0; i < numCalls; i++) |
| render(); |
| |
| return CONTINUE; |
| } |
| else if (state == gls::TheilSenCalibrator::STATE_RECOMPUTE_PARAMS) |
| { |
| m_calibrator.recomputeParameters(); |
| m_lastIterationRender = false; |
| return CONTINUE; |
| } |
| else |
| { |
| DE_ASSERT(state == gls::TheilSenCalibrator::STATE_FINISHED); |
| GLU_EXPECT_NO_ERROR(m_context.getRenderContext().getFunctions().getError(), "finish"); |
| logResults(); |
| return STOP; |
| } |
| } |
| |
| // Texture upload tests |
| |
| TextureUploadTests::TextureUploadTests (Context& context) |
| : TestCaseGroup(context, "upload", "Texture upload tests") |
| { |
| } |
| |
| TextureUploadTests::~TextureUploadTests (void) |
| { |
| TextureUploadTests::deinit(); |
| } |
| |
| void TextureUploadTests::deinit (void) |
| { |
| } |
| |
| void TextureUploadTests::init (void) |
| { |
| TestCaseGroup* uploadCall = new TestCaseGroup(m_context, "upload", "Texture upload"); |
| TestCaseGroup* uploadAndDraw = new TestCaseGroup(m_context, "upload_draw_swap", "Texture upload, draw & buffer swap"); |
| |
| addChild(uploadCall); |
| addChild(uploadAndDraw); |
| |
| static const struct |
| { |
| const char* name; |
| const char* nameLower; |
| UploadFunction func; |
| } uploadFunctions[] = |
| { |
| { "texImage2D", "teximage2d", UPLOAD_TEXIMAGE2D }, |
| { "texSubImage2D", "texsubimage2d", UPLOAD_TEXSUBIMAGE2D } |
| }; |
| |
| static const struct |
| { |
| const char* name; |
| deUint32 format; |
| deUint32 type; |
| } textureCombinations[] = |
| { |
| { "rgb_ubyte", GL_RGB, GL_UNSIGNED_BYTE }, |
| { "rgba_ubyte", GL_RGBA, GL_UNSIGNED_BYTE }, |
| { "alpha_ubyte", GL_ALPHA, GL_UNSIGNED_BYTE }, |
| { "luminance_ubyte", GL_LUMINANCE, GL_UNSIGNED_BYTE }, |
| { "luminance-alpha_ubyte", GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE }, |
| { "rgb_ushort565", GL_RGB, GL_UNSIGNED_SHORT_5_6_5 }, |
| { "rgba_ushort4444", GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 }, |
| { "rgba_ushort5551", GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 }, |
| }; |
| |
| static const struct |
| { |
| int size; |
| TestCaseGroup* uploadCallGroup; |
| TestCaseGroup* uploadAndDrawGroup; |
| } textureSizes[] = |
| { |
| { 16, new TestCaseGroup(m_context, "16x16", "Texture size 16x16"), new TestCaseGroup(m_context, "16x16", "Texture size 16x16") }, |
| { 256, new TestCaseGroup(m_context, "256x256", "Texture size 256x256"), new TestCaseGroup(m_context, "256x256", "Texture size 256x256") }, |
| { 257, new TestCaseGroup(m_context, "257x257", "Texture size 257x257"), new TestCaseGroup(m_context, "257x257", "Texture size 257x257") }, |
| { 1024, new TestCaseGroup(m_context, "1024x1024", "Texture size 1024x1024"), new TestCaseGroup(m_context, "1024x1024", "Texture size 1024x1024") }, |
| { 2048, new TestCaseGroup(m_context, "2048x2048", "Texture size 2048x2048"), new TestCaseGroup(m_context, "2048x2048", "Texture size 2048x2048") }, |
| }; |
| |
| #define FOR_EACH(ITERATOR, ARRAY, BODY) \ |
| for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++) \ |
| BODY |
| |
| FOR_EACH(uploadFunc, uploadFunctions, |
| FOR_EACH(texSize, textureSizes, |
| FOR_EACH(texCombination, textureCombinations, |
| { |
| string caseName = string("") + uploadFunctions[uploadFunc].nameLower + "_" + textureCombinations[texCombination].name; |
| UploadFunction function = uploadFunctions[uploadFunc].func; |
| deUint32 format = textureCombinations[texCombination].format; |
| deUint32 type = textureCombinations[texCombination].type; |
| int size = textureSizes[texSize].size; |
| |
| textureSizes[texSize].uploadCallGroup->addChild (new TextureUploadCallCase (m_context, caseName.c_str(), "", function, format, type, size)); |
| textureSizes[texSize].uploadAndDrawGroup->addChild (new TextureUploadAndDrawCase (m_context, caseName.c_str(), "", function, format, type, size)); |
| }))); |
| |
| for (int i = 0; i < DE_LENGTH_OF_ARRAY(textureSizes); i++) |
| { |
| uploadCall->addChild (textureSizes[i].uploadCallGroup); |
| uploadAndDraw->addChild (textureSizes[i].uploadAndDrawGroup); |
| } |
| } |
| |
| |
| } // Performance |
| } // gles2 |
| } // deqp |