| // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "glbench/testbase.h" |
| |
| #include <gflags/gflags.h> |
| #include <png.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| |
| #include <base/files/file_util.h> |
| #include <base/memory/scoped_ptr.h> |
| |
| #include "glbench/glinterface.h" |
| #include "glbench/md5.h" |
| #include "glbench/png_helper.h" |
| #include "glbench/utils.h" |
| |
| extern bool g_hasty; |
| extern bool g_notemp; |
| |
| DEFINE_bool(save, false, "save images after each test case"); |
| DEFINE_string(outdir, "", "directory to save images"); |
| |
| namespace glbench { |
| |
| uint64_t TimeTest(TestBase* test, uint64_t iterations) { |
| g_main_gl_interface->SwapBuffers(); |
| glFinish(); |
| uint64_t time1 = GetUTime(); |
| if (!test->TestFunc(iterations)) |
| return ~0; |
| glFinish(); |
| uint64_t time2 = GetUTime(); |
| return time2 - time1; |
| } |
| |
| // Target minimum iteration duration of 1s. This means the final/longest |
| // iteration is between 1s and 2s and the machine is active for 2s to 4s. |
| // Notice as of March 2014 the BVT suite has a hard limit per job of 20 minutes. |
| #define MIN_ITERATION_DURATION_US 1000000 |
| |
| #define MAX_TESTNAME 45 |
| |
| // Benchmark some draw commands, by running it many times. We want to measure |
| // the marginal cost, so we try more and more iterations until we reach the |
| // minimum specified iteration time. |
| double Bench(TestBase* test) { |
| // Try to wait a bit to let machine cool down for next test. We allow for a |
| // bit of hysteresis as it might take too long to do a perfect job, which is |
| // probably not required. But these parameters could be tuned. |
| double initial_temperature = GetInitialMachineTemperature(); |
| double temperature = 0; |
| double wait = 0; |
| |
| // By default we try to cool to initial + 5'C but don't wait longer than 30s. |
| // But in hasty mode we really don't want to spend too much time to get the |
| // numbers right, so we don't wait at all. |
| if (!::g_notemp) { |
| wait = WaitForCoolMachine(initial_temperature + 5.0, 30.0, &temperature); |
| printf("Bench: Cooled down to %.1f'C (initial=%.1f'C) after waiting " |
| "%.1fs.\n", |
| temperature, initial_temperature, wait); |
| if (temperature > initial_temperature + 10.0) |
| printf("Warning: Machine did not cool down enough for next test!"); |
| } |
| |
| // Do two iterations because initial timings can vary wildly. |
| TimeTest(test, 2); |
| |
| // We average the times for the last two runs to reduce noise. We could |
| // sum up all runs but the initial measurements have high CPU overhead, |
| // while the last two runs are both on the order of MIN_ITERATION_DURATION_US. |
| uint64_t iterations = 1; |
| uint64_t iterations_prev = 0; |
| uint64_t time = 0; |
| uint64_t time_prev = 0; |
| do { |
| time = TimeTest(test, iterations); |
| // If we are running in hasty mode we will stop after a fraction of the |
| // testing time and return much more noisy performance numbers. The MD5s |
| // of the images should stay the same though. |
| if (time > MIN_ITERATION_DURATION_US / (::g_hasty ? 20.0 : 1.0)) |
| return (static_cast<double>(time + time_prev) / |
| (iterations + iterations_prev)); |
| |
| time_prev = time; |
| iterations_prev = iterations; |
| iterations *= 2; |
| } while (iterations < (1ULL<<40)); |
| |
| return 0.0; |
| } |
| |
| void SaveImage(const char* name, const int width, const int height) { |
| const int size = width * height * 4; |
| scoped_ptr<char[]> pixels(new char[size]); |
| glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get()); |
| // I really think we want to use outdir as a straight argument |
| base::FilePath dirname = base::FilePath(FLAGS_outdir); |
| base::CreateDirectory(dirname); |
| base::FilePath filename = dirname.Append(name); |
| write_png_file(filename.value().c_str(), |
| pixels.get(), width, height); |
| } |
| |
| void ComputeMD5(unsigned char digest[16], const int width, const int height) { |
| MD5Context ctx; |
| MD5Init(&ctx); |
| const int size = width * height * 4; |
| scoped_ptr<char[]> pixels(new char[size]); |
| glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get()); |
| MD5Update(&ctx, (unsigned char *)pixels.get(), size); |
| MD5Final(digest, &ctx); |
| } |
| |
| void RunTest(TestBase* test, const char* testname, const double coefficient, |
| const int width, const int height, bool inverse) { |
| double value; |
| char name_png[512] = ""; |
| GLenum error = glGetError(); |
| |
| if (error != GL_NO_ERROR) { |
| value = -1.0; |
| printf("# Error: %s aborted, glGetError returned 0x%02x.\n", |
| testname, error); |
| sprintf(name_png, "glGetError=0x%02x", error); |
| } else { |
| value = Bench(test); |
| |
| // Bench returns 0.0 if it ran max iterations in less than a min test time. |
| if (value == 0.0) { |
| strcpy(name_png, "no_score"); |
| } else { |
| value = coefficient * (inverse ? 1.0 / value : value); |
| |
| if (!test->IsDrawTest()) { |
| strcpy(name_png, "none"); |
| } else { |
| // save as png with MD5 as hex string attached |
| char pixmd5[33]; |
| unsigned char d[16]; |
| ComputeMD5(d, width, height); |
| // translate to hexadecimal ASCII of MD5 |
| sprintf(pixmd5, |
| "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", |
| d[ 0], d[ 1], d[ 2], d[ 3], d[ 4], d[ 5], d[ 6], d[ 7], |
| d[ 8], d[ 9], d[10], d[11], d[12], d[13], d[14], d[15]); |
| sprintf(name_png, "%s.pixmd5-%s.png", testname, pixmd5); |
| |
| if (FLAGS_save) |
| SaveImage(name_png, width, height); |
| } |
| } |
| } |
| |
| // TODO(ihf) adjust string length based on longest test name |
| int name_length = strlen(testname); |
| if (name_length > MAX_TESTNAME) |
| printf("# Warning: adjust string formatting to length = %d\n", |
| name_length); |
| // Results are marked using a leading '@RESULT: ' to allow parsing. |
| printf("@RESULT: %-*s = %10.2f %-15s [%s]\n", |
| MAX_TESTNAME, testname, value, test->Unit(), name_png); |
| } |
| |
| bool DrawArraysTestFunc::TestFunc(uint64_t iterations) { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| glFlush(); |
| for (uint64_t i = 0; i < iterations - 1; ++i) { |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| } |
| return true; |
| } |
| |
| |
| void DrawArraysTestFunc::FillRateTestNormal(const char* name) { |
| FillRateTestNormalSubWindow(name, g_width, g_height); |
| } |
| |
| |
| void DrawArraysTestFunc::FillRateTestNormalSubWindow(const char* name, |
| const int width, |
| const int height) { |
| RunTest(this, name, width * height, width, height, true); |
| } |
| |
| |
| void DrawArraysTestFunc::FillRateTestBlendDepth(const char *name) { |
| const int kBufferLen = 64; |
| char buffer[kBufferLen]; |
| |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| glEnable(GL_BLEND); |
| snprintf(buffer, kBufferLen, "%s_blended", name); |
| RunTest(this, buffer, g_width * g_height, g_width, g_height, true); |
| glDisable(GL_BLEND); |
| |
| // We are relying on the default depth clear value of 1 here. |
| // Fragments should have depth 0. |
| glEnable(GL_DEPTH_TEST); |
| glDepthFunc(GL_NOTEQUAL); |
| snprintf(buffer, kBufferLen, "%s_depth_neq", name); |
| RunTest(this, buffer, g_width * g_height, g_width, g_height, true); |
| |
| // The DrawArrays call invoked by this test shouldn't render anything |
| // because every fragment will fail the depth test. Therefore we |
| // should see the clear color. |
| glDepthFunc(GL_NEVER); |
| snprintf(buffer, kBufferLen, "%s_depth_never", name); |
| RunTest(this, buffer, g_width * g_height, g_width, g_height, true); |
| glDisable(GL_DEPTH_TEST); |
| } |
| |
| |
| bool DrawElementsTestFunc::TestFunc(uint64_t iterations) { |
| glClearColor(0, 1.f, 0, 1.f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| glDrawElements(GL_TRIANGLES, count_, GL_UNSIGNED_SHORT, 0); |
| glFlush(); |
| for (uint64_t i = 0 ; i < iterations - 1; ++i) { |
| glDrawElements(GL_TRIANGLES, count_, GL_UNSIGNED_SHORT, 0); |
| } |
| return true; |
| } |
| |
| } // namespace glbench |