1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <gflags/gflags.h> 6 #include <png.h> 7 #include <stdio.h> 8 #include <unistd.h> 9 10 #include <base/files/file_util.h> 11 #include <base/memory/scoped_ptr.h> 12 13 #include "glinterface.h" 14 #include "md5.h" 15 #include "png_helper.h" 16 #include "testbase.h" 17 #include "utils.h" 18 19 extern bool g_hasty; 20 extern bool g_notemp; 21 22 DEFINE_bool(save, false, "save images after each test case"); 23 DEFINE_string(outdir, "", "directory to save images"); 24 25 namespace glbench { 26 27 uint64_t TimeTest(TestBase* test, uint64_t iterations) { 28 g_main_gl_interface->SwapBuffers(); 29 glFinish(); 30 uint64_t time1 = GetUTime(); 31 if (!test->TestFunc(iterations)) 32 return ~0; 33 glFinish(); 34 uint64_t time2 = GetUTime(); 35 return time2 - time1; 36 } 37 38 // Target minimum iteration duration of 1s. This means the final/longest 39 // iteration is between 1s and 2s and the machine is active for 2s to 4s. 40 // Notice as of March 2014 the BVT suite has a hard limit per job of 20 minutes. 41 #define MIN_ITERATION_DURATION_US 1000000 42 43 #define MAX_TESTNAME 45 44 45 // Benchmark some draw commands, by running it many times. We want to measure 46 // the marginal cost, so we try more and more iterations until we reach the 47 // minimum specified iteration time. 48 double Bench(TestBase* test) { 49 // Try to wait a bit to let machine cool down for next test. We allow for a 50 // bit of hysteresis as it might take too long to do a perfect job, which is 51 // probably not required. But these parameters could be tuned. 52 double initial_temperature = GetInitialMachineTemperature(); 53 double temperature = 0; 54 double wait = 0; 55 56 // By default we try to cool to initial + 5'C but don't wait longer than 30s. 57 // But in hasty mode we really don't want to spend too much time to get the 58 // numbers right, so we don't wait at all. 59 if (!::g_notemp) { 60 wait = WaitForCoolMachine(initial_temperature + 5.0, 30.0, &temperature); 61 printf("Bench: Cooled down to %.1f'C (initial=%.1f'C) after waiting %.1fs.\n", 62 temperature, initial_temperature, wait); 63 if (temperature > initial_temperature + 10.0) 64 printf("Warning: Machine did not cool down enough for next test!"); 65 } 66 67 // Do two iterations because initial timings can vary wildly. 68 TimeTest(test, 2); 69 70 // We average the times for the last two runs to reduce noise. We could 71 // sum up all runs but the initial measurements have high CPU overhead, 72 // while the last two runs are both on the order of MIN_ITERATION_DURATION_US. 73 uint64_t iterations = 1; 74 uint64_t iterations_prev = 0; 75 uint64_t time = 0; 76 uint64_t time_prev = 0; 77 do { 78 time = TimeTest(test, iterations); 79 dbg_printf("iterations: %llu: time: %llu time/iter: %llu\n", 80 iterations, time, time / iterations); 81 82 // If we are running in hasty mode we will stop after a fraction of the 83 // testing time and return much more noisy performance numbers. The MD5s 84 // of the images should stay the same though. 85 if (time > MIN_ITERATION_DURATION_US / (::g_hasty ? 20.0 : 1.0)) 86 return (static_cast<double>(time + time_prev) / 87 (iterations + iterations_prev)); 88 89 time_prev = time; 90 iterations_prev = iterations; 91 iterations *= 2; 92 } while (iterations < (1ULL<<40)); 93 94 return 0.0; 95 } 96 97 void SaveImage(const char* name, const int width, const int height) { 98 const int size = width * height * 4; 99 scoped_ptr<char[]> pixels(new char[size]); 100 glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get()); 101 // I really think we want to use outdir as a straight argument 102 base::FilePath dirname = base::FilePath(FLAGS_outdir); 103 base::CreateDirectory(dirname); 104 base::FilePath filename = dirname.Append(name); 105 write_png_file(filename.value().c_str(), 106 pixels.get(), width, height); 107 } 108 109 void ComputeMD5(unsigned char digest[16], const int width, const int height) { 110 MD5Context ctx; 111 MD5Init(&ctx); 112 const int size = width * height * 4; 113 scoped_ptr<char[]> pixels(new char[size]); 114 glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get()); 115 MD5Update(&ctx, (unsigned char *)pixels.get(), size); 116 MD5Final(digest, &ctx); 117 } 118 119 void RunTest(TestBase* test, const char* testname, const double coefficient, 120 const int width, const int height, bool inverse) { 121 double value; 122 char name_png[512] = ""; 123 GLenum error = glGetError(); 124 125 if (error != GL_NO_ERROR) { 126 value = -1.0; 127 printf("# Error: %s aborted, glGetError returned 0x%02x.\n", 128 testname, error); 129 sprintf(name_png, "glGetError=0x%02x", error); 130 } else { 131 value = Bench(test); 132 133 // Bench returns 0.0 if it ran max iterations in less than a min test time. 134 if (value == 0.0) { 135 strcpy(name_png, "no_score"); 136 } else { 137 value = coefficient * (inverse ? 1.0 / value : value); 138 139 if (!test->IsDrawTest()) { 140 strcpy(name_png, "none"); 141 } else { 142 // save as png with MD5 as hex string attached 143 char pixmd5[33]; 144 unsigned char d[16]; 145 ComputeMD5(d, width, height); 146 // translate to hexadecimal ASCII of MD5 147 sprintf(pixmd5, 148 "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 149 d[ 0],d[ 1],d[ 2],d[ 3],d[ 4],d[ 5],d[ 6],d[ 7], 150 d[ 8],d[ 9],d[10],d[11],d[12],d[13],d[14],d[15]); 151 sprintf(name_png, "%s.pixmd5-%s.png", testname, pixmd5); 152 153 if (FLAGS_save) 154 SaveImage(name_png, width, height); 155 } 156 } 157 } 158 159 // TODO(ihf) adjust string length based on longest test name 160 int name_length = strlen(testname); 161 if (name_length > MAX_TESTNAME) 162 printf("# Warning: adjust string formatting to length = %d\n", 163 name_length); 164 // Results are marked using a leading '@RESULT: ' to allow parsing. 165 printf("@RESULT: %-*s = %10.2f %-15s [%s]\n", 166 MAX_TESTNAME, testname, value, test->Unit(), name_png); 167 } 168 169 bool DrawArraysTestFunc::TestFunc(uint64_t iterations) { 170 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 171 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 172 glFlush(); 173 for (uint64_t i = 0; i < iterations - 1; ++i) { 174 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 175 } 176 return true; 177 } 178 179 180 void DrawArraysTestFunc::FillRateTestNormal(const char* name) { 181 FillRateTestNormalSubWindow(name, g_width, g_height); 182 } 183 184 185 void DrawArraysTestFunc::FillRateTestNormalSubWindow(const char* name, 186 const int width, 187 const int height) 188 { 189 RunTest(this, name, width * height, width, height, true); 190 } 191 192 193 void DrawArraysTestFunc::FillRateTestBlendDepth(const char *name) { 194 const int buffer_len = 64; 195 char buffer[buffer_len]; 196 197 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 198 glEnable(GL_BLEND); 199 snprintf(buffer, buffer_len, "%s_blended", name); 200 RunTest(this, buffer, g_width * g_height, g_width, g_height, true); 201 glDisable(GL_BLEND); 202 203 // We are relying on the default depth clear value of 1 here. 204 // Fragments should have depth 0. 205 glEnable(GL_DEPTH_TEST); 206 glDepthFunc(GL_NOTEQUAL); 207 snprintf(buffer, buffer_len, "%s_depth_neq", name); 208 RunTest(this, buffer, g_width * g_height, g_width, g_height, true); 209 210 // The DrawArrays call invoked by this test shouldn't render anything 211 // because every fragment will fail the depth test. Therefore we 212 // should see the clear color. 213 glDepthFunc(GL_NEVER); 214 snprintf(buffer, buffer_len, "%s_depth_never", name); 215 RunTest(this, buffer, g_width * g_height, g_width, g_height, true); 216 glDisable(GL_DEPTH_TEST); 217 } 218 219 220 bool DrawElementsTestFunc::TestFunc(uint64_t iterations) { 221 glClearColor(0, 1.f, 0, 1.f); 222 glClear(GL_COLOR_BUFFER_BIT); 223 glDrawElements(GL_TRIANGLES, count_, GL_UNSIGNED_SHORT, 0); 224 glFlush(); 225 for (uint64_t i = 0 ; i < iterations - 1; ++i) { 226 glDrawElements(GL_TRIANGLES, count_, GL_UNSIGNED_SHORT, 0); 227 } 228 return true; 229 } 230 231 } // namespace glbench 232