]> git.cworth.org Git - apitrace/blob - retrace/retrace_main.cpp
mt retrace: add support for multi-threaded playback
[apitrace] / retrace / retrace_main.cpp
1 /**************************************************************************
2  *
3  * Copyright 2011 Jose Fonseca
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  *
24  **************************************************************************/
25
26
27 #include <string.h>
28 #include <iostream>
29
30 #include "glws.hpp"
31 #include "os_binary.hpp"
32 #include "os_time.hpp"
33 #include "os_workqueue.hpp"
34 #include "image.hpp"
35 #include "trace_callset.hpp"
36 #include "trace_dump.hpp"
37 #include "retrace.hpp"
38
39
40 static bool waitOnFinish = false;
41 static bool use_threads;
42
43 static const char *comparePrefix = NULL;
44 static const char *snapshotPrefix = NULL;
45 static trace::CallSet snapshotFrequency;
46 static trace::CallSet compareFrequency;
47
48 static unsigned dumpStateCallNo = ~0;
49
50 retrace::Retracer retracer;
51
52
53 namespace retrace {
54
55
56 trace::Parser parser;
57 trace::Profiler profiler;
58
59 static std::map<unsigned long, os::WorkQueue *> thread_wq_map;
60
61 int verbosity = 0;
62 bool debug = true;
63 bool dumpingState = false;
64
65 bool doubleBuffer = true;
66 bool coreProfile = false;
67
68 bool profiling = false;
69 bool profilingGpuTimes = false;
70 bool profilingCpuTimes = false;
71 bool profilingPixelsDrawn = false;
72
73 unsigned frameNo = 0;
74 unsigned callNo = 0;
75 static bool state_dumped;
76
77 class RenderWork : public os::WorkQueueWork
78 {
79         trace::Call *call;
80 public:
81         void run(void);
82         RenderWork(trace::Call *_call) { call = _call; }
83         ~RenderWork(void) { delete call; }
84 };
85
86 class FlushGLWork : public os::WorkQueueWork
87 {
88 public:
89     void run(void) { flushRendering(); }
90 };
91
92 void
93 frameComplete(trace::Call &call) {
94     ++frameNo;
95 }
96
97
98 static void
99 takeSnapshot(unsigned call_no) {
100     assert(snapshotPrefix || comparePrefix);
101
102     image::Image *ref = NULL;
103
104     if (comparePrefix) {
105         os::String filename = os::String::format("%s%010u.png", comparePrefix, call_no);
106         ref = image::readPNG(filename);
107         if (!ref) {
108             return;
109         }
110         if (retrace::verbosity >= 0) {
111             std::cout << "Read " << filename << "\n";
112         }
113     }
114
115     image::Image *src = getSnapshot();
116     if (!src) {
117         return;
118     }
119
120     if (snapshotPrefix) {
121         if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
122             char comment[21];
123             snprintf(comment, sizeof comment, "%u", call_no);
124             src->writePNM(std::cout, comment);
125         } else {
126             os::String filename = os::String::format("%s%010u.png", snapshotPrefix, call_no);
127             if (src->writePNG(filename) && retrace::verbosity >= 0) {
128                 std::cout << "Wrote " << filename << "\n";
129             }
130         }
131     }
132
133     if (ref) {
134         std::cout << "Snapshot " << call_no << " average precision of " << src->compare(*ref) << " bits\n";
135         delete ref;
136     }
137
138     delete src;
139
140     return;
141 }
142
143 void RenderWork::run(void)
144 {
145     bool swapRenderTarget = call->flags &
146         trace::CALL_FLAG_SWAP_RENDERTARGET;
147     bool doSnapshot = snapshotFrequency.contains(*call) ||
148         compareFrequency.contains(*call);
149
150     if (state_dumped)
151         return;
152
153     // For calls which cause rendertargets to be swaped, we take the
154     // snapshot _before_ swapping the rendertargets.
155     if (doSnapshot && swapRenderTarget) {
156         if (call->flags & trace::CALL_FLAG_END_FRAME) {
157             // For swapbuffers/presents we still use this
158             // call number, spite not have been executed yet.
159             takeSnapshot(call->no);
160         } else {
161             // Whereas for ordinate fbo/rendertarget changes we
162             // use the previous call's number.
163             takeSnapshot(call->no - 1);
164         }
165     }
166
167     callNo = call->no;
168     retracer.retrace(*call);
169
170     if (doSnapshot && !swapRenderTarget)
171         takeSnapshot(call->no);
172
173     if (call->no >= dumpStateCallNo && dumpState(std::cout))
174         state_dumped = true;
175 }
176
177 static os::WorkQueue *get_work_queue(unsigned long thread_id)
178 {
179     os::WorkQueue *thread;
180     std::map<unsigned long, os::WorkQueue *>::iterator it;
181
182     it = thread_wq_map.find(thread_id);
183     if (it == thread_wq_map.end()) {
184         thread = new os::WorkQueue();
185         thread_wq_map[thread_id] = thread;
186     } else {
187         thread = it->second;
188     }
189
190     return thread;
191 }
192
193 static void exit_work_queues(void)
194 {
195     std::map<unsigned long, os::WorkQueue *>::iterator it;
196
197     it = thread_wq_map.begin();
198     while (it != thread_wq_map.end()) {
199         os::WorkQueue *thread_wq = it->second;
200
201         thread_wq->queue_work(new FlushGLWork);
202         thread_wq->flush();
203         thread_wq->destroy();
204         thread_wq_map.erase(it++);
205     }
206 }
207
208 static void do_all_calls(void)
209 {
210     trace::Call *call;
211     int prev_thread_id = -1;
212     os::WorkQueue *thread_wq = NULL;
213
214     while ((call = parser.parse_call())) {
215         RenderWork *render_work = new RenderWork(call);
216
217         if (use_threads) {
218             if (prev_thread_id != call->thread_id) {
219                 if (thread_wq)
220                     thread_wq->flush();
221                 thread_wq = get_work_queue(call->thread_id);
222                 prev_thread_id = call->thread_id;
223             }
224
225             thread_wq->queue_work(render_work);
226         } else {
227             render_work->run();
228             delete render_work;
229         }
230
231         if (state_dumped)
232             break;
233     }
234
235     exit_work_queues();
236 }
237
238
239 static void
240 mainLoop() {
241     addCallbacks(retracer);
242
243     long long startTime = 0; 
244     frameNo = 0;
245
246     startTime = os::getTime();
247
248     do_all_calls();
249
250     if (!use_threads)
251         /*
252          * Reached the end of trace; if using threads we do the flush
253          * when exiting the threads.
254          */
255         flushRendering();
256
257     long long endTime = os::getTime();
258     float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
259
260     if ((retrace::verbosity >= -1) || (retrace::profiling)) {
261         std::cout << 
262             "Rendered " << frameNo << " frames"
263             " in " <<  timeInterval << " secs,"
264             " average of " << (frameNo/timeInterval) << " fps\n";
265     }
266
267     if (waitOnFinish) {
268         waitForInput();
269     } else {
270         return;
271     }
272 }
273
274
275 } /* namespace retrace */
276
277
278 static void
279 usage(const char *argv0) {
280     std::cout << 
281         "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
282         "Replay TRACE.\n"
283         "\n"
284         "  -b           benchmark mode (no error checking or warning messages)\n"
285         "  -pcpu        cpu profiling (cpu times per call)\n"
286         "  -pgpu        gpu profiling (gpu times per draw call)\n"
287         "  -ppd         pixels drawn profiling (pixels drawn per draw call)\n"
288         "  -c PREFIX    compare against snapshots\n"
289         "  -C CALLSET   calls to compare (default is every frame)\n"
290         "  -core        use core profile\n"
291         "  -db          use a double buffer visual (default)\n"
292         "  -sb          use a single buffer visual\n"
293         "  -s PREFIX    take snapshots; `-` for PNM stdout output\n"
294         "  -S CALLSET   calls to snapshot (default is every frame)\n"
295         "  -v           increase output verbosity\n"
296         "  -D CALLNO    dump state at specific call no\n"
297         "  -w           waitOnFinish on final frame\n"
298         "  -t           enable threading\n";
299 }
300
301
302 extern "C"
303 int main(int argc, char **argv)
304 {
305     using namespace retrace;
306
307     assert(compareFrequency.empty());
308     assert(snapshotFrequency.empty());
309
310     int i;
311     for (i = 1; i < argc; ++i) {
312         const char *arg = argv[i];
313
314         if (arg[0] != '-') {
315             break;
316         }
317
318         if (!strcmp(arg, "--")) {
319             break;
320         } else if (!strcmp(arg, "-b")) {
321             retrace::debug = false;
322             retrace::verbosity = -1;
323         } else if (!strcmp(arg, "-c")) {
324             comparePrefix = argv[++i];
325             if (compareFrequency.empty()) {
326                 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
327             }
328         } else if (!strcmp(arg, "-C")) {
329             compareFrequency = trace::CallSet(argv[++i]);
330             if (comparePrefix == NULL) {
331                 comparePrefix = "";
332             }
333         } else if (!strcmp(arg, "-D")) {
334             dumpStateCallNo = atoi(argv[++i]);
335             dumpingState = true;
336             retrace::verbosity = -2;
337         } else if (!strcmp(arg, "-core")) {
338             retrace::coreProfile = true;
339         } else if (!strcmp(arg, "-db")) {
340             retrace::doubleBuffer = true;
341         } else if (!strcmp(arg, "-sb")) {
342             retrace::doubleBuffer = false;
343         } else if (!strcmp(arg, "--help")) {
344             usage(argv[0]);
345             return 0;
346         } else if (!strcmp(arg, "-s")) {
347             snapshotPrefix = argv[++i];
348             if (snapshotFrequency.empty()) {
349                 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
350             }
351             if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
352                 os::setBinaryMode(stdout);
353                 retrace::verbosity = -2;
354             }
355         } else if (!strcmp(arg, "-S")) {
356             snapshotFrequency = trace::CallSet(argv[++i]);
357             if (snapshotPrefix == NULL) {
358                 snapshotPrefix = "";
359             }
360         } else if (!strcmp(arg, "-v")) {
361             ++retrace::verbosity;
362         } else if (!strcmp(arg, "-w")) {
363             waitOnFinish = true;
364         } else if (arg[1] == 'p') {
365             retrace::debug = false;
366             retrace::profiling = true;
367             retrace::verbosity = -1;
368
369             if (!strcmp(arg, "-pcpu")) {
370                 retrace::profilingCpuTimes = true;
371             } else if (!strcmp(arg, "-pgpu")) {
372                 retrace::profilingGpuTimes = true;
373             } else if (!strcmp(arg, "-ppd")) {
374                 retrace::profilingPixelsDrawn = true;
375             }
376         } else if (!strcmp(arg, "-t")) {
377             use_threads = true;
378         } else {
379             std::cerr << "error: unknown option " << arg << "\n";
380             usage(argv[0]);
381             return 1;
382         }
383     }
384
385     retrace::setUp();
386     if (retrace::profiling) {
387         retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn);
388     }
389
390     for ( ; i < argc; ++i) {
391         if (!retrace::parser.open(argv[i])) {
392             std::cerr << "error: failed to open " << argv[i] << "\n";
393             return 1;
394         }
395
396         retrace::mainLoop();
397
398         retrace::parser.close();
399     }
400
401     // XXX: X often hangs on XCloseDisplay
402     //retrace::cleanUp();
403
404     return 0;
405 }
406