/*------------------------------------------------------------------------- * 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 Draw call batching performance tests *//*--------------------------------------------------------------------*/ #include "es2pDrawCallBatchingTests.hpp" #include "gluShaderProgram.hpp" #include "gluRenderContext.hpp" #include "glwDefs.hpp" #include "glwFunctions.hpp" #include "glwEnums.hpp" #include "tcuTestLog.hpp" #include "deRandom.hpp" #include "deStringUtil.hpp" #include "deFile.h" #include "deString.h" #include "deClock.h" #include "deThread.h" #include <cmath> #include <vector> #include <string> #include <sstream> using tcu::TestLog; using namespace glw; using std::vector; using std::string; namespace deqp { namespace gles2 { namespace Performance { namespace { const int CALIBRATION_SAMPLE_COUNT = 34; class DrawCallBatchingTest : public tcu::TestCase { public: struct TestSpec { bool useStaticBuffer; int staticAttributeCount; bool useDynamicBuffer; int dynamicAttributeCount; int triangleCount; int drawCallCount; bool useDrawElements; bool useIndexBuffer; bool dynamicIndices; }; DrawCallBatchingTest (Context& context, const char* name, const char* description, const TestSpec& spec); ~DrawCallBatchingTest (void); void init (void); void deinit (void); IterateResult iterate (void); private: enum State { STATE_LOG_INFO = 0, STATE_WARMUP_BATCHED, STATE_WARMUP_UNBATCHED, STATE_CALC_CALIBRATION, STATE_SAMPLE }; State m_state; glu::RenderContext& m_renderCtx; de::Random m_rnd; int m_sampleIteration; int m_unbatchedSampleCount; int m_batchedSampleCount; TestSpec m_spec; glu::ShaderProgram* m_program; vector<deUint8> m_dynamicIndexData; vector<deUint8> m_staticIndexData; vector<GLuint> m_unbatchedDynamicIndexBuffers; GLuint m_batchedDynamicIndexBuffer; GLuint m_unbatchedStaticIndexBuffer; GLuint m_batchedStaticIndexBuffer; vector<vector<deInt8> > m_staticAttributeDatas; vector<vector<deInt8> > m_dynamicAttributeDatas; vector<GLuint> m_batchedStaticBuffers; vector<GLuint> m_unbatchedStaticBuffers; vector<GLuint> m_batchedDynamicBuffers; vector<vector<GLuint> > m_unbatchedDynamicBuffers; vector<deUint64> m_unbatchedSamplesUs; vector<deUint64> m_batchedSamplesUs; void logTestInfo (void); deUint64 renderUnbatched (void); deUint64 renderBatched (void); void createIndexData (void); void createIndexBuffer (void); void createShader (void); void createAttributeDatas (void); void createArrayBuffers (void); }; DrawCallBatchingTest::DrawCallBatchingTest (Context& context, const char* name, const char* description, const TestSpec& spec) : tcu::TestCase (context.getTestContext(), tcu::NODETYPE_PERFORMANCE, name, description) , m_state (STATE_LOG_INFO) , m_renderCtx (context.getRenderContext()) , m_rnd (deStringHash(name)) , m_sampleIteration (0) , m_unbatchedSampleCount (CALIBRATION_SAMPLE_COUNT) , m_batchedSampleCount (CALIBRATION_SAMPLE_COUNT) , m_spec (spec) , m_program (NULL) , m_batchedDynamicIndexBuffer (0) , m_unbatchedStaticIndexBuffer (0) , m_batchedStaticIndexBuffer (0) { } DrawCallBatchingTest::~DrawCallBatchingTest (void) { deinit(); } void DrawCallBatchingTest::createIndexData (void) { if (m_spec.dynamicIndices) { for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++) { for (int triangleNdx = 0; triangleNdx < m_spec.triangleCount; triangleNdx++) { m_dynamicIndexData.push_back(triangleNdx * 3); m_dynamicIndexData.push_back(triangleNdx * 3 + 1); m_dynamicIndexData.push_back(triangleNdx * 3 + 2); } } } else { for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++) { for (int triangleNdx = 0; triangleNdx < m_spec.triangleCount; triangleNdx++) { m_staticIndexData.push_back(triangleNdx * 3); m_staticIndexData.push_back(triangleNdx * 3 + 1); m_staticIndexData.push_back(triangleNdx * 3 + 2); } } } } void DrawCallBatchingTest::createShader (void) { std::ostringstream vertexShader; std::ostringstream fragmentShader; for (int attributeNdx = 0; attributeNdx < m_spec.staticAttributeCount; attributeNdx++) vertexShader << "attribute mediump vec4 a_static" << attributeNdx << ";\n"; if (m_spec.staticAttributeCount > 0 && m_spec.dynamicAttributeCount > 0) vertexShader << "\n"; for (int attributeNdx = 0; attributeNdx < m_spec.dynamicAttributeCount; attributeNdx++) vertexShader << "attribute mediump vec4 a_dyn" << attributeNdx << ";\n"; vertexShader << "\n" << "varying mediump vec4 v_color;\n" << "\n" << "void main (void)\n" << "{\n"; vertexShader << "\tv_color = "; bool first = true; for (int attributeNdx = 0; attributeNdx < m_spec.staticAttributeCount; attributeNdx++) { if (!first) vertexShader << " + "; first = false; vertexShader << "a_static" << attributeNdx; } for (int attributeNdx = 0; attributeNdx < m_spec.dynamicAttributeCount; attributeNdx++) { if (!first) vertexShader << " + "; first = false; vertexShader << "a_dyn" << attributeNdx; } vertexShader << ";\n"; if (m_spec.dynamicAttributeCount > 0) vertexShader << "\tgl_Position = a_dyn0;\n"; else vertexShader << "\tgl_Position = a_static0;\n"; vertexShader << "}"; fragmentShader << "varying mediump vec4 v_color;\n" << "\n" << "void main(void)\n" << "{\n" << "\tgl_FragColor = v_color;\n" << "}\n"; m_program = new glu::ShaderProgram(m_renderCtx, glu::ProgramSources() << glu::VertexSource(vertexShader.str()) << glu::FragmentSource(fragmentShader.str())); m_testCtx.getLog() << (*m_program); TCU_CHECK(m_program->isOk()); } void DrawCallBatchingTest::createAttributeDatas (void) { // Generate data for static attributes for (int attribute = 0; attribute < m_spec.staticAttributeCount; attribute++) { vector<deInt8> data; if (m_spec.dynamicAttributeCount == 0 && attribute == 0) { data.reserve(4 * 3 * m_spec.triangleCount * m_spec.drawCallCount); for (int i = 0; i < m_spec.triangleCount * m_spec.drawCallCount; i++) { int sign = (m_spec.triangleCount % 2 == 1 || i % 2 == 0 ? 1 : -1); data.push_back(-127 * sign); data.push_back(-127 * sign); data.push_back(0); data.push_back(127); data.push_back(127 * sign); data.push_back(-127 * sign); data.push_back(0); data.push_back(127); data.push_back(127 * sign); data.push_back(127 * sign); data.push_back(0); data.push_back(127); } } else { data.reserve(4 * 3 * m_spec.triangleCount * m_spec.drawCallCount); for (int i = 0; i < 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount; i++) data.push_back(m_rnd.getUint32()); } m_staticAttributeDatas.push_back(data); } // Generate data for dynamic attributes for (int attribute = 0; attribute < m_spec.dynamicAttributeCount; attribute++) { vector<deInt8> data; if (attribute == 0) { data.reserve(4 * 3 * m_spec.triangleCount * m_spec.drawCallCount); for (int i = 0; i < m_spec.triangleCount * m_spec.drawCallCount; i++) { int sign = (i % 2 == 0 ? 1 : -1); data.push_back(-127 * sign); data.push_back(-127 * sign); data.push_back(0); data.push_back(127); data.push_back(127 * sign); data.push_back(-127 * sign); data.push_back(0); data.push_back(127); data.push_back(127 * sign); data.push_back(127 * sign); data.push_back(0); data.push_back(127); } } else { data.reserve(4 * 3 * m_spec.triangleCount * m_spec.drawCallCount); for (int i = 0; i < 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount; i++) data.push_back(m_rnd.getUint32()); } m_dynamicAttributeDatas.push_back(data); } } void DrawCallBatchingTest::createArrayBuffers (void) { const glw::Functions& gl = m_renderCtx.getFunctions(); if (m_spec.useStaticBuffer) { // Upload static attributes for batched for (int attribute = 0; attribute < m_spec.staticAttributeCount; attribute++) { GLuint buffer; gl.genBuffers(1, &buffer); gl.bindBuffer(GL_ARRAY_BUFFER, buffer); gl.bufferData(GL_ARRAY_BUFFER, 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_staticAttributeDatas[attribute][0]), GL_STATIC_DRAW); gl.bindBuffer(GL_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating static buffer failed"); m_batchedStaticBuffers.push_back(buffer); } // Upload static attributes for unbatched for (int attribute = 0; attribute < m_spec.staticAttributeCount; attribute++) { GLuint buffer; gl.genBuffers(1, &buffer); gl.bindBuffer(GL_ARRAY_BUFFER, buffer); gl.bufferData(GL_ARRAY_BUFFER, 4 * 3 * m_spec.triangleCount, &(m_staticAttributeDatas[attribute][0]), GL_STATIC_DRAW); gl.bindBuffer(GL_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating static buffer failed"); m_unbatchedStaticBuffers.push_back(buffer); } } if (m_spec.useDynamicBuffer) { // Upload dynamic attributes for batched for (int attribute = 0; attribute < m_spec.dynamicAttributeCount; attribute++) { GLuint buffer; gl.genBuffers(1, &buffer); gl.bindBuffer(GL_ARRAY_BUFFER, buffer); gl.bufferData(GL_ARRAY_BUFFER, 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_dynamicAttributeDatas[attribute][0]), GL_STATIC_DRAW); gl.bindBuffer(GL_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic buffer failed"); m_batchedDynamicBuffers.push_back(buffer); } // Upload dynamic attributes for unbatched for (int attribute = 0; attribute < m_spec.dynamicAttributeCount; attribute++) { vector<GLuint> buffers; for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++) { GLuint buffer; gl.genBuffers(1, &buffer); gl.bindBuffer(GL_ARRAY_BUFFER, buffer); gl.bufferData(GL_ARRAY_BUFFER, 4 * 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_dynamicAttributeDatas[attribute][0]), GL_STATIC_DRAW); gl.bindBuffer(GL_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic buffer failed"); buffers.push_back(buffer); } m_unbatchedDynamicBuffers.push_back(buffers); } } } void DrawCallBatchingTest::createIndexBuffer (void) { const glw::Functions& gl = m_renderCtx.getFunctions(); if (m_spec.dynamicIndices) { for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++) { GLuint buffer; gl.genBuffers(1, &buffer); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * m_spec.triangleCount, &(m_dynamicIndexData[drawNdx * m_spec.triangleCount * 3]), GL_STATIC_DRAW); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic index buffer failed"); m_unbatchedDynamicIndexBuffers.push_back(buffer); } { GLuint buffer; gl.genBuffers(1, &buffer); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_dynamicIndexData[0]), GL_STATIC_DRAW); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic index buffer failed"); m_batchedDynamicIndexBuffer = buffer; } } else { { GLuint buffer; gl.genBuffers(1, &buffer); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * m_spec.triangleCount * m_spec.drawCallCount, &(m_staticIndexData[0]), GL_STATIC_DRAW); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic index buffer failed"); m_batchedStaticIndexBuffer = buffer; } { GLuint buffer; gl.genBuffers(1, &buffer); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * m_spec.triangleCount, &(m_staticIndexData[0]), GL_STATIC_DRAW); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Creating dynamic index buffer failed"); m_unbatchedStaticIndexBuffer = buffer; } } } void DrawCallBatchingTest::init (void) { createShader(); createAttributeDatas(); createArrayBuffers(); if (m_spec.useDrawElements) { createIndexData(); if (m_spec.useIndexBuffer) createIndexBuffer(); } } void DrawCallBatchingTest::deinit (void) { const glw::Functions& gl = m_renderCtx.getFunctions(); delete m_program; m_program = NULL; m_dynamicIndexData = vector<deUint8>(); m_staticIndexData = vector<deUint8>(); if (!m_unbatchedDynamicIndexBuffers.empty()) { gl.deleteBuffers((GLsizei)m_unbatchedDynamicIndexBuffers.size(), &(m_unbatchedDynamicIndexBuffers[0])); GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()"); m_unbatchedDynamicIndexBuffers = vector<GLuint>(); } if (m_batchedDynamicIndexBuffer) { gl.deleteBuffers((GLsizei)1, &m_batchedDynamicIndexBuffer); GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()"); m_batchedDynamicIndexBuffer = 0; } if (m_unbatchedStaticIndexBuffer) { gl.deleteBuffers((GLsizei)1, &m_unbatchedStaticIndexBuffer); GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()"); m_unbatchedStaticIndexBuffer = 0; } if (m_batchedStaticIndexBuffer) { gl.deleteBuffers((GLsizei)1, &m_batchedStaticIndexBuffer); GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()"); m_batchedStaticIndexBuffer = 0; } m_staticAttributeDatas = vector<vector<deInt8> >(); m_dynamicAttributeDatas = vector<vector<deInt8> >(); if (!m_batchedStaticBuffers.empty()) { gl.deleteBuffers((GLsizei)m_batchedStaticBuffers.size(), &(m_batchedStaticBuffers[0])); GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()"); m_batchedStaticBuffers = vector<GLuint>(); } if (!m_unbatchedStaticBuffers.empty()) { gl.deleteBuffers((GLsizei)m_unbatchedStaticBuffers.size(), &(m_unbatchedStaticBuffers[0])); GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()"); m_unbatchedStaticBuffers = vector<GLuint>(); } if (!m_batchedDynamicBuffers.empty()) { gl.deleteBuffers((GLsizei)m_batchedDynamicBuffers.size(), &(m_batchedDynamicBuffers[0])); GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()"); m_batchedDynamicBuffers = vector<GLuint>(); } for (int i = 0; i < (int)m_unbatchedDynamicBuffers.size(); i++) { gl.deleteBuffers((GLsizei)m_unbatchedDynamicBuffers[i].size(), &(m_unbatchedDynamicBuffers[i][0])); GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteBuffers()"); } m_unbatchedDynamicBuffers = vector<vector<GLuint> >(); m_unbatchedSamplesUs = vector<deUint64>(); m_batchedSamplesUs = vector<deUint64>(); } deUint64 DrawCallBatchingTest::renderUnbatched (void) { const glw::Functions& gl = m_renderCtx.getFunctions(); deUint64 beginUs = 0; deUint64 endUs = 0; vector<GLint> dynamicAttributeLocations; gl.viewport(0, 0, 32, 32); gl.useProgram(m_program->getProgram()); // Setup static buffers for (int attribNdx = 0; attribNdx < m_spec.staticAttributeCount; attribNdx++) { GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_static" + de::toString(attribNdx)).c_str()); gl.enableVertexAttribArray(location); if (m_spec.useStaticBuffer) { gl.bindBuffer(GL_ARRAY_BUFFER, m_unbatchedStaticBuffers[attribNdx]); gl.vertexAttribPointer(location, 4, GL_BYTE, GL_TRUE, 0, NULL); gl.bindBuffer(GL_ARRAY_BUFFER, 0); } else gl.vertexAttribPointer(location, 4, GL_BYTE, GL_TRUE, 0, &(m_staticAttributeDatas[attribNdx][0])); } // Get locations of dynamic attributes for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++) { GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_dyn" + de::toString(attribNdx)).c_str()); gl.enableVertexAttribArray(location); dynamicAttributeLocations.push_back(location); } if (m_spec.useDrawElements && m_spec.useIndexBuffer && !m_spec.dynamicIndices) gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_unbatchedStaticIndexBuffer); GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup initial state for rendering."); gl.finish(); beginUs = deGetMicroseconds(); for (int drawNdx = 0; drawNdx < m_spec.drawCallCount; drawNdx++) { for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++) { if (m_spec.useDynamicBuffer) { gl.bindBuffer(GL_ARRAY_BUFFER, m_unbatchedDynamicBuffers[attribNdx][drawNdx]); gl.vertexAttribPointer(dynamicAttributeLocations[attribNdx], 4, GL_BYTE, GL_TRUE, 0, NULL); gl.bindBuffer(GL_ARRAY_BUFFER, 0); } else gl.vertexAttribPointer(dynamicAttributeLocations[attribNdx], 4, GL_BYTE, GL_TRUE, 0, &(m_dynamicAttributeDatas[attribNdx][m_spec.triangleCount * 3 * drawNdx * 4])); } if (m_spec.useDrawElements) { if (m_spec.useIndexBuffer) { if (m_spec.dynamicIndices) { gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_unbatchedDynamicIndexBuffers[drawNdx]); gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3, GL_UNSIGNED_BYTE, NULL); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } else gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3, GL_UNSIGNED_BYTE, NULL); } else { if (m_spec.dynamicIndices) gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3, GL_UNSIGNED_BYTE, &(m_dynamicIndexData[drawNdx * m_spec.triangleCount * 3])); else gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3, GL_UNSIGNED_BYTE, &(m_staticIndexData[0])); } } else gl.drawArrays(GL_TRIANGLES, 0, 3 * m_spec.triangleCount); } gl.finish(); endUs = deGetMicroseconds(); GLU_EXPECT_NO_ERROR(gl.getError(), "Unbatched rendering failed"); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); for (int attribNdx = 0; attribNdx < m_spec.staticAttributeCount; attribNdx++) { GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_static" + de::toString(attribNdx)).c_str()); gl.disableVertexAttribArray(location); } for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++) gl.disableVertexAttribArray(dynamicAttributeLocations[attribNdx]); GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to reset state after unbatched rendering"); return endUs - beginUs; } deUint64 DrawCallBatchingTest::renderBatched (void) { const glw::Functions& gl = m_renderCtx.getFunctions(); deUint64 beginUs = 0; deUint64 endUs = 0; vector<GLint> dynamicAttributeLocations; gl.viewport(0, 0, 32, 32); gl.useProgram(m_program->getProgram()); // Setup static buffers for (int attribNdx = 0; attribNdx < m_spec.staticAttributeCount; attribNdx++) { GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_static" + de::toString(attribNdx)).c_str()); gl.enableVertexAttribArray(location); if (m_spec.useStaticBuffer) { gl.bindBuffer(GL_ARRAY_BUFFER, m_batchedStaticBuffers[attribNdx]); gl.vertexAttribPointer(location, 4, GL_BYTE, GL_TRUE, 0, NULL); gl.bindBuffer(GL_ARRAY_BUFFER, 0); } else gl.vertexAttribPointer(location, 4, GL_BYTE, GL_TRUE, 0, &(m_staticAttributeDatas[attribNdx][0])); } // Get locations of dynamic attributes for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++) { GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_dyn" + de::toString(attribNdx)).c_str()); gl.enableVertexAttribArray(location); dynamicAttributeLocations.push_back(location); } if (m_spec.useDrawElements && m_spec.useIndexBuffer && !m_spec.dynamicIndices) gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_batchedStaticIndexBuffer); GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup initial state for rendering."); gl.finish(); beginUs = deGetMicroseconds(); for (int attribute = 0; attribute < m_spec.dynamicAttributeCount; attribute++) { if (m_spec.useDynamicBuffer) { gl.bindBuffer(GL_ARRAY_BUFFER, m_batchedDynamicBuffers[attribute]); gl.vertexAttribPointer(dynamicAttributeLocations[attribute], 4, GL_BYTE, GL_TRUE, 0, NULL); gl.bindBuffer(GL_ARRAY_BUFFER, 0); } else gl.vertexAttribPointer(dynamicAttributeLocations[attribute], 4, GL_BYTE, GL_TRUE, 0, &(m_dynamicAttributeDatas[attribute][0])); } if (m_spec.useDrawElements) { if (m_spec.useIndexBuffer) { if (m_spec.dynamicIndices) { gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_batchedDynamicIndexBuffer); gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3 * m_spec.drawCallCount, GL_UNSIGNED_BYTE, NULL); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } else gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3 * m_spec.drawCallCount, GL_UNSIGNED_BYTE, NULL); } else { if (m_spec.dynamicIndices) gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3 * m_spec.drawCallCount, GL_UNSIGNED_BYTE, &(m_dynamicIndexData[0])); else gl.drawElements(GL_TRIANGLES, m_spec.triangleCount * 3 * m_spec.drawCallCount, GL_UNSIGNED_BYTE, &(m_staticIndexData[0])); } } else gl.drawArrays(GL_TRIANGLES, 0, 3 * m_spec.triangleCount * m_spec.drawCallCount); gl.finish(); endUs = deGetMicroseconds(); GLU_EXPECT_NO_ERROR(gl.getError(), "Batched rendering failed"); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); for (int attribNdx = 0; attribNdx < m_spec.staticAttributeCount; attribNdx++) { GLint location = gl.getAttribLocation(m_program->getProgram(), ("a_static" + de::toString(attribNdx)).c_str()); gl.disableVertexAttribArray(location); } for (int attribNdx = 0; attribNdx < m_spec.dynamicAttributeCount; attribNdx++) gl.disableVertexAttribArray(dynamicAttributeLocations[attribNdx]); GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to reset state after batched rendering"); return endUs - beginUs; } struct Statistics { double mean; double standardDeviation; double standardErrorOfMean; }; Statistics calculateStats (const vector<deUint64>& samples) { double mean = 0.0; for (int i = 0; i < (int)samples.size(); i++) mean += (double)samples[i]; mean /= (double)samples.size(); double standardDeviation = 0.0; for (int i = 0; i < (int)samples.size(); i++) { double x = (double)samples[i]; standardDeviation += (x - mean) * (x - mean); } standardDeviation /= (double)samples.size(); standardDeviation = std::sqrt(standardDeviation); double standardErrorOfMean = standardDeviation / std::sqrt((double)samples.size()); Statistics stats; stats.mean = mean; stats.standardDeviation = standardDeviation; stats.standardErrorOfMean = standardErrorOfMean; return stats; } void DrawCallBatchingTest::logTestInfo (void) { TestLog& log = m_testCtx.getLog(); tcu::ScopedLogSection section (log, "Test info", "Test info"); log << TestLog::Message << "Rendering using " << (m_spec.useDrawElements ? "glDrawElements()" : "glDrawArrays()") << "." << TestLog::EndMessage; if (m_spec.useDrawElements) log << TestLog::Message << "Using " << (m_spec.dynamicIndices ? "dynamic " : "") << "indices from " << (m_spec.useIndexBuffer ? "buffer" : "pointer") << "." << TestLog::EndMessage; if (m_spec.staticAttributeCount > 0) log << TestLog::Message << "Using " << m_spec.staticAttributeCount << " static attribute" << (m_spec.staticAttributeCount > 1 ? "s" : "") << " from " << (m_spec.useStaticBuffer ? "buffer" : "pointer") << "." << TestLog::EndMessage; if (m_spec.dynamicAttributeCount > 0) log << TestLog::Message << "Using " << m_spec.dynamicAttributeCount << " dynamic attribute" << (m_spec.dynamicAttributeCount > 1 ? "s" : "") << " from " << (m_spec.useDynamicBuffer ? "buffer" : "pointer") << "." << TestLog::EndMessage; log << TestLog::Message << "Rendering " << m_spec.drawCallCount << " draw calls with " << m_spec.triangleCount << " triangles per call." << TestLog::EndMessage; } tcu::TestCase::IterateResult DrawCallBatchingTest::iterate (void) { if (m_state == STATE_LOG_INFO) { logTestInfo(); m_state = STATE_WARMUP_BATCHED; } else if (m_state == STATE_WARMUP_BATCHED) { renderBatched(); m_state = STATE_WARMUP_UNBATCHED; } else if (m_state == STATE_WARMUP_UNBATCHED) { renderUnbatched(); m_state = STATE_SAMPLE; } else if (m_state == STATE_SAMPLE) { if ((int)m_unbatchedSamplesUs.size() < m_unbatchedSampleCount && (m_unbatchedSamplesUs.size() / ((double)m_unbatchedSampleCount) < m_batchedSamplesUs.size() / ((double)m_batchedSampleCount) || (int)m_batchedSamplesUs.size() >= m_batchedSampleCount)) m_unbatchedSamplesUs.push_back(renderUnbatched()); else if ((int)m_batchedSamplesUs.size() < m_batchedSampleCount) m_batchedSamplesUs.push_back(renderBatched()); else m_state = STATE_CALC_CALIBRATION; } else if (m_state == STATE_CALC_CALIBRATION) { TestLog& log = m_testCtx.getLog(); tcu::ScopedLogSection section(log, ("Sampling iteration " + de::toString(m_sampleIteration)).c_str(), ("Sampling iteration " + de::toString(m_sampleIteration)).c_str()); const double targetSEM = 0.02; const double limitSEM = 0.025; Statistics unbatchedStats = calculateStats(m_unbatchedSamplesUs); Statistics batchedStats = calculateStats(m_batchedSamplesUs); log << TestLog::Message << "Batched samples; Count: " << m_batchedSamplesUs.size() << ", Mean: " << batchedStats.mean << "us, Standard deviation: " << batchedStats.standardDeviation << "us, Standard error of mean: " << batchedStats.standardErrorOfMean << "us(" << (batchedStats.standardErrorOfMean/batchedStats.mean) << ")" << TestLog::EndMessage; log << TestLog::Message << "Unbatched samples; Count: " << m_unbatchedSamplesUs.size() << ", Mean: " << unbatchedStats.mean << "us, Standard deviation: " << unbatchedStats.standardDeviation << "us, Standard error of mean: " << unbatchedStats.standardErrorOfMean << "us(" << (unbatchedStats.standardErrorOfMean/unbatchedStats.mean) << ")" << TestLog::EndMessage; if (m_sampleIteration > 2 || (m_sampleIteration > 0 && (unbatchedStats.standardErrorOfMean/unbatchedStats.mean) + (batchedStats.standardErrorOfMean/batchedStats.mean) <= 2.0 * limitSEM)) { if (m_sampleIteration > 2) log << TestLog::Message << "Maximum iteration count reached." << TestLog::EndMessage; log << TestLog::Message << "Standard errors in target range." << TestLog::EndMessage; log << TestLog::Message << "Batched/Unbatched ratio: " << (batchedStats.mean / unbatchedStats.mean) << TestLog::EndMessage; m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)(batchedStats.mean/unbatchedStats.mean), 1).c_str()); return STOP; } else { if ((unbatchedStats.standardErrorOfMean/unbatchedStats.mean) > targetSEM) log << TestLog::Message << "Unbatched standard error of mean outside of range." << TestLog::EndMessage; if ((batchedStats.standardErrorOfMean/batchedStats.mean) > targetSEM) log << TestLog::Message << "Batched standard error of mean outside of range." << TestLog::EndMessage; if (unbatchedStats.standardDeviation > 0.0) { double x = (unbatchedStats.standardDeviation / unbatchedStats.mean) / targetSEM; m_unbatchedSampleCount = std::max((int)m_unbatchedSamplesUs.size(), (int)(x * x)); } else m_unbatchedSampleCount = (int)m_unbatchedSamplesUs.size(); if (batchedStats.standardDeviation > 0.0) { double x = (batchedStats.standardDeviation / batchedStats.mean) / targetSEM; m_batchedSampleCount = std::max((int)m_batchedSamplesUs.size(), (int)(x * x)); } else m_batchedSampleCount = (int)m_batchedSamplesUs.size(); m_batchedSamplesUs.clear(); m_unbatchedSamplesUs.clear(); m_sampleIteration++; m_state = STATE_SAMPLE; } } else DE_ASSERT(false); return CONTINUE; } string specToName (const DrawCallBatchingTest::TestSpec& spec) { std::ostringstream stream; DE_ASSERT(!spec.useStaticBuffer || spec.staticAttributeCount > 0); DE_ASSERT(!spec.useDynamicBuffer|| spec.dynamicAttributeCount > 0); if (spec.staticAttributeCount > 0) stream << spec.staticAttributeCount << "_static_"; if (spec.useStaticBuffer) stream << (spec.staticAttributeCount == 1 ? "buffer_" : "buffers_"); if (spec.dynamicAttributeCount > 0) stream << spec.dynamicAttributeCount << "_dynamic_"; if (spec.useDynamicBuffer) stream << (spec.dynamicAttributeCount == 1 ? "buffer_" : "buffers_"); stream << spec.triangleCount << "_triangles"; return stream.str(); } string specToDescrpition (const DrawCallBatchingTest::TestSpec& spec) { DE_UNREF(spec); return "Test performance of batched rendering against non-batched rendering."; } } // anonymous DrawCallBatchingTests::DrawCallBatchingTests (Context& context) : TestCaseGroup(context, "draw_call_batching", "Draw call batching performance tests.") { } DrawCallBatchingTests::~DrawCallBatchingTests (void) { } void DrawCallBatchingTests::init (void) { int drawCallCounts[] = { 10, 100 }; int triangleCounts[] = { 2, 10 }; int staticAttributeCounts[] = { 1, 0, 4, 8, 0 }; int dynamicAttributeCounts[] = { 0, 1, 4, 0, 8 }; DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(staticAttributeCounts) == DE_LENGTH_OF_ARRAY(dynamicAttributeCounts)); for (int drawType = 0; drawType < 2; drawType++) { bool drawElements = (drawType == 1); for (int indexBufferNdx = 0; indexBufferNdx < 2; indexBufferNdx++) { bool useIndexBuffer = (indexBufferNdx == 1); if (useIndexBuffer && !drawElements) continue; for (int dynamicIndexNdx = 0; dynamicIndexNdx < 2; dynamicIndexNdx++) { bool dynamicIndices = (dynamicIndexNdx == 1); if (dynamicIndices && !drawElements) continue; if (dynamicIndices && !useIndexBuffer) continue; TestCaseGroup* drawTypeGroup = new TestCaseGroup(m_context, (string(dynamicIndices ? "dynamic_" : "") + (useIndexBuffer ? "buffer_" : "" ) + (drawElements ? "draw_elements" : "draw_arrays")).c_str(), (string("Test batched rendering with ") + (drawElements ? "draw_elements" : "draw_arrays")).c_str()); addChild(drawTypeGroup); for (int drawCallCountNdx = 0; drawCallCountNdx < DE_LENGTH_OF_ARRAY(drawCallCounts); drawCallCountNdx++) { int drawCallCount = drawCallCounts[drawCallCountNdx]; TestCaseGroup* callCountGroup = new TestCaseGroup(m_context, (de::toString(drawCallCount) + (drawCallCount == 1 ? "_draw" : "_draws")).c_str(), ("Test batched rendering performance with " + de::toString(drawCallCount) + " draw calls.").c_str()); TestCaseGroup* attributeCount1Group = new TestCaseGroup(m_context, "1_attribute", "Test draw call batching with 1 attribute."); TestCaseGroup* attributeCount8Group = new TestCaseGroup(m_context, "8_attributes", "Test draw call batching with 8 attributes."); callCountGroup->addChild(attributeCount1Group); callCountGroup->addChild(attributeCount8Group); drawTypeGroup->addChild(callCountGroup); for (int attributeCountNdx = 0; attributeCountNdx < DE_LENGTH_OF_ARRAY(dynamicAttributeCounts); attributeCountNdx++) { TestCaseGroup* attributeCountGroup = NULL; int staticAttributeCount = staticAttributeCounts[attributeCountNdx]; int dynamicAttributeCount = dynamicAttributeCounts[attributeCountNdx]; if (staticAttributeCount + dynamicAttributeCount == 1) attributeCountGroup = attributeCount1Group; else if (staticAttributeCount + dynamicAttributeCount == 8) attributeCountGroup = attributeCount8Group; else DE_ASSERT(false); for (int triangleCountNdx = 0; triangleCountNdx < DE_LENGTH_OF_ARRAY(triangleCounts); triangleCountNdx++) { int triangleCount = triangleCounts[triangleCountNdx]; for (int dynamicBufferNdx = 0; dynamicBufferNdx < 2; dynamicBufferNdx++) { bool useDynamicBuffer = (dynamicBufferNdx != 0); for (int staticBufferNdx = 0; staticBufferNdx < 2; staticBufferNdx++) { bool useStaticBuffer = (staticBufferNdx != 0); DrawCallBatchingTest::TestSpec spec; spec.useStaticBuffer = useStaticBuffer; spec.staticAttributeCount = staticAttributeCount; spec.useDynamicBuffer = useDynamicBuffer; spec.dynamicAttributeCount = dynamicAttributeCount; spec.drawCallCount = drawCallCount; spec.triangleCount = triangleCount; spec.useDrawElements = drawElements; spec.useIndexBuffer = useIndexBuffer; spec.dynamicIndices = dynamicIndices; if (spec.useStaticBuffer && spec.staticAttributeCount == 0) continue; if (spec.useDynamicBuffer && spec.dynamicAttributeCount == 0) continue; attributeCountGroup->addChild(new DrawCallBatchingTest(m_context, specToName(spec).c_str(), specToDescrpition(spec).c_str(), spec)); } } } } } } } } } } // Performance } // gles2 } // deqp