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