include_directories (${X11_INCLUDE_DIR})
add_definitions (-DHAVE_X11)
endif ()
-
- if (ENABLE_EGL)
- add_definitions (-DHAVE_EGL)
- endif ()
endif ()
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 ""
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
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
)
)
target_link_libraries (glretrace
+ retrace_common
common
${PNG_LIBRARIES}
${ZLIB_LIBRARIES}
if (ENABLE_EGL AND X11_FOUND AND NOT WIN32 AND NOT APPLE)
add_executable (eglretrace
- ${retrace_sources}
glws_egl_xlib.cpp
glproc_egl.cpp
)
TARGET eglretrace
APPEND
PROPERTY COMPILE_DEFINITIONS "RETRACE"
- PROPERTY COMPILE_DEFINITIONS "TRACE_EGL"
)
target_link_libraries (eglretrace
+ retrace_common
common
${PNG_LIBRARIES}
${ZLIB_LIBRARIES}
and their authors see the git history.
+Development
+===========
+
+* Support to trace in Android
+
+* Show frame thumbnails in the GUI
+
+
Version 3.0
===========
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
#include <string.h>
#include <limits.h> // for CHAR_MAX
#include <getopt.h>
+#ifndef _WIN32
+#include <unistd.h> // for isatty()
+#endif
#include "cli.hpp"
#include "cli_pager.hpp"
Image *
readPNG(const char *filename);
+const char *
+readPNMHeader(const char *buffer, size_t size, unsigned *channels, unsigned *width, unsigned *height);
} /* namespace image */
#include <assert.h>
#include <string.h>
#include <stdint.h>
+#include <stdio.h>
#include "image.hpp"
}
}
+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 */
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,
file = NULL;
next_call_no = 0;
version = 0;
+ api = API_UNKNOWN;
glGetErrorSig = NULL;
}
std::cerr << "error: unsupported trace format version " << version << "\n";
return false;
}
+ api = API_UNKNOWN;
return true;
}
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.
*
#include "trace_file.hpp"
#include "trace_format.hpp"
#include "trace_model.hpp"
+#include "trace_api.hpp"
namespace trace {
public:
unsigned long long version;
+ API api;
Parser();
{ "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 },
#define _EGLIMPORTS_HPP_
-#ifdef HAVE_EGL
+#include <KHR/khrplatform.h>
+
+#ifdef _GDI32_
+/* Prevent __declspec(dllimport) attribute */
+#undef KHRONOS_APICALL
+#define KHRONOS_APICALL
+#endif
// EGL
#include <EGL/egl.h>
#include <GLES/glplatform.h>
#include <GLES2/gl2platform.h>
-#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;
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)
])
misc_draw_function_names = set([
+ "glCallList",
+ "glCallLists",
"glClear",
"glEnd",
"glDrawPixels",
# 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.
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':
#include <algorithm>
#include <iostream>
-#include <map>
-#include <sstream>
#include "image.hpp"
#include "json.hpp"
#include "glstate_internal.hpp"
-#ifdef __APPLE__
-
-#include <Carbon/Carbon.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-OSStatus CGSGetSurfaceBounds(CGSConnectionID, CGWindowID, CGSSurfaceID, CGRect *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __APPLE__ */
-
-
namespace glstate {
}
-// Mapping from shader type to shader source, used to accumulated the sources
-// of different shaders with same type.
-typedef std::map<std::string, std::string> 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 = "<<pngBufferSize << ", ratio = " << double(width * height * channels * sizeof *pixels)/pngBufferSize;
- json.writeBase64(pngBuffer, pngBufferSize);
- free(pngBuffer);
- json.endMember(); // __data__
-
- delete [] pixels;
- json.endObject();
-}
-
-
-static inline GLuint
-downsampledFramebuffer(GLuint oldFbo, GLint drawbuffer,
- GLint colorRb, GLint depthRb, GLint stencilRb,
- GLuint *rbs, GLint *numRbs)
-{
- GLuint fbo;
- GLint format;
- GLint w, h;
-
- *numRbs = 0;
-
- glGenFramebuffers(1, &fbo);
- glBindFramebuffer(GL_FRAMEBUFFER, fbo);
-
- glBindRenderbuffer(GL_RENDERBUFFER, colorRb);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_WIDTH, &w);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_HEIGHT, &h);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_INTERNAL_FORMAT, &format);
-
- glGenRenderbuffers(1, &rbs[*numRbs]);
- glBindRenderbuffer(GL_RENDERBUFFER, rbs[*numRbs]);
- glRenderbufferStorage(GL_RENDERBUFFER, format, w, h);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffer,
- GL_RENDERBUFFER, rbs[*numRbs]);
-
- glBindFramebuffer(GL_READ_FRAMEBUFFER, oldFbo);
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
- glDrawBuffer(drawbuffer);
- glReadBuffer(drawbuffer);
- glBlitFramebuffer(0, 0, w, h, 0, 0, w, h,
- GL_COLOR_BUFFER_BIT, GL_NEAREST);
- glBindFramebuffer(GL_FRAMEBUFFER, fbo);
- ++*numRbs;
-
- if (stencilRb == depthRb && stencilRb) {
- //combined depth and stencil buffer
- glBindRenderbuffer(GL_RENDERBUFFER, depthRb);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_WIDTH, &w);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_HEIGHT, &h);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_INTERNAL_FORMAT, &format);
-
- glGenRenderbuffers(1, &rbs[*numRbs]);
- glBindRenderbuffer(GL_RENDERBUFFER, rbs[*numRbs]);
- glRenderbufferStorage(GL_RENDERBUFFER, format, w, h);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
- GL_RENDERBUFFER, rbs[*numRbs]);
- glBindFramebuffer(GL_READ_FRAMEBUFFER, oldFbo);
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
- glBlitFramebuffer(0, 0, w, h, 0, 0, w, h,
- GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
- glBindFramebuffer(GL_FRAMEBUFFER, fbo);
- ++*numRbs;
- } else {
- if (depthRb) {
- glBindRenderbuffer(GL_RENDERBUFFER, depthRb);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_WIDTH, &w);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_HEIGHT, &h);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_INTERNAL_FORMAT, &format);
-
- glGenRenderbuffers(1, &rbs[*numRbs]);
- glBindRenderbuffer(GL_RENDERBUFFER, rbs[*numRbs]);
- glRenderbufferStorage(GL_RENDERBUFFER, format, w, h);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER,
- GL_DEPTH_ATTACHMENT,
- GL_RENDERBUFFER, rbs[*numRbs]);
- glBindFramebuffer(GL_READ_FRAMEBUFFER, oldFbo);
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
- glDrawBuffer(GL_DEPTH_ATTACHMENT);
- glReadBuffer(GL_DEPTH_ATTACHMENT);
- glBlitFramebuffer(0, 0, w, h, 0, 0, w, h,
- GL_DEPTH_BUFFER_BIT, GL_NEAREST);
- ++*numRbs;
- }
- if (stencilRb) {
- glBindRenderbuffer(GL_RENDERBUFFER, stencilRb);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_WIDTH, &w);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_HEIGHT, &h);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER,
- GL_RENDERBUFFER_INTERNAL_FORMAT, &format);
-
- glGenRenderbuffers(1, &rbs[*numRbs]);
- glBindRenderbuffer(GL_RENDERBUFFER, rbs[*numRbs]);
- glRenderbufferStorage(GL_RENDERBUFFER, format, w, h);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER,
- GL_STENCIL_ATTACHMENT,
- GL_RENDERBUFFER, rbs[*numRbs]);
- glBindFramebuffer(GL_READ_FRAMEBUFFER, oldFbo);
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
- glDrawBuffer(GL_STENCIL_ATTACHMENT);
- glReadBuffer(GL_STENCIL_ATTACHMENT);
- glBlitFramebuffer(0, 0, w, h, 0, 0, w, h,
- GL_STENCIL_BUFFER_BIT, GL_NEAREST);
- ++*numRbs;
- }
- }
-
- return fbo;
-}
-
-
-/**
- * Dump images of current draw drawable/window.
- */
-static void
-dumpDrawableImages(JSONWriter &json, Context &context)
-{
- GLint width, height;
-
- if (!getDrawableBounds(&width, &height)) {
- return;
- }
-
- GLint draw_buffer = GL_NONE;
- if (context.ES) {
- draw_buffer = GL_BACK;
- } else {
- glGetIntegerv(GL_DRAW_BUFFER, &draw_buffer);
- glReadBuffer(draw_buffer);
- }
-
- if (draw_buffer != GL_NONE) {
- GLint read_buffer = GL_NONE;
- if (!context.ES) {
- glGetIntegerv(GL_READ_BUFFER, &read_buffer);
- }
-
- GLint alpha_bits = 0;
-#if 0
- // XXX: Ignore alpha until we are able to match the traced visual
- glGetIntegerv(GL_ALPHA_BITS, &alpha_bits);
-#endif
- GLenum format = alpha_bits ? GL_RGBA : GL_RGB;
- json.beginMember(enumToString(draw_buffer));
- dumpReadBufferImage(json, width, height, format);
- json.endMember();
-
- if (!context.ES) {
- glReadBuffer(read_buffer);
- }
- }
-
- if (!context.ES) {
- GLint depth_bits = 0;
- glGetIntegerv(GL_DEPTH_BITS, &depth_bits);
- if (depth_bits) {
- json.beginMember("GL_DEPTH_COMPONENT");
- dumpReadBufferImage(json, width, height, GL_DEPTH_COMPONENT);
- json.endMember();
- }
-
- GLint stencil_bits = 0;
- glGetIntegerv(GL_STENCIL_BITS, &stencil_bits);
- if (stencil_bits) {
- json.beginMember("GL_STENCIL_INDEX");
- dumpReadBufferImage(json, width, height, GL_STENCIL_INDEX);
- json.endMember();
- }
- }
-}
-
-
-/**
- * Dump the specified framebuffer attachment.
- *
- * In the case of a color attachment, it assumes it is already bound for read.
- */
-static void
-dumpFramebufferAttachment(JSONWriter &json, GLenum target, GLenum attachment, GLenum format)
-{
- GLint width = 0, height = 0;
- if (!getFramebufferAttachmentSize(target, attachment, &width, &height)) {
- return;
- }
-
- GLint internalFormat = getFramebufferAttachmentFormat(target, attachment);
-
- json.beginMember(enumToString(attachment));
- dumpReadBufferImage(json, width, height, format, internalFormat);
- json.endMember();
-}
-
-
-static void
-dumpFramebufferAttachments(JSONWriter &json, Context &context, GLenum target)
-{
- GLint read_framebuffer = 0;
- glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read_framebuffer);
-
- GLint read_buffer = GL_NONE;
- glGetIntegerv(GL_READ_BUFFER, &read_buffer);
-
- GLint max_draw_buffers = 1;
- glGetIntegerv(GL_MAX_DRAW_BUFFERS, &max_draw_buffers);
- GLint max_color_attachments = 0;
- glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &max_color_attachments);
-
- for (GLint i = 0; i < max_draw_buffers; ++i) {
- GLint draw_buffer = GL_NONE;
- glGetIntegerv(GL_DRAW_BUFFER0 + i, &draw_buffer);
- if (draw_buffer != GL_NONE) {
- glReadBuffer(draw_buffer);
- GLint attachment;
- if (draw_buffer >= 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,
#include "glimports.hpp"
-class JSONWriter;
-
-
namespace image {
class Image;
}
+++ /dev/null
-##########################################################################
-#
-# 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 <string.h>'
- 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()
--- /dev/null
+/**************************************************************************
+ *
+ * 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 <string.h>
+
+#include <algorithm>
+#include <iostream>
+
+#include "image.hpp"
+#include "json.hpp"
+#include "glproc.hpp"
+#include "glsize.hpp"
+#include "glstate.hpp"
+#include "glstate_internal.hpp"
+
+
+#ifdef __linux__
+#include <dlfcn.h>
+#endif
+
+#ifdef __APPLE__
+
+#include <Carbon/Carbon.h>
+
+#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 = "<<pngBufferSize << ", ratio = " << double(width * height * channels * sizeof *pixels)/pngBufferSize;
+ json.writeBase64(pngBuffer, pngBufferSize);
+ free(pngBuffer);
+ json.endMember(); // __data__
+
+ delete [] pixels;
+ json.endObject();
+}
+
+
+static inline GLuint
+downsampledFramebuffer(Context &context,
+ GLuint oldFbo, GLint drawbuffer,
+ GLint colorRb, GLint depthRb, GLint stencilRb,
+ GLuint *rbs, GLint *numRbs)
+{
+ GLuint fbo;
+
+
+ *numRbs = 0;
+
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+ {
+ // color buffer
+ ImageDesc desc;
+ glBindRenderbuffer(GL_RENDERBUFFER, colorRb);
+ getBoundRenderbufferDesc(context, desc);
+
+ glGenRenderbuffers(1, &rbs[*numRbs]);
+ glBindRenderbuffer(GL_RENDERBUFFER, rbs[*numRbs]);
+ glRenderbufferStorage(GL_RENDERBUFFER, desc.internalFormat, desc.width, desc.height);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffer,
+ GL_RENDERBUFFER, rbs[*numRbs]);
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, oldFbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ glDrawBuffer(drawbuffer);
+ glReadBuffer(drawbuffer);
+ glBlitFramebuffer(0, 0, desc.width, desc.height, 0, 0, desc.width, desc.height,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ ++*numRbs;
+ }
+
+ if (stencilRb == depthRb && stencilRb) {
+ //combined depth and stencil buffer
+ ImageDesc desc;
+ glBindRenderbuffer(GL_RENDERBUFFER, depthRb);
+ getBoundRenderbufferDesc(context, desc);
+
+ glGenRenderbuffers(1, &rbs[*numRbs]);
+ glBindRenderbuffer(GL_RENDERBUFFER, rbs[*numRbs]);
+ glRenderbufferStorage(GL_RENDERBUFFER, desc.internalFormat, desc.width, desc.height);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, rbs[*numRbs]);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, oldFbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ glBlitFramebuffer(0, 0, desc.width, desc.height, 0, 0, desc.width, desc.height,
+ GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ ++*numRbs;
+ } else {
+ if (depthRb) {
+ ImageDesc desc;
+ glBindRenderbuffer(GL_RENDERBUFFER, depthRb);
+ getBoundRenderbufferDesc(context, desc);
+
+ glGenRenderbuffers(1, &rbs[*numRbs]);
+ glBindRenderbuffer(GL_RENDERBUFFER, rbs[*numRbs]);
+ glRenderbufferStorage(GL_RENDERBUFFER, desc.internalFormat, desc.width, desc.height);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER,
+ GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, rbs[*numRbs]);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, oldFbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ glDrawBuffer(GL_DEPTH_ATTACHMENT);
+ glReadBuffer(GL_DEPTH_ATTACHMENT);
+ glBlitFramebuffer(0, 0, desc.width, desc.height, 0, 0, desc.width, desc.height,
+ GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+ ++*numRbs;
+ }
+ if (stencilRb) {
+ ImageDesc desc;
+ glBindRenderbuffer(GL_RENDERBUFFER, stencilRb);
+ getBoundRenderbufferDesc(context, desc);
+
+ glGenRenderbuffers(1, &rbs[*numRbs]);
+ glBindRenderbuffer(GL_RENDERBUFFER, rbs[*numRbs]);
+ glRenderbufferStorage(GL_RENDERBUFFER, desc.internalFormat, desc.width, desc.height);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER,
+ GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, rbs[*numRbs]);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, oldFbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ glDrawBuffer(GL_STENCIL_ATTACHMENT);
+ glReadBuffer(GL_STENCIL_ATTACHMENT);
+ glBlitFramebuffer(0, 0, desc.width, desc.height, 0, 0, desc.width, desc.height,
+ GL_STENCIL_BUFFER_BIT, GL_NEAREST);
+ ++*numRbs;
+ }
+ }
+
+ return fbo;
+}
+
+
+/**
+ * Dump images of current draw drawable/window.
+ */
+static void
+dumpDrawableImages(JSONWriter &json, Context &context)
+{
+ GLint width, height;
+
+ if (!getDrawableBounds(&width, &height)) {
+ return;
+ }
+
+ GLint draw_buffer = GL_NONE;
+ if (context.ES) {
+ draw_buffer = GL_BACK;
+ } else {
+ glGetIntegerv(GL_DRAW_BUFFER, &draw_buffer);
+ glReadBuffer(draw_buffer);
+ }
+
+ if (draw_buffer != GL_NONE) {
+ GLint read_buffer = GL_NONE;
+ if (!context.ES) {
+ glGetIntegerv(GL_READ_BUFFER, &read_buffer);
+ }
+
+ GLint alpha_bits = 0;
+#if 0
+ // XXX: Ignore alpha until we are able to match the traced visual
+ glGetIntegerv(GL_ALPHA_BITS, &alpha_bits);
+#endif
+ GLenum format = alpha_bits ? GL_RGBA : GL_RGB;
+ json.beginMember(enumToString(draw_buffer));
+ dumpReadBufferImage(json, width, height, format);
+ json.endMember();
+
+ if (!context.ES) {
+ glReadBuffer(read_buffer);
+ }
+ }
+
+ if (!context.ES) {
+ GLint depth_bits = 0;
+ glGetIntegerv(GL_DEPTH_BITS, &depth_bits);
+ if (depth_bits) {
+ json.beginMember("GL_DEPTH_COMPONENT");
+ dumpReadBufferImage(json, width, height, GL_DEPTH_COMPONENT);
+ json.endMember();
+ }
+
+ GLint stencil_bits = 0;
+ glGetIntegerv(GL_STENCIL_BITS, &stencil_bits);
+ if (stencil_bits) {
+ json.beginMember("GL_STENCIL_INDEX");
+ dumpReadBufferImage(json, width, height, GL_STENCIL_INDEX);
+ json.endMember();
+ }
+ }
+}
+
+
+/**
+ * Dump the specified framebuffer attachment.
+ *
+ * In the case of a color attachment, it assumes it is already bound for read.
+ */
+static void
+dumpFramebufferAttachment(JSONWriter &json, Context &context, GLenum target, GLenum attachment, GLenum format)
+{
+ ImageDesc desc;
+ if (!getFramebufferAttachmentDesc(context, target, attachment, desc)) {
+ return;
+ }
+
+ json.beginMember(enumToString(attachment));
+ dumpReadBufferImage(json, desc.width, desc.height, format, desc.internalFormat);
+ json.endMember();
+}
+
+
+static void
+dumpFramebufferAttachments(JSONWriter &json, Context &context, GLenum target)
+{
+ GLint read_framebuffer = 0;
+ glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read_framebuffer);
+
+ GLint read_buffer = GL_NONE;
+ glGetIntegerv(GL_READ_BUFFER, &read_buffer);
+
+ GLint max_draw_buffers = 1;
+ glGetIntegerv(GL_MAX_DRAW_BUFFERS, &max_draw_buffers);
+ GLint max_color_attachments = 0;
+ glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &max_color_attachments);
+
+ for (GLint i = 0; i < max_draw_buffers; ++i) {
+ GLint draw_buffer = GL_NONE;
+ glGetIntegerv(GL_DRAW_BUFFER0 + i, &draw_buffer);
+ if (draw_buffer != GL_NONE) {
+ glReadBuffer(draw_buffer);
+ GLint attachment;
+ if (draw_buffer >= 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 */
bool ARB_draw_buffers;
- inline
Context(void);
GLint packAlignment;
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 */
--- /dev/null
+##########################################################################
+#
+# 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 <string.h>'
+ 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()
--- /dev/null
+/**************************************************************************
+ *
+ * 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 <string.h>
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <sstream>
+
+#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<std::string, std::string> 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 */
*
**************************************************************************/
+
+/*
+ * WGL bindings.
+ */
+
+
#include <iostream>
#include "glproc.hpp"
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)
{
{
static bool first = TRUE;
RECT rect;
+ BOOL bRet;
if (first) {
WNDCLASS wc;
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() {
}
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
const char * libgl_filename = getenv("TRACE_LIBGL");
- if (!libgl_filename) {
+ if (libgl_filename) {
+ pfnChoosePixelFormat = &wglChoosePixelFormat;
+ pfnSetPixelFormat = &wglSetPixelFormat;
+ pfnSwapBuffers = &wglSwapBuffers;
+ } else {
libgl_filename = "OPENGL32";
}
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";
+ }
}
}
#include "apitracecall.h"
#include "apitracemodel.h"
+#include "thumbnail.h"
#include <QApplication>
#include <QDebug>
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<ApiTraceFrame*>(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<ApiTraceCall*>(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);
}
QSize ApiCallDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
- ApiTraceEvent *event =
- index.data(ApiTraceModel::EventRole).value<ApiTraceEvent*>();
+ QVariant var = index.data(ApiTraceModel::EventRole);
+ ApiTraceEvent *event = var.value<ApiTraceEvent*>();
#ifndef __APPLE__
/* XXX: This fails on MacOSX, but seems safe to ignore */
QStaticText text = event->staticText();
//text.setTextWidth(option.rect.width());
//qDebug()<<"text size = "<<text.size();
- return text.size().toSize();
+ QSize size = text.size().toSize();
+
+ // Adjust for thumbnail
+ if (event->type() == ApiTraceEvent::Frame) {
+ ApiTraceFrame *frame = static_cast<ApiTraceFrame*>(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);
}
#include "apisurface.h"
+#include "thumbnail.h"
#include <QDebug>
#include <QSysInfo>
{
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
#include <QThread>
ApiTrace::ApiTrace()
- : m_frameMarker(ApiTrace::FrameMarker_SwapBuffers),
- m_needsSaving(false)
+ : m_needsSaving(false)
{
m_loader = new TraceLoader();
SIGNAL(frameContentsLoaded(ApiTraceFrame*,QVector<ApiTraceCall*>,quint64)),
this,
SLOT(loaderFrameLoaded(ApiTraceFrame*,QVector<ApiTraceCall*>,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)),
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();
return m_fileName;
}
-ApiTrace::FrameMarker ApiTrace::frameMarker() const
-{
- return m_frameMarker;
-}
-
-QList<ApiTraceFrame*> ApiTrace::frames() const
+const QList<ApiTraceFrame*> & ApiTrace::frames() const
{
return m_frames;
}
}
}
+void ApiTrace::guessedApi(int api)
+{
+ m_api = static_cast<trace::API>(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);
+ }
}
}
{
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()
return m_loadingFrames.contains(frame);
}
+void ApiTrace::bindThumbnailsToFrames(const QList<QImage> &thumbnails)
+{
+ QList<ApiTraceFrame *> frames = m_frames;
+
+ QList<QImage>::const_iterator thumbnail = thumbnails.begin();
+
+ foreach (ApiTraceFrame *frame, frames) {
+ if (thumbnail != thumbnails.end()) {
+ frame->setThumbnail(*thumbnail);
+
+ ++thumbnail;
+
+ emit changed(frame);
+ }
+ }
+}
+
#include "apitrace.moc"
#include "apitracecall.h"
+#include "trace_api.hpp"
+
#include <QObject>
#include <QSet>
{
Q_OBJECT
public:
- enum FrameMarker {
- FrameMarker_SwapBuffers,
- FrameMarker_Flush,
- FrameMarker_Finish,
- FrameMarker_Clear
- };
enum SearchResult {
SearchResult_NotFound,
SearchResult_Found,
Qt::CaseSensitivity cs;
};
- static bool isCallAFrameMarker(const ApiTraceCall *call,
- FrameMarker marker);
public:
ApiTrace();
~ApiTrace();
QString fileName() const;
- FrameMarker frameMarker() const;
-
ApiTraceState defaultState() const;
ApiTraceCall *callWithIndex(int idx) const;
- QList<ApiTraceFrame*> frames() const;
+ const QList<ApiTraceFrame*> & frames() const;
ApiTraceFrame *frameAt(int idx) const;
int numFrames() const;
int numCallsInFrame(int idx) const;
bool hasErrors() const;
+ trace::API api() const;
+
public slots:
void setFileName(const QString &name);
void save();
void findCallIndex(int index);
void setCallError(const ApiTraceError &error);
+ void bindThumbnailsToFrames(const QList<QImage> &thumbnails);
signals:
void loadTrace(const QString &name);
void finishedLoadingTrace();
void invalidated();
void framesInvalidated();
- void changed(ApiTraceCall *call);
+ void changed(ApiTraceEvent *event);
void startedSaving();
void saved();
void findResult(const ApiTrace::SearchRequest &request,
private slots:
void addFrames(const QList<ApiTraceFrame*> &frames);
void slotSaved();
+ void guessedApi(int api);
void finishedParsing();
void loaderFrameLoaded(ApiTraceFrame *frame,
const QVector<ApiTraceCall*> &calls,
QString m_tempFileName;
QList<ApiTraceFrame*> m_frames;
-
- FrameMarker m_frameMarker;
+ trace::API m_api;
TraceLoader *m_loader;
QThread *m_loaderThread;
return m_lastCallIndex;
}
}
+
+void ApiTraceFrame::setThumbnail(const QImage & thumbnail)
+{
+ m_thumbnail = thumbnail;
+}
+
+const QImage & ApiTraceFrame::thumbnail() const
+{
+ return m_thumbnail;
+}
void setLastCallIndex(unsigned index);
unsigned lastCallIndex() const;
+
+ void setThumbnail(const QImage & thumbnail);
+ const QImage & thumbnail() const;
+
private:
ApiTrace *m_parentTrace;
quint64 m_binaryDataSize;
bool m_loaded;
unsigned m_callsToLoad;
unsigned m_lastCallIndex;
+ QImage m_thumbnail;
};
Q_DECLARE_METATYPE(ApiTraceFrame*);
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*)),
emit dataChanged(index, index);
} else if (event->type() == ApiTraceEvent::Frame) {
ApiTraceFrame *frame = static_cast<ApiTraceFrame*>(event);
- const QList<ApiTraceFrame*> frames = m_trace->frames();
+ const QList<ApiTraceFrame*> & frames = m_trace->frames();
int row = frames.indexOf(frame);
QModelIndex index = createIndex(row, 0, frame);
emit dataChanged(index, index);
return createIndex(row, 0, call);
}
+void ApiTraceModel::changed(ApiTraceEvent *event)
+{
+ if (event->type() == ApiTraceEvent::Call) {
+ callChanged(static_cast<ApiTraceCall*>(event));
+ } else if (event->type() == ApiTraceEvent::Frame) {
+ frameChanged(static_cast<ApiTraceFrame*>(event));
+ }
+}
+
void ApiTraceModel::callChanged(ApiTraceCall *call)
{
ApiTrace *trace = call->parentFrame()->parentTrace();
emit dataChanged(index, index);
}
+void ApiTraceModel::frameChanged(ApiTraceFrame *frame)
+{
+ const QList<ApiTraceFrame*> & frames = m_trace->frames();
+ int row = frames.indexOf(frame);
+ QModelIndex index = createIndex(row, 0, frame);
+ emit dataChanged(index, index);
+}
+
void ApiTraceModel::endAddingFrames()
{
endInsertRows();
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);
#include "imageviewer.h"
+#include <QDebug>
#include <QDesktopWidget>
#include <QPainter>
#include <QPixmap>
{
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);
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();
}
void setImage(const QImage &image);
QSize sizeHint() const;
+
+private slots:
+ void slotUpdate();
+
private:
QImage m_image;
+ QImage m_temp;
};
#include <QApplication>
#include <QMetaType>
#include <QVariant>
+#include <QImage>
Q_DECLARE_METATYPE(QList<ApiTraceFrame*>);
Q_DECLARE_METATYPE(QVector<ApiTraceCall*>);
Q_DECLARE_METATYPE(Qt::CaseSensitivity);
Q_DECLARE_METATYPE(ApiTrace::SearchResult);
Q_DECLARE_METATYPE(ApiTrace::SearchRequest);
+Q_DECLARE_METATYPE(QList<QImage>);
static void usage(void)
{
qRegisterMetaType<Qt::CaseSensitivity>();
qRegisterMetaType<ApiTrace::SearchResult>();
qRegisterMetaType<ApiTrace::SearchRequest>();
+ qRegisterMetaType<QList<QImage> >();
QStringList args = app.arguments();
int i = 1;
#include "shaderssourcewidget.h"
#include "tracedialog.h"
#include "traceprocess.h"
+#include "thumbnail.h"
#include "ui_retracerdialog.h"
#include "vertexdatainterpreter.h"
dlgUi.doubleBufferingCB->isChecked());
m_retracer->setBenchmarking(
!dlgUi.errorCheckCB->isChecked());
- replayTrace(false);
+ replayTrace(false, true);
}
}
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)
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)
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;
if (!m_trace) {
return;
}
+ m_api = m_trace->api();
QFileInfo info(m_trace->fileName());
statusBar()->showMessage(
tr("Loaded %1").arg(info.fileName()), 3000);
}
}
-void MainWindow::replayTrace(bool dumpState)
+void MainWindow::replayTrace(bool dumpState, bool dumpThumbnails)
{
if (m_trace->fileName().isEmpty()) {
return;
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) {
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..."));
return;
}
m_stateEvent = m_selectedEvent;
- replayTrace(true);
+ replayTrace(true, false);
+}
+
+void MainWindow::showThumbnails()
+{
+ replayTrace(false, true);
}
MainWindow::~MainWindow()
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);
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*)),
this, SLOT(replayError(const QString&)));
connect(m_retracer, SIGNAL(foundState(ApiTraceState*)),
this, SLOT(replayStateFound(ApiTraceState*)));
+ connect(m_retracer, SIGNAL(foundThumbnails(const QList<QImage>&)),
+ this, SLOT(replayThumbnailsFound(const QList<QImage>&)));
connect(m_retracer, SIGNAL(retraceErrors(const QList<ApiTraceError>&)),
this, SLOT(slotRetraceErrors(const QList<ApiTraceError>&)));
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()));
m_nonDefaultsLookupEvent = 0;
}
+void MainWindow::replayThumbnailsFound(const QList<QImage> &thumbnails)
+{
+ m_ui.callView->setUniformRowHeights(false);
+ m_trace->bindThumbnailsToFrames(thumbnails);
+}
+
void MainWindow::slotGoTo()
{
m_searchWidget->hide();
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<ApiTraceCall*>(event);
+ m_ui.detailsWebView->setHtml(call->toHtml());
+ }
}
}
#include <QMainWindow>
#include <QProcess>
+#include <QList>
+#include <QImage>
class ApiTrace;
class ApiTraceCall;
void openTrace();
void replayStart();
void replayStop();
- void replayFinished(const QString &output);
+ void replayFinished(const QString &message);
void replayStateFound(ApiTraceState *state);
+ void replayThumbnailsFound(const QList<QImage> &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);
void slotSaved();
void slotGoFrameStart();
void slotGoFrameEnd();
- void slotTraceChanged(ApiTraceCall *call);
+ void slotTraceChanged(ApiTraceEvent *event);
void slotRetraceErrors(const QList<ApiTraceError> &errors);
void slotErrorSelected(QTreeWidgetItem *current);
void slotSearchResult(const ApiTrace::SearchRequest &request,
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
#include "retracer.h"
#include "apitracecall.h"
+#include "thumbnail.h"
+
+#include "image.hpp"
#include <QDebug>
#include <QVariant>
+#include <QList>
+#include <QImage>
#include <qjson/parser.h>
+/**
+ * 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<ApiTraceError>);
+
Retracer::Retracer(QObject *parent)
: QThread(parent),
m_benchmarking(false),
m_captureState(false),
m_captureCall(0)
{
+ qRegisterMetaType<QList<ApiTraceError> >();
+
#ifdef Q_OS_WIN
QString format = QLatin1String("%1;");
#else
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<ApiTraceError>&)),
- this, SIGNAL(retraceErrors(const QList<ApiTraceError>&)));
-
- 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;
} else if (m_api == trace::API_EGL) {
prog = QLatin1String("eglretrace");
} else {
- assert(0);
+ emit finished(QLatin1String("Unsupported API"));
return;
}
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 = "<<errStr;
- qDebug()<<"\tout = "<<output;
-#endif
+ process.start(prog, arguments, QIODevice::ReadOnly);
+ if (!process.waitForStarted(-1)) {
+ emit finished(QLatin1String("Could not start process"));
+ return;
+ }
+
+ /*
+ * Process standard output
+ */
+
+ QList<QImage> 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<ApiTraceError> 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 = "<<err;
- qDebug()<<"\terr = "<<m_process->readAllStandardError();
- qDebug()<<"\tout = "<<m_process->readAllStandardOutput();
-#endif
+ for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
+ qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
- emit error(
- tr("Couldn't execute the replay file '%1'").arg(m_fileName));
-}
+ // if header actually contains optional comment line, ...
+ if (headerLine == 1 && header[headerSize] == '#') {
+ ++headerLines;
+ }
-Q_DECLARE_METATYPE(QList<ApiTraceError>);
-RetraceProcess::RetraceProcess(QObject *parent)
- : QObject(parent)
-{
- m_process = new QProcess(this);
- m_jsonParser = new QJson::Parser();
+ headerSize += headerRead;
+ }
- qRegisterMetaType<QList<ApiTraceError> >();
+ 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<ApiTraceError> 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"
#include <QProcess>
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<ApiTraceError> &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
{
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<QImage> &thumbnails);
void error(const QString &msg);
void retraceErrors(const QList<ApiTraceError> &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;
--- /dev/null
+#ifndef THUMBNAIL_H
+#define THUMBNAIL_H
+
+#define THUMBNAIL_SIZE 64
+
+#include <QImage>
+
+inline QImage
+thumbnail(const QImage &image, Qt::TransformationMode transformationMode = Qt::FastTransformation) {
+ return image.scaled(THUMBNAIL_SIZE, THUMBNAIL_SIZE, Qt::KeepAspectRatio, transformationMode);
+}
+
+#endif
}
TraceLoader::TraceLoader(QObject *parent)
- : QObject(parent),
- m_frameMarker(ApiTrace::FrameMarker_SwapBuffers)
+ : QObject(parent)
{
}
//Load the entire file into memory
parseTrace();
}
+ emit guessedApi(static_cast<int>(m_parser.api));
emit finishedParsing();
}
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();
int TraceLoader::numberOfCallsInFrame(int frameIdx) const
{
- if (frameIdx > m_frameBookmarks.size()) {
+ if (frameIdx >= m_frameBookmarks.size()) {
return 0;
}
FrameBookmarks::const_iterator itr =
while ((call = m_parser.scan_call())) {
++numOfCalls;
- if (isCallAFrameMarker(call)) {
+ if (call->flags & trace::CALL_FLAG_END_FRAME) {
FrameBookmark frameBookmark(startBookmark);
frameBookmark.numberOfCalls = numOfCalls;
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();
{
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;
delete call;
- if (ApiTrace::isCallAFrameMarker(apiCall, m_frameMarker)) {
+ if (apiCall->flags() & trace::CALL_FLAG_END_FRAME) {
break;
}
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);
signals:
void startedParsing();
void parsed(int percent);
+ void guessedApi(int api);
void finishedParsing();
void framesLoaded(const QList<ApiTraceFrame*> &frames);
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();
private:
trace::Parser m_parser;
- ApiTrace::FrameMarker m_frameMarker;
typedef QMap<int, FrameBookmark> FrameBookmarks;
FrameBookmarks m_frameBookmarks;
</widget>
</widget>
</item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="lowerLabel">
+ <property name="text">
+ <string>Lower</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="lowerSpinBox">
+ <property name="singleStep">
+ <double>0.050000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="upperLabel">
+ <property name="text">
+ <string>Upper</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="upperSpinBox">
+ <property name="singleStep">
+ <double>0.050000000000000</double>
+ </property>
+ <property name="value">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="flipCheckBox">
+ <property name="text">
+ <string>Flip</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="opaqueCheckBox">
+ <property name="text">
+ <string>Opaque</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
<resources/>
<addaction name="actionReplay"/>
<addaction name="actionStop"/>
<addaction name="actionLookupState"/>
+ <addaction name="actionShowThumbnails"/>
<addaction name="separator"/>
<addaction name="actionOptions"/>
</widget>
<string>Ctrl+L</string>
</property>
</action>
+ <action name="actionShowThumbnails">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Show &Thumbnails</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+T</string>
+ </property>
+ </action>
<action name="actionOptions">
<property name="text">
<string>Options</string>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QComboBox" name="apiComboBox">
+ <item>
+ <property name="text">
+ <string>Unknown</string>
+ </property>
+ </item>
<item>
<property name="text">
<string>GL</string>
/**************************************************************************
*
- * 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
};
+/**
+ * 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<void **>(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<T *>(alloc(sizeof(T) * n));
+ }
+
+ inline
+ ~ScopedAllocator() {
+ while (next) {
+ void *temp = *static_cast<void **>(next);
+ free(next);
+ next = temp;
+ }
+ }
+};
+
+
void
addRegion(unsigned long long address, void *buffer, unsigned long long size);
print ' const trace::Array *__a%s = dynamic_cast<const trace::Array *>(&%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:
def visitPointer(self, pointer, lvalue, rvalue):
print ' const trace::Array *__a%s = dynamic_cast<const trace::Array *>(&%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:
# 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)
'glGetString',
'glXGetClientString',
'glXGetCurrentDisplay',
+ 'glXGetCurrentContext',
'glXGetProcAddress',
'glXGetProcAddressARB',
'wglGetProcAddress',
stdout = subprocess.PIPE,
)
- calls = []
parser = Loader(p.stdout)
parser.parse()
return parser.calls
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:
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('- ')
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('+ ')
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]
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):