Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ProfileViewer.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2012 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*
19  * Implementation of profile display (containing only display routines,
20  * the data model(s) are implemented elsewhere).
21  */
22 
23 #include "precompiled.h"
24 
25 #include <ctime>
26 #include <algorithm>
27 
28 #include "ProfileViewer.h"
29 
30 #include "gui/GUIutil.h"
31 #include "graphics/ShaderManager.h"
32 #include "graphics/TextRenderer.h"
33 #include "ps/CLogger.h"
34 #include "ps/Filesystem.h"
35 #include "ps/Font.h"
36 #include "ps/Hotkey.h"
37 #include "ps/Profile.h"
39 #include "renderer/Renderer.h"
41 
42 extern int g_xres, g_yres;
43 
45 {
47 
48  /// Whether the profiling display is currently visible
50 
51  /// List of root tables
52  std::vector<AbstractProfileTable*> rootTables;
53 
54  /// Path from a root table (path[0]) to the currently visible table (path[size-1])
55  std::vector<AbstractProfileTable*> path;
56 
57  /// Helper functions
59  void NavigateTree(int id);
60 
61  /// File for saved profile output (reset when the game is restarted)
62  std::ofstream outputStream;
63 
64 private:
65  // Cannot be copied/assigned, because of the ofstream
68 };
69 
70 
71 ///////////////////////////////////////////////////////////////////////////////////////////////
72 // AbstractProfileTable implementation
73 
75 {
77  {
78  g_ProfileViewer.m->TableIsDeleted(this);
79  }
80 }
81 
82 
83 ///////////////////////////////////////////////////////////////////////////////////////////////
84 // CProfileViewer implementation
85 
86 
87 // AbstractProfileTable got deleted, make sure we have no dangling pointers
89 {
90  for(int idx = (int)rootTables.size()-1; idx >= 0; --idx)
91  {
92  if (rootTables[idx] == table)
93  rootTables.erase(rootTables.begin() + idx);
94  }
95 
96  for(size_t idx = 0; idx < path.size(); ++idx)
97  {
98  if (path[idx] != table)
99  continue;
100 
101  path.erase(path.begin() + idx, path.end());
102  if (path.size() == 0)
103  profileVisible = false;
104  }
105 }
106 
107 
108 // Move into child tables or return to parent tables based on the given number
110 {
111  if (id == 0)
112  {
113  if (path.size() > 1)
114  path.pop_back();
115  }
116  else
117  {
118  AbstractProfileTable* table = path[path.size() - 1];
119  size_t numrows = table->GetNumberRows();
120 
121  for(size_t row = 0; row < numrows; ++row)
122  {
123  AbstractProfileTable* child = table->GetChild(row);
124 
125  if (!child)
126  continue;
127 
128  --id;
129  if (id == 0)
130  {
131  path.push_back(child);
132  break;
133  }
134  }
135  }
136 }
137 
138 
139 // Construction/Destruction
141 {
143  m->profileVisible = false;
144 }
145 
147 {
148  delete m;
149 }
150 
151 
152 // Render
154 {
155  if (!m->profileVisible)
156  return;
157 
158  if (!m->path.size())
159  {
160  m->profileVisible = false;
161  return;
162  }
163 
164  PROFILE3_GPU("profile viewer");
165 
166  glEnable(GL_BLEND);
167  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
168 
169  AbstractProfileTable* table = m->path[m->path.size() - 1];
170  const std::vector<ProfileColumn>& columns = table->GetColumns();
171  size_t numrows = table->GetNumberRows();
172 
173  CStrW font_name = L"mono-stroke-10";
174  CFont font(font_name);
175  int lineSpacing = font.GetLineSpacing();
176 
177  // Render background
178  GLint estimate_height;
179  GLint estimate_width;
180 
181  estimate_width = 50;
182  for(size_t i = 0; i < columns.size(); ++i)
183  estimate_width += (GLint)columns[i].width;
184 
185  estimate_height = 3 + (GLint)numrows;
186  if (m->path.size() > 1)
187  estimate_height += 2;
188  estimate_height = lineSpacing*estimate_height;
189 
190  CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
191  solidTech->BeginPass();
192  CShaderProgramPtr solidShader = solidTech->GetShader();
193 
194  solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.5f);
195 
196  CMatrix3D transform = GetDefaultGuiMatrix();
197  solidShader->Uniform(str_transform, transform);
198 
199  float backgroundVerts[] = {
200  (float)estimate_width, 0.0f,
201  0.0f, 0.0f,
202  0.0f, (float)estimate_height,
203  0.0f, (float)estimate_height,
204  (float)estimate_width, (float)estimate_height,
205  (float)estimate_width, 0.0f
206  };
207  solidShader->VertexPointer(2, GL_FLOAT, 0, backgroundVerts);
208  solidShader->AssertPointersBound();
209  glDrawArrays(GL_TRIANGLES, 0, 6);
210 
211  transform.PostTranslate(22.0f, lineSpacing*3.0f, 0.0f);
212  solidShader->Uniform(str_transform, transform);
213 
214  // Draw row backgrounds
215  for (size_t row = 0; row < numrows; ++row)
216  {
217  if (row % 2)
218  solidShader->Uniform(str_color, 1.0f, 1.0f, 1.0f, 0.1f);
219  else
220  solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.1f);
221 
222  float rowVerts[] = {
223  -22.f, 2.f,
224  estimate_width-22.f, 2.f,
225  estimate_width-22.f, 2.f-lineSpacing,
226 
227  estimate_width-22.f, 2.f-lineSpacing,
228  -22.f, 2.f-lineSpacing,
229  -22.f, 2.f
230  };
231  solidShader->VertexPointer(2, GL_FLOAT, 0, rowVerts);
232  solidShader->AssertPointersBound();
233  glDrawArrays(GL_TRIANGLES, 0, 6);
234 
235  transform.PostTranslate(0.0f, lineSpacing, 0.0f);
236  solidShader->Uniform(str_transform, transform);
237  }
238 
239  solidTech->EndPass();
240 
241  // Print table and column titles
242 
243  CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
244  textTech->BeginPass();
245 
246  CTextRenderer textRenderer(textTech->GetShader());
247  textRenderer.Font(font_name);
248  textRenderer.Color(1.0f, 1.0f, 1.0f);
249 
250  textRenderer.PrintfAt(2.0f, lineSpacing, L"%hs", table->GetTitle().c_str());
251 
252  textRenderer.Translate(22.0f, lineSpacing*2.0f, 0.0f);
253 
254  float colX = 0.0f;
255  for (size_t col = 0; col < columns.size(); ++col)
256  {
257  CStrW text = columns[col].title.FromUTF8();
258  int w, h;
259  font.CalculateStringSize(text.c_str(), w, h);
260 
261  float x = colX;
262  if (col > 0) // right-align all but the first column
263  x += columns[col].width - w;
264  textRenderer.Put(x, 0.0f, text.c_str());
265 
266  colX += columns[col].width;
267  }
268 
269  textRenderer.Translate(0.0f, lineSpacing, 0.0f);
270 
271  // Print rows
272  int currentExpandId = 1;
273 
274  for (size_t row = 0; row < numrows; ++row)
275  {
276  if (table->IsHighlightRow(row))
277  textRenderer.Color(1.0f, 0.5f, 0.5f);
278  else
279  textRenderer.Color(1.0f, 1.0f, 1.0f);
280 
281  if (table->GetChild(row))
282  {
283  textRenderer.PrintfAt(-15.0f, 0.0f, L"%d", currentExpandId);
284  currentExpandId++;
285  }
286 
287  float colX = 0.0f;
288  for (size_t col = 0; col < columns.size(); ++col)
289  {
290  CStrW text = table->GetCellText(row, col).FromUTF8();
291  int w, h;
292  font.CalculateStringSize(text.c_str(), w, h);
293 
294  float x = colX;
295  if (col > 0) // right-align all but the first column
296  x += columns[col].width - w;
297  textRenderer.Put(x, 0.0f, text.c_str());
298 
299  colX += columns[col].width;
300  }
301 
302  textRenderer.Translate(0.0f, lineSpacing, 0.0f);
303  }
304 
305  textRenderer.Color(1.0f, 1.0f, 1.0f);
306 
307  if (m->path.size() > 1)
308  {
309  textRenderer.Translate(0.0f, lineSpacing, 0.0f);
310  textRenderer.Put(-15.0f, 0.0f, L"0");
311  textRenderer.Put(0.0f, 0.0f, L"back to parent");
312  }
313 
314  textRenderer.Render();
315  textTech->EndPass();
316 
317  glDisable(GL_BLEND);
318 
319  glEnable(GL_DEPTH_TEST);
320 }
321 
322 
323 // Handle input
325 {
326  switch(ev->ev.type)
327  {
328  case SDL_KEYDOWN:
329  {
330  if (!m->profileVisible)
331  break;
332 
333  int k = ev->ev.key.keysym.sym;
334  if (k >= SDLK_0 && k <= SDLK_9)
335  {
336  m->NavigateTree(k - SDLK_0);
337  return IN_HANDLED;
338  }
339  break;
340  }
341  case SDL_HOTKEYDOWN:
342  std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
343 
344  if( hotkey == "profile.toggle" )
345  {
346  if (!m->profileVisible)
347  {
348  if (m->rootTables.size())
349  {
350  m->profileVisible = true;
351  m->path.push_back(m->rootTables[0]);
352  }
353  }
354  else
355  {
356  size_t i;
357 
358  for(i = 0; i < m->rootTables.size(); ++i)
359  {
360  if (m->rootTables[i] == m->path[0])
361  break;
362  }
363  i++;
364 
365  m->path.clear();
366  if (i < m->rootTables.size())
367  {
368  m->path.push_back(m->rootTables[i]);
369  }
370  else
371  {
372  m->profileVisible = false;
373  }
374  }
375  return( IN_HANDLED );
376  }
377  else if( hotkey == "profile.save" )
378  {
379  SaveToFile();
380  return( IN_HANDLED );
381  }
382  break;
383  }
384  return( IN_PASS );
385 }
386 
388 {
390  return g_ProfileViewer.Input(ev);
391 
392  return IN_PASS;
393 }
394 
395 
396 // Add a table to the list of roots
398 {
399  if (front)
400  m->rootTables.insert(m->rootTables.begin(), table);
401  else
402  m->rootTables.push_back(table);
403 }
404 
405 namespace
406 {
407  struct WriteTable
408  {
409  std::ofstream& f;
410  WriteTable(std::ofstream& f) : f(f) {}
411 
412  void operator() (AbstractProfileTable* table)
413  {
414  std::vector<CStr> data; // 2d array of (rows+head)*columns elements
415 
416  const std::vector<ProfileColumn>& columns = table->GetColumns();
417 
418  // Add column headers to 'data'
419  for (std::vector<ProfileColumn>::const_iterator col_it = columns.begin();
420  col_it != columns.end(); ++col_it)
421  data.push_back(col_it->title);
422 
423  // Recursively add all profile data to 'data'
424  WriteRows(1, table, data);
425 
426  // Calculate the width of each column ( = the maximum width of
427  // any value in that column)
428  std::vector<size_t> columnWidths;
429  size_t cols = columns.size();
430  for (size_t c = 0; c < cols; ++c)
431  {
432  size_t max = 0;
433  for (size_t i = c; i < data.size(); i += cols)
434  max = std::max(max, data[i].length());
435  columnWidths.push_back(max);
436  }
437 
438  // Output data as a formatted table:
439 
440  f << "\n\n" << table->GetTitle() << "\n";
441 
442  if (cols == 0) // avoid divide-by-zero
443  return;
444 
445  for (size_t r = 0; r < data.size()/cols; ++r)
446  {
447  for (size_t c = 0; c < cols; ++c)
448  f << (c ? " | " : "\n")
449  << data[r*cols + c].Pad(PS_TRIM_RIGHT, columnWidths[c]);
450 
451  // Add dividers under some rows. (Currently only the first, since
452  // that contains the column headers.)
453  if (r == 0)
454  for (size_t c = 0; c < cols; ++c)
455  f << (c ? "-|-" : "\n")
456  << CStr::Repeat("-", columnWidths[c]);
457  }
458  }
459 
460  void WriteRows(int indent, AbstractProfileTable* table, std::vector<CStr>& data)
461  {
462  const std::vector<ProfileColumn>& columns = table->GetColumns();
463 
464  for (size_t r = 0; r < table->GetNumberRows(); ++r)
465  {
466  // Do pretty tree-structure indenting
467  CStr indentation = CStr::Repeat("| ", indent-1);
468  if (r+1 == table->GetNumberRows())
469  indentation += "'-";
470  else
471  indentation += "|-";
472 
473  for (size_t c = 0; c < columns.size(); ++c)
474  if (c == 0)
475  data.push_back(indentation + table->GetCellText(r, c));
476  else
477  data.push_back(table->GetCellText(r, c));
478 
479  if (table->GetChild(r))
480  WriteRows(indent+1, table->GetChild(r), data);
481  }
482  }
483 
484  private:
485  const WriteTable& operator=(const WriteTable&);
486  };
487 
488  struct DumpTable
489  {
492  DumpTable(ScriptInterface& scriptInterface, CScriptVal root) :
493  scriptInterface(scriptInterface), root(root)
494  {
495  }
496 
497  void operator() (AbstractProfileTable* table)
498  {
499  CScriptVal t;
500  scriptInterface.Eval(L"({})", t);
501  scriptInterface.SetProperty(t.get(), "cols", DumpCols(table));
502  scriptInterface.SetProperty(t.get(), "data", DumpRows(table));
503 
504  scriptInterface.SetProperty(root.get(), table->GetTitle().c_str(), t);
505  }
506 
507  std::vector<std::string> DumpCols(AbstractProfileTable* table)
508  {
509  std::vector<std::string> titles;
510 
511  const std::vector<ProfileColumn>& columns = table->GetColumns();
512 
513  for (size_t c = 0; c < columns.size(); ++c)
514  titles.push_back(columns[c].title);
515 
516  return titles;
517  }
518 
520  {
521  CScriptVal data;
522  scriptInterface.Eval("({})", data);
523 
524  const std::vector<ProfileColumn>& columns = table->GetColumns();
525 
526  for (size_t r = 0; r < table->GetNumberRows(); ++r)
527  {
528  CScriptVal row;
529  scriptInterface.Eval("([])", row);
530  scriptInterface.SetProperty(data.get(), table->GetCellText(r, 0).c_str(), row);
531 
532  if (table->GetChild(r))
533  scriptInterface.SetPropertyInt(row.get(), 0, DumpRows(table->GetChild(r)));
534 
535  for (size_t c = 1; c < columns.size(); ++c)
536  scriptInterface.SetPropertyInt(row.get(), c, table->GetCellText(r, c));
537  }
538 
539  return data;
540  }
541 
542  private:
543  const DumpTable& operator=(const DumpTable&);
544  };
545 
547  {
548  return (a->GetName() < b->GetName());
549  }
550 }
551 
553 {
554  // Open the file, if necessary. If this method is called several times,
555  // the profile results will be appended to the previous ones from the same
556  // run.
557  if (! m->outputStream.is_open())
558  {
559  // Open the file. (It will be closed when the CProfileViewer
560  // destructor is called.)
561  OsPath path = psLogDir()/"profile.txt";
562  m->outputStream.open(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
563 
564  if (m->outputStream.fail())
565  {
566  LOGERROR(L"Failed to open profile log file");
567  return;
568  }
569  else
570  {
571  LOGMESSAGERENDER(L"Profiler snapshot saved to '%ls'", path.string().c_str());
572  }
573  }
574 
575  time_t t;
576  time(&t);
577  m->outputStream << "================================================================\n\n";
578  m->outputStream << "PS profiler snapshot - " << asctime(localtime(&t));
579 
580  std::vector<AbstractProfileTable*> tables = m->rootTables;
581  sort(tables.begin(), tables.end(), SortByName);
582  for_each(tables.begin(), tables.end(), WriteTable(m->outputStream));
583 
584  m->outputStream << "\n\n================================================================\n";
585  m->outputStream.flush();
586 }
587 
589 {
590  CScriptVal root;
591  scriptInterface.Eval("({})", root);
592 
593  std::vector<AbstractProfileTable*> tables = m->rootTables;
594  sort(tables.begin(), tables.end(), SortByName);
595  for_each(tables.begin(), tables.end(), DumpTable(scriptInterface, root));
596 
597  return root;
598 }
599 
600 void CProfileViewer::ShowTable(const CStr& table)
601 {
602  m->path.clear();
603 
604  if (table.length() > 0)
605  {
606  for (size_t i = 0; i < m->rootTables.size(); ++i)
607  {
608  if (m->rootTables[i]->GetName() == table)
609  {
610  m->path.push_back(m->rootTables[i]);
611  m->profileVisible = true;
612  return;
613  }
614  }
615  }
616 
617  // No matching table found, so don't display anything
618  m->profileVisible = false;
619 }
int GetLineSpacing()
Definition: Font.cpp:55
CMatrix3D GetDefaultGuiMatrix()
Definition: GUIutil.cpp:253
bool SortByName(AbstractProfileTable *a, AbstractProfileTable *b)
Trim all white space from the beginning of the string.
Definition: CStr.h:34
SDL_KeyboardEvent key
Definition: wsdl.h:305
void CalculateStringSize(const wchar_t *string, int &w, int &h)
Definition: Font.cpp:70
const OsPath & psLogDir()
Definition: Pyrogenesis.cpp:96
#define LOGERROR
Definition: CLogger.h:35
virtual const std::vector< ProfileColumn > & GetColumns()=0
GetColumnDescriptions.
Class AbstractProfileTable: Profile table data model.
Definition: ProfileViewer.h:60
int g_xres
Definition: Config.cpp:58
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:419
virtual CStr GetName()=0
GetName: Short descriptive name of this table (should be static).
std::vector< AbstractProfileTable * > path
Path from a root table (path[0]) to the currently visible table (path[size-1])
const jsval & get() const
Returns the current value.
Definition: ScriptVal.h:38
std::vector< std::string > DumpCols(AbstractProfileTable *table)
InReaction Input(const SDL_Event_ *ev)
Input: Filter and handle any input events that the profile display is interested in.
A trivial wrapper around a jsval.
Definition: ScriptVal.h:29
shared_ptr< CShaderTechnique > CShaderTechniquePtr
void RenderProfile()
RenderProfile: Render the profile display using OpenGL if the user has enabled it.
void SaveToFile()
SaveToFile: Save the current profiler data (for all profile tables) to a file in the &#39;logs&#39; directory...
#define g_Renderer
Definition: Renderer.h:61
Hotkey system.
void PostTranslate(float x, float y, float z)
Definition: Matrix3D.cpp:186
SDL_Event ev
Definition: libsdl.h:56
bool profileVisible
Whether the profiling display is currently visible.
void AddRootTable(AbstractProfileTable *table, bool front=false)
AddRootTable: Add a new profile table as a root table (i.e.
void ShowTable(const CStr &table)
ShowTable: Set the named profile table to be the displayed one.
Uint8 type
Definition: wsdl.h:302
Definition: Font.h:28
const CProfileViewerInternals & operator=(const CProfileViewerInternals &rhs)
Definition: path.h:75
const String & string() const
Definition: path.h:123
InReaction
Definition: input.h:34
Definition: input.h:40
int g_yres
Definition: Config.cpp:58
bool Eval(const char *code)
static bool IsInitialised()
Definition: Singleton.h:63
CScriptVal DumpRows(AbstractProfileTable *table)
const int SDL_HOTKEYDOWN
Definition: Hotkey.h:41
virtual size_t GetNumberRows()=0
GetNumberRows.
void WriteRows(int indent, AbstractProfileTable *table, std::vector< CStr > &data)
std::vector< AbstractProfileTable * > rootTables
List of root tables.
static InReaction InputThunk(const SDL_Event_ *ev)
InputThunk: Delegate to the singleton&#39;s Input() member function if the singleton has been initialized...
CProfileViewerInternals * m
DumpTable(ScriptInterface &scriptInterface, CScriptVal root)
static const AcpiTable ** tables
Definition: acpi.cpp:342
virtual AbstractProfileTable * GetChild(size_t row)=0
GetChild: Return a row&#39;s child table if the child is expandable.
virtual CStr GetTitle()=0
GetTitle: Longer, explanatory text (can be dynamic).
CScriptVal SaveToJS(ScriptInterface &scriptInterface)
SaveToJS: Return a script value containing the current profiler data (for all profile tables)...
void Font(const CStrW &font)
Set the font for subsequent print calls.
#define g_ProfileViewer
virtual CStr GetCellText(size_t row, size_t col)=0
GetCellText.
void TableIsDeleted(AbstractProfileTable *table)
Helper functions.
#define LOGMESSAGERENDER
Definition: CLogger.h:33
SDL_keysym keysym
Definition: wsdl.h:196
Abstraction around a SpiderMonkey JSContext.
#define PROFILE3_GPU(name)
Definition: Profile.h:204
std::ofstream outputStream
File for saved profile output (reset when the game is restarted)
SDL_UserEvent user
Definition: wsdl.h:312
virtual bool IsHighlightRow(size_t row)
IsHighlightRow.
SDLKey sym
Definition: wsdl.h:188
virtual ~AbstractProfileTable()
shared_ptr< CShaderProgram > CShaderProgramPtr
void * data1
Definition: wsdl.h:282
static std::string OsString(const OsPath &path)
Definition: os_path.h:42