]> git.cworth.org Git - apitrace/blob - retrace/glretrace_main.cpp
replay: Support applications mixing glCreateProgramObjectARB and glUseProgram
[apitrace] / retrace / glretrace_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
29 #include "retrace.hpp"
30 #include "glproc.hpp"
31 #include "glstate.hpp"
32 #include "glretrace.hpp"
33 #include "os_time.hpp"
34
35 /* Synchronous debug output may reduce performance however,
36  * without it the callNo in the callback may be inaccurate
37  * as the callback may be called at any time.
38  */
39 #define DEBUG_OUTPUT_SYNCHRONOUS 0
40
41 namespace glretrace {
42
43 bool insideList = false;
44 bool insideGlBeginEnd = false;
45 bool supportsARBShaderObjects = false;
46
47 enum {
48     GPU_START = 0,
49     GPU_DURATION,
50     OCCLUSION,
51     NUM_QUERIES,
52 };
53
54 struct CallQuery
55 {
56     GLuint ids[NUM_QUERIES];
57     unsigned call;
58     bool isDraw;
59     GLuint program;
60     const trace::FunctionSig *sig;
61     int64_t cpuStart;
62     int64_t cpuEnd;
63 };
64
65 static bool supportsElapsed = true;
66 static bool supportsTimestamp = true;
67 static bool supportsOcclusion = true;
68 static bool supportsDebugOutput = true;
69
70 static std::list<CallQuery> callQueries;
71
72 static void APIENTRY
73 debugOutputCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam);
74
75 void
76 checkGlError(trace::Call &call) {
77     GLenum error = glGetError();
78     while (error != GL_NO_ERROR) {
79         std::ostream & os = retrace::warning(call);
80
81         os << "glGetError(";
82         os << call.name();
83         os << ") = ";
84
85         switch (error) {
86         case GL_INVALID_ENUM:
87             os << "GL_INVALID_ENUM";
88             break;
89         case GL_INVALID_VALUE:
90             os << "GL_INVALID_VALUE";
91             break;
92         case GL_INVALID_OPERATION:
93             os << "GL_INVALID_OPERATION";
94             break;
95         case GL_STACK_OVERFLOW:
96             os << "GL_STACK_OVERFLOW";
97             break;
98         case GL_STACK_UNDERFLOW:
99             os << "GL_STACK_UNDERFLOW";
100             break;
101         case GL_OUT_OF_MEMORY:
102             os << "GL_OUT_OF_MEMORY";
103             break;
104         case GL_INVALID_FRAMEBUFFER_OPERATION:
105             os << "GL_INVALID_FRAMEBUFFER_OPERATION";
106             break;
107         case GL_TABLE_TOO_LARGE:
108             os << "GL_TABLE_TOO_LARGE";
109             break;
110         default:
111             os << error;
112             break;
113         }
114         os << "\n";
115     
116         error = glGetError();
117     }
118 }
119
120 static inline int64_t
121 getCurrentTime(void) {
122     if (retrace::profilingGpuTimes && supportsTimestamp) {
123         /* Get the current GL time without stalling */
124         GLint64 timestamp = 0;
125         glGetInteger64v(GL_TIMESTAMP, &timestamp);
126         return timestamp;
127     } else {
128         return os::getTime();
129     }
130 }
131
132 static inline int64_t
133 getTimeFrequency(void) {
134     if (retrace::profilingGpuTimes && supportsTimestamp) {
135         return 1000000000;
136     } else {
137         return os::timeFrequency;
138     }
139 }
140
141 static void
142 completeCallQuery(CallQuery& query) {
143     /* Get call start and duration */
144     int64_t gpuStart = 0, gpuDuration = 0, cpuDuration = 0, pixels = 0;
145
146     if (query.isDraw) {
147         if (retrace::profilingGpuTimes) {
148             if (supportsTimestamp) {
149                 glGetQueryObjecti64vEXT(query.ids[GPU_START], GL_QUERY_RESULT, &gpuStart);
150             }
151
152             glGetQueryObjecti64vEXT(query.ids[GPU_DURATION], GL_QUERY_RESULT, &gpuDuration);
153         }
154
155         if (retrace::profilingPixelsDrawn) {
156             glGetQueryObjecti64vEXT(query.ids[OCCLUSION], GL_QUERY_RESULT, &pixels);
157         }
158
159     } else {
160         pixels = -1;
161     }
162
163     if (retrace::profilingCpuTimes) {
164         cpuDuration = query.cpuEnd - query.cpuStart;
165     }
166
167     glDeleteQueries(NUM_QUERIES, query.ids);
168
169     /* Add call to profile */
170     retrace::profiler.addCall(query.call, query.sig->name, query.program, pixels, gpuStart, gpuDuration, query.cpuStart, cpuDuration);
171 }
172
173 void
174 flushQueries() {
175     for (std::list<CallQuery>::iterator itr = callQueries.begin(); itr != callQueries.end(); ++itr) {
176         completeCallQuery(*itr);
177     }
178
179     callQueries.clear();
180 }
181
182 void
183 beginProfile(trace::Call &call, bool isDraw) {
184     glretrace::Context *currentContext = glretrace::getCurrentContext();
185
186     /* Create call query */
187     CallQuery query;
188     query.isDraw = isDraw;
189     query.call = call.no;
190     query.sig = call.sig;
191     query.program = currentContext ? currentContext->activeProgram : 0;
192
193     glGenQueries(NUM_QUERIES, query.ids);
194
195     /* GPU profiling only for draw calls */
196     if (isDraw) {
197         if (retrace::profilingGpuTimes) {
198             if (supportsTimestamp) {
199                 glQueryCounter(query.ids[GPU_START], GL_TIMESTAMP);
200             }
201
202             glBeginQuery(GL_TIME_ELAPSED, query.ids[GPU_DURATION]);
203         }
204
205         if (retrace::profilingPixelsDrawn) {
206             glBeginQuery(GL_SAMPLES_PASSED, query.ids[OCCLUSION]);
207         }
208     }
209
210     callQueries.push_back(query);
211
212     /* CPU profiling for all calls */
213     if (retrace::profilingCpuTimes) {
214         CallQuery& query = callQueries.back();
215         query.cpuStart = getCurrentTime();
216     }
217 }
218
219 void
220 endProfile(trace::Call &call, bool isDraw) {
221
222     /* CPU profiling for all calls */
223     if (retrace::profilingCpuTimes) {
224         CallQuery& query = callQueries.back();
225         query.cpuEnd = getCurrentTime();
226     }
227
228     /* GPU profiling only for draw calls */
229     if (isDraw) {
230         if (retrace::profilingGpuTimes) {
231             glEndQuery(GL_TIME_ELAPSED);
232         }
233
234         if (retrace::profilingPixelsDrawn) {
235             glEndQuery(GL_SAMPLES_PASSED);
236         }
237     }
238 }
239
240 void
241 initContext() {
242     glretrace::Context *currentContext = glretrace::getCurrentContext();
243
244     /* Ensure we have adequate extension support */
245     assert(currentContext);
246     supportsTimestamp   = currentContext->hasExtension("GL_ARB_timer_query");
247     supportsElapsed     = currentContext->hasExtension("GL_EXT_timer_query") || supportsTimestamp;
248     supportsOcclusion   = currentContext->hasExtension("GL_ARB_occlusion_query");
249     supportsDebugOutput = currentContext->hasExtension("GL_ARB_debug_output");
250     supportsARBShaderObjects = currentContext->hasExtension("GL_ARB_shader_objects");
251
252     /* Check for timer query support */
253     if (retrace::profilingGpuTimes) {
254         if (!supportsTimestamp && !supportsElapsed) {
255             std::cout << "Error: Cannot run profile, GL_EXT_timer_query extension is not supported." << std::endl;
256             exit(-1);
257         }
258
259         GLint bits = 0;
260         glGetQueryiv(GL_TIME_ELAPSED, GL_QUERY_COUNTER_BITS, &bits);
261
262         if (!bits) {
263             std::cout << "Error: Cannot run profile, GL_QUERY_COUNTER_BITS == 0." << std::endl;
264             exit(-1);
265         }
266     }
267
268     /* Check for occlusion query support */
269     if (retrace::profilingPixelsDrawn && !supportsOcclusion) {
270         std::cout << "Error: Cannot run profile, GL_ARB_occlusion_query extension is not supported." << std::endl;
271         exit(-1);
272     }
273
274     /* Setup debug message call back */
275     if (retrace::debug && supportsDebugOutput) {
276         glretrace::Context *currentContext = glretrace::getCurrentContext();
277         glDebugMessageCallbackARB(&debugOutputCallback, currentContext);
278
279         if (DEBUG_OUTPUT_SYNCHRONOUS) {
280             glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
281         }
282     }
283
284     /* Sync the gpu and cpu start times */
285     if (retrace::profilingCpuTimes || retrace::profilingGpuTimes) {
286         if (!retrace::profiler.hasBaseTimes()) {
287             GLint64 currentTime = getCurrentTime();
288             retrace::profiler.setBaseCpuTime(currentTime);
289             retrace::profiler.setBaseGpuTime(currentTime);
290         }
291     }
292 }
293
294 void
295 frame_complete(trace::Call &call) {
296     if (retrace::profiling) {
297         /* Complete any remaining queries */
298         flushQueries();
299
300         /* Indicate end of current frame */
301         retrace::profiler.addFrameEnd();
302     }
303
304     retrace::frameComplete(call);
305
306     glretrace::Context *currentContext = glretrace::getCurrentContext();
307     if (!currentContext) {
308         return;
309     }
310
311     assert(currentContext->drawable);
312     if (retrace::debug && !currentContext->drawable->visible) {
313         retrace::warning(call) << "could not infer drawable size (glViewport never called)\n";
314     }
315 }
316
317 static const char*
318 getDebugOutputSource(GLenum source) {
319     switch(source) {
320     case GL_DEBUG_SOURCE_API_ARB:
321         return "API";
322     case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB:
323         return "Window System";
324     case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB:
325         return "Shader Compiler";
326     case GL_DEBUG_SOURCE_THIRD_PARTY_ARB:
327         return "Third Party";
328     case GL_DEBUG_SOURCE_APPLICATION_ARB:
329         return "Application";
330     case GL_DEBUG_SOURCE_OTHER_ARB:
331     default:
332         return "";
333     }
334 }
335
336 static const char*
337 getDebugOutputType(GLenum type) {
338     switch(type) {
339     case GL_DEBUG_TYPE_ERROR_ARB:
340         return "error";
341     case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB:
342         return "deprecated behaviour";
343     case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB:
344         return "undefined behaviour";
345     case GL_DEBUG_TYPE_PORTABILITY_ARB:
346         return "portability issue";
347     case GL_DEBUG_TYPE_PERFORMANCE_ARB:
348         return "performance issue";
349     case GL_DEBUG_TYPE_OTHER_ARB:
350     default:
351         return "unknown issue";
352     }
353 }
354
355 static const char*
356 getDebugOutputSeverity(GLenum severity) {
357     switch(severity) {
358     case GL_DEBUG_SEVERITY_HIGH_ARB:
359         return "High";
360     case GL_DEBUG_SEVERITY_MEDIUM_ARB:
361         return "Medium";
362     case GL_DEBUG_SEVERITY_LOW_ARB:
363         return "Low";
364     default:
365         return "usnknown";
366     }
367 }
368
369 static void APIENTRY
370 debugOutputCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam) {
371     std::cerr << retrace::callNo << ": ";
372     std::cerr << "glDebugOutputCallback: ";
373     std::cerr << getDebugOutputSeverity(severity) << " severity ";
374     std::cerr << getDebugOutputSource(source) << " " << getDebugOutputType(type);
375     std::cerr << " " << id;
376     std::cerr << ", " << message;
377     std::cerr << std::endl;
378 }
379
380 } /* namespace glretrace */
381
382
383 class GLDumper : public retrace::Dumper {
384 public:
385     image::Image *
386     getSnapshot(void) {
387         if (!glretrace::getCurrentContext()) {
388             return NULL;
389         }
390         return glstate::getDrawBufferImage();
391     }
392
393     bool
394     dumpState(std::ostream &os) {
395         glretrace::Context *currentContext = glretrace::getCurrentContext();
396         if (glretrace::insideGlBeginEnd ||
397             !currentContext) {
398             return false;
399         }
400         glstate::dumpCurrentContext(os);
401         return true;
402     }
403 };
404
405 static GLDumper glDumper;
406
407
408 void
409 retrace::setUp(void) {
410     glws::init();
411     dumper = &glDumper;
412 }
413
414
415 void
416 retrace::addCallbacks(retrace::Retracer &retracer)
417 {
418     retracer.addCallbacks(glretrace::gl_callbacks);
419     retracer.addCallbacks(glretrace::glx_callbacks);
420     retracer.addCallbacks(glretrace::wgl_callbacks);
421     retracer.addCallbacks(glretrace::cgl_callbacks);
422     retracer.addCallbacks(glretrace::egl_callbacks);
423 }
424
425
426 void
427 retrace::flushRendering(void) {
428     glretrace::Context *currentContext = glretrace::getCurrentContext();
429     if (currentContext) {
430         glretrace::flushQueries();
431         glFlush();
432     }
433 }
434
435 void
436 retrace::waitForInput(void) {
437     while (glws::processEvents()) {
438         os::sleep(100*1000);
439     }
440 }
441
442 void
443 retrace::cleanUp(void) {
444     glws::cleanup();
445 }