From: José Fonseca Date: Fri, 30 Mar 2012 19:41:25 +0000 (+0100) Subject: Merge branch 'master' into d3dretrace X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=12a22803faa06a0db229b6dfb6130a0e8156ba31;hp=ee659c84695e9f94b1f6a5f8be203dc0012ca685;p=apitrace Merge branch 'master' into d3dretrace Conflicts: retrace.py --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 35d9a78..03208fb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,10 +60,6 @@ else () include_directories (${X11_INCLUDE_DIR}) add_definitions (-DHAVE_X11) endif () - - if (ENABLE_EGL) - add_definitions (-DHAVE_EGL) - endif () endif () @@ -510,12 +506,6 @@ if (ENABLE_EGL AND NOT WIN32 AND NOT APPLE) add_dependencies (egltrace glproc) - set_property ( - TARGET egltrace - APPEND - PROPERTY COMPILE_DEFINITIONS "TRACE_EGL" - ) - set_target_properties (egltrace PROPERTIES # avoid the default "lib" prefix PREFIX "" @@ -548,11 +538,11 @@ add_custom_command ( add_custom_command ( OUTPUT glstate_params.cpp - COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/glstate.py > ${CMAKE_CURRENT_BINARY_DIR}/glstate_params.cpp - DEPENDS glstate.py specs/glparams.py specs/gltypes.py specs/stdapi.py + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/glstate_params.py > ${CMAKE_CURRENT_BINARY_DIR}/glstate_params.cpp + DEPENDS glstate_params.py specs/glparams.py specs/gltypes.py specs/stdapi.py ) -set (retrace_sources +add_library (retrace_common glretrace_gl.cpp glretrace_cgl.cpp glretrace_glx.cpp @@ -560,15 +550,22 @@ set (retrace_sources glretrace_egl.cpp glretrace_main.cpp glstate.cpp + glstate_images.cpp glstate_params.cpp + glstate_shaders.cpp retrace.cpp retrace_stdc.cpp glws.cpp ) +set_property ( + TARGET retrace_common + APPEND + PROPERTY COMPILE_DEFINITIONS "RETRACE" +) + if (WIN32 OR APPLE OR X11_FOUND) add_executable (glretrace - ${retrace_sources} ${glws_os} glproc_gl.cpp ) @@ -582,6 +579,7 @@ if (WIN32 OR APPLE OR X11_FOUND) ) target_link_libraries (glretrace + retrace_common common ${PNG_LIBRARIES} ${ZLIB_LIBRARIES} @@ -619,7 +617,6 @@ endif () if (ENABLE_EGL AND X11_FOUND AND NOT WIN32 AND NOT APPLE) add_executable (eglretrace - ${retrace_sources} glws_egl_xlib.cpp glproc_egl.cpp ) @@ -630,10 +627,10 @@ if (ENABLE_EGL AND X11_FOUND AND NOT WIN32 AND NOT APPLE) TARGET eglretrace APPEND PROPERTY COMPILE_DEFINITIONS "RETRACE" - PROPERTY COMPILE_DEFINITIONS "TRACE_EGL" ) target_link_libraries (eglretrace + retrace_common common ${PNG_LIBRARIES} ${ZLIB_LIBRARIES} diff --git a/NEWS.markdown b/NEWS.markdown index 562fb74..658a907 100644 --- a/NEWS.markdown +++ b/NEWS.markdown @@ -2,6 +2,14 @@ This file lists the major user visible improvements. For a full list of changes and their authors see the git history. +Development +=========== + +* Support to trace in Android + +* Show frame thumbnails in the GUI + + Version 3.0 =========== diff --git a/README.markdown b/README.markdown index 168a6ea..536729e 100644 --- a/README.markdown +++ b/README.markdown @@ -358,16 +358,20 @@ reference software renderer. This can be achieved with retracediff.py script, which invokes glretrace with different environments, allowing to choose the desired GL driver by -manipulating variables such as `LD_LIBRARY_PATH` or `LIBGL_DRIVERS_DIR`. +manipulating variables such as `LD_LIBRARY_PATH`, `LIBGL_DRIVERS_DIR`, or +`TRACE_LIBGL`. -For example: +For example, on Linux: ./scripts/retracediff.py \ --ref-env LD_LIBRARY_PATH=/path/to/reference/GL/implementation \ - -r ./glretrace \ + --retrace /path/to/glretrace \ --diff-prefix=/path/to/output/diffs \ application.trace +Or on Windows: + + python scripts\retracediff.py --retrace \path\to\glretrace.exe --ref-env TRACE_LIBGL=\path\to\reference\opengl32.dll application.trace Links diff --git a/cli/cli_dump.cpp b/cli/cli_dump.cpp index 28c91af..f52b83c 100644 --- a/cli/cli_dump.cpp +++ b/cli/cli_dump.cpp @@ -28,6 +28,9 @@ #include #include // for CHAR_MAX #include +#ifndef _WIN32 +#include // for isatty() +#endif #include "cli.hpp" #include "cli_pager.hpp" diff --git a/common/image.hpp b/common/image.hpp index 6316791..ea3eefb 100644 --- a/common/image.hpp +++ b/common/image.hpp @@ -108,6 +108,8 @@ bool writePixelsToBuffer(unsigned char *pixels, Image * readPNG(const char *filename); +const char * +readPNMHeader(const char *buffer, size_t size, unsigned *channels, unsigned *width, unsigned *height); } /* namespace image */ diff --git a/common/image_pnm.cpp b/common/image_pnm.cpp index c95f640..f9cd05d 100644 --- a/common/image_pnm.cpp +++ b/common/image_pnm.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "image.hpp" @@ -108,5 +109,55 @@ Image::writePNM(std::ostream &os, const char *comment) const { } } +const char * +readPNMHeader(const char *buffer, size_t bufferSize, unsigned *channels, unsigned *width, unsigned *height) +{ + *channels = 0; + *width = 0; + *height = 0; + + const char *currentBuffer = buffer; + const char *nextBuffer; + + // parse number of channels + int scannedChannels = sscanf(currentBuffer, "P%d\n", channels); + if (scannedChannels != 1) { // validate scanning of channels + // invalid channel line + return buffer; + } + // convert channel token to number of channels + *channels = (*channels == 5) ? 1 : 3; + + // advance past channel line + nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1; + bufferSize -= nextBuffer - currentBuffer; + currentBuffer = nextBuffer; + + // skip over optional comment + if (*currentBuffer == '#') { + // advance past comment line + nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1; + bufferSize -= nextBuffer - currentBuffer; + currentBuffer = nextBuffer; + } + + // parse dimensions of image + int scannedDimensions = sscanf(currentBuffer, "%d %d\n", width, height); + if (scannedDimensions != 2) { // validate scanning of dimensions + // invalid dimension line + return buffer; + } + + // advance past dimension line + nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1; + bufferSize -= nextBuffer - currentBuffer; + currentBuffer = nextBuffer; + + // skip over "255\n" at end of header + nextBuffer = (const char *) memchr((const void *) currentBuffer, '\n', bufferSize) + 1; + + // return start of image data + return nextBuffer; +} } /* namespace image */ diff --git a/common/trace_api.hpp b/common/trace_api.hpp index ed4823f..7619f71 100644 --- a/common/trace_api.hpp +++ b/common/trace_api.hpp @@ -35,7 +35,13 @@ namespace trace { +/** + * Enum to distuinguish the API for tools. + * + * It should never be embedded in the trace file. + */ enum API { + API_UNKNOWN = 0, API_GL, // GL + GLX/WGL/CGL API_EGL, // GL/GLES1/GLES2/VG + EGL API_D3D7, diff --git a/common/trace_parser.cpp b/common/trace_parser.cpp index dc9634f..896bfd4 100644 --- a/common/trace_parser.cpp +++ b/common/trace_parser.cpp @@ -43,6 +43,7 @@ Parser::Parser() { file = NULL; next_call_no = 0; version = 0; + api = API_UNKNOWN; glGetErrorSig = NULL; } @@ -65,6 +66,7 @@ bool Parser::open(const char *filename) { std::cerr << "error: unsupported trace format version " << version << "\n"; return false; } + api = API_UNKNOWN; return true; } @@ -232,6 +234,25 @@ Parser::parse_function_sig(void) { sig->offset = file->currentOffset(); functions[id] = sig; + /** + * Try to autodetect the API. + * + * XXX: Ideally we would allow to mix multiple APIs in a single trace, + * but as it stands today, retrace is done separately for each API. + */ + if (api == API_UNKNOWN) { + const char *n = sig->name; + if ((n[0] == 'g' && n[1] == 'l' && n[2] == 'X') || // glX + (n[0] == 'w' && n[1] == 'g' && n[2] == 'l' && n[3] >= 'A' && n[3] <= 'Z') || // wgl[A-Z] + (n[0] == 'C' && n[1] == 'G' && n[2] == 'L')) { // CGL + api = trace::API_GL; + } else if (n[0] == 'e' && n[1] == 'g' && n[2] == 'l' && n[3] >= 'A' && n[3] <= 'Z') { // egl + api = trace::API_EGL; + } else { + /* TODO */ + } + } + /** * Note down the signature of special functions for future reference. * diff --git a/common/trace_parser.hpp b/common/trace_parser.hpp index d8c5915..43c1356 100644 --- a/common/trace_parser.hpp +++ b/common/trace_parser.hpp @@ -33,6 +33,7 @@ #include "trace_file.hpp" #include "trace_format.hpp" #include "trace_model.hpp" +#include "trace_api.hpp" namespace trace { @@ -94,6 +95,7 @@ protected: public: unsigned long long version; + API api; Parser(); diff --git a/common/trace_parser_flags.cpp b/common/trace_parser_flags.cpp index 936a891..304e2a8 100644 --- a/common/trace_parser_flags.cpp +++ b/common/trace_parser_flags.cpp @@ -64,6 +64,8 @@ callFlagTable[] = { { "glBindFramebufferOES", CALL_FLAG_SWAP_RENDERTARGET }, { "glBlitFramebuffer", CALL_FLAG_RENDER }, { "glBlitFramebufferEXT", CALL_FLAG_RENDER }, + { "glCallList", CALL_FLAG_RENDER }, + { "glCallLists", CALL_FLAG_RENDER }, { "glClear", CALL_FLAG_RENDER }, { "glDrawArrays", CALL_FLAG_RENDER }, { "glDrawArraysEXT", CALL_FLAG_RENDER }, diff --git a/eglimports.hpp b/eglimports.hpp index 5443bef..a3e396a 100644 --- a/eglimports.hpp +++ b/eglimports.hpp @@ -32,7 +32,13 @@ #define _EGLIMPORTS_HPP_ -#ifdef HAVE_EGL +#include + +#ifdef _GDI32_ +/* Prevent __declspec(dllimport) attribute */ +#undef KHRONOS_APICALL +#define KHRONOS_APICALL +#endif // EGL #include @@ -45,28 +51,6 @@ #include #include -#else // HAVE_EGL - -// We always include GLES headers below to have the types and enums defined. -// For that to work without GLES platform headers, we need to define GL_API, -// GL_APICALL, and GL_APIENTRY. It does not matter what they are defined to. -// When we hit here, EGL/GLES support is disabled and all we need from the -// headers are the types and enums. - -#ifndef GL_API -#define GL_API GLAPI -#endif - -#ifndef GL_APICALL -#define GL_APICALL GLAPI -#endif - -#ifndef GL_APIENTRY -#define GL_APIENTRY APIENTRY -#endif - -#endif //! HAVE_EGL - // OpenGL ES 1.1 typedef int32_t GLfixed; diff --git a/glproc.py b/glproc.py index 95ab80f..b54f713 100644 --- a/glproc.py +++ b/glproc.py @@ -515,20 +515,24 @@ if __name__ == '__main__': print '#include "glimports.hpp"' print '#include "os.hpp"' print - print dispatcher = GlDispatcher() + print dispatcher.header() - print '#if defined(TRACE_EGL)' print dispatcher.dispatch_api(eglapi) - print '#elif defined(_WIN32)' + print + print '#if defined(_WIN32)' print dispatcher.dispatch_api(wglapi) + print print '#elif defined(__APPLE__)' + print dispatcher.dispatch_api(cglapi) - print '#else' + print + print '#elif defined(HAVE_X11)' print dispatcher.dispatch_api(glxapi) + print print '#endif' print dispatcher.dispatch_api(glapi) diff --git a/glretrace.py b/glretrace.py index e3fca8f..bb88288 100644 --- a/glretrace.py +++ b/glretrace.py @@ -111,6 +111,8 @@ class GlRetracer(Retracer): ]) misc_draw_function_names = set([ + "glCallList", + "glCallLists", "glClear", "glEnd", "glDrawPixels", @@ -222,6 +224,21 @@ class GlRetracer(Retracer): # Infer the drawable size from GL calls if function.name == "glViewport": print ' glretrace::updateDrawable(x + width, y + height);' + if function.name == "glViewportArray": + # We are concerned about drawables so only care for the first viewport + print ' if (first == 0 && count > 0) {' + print ' GLfloat x = v[0], y = v[1], w = v[2], h = v[3];' + print ' glretrace::updateDrawable(x + w, y + h);' + print ' }' + if function.name == "glViewportIndexedf": + print ' if (index == 0) {' + print ' glretrace::updateDrawable(x + w, y + h);' + print ' }' + if function.name == "glViewportIndexedfv": + print ' if (index == 0) {' + print ' GLfloat x = v[0], y = v[1], w = v[2], h = v[3];' + print ' glretrace::updateDrawable(x + w, y + h);' + print ' }' if function.name in ('glBlitFramebuffer', 'glBlitFramebufferEXT'): # Some applications do all their rendering in a framebuffer, and # then just blit to the drawable without ever calling glViewport. @@ -338,21 +355,21 @@ class GlRetracer(Retracer): print r' }' print ' }' - # Query the buffer length for whole buffer mappings - if function.name in self.map_function_names: - if 'length' in function.argNames(): - assert 'BufferRange' in function.name + # Query the buffer length for whole buffer mappings + if function.name in self.map_function_names: + if 'length' in function.argNames(): + assert 'BufferRange' in function.name + else: + assert 'BufferRange' not in function.name + print r' GLint length = 0;' + if function.name in ('glMapBuffer', 'glMapBufferOES'): + print r' glGetBufferParameteriv(target, GL_BUFFER_SIZE, &length);' + elif function.name == 'glMapBufferARB': + print r' glGetBufferParameterivARB(target, GL_BUFFER_SIZE_ARB, &length);' + elif function.name == 'glMapNamedBufferEXT': + print r' glGetNamedBufferParameterivEXT(buffer, GL_BUFFER_SIZE, &length);' else: - assert 'BufferRange' not in function.name - print r' GLint length = 0;' - if function.name in ('glMapBuffer', 'glMapBufferOES'): - print r' glGetBufferParameteriv(target, GL_BUFFER_SIZE, &length);' - elif function.name == 'glMapBufferARB': - print r' glGetBufferParameterivARB(target, GL_BUFFER_SIZE_ARB, &length);' - elif function.name == 'glMapNamedBufferEXT': - print r' glGetNamedBufferParameterivEXT(buffer, GL_BUFFER_SIZE, &length);' - else: - assert False + assert False def extractArg(self, function, arg, arg_type, lvalue, rvalue): if function.name in self.array_pointer_function_names and arg.name == 'pointer': diff --git a/glstate.cpp b/glstate.cpp index 55f3631..cac9f4e 100644 --- a/glstate.cpp +++ b/glstate.cpp @@ -28,8 +28,6 @@ #include #include -#include -#include #include "image.hpp" #include "json.hpp" @@ -39,23 +37,6 @@ #include "glstate_internal.hpp" -#ifdef __APPLE__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -OSStatus CGSGetSurfaceBounds(CGSConnectionID, CGWindowID, CGSSurfaceID, CGRect *); - -#ifdef __cplusplus -} -#endif - -#endif /* __APPLE__ */ - - namespace glstate { @@ -112,1374 +93,6 @@ Context::restorePixelPackState(void) { } -// Mapping from shader type to shader source, used to accumulated the sources -// of different shaders with same type. -typedef std::map ShaderMap; - - -static void -getShaderSource(ShaderMap &shaderMap, GLuint shader) -{ - if (!shader) { - return; - } - - GLint shader_type = 0; - glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type); - if (!shader_type) { - return; - } - - GLint source_length = 0; - glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &source_length); - if (!source_length) { - return; - } - - GLchar *source = new GLchar[source_length]; - GLsizei length = 0; - source[0] = 0; - glGetShaderSource(shader, source_length, &length, source); - - shaderMap[enumToString(shader_type)] += source; - - delete [] source; -} - - -static void -getShaderObjSource(ShaderMap &shaderMap, GLhandleARB shaderObj) -{ - if (!shaderObj) { - return; - } - - GLint object_type = 0; - glGetObjectParameterivARB(shaderObj, GL_OBJECT_TYPE_ARB, &object_type); - if (object_type != GL_SHADER_OBJECT_ARB) { - return; - } - - GLint shader_type = 0; - glGetObjectParameterivARB(shaderObj, GL_OBJECT_SUBTYPE_ARB, &shader_type); - if (!shader_type) { - return; - } - - GLint source_length = 0; - glGetObjectParameterivARB(shaderObj, GL_OBJECT_SHADER_SOURCE_LENGTH_ARB, &source_length); - if (!source_length) { - return; - } - - GLcharARB *source = new GLcharARB[source_length]; - GLsizei length = 0; - source[0] = 0; - glGetShaderSource(shaderObj, source_length, &length, source); - - shaderMap[enumToString(shader_type)] += source; - - delete [] source; -} - - -static inline void -dumpProgram(JSONWriter &json, GLint program) -{ - GLint attached_shaders = 0; - glGetProgramiv(program, GL_ATTACHED_SHADERS, &attached_shaders); - if (!attached_shaders) { - return; - } - - ShaderMap shaderMap; - - GLuint *shaders = new GLuint[attached_shaders]; - GLsizei count = 0; - glGetAttachedShaders(program, attached_shaders, &count, shaders); - std::sort(shaders, shaders + count); - for (GLsizei i = 0; i < count; ++ i) { - getShaderSource(shaderMap, shaders[i]); - } - delete [] shaders; - - for (ShaderMap::const_iterator it = shaderMap.begin(); it != shaderMap.end(); ++it) { - json.beginMember(it->first); - json.writeString(it->second); - json.endMember(); - } -} - - -static inline void -dumpProgramObj(JSONWriter &json, GLhandleARB programObj) -{ - GLint attached_shaders = 0; - glGetObjectParameterivARB(programObj, GL_OBJECT_ATTACHED_OBJECTS_ARB, &attached_shaders); - if (!attached_shaders) { - return; - } - - ShaderMap shaderMap; - - GLhandleARB *shaderObjs = new GLhandleARB[attached_shaders]; - GLsizei count = 0; - glGetAttachedObjectsARB(programObj, attached_shaders, &count, shaderObjs); - std::sort(shaderObjs, shaderObjs + count); - for (GLsizei i = 0; i < count; ++ i) { - getShaderObjSource(shaderMap, shaderObjs[i]); - } - delete [] shaderObjs; - - for (ShaderMap::const_iterator it = shaderMap.begin(); it != shaderMap.end(); ++it) { - json.beginMember(it->first); - json.writeString(it->second); - json.endMember(); - } -} - -/* - * When fetching the uniform name of an array we usually get name[0] - * so we need to cut the trailing "[0]" in order to properly construct - * array names later. Otherwise we endup with stuff like - * uniformArray[0][0], - * uniformArray[0][1], - * instead of - * uniformArray[0], - * uniformArray[1]. - */ -static std::string -resolveUniformName(const GLchar *name, GLint size) -{ - std::string qualifiedName(name); - if (size > 1) { - std::string::size_type nameLength = qualifiedName.length(); - static const char * const arrayStart = "[0]"; - static const int arrayStartLen = 3; - if (qualifiedName.rfind(arrayStart) == (nameLength - arrayStartLen)) { - qualifiedName = qualifiedName.substr(0, nameLength - 3); - } - } - return qualifiedName; -} - -static void -dumpUniform(JSONWriter &json, GLint program, GLint size, GLenum type, const GLchar *name) { - GLenum elemType; - GLint numElems; - __gl_uniform_size(type, elemType, numElems); - if (elemType == GL_NONE) { - return; - } - - GLfloat fvalues[4*4]; - GLdouble dvalues[4*4]; - GLint ivalues[4*4]; - GLuint uivalues[4*4]; - - GLint i, j; - - std::string qualifiedName = resolveUniformName(name, size); - - for (i = 0; i < size; ++i) { - std::stringstream ss; - ss << qualifiedName; - - if (size > 1) { - ss << '[' << i << ']'; - } - - std::string elemName = ss.str(); - - json.beginMember(elemName); - - GLint location = glGetUniformLocation(program, elemName.c_str()); - - if (numElems > 1) { - json.beginArray(); - } - - switch (elemType) { - case GL_FLOAT: - glGetUniformfv(program, location, fvalues); - for (j = 0; j < numElems; ++j) { - json.writeNumber(fvalues[j]); - } - break; - case GL_DOUBLE: - glGetUniformdv(program, location, dvalues); - for (j = 0; j < numElems; ++j) { - json.writeNumber(dvalues[j]); - } - break; - case GL_INT: - glGetUniformiv(program, location, ivalues); - for (j = 0; j < numElems; ++j) { - json.writeNumber(ivalues[j]); - } - break; - case GL_UNSIGNED_INT: - glGetUniformuiv(program, location, uivalues); - for (j = 0; j < numElems; ++j) { - json.writeNumber(uivalues[j]); - } - break; - case GL_BOOL: - glGetUniformiv(program, location, ivalues); - for (j = 0; j < numElems; ++j) { - json.writeBool(ivalues[j]); - } - break; - default: - assert(0); - break; - } - - if (numElems > 1) { - json.endArray(); - } - - json.endMember(); - } -} - - -static void -dumpUniformARB(JSONWriter &json, GLhandleARB programObj, GLint size, GLenum type, const GLchar *name) { - - GLenum elemType; - GLint numElems; - __gl_uniform_size(type, elemType, numElems); - if (elemType == GL_NONE) { - return; - } - - GLfloat fvalues[4*4]; - GLint ivalues[4*4]; - - GLint i, j; - - std::string qualifiedName = resolveUniformName(name, size); - - for (i = 0; i < size; ++i) { - std::stringstream ss; - ss << qualifiedName; - - if (size > 1) { - ss << '[' << i << ']'; - } - - std::string elemName = ss.str(); - - json.beginMember(elemName); - - GLint location = glGetUniformLocationARB(programObj, elemName.c_str()); - - if (numElems > 1) { - json.beginArray(); - } - - switch (elemType) { - case GL_DOUBLE: - // glGetUniformdvARB does not exists - case GL_FLOAT: - glGetUniformfvARB(programObj, location, fvalues); - for (j = 0; j < numElems; ++j) { - json.writeNumber(fvalues[j]); - } - break; - case GL_UNSIGNED_INT: - // glGetUniformuivARB does not exists - case GL_INT: - glGetUniformivARB(programObj, location, ivalues); - for (j = 0; j < numElems; ++j) { - json.writeNumber(ivalues[j]); - } - break; - case GL_BOOL: - glGetUniformivARB(programObj, location, ivalues); - for (j = 0; j < numElems; ++j) { - json.writeBool(ivalues[j]); - } - break; - default: - assert(0); - break; - } - - if (numElems > 1) { - json.endArray(); - } - - json.endMember(); - } -} - - -static inline void -dumpProgramUniforms(JSONWriter &json, GLint program) -{ - GLint active_uniforms = 0; - glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &active_uniforms); - if (!active_uniforms) { - return; - } - - GLint active_uniform_max_length = 0; - glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &active_uniform_max_length); - GLchar *name = new GLchar[active_uniform_max_length]; - if (!name) { - return; - } - - for (GLint index = 0; index < active_uniforms; ++index) { - GLsizei length = 0; - GLint size = 0; - GLenum type = GL_NONE; - glGetActiveUniform(program, index, active_uniform_max_length, &length, &size, &type, name); - - dumpUniform(json, program, size, type, name); - } - - delete [] name; -} - - -static inline void -dumpProgramObjUniforms(JSONWriter &json, GLhandleARB programObj) -{ - GLint active_uniforms = 0; - glGetObjectParameterivARB(programObj, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &active_uniforms); - if (!active_uniforms) { - return; - } - - GLint active_uniform_max_length = 0; - glGetObjectParameterivARB(programObj, GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB, &active_uniform_max_length); - GLchar *name = new GLchar[active_uniform_max_length]; - if (!name) { - return; - } - - for (GLint index = 0; index < active_uniforms; ++index) { - GLsizei length = 0; - GLint size = 0; - GLenum type = GL_NONE; - glGetActiveUniformARB(programObj, index, active_uniform_max_length, &length, &size, &type, name); - - dumpUniformARB(json, programObj, size, type, name); - } - - delete [] name; -} - - -static inline void -dumpArbProgram(JSONWriter &json, GLenum target) -{ - if (!glIsEnabled(target)) { - return; - } - - GLint program_length = 0; - glGetProgramivARB(target, GL_PROGRAM_LENGTH_ARB, &program_length); - if (!program_length) { - return; - } - - GLchar *source = new GLchar[program_length + 1]; - source[0] = 0; - glGetProgramStringARB(target, GL_PROGRAM_STRING_ARB, source); - source[program_length] = 0; - - json.beginMember(enumToString(target)); - json.writeString(source); - json.endMember(); - - delete [] source; -} - - -static inline void -dumpArbProgramUniforms(JSONWriter &json, GLenum target, const char *prefix) -{ - if (!glIsEnabled(target)) { - return; - } - - GLint program_parameters = 0; - glGetProgramivARB(target, GL_PROGRAM_PARAMETERS_ARB, &program_parameters); - if (!program_parameters) { - return; - } - - GLint max_program_local_parameters = 0; - glGetProgramivARB(target, GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB, &max_program_local_parameters); - for (GLint index = 0; index < max_program_local_parameters; ++index) { - GLdouble params[4] = {0, 0, 0, 0}; - glGetProgramLocalParameterdvARB(target, index, params); - - if (!params[0] && !params[1] && !params[2] && !params[3]) { - continue; - } - - char name[256]; - snprintf(name, sizeof name, "%sprogram.local[%i]", prefix, index); - - json.beginMember(name); - json.beginArray(); - json.writeNumber(params[0]); - json.writeNumber(params[1]); - json.writeNumber(params[2]); - json.writeNumber(params[3]); - json.endArray(); - json.endMember(); - } - - GLint max_program_env_parameters = 0; - glGetProgramivARB(target, GL_MAX_PROGRAM_ENV_PARAMETERS_ARB, &max_program_env_parameters); - for (GLint index = 0; index < max_program_env_parameters; ++index) { - GLdouble params[4] = {0, 0, 0, 0}; - glGetProgramEnvParameterdvARB(target, index, params); - - if (!params[0] && !params[1] && !params[2] && !params[3]) { - continue; - } - - char name[256]; - snprintf(name, sizeof name, "%sprogram.env[%i]", prefix, index); - - json.beginMember(name); - json.beginArray(); - json.writeNumber(params[0]); - json.writeNumber(params[1]); - json.writeNumber(params[2]); - json.writeNumber(params[3]); - json.endArray(); - json.endMember(); - } -} - - -static inline void -dumpShadersUniforms(JSONWriter &json) -{ - GLint program = 0; - glGetIntegerv(GL_CURRENT_PROGRAM, &program); - - GLhandleARB programObj = glGetHandleARB(GL_PROGRAM_OBJECT_ARB); - - json.beginMember("shaders"); - json.beginObject(); - if (program) { - dumpProgram(json, program); - } else if (programObj) { - dumpProgramObj(json, programObj); - } else { - dumpArbProgram(json, GL_FRAGMENT_PROGRAM_ARB); - dumpArbProgram(json, GL_VERTEX_PROGRAM_ARB); - } - json.endObject(); - json.endMember(); // shaders - - json.beginMember("uniforms"); - json.beginObject(); - if (program) { - dumpProgramUniforms(json, program); - } else if (programObj) { - dumpProgramObjUniforms(json, programObj); - } else { - dumpArbProgramUniforms(json, GL_FRAGMENT_PROGRAM_ARB, "fp."); - dumpArbProgramUniforms(json, GL_VERTEX_PROGRAM_ARB, "vp."); - } - json.endObject(); - json.endMember(); // uniforms -} - - -static inline void -dumpTextureImage(JSONWriter &json, Context &context, GLenum target, GLint level) -{ - GLint width, height = 1, depth = 1; - GLint format; - - glGetTexLevelParameteriv(target, level, GL_TEXTURE_INTERNAL_FORMAT, &format); - - width = 0; - glGetTexLevelParameteriv(target, level, GL_TEXTURE_WIDTH, &width); - - if (target != GL_TEXTURE_1D) { - height = 0; - glGetTexLevelParameteriv(target, level, GL_TEXTURE_HEIGHT, &height); - if (target == GL_TEXTURE_3D) { - depth = 0; - glGetTexLevelParameteriv(target, level, GL_TEXTURE_DEPTH, &depth); - } - } - - if (width <= 0 || height <= 0 || depth <= 0) { - return; - } else { - char label[512]; - - GLint active_texture = GL_TEXTURE0; - glGetIntegerv(GL_ACTIVE_TEXTURE, &active_texture); - snprintf(label, sizeof label, "%s, %s, level = %d", - enumToString(active_texture), enumToString(target), level); - - json.beginMember(label); - - json.beginObject(); - - // Tell the GUI this is no ordinary object, but an image - json.writeStringMember("__class__", "image"); - - json.writeNumberMember("__width__", width); - json.writeNumberMember("__height__", height); - json.writeNumberMember("__depth__", depth); - - json.writeStringMember("__format__", enumToString(format)); - - // Hardcoded for now, but we could chose types more adequate to the - // texture internal format - json.writeStringMember("__type__", "uint8"); - json.writeBoolMember("__normalized__", true); - json.writeNumberMember("__channels__", 4); - - GLubyte *pixels = new GLubyte[depth*width*height*4]; - - context.resetPixelPackState(); - - glGetTexImage(target, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - context.restorePixelPackState(); - - json.beginMember("__data__"); - char *pngBuffer; - int pngBufferSize; - image::writePixelsToBuffer(pixels, width, height, 4, true, &pngBuffer, &pngBufferSize); - json.writeBase64(pngBuffer, pngBufferSize); - free(pngBuffer); - json.endMember(); // __data__ - - delete [] pixels; - json.endObject(); - } -} - - -static inline void -dumpTexture(JSONWriter &json, Context &context, GLenum target, GLenum binding) -{ - GLint texture_binding = 0; - glGetIntegerv(binding, &texture_binding); - if (!glIsEnabled(target) && !texture_binding) { - return; - } - - GLint level = 0; - do { - GLint width = 0, height = 0; - glGetTexLevelParameteriv(target, level, GL_TEXTURE_WIDTH, &width); - glGetTexLevelParameteriv(target, level, GL_TEXTURE_HEIGHT, &height); - if (!width || !height) { - break; - } - - if (target == GL_TEXTURE_CUBE_MAP) { - for (int face = 0; face < 6; ++face) { - dumpTextureImage(json, context, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level); - } - } else { - dumpTextureImage(json, context, target, level); - } - - ++level; - } while(true); -} - - -static inline void -dumpTextures(JSONWriter &json, Context &context) -{ - json.beginMember("textures"); - json.beginObject(); - GLint active_texture = GL_TEXTURE0; - glGetIntegerv(GL_ACTIVE_TEXTURE, &active_texture); - GLint max_texture_coords = 0; - glGetIntegerv(GL_MAX_TEXTURE_COORDS, &max_texture_coords); - GLint max_combined_texture_image_units = 0; - glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_combined_texture_image_units); - GLint max_units = std::max(max_combined_texture_image_units, max_texture_coords); - for (GLint unit = 0; unit < max_units; ++unit) { - GLenum texture = GL_TEXTURE0 + unit; - glActiveTexture(texture); - dumpTexture(json, context, GL_TEXTURE_1D, GL_TEXTURE_BINDING_1D); - dumpTexture(json, context, GL_TEXTURE_2D, GL_TEXTURE_BINDING_2D); - dumpTexture(json, context, GL_TEXTURE_3D, GL_TEXTURE_BINDING_3D); - dumpTexture(json, context, GL_TEXTURE_RECTANGLE, GL_TEXTURE_BINDING_RECTANGLE); - dumpTexture(json, context, GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BINDING_CUBE_MAP); - } - glActiveTexture(active_texture); - json.endObject(); - json.endMember(); // textures -} - - -static bool -getDrawableBounds(GLint *width, GLint *height) { -#if defined(TRACE_EGL) - - EGLContext currentContext = eglGetCurrentContext(); - if (currentContext == EGL_NO_CONTEXT) { - return false; - } - - EGLSurface currentSurface = eglGetCurrentSurface(EGL_DRAW); - if (currentSurface == EGL_NO_SURFACE) { - return false; - } - - EGLDisplay currentDisplay = eglGetCurrentDisplay(); - if (currentDisplay == EGL_NO_DISPLAY) { - return false; - } - - if (!eglQuerySurface(currentDisplay, currentSurface, EGL_WIDTH, width) || - !eglQuerySurface(currentDisplay, currentSurface, EGL_HEIGHT, height)) { - return false; - } - - return true; - -#elif defined(_WIN32) - - HDC hDC = wglGetCurrentDC(); - if (!hDC) { - return false; - } - - HWND hWnd = WindowFromDC(hDC); - RECT rect; - - if (!GetClientRect(hWnd, &rect)) { - return false; - } - - *width = rect.right - rect.left; - *height = rect.bottom - rect.top; - return true; - -#elif defined(__APPLE__) - - CGLContextObj ctx = CGLGetCurrentContext(); - if (ctx == NULL) { - return false; - } - - CGSConnectionID cid; - CGSWindowID wid; - CGSSurfaceID sid; - - if (CGLGetSurface(ctx, &cid, &wid, &sid) != kCGLNoError) { - return false; - } - - CGRect rect; - - if (CGSGetSurfaceBounds(cid, wid, sid, &rect) != 0) { - return false; - } - - *width = rect.size.width; - *height = rect.size.height; - return true; - -#elif defined(HAVE_X11) - - Display *display; - Drawable drawable; - Window root; - int x, y; - unsigned int w, h, bw, depth; - - display = glXGetCurrentDisplay(); - if (!display) { - return false; - } - - drawable = glXGetCurrentDrawable(); - if (drawable == None) { - return false; - } - - if (!XGetGeometry(display, drawable, &root, &x, &y, &w, &h, &bw, &depth)) { - return false; - } - - *width = w; - *height = h; - return true; - -#else - - return false; - -#endif -} - - -static const GLenum texture_bindings[][2] = { - {GL_TEXTURE_1D, GL_TEXTURE_BINDING_1D}, - {GL_TEXTURE_2D, GL_TEXTURE_BINDING_2D}, - {GL_TEXTURE_3D, GL_TEXTURE_BINDING_3D}, - {GL_TEXTURE_RECTANGLE, GL_TEXTURE_BINDING_RECTANGLE}, - {GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BINDING_CUBE_MAP} -}; - - -static bool -bindTexture(GLint texture, GLenum &target, GLint &bound_texture) -{ - - for (unsigned i = 0; i < sizeof(texture_bindings)/sizeof(texture_bindings[0]); ++i) { - target = texture_bindings[i][0]; - - GLenum binding = texture_bindings[i][1]; - - while (glGetError() != GL_NO_ERROR) - ; - - glGetIntegerv(binding, &bound_texture); - glBindTexture(target, texture); - - if (glGetError() == GL_NO_ERROR) { - return true; - } - - glBindTexture(target, bound_texture); - } - - target = GL_NONE; - - return false; -} - - -static bool -getTextureLevelSize(GLint texture, GLint level, GLint *width, GLint *height) -{ - *width = 0; - *height = 0; - - GLenum target; - GLint bound_texture = 0; - if (!bindTexture(texture, target, bound_texture)) { - return false; - } - - glGetTexLevelParameteriv(target, level, GL_TEXTURE_WIDTH, width); - glGetTexLevelParameteriv(target, level, GL_TEXTURE_HEIGHT, height); - - glBindTexture(target, bound_texture); - - return *width > 0 && *height > 0; -} - - -static GLenum -getTextureLevelFormat(GLint texture, GLint level) -{ - GLenum target; - GLint bound_texture = 0; - if (!bindTexture(texture, target, bound_texture)) { - return GL_NONE; - } - - GLint format = GL_NONE; - glGetTexLevelParameteriv(target, level, GL_TEXTURE_INTERNAL_FORMAT, &format); - - glBindTexture(target, bound_texture); - - return format; -} - - - -static bool -getRenderbufferSize(GLint renderbuffer, GLint *width, GLint *height) -{ - GLint bound_renderbuffer = 0; - glGetIntegerv(GL_RENDERBUFFER_BINDING, &bound_renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - - *width = 0; - *height = 0; - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, width); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, height); - - glBindRenderbuffer(GL_RENDERBUFFER, bound_renderbuffer); - - return *width > 0 && *height > 0; -} - - -static GLenum -getRenderbufferFormat(GLint renderbuffer) -{ - GLint bound_renderbuffer = 0; - glGetIntegerv(GL_RENDERBUFFER_BINDING, &bound_renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - - GLint format = GL_NONE; - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &format); - - glBindRenderbuffer(GL_RENDERBUFFER, bound_renderbuffer); - - return format; -} - - -static bool -getFramebufferAttachmentSize(GLenum target, GLenum attachment, GLint *width, GLint *height) -{ - GLint object_type = GL_NONE; - glGetFramebufferAttachmentParameteriv(target, attachment, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, - &object_type); - if (object_type == GL_NONE) { - return false; - } - - GLint object_name = 0; - glGetFramebufferAttachmentParameteriv(target, attachment, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, - &object_name); - if (object_name == 0) { - return false; - } - - if (object_type == GL_RENDERBUFFER) { - return getRenderbufferSize(object_name, width, height); - } else if (object_type == GL_TEXTURE) { - GLint texture_level = 0; - glGetFramebufferAttachmentParameteriv(target, attachment, - GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, - &texture_level); - return getTextureLevelSize(object_name, texture_level, width, height); - } else { - std::cerr << "warning: unexpected GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = " << object_type << "\n"; - return false; - } -} - - - -static GLint -getFramebufferAttachmentFormat(GLenum target, GLenum attachment) -{ - GLint object_type = GL_NONE; - glGetFramebufferAttachmentParameteriv(target, attachment, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, - &object_type); - if (object_type == GL_NONE) { - return GL_NONE; - } - - GLint object_name = 0; - glGetFramebufferAttachmentParameteriv(target, attachment, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, - &object_name); - if (object_name == 0) { - return GL_NONE; - } - - if (object_type == GL_RENDERBUFFER) { - return getRenderbufferFormat(object_name); - } else if (object_type == GL_TEXTURE) { - GLint texture_level = 0; - glGetFramebufferAttachmentParameteriv(target, attachment, - GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, - &texture_level); - return getTextureLevelFormat(object_name, texture_level); - } else { - std::cerr << "warning: unexpected GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = " << object_type << "\n"; - return GL_NONE; - } -} - - - -image::Image * -getDrawBufferImage() { - GLenum format = GL_RGB; - GLint channels = __gl_format_channels(format); - if (channels > 4) { - return NULL; - } - - Context context; - - GLenum framebuffer_binding; - GLenum framebuffer_target; - if (context.ES) { - framebuffer_binding = GL_FRAMEBUFFER_BINDING; - framebuffer_target = GL_FRAMEBUFFER; - } else { - framebuffer_binding = GL_DRAW_FRAMEBUFFER_BINDING; - framebuffer_target = GL_DRAW_FRAMEBUFFER; - } - - GLint draw_framebuffer = 0; - glGetIntegerv(framebuffer_binding, &draw_framebuffer); - - GLint draw_buffer = GL_NONE; - GLint width, height; - if (draw_framebuffer) { - if (context.ARB_draw_buffers) { - glGetIntegerv(GL_DRAW_BUFFER0, &draw_buffer); - if (draw_buffer == GL_NONE) { - return NULL; - } - } - - if (!getFramebufferAttachmentSize(framebuffer_target, draw_buffer, &width, &height)) { - return NULL; - } - } else { - if (!context.ES) { - glGetIntegerv(GL_DRAW_BUFFER, &draw_buffer); - if (draw_buffer == GL_NONE) { - return NULL; - } - } - - if (!getDrawableBounds(&width, &height)) { - return NULL; - } - } - - image::Image *image = new image::Image(width, height, channels, true); - if (!image) { - return NULL; - } - - while (glGetError() != GL_NO_ERROR) {} - - GLint read_framebuffer = 0; - GLint read_buffer = 0; - if (!context.ES) { - glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read_framebuffer); - glBindFramebuffer(GL_READ_FRAMEBUFFER, draw_framebuffer); - - glGetIntegerv(GL_READ_BUFFER, &read_buffer); - glReadBuffer(draw_buffer); - } - - // TODO: reset imaging state too - context.resetPixelPackState(); - - glReadPixels(0, 0, width, height, format, GL_UNSIGNED_BYTE, image->pixels); - - context.restorePixelPackState(); - - if (!context.ES) { - glReadBuffer(read_buffer); - glBindFramebuffer(GL_READ_FRAMEBUFFER, read_framebuffer); - } - - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - do { - std::cerr << "warning: " << enumToString(error) << " while getting snapshot\n"; - error = glGetError(); - } while(error != GL_NO_ERROR); - delete image; - return NULL; - } - - return image; -} - - -/** - * Dump the image of the currently bound read buffer. - */ -static inline void -dumpReadBufferImage(JSONWriter &json, GLint width, GLint height, GLenum format, - GLint internalFormat = GL_NONE) -{ - GLint channels = __gl_format_channels(format); - - Context context; - - json.beginObject(); - - // Tell the GUI this is no ordinary object, but an image - json.writeStringMember("__class__", "image"); - - json.writeNumberMember("__width__", width); - json.writeNumberMember("__height__", height); - json.writeNumberMember("__depth__", 1); - - json.writeStringMember("__format__", enumToString(internalFormat)); - - // Hardcoded for now, but we could chose types more adequate to the - // texture internal format - json.writeStringMember("__type__", "uint8"); - json.writeBoolMember("__normalized__", true); - json.writeNumberMember("__channels__", channels); - - GLubyte *pixels = new GLubyte[width*height*channels]; - - // TODO: reset imaging state too - context.resetPixelPackState(); - - glReadPixels(0, 0, width, height, format, GL_UNSIGNED_BYTE, pixels); - - context.restorePixelPackState(); - - json.beginMember("__data__"); - char *pngBuffer; - int pngBufferSize; - image::writePixelsToBuffer(pixels, width, height, channels, true, &pngBuffer, &pngBufferSize); - //std::cerr <<" Before = "<<(width * height * channels * sizeof *pixels) - // <<", after = "<= GL_COLOR_ATTACHMENT0 && draw_buffer < GL_COLOR_ATTACHMENT0 + max_color_attachments) { - attachment = draw_buffer; - } else { - std::cerr << "warning: unexpected GL_DRAW_BUFFER" << i << " = " << draw_buffer << "\n"; - attachment = GL_COLOR_ATTACHMENT0; - } - GLint alpha_size = 0; - glGetFramebufferAttachmentParameteriv(target, attachment, - GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, - &alpha_size); - GLenum format = alpha_size ? GL_RGBA : GL_RGB; - dumpFramebufferAttachment(json, target, attachment, format); - } - } - - glReadBuffer(read_buffer); - - if (!context.ES) { - dumpFramebufferAttachment(json, target, GL_DEPTH_ATTACHMENT, GL_DEPTH_COMPONENT); - dumpFramebufferAttachment(json, target, GL_STENCIL_ATTACHMENT, GL_STENCIL_INDEX); - } - - glBindFramebuffer(GL_READ_FRAMEBUFFER, read_framebuffer); -} - - -static void -dumpFramebuffer(JSONWriter &json, Context &context) -{ - json.beginMember("framebuffer"); - json.beginObject(); - - GLint boundDrawFbo = 0, boundReadFbo = 0; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &boundDrawFbo); - glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &boundReadFbo); - if (!boundDrawFbo) { - dumpDrawableImages(json, context); - } else if (context.ES) { - dumpFramebufferAttachments(json, context, GL_FRAMEBUFFER); - } else { - GLint colorRb = 0, stencilRb = 0, depthRb = 0; - GLint draw_buffer0 = GL_NONE; - glGetIntegerv(GL_DRAW_BUFFER0, &draw_buffer0); - bool multisample = false; - - GLint boundRb = 0; - glGetIntegerv(GL_RENDERBUFFER_BINDING, &boundRb); - - GLint object_type; - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, draw_buffer0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &object_type); - if (object_type == GL_RENDERBUFFER) { - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, draw_buffer0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &colorRb); - glBindRenderbuffer(GL_RENDERBUFFER, colorRb); - GLint samples = 0; - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); - if (samples) { - multisample = true; - } - } - - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &object_type); - if (object_type == GL_RENDERBUFFER) { - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthRb); - glBindRenderbuffer(GL_RENDERBUFFER, depthRb); - GLint samples = 0; - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); - if (samples) { - multisample = true; - } - } - - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &object_type); - if (object_type == GL_RENDERBUFFER) { - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &stencilRb); - glBindRenderbuffer(GL_RENDERBUFFER, stencilRb); - GLint samples = 0; - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); - if (samples) { - multisample = true; - } - } - - glBindRenderbuffer(GL_RENDERBUFFER, boundRb); - - GLuint rbs[3]; - GLint numRbs = 0; - GLuint fboCopy = 0; - - if (multisample) { - // glReadPixels doesnt support multisampled buffers so we need - // to blit the fbo to a temporary one - fboCopy = downsampledFramebuffer(boundDrawFbo, draw_buffer0, - colorRb, depthRb, stencilRb, - rbs, &numRbs); - } - - dumpFramebufferAttachments(json, context, GL_DRAW_FRAMEBUFFER); - - if (multisample) { - glBindRenderbuffer(GL_RENDERBUFFER_BINDING, boundRb); - glDeleteRenderbuffers(numRbs, rbs); - glDeleteFramebuffers(1, &fboCopy); - } - - glBindFramebuffer(GL_READ_FRAMEBUFFER, boundReadFbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, boundDrawFbo); - } - - json.endObject(); - json.endMember(); // framebuffer -} - - static const GLenum bindings[] = { GL_DRAW_BUFFER, GL_READ_BUFFER, diff --git a/glstate.hpp b/glstate.hpp index dc73e58..6fb615f 100644 --- a/glstate.hpp +++ b/glstate.hpp @@ -32,9 +32,6 @@ #include "glimports.hpp" -class JSONWriter; - - namespace image { class Image; } diff --git a/glstate.py b/glstate.py deleted file mode 100644 index ad8e258..0000000 --- a/glstate.py +++ /dev/null @@ -1,503 +0,0 @@ -########################################################################## -# -# Copyright 2011 Jose Fonseca -# All Rights Reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -##########################################################################/ - - -'''Generate code to dump most GL state into JSON.''' - - -from specs.stdapi import * - -from specs.gltypes import * -from specs.glparams import * - - -texture_targets = [ - ('GL_TEXTURE_1D', 'GL_TEXTURE_BINDING_1D'), - ('GL_TEXTURE_2D', 'GL_TEXTURE_BINDING_2D'), - ('GL_TEXTURE_3D', 'GL_TEXTURE_BINDING_3D'), - ('GL_TEXTURE_RECTANGLE', 'GL_TEXTURE_BINDING_RECTANGLE'), - ('GL_TEXTURE_CUBE_MAP', 'GL_TEXTURE_BINDING_CUBE_MAP') -] - -framebuffer_targets = [ - ('GL_DRAW_FRAMEBUFFER', 'GL_DRAW_FRAMEBUFFER_BINDING'), - ('GL_READ_FRAMEBUFFER', 'GL_READ_FRAMEBUFFER_BINDING'), -] - -class GetInflector: - '''Objects that describes how to inflect.''' - - reduced_types = { - B: I, - E: I, - I: F, - } - - def __init__(self, radical, inflections, suffix = ''): - self.radical = radical - self.inflections = inflections - self.suffix = suffix - - def reduced_type(self, type): - if type in self.inflections: - return type - if type in self.reduced_types: - return self.reduced_type(self.reduced_types[type]) - raise NotImplementedError - - def inflect(self, type): - return self.radical + self.inflection(type) + self.suffix - - def inflection(self, type): - type = self.reduced_type(type) - assert type in self.inflections - return self.inflections[type] - - def __str__(self): - return self.radical + self.suffix - - -class StateGetter(Visitor): - '''Type visitor that is able to extract the state via one of the glGet* - functions. - - It will declare any temporary variable - ''' - - def __init__(self, radical, inflections, suffix=''): - self.inflector = GetInflector(radical, inflections) - self.suffix = suffix - - def iter(self): - for function, type, count, name in parameters: - inflection = self.inflector.radical + self.suffix - if inflection not in function.split(','): - continue - if type is X: - continue - yield type, count, name - - def __call__(self, *args): - pname = args[-1] - - for type, count, name in self.iter(): - if name == pname: - if count != 1: - type = Array(type, str(count)) - - return type, self.visit(type, args) - - raise NotImplementedError - - def temp_name(self, args): - '''Return the name of a temporary variable to hold the state.''' - pname = args[-1] - - return pname[3:].lower() - - def visitConst(self, const, args): - return self.visit(const.type, args) - - def visitScalar(self, type, args): - temp_name = self.temp_name(args) - elem_type = self.inflector.reduced_type(type) - inflection = self.inflector.inflect(type) - if inflection.endswith('v'): - print ' %s %s = 0;' % (elem_type, temp_name) - print ' %s(%s, &%s);' % (inflection + self.suffix, ', '.join(args), temp_name) - else: - print ' %s %s = %s(%s);' % (elem_type, temp_name, inflection + self.suffix, ', '.join(args)) - return temp_name - - def visitString(self, string, args): - temp_name = self.temp_name(args) - inflection = self.inflector.inflect(string) - assert not inflection.endswith('v') - print ' %s %s = (%s)%s(%s);' % (string, temp_name, string, inflection + self.suffix, ', '.join(args)) - return temp_name - - def visitAlias(self, alias, args): - return self.visitScalar(alias, args) - - def visitEnum(self, enum, args): - return self.visit(GLint, args) - - def visitBitmask(self, bitmask, args): - return self.visit(GLint, args) - - def visitArray(self, array, args): - temp_name = self.temp_name(args) - if array.length == '1': - return self.visit(array.type) - elem_type = self.inflector.reduced_type(array.type) - inflection = self.inflector.inflect(array.type) - assert inflection.endswith('v') - print ' %s %s[%s + 1];' % (elem_type, temp_name, array.length) - print ' memset(%s, 0, %s * sizeof *%s);' % (temp_name, array.length, temp_name) - print ' %s[%s] = (%s)0xdeadc0de;' % (temp_name, array.length, elem_type) - print ' %s(%s, %s);' % (inflection + self.suffix, ', '.join(args), temp_name) - # Simple buffer overflow detection - print ' assert(%s[%s] == (%s)0xdeadc0de);' % (temp_name, array.length, elem_type) - return temp_name - - def visitOpaque(self, pointer, args): - temp_name = self.temp_name(args) - inflection = self.inflector.inflect(pointer) - assert inflection.endswith('v') - print ' GLvoid *%s;' % temp_name - print ' %s(%s, &%s);' % (inflection + self.suffix, ', '.join(args), temp_name) - return temp_name - - -glGet = StateGetter('glGet', { - B: 'Booleanv', - I: 'Integerv', - F: 'Floatv', - D: 'Doublev', - S: 'String', - P: 'Pointerv', -}) - -glGetMaterial = StateGetter('glGetMaterial', {I: 'iv', F: 'fv'}) -glGetLight = StateGetter('glGetLight', {I: 'iv', F: 'fv'}) -glGetVertexAttrib = StateGetter('glGetVertexAttrib', {I: 'iv', F: 'fv', D: 'dv', P: 'Pointerv'}) -glGetTexParameter = StateGetter('glGetTexParameter', {I: 'iv', F: 'fv'}) -glGetTexEnv = StateGetter('glGetTexEnv', {I: 'iv', F: 'fv'}) -glGetTexLevelParameter = StateGetter('glGetTexLevelParameter', {I: 'iv', F: 'fv'}) -glGetShader = StateGetter('glGetShaderiv', {I: 'iv'}) -glGetProgram = StateGetter('glGetProgram', {I: 'iv'}) -glGetProgramARB = StateGetter('glGetProgram', {I: 'iv', F: 'fv', S: 'Stringv'}, 'ARB') -glGetFramebufferAttachmentParameter = StateGetter('glGetFramebufferAttachmentParameter', {I: 'iv'}) - - -class JsonWriter(Visitor): - '''Type visitor that will dump a value of the specified type through the - JSON writer. - - It expects a previously declared JSONWriter instance named "json".''' - - def visitLiteral(self, literal, instance): - if literal.kind == 'Bool': - print ' json.writeBool(%s);' % instance - elif literal.kind in ('SInt', 'Uint', 'Float', 'Double'): - print ' json.writeNumber(%s);' % instance - else: - raise NotImplementedError - - def visitString(self, string, instance): - assert string.length is None - print ' json.writeString((const char *)%s);' % instance - - def visitEnum(self, enum, instance): - if enum.expr == 'GLenum': - print ' dumpEnum(json, %s);' % instance - else: - print ' json.writeNumber(%s);' % instance - - def visitBitmask(self, bitmask, instance): - raise NotImplementedError - - def visitAlias(self, alias, instance): - self.visit(alias.type, instance) - - def visitOpaque(self, opaque, instance): - print ' json.writeNumber((size_t)%s);' % instance - - __index = 0 - - def visitArray(self, array, instance): - index = '__i%u' % JsonWriter.__index - JsonWriter.__index += 1 - print ' json.beginArray();' - print ' for (unsigned %s = 0; %s < %s; ++%s) {' % (index, index, array.length, index) - self.visit(array.type, '%s[%s]' % (instance, index)) - print ' }' - print ' json.endArray();' - - - -class StateDumper: - '''Class to generate code to dump all GL state in JSON format via - stdout.''' - - def __init__(self): - pass - - def dump(self): - print '#include ' - print - print '#include "json.hpp"' - print '#include "glproc.hpp"' - print '#include "glsize.hpp"' - print '#include "glstate.hpp"' - print '#include "glstate_internal.hpp"' - print - print 'namespace glstate {' - print - - print 'const char *' - print 'enumToString(GLenum pname)' - print '{' - print ' switch (pname) {' - for name in GLenum.values: - print ' case %s:' % name - print ' return "%s";' % name - print ' default:' - print ' return NULL;' - print ' }' - print '}' - print - - print 'static void' - print 'dumpFramebufferAttachementParameters(JSONWriter &json, GLenum target, GLenum attachment)' - print '{' - self.dump_attachment_parameters('target', 'attachment') - print '}' - print - - print 'void' - print 'dumpEnum(JSONWriter &json, GLenum pname)' - print '{' - print ' const char *s = enumToString(pname);' - print ' if (s) {' - print ' json.writeString(s);' - print ' } else {' - print ' json.writeNumber(pname);' - print ' }' - print '}' - print - - print 'void dumpParameters(JSONWriter &json, Context &context)' - print '{' - print ' json.beginMember("parameters");' - print ' json.beginObject();' - - self.dump_atoms(glGet) - - self.dump_material_params() - self.dump_light_params() - self.dump_vertex_attribs() - self.dump_program_params() - self.dump_texture_parameters() - self.dump_framebuffer_parameters() - - print ' json.endObject();' - print ' json.endMember(); // parameters' - print '}' - print - - print '} /*namespace glstate */' - - def dump_material_params(self): - print ' if (!context.ES) {' - for face in ['GL_FRONT', 'GL_BACK']: - print ' json.beginMember("%s");' % face - print ' json.beginObject();' - self.dump_atoms(glGetMaterial, face) - print ' json.endObject();' - print ' }' - print - - def dump_light_params(self): - print ' GLint max_lights = 0;' - print ' __glGetIntegerv(GL_MAX_LIGHTS, &max_lights);' - print ' for (GLint index = 0; index < max_lights; ++index) {' - print ' GLenum light = GL_LIGHT0 + index;' - print ' if (glIsEnabled(light)) {' - print ' char name[32];' - print ' snprintf(name, sizeof name, "GL_LIGHT%i", index);' - print ' json.beginMember(name);' - print ' json.beginObject();' - self.dump_atoms(glGetLight, ' GL_LIGHT0 + index') - print ' json.endObject();' - print ' json.endMember(); // GL_LIGHTi' - print ' }' - print ' }' - print - - def texenv_param_target(self, name): - if name == 'GL_TEXTURE_LOD_BIAS': - return 'GL_TEXTURE_FILTER_CONTROL' - elif name == 'GL_COORD_REPLACE': - return 'GL_POINT_SPRITE' - else: - return 'GL_TEXTURE_ENV' - - def dump_texenv_params(self): - for target in ['GL_TEXTURE_ENV', 'GL_TEXTURE_FILTER_CONTROL', 'GL_POINT_SPRITE']: - print ' if (!context.ES) {' - print ' json.beginMember("%s");' % target - print ' json.beginObject();' - for _, _, name in glGetTexEnv.iter(): - if self.texenv_param_target(name) == target: - self.dump_atom(glGetTexEnv, target, name) - print ' json.endObject();' - print ' }' - - def dump_vertex_attribs(self): - print ' GLint max_vertex_attribs = 0;' - print ' __glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attribs);' - print ' for (GLint index = 0; index < max_vertex_attribs; ++index) {' - print ' char name[32];' - print ' snprintf(name, sizeof name, "GL_VERTEX_ATTRIB_ARRAY%i", index);' - print ' json.beginMember(name);' - print ' json.beginObject();' - self.dump_atoms(glGetVertexAttrib, 'index') - print ' json.endObject();' - print ' json.endMember(); // GL_VERTEX_ATTRIB_ARRAYi' - print ' }' - print - - program_targets = [ - 'GL_FRAGMENT_PROGRAM_ARB', - 'GL_VERTEX_PROGRAM_ARB', - ] - - def dump_program_params(self): - for target in self.program_targets: - print ' if (glIsEnabled(%s)) {' % target - print ' json.beginMember("%s");' % target - print ' json.beginObject();' - self.dump_atoms(glGetProgramARB, target) - print ' json.endObject();' - print ' }' - - def dump_texture_parameters(self): - print ' {' - print ' GLint active_texture = GL_TEXTURE0;' - print ' glGetIntegerv(GL_ACTIVE_TEXTURE, &active_texture);' - print ' GLint max_texture_coords = 0;' - print ' glGetIntegerv(GL_MAX_TEXTURE_COORDS, &max_texture_coords);' - print ' GLint max_combined_texture_image_units = 0;' - print ' glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_combined_texture_image_units);' - print ' GLint max_units = std::max(max_combined_texture_image_units, max_texture_coords);' - print ' for (GLint unit = 0; unit < max_units; ++unit) {' - print ' char name[32];' - print ' snprintf(name, sizeof name, "GL_TEXTURE%i", unit);' - print ' json.beginMember(name);' - print ' glActiveTexture(GL_TEXTURE0 + unit);' - print ' json.beginObject();' - print ' GLboolean enabled;' - print ' GLint binding;' - print - for target, binding in texture_targets: - print ' // %s' % target - print ' enabled = GL_FALSE;' - print ' glGetBooleanv(%s, &enabled);' % target - print ' json.writeBoolMember("%s", enabled);' % target - print ' binding = 0;' - print ' glGetIntegerv(%s, &binding);' % binding - print ' json.writeNumberMember("%s", binding);' % binding - print ' if (enabled || binding) {' - print ' json.beginMember("%s");' % target - print ' json.beginObject();' - self.dump_atoms(glGetTexParameter, target) - print ' if (!context.ES) {' - # We only dump the first level parameters - self.dump_atoms(glGetTexLevelParameter, target, "0") - print ' }' - print ' json.endObject();' - print ' json.endMember(); // %s' % target - print ' }' - print - print ' if (unit < max_texture_coords) {' - self.dump_texenv_params() - print ' }' - print ' json.endObject();' - print ' json.endMember(); // GL_TEXTUREi' - print ' }' - print ' glActiveTexture(active_texture);' - print ' }' - print - - def dump_framebuffer_parameters(self): - print ' {' - print ' GLint max_color_attachments = 0;' - print ' glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &max_color_attachments);' - print ' GLint framebuffer;' - for target, binding in framebuffer_targets: - print ' // %s' % target - print ' framebuffer = 0;' - print ' glGetIntegerv(%s, &framebuffer);' % binding - print ' if (framebuffer) {' - print ' json.beginMember("%s");' % target - print ' json.beginObject();' - print ' for (GLint i = 0; i < max_color_attachments; ++i) {' - print ' GLint color_attachment = GL_COLOR_ATTACHMENT0 + i;' - print ' dumpFramebufferAttachementParameters(json, %s, color_attachment);' % target - print ' }' - print ' dumpFramebufferAttachementParameters(json, %s, GL_DEPTH_ATTACHMENT);' % target - print ' dumpFramebufferAttachementParameters(json, %s, GL_STENCIL_ATTACHMENT);' % target - print ' json.endObject();' - print ' json.endMember(); // %s' % target - print ' }' - print - print ' }' - print - - def dump_attachment_parameters(self, target, attachment): - print ' {' - print ' GLint object_type = GL_NONE;' - print ' glGetFramebufferAttachmentParameteriv(%s, %s, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &object_type);' % (target, attachment) - print ' if (object_type != GL_NONE) {' - print ' json.beginMember(enumToString(%s));' % attachment - print ' json.beginObject();' - self.dump_atoms(glGetFramebufferAttachmentParameter, target, attachment) - print ' json.endObject();' - print ' json.endMember(); // GL_x_ATTACHMENT' - print ' }' - print ' }' - - def dump_atoms(self, getter, *args): - for _, _, name in getter.iter(): - self.dump_atom(getter, *(args + (name,))) - - def dump_atom(self, getter, *args): - name = args[-1] - - # Avoid crash on MacOSX - # XXX: The right fix would be to look at the support extensions.. - import platform - if name == 'GL_SAMPLER_BINDING' and platform.system() == 'Darwin': - return - - print ' // %s' % name - print ' {' - #print ' assert(glGetError() == GL_NO_ERROR);' - type, value = getter(*args) - print ' if (glGetError() != GL_NO_ERROR) {' - #print ' std::cerr << "warning: %s(%s) failed\\n";' % (inflection, name) - print ' while (glGetError() != GL_NO_ERROR) {}' - print ' } else {' - print ' json.beginMember("%s");' % name - JsonWriter().visit(type, value) - print ' json.endMember();' - print ' }' - print ' }' - print - - -if __name__ == '__main__': - StateDumper().dump() diff --git a/glstate_images.cpp b/glstate_images.cpp new file mode 100644 index 0000000..2c7c66b --- /dev/null +++ b/glstate_images.cpp @@ -0,0 +1,1146 @@ +/************************************************************************** + * + * Copyright 2011 Jose Fonseca + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#include + +#include +#include + +#include "image.hpp" +#include "json.hpp" +#include "glproc.hpp" +#include "glsize.hpp" +#include "glstate.hpp" +#include "glstate_internal.hpp" + + +#ifdef __linux__ +#include +#endif + +#ifdef __APPLE__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +OSStatus CGSGetSurfaceBounds(CGSConnectionID, CGWindowID, CGSSurfaceID, CGRect *); + +#ifdef __cplusplus +} +#endif + +#endif /* __APPLE__ */ + + +/* Change thi to one to force interpreting depth buffers as RGBA, which enables + * visualizing full dynamic range, until we can transmit HDR images to the GUI */ +#define DEPTH_AS_RGBA 0 + + +namespace glstate { + + +struct ImageDesc +{ + GLint width; + GLint height; + GLint depth; + GLint internalFormat; + + inline + ImageDesc() : + width(0), + height(0), + depth(0), + internalFormat(GL_NONE) + {} + + inline bool + valid(void) const { + return width > 0 && height > 0 && depth > 0; + } +}; + + +/** + * OpenGL ES does not support glGetTexLevelParameteriv, but it is possible to + * probe whether a texture has a given size by crafting a dummy glTexSubImage() + * call. + */ +static bool +probeTextureLevelSizeOES(GLenum target, GLint level, const GLint size[3]) { + while (glGetError() != GL_NO_ERROR) + ; + + GLenum internalFormat = GL_RGBA; + GLenum type = GL_UNSIGNED_BYTE; + GLint dummy = 0; + + switch (target) { + case GL_TEXTURE_2D: + case GL_TEXTURE_CUBE_MAP: + case GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + glTexSubImage2D(target, level, size[0], size[1], 0, 0, internalFormat, type, &dummy); + break; + case GL_TEXTURE_3D_OES: + glTexSubImage3DOES(target, level, size[0], size[1], size[2], 0, 0, 0, internalFormat, type, &dummy); + default: + assert(0); + return false; + } + + GLenum error = glGetError(); + + if (0) { + std::cerr << "(" << size[0] << ", " << size[1] << ", " << size[2] << ") = " << enumToString(error) << "\n"; + } + + if (error == GL_NO_ERROR) { + return true; + } + + while (glGetError() != GL_NO_ERROR) + ; + + return false; +} + + +/** + * Bisect the texture size along an axis. + * + * It is assumed that the texture exists. + */ +static GLint +bisectTextureLevelSizeOES(GLenum target, GLint level, GLint axis, GLint max) { + GLint size[3] = {0, 0, 0}; + + assert(axis < 3); + assert(max >= 0); + + GLint min = 0; + while (true) { + GLint test = (min + max) / 2; + if (test == min) { + return min; + } + + size[axis] = test; + + if (probeTextureLevelSizeOES(target, level, size)) { + min = test; + } else { + max = test; + } + } +} + + +/** + * Special path to obtain texture size on OpenGL ES, that does not rely on + * glGetTexLevelParameteriv + */ +static bool +getActiveTextureLevelDescOES(Context &context, GLenum target, GLint level, ImageDesc &desc) +{ + if (target == GL_TEXTURE_1D) { + // OpenGL ES does not support 1D textures + return false; + } + + const GLint size[3] = {1, 1, 1}; + if (!probeTextureLevelSizeOES(target, level, size)) { + return false; + } + + // XXX: mere guess + desc.internalFormat = GL_RGBA; + + GLint maxSize = 0; + switch (target) { + case GL_TEXTURE_2D: + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize); + desc.width = bisectTextureLevelSizeOES(target, level, 0, maxSize); + desc.height = bisectTextureLevelSizeOES(target, level, 1, maxSize); + desc.depth = 1; + break; + case GL_TEXTURE_CUBE_MAP: + case GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &maxSize); + desc.width = bisectTextureLevelSizeOES(target, level, 0, maxSize); + desc.height = desc.width; + desc.depth = 1; + break; + case GL_TEXTURE_3D_OES: + glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE_OES, &maxSize); + desc.width = bisectTextureLevelSizeOES(target, level, 0, maxSize); + desc.width = bisectTextureLevelSizeOES(target, level, 1, maxSize); + desc.depth = bisectTextureLevelSizeOES(target, level, 2, maxSize); + break; + default: + return false; + } + + if (0) { + std::cerr + << enumToString(target) << " " + << level << " " + << desc.width << "x" << desc.height << "x" << desc.depth + << "\n"; + } + + return desc.valid(); +} + + +static inline bool +getActiveTextureLevelDesc(Context &context, GLenum target, GLint level, ImageDesc &desc) +{ + if (context.ES) { + return getActiveTextureLevelDescOES(context, target, level, desc); + } + + glGetTexLevelParameteriv(target, level, GL_TEXTURE_INTERNAL_FORMAT, &desc.internalFormat); + + desc.width = 0; + glGetTexLevelParameteriv(target, level, GL_TEXTURE_WIDTH, &desc.width); + + if (target == GL_TEXTURE_1D) { + desc.height = 1; + desc.depth = 1; + } else { + desc.height = 0; + glGetTexLevelParameteriv(target, level, GL_TEXTURE_HEIGHT, &desc.height); + if (target != GL_TEXTURE_3D) { + desc.depth = 1; + } else { + desc.depth = 0; + glGetTexLevelParameteriv(target, level, GL_TEXTURE_DEPTH, &desc.depth); + } + } + + return desc.valid(); +} + + +/** + * OpenGL ES does not support glGetTexImage. Obtain the pixels by attaching the + * texture to a framebuffer. + */ +static inline void +getTexImageOES(GLenum target, GLint level, ImageDesc &desc, GLubyte *pixels) +{ + memset(pixels, 0x80, desc.height * desc.width * 4); + + GLenum texture_binding = GL_NONE; + switch (target) { + case GL_TEXTURE_2D: + texture_binding = GL_TEXTURE_BINDING_2D; + break; + case GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + texture_binding = GL_TEXTURE_BINDING_CUBE_MAP; + break; + case GL_TEXTURE_3D_OES: + texture_binding = GL_TEXTURE_BINDING_3D_OES; + default: + return; + } + + GLint texture = 0; + glGetIntegerv(texture_binding, &texture); + if (!texture) { + return; + } + + GLint prev_fbo = 0; + GLuint fbo = 0; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo); + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + GLenum status; + + switch (target) { + case GL_TEXTURE_2D: + case GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level); + status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + std::cerr << __FUNCTION__ << ": " << enumToString(status) << "\n"; + } + glReadPixels(0, 0, desc.width, desc.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + break; + case GL_TEXTURE_3D_OES: + for (int i = 0; i < desc.depth; i++) { + glFramebufferTexture3D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, texture, level, i); + glReadPixels(0, 0, desc.width, desc.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels + 4 * i * desc.width * desc.height); + } + break; + } + + glBindFramebuffer(GL_FRAMEBUFFER, prev_fbo); + + glDeleteFramebuffers(1, &fbo); +} + + +static inline void +dumpActiveTextureLevel(JSONWriter &json, Context &context, GLenum target, GLint level) +{ + ImageDesc desc; + if (!getActiveTextureLevelDesc(context, target, level, desc)) { + return; + } + + char label[512]; + + GLint active_texture = GL_TEXTURE0; + glGetIntegerv(GL_ACTIVE_TEXTURE, &active_texture); + snprintf(label, sizeof label, "%s, %s, level = %d", + enumToString(active_texture), enumToString(target), level); + + json.beginMember(label); + + json.beginObject(); + + // Tell the GUI this is no ordinary object, but an image + json.writeStringMember("__class__", "image"); + + json.writeNumberMember("__width__", desc.width); + json.writeNumberMember("__height__", desc.height); + json.writeNumberMember("__depth__", desc.depth); + + json.writeStringMember("__format__", enumToString(desc.internalFormat)); + + // Hardcoded for now, but we could chose types more adequate to the + // texture internal format + json.writeStringMember("__type__", "uint8"); + json.writeBoolMember("__normalized__", true); + json.writeNumberMember("__channels__", 4); + + GLubyte *pixels = new GLubyte[desc.depth*desc.width*desc.height*4]; + + context.resetPixelPackState(); + + if (context.ES) { + getTexImageOES(target, level, desc, pixels); + } else { + glGetTexImage(target, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + } + + context.restorePixelPackState(); + + json.beginMember("__data__"); + char *pngBuffer; + int pngBufferSize; + image::writePixelsToBuffer(pixels, desc.width, desc.height, 4, true, &pngBuffer, &pngBufferSize); + json.writeBase64(pngBuffer, pngBufferSize); + free(pngBuffer); + json.endMember(); // __data__ + + delete [] pixels; + json.endObject(); +} + + +static inline void +dumpTexture(JSONWriter &json, Context &context, GLenum target, GLenum binding) +{ + GLint texture_binding = 0; + glGetIntegerv(binding, &texture_binding); + if (!glIsEnabled(target) && !texture_binding) { + return; + } + + GLint level = 0; + do { + ImageDesc desc; + if (!getActiveTextureLevelDesc(context, target, level, desc)) { + break; + } + + if (target == GL_TEXTURE_CUBE_MAP) { + for (int face = 0; face < 6; ++face) { + dumpActiveTextureLevel(json, context, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level); + } + } else { + dumpActiveTextureLevel(json, context, target, level); + } + + ++level; + } while(true); +} + + +void +dumpTextures(JSONWriter &json, Context &context) +{ + json.beginMember("textures"); + json.beginObject(); + GLint active_texture = GL_TEXTURE0; + glGetIntegerv(GL_ACTIVE_TEXTURE, &active_texture); + GLint max_texture_coords = 0; + glGetIntegerv(GL_MAX_TEXTURE_COORDS, &max_texture_coords); + GLint max_combined_texture_image_units = 0; + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_combined_texture_image_units); + GLint max_units = std::max(max_combined_texture_image_units, max_texture_coords); + for (GLint unit = 0; unit < max_units; ++unit) { + GLenum texture = GL_TEXTURE0 + unit; + glActiveTexture(texture); + dumpTexture(json, context, GL_TEXTURE_1D, GL_TEXTURE_BINDING_1D); + dumpTexture(json, context, GL_TEXTURE_2D, GL_TEXTURE_BINDING_2D); + dumpTexture(json, context, GL_TEXTURE_3D, GL_TEXTURE_BINDING_3D); + dumpTexture(json, context, GL_TEXTURE_RECTANGLE, GL_TEXTURE_BINDING_RECTANGLE); + dumpTexture(json, context, GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BINDING_CUBE_MAP); + } + glActiveTexture(active_texture); + json.endObject(); + json.endMember(); // textures +} + + +static bool +getDrawableBounds(GLint *width, GLint *height) { +#if defined(__linux__) + if (dlsym(RTLD_DEFAULT, "eglGetCurrentContext")) { + EGLContext currentContext = eglGetCurrentContext(); + if (currentContext == EGL_NO_CONTEXT) { + return false; + } + + EGLSurface currentSurface = eglGetCurrentSurface(EGL_DRAW); + if (currentSurface == EGL_NO_SURFACE) { + return false; + } + + EGLDisplay currentDisplay = eglGetCurrentDisplay(); + if (currentDisplay == EGL_NO_DISPLAY) { + return false; + } + + if (!eglQuerySurface(currentDisplay, currentSurface, EGL_WIDTH, width) || + !eglQuerySurface(currentDisplay, currentSurface, EGL_HEIGHT, height)) { + return false; + } + + return true; + } +#endif + +#if defined(_WIN32) + + HDC hDC = wglGetCurrentDC(); + if (!hDC) { + return false; + } + + HWND hWnd = WindowFromDC(hDC); + RECT rect; + + if (!GetClientRect(hWnd, &rect)) { + return false; + } + + *width = rect.right - rect.left; + *height = rect.bottom - rect.top; + return true; + +#elif defined(__APPLE__) + + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx == NULL) { + return false; + } + + CGSConnectionID cid; + CGSWindowID wid; + CGSSurfaceID sid; + + if (CGLGetSurface(ctx, &cid, &wid, &sid) != kCGLNoError) { + return false; + } + + CGRect rect; + + if (CGSGetSurfaceBounds(cid, wid, sid, &rect) != 0) { + return false; + } + + *width = rect.size.width; + *height = rect.size.height; + return true; + +#elif defined(HAVE_X11) + + Display *display; + Drawable drawable; + Window root; + int x, y; + unsigned int w, h, bw, depth; + + display = glXGetCurrentDisplay(); + if (!display) { + return false; + } + + drawable = glXGetCurrentDrawable(); + if (drawable == None) { + return false; + } + + if (!XGetGeometry(display, drawable, &root, &x, &y, &w, &h, &bw, &depth)) { + return false; + } + + *width = w; + *height = h; + return true; + +#else + + return false; + +#endif +} + + +static const GLenum texture_bindings[][2] = { + {GL_TEXTURE_1D, GL_TEXTURE_BINDING_1D}, + {GL_TEXTURE_2D, GL_TEXTURE_BINDING_2D}, + {GL_TEXTURE_3D, GL_TEXTURE_BINDING_3D}, + {GL_TEXTURE_RECTANGLE, GL_TEXTURE_BINDING_RECTANGLE}, + {GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BINDING_CUBE_MAP} +}; + + +static bool +bindTexture(GLint texture, GLenum &target, GLint &bound_texture) +{ + + for (unsigned i = 0; i < sizeof(texture_bindings)/sizeof(texture_bindings[0]); ++i) { + target = texture_bindings[i][0]; + + GLenum binding = texture_bindings[i][1]; + + while (glGetError() != GL_NO_ERROR) + ; + + glGetIntegerv(binding, &bound_texture); + glBindTexture(target, texture); + + if (glGetError() == GL_NO_ERROR) { + return true; + } + + glBindTexture(target, bound_texture); + } + + target = GL_NONE; + + return false; +} + + +static bool +getTextureLevelDesc(Context &context, GLint texture, GLint level, ImageDesc &desc) +{ + GLenum target; + GLint bound_texture = 0; + if (!bindTexture(texture, target, bound_texture)) { + return false; + } + + getActiveTextureLevelDesc(context, target, level, desc); + + glBindTexture(target, bound_texture); + + return desc.valid(); +} + + +static bool +getBoundRenderbufferDesc(Context &context, ImageDesc &desc) +{ + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &desc.width); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &desc.height); + desc.depth = 1; + + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &desc.internalFormat); + + return desc.valid(); +} + + +static bool +getRenderbufferDesc(Context &context, GLint renderbuffer, ImageDesc &desc) +{ + GLint bound_renderbuffer = 0; + glGetIntegerv(GL_RENDERBUFFER_BINDING, &bound_renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + + getBoundRenderbufferDesc(context, desc); + + glBindRenderbuffer(GL_RENDERBUFFER, bound_renderbuffer); + + return desc.valid(); +} + + +static bool +getFramebufferAttachmentDesc(Context &context, GLenum target, GLenum attachment, ImageDesc &desc) +{ + GLint object_type = GL_NONE; + glGetFramebufferAttachmentParameteriv(target, attachment, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, + &object_type); + if (object_type == GL_NONE) { + return false; + } + + GLint object_name = 0; + glGetFramebufferAttachmentParameteriv(target, attachment, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, + &object_name); + if (object_name == 0) { + return false; + } + + if (object_type == GL_RENDERBUFFER) { + return getRenderbufferDesc(context, object_name, desc); + } else if (object_type == GL_TEXTURE) { + GLint texture_level = 0; + glGetFramebufferAttachmentParameteriv(target, attachment, + GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, + &texture_level); + return getTextureLevelDesc(context, object_name, texture_level, desc); + } else { + std::cerr << "warning: unexpected GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = " << object_type << "\n"; + return false; + } +} + + + +image::Image * +getDrawBufferImage() { + GLenum format = GL_RGB; + GLint channels = __gl_format_channels(format); + if (channels > 4) { + return NULL; + } + + Context context; + + GLenum framebuffer_binding; + GLenum framebuffer_target; + if (context.ES) { + framebuffer_binding = GL_FRAMEBUFFER_BINDING; + framebuffer_target = GL_FRAMEBUFFER; + } else { + framebuffer_binding = GL_DRAW_FRAMEBUFFER_BINDING; + framebuffer_target = GL_DRAW_FRAMEBUFFER; + } + + GLint draw_framebuffer = 0; + glGetIntegerv(framebuffer_binding, &draw_framebuffer); + + GLint draw_buffer = GL_NONE; + ImageDesc desc; + if (draw_framebuffer) { + if (context.ARB_draw_buffers) { + glGetIntegerv(GL_DRAW_BUFFER0, &draw_buffer); + if (draw_buffer == GL_NONE) { + return NULL; + } + } + + if (!getFramebufferAttachmentDesc(context, framebuffer_target, draw_buffer, desc)) { + return NULL; + } + } else { + if (!context.ES) { + glGetIntegerv(GL_DRAW_BUFFER, &draw_buffer); + if (draw_buffer == GL_NONE) { + return NULL; + } + } + + if (!getDrawableBounds(&desc.width, &desc.height)) { + return NULL; + } + + desc.depth = 1; + } + + GLenum type = GL_UNSIGNED_BYTE; + +#if DEPTH_AS_RGBA + if (format == GL_DEPTH_COMPONENT) { + type = GL_UNSIGNED_INT; + channels = 4; + } +#endif + + image::Image *image = new image::Image(desc.width, desc.height, channels, true); + if (!image) { + return NULL; + } + + while (glGetError() != GL_NO_ERROR) {} + + GLint read_framebuffer = 0; + GLint read_buffer = GL_NONE; + if (!context.ES) { + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read_framebuffer); + glBindFramebuffer(GL_READ_FRAMEBUFFER, draw_framebuffer); + + glGetIntegerv(GL_READ_BUFFER, &read_buffer); + glReadBuffer(draw_buffer); + } + + // TODO: reset imaging state too + context.resetPixelPackState(); + + glReadPixels(0, 0, desc.width, desc.height, format, type, image->pixels); + + context.restorePixelPackState(); + + if (!context.ES) { + glReadBuffer(read_buffer); + glBindFramebuffer(GL_READ_FRAMEBUFFER, read_framebuffer); + } + + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + do { + std::cerr << "warning: " << enumToString(error) << " while getting snapshot\n"; + error = glGetError(); + } while(error != GL_NO_ERROR); + delete image; + return NULL; + } + + return image; +} + + +/** + * Dump the image of the currently bound read buffer. + */ +static inline void +dumpReadBufferImage(JSONWriter &json, GLint width, GLint height, GLenum format, + GLint internalFormat = GL_NONE) +{ + GLint channels = __gl_format_channels(format); + + Context context; + + json.beginObject(); + + // Tell the GUI this is no ordinary object, but an image + json.writeStringMember("__class__", "image"); + + json.writeNumberMember("__width__", width); + json.writeNumberMember("__height__", height); + json.writeNumberMember("__depth__", 1); + + json.writeStringMember("__format__", enumToString(internalFormat)); + + // Hardcoded for now, but we could chose types more adequate to the + // texture internal format + json.writeStringMember("__type__", "uint8"); + json.writeBoolMember("__normalized__", true); + json.writeNumberMember("__channels__", channels); + + GLenum type = GL_UNSIGNED_BYTE; + +#if DEPTH_AS_RGBA + if (format == GL_DEPTH_COMPONENT) { + type = GL_UNSIGNED_INT; + channels = 4; + } +#endif + + GLubyte *pixels = new GLubyte[width*height*channels]; + + // TODO: reset imaging state too + context.resetPixelPackState(); + + glReadPixels(0, 0, width, height, format, type, pixels); + + context.restorePixelPackState(); + + json.beginMember("__data__"); + char *pngBuffer; + int pngBufferSize; + image::writePixelsToBuffer(pixels, width, height, channels, true, &pngBuffer, &pngBufferSize); + //std::cerr <<" Before = "<<(width * height * channels * sizeof *pixels) + // <<", after = "<= GL_COLOR_ATTACHMENT0 && draw_buffer < GL_COLOR_ATTACHMENT0 + max_color_attachments) { + attachment = draw_buffer; + } else { + std::cerr << "warning: unexpected GL_DRAW_BUFFER" << i << " = " << draw_buffer << "\n"; + attachment = GL_COLOR_ATTACHMENT0; + } + GLint alpha_size = 0; + glGetFramebufferAttachmentParameteriv(target, attachment, + GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, + &alpha_size); + GLenum format = alpha_size ? GL_RGBA : GL_RGB; + dumpFramebufferAttachment(json, context, target, attachment, format); + } + } + + glReadBuffer(read_buffer); + + if (!context.ES) { + dumpFramebufferAttachment(json, context, target, GL_DEPTH_ATTACHMENT, GL_DEPTH_COMPONENT); + dumpFramebufferAttachment(json, context, target, GL_STENCIL_ATTACHMENT, GL_STENCIL_INDEX); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, read_framebuffer); +} + + +void +dumpFramebuffer(JSONWriter &json, Context &context) +{ + json.beginMember("framebuffer"); + json.beginObject(); + + GLint boundDrawFbo = 0, boundReadFbo = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &boundDrawFbo); + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &boundReadFbo); + if (!boundDrawFbo) { + dumpDrawableImages(json, context); + } else if (context.ES) { + dumpFramebufferAttachments(json, context, GL_FRAMEBUFFER); + } else { + GLint colorRb = 0, stencilRb = 0, depthRb = 0; + GLint draw_buffer0 = GL_NONE; + glGetIntegerv(GL_DRAW_BUFFER0, &draw_buffer0); + bool multisample = false; + + GLint boundRb = 0; + glGetIntegerv(GL_RENDERBUFFER_BINDING, &boundRb); + + GLint object_type; + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, draw_buffer0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &object_type); + if (object_type == GL_RENDERBUFFER) { + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, draw_buffer0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &colorRb); + glBindRenderbuffer(GL_RENDERBUFFER, colorRb); + GLint samples = 0; + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); + if (samples) { + multisample = true; + } + } + + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &object_type); + if (object_type == GL_RENDERBUFFER) { + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthRb); + glBindRenderbuffer(GL_RENDERBUFFER, depthRb); + GLint samples = 0; + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); + if (samples) { + multisample = true; + } + } + + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &object_type); + if (object_type == GL_RENDERBUFFER) { + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &stencilRb); + glBindRenderbuffer(GL_RENDERBUFFER, stencilRb); + GLint samples = 0; + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); + if (samples) { + multisample = true; + } + } + + glBindRenderbuffer(GL_RENDERBUFFER, boundRb); + + GLuint rbs[3]; + GLint numRbs = 0; + GLuint fboCopy = 0; + + if (multisample) { + // glReadPixels doesnt support multisampled buffers so we need + // to blit the fbo to a temporary one + fboCopy = downsampledFramebuffer(context, + boundDrawFbo, draw_buffer0, + colorRb, depthRb, stencilRb, + rbs, &numRbs); + } + + dumpFramebufferAttachments(json, context, GL_DRAW_FRAMEBUFFER); + + if (multisample) { + glBindRenderbuffer(GL_RENDERBUFFER_BINDING, boundRb); + glDeleteRenderbuffers(numRbs, rbs); + glDeleteFramebuffers(1, &fboCopy); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, boundReadFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, boundDrawFbo); + } + + json.endObject(); + json.endMember(); // framebuffer +} + + +} /* namespace glstate */ diff --git a/glstate_internal.hpp b/glstate_internal.hpp index 0398dec..ddc802e 100644 --- a/glstate_internal.hpp +++ b/glstate_internal.hpp @@ -42,7 +42,6 @@ struct Context bool ARB_draw_buffers; - inline Context(void); GLint packAlignment; @@ -59,7 +58,11 @@ void dumpEnum(JSONWriter &json, GLenum pname); void dumpParameters(JSONWriter &json, Context &context); -void dumpCurrentContext(std::ostream &os); +void dumpShadersUniforms(JSONWriter &json); + +void dumpTextures(JSONWriter &json, Context &context); + +void dumpFramebuffer(JSONWriter &json, Context &context); } /* namespace glstate */ diff --git a/glstate_params.py b/glstate_params.py new file mode 100644 index 0000000..ad8e258 --- /dev/null +++ b/glstate_params.py @@ -0,0 +1,503 @@ +########################################################################## +# +# Copyright 2011 Jose Fonseca +# All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +##########################################################################/ + + +'''Generate code to dump most GL state into JSON.''' + + +from specs.stdapi import * + +from specs.gltypes import * +from specs.glparams import * + + +texture_targets = [ + ('GL_TEXTURE_1D', 'GL_TEXTURE_BINDING_1D'), + ('GL_TEXTURE_2D', 'GL_TEXTURE_BINDING_2D'), + ('GL_TEXTURE_3D', 'GL_TEXTURE_BINDING_3D'), + ('GL_TEXTURE_RECTANGLE', 'GL_TEXTURE_BINDING_RECTANGLE'), + ('GL_TEXTURE_CUBE_MAP', 'GL_TEXTURE_BINDING_CUBE_MAP') +] + +framebuffer_targets = [ + ('GL_DRAW_FRAMEBUFFER', 'GL_DRAW_FRAMEBUFFER_BINDING'), + ('GL_READ_FRAMEBUFFER', 'GL_READ_FRAMEBUFFER_BINDING'), +] + +class GetInflector: + '''Objects that describes how to inflect.''' + + reduced_types = { + B: I, + E: I, + I: F, + } + + def __init__(self, radical, inflections, suffix = ''): + self.radical = radical + self.inflections = inflections + self.suffix = suffix + + def reduced_type(self, type): + if type in self.inflections: + return type + if type in self.reduced_types: + return self.reduced_type(self.reduced_types[type]) + raise NotImplementedError + + def inflect(self, type): + return self.radical + self.inflection(type) + self.suffix + + def inflection(self, type): + type = self.reduced_type(type) + assert type in self.inflections + return self.inflections[type] + + def __str__(self): + return self.radical + self.suffix + + +class StateGetter(Visitor): + '''Type visitor that is able to extract the state via one of the glGet* + functions. + + It will declare any temporary variable + ''' + + def __init__(self, radical, inflections, suffix=''): + self.inflector = GetInflector(radical, inflections) + self.suffix = suffix + + def iter(self): + for function, type, count, name in parameters: + inflection = self.inflector.radical + self.suffix + if inflection not in function.split(','): + continue + if type is X: + continue + yield type, count, name + + def __call__(self, *args): + pname = args[-1] + + for type, count, name in self.iter(): + if name == pname: + if count != 1: + type = Array(type, str(count)) + + return type, self.visit(type, args) + + raise NotImplementedError + + def temp_name(self, args): + '''Return the name of a temporary variable to hold the state.''' + pname = args[-1] + + return pname[3:].lower() + + def visitConst(self, const, args): + return self.visit(const.type, args) + + def visitScalar(self, type, args): + temp_name = self.temp_name(args) + elem_type = self.inflector.reduced_type(type) + inflection = self.inflector.inflect(type) + if inflection.endswith('v'): + print ' %s %s = 0;' % (elem_type, temp_name) + print ' %s(%s, &%s);' % (inflection + self.suffix, ', '.join(args), temp_name) + else: + print ' %s %s = %s(%s);' % (elem_type, temp_name, inflection + self.suffix, ', '.join(args)) + return temp_name + + def visitString(self, string, args): + temp_name = self.temp_name(args) + inflection = self.inflector.inflect(string) + assert not inflection.endswith('v') + print ' %s %s = (%s)%s(%s);' % (string, temp_name, string, inflection + self.suffix, ', '.join(args)) + return temp_name + + def visitAlias(self, alias, args): + return self.visitScalar(alias, args) + + def visitEnum(self, enum, args): + return self.visit(GLint, args) + + def visitBitmask(self, bitmask, args): + return self.visit(GLint, args) + + def visitArray(self, array, args): + temp_name = self.temp_name(args) + if array.length == '1': + return self.visit(array.type) + elem_type = self.inflector.reduced_type(array.type) + inflection = self.inflector.inflect(array.type) + assert inflection.endswith('v') + print ' %s %s[%s + 1];' % (elem_type, temp_name, array.length) + print ' memset(%s, 0, %s * sizeof *%s);' % (temp_name, array.length, temp_name) + print ' %s[%s] = (%s)0xdeadc0de;' % (temp_name, array.length, elem_type) + print ' %s(%s, %s);' % (inflection + self.suffix, ', '.join(args), temp_name) + # Simple buffer overflow detection + print ' assert(%s[%s] == (%s)0xdeadc0de);' % (temp_name, array.length, elem_type) + return temp_name + + def visitOpaque(self, pointer, args): + temp_name = self.temp_name(args) + inflection = self.inflector.inflect(pointer) + assert inflection.endswith('v') + print ' GLvoid *%s;' % temp_name + print ' %s(%s, &%s);' % (inflection + self.suffix, ', '.join(args), temp_name) + return temp_name + + +glGet = StateGetter('glGet', { + B: 'Booleanv', + I: 'Integerv', + F: 'Floatv', + D: 'Doublev', + S: 'String', + P: 'Pointerv', +}) + +glGetMaterial = StateGetter('glGetMaterial', {I: 'iv', F: 'fv'}) +glGetLight = StateGetter('glGetLight', {I: 'iv', F: 'fv'}) +glGetVertexAttrib = StateGetter('glGetVertexAttrib', {I: 'iv', F: 'fv', D: 'dv', P: 'Pointerv'}) +glGetTexParameter = StateGetter('glGetTexParameter', {I: 'iv', F: 'fv'}) +glGetTexEnv = StateGetter('glGetTexEnv', {I: 'iv', F: 'fv'}) +glGetTexLevelParameter = StateGetter('glGetTexLevelParameter', {I: 'iv', F: 'fv'}) +glGetShader = StateGetter('glGetShaderiv', {I: 'iv'}) +glGetProgram = StateGetter('glGetProgram', {I: 'iv'}) +glGetProgramARB = StateGetter('glGetProgram', {I: 'iv', F: 'fv', S: 'Stringv'}, 'ARB') +glGetFramebufferAttachmentParameter = StateGetter('glGetFramebufferAttachmentParameter', {I: 'iv'}) + + +class JsonWriter(Visitor): + '''Type visitor that will dump a value of the specified type through the + JSON writer. + + It expects a previously declared JSONWriter instance named "json".''' + + def visitLiteral(self, literal, instance): + if literal.kind == 'Bool': + print ' json.writeBool(%s);' % instance + elif literal.kind in ('SInt', 'Uint', 'Float', 'Double'): + print ' json.writeNumber(%s);' % instance + else: + raise NotImplementedError + + def visitString(self, string, instance): + assert string.length is None + print ' json.writeString((const char *)%s);' % instance + + def visitEnum(self, enum, instance): + if enum.expr == 'GLenum': + print ' dumpEnum(json, %s);' % instance + else: + print ' json.writeNumber(%s);' % instance + + def visitBitmask(self, bitmask, instance): + raise NotImplementedError + + def visitAlias(self, alias, instance): + self.visit(alias.type, instance) + + def visitOpaque(self, opaque, instance): + print ' json.writeNumber((size_t)%s);' % instance + + __index = 0 + + def visitArray(self, array, instance): + index = '__i%u' % JsonWriter.__index + JsonWriter.__index += 1 + print ' json.beginArray();' + print ' for (unsigned %s = 0; %s < %s; ++%s) {' % (index, index, array.length, index) + self.visit(array.type, '%s[%s]' % (instance, index)) + print ' }' + print ' json.endArray();' + + + +class StateDumper: + '''Class to generate code to dump all GL state in JSON format via + stdout.''' + + def __init__(self): + pass + + def dump(self): + print '#include ' + print + print '#include "json.hpp"' + print '#include "glproc.hpp"' + print '#include "glsize.hpp"' + print '#include "glstate.hpp"' + print '#include "glstate_internal.hpp"' + print + print 'namespace glstate {' + print + + print 'const char *' + print 'enumToString(GLenum pname)' + print '{' + print ' switch (pname) {' + for name in GLenum.values: + print ' case %s:' % name + print ' return "%s";' % name + print ' default:' + print ' return NULL;' + print ' }' + print '}' + print + + print 'static void' + print 'dumpFramebufferAttachementParameters(JSONWriter &json, GLenum target, GLenum attachment)' + print '{' + self.dump_attachment_parameters('target', 'attachment') + print '}' + print + + print 'void' + print 'dumpEnum(JSONWriter &json, GLenum pname)' + print '{' + print ' const char *s = enumToString(pname);' + print ' if (s) {' + print ' json.writeString(s);' + print ' } else {' + print ' json.writeNumber(pname);' + print ' }' + print '}' + print + + print 'void dumpParameters(JSONWriter &json, Context &context)' + print '{' + print ' json.beginMember("parameters");' + print ' json.beginObject();' + + self.dump_atoms(glGet) + + self.dump_material_params() + self.dump_light_params() + self.dump_vertex_attribs() + self.dump_program_params() + self.dump_texture_parameters() + self.dump_framebuffer_parameters() + + print ' json.endObject();' + print ' json.endMember(); // parameters' + print '}' + print + + print '} /*namespace glstate */' + + def dump_material_params(self): + print ' if (!context.ES) {' + for face in ['GL_FRONT', 'GL_BACK']: + print ' json.beginMember("%s");' % face + print ' json.beginObject();' + self.dump_atoms(glGetMaterial, face) + print ' json.endObject();' + print ' }' + print + + def dump_light_params(self): + print ' GLint max_lights = 0;' + print ' __glGetIntegerv(GL_MAX_LIGHTS, &max_lights);' + print ' for (GLint index = 0; index < max_lights; ++index) {' + print ' GLenum light = GL_LIGHT0 + index;' + print ' if (glIsEnabled(light)) {' + print ' char name[32];' + print ' snprintf(name, sizeof name, "GL_LIGHT%i", index);' + print ' json.beginMember(name);' + print ' json.beginObject();' + self.dump_atoms(glGetLight, ' GL_LIGHT0 + index') + print ' json.endObject();' + print ' json.endMember(); // GL_LIGHTi' + print ' }' + print ' }' + print + + def texenv_param_target(self, name): + if name == 'GL_TEXTURE_LOD_BIAS': + return 'GL_TEXTURE_FILTER_CONTROL' + elif name == 'GL_COORD_REPLACE': + return 'GL_POINT_SPRITE' + else: + return 'GL_TEXTURE_ENV' + + def dump_texenv_params(self): + for target in ['GL_TEXTURE_ENV', 'GL_TEXTURE_FILTER_CONTROL', 'GL_POINT_SPRITE']: + print ' if (!context.ES) {' + print ' json.beginMember("%s");' % target + print ' json.beginObject();' + for _, _, name in glGetTexEnv.iter(): + if self.texenv_param_target(name) == target: + self.dump_atom(glGetTexEnv, target, name) + print ' json.endObject();' + print ' }' + + def dump_vertex_attribs(self): + print ' GLint max_vertex_attribs = 0;' + print ' __glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attribs);' + print ' for (GLint index = 0; index < max_vertex_attribs; ++index) {' + print ' char name[32];' + print ' snprintf(name, sizeof name, "GL_VERTEX_ATTRIB_ARRAY%i", index);' + print ' json.beginMember(name);' + print ' json.beginObject();' + self.dump_atoms(glGetVertexAttrib, 'index') + print ' json.endObject();' + print ' json.endMember(); // GL_VERTEX_ATTRIB_ARRAYi' + print ' }' + print + + program_targets = [ + 'GL_FRAGMENT_PROGRAM_ARB', + 'GL_VERTEX_PROGRAM_ARB', + ] + + def dump_program_params(self): + for target in self.program_targets: + print ' if (glIsEnabled(%s)) {' % target + print ' json.beginMember("%s");' % target + print ' json.beginObject();' + self.dump_atoms(glGetProgramARB, target) + print ' json.endObject();' + print ' }' + + def dump_texture_parameters(self): + print ' {' + print ' GLint active_texture = GL_TEXTURE0;' + print ' glGetIntegerv(GL_ACTIVE_TEXTURE, &active_texture);' + print ' GLint max_texture_coords = 0;' + print ' glGetIntegerv(GL_MAX_TEXTURE_COORDS, &max_texture_coords);' + print ' GLint max_combined_texture_image_units = 0;' + print ' glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_combined_texture_image_units);' + print ' GLint max_units = std::max(max_combined_texture_image_units, max_texture_coords);' + print ' for (GLint unit = 0; unit < max_units; ++unit) {' + print ' char name[32];' + print ' snprintf(name, sizeof name, "GL_TEXTURE%i", unit);' + print ' json.beginMember(name);' + print ' glActiveTexture(GL_TEXTURE0 + unit);' + print ' json.beginObject();' + print ' GLboolean enabled;' + print ' GLint binding;' + print + for target, binding in texture_targets: + print ' // %s' % target + print ' enabled = GL_FALSE;' + print ' glGetBooleanv(%s, &enabled);' % target + print ' json.writeBoolMember("%s", enabled);' % target + print ' binding = 0;' + print ' glGetIntegerv(%s, &binding);' % binding + print ' json.writeNumberMember("%s", binding);' % binding + print ' if (enabled || binding) {' + print ' json.beginMember("%s");' % target + print ' json.beginObject();' + self.dump_atoms(glGetTexParameter, target) + print ' if (!context.ES) {' + # We only dump the first level parameters + self.dump_atoms(glGetTexLevelParameter, target, "0") + print ' }' + print ' json.endObject();' + print ' json.endMember(); // %s' % target + print ' }' + print + print ' if (unit < max_texture_coords) {' + self.dump_texenv_params() + print ' }' + print ' json.endObject();' + print ' json.endMember(); // GL_TEXTUREi' + print ' }' + print ' glActiveTexture(active_texture);' + print ' }' + print + + def dump_framebuffer_parameters(self): + print ' {' + print ' GLint max_color_attachments = 0;' + print ' glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &max_color_attachments);' + print ' GLint framebuffer;' + for target, binding in framebuffer_targets: + print ' // %s' % target + print ' framebuffer = 0;' + print ' glGetIntegerv(%s, &framebuffer);' % binding + print ' if (framebuffer) {' + print ' json.beginMember("%s");' % target + print ' json.beginObject();' + print ' for (GLint i = 0; i < max_color_attachments; ++i) {' + print ' GLint color_attachment = GL_COLOR_ATTACHMENT0 + i;' + print ' dumpFramebufferAttachementParameters(json, %s, color_attachment);' % target + print ' }' + print ' dumpFramebufferAttachementParameters(json, %s, GL_DEPTH_ATTACHMENT);' % target + print ' dumpFramebufferAttachementParameters(json, %s, GL_STENCIL_ATTACHMENT);' % target + print ' json.endObject();' + print ' json.endMember(); // %s' % target + print ' }' + print + print ' }' + print + + def dump_attachment_parameters(self, target, attachment): + print ' {' + print ' GLint object_type = GL_NONE;' + print ' glGetFramebufferAttachmentParameteriv(%s, %s, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &object_type);' % (target, attachment) + print ' if (object_type != GL_NONE) {' + print ' json.beginMember(enumToString(%s));' % attachment + print ' json.beginObject();' + self.dump_atoms(glGetFramebufferAttachmentParameter, target, attachment) + print ' json.endObject();' + print ' json.endMember(); // GL_x_ATTACHMENT' + print ' }' + print ' }' + + def dump_atoms(self, getter, *args): + for _, _, name in getter.iter(): + self.dump_atom(getter, *(args + (name,))) + + def dump_atom(self, getter, *args): + name = args[-1] + + # Avoid crash on MacOSX + # XXX: The right fix would be to look at the support extensions.. + import platform + if name == 'GL_SAMPLER_BINDING' and platform.system() == 'Darwin': + return + + print ' // %s' % name + print ' {' + #print ' assert(glGetError() == GL_NO_ERROR);' + type, value = getter(*args) + print ' if (glGetError() != GL_NO_ERROR) {' + #print ' std::cerr << "warning: %s(%s) failed\\n";' % (inflection, name) + print ' while (glGetError() != GL_NO_ERROR) {}' + print ' } else {' + print ' json.beginMember("%s");' % name + JsonWriter().visit(type, value) + print ' json.endMember();' + print ' }' + print ' }' + print + + +if __name__ == '__main__': + StateDumper().dump() diff --git a/glstate_shaders.cpp b/glstate_shaders.cpp new file mode 100644 index 0000000..90e5d0a --- /dev/null +++ b/glstate_shaders.cpp @@ -0,0 +1,529 @@ +/************************************************************************** + * + * Copyright 2011 Jose Fonseca + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#include + +#include +#include +#include +#include + +#include "json.hpp" +#include "glproc.hpp" +#include "glsize.hpp" +#include "glstate.hpp" +#include "glstate_internal.hpp" + + +namespace glstate { + + +// Mapping from shader type to shader source, used to accumulated the sources +// of different shaders with same type. +typedef std::map ShaderMap; + + +static void +getShaderSource(ShaderMap &shaderMap, GLuint shader) +{ + if (!shader) { + return; + } + + GLint shader_type = 0; + glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type); + if (!shader_type) { + return; + } + + GLint source_length = 0; + glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &source_length); + if (!source_length) { + return; + } + + GLchar *source = new GLchar[source_length]; + GLsizei length = 0; + source[0] = 0; + glGetShaderSource(shader, source_length, &length, source); + + shaderMap[enumToString(shader_type)] += source; + + delete [] source; +} + + +static void +getShaderObjSource(ShaderMap &shaderMap, GLhandleARB shaderObj) +{ + if (!shaderObj) { + return; + } + + GLint object_type = 0; + glGetObjectParameterivARB(shaderObj, GL_OBJECT_TYPE_ARB, &object_type); + if (object_type != GL_SHADER_OBJECT_ARB) { + return; + } + + GLint shader_type = 0; + glGetObjectParameterivARB(shaderObj, GL_OBJECT_SUBTYPE_ARB, &shader_type); + if (!shader_type) { + return; + } + + GLint source_length = 0; + glGetObjectParameterivARB(shaderObj, GL_OBJECT_SHADER_SOURCE_LENGTH_ARB, &source_length); + if (!source_length) { + return; + } + + GLcharARB *source = new GLcharARB[source_length]; + GLsizei length = 0; + source[0] = 0; + glGetShaderSource(shaderObj, source_length, &length, source); + + shaderMap[enumToString(shader_type)] += source; + + delete [] source; +} + + +static inline void +dumpProgram(JSONWriter &json, GLint program) +{ + GLint attached_shaders = 0; + glGetProgramiv(program, GL_ATTACHED_SHADERS, &attached_shaders); + if (!attached_shaders) { + return; + } + + ShaderMap shaderMap; + + GLuint *shaders = new GLuint[attached_shaders]; + GLsizei count = 0; + glGetAttachedShaders(program, attached_shaders, &count, shaders); + std::sort(shaders, shaders + count); + for (GLsizei i = 0; i < count; ++ i) { + getShaderSource(shaderMap, shaders[i]); + } + delete [] shaders; + + for (ShaderMap::const_iterator it = shaderMap.begin(); it != shaderMap.end(); ++it) { + json.beginMember(it->first); + json.writeString(it->second); + json.endMember(); + } +} + + +static inline void +dumpProgramObj(JSONWriter &json, GLhandleARB programObj) +{ + GLint attached_shaders = 0; + glGetObjectParameterivARB(programObj, GL_OBJECT_ATTACHED_OBJECTS_ARB, &attached_shaders); + if (!attached_shaders) { + return; + } + + ShaderMap shaderMap; + + GLhandleARB *shaderObjs = new GLhandleARB[attached_shaders]; + GLsizei count = 0; + glGetAttachedObjectsARB(programObj, attached_shaders, &count, shaderObjs); + std::sort(shaderObjs, shaderObjs + count); + for (GLsizei i = 0; i < count; ++ i) { + getShaderObjSource(shaderMap, shaderObjs[i]); + } + delete [] shaderObjs; + + for (ShaderMap::const_iterator it = shaderMap.begin(); it != shaderMap.end(); ++it) { + json.beginMember(it->first); + json.writeString(it->second); + json.endMember(); + } +} + +/* + * When fetching the uniform name of an array we usually get name[0] + * so we need to cut the trailing "[0]" in order to properly construct + * array names later. Otherwise we endup with stuff like + * uniformArray[0][0], + * uniformArray[0][1], + * instead of + * uniformArray[0], + * uniformArray[1]. + */ +static std::string +resolveUniformName(const GLchar *name, GLint size) +{ + std::string qualifiedName(name); + if (size > 1) { + std::string::size_type nameLength = qualifiedName.length(); + static const char * const arrayStart = "[0]"; + static const int arrayStartLen = 3; + if (qualifiedName.rfind(arrayStart) == (nameLength - arrayStartLen)) { + qualifiedName = qualifiedName.substr(0, nameLength - 3); + } + } + return qualifiedName; +} + +static void +dumpUniform(JSONWriter &json, GLint program, GLint size, GLenum type, const GLchar *name) { + GLenum elemType; + GLint numElems; + __gl_uniform_size(type, elemType, numElems); + if (elemType == GL_NONE) { + return; + } + + GLfloat fvalues[4*4]; + GLdouble dvalues[4*4]; + GLint ivalues[4*4]; + GLuint uivalues[4*4]; + + GLint i, j; + + std::string qualifiedName = resolveUniformName(name, size); + + for (i = 0; i < size; ++i) { + std::stringstream ss; + ss << qualifiedName; + + if (size > 1) { + ss << '[' << i << ']'; + } + + std::string elemName = ss.str(); + + json.beginMember(elemName); + + GLint location = glGetUniformLocation(program, elemName.c_str()); + + if (numElems > 1) { + json.beginArray(); + } + + switch (elemType) { + case GL_FLOAT: + glGetUniformfv(program, location, fvalues); + for (j = 0; j < numElems; ++j) { + json.writeNumber(fvalues[j]); + } + break; + case GL_DOUBLE: + glGetUniformdv(program, location, dvalues); + for (j = 0; j < numElems; ++j) { + json.writeNumber(dvalues[j]); + } + break; + case GL_INT: + glGetUniformiv(program, location, ivalues); + for (j = 0; j < numElems; ++j) { + json.writeNumber(ivalues[j]); + } + break; + case GL_UNSIGNED_INT: + glGetUniformuiv(program, location, uivalues); + for (j = 0; j < numElems; ++j) { + json.writeNumber(uivalues[j]); + } + break; + case GL_BOOL: + glGetUniformiv(program, location, ivalues); + for (j = 0; j < numElems; ++j) { + json.writeBool(ivalues[j]); + } + break; + default: + assert(0); + break; + } + + if (numElems > 1) { + json.endArray(); + } + + json.endMember(); + } +} + + +static void +dumpUniformARB(JSONWriter &json, GLhandleARB programObj, GLint size, GLenum type, const GLchar *name) { + + GLenum elemType; + GLint numElems; + __gl_uniform_size(type, elemType, numElems); + if (elemType == GL_NONE) { + return; + } + + GLfloat fvalues[4*4]; + GLint ivalues[4*4]; + + GLint i, j; + + std::string qualifiedName = resolveUniformName(name, size); + + for (i = 0; i < size; ++i) { + std::stringstream ss; + ss << qualifiedName; + + if (size > 1) { + ss << '[' << i << ']'; + } + + std::string elemName = ss.str(); + + json.beginMember(elemName); + + GLint location = glGetUniformLocationARB(programObj, elemName.c_str()); + + if (numElems > 1) { + json.beginArray(); + } + + switch (elemType) { + case GL_DOUBLE: + // glGetUniformdvARB does not exists + case GL_FLOAT: + glGetUniformfvARB(programObj, location, fvalues); + for (j = 0; j < numElems; ++j) { + json.writeNumber(fvalues[j]); + } + break; + case GL_UNSIGNED_INT: + // glGetUniformuivARB does not exists + case GL_INT: + glGetUniformivARB(programObj, location, ivalues); + for (j = 0; j < numElems; ++j) { + json.writeNumber(ivalues[j]); + } + break; + case GL_BOOL: + glGetUniformivARB(programObj, location, ivalues); + for (j = 0; j < numElems; ++j) { + json.writeBool(ivalues[j]); + } + break; + default: + assert(0); + break; + } + + if (numElems > 1) { + json.endArray(); + } + + json.endMember(); + } +} + + +static inline void +dumpProgramUniforms(JSONWriter &json, GLint program) +{ + GLint active_uniforms = 0; + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &active_uniforms); + if (!active_uniforms) { + return; + } + + GLint active_uniform_max_length = 0; + glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &active_uniform_max_length); + GLchar *name = new GLchar[active_uniform_max_length]; + if (!name) { + return; + } + + for (GLint index = 0; index < active_uniforms; ++index) { + GLsizei length = 0; + GLint size = 0; + GLenum type = GL_NONE; + glGetActiveUniform(program, index, active_uniform_max_length, &length, &size, &type, name); + + dumpUniform(json, program, size, type, name); + } + + delete [] name; +} + + +static inline void +dumpProgramObjUniforms(JSONWriter &json, GLhandleARB programObj) +{ + GLint active_uniforms = 0; + glGetObjectParameterivARB(programObj, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &active_uniforms); + if (!active_uniforms) { + return; + } + + GLint active_uniform_max_length = 0; + glGetObjectParameterivARB(programObj, GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB, &active_uniform_max_length); + GLchar *name = new GLchar[active_uniform_max_length]; + if (!name) { + return; + } + + for (GLint index = 0; index < active_uniforms; ++index) { + GLsizei length = 0; + GLint size = 0; + GLenum type = GL_NONE; + glGetActiveUniformARB(programObj, index, active_uniform_max_length, &length, &size, &type, name); + + dumpUniformARB(json, programObj, size, type, name); + } + + delete [] name; +} + + +static inline void +dumpArbProgram(JSONWriter &json, GLenum target) +{ + if (!glIsEnabled(target)) { + return; + } + + GLint program_length = 0; + glGetProgramivARB(target, GL_PROGRAM_LENGTH_ARB, &program_length); + if (!program_length) { + return; + } + + GLchar *source = new GLchar[program_length + 1]; + source[0] = 0; + glGetProgramStringARB(target, GL_PROGRAM_STRING_ARB, source); + source[program_length] = 0; + + json.beginMember(enumToString(target)); + json.writeString(source); + json.endMember(); + + delete [] source; +} + + +static inline void +dumpArbProgramUniforms(JSONWriter &json, GLenum target, const char *prefix) +{ + if (!glIsEnabled(target)) { + return; + } + + GLint program_parameters = 0; + glGetProgramivARB(target, GL_PROGRAM_PARAMETERS_ARB, &program_parameters); + if (!program_parameters) { + return; + } + + GLint max_program_local_parameters = 0; + glGetProgramivARB(target, GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB, &max_program_local_parameters); + for (GLint index = 0; index < max_program_local_parameters; ++index) { + GLdouble params[4] = {0, 0, 0, 0}; + glGetProgramLocalParameterdvARB(target, index, params); + + if (!params[0] && !params[1] && !params[2] && !params[3]) { + continue; + } + + char name[256]; + snprintf(name, sizeof name, "%sprogram.local[%i]", prefix, index); + + json.beginMember(name); + json.beginArray(); + json.writeNumber(params[0]); + json.writeNumber(params[1]); + json.writeNumber(params[2]); + json.writeNumber(params[3]); + json.endArray(); + json.endMember(); + } + + GLint max_program_env_parameters = 0; + glGetProgramivARB(target, GL_MAX_PROGRAM_ENV_PARAMETERS_ARB, &max_program_env_parameters); + for (GLint index = 0; index < max_program_env_parameters; ++index) { + GLdouble params[4] = {0, 0, 0, 0}; + glGetProgramEnvParameterdvARB(target, index, params); + + if (!params[0] && !params[1] && !params[2] && !params[3]) { + continue; + } + + char name[256]; + snprintf(name, sizeof name, "%sprogram.env[%i]", prefix, index); + + json.beginMember(name); + json.beginArray(); + json.writeNumber(params[0]); + json.writeNumber(params[1]); + json.writeNumber(params[2]); + json.writeNumber(params[3]); + json.endArray(); + json.endMember(); + } +} + + +void +dumpShadersUniforms(JSONWriter &json) +{ + GLint program = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, &program); + + GLhandleARB programObj = glGetHandleARB(GL_PROGRAM_OBJECT_ARB); + + json.beginMember("shaders"); + json.beginObject(); + if (program) { + dumpProgram(json, program); + } else if (programObj) { + dumpProgramObj(json, programObj); + } else { + dumpArbProgram(json, GL_FRAGMENT_PROGRAM_ARB); + dumpArbProgram(json, GL_VERTEX_PROGRAM_ARB); + } + json.endObject(); + json.endMember(); // shaders + + json.beginMember("uniforms"); + json.beginObject(); + if (program) { + dumpProgramUniforms(json, program); + } else if (programObj) { + dumpProgramObjUniforms(json, programObj); + } else { + dumpArbProgramUniforms(json, GL_FRAGMENT_PROGRAM_ARB, "fp."); + dumpArbProgramUniforms(json, GL_VERTEX_PROGRAM_ARB, "vp."); + } + json.endObject(); + json.endMember(); // uniforms +} + + +} /* namespace glstate */ diff --git a/glws_wgl.cpp b/glws_wgl.cpp index 5fddb79..efefff0 100644 --- a/glws_wgl.cpp +++ b/glws_wgl.cpp @@ -23,6 +23,12 @@ * **************************************************************************/ + +/* + * WGL bindings. + */ + + #include #include "glproc.hpp" @@ -32,6 +38,27 @@ namespace glws { +/* + * Several WGL functions come in two flavors: + * - GDI (ChoosePixelFormat, SetPixelFormat, SwapBuffers, etc) + * - WGL (wglChoosePixelFormat, wglSetPixelFormat, wglSwapBuffers, etc) + * + * The GDI entrypoints will inevitably dispatch to the first module named + * "OPENGL32", loading "C:\Windows\System32\opengl32.dll" if none was loaded so + * far. + * + * In order to use a implementation other than the one installed in the system + * (when specified via the TRACE_LIBGL environment variable), we need to use + * WGL entrypoints. + * + * See also: + * - http://www.opengl.org/archives/resources/faq/technical/mswindows.htm + */ +static __PFNWGLCHOOSEPIXELFORMAT pfnChoosePixelFormat = &ChoosePixelFormat; +static __PFNWGLSETPIXELFORMAT pfnSetPixelFormat = &SetPixelFormat; +static __PFNWGLSWAPBUFFERS pfnSwapBuffers = &SwapBuffers; + + static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { @@ -68,6 +95,7 @@ public: { static bool first = TRUE; RECT rect; + BOOL bRet; if (first) { WNDCLASS wc; @@ -126,9 +154,17 @@ public: pfd.dwFlags |= PFD_DOUBLEBUFFER; } - iPixelFormat = ChoosePixelFormat(hDC, &pfd); + iPixelFormat = pfnChoosePixelFormat(hDC, &pfd); + if (iPixelFormat <= 0) { + std::cerr << "error: ChoosePixelFormat failed\n"; + exit(1); + } - SetPixelFormat(hDC, iPixelFormat, &pfd); + bRet = pfnSetPixelFormat(hDC, iPixelFormat, &pfd); + if (!bRet) { + std::cerr << "error: SetPixelFormat failed\n"; + exit(1); + } } ~WglDrawable() { @@ -163,7 +199,11 @@ public: } void swapBuffers(void) { - SwapBuffers(hDC); + BOOL bRet; + bRet = pfnSwapBuffers(hDC); + if (!bRet) { + std::cerr << "warning: SwapBuffers failed\n"; + } // Drain message queue to prevent window from being considered // non-responsive @@ -204,7 +244,11 @@ init(void) { const char * libgl_filename = getenv("TRACE_LIBGL"); - if (!libgl_filename) { + if (libgl_filename) { + pfnChoosePixelFormat = &wglChoosePixelFormat; + pfnSetPixelFormat = &wglSetPixelFormat; + pfnSwapBuffers = &wglSwapBuffers; + } else { libgl_filename = "OPENGL32"; } @@ -260,11 +304,17 @@ makeCurrent(Drawable *drawable, Context *context) if (!wglContext->hglrc) { wglContext->hglrc = wglCreateContext(wglDrawable->hDC); if (!wglContext->hglrc) { + std::cerr << "error: wglCreateContext failed\n"; + exit(1); return false; } if (wglContext->shareContext) { - wglShareLists(wglContext->shareContext->hglrc, - wglContext->hglrc); + BOOL bRet; + bRet = wglShareLists(wglContext->shareContext->hglrc, + wglContext->hglrc); + if (!bRet) { + std::cerr << "warning: wglShareLists failed\n"; + } } } diff --git a/gui/apicalldelegate.cpp b/gui/apicalldelegate.cpp index 11ed3a5..9ad9f24 100644 --- a/gui/apicalldelegate.cpp +++ b/gui/apicalldelegate.cpp @@ -2,6 +2,7 @@ #include "apitracecall.h" #include "apitracemodel.h" +#include "thumbnail.h" #include #include @@ -27,35 +28,47 @@ void ApiCallDelegate::paint(QPainter *painter, Q_ASSERT(index.column() == 0); if (event) { - QPoint offset; + QPoint offset = option.rect.topLeft(); QStaticText text = event->staticText(); + QSize textSize = text.size().toSize(); //text.setTextWidth(option.rect.width()); //QStyledItemDelegate::paint(painter, option, index); QStyle *style = QApplication::style(); style->drawControl(QStyle::CE_ItemViewItem, &option, painter, 0); + + // draw thumbnail of frame + if (event->type() == ApiTraceEvent::Frame) { + ApiTraceFrame *frame = static_cast(event); + const QImage & thumbnail = frame->thumbnail(); + if (!thumbnail.isNull()) { + painter->drawImage(offset, thumbnail); + offset += QPoint(textSize.height() + thumbnail.width(), option.rect.height()/2 - textSize.height()/2); + } + } + if (event->hasState()) { - QPixmap px = m_stateEmblem.pixmap(option.rect.height(), - option.rect.height()); + QPixmap px = m_stateEmblem.pixmap(textSize.height(), + textSize.height()); painter->drawPixmap(option.rect.topLeft(), px); - offset = QPoint(option.rect.height() + 5, 0); + offset += QPoint(textSize.height() + 5, 0); } if (event->type() == ApiTraceEvent::Call) { ApiTraceCall *call = static_cast(event); if (call->hasError()) { - QPixmap px = m_errorEmblem.pixmap(option.rect.height(), - option.rect.height()); - painter->drawPixmap(option.rect.topLeft() + offset, px); - offset += QPoint(option.rect.height() + 5, 0); + QPixmap px = m_errorEmblem.pixmap(textSize.height(), + textSize.height()); + painter->drawPixmap(offset, px); + offset += QPoint(textSize.height() + 5, 0); } if (call->edited()) { - QPixmap px = m_editEmblem.pixmap(option.rect.height(), - option.rect.height()); - painter->drawPixmap(option.rect.topLeft() + offset, px); - offset += QPoint(option.rect.height() + 5, 0); + QPixmap px = m_editEmblem.pixmap(textSize.height(), + textSize.height()); + painter->drawPixmap(offset, px); + offset += QPoint(textSize.height() + 5, 0); } } - painter->drawStaticText(option.rect.topLeft() + offset, text); + painter->drawStaticText(offset, text); } else { QStyledItemDelegate::paint(painter, option, index); } @@ -64,8 +77,8 @@ void ApiCallDelegate::paint(QPainter *painter, QSize ApiCallDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - ApiTraceEvent *event = - index.data(ApiTraceModel::EventRole).value(); + QVariant var = index.data(ApiTraceModel::EventRole); + ApiTraceEvent *event = var.value(); #ifndef __APPLE__ /* XXX: This fails on MacOSX, but seems safe to ignore */ @@ -76,7 +89,21 @@ QSize ApiCallDelegate::sizeHint(const QStyleOptionViewItem &option, QStaticText text = event->staticText(); //text.setTextWidth(option.rect.width()); //qDebug()<<"text size = "<type() == ApiTraceEvent::Frame) { + ApiTraceFrame *frame = static_cast(event); + const QImage & thumbnail = frame->thumbnail(); + if (!thumbnail.isNull()) { + size.rwidth() += thumbnail.width(); + if (size.height() < thumbnail.height()) { + size.setHeight(thumbnail.height()); + } + } + } + + return size; } return QStyledItemDelegate::sizeHint(option, index); } diff --git a/gui/apisurface.cpp b/gui/apisurface.cpp index 7bf3c8f..bfc6cfd 100644 --- a/gui/apisurface.cpp +++ b/gui/apisurface.cpp @@ -1,4 +1,5 @@ #include "apisurface.h" +#include "thumbnail.h" #include #include @@ -31,7 +32,7 @@ void ApiSurface::contentsFromBase64(const QByteArray &base64) { QByteArray dataArray = QByteArray::fromBase64(base64); m_image.loadFromData(dataArray, "png"); - m_thumb = m_image.scaled(64, 64, Qt::KeepAspectRatio); + m_thumb = thumbnail(m_image); } QImage ApiSurface::image() const diff --git a/gui/apitrace.cpp b/gui/apitrace.cpp index 5758b07..b11c17c 100644 --- a/gui/apitrace.cpp +++ b/gui/apitrace.cpp @@ -8,8 +8,7 @@ #include ApiTrace::ApiTrace() - : m_frameMarker(ApiTrace::FrameMarker_SwapBuffers), - m_needsSaving(false) + : m_needsSaving(false) { m_loader = new TraceLoader(); @@ -23,6 +22,8 @@ ApiTrace::ApiTrace() SIGNAL(frameContentsLoaded(ApiTraceFrame*,QVector,quint64)), this, SLOT(loaderFrameLoaded(ApiTraceFrame*,QVector,quint64))); + connect(m_loader, SIGNAL(guessedApi(int)), + this, SLOT(guessedApi(int))); connect(m_loader, SIGNAL(finishedParsing()), this, SLOT(finishedParsing())); connect(this, SIGNAL(loaderSearch(ApiTrace::SearchRequest)), @@ -73,29 +74,6 @@ ApiTrace::~ApiTrace() delete m_saver; } -bool ApiTrace::isCallAFrameMarker(const ApiTraceCall *call, - ApiTrace::FrameMarker marker) -{ - if (!call) { - return false; - } - - switch (marker) { - case FrameMarker_SwapBuffers: - return call->flags() & trace::CALL_FLAG_END_FRAME; - case FrameMarker_Flush: - return call->name() == QLatin1String("glFlush"); - case FrameMarker_Finish: - return call->name() == QLatin1String("glFinish"); - case FrameMarker_Clear: - return call->name() == QLatin1String("glClear"); - } - - Q_ASSERT(!"unknown frame marker"); - - return false; -} - bool ApiTrace::isEmpty() const { return m_frames.isEmpty(); @@ -110,12 +88,7 @@ QString ApiTrace::fileName() const return m_fileName; } -ApiTrace::FrameMarker ApiTrace::frameMarker() const -{ - return m_frameMarker; -} - -QList ApiTrace::frames() const +const QList & ApiTrace::frames() const { return m_frames; } @@ -269,11 +242,23 @@ void ApiTrace::loadFrame(ApiTraceFrame *frame) } } +void ApiTrace::guessedApi(int api) +{ + m_api = static_cast(api); +} + +trace::API ApiTrace::api() const +{ + return m_api; +} + void ApiTrace::finishedParsing() { - ApiTraceFrame *firstFrame = m_frames[0]; - if (firstFrame && !firstFrame->isLoaded()) { - loadFrame(firstFrame); + if (!m_frames.isEmpty()) { + ApiTraceFrame *firstFrame = m_frames[0]; + if (firstFrame && !firstFrame->isLoaded()) { + loadFrame(firstFrame); + } } } @@ -450,7 +435,7 @@ int ApiTrace::callInFrame(int callIdx) const { unsigned numCalls = 0; - for (int frameIdx = 0; frameIdx <= m_frames.size(); ++frameIdx) { + for (int frameIdx = 0; frameIdx < m_frames.size(); ++frameIdx) { const ApiTraceFrame *frame = m_frames[frameIdx]; unsigned numCallsInFrame = frame->isLoaded() ? frame->numChildren() @@ -496,4 +481,21 @@ bool ApiTrace::isFrameLoading(ApiTraceFrame *frame) const return m_loadingFrames.contains(frame); } +void ApiTrace::bindThumbnailsToFrames(const QList &thumbnails) +{ + QList frames = m_frames; + + QList::const_iterator thumbnail = thumbnails.begin(); + + foreach (ApiTraceFrame *frame, frames) { + if (thumbnail != thumbnails.end()) { + frame->setThumbnail(*thumbnail); + + ++thumbnail; + + emit changed(frame); + } + } +} + #include "apitrace.moc" diff --git a/gui/apitrace.h b/gui/apitrace.h index 2833f60..04e295c 100644 --- a/gui/apitrace.h +++ b/gui/apitrace.h @@ -3,6 +3,8 @@ #include "apitracecall.h" +#include "trace_api.hpp" + #include #include @@ -14,12 +16,6 @@ class ApiTrace : public QObject { Q_OBJECT public: - enum FrameMarker { - FrameMarker_SwapBuffers, - FrameMarker_Flush, - FrameMarker_Finish, - FrameMarker_Clear - }; enum SearchResult { SearchResult_NotFound, SearchResult_Found, @@ -51,8 +47,6 @@ public: Qt::CaseSensitivity cs; }; - static bool isCallAFrameMarker(const ApiTraceCall *call, - FrameMarker marker); public: ApiTrace(); ~ApiTrace(); @@ -61,13 +55,11 @@ public: QString fileName() const; - FrameMarker frameMarker() const; - ApiTraceState defaultState() const; ApiTraceCall *callWithIndex(int idx) const; - QList frames() const; + const QList & frames() const; ApiTraceFrame *frameAt(int idx) const; int numFrames() const; int numCallsInFrame(int idx) const; @@ -83,6 +75,8 @@ public: bool hasErrors() const; + trace::API api() const; + public slots: void setFileName(const QString &name); void save(); @@ -100,6 +94,7 @@ public slots: void findCallIndex(int index); void setCallError(const ApiTraceError &error); + void bindThumbnailsToFrames(const QList &thumbnails); signals: void loadTrace(const QString &name); @@ -109,7 +104,7 @@ signals: void finishedLoadingTrace(); void invalidated(); void framesInvalidated(); - void changed(ApiTraceCall *call); + void changed(ApiTraceEvent *event); void startedSaving(); void saved(); void findResult(const ApiTrace::SearchRequest &request, @@ -133,6 +128,7 @@ signals: private slots: void addFrames(const QList &frames); void slotSaved(); + void guessedApi(int api); void finishedParsing(); void loaderFrameLoaded(ApiTraceFrame *frame, const QVector &calls, @@ -149,8 +145,7 @@ private: QString m_tempFileName; QList m_frames; - - FrameMarker m_frameMarker; + trace::API m_api; TraceLoader *m_loader; QThread *m_loaderThread; diff --git a/gui/apitracecall.cpp b/gui/apitracecall.cpp index 267dc09..dfaaeef 100644 --- a/gui/apitracecall.cpp +++ b/gui/apitracecall.cpp @@ -1197,3 +1197,13 @@ unsigned ApiTraceFrame::lastCallIndex() const return m_lastCallIndex; } } + +void ApiTraceFrame::setThumbnail(const QImage & thumbnail) +{ + m_thumbnail = thumbnail; +} + +const QImage & ApiTraceFrame::thumbnail() const +{ + return m_thumbnail; +} diff --git a/gui/apitracecall.h b/gui/apitracecall.h index 3a9faaf..945ab0c 100644 --- a/gui/apitracecall.h +++ b/gui/apitracecall.h @@ -335,6 +335,10 @@ public: void setLastCallIndex(unsigned index); unsigned lastCallIndex() const; + + void setThumbnail(const QImage & thumbnail); + const QImage & thumbnail() const; + private: ApiTrace *m_parentTrace; quint64 m_binaryDataSize; @@ -342,6 +346,7 @@ private: bool m_loaded; unsigned m_callsToLoad; unsigned m_lastCallIndex; + QImage m_thumbnail; }; Q_DECLARE_METATYPE(ApiTraceFrame*); diff --git a/gui/apitracemodel.cpp b/gui/apitracemodel.cpp index 7303ae1..0863c1b 100644 --- a/gui/apitracemodel.cpp +++ b/gui/apitracemodel.cpp @@ -248,8 +248,8 @@ void ApiTraceModel::setApiTrace(ApiTrace *trace) this, SLOT(beginAddingFrames(int, int))); connect(m_trace, SIGNAL(endAddingFrames()), this, SLOT(endAddingFrames())); - connect(m_trace, SIGNAL(changed(ApiTraceCall*)), - this, SLOT(callChanged(ApiTraceCall*))); + connect(m_trace, SIGNAL(changed(ApiTraceEvent*)), + this, SLOT(changed(ApiTraceEvent*))); connect(m_trace, SIGNAL(beginLoadingFrame(ApiTraceFrame*,int)), this, SLOT(beginLoadingFrame(ApiTraceFrame*,int))); connect(m_trace, SIGNAL(endLoadingFrame(ApiTraceFrame*)), @@ -294,7 +294,7 @@ void ApiTraceModel::stateSetOnEvent(ApiTraceEvent *event) emit dataChanged(index, index); } else if (event->type() == ApiTraceEvent::Frame) { ApiTraceFrame *frame = static_cast(event); - const QList frames = m_trace->frames(); + const QList & frames = m_trace->frames(); int row = frames.indexOf(frame); QModelIndex index = createIndex(row, 0, frame); emit dataChanged(index, index); @@ -318,6 +318,15 @@ QModelIndex ApiTraceModel::indexForCall(ApiTraceCall *call) const return createIndex(row, 0, call); } +void ApiTraceModel::changed(ApiTraceEvent *event) +{ + if (event->type() == ApiTraceEvent::Call) { + callChanged(static_cast(event)); + } else if (event->type() == ApiTraceEvent::Frame) { + frameChanged(static_cast(event)); + } +} + void ApiTraceModel::callChanged(ApiTraceCall *call) { ApiTrace *trace = call->parentFrame()->parentTrace(); @@ -339,6 +348,14 @@ void ApiTraceModel::callChanged(ApiTraceCall *call) emit dataChanged(index, index); } +void ApiTraceModel::frameChanged(ApiTraceFrame *frame) +{ + const QList & frames = m_trace->frames(); + int row = frames.indexOf(frame); + QModelIndex index = createIndex(row, 0, frame); + emit dataChanged(index, index); +} + void ApiTraceModel::endAddingFrames() { endInsertRows(); diff --git a/gui/apitracemodel.h b/gui/apitracemodel.h index fe6b5ce..e7354aa 100644 --- a/gui/apitracemodel.h +++ b/gui/apitracemodel.h @@ -54,7 +54,9 @@ private slots: void invalidateFrames(); void beginAddingFrames(int oldCount, int numAdded); void endAddingFrames(); + void changed(ApiTraceEvent *event); void callChanged(ApiTraceCall *call); + void frameChanged(ApiTraceFrame *frame); void beginLoadingFrame(ApiTraceFrame *frame, int numAdded); void endLoadingFrame(ApiTraceFrame *frame); diff --git a/gui/imageviewer.cpp b/gui/imageviewer.cpp index 4a11da5..7b1e425 100644 --- a/gui/imageviewer.cpp +++ b/gui/imageviewer.cpp @@ -1,5 +1,6 @@ #include "imageviewer.h" +#include #include #include #include @@ -10,6 +11,15 @@ ImageViewer::ImageViewer(QWidget *parent) { setupUi(this); + connect(lowerSpinBox, SIGNAL(valueChanged(double)), + SLOT(slotUpdate())); + connect(upperSpinBox, SIGNAL(valueChanged(double)), + SLOT(slotUpdate())); + connect(flipCheckBox, SIGNAL(stateChanged(int)), + SLOT(slotUpdate())); + connect(opaqueCheckBox, SIGNAL(stateChanged(int)), + SLOT(slotUpdate())); + QPixmap px(32, 32); QPainter p(&px); p.fillRect(0, 0, 32, 32, Qt::white); @@ -27,8 +37,76 @@ ImageViewer::ImageViewer(QWidget *parent) void ImageViewer::setImage(const QImage &image) { m_image = image; - QPixmap px = QPixmap::fromImage(image); + m_temp = m_image; + QPixmap px = QPixmap::fromImage(m_temp); + imageLabel->setPixmap(px); + updateGeometry(); +} + +static inline int clamp(int x) +{ + if (x <= 0) { + return 0; + } + if (x > 255) { + return 255; + } + return x; +} + +void ImageViewer::slotUpdate() +{ + m_temp = m_image.mirrored(false, flipCheckBox->isChecked()); + + double lowerValue = lowerSpinBox->value(); + double upperValue = upperSpinBox->value(); + + bool opaque = opaqueCheckBox->isChecked(); + + if (lowerValue != 0.0 || upperValue != 1.0 || opaque) { + /* + * Rescale the image. + * + * XXX: This would be much more useful if done with the full precision + * of the original image + */ + + int offset = - lowerValue * 255; + int scale = 256 / (upperValue - lowerValue); + + m_temp = m_temp.convertToFormat(QImage::Format_ARGB32); + + if (0) { + qDebug() + << "offset = " << offset << "\n" + << "scale = " << scale << "\n"; + } + + int width = m_temp.width(); + int height = m_temp.height(); + + int aMask = opaque ? 0xff : 0; + + for (int y = 0; y < height; ++y) { + QRgb *scanline = (QRgb *)m_temp.scanLine(y); + for (int x = 0; x < width; ++x) { + QRgb pixel = scanline[x]; + int r = qRed(pixel); + int g = qGreen(pixel); + int b = qBlue(pixel); + int a = qAlpha(pixel); + r = clamp(((r + offset) * scale) >> 8); + g = clamp(((g + offset) * scale) >> 8); + b = clamp(((b + offset) * scale) >> 8); + a |= aMask; + scanline[x] = qRgba(r, g, b, a); + } + } + } + + QPixmap px = QPixmap::fromImage(m_temp); imageLabel->setPixmap(px); + updateGeometry(); } diff --git a/gui/imageviewer.h b/gui/imageviewer.h index 16bc7f2..e878118 100644 --- a/gui/imageviewer.h +++ b/gui/imageviewer.h @@ -13,8 +13,13 @@ public: void setImage(const QImage &image); QSize sizeHint() const; + +private slots: + void slotUpdate(); + private: QImage m_image; + QImage m_temp; }; diff --git a/gui/main.cpp b/gui/main.cpp index d7af53d..18e07f1 100644 --- a/gui/main.cpp +++ b/gui/main.cpp @@ -6,12 +6,14 @@ #include #include #include +#include Q_DECLARE_METATYPE(QList); Q_DECLARE_METATYPE(QVector); Q_DECLARE_METATYPE(Qt::CaseSensitivity); Q_DECLARE_METATYPE(ApiTrace::SearchResult); Q_DECLARE_METATYPE(ApiTrace::SearchRequest); +Q_DECLARE_METATYPE(QList); static void usage(void) { @@ -28,6 +30,7 @@ int main(int argc, char **argv) qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType >(); QStringList args = app.arguments(); int i = 1; diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 039889c..5e2a55d 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -14,6 +14,7 @@ #include "shaderssourcewidget.h" #include "tracedialog.h" #include "traceprocess.h" +#include "thumbnail.h" #include "ui_retracerdialog.h" #include "vertexdatainterpreter.h" @@ -176,7 +177,7 @@ void MainWindow::replayStart() dlgUi.doubleBufferingCB->isChecked()); m_retracer->setBenchmarking( !dlgUi.errorCheckCB->isChecked()); - replayTrace(false); + replayTrace(false, true); } } @@ -186,6 +187,7 @@ void MainWindow::replayStop() m_ui.actionStop->setEnabled(false); m_ui.actionReplay->setEnabled(true); m_ui.actionLookupState->setEnabled(true); + m_ui.actionShowThumbnails->setEnabled(true); } void MainWindow::newTraceFile(const QString &fileName) @@ -198,35 +200,33 @@ void MainWindow::newTraceFile(const QString &fileName) if (fileName.isEmpty()) { m_ui.actionReplay->setEnabled(false); m_ui.actionLookupState->setEnabled(false); + m_ui.actionShowThumbnails->setEnabled(false); setWindowTitle(tr("QApiTrace")); } else { QFileInfo info(fileName); m_ui.actionReplay->setEnabled(true); m_ui.actionLookupState->setEnabled(true); + m_ui.actionShowThumbnails->setEnabled(true); setWindowTitle( tr("QApiTrace - %1").arg(info.fileName())); } } -void MainWindow::replayFinished(const QString &output) +void MainWindow::replayFinished(const QString &message) { m_ui.actionStop->setEnabled(false); m_ui.actionReplay->setEnabled(true); m_ui.actionLookupState->setEnabled(true); + m_ui.actionShowThumbnails->setEnabled(true); m_progressBar->hide(); - if (output.length() < 80) { - statusBar()->showMessage(output); - } + statusBar()->showMessage(message, 2000); m_stateEvent = 0; m_ui.actionShowErrorsDock->setEnabled(m_trace->hasErrors()); m_ui.errorsDock->setVisible(m_trace->hasErrors()); if (!m_trace->hasErrors()) { m_ui.errorsTreeWidget->clear(); } - - statusBar()->showMessage( - tr("Replaying finished!"), 2000); } void MainWindow::replayError(const QString &message) @@ -234,6 +234,7 @@ void MainWindow::replayError(const QString &message) m_ui.actionStop->setEnabled(false); m_ui.actionReplay->setEnabled(true); m_ui.actionLookupState->setEnabled(true); + m_ui.actionShowThumbnails->setEnabled(true); m_stateEvent = 0; m_nonDefaultsLookupEvent = 0; @@ -259,6 +260,7 @@ void MainWindow::finishedLoadingTrace() if (!m_trace) { return; } + m_api = m_trace->api(); QFileInfo info(m_trace->fileName()); statusBar()->showMessage( tr("Loaded %1").arg(info.fileName()), 3000); @@ -268,7 +270,7 @@ void MainWindow::finishedLoadingTrace() } } -void MainWindow::replayTrace(bool dumpState) +void MainWindow::replayTrace(bool dumpState, bool dumpThumbnails) { if (m_trace->fileName().isEmpty()) { return; @@ -277,6 +279,7 @@ void MainWindow::replayTrace(bool dumpState) m_retracer->setFileName(m_trace->fileName()); m_retracer->setAPI(m_api); m_retracer->setCaptureState(dumpState); + m_retracer->setCaptureThumbnails(dumpThumbnails); if (m_retracer->captureState() && m_selectedEvent) { int index = 0; if (m_selectedEvent->type() == ApiTraceEvent::Call) { @@ -300,9 +303,17 @@ void MainWindow::replayTrace(bool dumpState) m_ui.actionStop->setEnabled(true); m_progressBar->show(); - if (dumpState) { - statusBar()->showMessage( - tr("Looking up the state...")); + if (dumpState || dumpThumbnails) { + if (dumpState && dumpThumbnails) { + statusBar()->showMessage( + tr("Looking up the state and capturing thumbnails...")); + } else if (dumpState) { + statusBar()->showMessage( + tr("Looking up the state...")); + } else if (dumpThumbnails) { + statusBar()->showMessage( + tr("Capturing thumbnails...")); + } } else { statusBar()->showMessage( tr("Replaying the trace file...")); @@ -326,7 +337,12 @@ void MainWindow::lookupState() return; } m_stateEvent = m_selectedEvent; - replayTrace(true); + replayTrace(true, false); +} + +void MainWindow::showThumbnails() +{ + replayTrace(false, true); } MainWindow::~MainWindow() @@ -535,7 +551,7 @@ void MainWindow::fillStateForFrame() if (textures.isEmpty() && fbos.isEmpty()) { m_ui.surfacesTab->setDisabled(false); } else { - m_ui.surfacesTreeWidget->setIconSize(QSize(64, 64)); + m_ui.surfacesTreeWidget->setIconSize(QSize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)); if (!textures.isEmpty()) { QTreeWidgetItem *textureItem = new QTreeWidgetItem(m_ui.surfacesTreeWidget); @@ -724,8 +740,8 @@ void MainWindow::initConnections() this, SLOT(slotStartedSaving())); connect(m_trace, SIGNAL(saved()), this, SLOT(slotSaved())); - connect(m_trace, SIGNAL(changed(ApiTraceCall*)), - this, SLOT(slotTraceChanged(ApiTraceCall*))); + connect(m_trace, SIGNAL(changed(ApiTraceEvent*)), + this, SLOT(slotTraceChanged(ApiTraceEvent*))); connect(m_trace, SIGNAL(findResult(ApiTrace::SearchRequest,ApiTrace::SearchResult,ApiTraceCall*)), this, SLOT(slotSearchResult(ApiTrace::SearchRequest,ApiTrace::SearchResult,ApiTraceCall*))); connect(m_trace, SIGNAL(foundFrameStart(ApiTraceFrame*)), @@ -741,6 +757,8 @@ void MainWindow::initConnections() this, SLOT(replayError(const QString&))); connect(m_retracer, SIGNAL(foundState(ApiTraceState*)), this, SLOT(replayStateFound(ApiTraceState*))); + connect(m_retracer, SIGNAL(foundThumbnails(const QList&)), + this, SLOT(replayThumbnailsFound(const QList&))); connect(m_retracer, SIGNAL(retraceErrors(const QList&)), this, SLOT(slotRetraceErrors(const QList&))); @@ -778,6 +796,8 @@ void MainWindow::initConnections() this, SLOT(replayStop())); connect(m_ui.actionLookupState, SIGNAL(triggered()), this, SLOT(lookupState())); + connect(m_ui.actionShowThumbnails, SIGNAL(triggered()), + this, SLOT(showThumbnails())); connect(m_ui.actionOptions, SIGNAL(triggered()), this, SLOT(showSettings())); @@ -838,6 +858,12 @@ void MainWindow::replayStateFound(ApiTraceState *state) m_nonDefaultsLookupEvent = 0; } +void MainWindow::replayThumbnailsFound(const QList &thumbnails) +{ + m_ui.callView->setUniformRowHeights(false); + m_trace->bindThumbnailsToFrames(thumbnails); +} + void MainWindow::slotGoTo() { m_searchWidget->hide(); @@ -1013,11 +1039,14 @@ ApiTraceFrame * MainWindow::selectedFrame() const return NULL; } -void MainWindow::slotTraceChanged(ApiTraceCall *call) +void MainWindow::slotTraceChanged(ApiTraceEvent *event) { - Q_ASSERT(call); - if (call == m_selectedEvent) { - m_ui.detailsWebView->setHtml(call->toHtml()); + Q_ASSERT(event); + if (event == m_selectedEvent) { + if (event->type() == ApiTraceEvent::Call) { + ApiTraceCall *call = static_cast(event); + m_ui.detailsWebView->setHtml(call->toHtml()); + } } } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 66be2e2..a8f8c1d 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -8,6 +8,8 @@ #include #include +#include +#include class ApiTrace; class ApiTraceCall; @@ -46,13 +48,15 @@ private slots: void openTrace(); void replayStart(); void replayStop(); - void replayFinished(const QString &output); + void replayFinished(const QString &message); void replayStateFound(ApiTraceState *state); + void replayThumbnailsFound(const QList &thumbnails); void replayError(const QString &msg); void startedLoadingTrace(); void loadProgess(int percent); void finishedLoadingTrace(); void lookupState(); + void showThumbnails(); void showSettings(); void openHelp(const QUrl &url); void showSurfacesMenu(const QPoint &pos); @@ -72,7 +76,7 @@ private slots: void slotSaved(); void slotGoFrameStart(); void slotGoFrameEnd(); - void slotTraceChanged(ApiTraceCall *call); + void slotTraceChanged(ApiTraceEvent *event); void slotRetraceErrors(const QList &errors); void slotErrorSelected(QTreeWidgetItem *current); void slotSearchResult(const ApiTrace::SearchRequest &request, @@ -86,7 +90,7 @@ private: void initObjects(); void initConnections(); void newTraceFile(const QString &fileName); - void replayTrace(bool dumpState); + void replayTrace(bool dumpState, bool dumpThumbnails); void fillStateForFrame(); /* there's a difference between selected frame/call and diff --git a/gui/retracer.cpp b/gui/retracer.cpp index 17ac14d..84f9742 100644 --- a/gui/retracer.cpp +++ b/gui/retracer.cpp @@ -1,12 +1,129 @@ #include "retracer.h" #include "apitracecall.h" +#include "thumbnail.h" + +#include "image.hpp" #include #include +#include +#include #include +/** + * Wrapper around a QProcess which enforces IO to block . + * + * Several QIODevice users (notably QJSON) expect blocking semantics, e.g., + * they expect that QIODevice::read() will blocked until the requested ammount + * of bytes is read or end of file is reached. But by default QProcess, does + * not block. And passing QIODevice::Unbuffered mitigates but does not fully + * address the problem either. + * + * This class wraps around QProcess, providing QIODevice interface, while + * ensuring that all reads block. + * + * This class also works around a bug in QProcess::atEnd() implementation. + * + * See also: + * - http://qt-project.org/wiki/Simple_Crypt_IO_Device + * - http://qt-project.org/wiki/Custom_IO_Device + */ +class BlockingIODevice : public QIODevice +{ + /* We don't use the Q_OBJECT in this class given we don't declare any + * signals and slots or use any other services provided by Qt's meta-object + * system. */ +public: + BlockingIODevice(QProcess * io); + bool isSequential() const; + bool atEnd() const; + bool waitForReadyRead(int msecs = -1); + +protected: + qint64 readData(char * data, qint64 maxSize); + qint64 writeData(const char * data, qint64 maxSize); + +private: + QProcess *m_device; +}; + +BlockingIODevice::BlockingIODevice(QProcess * io) : + m_device(io) +{ + /* + * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do + * its own buffering on top of the overridden readData() method. + * + * The only buffering used will be to satisfy QIODevice::peek() and + * QIODevice::ungetChar(). + */ + setOpenMode(ReadOnly | Unbuffered); +} + +bool BlockingIODevice::isSequential() const +{ + return true; +} + +bool BlockingIODevice::atEnd() const +{ + /* + * XXX: QProcess::atEnd() documentation is wrong -- it will return true + * even when the process is running --, so we try to workaround that here. + */ + if (m_device->atEnd()) { + if (m_device->state() == QProcess::Running) { + if (!m_device->waitForReadyRead(-1)) { + return true; + } + } + } + return false; +} + +bool BlockingIODevice::waitForReadyRead(int msecs) +{ + Q_UNUSED(msecs); + return true; +} + +qint64 BlockingIODevice::readData(char * data, qint64 maxSize) +{ + qint64 bytesToRead = maxSize; + qint64 readSoFar = 0; + do { + qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead); + if (chunkSize < 0) { + if (readSoFar) { + return readSoFar; + } else { + return chunkSize; + } + } + Q_ASSERT(chunkSize <= bytesToRead); + bytesToRead -= chunkSize; + readSoFar += chunkSize; + if (bytesToRead) { + if (!m_device->waitForReadyRead(-1)) { + qDebug() << "waitForReadyRead failed\n"; + break; + } + } + } while(bytesToRead); + + return readSoFar; +} + +qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize) +{ + Q_ASSERT(false); + return -1; +} + +Q_DECLARE_METATYPE(QList); + Retracer::Retracer(QObject *parent) : QThread(parent), m_benchmarking(false), @@ -14,6 +131,8 @@ Retracer::Retracer(QObject *parent) m_captureState(false), m_captureCall(0) { + qRegisterMetaType >(); + #ifdef Q_OS_WIN QString format = QLatin1String("%1;"); #else @@ -83,47 +202,30 @@ void Retracer::setCaptureState(bool enable) m_captureState = enable; } - -void Retracer::run() +bool Retracer::captureThumbnails() const { - RetraceProcess *retrace = new RetraceProcess(); - retrace->process()->setProcessEnvironment(m_processEnvironment); - - retrace->setFileName(m_fileName); - retrace->setAPI(m_api); - retrace->setBenchmarking(m_benchmarking); - retrace->setDoubleBuffered(m_doubleBuffered); - retrace->setCaptureState(m_captureState); - retrace->setCaptureAtCallNumber(m_captureCall); - - connect(retrace, SIGNAL(finished(const QString&)), - this, SLOT(cleanup())); - connect(retrace, SIGNAL(error(const QString&)), - this, SLOT(cleanup())); - connect(retrace, SIGNAL(finished(const QString&)), - this, SIGNAL(finished(const QString&))); - connect(retrace, SIGNAL(error(const QString&)), - this, SIGNAL(error(const QString&))); - connect(retrace, SIGNAL(foundState(ApiTraceState*)), - this, SIGNAL(foundState(ApiTraceState*))); - connect(retrace, SIGNAL(retraceErrors(const QList&)), - this, SIGNAL(retraceErrors(const QList&))); - - retrace->start(); - - exec(); - - /* means we need to kill the process */ - if (retrace->process()->state() != QProcess::NotRunning) { - retrace->terminate(); - } + return m_captureThumbnails; +} - delete retrace; +void Retracer::setCaptureThumbnails(bool enable) +{ + m_captureThumbnails = enable; } -void RetraceProcess::start() +/** + * Starting point for the retracing thread. + * + * Overrides QThread::run(). + */ +void Retracer::run() { + QString msg = QLatin1String("Replay finished!"); + + /* + * Construct command line + */ + QString prog; QStringList arguments; @@ -132,7 +234,7 @@ void RetraceProcess::start() } else if (m_api == trace::API_EGL) { prog = QLatin1String("eglretrace"); } else { - assert(0); + emit finished(QLatin1String("Unsupported API")); return; } @@ -145,172 +247,163 @@ void RetraceProcess::start() if (m_captureState) { arguments << QLatin1String("-D"); arguments << QString::number(m_captureCall); - } else { - if (m_benchmarking) { - arguments << QLatin1String("-b"); - } + } else if (m_captureThumbnails) { + arguments << QLatin1String("-s"); // emit snapshots + arguments << QLatin1String("-"); // emit to stdout + } else if (m_benchmarking) { + arguments << QLatin1String("-b"); } arguments << m_fileName; - m_process->start(prog, arguments); -} + /* + * Start the process. + */ + QProcess process; -void RetraceProcess::replayFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - QByteArray output = m_process->readAllStandardOutput(); - QString msg; - QString errStr = m_process->readAllStandardError(); - -#if 0 - qDebug()<<"Process finished = "; - qDebug()<<"\terr = "< thumbnails; + QVariantMap parsedJson; + + process.setReadChannel(QProcess::StandardOutput); + if (process.waitForReadyRead(-1)) { + BlockingIODevice io(&process); - if (exitStatus != QProcess::NormalExit) { - msg = QLatin1String("Process crashed"); - } else if (exitCode != 0) { - msg = QLatin1String("Process exited with non zero exit code"); - } else { if (m_captureState) { + /* + * Parse JSON from the output. + * + * XXX: QJSON expects blocking IO. + * + * XXX: QJSON's scanner is inneficient as it abuses single + * character QIODevice::peek (not cheap), instead of maintaining a + * lookahead character on its own. + */ + bool ok = false; - QVariantMap parsedJson = m_jsonParser->parse(output, &ok).toMap(); - ApiTraceState *state = new ApiTraceState(parsedJson); - emit foundState(state); - msg = tr("State fetched."); - } else { - msg = QString::fromUtf8(output); - } - } + QJson::Parser jsonParser; + parsedJson = jsonParser.parse(&io, &ok).toMap(); + if (!ok) { + msg = QLatin1String("failed to parse JSON"); + } + } else if (m_captureThumbnails) { + /* + * Parse concatenated PNM images from output. + */ - QStringList errorLines = errStr.split('\n'); - QList errors; - QRegExp regexp("(^\\d+): +(\\b\\w+\\b): (.+$)"); - foreach(QString line, errorLines) { - if (regexp.indexIn(line) != -1) { - ApiTraceError error; - error.callIndex = regexp.cap(1).toInt(); - error.type = regexp.cap(2); - error.message = regexp.cap(3); - errors.append(error); - } - } - if (!errors.isEmpty()) { - emit retraceErrors(errors); - } - emit finished(msg); -} + while (!io.atEnd()) { + unsigned channels = 0; + unsigned width = 0; + unsigned height = 0; -void RetraceProcess::replayError(QProcess::ProcessError err) -{ - /* - * XXX: this function is likely unnecessary and should be eliminated given - * that replayFinished is always called, even on errors. - */ + char header[512]; + qint64 headerSize = 0; + int headerLines = 3; // assume no optional comment line -#if 0 - qDebug()<<"Process error = "<); -RetraceProcess::RetraceProcess(QObject *parent) - : QObject(parent) -{ - m_process = new QProcess(this); - m_jsonParser = new QJson::Parser(); + headerSize += headerRead; + } - qRegisterMetaType >(); + const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height); - connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), - this, SLOT(replayFinished(int, QProcess::ExitStatus))); - connect(m_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(replayError(QProcess::ProcessError))); -} + // if invalid PNM header was encountered, ... + if (header == headerEnd) { + qDebug() << "error: invalid snapshot stream encountered"; + break; + } -QProcess * RetraceProcess::process() const -{ - return m_process; -} + // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height"; -QString RetraceProcess::fileName() const -{ - return m_fileName; -} + QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888); -void RetraceProcess::setFileName(const QString &name) -{ - m_fileName = name; -} + int rowBytes = channels * width; + for (int y = 0; y < height; ++y) { + unsigned char *scanLine = snapshot.scanLine(y); + qint64 readBytes = io.read((char *) scanLine, rowBytes); + Q_ASSERT(readBytes == rowBytes); + } -void RetraceProcess::setAPI(trace::API api) -{ - m_api = api; -} + QImage thumb = thumbnail(snapshot); + thumbnails.append(thumb); + } -bool RetraceProcess::isBenchmarking() const -{ - return m_benchmarking; -} + Q_ASSERT(process.state() != QProcess::Running); -void RetraceProcess::setBenchmarking(bool bench) -{ - m_benchmarking = bench; -} + } else { + QByteArray output; + output = process.readAllStandardOutput(); + if (output.length() < 80) { + msg = QString::fromUtf8(output); + } + } + } -bool RetraceProcess::isDoubleBuffered() const -{ - return m_doubleBuffered; -} + /* + * Wait for process termination + */ -void RetraceProcess::setDoubleBuffered(bool db) -{ - m_doubleBuffered = db; -} + process.waitForFinished(-1); -void RetraceProcess::setCaptureAtCallNumber(qlonglong num) -{ - m_captureCall = num; -} + if (process.exitStatus() != QProcess::NormalExit) { + msg = QLatin1String("Process crashed"); + } else if (process.exitCode() != 0) { + msg = QLatin1String("Process exited with non zero exit code"); + } -qlonglong RetraceProcess::captureAtCallNumber() const -{ - return m_captureCall; -} + /* + * Parse errors. + */ -bool RetraceProcess::captureState() const -{ - return m_captureState; -} + QList errors; + process.setReadChannel(QProcess::StandardError); + QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$"); + while (!process.atEnd()) { + QString line = process.readLine(); + if (regexp.indexIn(line) != -1) { + ApiTraceError error; + error.callIndex = regexp.cap(1).toInt(); + error.type = regexp.cap(2); + error.message = regexp.cap(3); + errors.append(error); + } + } -void RetraceProcess::setCaptureState(bool enable) -{ - m_captureState = enable; -} + /* + * Emit signals + */ -void RetraceProcess::terminate() -{ - if (m_process) { - m_process->terminate(); - emit finished(tr("Process terminated.")); + if (m_captureState) { + ApiTraceState *state = new ApiTraceState(parsedJson); + emit foundState(state); + msg = QLatin1String("State fetched."); } -} -void Retracer::cleanup() -{ - quit(); -} + if (m_captureThumbnails && !thumbnails.isEmpty()) { + emit foundThumbnails(thumbnails); + } -RetraceProcess::~RetraceProcess() -{ - delete m_jsonParser; + if (!errors.isEmpty()) { + emit retraceErrors(errors); + } + + emit finished(msg); } #include "retracer.moc" diff --git a/gui/retracer.h b/gui/retracer.h index e5c391b..d6da7ac 100644 --- a/gui/retracer.h +++ b/gui/retracer.h @@ -8,63 +8,6 @@ #include class ApiTraceState; -namespace QJson { - class Parser; -} - -/* internal class used by the retracer to run - * in the thread */ -class RetraceProcess : public QObject -{ - Q_OBJECT -public: - RetraceProcess(QObject *parent=0); - ~RetraceProcess(); - - QProcess *process() const; - - QString fileName() const; - void setFileName(const QString &name); - - void setAPI(trace::API api); - - bool isBenchmarking() const; - void setBenchmarking(bool bench); - - bool isDoubleBuffered() const; - void setDoubleBuffered(bool db); - - void setCaptureAtCallNumber(qlonglong num); - qlonglong captureAtCallNumber() const; - - bool captureState() const; - void setCaptureState(bool enable); - -public slots: - void start(); - void terminate(); - -signals: - void finished(const QString &output); - void error(const QString &msg); - void foundState(ApiTraceState *state); - void retraceErrors(const QList &errors); - -private slots: - void replayFinished(int exitCode, QProcess::ExitStatus exitStatus); - void replayError(QProcess::ProcessError err); - -private: - QString m_fileName; - trace::API m_api; - bool m_benchmarking; - bool m_doubleBuffered; - bool m_captureState; - qlonglong m_captureCall; - - QProcess *m_process; - QJson::Parser *m_jsonParser; -}; class Retracer : public QThread { @@ -89,23 +32,26 @@ public: bool captureState() const; void setCaptureState(bool enable); + bool captureThumbnails() const; + void setCaptureThumbnails(bool enable); + signals: void finished(const QString &output); void foundState(ApiTraceState *state); + void foundThumbnails(const QList &thumbnails); void error(const QString &msg); void retraceErrors(const QList &errors); protected: virtual void run(); -private slots: - void cleanup(); private: QString m_fileName; trace::API m_api; bool m_benchmarking; bool m_doubleBuffered; bool m_captureState; + bool m_captureThumbnails; qlonglong m_captureCall; QProcessEnvironment m_processEnvironment; diff --git a/gui/thumbnail.h b/gui/thumbnail.h new file mode 100644 index 0000000..2315564 --- /dev/null +++ b/gui/thumbnail.h @@ -0,0 +1,13 @@ +#ifndef THUMBNAIL_H +#define THUMBNAIL_H + +#define THUMBNAIL_SIZE 64 + +#include + +inline QImage +thumbnail(const QImage &image, Qt::TransformationMode transformationMode = Qt::FastTransformation) { + return image.scaled(THUMBNAIL_SIZE, THUMBNAIL_SIZE, Qt::KeepAspectRatio, transformationMode); +} + +#endif diff --git a/gui/traceloader.cpp b/gui/traceloader.cpp index 7cb07f0..2ad32b1 100644 --- a/gui/traceloader.cpp +++ b/gui/traceloader.cpp @@ -20,8 +20,7 @@ apiCallFromTraceCall(const trace::Call *call, } TraceLoader::TraceLoader(QObject *parent) - : QObject(parent), - m_frameMarker(ApiTrace::FrameMarker_SwapBuffers) + : QObject(parent) { } @@ -61,6 +60,7 @@ void TraceLoader::loadTrace(const QString &filename) //Load the entire file into memory parseTrace(); } + emit guessedApi(static_cast(m_parser.api)); emit finishedParsing(); } @@ -69,34 +69,6 @@ void TraceLoader::loadFrame(ApiTraceFrame *currentFrame) fetchFrameContents(currentFrame); } -void TraceLoader::setFrameMarker(ApiTrace::FrameMarker marker) -{ - m_frameMarker = marker; -} - -bool TraceLoader::isCallAFrameMarker(const trace::Call *call) const -{ - std::string name = call->name(); - - switch (m_frameMarker) { - case ApiTrace::FrameMarker_SwapBuffers: - return name.find("SwapBuffers") != std::string::npos || - name == "CGLFlushDrawable" || - name == "glFrameTerminatorGREMEDY"; - break; - case ApiTrace::FrameMarker_Flush: - return name == "glFlush"; - break; - case ApiTrace::FrameMarker_Finish: - return name == "glFinish"; - break; - case ApiTrace::FrameMarker_Clear: - return name == "glClear"; - break; - } - return false; -} - int TraceLoader::numberOfFrames() const { return m_frameBookmarks.size(); @@ -104,7 +76,7 @@ int TraceLoader::numberOfFrames() const int TraceLoader::numberOfCallsInFrame(int frameIdx) const { - if (frameIdx > m_frameBookmarks.size()) { + if (frameIdx >= m_frameBookmarks.size()) { return 0; } FrameBookmarks::const_iterator itr = @@ -147,7 +119,7 @@ void TraceLoader::scanTrace() while ((call = m_parser.scan_call())) { ++numOfCalls; - if (isCallAFrameMarker(call)) { + if (call->flags & trace::CALL_FLAG_END_FRAME) { FrameBookmark frameBookmark(startBookmark); frameBookmark.numberOfCalls = numOfCalls; @@ -217,8 +189,7 @@ void TraceLoader::parseTrace() apiCall->arguments()[apiCall->binaryDataIndex()].toByteArray(); binaryDataSize += data.size(); } - if (ApiTrace::isCallAFrameMarker(apiCall, - m_frameMarker)) { + if (call->flags & trace::CALL_FLAG_END_FRAME) { calls.squeeze(); currentFrame->setCalls(calls, binaryDataSize); calls.clear(); @@ -386,7 +357,7 @@ int TraceLoader::callInFrame(int callIdx) const { unsigned numCalls = 0; - for (int frameIdx = 0; frameIdx <= m_frameBookmarks.size(); ++frameIdx) { + for (int frameIdx = 0; frameIdx < m_frameBookmarks.size(); ++frameIdx) { const FrameBookmark &frameBookmark = m_frameBookmarks[frameIdx]; unsigned firstCall = numCalls; unsigned endCall = numCalls + frameBookmark.numberOfCalls; @@ -452,7 +423,7 @@ TraceLoader::fetchFrameContents(ApiTraceFrame *currentFrame) delete call; - if (ApiTrace::isCallAFrameMarker(apiCall, m_frameMarker)) { + if (apiCall->flags() & trace::CALL_FLAG_END_FRAME) { break; } diff --git a/gui/traceloader.h b/gui/traceloader.h index 3a310ba..0954078 100644 --- a/gui/traceloader.h +++ b/gui/traceloader.h @@ -27,7 +27,6 @@ public: public slots: void loadTrace(const QString &filename); void loadFrame(ApiTraceFrame *frame); - void setFrameMarker(ApiTrace::FrameMarker marker); void findFrameStart(ApiTraceFrame *frame); void findFrameEnd(ApiTraceFrame *frame); void findCallIndex(int index); @@ -36,6 +35,7 @@ public slots: signals: void startedParsing(); void parsed(int percent); + void guessedApi(int api); void finishedParsing(); void framesLoaded(const QList &frames); @@ -62,11 +62,11 @@ private: trace::ParseBookmark start; int numberOfCalls; }; - bool isCallAFrameMarker(const trace::Call *call) const; int numberOfFrames() const; int numberOfCallsInFrame(int frameIdx) const; void loadHelpFile(); + void guessApi(const trace::Call *call); void scanTrace(); void parseTrace(); @@ -84,7 +84,6 @@ private: private: trace::Parser m_parser; - ApiTrace::FrameMarker m_frameMarker; typedef QMap FrameBookmarks; FrameBookmarks m_frameBookmarks; diff --git a/gui/ui/imageviewer.ui b/gui/ui/imageviewer.ui index 3ae2dad..c6ab846 100644 --- a/gui/ui/imageviewer.ui +++ b/gui/ui/imageviewer.ui @@ -43,6 +43,68 @@ + + + + + + Lower + + + + + + + 0.050000000000000 + + + + + + + Upper + + + + + + + 0.050000000000000 + + + 1.000000000000000 + + + + + + + Flip + + + + + + + Opaque + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/gui/ui/mainwindow.ui b/gui/ui/mainwindow.ui index 5b48dc2..52cf49e 100644 --- a/gui/ui/mainwindow.ui +++ b/gui/ui/mainwindow.ui @@ -75,6 +75,7 @@ + @@ -525,6 +526,17 @@ Ctrl+L + + + false + + + Show &Thumbnails + + + Ctrl+T + + Options diff --git a/gui/ui/settings.ui b/gui/ui/settings.ui index 683557d..dac4bc2 100644 --- a/gui/ui/settings.ui +++ b/gui/ui/settings.ui @@ -25,6 +25,11 @@ + + + Unknown + + GL diff --git a/retrace.hpp b/retrace.hpp index a4559c7..1b5fdd2 100644 --- a/retrace.hpp +++ b/retrace.hpp @@ -1,6 +1,6 @@ /************************************************************************** * - * Copyright 2011 Jose Fonseca + * Copyright 2011-2012 Jose Fonseca * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -84,6 +84,53 @@ public: }; +/** + * Similar to alloca(), but implemented with malloc. + */ +class ScopedAllocator +{ +private: + void *next; + +public: + ScopedAllocator() : + next(NULL) { + } + + inline void * + alloc(size_t size) { + if (!size) { + return NULL; + } + + void * * buf = static_cast(malloc(sizeof(void *) + size)); + if (!buf) { + return NULL; + } + + *buf = next; + next = buf; + + return &buf[1]; + } + + template< class T > + inline T * + alloc(size_t n = 1) { + return static_cast(alloc(sizeof(T) * n)); + } + + inline + ~ScopedAllocator() { + while (next) { + void *temp = *static_cast(next); + free(next); + next = temp; + } + } +}; + + void addRegion(unsigned long long address, void *buffer, unsigned long long size); diff --git a/retrace.py b/retrace.py index 15cdaf5..d6e8387 100644 --- a/retrace.py +++ b/retrace.py @@ -72,7 +72,7 @@ class ValueDeserializer(stdapi.Visitor): print ' const trace::Array *__a%s = dynamic_cast(&%s);' % (array.tag, rvalue) print ' if (__a%s) {' % (array.tag) length = '__a%s->values.size()' % array.tag - print ' %s = new %s[%s];' % (lvalue, array.type, length) + print ' %s = _allocator.alloc<%s>(%s);' % (lvalue, array.type, length) index = '__j' + array.tag print ' for (size_t {i} = 0; {i} < {length}; ++{i}) {{'.format(i = index, length = length) try: @@ -86,7 +86,7 @@ class ValueDeserializer(stdapi.Visitor): def visitPointer(self, pointer, lvalue, rvalue): print ' const trace::Array *__a%s = dynamic_cast(&%s);' % (pointer.tag, rvalue) print ' if (__a%s) {' % (pointer.tag) - print ' %s = new %s;' % (lvalue, pointer.type) + print ' %s = _allocator.alloc<%s>();' % (lvalue, pointer.type) try: self.visit(pointer.type, '%s[0]' % (lvalue,), '*__a%s->values[0]' % (pointer.tag,)) finally: @@ -242,6 +242,8 @@ class Retracer: # FIXME def deserializeArgs(self, function): + print ' retrace::ScopedAllocator _allocator;' + print ' (void)_allocator;' success = True for arg in function.args: arg_type = ConstRemover().visit(arg.type) diff --git a/scripts/tracediff2.py b/scripts/tracediff2.py index d6fd392..3872071 100755 --- a/scripts/tracediff2.py +++ b/scripts/tracediff2.py @@ -40,6 +40,7 @@ ignoredFunctionNames = set([ 'glGetString', 'glXGetClientString', 'glXGetCurrentDisplay', + 'glXGetCurrentContext', 'glXGetProcAddress', 'glXGetProcAddressARB', 'wglGetProcAddress', @@ -99,7 +100,6 @@ def readtrace(trace, calls): stdout = subprocess.PIPE, ) - calls = [] parser = Loader(p.stdout) parser.parse() return parser.calls @@ -186,15 +186,11 @@ class SDiffer: def replace_dissimilar(self, alo, ahi, blo, bhi): assert alo < ahi and blo < bhi if bhi - blo < ahi - alo: - first = self.insert(blo, bhi) - second = self.delete(alo, ahi) + self.insert(alo, alo, blo, bhi) + self.delete(alo, ahi, bhi, bhi) else: - first = self.delete(alo, ahi) - second = self.insert(blo, bhi) - - for g in first, second: - for line in g: - yield line + self.delete(alo, ahi, blo, blo) + self.insert(ahi, ahi, blo, bhi) def replace_value(self, a, b): if b == a: @@ -212,6 +208,8 @@ class SDiffer: escape = "\33[" def delete(self, alo, ahi, blo, bhi): + assert alo < ahi + assert blo == bhi for i in xrange(alo, ahi): call = self.a[i] self.highlighter.write('- ') @@ -221,6 +219,8 @@ class SDiffer: self.dumpCall(call) def insert(self, alo, ahi, blo, bhi): + assert alo == ahi + assert blo < bhi for i in xrange(blo, bhi): call = self.b[i] self.highlighter.write('+ ') @@ -229,6 +229,8 @@ class SDiffer: self.dumpCall(call) def equal(self, alo, ahi, blo, bhi): + assert alo < ahi and blo < bhi + assert ahi - alo == bhi - blo for i in xrange(0, bhi - blo): self.highlighter.write(' ') a_call = self.a[alo + i] @@ -245,21 +247,21 @@ class SDiffer: if aNo is None: self.highlighter.write(' '*self.aSpace) else: - aStr = str(aNo) + aNoStr = str(aNo) self.highlighter.strike() self.highlighter.color(self.delete_color) - self.highlighter.write(str(aNo)) + self.highlighter.write(aNoStr) self.highlighter.normal() - self.aSpace = len(aStr) + self.aSpace = len(aNoStr) self.highlighter.write(' ') if bNo is None: - self.highlighter.write(' '*self.aSpace) + self.highlighter.write(' '*self.bSpace) else: - bStr = str(bNo) + bNoStr = str(bNo) self.highlighter.color(self.insert_color) - self.highlighter.write(str(bNo)) + self.highlighter.write(bNoStr) self.highlighter.normal() - self.bSpace = len(bStr) + self.bSpace = len(bNoStr) self.highlighter.write(' ') def dumpCall(self, call):