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