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 39 using base::TimeTicks; 40 using base::DirectoryExists; 41 using base::PathExists; 42 using std::queue; 43 using std::string; 44 45 struct 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 54 class Simulator; 55 void _process_events(Simulator* sim); 56 void _update_loop(Simulator* sim); 57 58 class 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 355 int 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