1// Copyright (c) 2012 The Chromium 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// This tool is used to benchmark the render model used by the compositor
6
7// Most of this file is derived from the source of the tile_render_bench tool,
8// and has been changed to  support running a sequence of independent
9// simulations for our different render models and test cases.
10
11#include <stdio.h>
12#include <sys/dir.h>
13#include <sys/file.h>
14#include <sys/stat.h>
15#include <sys/types.h>
16#include <X11/keysym.h>
17#include <X11/Xlib.h>
18#include <X11/Xutil.h>
19
20#include <queue>
21#include <string>
22#include <vector>
23
24#include "base/at_exit.h"
25#include "base/basictypes.h"
26#include "base/bind.h"
27#include "base/command_line.h"
28#include "base/files/file_enumerator.h"
29#include "base/files/file_path.h"
30#include "base/files/file_util.h"
31#include "base/memory/scoped_ptr.h"
32#include "base/message_loop/message_loop.h"
33#include "base/time/time.h"
34#include "gpu/tools/compositor_model_bench/render_model_utils.h"
35#include "gpu/tools/compositor_model_bench/render_models.h"
36#include "gpu/tools/compositor_model_bench/render_tree.h"
37#include "ui/gl/gl_surface.h"
38
39using base::TimeTicks;
40using base::DirectoryExists;
41using base::PathExists;
42using std::queue;
43using std::string;
44
45struct SimulationSpecification {
46  string simulation_name;
47  base::FilePath input_path;
48  RenderModel model_under_test;
49  TimeTicks simulation_start_time;
50  int frames_rendered;
51};
52
53// Forward declarations
54class Simulator;
55void _process_events(Simulator* sim);
56void _update_loop(Simulator* sim);
57
58class Simulator {
59 public:
60  Simulator(int seconds_per_test, const base::FilePath& output_path)
61     : current_sim_(NULL),
62       output_path_(output_path),
63       seconds_per_test_(seconds_per_test),
64       weak_factory_(this),
65       display_(NULL),
66       window_(0),
67       gl_context_(NULL),
68       window_width_(WINDOW_WIDTH),
69       window_height_(WINDOW_HEIGHT) {
70  }
71
72  ~Simulator() {
73    // Cleanup GL.
74    glXMakeCurrent(display_, 0, NULL);
75    glXDestroyContext(display_, gl_context_);
76
77    // Destroy window and display.
78    XDestroyWindow(display_, window_);
79    XCloseDisplay(display_);
80  }
81
82  void QueueTest(const base::FilePath& path) {
83    SimulationSpecification spec;
84
85    // To get a std::string, we'll try to get an ASCII simulation name.
86    // If the name of the file wasn't ASCII, this will give an empty simulation
87    //  name, but that's not really harmful (we'll still warn about it though.)
88    spec.simulation_name = path.BaseName().RemoveExtension().MaybeAsASCII();
89    if (spec.simulation_name == "") {
90      LOG(WARNING) << "Simulation for path " << path.LossyDisplayName() <<
91        " will have a blank simulation name, since the file name isn't ASCII";
92    }
93    spec.input_path = path;
94    spec.model_under_test = ForwardRenderModel;
95    spec.frames_rendered = 0;
96
97    sims_remaining_.push(spec);
98
99    // The following lines are commented out pending the addition
100    // of the new render model once this version gets fully checked in.
101    //
102    //  spec.model_under_test = KDTreeRenderModel;
103    //  sims_remaining_.push(spec);
104  }
105
106  void Run() {
107    if (!sims_remaining_.size()) {
108      LOG(WARNING) << "No configuration files loaded.";
109      return;
110    }
111
112    base::AtExitManager at_exit;
113    base::MessageLoop loop;
114    if (!InitX11() || !InitGLContext()) {
115      LOG(FATAL) << "Failed to set up GUI.";
116    }
117
118    InitBuffers();
119
120    LOG(INFO) << "Running " << sims_remaining_.size() << " simulations.";
121
122    loop.PostTask(FROM_HERE,
123                  base::Bind(&Simulator::ProcessEvents,
124                             weak_factory_.GetWeakPtr()));
125    loop.Run();
126  }
127
128  void ProcessEvents() {
129    // Consume all the X events.
130    while (XPending(display_)) {
131      XEvent e;
132      XNextEvent(display_, &e);
133      switch (e.type) {
134        case Expose:
135          UpdateLoop();
136          break;
137        case ConfigureNotify:
138          Resize(e.xconfigure.width, e.xconfigure.height);
139          break;
140        default:
141          break;
142      }
143    }
144  }
145
146  void UpdateLoop() {
147    if (UpdateTestStatus())
148      UpdateCurrentTest();
149  }
150
151 private:
152  // Initialize X11. Returns true if successful. This method creates the
153  // X11 window. Further initialization is done in X11VideoRenderer.
154  bool InitX11() {
155    display_ = XOpenDisplay(NULL);
156    if (!display_) {
157      LOG(FATAL) << "Cannot open display";
158      return false;
159    }
160
161    // Get properties of the screen.
162    int screen = DefaultScreen(display_);
163    int root_window = RootWindow(display_, screen);
164
165    // Creates the window.
166    window_ = XCreateSimpleWindow(display_,
167                                  root_window,
168                                  1,
169                                  1,
170                                  window_width_,
171                                  window_height_,
172                                  0,
173                                  BlackPixel(display_, screen),
174                                  BlackPixel(display_, screen));
175    XStoreName(display_, window_, "Compositor Model Bench");
176
177    XSelectInput(display_, window_,
178                 ExposureMask | KeyPressMask | StructureNotifyMask);
179    XMapWindow(display_, window_);
180
181    XResizeWindow(display_, window_, WINDOW_WIDTH, WINDOW_HEIGHT);
182
183    return true;
184  }
185
186  // Initialize the OpenGL context.
187  bool InitGLContext() {
188    if (!gfx::GLSurface::InitializeOneOff()) {
189      LOG(FATAL) << "gfx::GLSurface::InitializeOneOff failed";
190      return false;
191    }
192
193    XWindowAttributes attributes;
194    XGetWindowAttributes(display_, window_, &attributes);
195    XVisualInfo visual_info_template;
196    visual_info_template.visualid = XVisualIDFromVisual(attributes.visual);
197    int visual_info_count = 0;
198    XVisualInfo* visual_info_list = XGetVisualInfo(display_, VisualIDMask,
199                                                   &visual_info_template,
200                                                   &visual_info_count);
201
202    for (int i = 0; i < visual_info_count && !gl_context_; ++i) {
203      gl_context_ = glXCreateContext(display_, visual_info_list + i, 0,
204                                     True /* Direct rendering */);
205    }
206
207    XFree(visual_info_list);
208    if (!gl_context_) {
209      return false;
210    }
211
212    if (!glXMakeCurrent(display_, window_, gl_context_)) {
213      glXDestroyContext(display_, gl_context_);
214      gl_context_ = NULL;
215      return false;
216    }
217
218    return true;
219  }
220
221  bool InitializeNextTest() {
222    SimulationSpecification& spec = sims_remaining_.front();
223    LOG(INFO) << "Initializing test for " << spec.simulation_name <<
224        "(" << ModelToString(spec.model_under_test) << ")";
225    const base::FilePath& path = spec.input_path;
226
227    RenderNode* root = NULL;
228    if (!(root = BuildRenderTreeFromFile(path))) {
229      LOG(ERROR) << "Couldn't parse test configuration file " <<
230          path.LossyDisplayName();
231      return false;
232    }
233
234    current_sim_ = ConstructSimulationModel(spec.model_under_test,
235                                            root,
236                                            window_width_,
237                                            window_height_);
238    if (!current_sim_)
239      return false;
240
241    return true;
242  }
243
244  void CleanupCurrentTest() {
245    LOG(INFO) << "Finished test " << sims_remaining_.front().simulation_name;
246
247    delete current_sim_;
248    current_sim_ = NULL;
249  }
250
251  void UpdateCurrentTest() {
252    ++sims_remaining_.front().frames_rendered;
253
254    if (current_sim_)
255      current_sim_->Update();
256
257    glXSwapBuffers(display_, window_);
258
259    XExposeEvent ev = { Expose, 0, 1, display_, window_,
260                        0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0 };
261    XSendEvent(display_,
262      window_,
263      False,
264      ExposureMask,
265      reinterpret_cast<XEvent*>(&ev));
266
267    base::MessageLoop::current()->PostTask(
268        FROM_HERE,
269        base::Bind(&Simulator::UpdateLoop, weak_factory_.GetWeakPtr()));
270  }
271
272  void DumpOutput() {
273    LOG(INFO) << "Successfully ran " << sims_completed_.size() << " tests";
274
275    FILE* f = base::OpenFile(output_path_, "w");
276
277    if (!f) {
278      LOG(ERROR) << "Failed to open output file " <<
279        output_path_.LossyDisplayName();
280      exit(-1);
281    }
282
283    LOG(INFO) << "Writing results to " << output_path_.LossyDisplayName();
284
285    fputs("{\n\t\"results\": [\n", f);
286
287    while (sims_completed_.size()) {
288      SimulationSpecification i = sims_completed_.front();
289      fprintf(f,
290        "\t\t{\"simulation_name\":\"%s\",\n"
291        "\t\t\t\"render_model\":\"%s\",\n"
292        "\t\t\t\"frames_drawn\":%d\n"
293        "\t\t},\n",
294        i.simulation_name.c_str(),
295        ModelToString(i.model_under_test),
296        i.frames_rendered);
297      sims_completed_.pop();
298    }
299
300    fputs("\t]\n}", f);
301    base::CloseFile(f);
302  }
303
304  bool UpdateTestStatus() {
305    TimeTicks& current_start = sims_remaining_.front().simulation_start_time;
306    base::TimeDelta d = TimeTicks::Now() - current_start;
307    if (!current_start.is_null() && d.InSeconds() > seconds_per_test_) {
308      CleanupCurrentTest();
309      sims_completed_.push(sims_remaining_.front());
310      sims_remaining_.pop();
311    }
312
313    if (sims_remaining_.size() &&
314      sims_remaining_.front().simulation_start_time.is_null()) {
315      while (sims_remaining_.size() && !InitializeNextTest()) {
316        sims_remaining_.pop();
317      }
318      if (sims_remaining_.size()) {
319        sims_remaining_.front().simulation_start_time = TimeTicks::Now();
320      }
321    }
322
323    if (!sims_remaining_.size()) {
324      DumpOutput();
325      base::MessageLoop::current()->Quit();
326      return false;
327    }
328
329    return true;
330  }
331
332  void Resize(int width, int height) {
333    window_width_ = width;
334    window_height_ = height;
335    if (current_sim_)
336      current_sim_->Resize(window_width_, window_height_);
337  }
338
339  // Simulation task list for this execution
340  RenderModelSimulator* current_sim_;
341  queue<SimulationSpecification> sims_remaining_;
342  queue<SimulationSpecification> sims_completed_;
343  base::FilePath output_path_;
344  // Amount of time to run each simulation
345  int seconds_per_test_;
346  // GUI data
347  base::WeakPtrFactory<Simulator> weak_factory_;
348  Display* display_;
349  Window window_;
350  GLXContext gl_context_;
351  int window_width_;
352  int window_height_;
353};
354
355int main(int argc, char* argv[]) {
356  CommandLine::Init(argc, argv);
357  const CommandLine* cl = CommandLine::ForCurrentProcess();
358
359  if (argc != 3 && argc != 4) {
360    LOG(INFO) << "Usage: \n" <<
361      cl->GetProgram().BaseName().LossyDisplayName() <<
362      "--in=[input path] --out=[output path] (duration=[seconds])\n"
363      "The input path specifies either a JSON configuration file or\n"
364      "a directory containing only these files\n"
365      "(if a directory is specified, simulations will be run for\n"
366      "all files in that directory and subdirectories)\n"
367      "The optional duration parameter specifies the (integer)\n"
368      "number of seconds to be spent on each simulation.\n"
369      "Performance measurements for the specified simulation(s) are\n"
370      "written to the output path.";
371    return -1;
372  }
373
374  int seconds_per_test = 1;
375  if (cl->HasSwitch("duration")) {
376    seconds_per_test = atoi(cl->GetSwitchValueASCII("duration").c_str());
377  }
378
379  Simulator sim(seconds_per_test, cl->GetSwitchValuePath("out"));
380  base::FilePath inPath = cl->GetSwitchValuePath("in");
381
382  if (!PathExists(inPath)) {
383    LOG(FATAL) << "Path does not exist: " << inPath.LossyDisplayName();
384    return -1;
385  }
386
387  if (DirectoryExists(inPath)) {
388    LOG(INFO) << "(input path is a directory)";
389    base::FileEnumerator dirItr(inPath, true, base::FileEnumerator::FILES);
390    for (base::FilePath f = dirItr.Next(); !f.empty(); f = dirItr.Next()) {
391      sim.QueueTest(f);
392    }
393  } else {
394    LOG(INFO) << "(input path is a file)";
395    sim.QueueTest(inPath);
396  }
397
398  sim.Run();
399
400  return 0;
401}
402