/************************************************************************** * * Copyright 2011 Jose Fonseca * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **************************************************************************/ #include #include #include #include #include #include "image.hpp" #include "json.hpp" #include "glproc.hpp" #include "glsize.hpp" #include "glstate.hpp" #ifdef __APPLE__ #include #ifdef __cplusplus extern "C" { #endif OSStatus CGSGetSurfaceBounds(CGSConnectionID, CGWindowID, CGSSurfaceID, CGRect *); #ifdef __cplusplus } #endif #endif /* __APPLE__ */ namespace glstate { static inline void resetPixelPackState(void) { glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); glPixelStorei(GL_PACK_SWAP_BYTES, GL_FALSE); glPixelStorei(GL_PACK_LSB_FIRST, GL_FALSE); glPixelStorei(GL_PACK_ROW_LENGTH, 0); glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); glPixelStorei(GL_PACK_SKIP_ROWS, 0); glPixelStorei(GL_PACK_SKIP_PIXELS, 0); glPixelStorei(GL_PACK_SKIP_IMAGES, 0); glPixelStorei(GL_PACK_ALIGNMENT, 1); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); } static inline void restorePixelPackState(void) { glPopClientAttrib(); } // Mapping from shader type to shader source, used to accumulated the sources // of different shaders with same type. typedef std::map ShaderMap; static void getShaderSource(ShaderMap &shaderMap, GLuint shader) { if (!shader) { return; } GLint shader_type = 0; glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type); if (!shader_type) { return; } GLint source_length = 0; glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &source_length); if (!source_length) { return; } GLchar *source = new GLchar[source_length]; GLsizei length = 0; source[0] = 0; glGetShaderSource(shader, source_length, &length, source); shaderMap[enumToString(shader_type)] += source; delete [] source; } static void getShaderObjSource(ShaderMap &shaderMap, GLhandleARB shaderObj) { if (!shaderObj) { return; } GLint object_type = 0; glGetObjectParameterivARB(shaderObj, GL_OBJECT_TYPE_ARB, &object_type); if (object_type != GL_SHADER_OBJECT_ARB) { return; } GLint shader_type = 0; glGetObjectParameterivARB(shaderObj, GL_OBJECT_SUBTYPE_ARB, &shader_type); if (!shader_type) { return; } GLint source_length = 0; glGetObjectParameterivARB(shaderObj, GL_OBJECT_SHADER_SOURCE_LENGTH_ARB, &source_length); if (!source_length) { return; } GLcharARB *source = new GLcharARB[source_length]; GLsizei length = 0; source[0] = 0; glGetShaderSource(shaderObj, source_length, &length, source); shaderMap[enumToString(shader_type)] += source; delete [] source; } static inline void dumpProgram(JSONWriter &json, GLint program) { GLint attached_shaders = 0; glGetProgramiv(program, GL_ATTACHED_SHADERS, &attached_shaders); if (!attached_shaders) { return; } ShaderMap shaderMap; GLuint *shaders = new GLuint[attached_shaders]; GLsizei count = 0; glGetAttachedShaders(program, attached_shaders, &count, shaders); std::sort(shaders, shaders + count); for (GLsizei i = 0; i < count; ++ i) { getShaderSource(shaderMap, shaders[i]); } delete [] shaders; for (ShaderMap::const_iterator it = shaderMap.begin(); it != shaderMap.end(); ++it) { json.beginMember(it->first); json.writeString(it->second); json.endMember(); } } static inline void dumpProgramObj(JSONWriter &json, GLhandleARB programObj) { GLint attached_shaders = 0; glGetObjectParameterivARB(programObj, GL_OBJECT_ATTACHED_OBJECTS_ARB, &attached_shaders); if (!attached_shaders) { return; } ShaderMap shaderMap; GLhandleARB *shaderObjs = new GLhandleARB[attached_shaders]; GLsizei count = 0; glGetAttachedObjectsARB(programObj, attached_shaders, &count, shaderObjs); std::sort(shaderObjs, shaderObjs + count); for (GLsizei i = 0; i < count; ++ i) { getShaderObjSource(shaderMap, shaderObjs[i]); } delete [] shaderObjs; for (ShaderMap::const_iterator it = shaderMap.begin(); it != shaderMap.end(); ++it) { json.beginMember(it->first); json.writeString(it->second); json.endMember(); } } /* * When fetching the uniform name of an array we usually get name[0] * so we need to cut the trailing "[0]" in order to properly construct * array names later. Otherwise we endup with stuff like * uniformArray[0][0], * uniformArray[0][1], * instead of * uniformArray[0], * uniformArray[1]. */ static std::string resolveUniformName(const GLchar *name, GLint size) { std::string qualifiedName(name); if (size > 1) { std::string::size_type nameLength = qualifiedName.length(); static const char * const arrayStart = "[0]"; static const int arrayStartLen = 3; if (qualifiedName.rfind(arrayStart) == (nameLength - arrayStartLen)) { qualifiedName = qualifiedName.substr(0, nameLength - 3); } } return qualifiedName; } static void dumpUniform(JSONWriter &json, GLint program, GLint size, GLenum type, const GLchar *name) { GLenum elemType; GLint numElems; __gl_uniform_size(type, elemType, numElems); if (elemType == GL_NONE) { return; } GLfloat fvalues[4*4]; GLdouble dvalues[4*4]; GLint ivalues[4*4]; GLuint uivalues[4*4]; GLint i, j; std::string qualifiedName = resolveUniformName(name, size); for (i = 0; i < size; ++i) { std::stringstream ss; ss << qualifiedName; if (size > 1) { ss << '[' << i << ']'; } std::string elemName = ss.str(); json.beginMember(elemName); GLint location = glGetUniformLocation(program, elemName.c_str()); if (numElems > 1) { json.beginArray(); } switch (elemType) { case GL_FLOAT: glGetUniformfv(program, location, fvalues); for (j = 0; j < numElems; ++j) { json.writeNumber(fvalues[j]); } break; case GL_DOUBLE: glGetUniformdv(program, location, dvalues); for (j = 0; j < numElems; ++j) { json.writeNumber(dvalues[j]); } break; case GL_INT: glGetUniformiv(program, location, ivalues); for (j = 0; j < numElems; ++j) { json.writeNumber(ivalues[j]); } break; case GL_UNSIGNED_INT: glGetUniformuiv(program, location, uivalues); for (j = 0; j < numElems; ++j) { json.writeNumber(uivalues[j]); } break; case GL_BOOL: glGetUniformiv(program, location, ivalues); for (j = 0; j < numElems; ++j) { json.writeBool(ivalues[j]); } break; default: assert(0); break; } if (numElems > 1) { json.endArray(); } json.endMember(); } } static void dumpUniformARB(JSONWriter &json, GLhandleARB programObj, GLint size, GLenum type, const GLchar *name) { GLenum elemType; GLint numElems; __gl_uniform_size(type, elemType, numElems); if (elemType == GL_NONE) { return; } GLfloat fvalues[4*4]; GLint ivalues[4*4]; GLint i, j; std::string qualifiedName = resolveUniformName(name, size); for (i = 0; i < size; ++i) { std::stringstream ss; ss << qualifiedName; if (size > 1) { ss << '[' << i << ']'; } std::string elemName = ss.str(); json.beginMember(elemName); GLint location = glGetUniformLocationARB(programObj, elemName.c_str()); if (numElems > 1) { json.beginArray(); } switch (elemType) { case GL_DOUBLE: // glGetUniformdvARB does not exists case GL_FLOAT: glGetUniformfvARB(programObj, location, fvalues); for (j = 0; j < numElems; ++j) { json.writeNumber(fvalues[j]); } break; case GL_UNSIGNED_INT: // glGetUniformuivARB does not exists case GL_INT: glGetUniformivARB(programObj, location, ivalues); for (j = 0; j < numElems; ++j) { json.writeNumber(ivalues[j]); } break; case GL_BOOL: glGetUniformivARB(programObj, location, ivalues); for (j = 0; j < numElems; ++j) { json.writeBool(ivalues[j]); } break; default: assert(0); break; } if (numElems > 1) { json.endArray(); } json.endMember(); } } static inline void dumpProgramUniforms(JSONWriter &json, GLint program) { GLint active_uniforms = 0; glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &active_uniforms); if (!active_uniforms) { return; } GLint active_uniform_max_length = 0; glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &active_uniform_max_length); GLchar *name = new GLchar[active_uniform_max_length]; if (!name) { return; } for (GLint index = 0; index < active_uniforms; ++index) { GLsizei length = 0; GLint size = 0; GLenum type = GL_NONE; glGetActiveUniform(program, index, active_uniform_max_length, &length, &size, &type, name); dumpUniform(json, program, size, type, name); } delete [] name; } static inline void dumpProgramObjUniforms(JSONWriter &json, GLhandleARB programObj) { GLint active_uniforms = 0; glGetObjectParameterivARB(programObj, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &active_uniforms); if (!active_uniforms) { return; } GLint active_uniform_max_length = 0; glGetObjectParameterivARB(programObj, GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB, &active_uniform_max_length); GLchar *name = new GLchar[active_uniform_max_length]; if (!name) { return; } for (GLint index = 0; index < active_uniforms; ++index) { GLsizei length = 0; GLint size = 0; GLenum type = GL_NONE; glGetActiveUniformARB(programObj, index, active_uniform_max_length, &length, &size, &type, name); dumpUniformARB(json, programObj, size, type, name); } delete [] name; } static inline void dumpArbProgram(JSONWriter &json, GLenum target) { if (!glIsEnabled(target)) { return; } GLint program_length = 0; glGetProgramivARB(target, GL_PROGRAM_LENGTH_ARB, &program_length); if (!program_length) { return; } GLchar *source = new GLchar[program_length + 1]; source[0] = 0; glGetProgramStringARB(target, GL_PROGRAM_STRING_ARB, source); source[program_length] = 0; json.beginMember(enumToString(target)); json.writeString(source); json.endMember(); delete [] source; } static inline void dumpArbProgramUniforms(JSONWriter &json, GLenum target, const char *prefix) { if (!glIsEnabled(target)) { return; } GLint program_parameters = 0; glGetProgramivARB(target, GL_PROGRAM_PARAMETERS_ARB, &program_parameters); if (!program_parameters) { return; } GLint max_program_local_parameters = 0; glGetProgramivARB(target, GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB, &max_program_local_parameters); for (GLint index = 0; index < max_program_local_parameters; ++index) { GLdouble params[4] = {0, 0, 0, 0}; glGetProgramLocalParameterdvARB(target, index, params); if (!params[0] && !params[1] && !params[2] && !params[3]) { continue; } char name[256]; snprintf(name, sizeof name, "%sprogram.local[%i]", prefix, index); json.beginMember(name); json.beginArray(); json.writeNumber(params[0]); json.writeNumber(params[1]); json.writeNumber(params[2]); json.writeNumber(params[3]); json.endArray(); json.endMember(); } GLint max_program_env_parameters = 0; glGetProgramivARB(target, GL_MAX_PROGRAM_ENV_PARAMETERS_ARB, &max_program_env_parameters); for (GLint index = 0; index < max_program_env_parameters; ++index) { GLdouble params[4] = {0, 0, 0, 0}; glGetProgramEnvParameterdvARB(target, index, params); if (!params[0] && !params[1] && !params[2] && !params[3]) { continue; } char name[256]; snprintf(name, sizeof name, "%sprogram.env[%i]", prefix, index); json.beginMember(name); json.beginArray(); json.writeNumber(params[0]); json.writeNumber(params[1]); json.writeNumber(params[2]); json.writeNumber(params[3]); json.endArray(); json.endMember(); } } static inline void dumpShadersUniforms(JSONWriter &json) { GLint program = 0; glGetIntegerv(GL_CURRENT_PROGRAM, &program); GLhandleARB programObj = glGetHandleARB(GL_PROGRAM_OBJECT_ARB); json.beginMember("shaders"); json.beginObject(); if (program) { dumpProgram(json, program); } else if (programObj) { dumpProgramObj(json, programObj); } else { dumpArbProgram(json, GL_FRAGMENT_PROGRAM_ARB); dumpArbProgram(json, GL_VERTEX_PROGRAM_ARB); } json.endObject(); json.endMember(); // shaders json.beginMember("uniforms"); json.beginObject(); if (program) { dumpProgramUniforms(json, program); } else if (programObj) { dumpProgramObjUniforms(json, programObj); } else { dumpArbProgramUniforms(json, GL_FRAGMENT_PROGRAM_ARB, "fp."); dumpArbProgramUniforms(json, GL_VERTEX_PROGRAM_ARB, "vp."); } json.endObject(); json.endMember(); // uniforms } static inline void dumpTextureImage(JSONWriter &json, 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]; resetPixelPackState(); glGetTexImage(target, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 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, 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, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level); } } else { dumpTextureImage(json, target, level); } ++level; } while(true); } static inline void dumpTextures(JSONWriter &json) { 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, GL_TEXTURE_1D, GL_TEXTURE_BINDING_1D); dumpTexture(json, GL_TEXTURE_2D, GL_TEXTURE_BINDING_2D); dumpTexture(json, GL_TEXTURE_3D, GL_TEXTURE_BINDING_3D); dumpTexture(json, GL_TEXTURE_RECTANGLE, GL_TEXTURE_BINDING_RECTANGLE); dumpTexture(json, 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(_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; #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; #else #if !TRACE_EGL 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; #else return false; #endif #endif return true; } 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) { GLint channels = __gl_format_channels(format); if (channels > 4) { return NULL; } GLint draw_framebuffer = 0; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw_framebuffer); GLint draw_buffer = GL_NONE; GLint width, height; if (draw_framebuffer) { glGetIntegerv(GL_DRAW_BUFFER0, &draw_buffer); if (draw_buffer == GL_NONE) { return NULL; } if (!getFramebufferAttachmentSize(GL_DRAW_FRAMEBUFFER, draw_buffer, &width, &height)) { return NULL; } } else { 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; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read_framebuffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, draw_framebuffer); GLint read_buffer = 0; glGetIntegerv(GL_READ_BUFFER, &read_buffer); glReadBuffer(draw_buffer); // TODO: reset imaging state too resetPixelPackState(); glReadPixels(0, 0, width, height, format, GL_UNSIGNED_BYTE, image->pixels); restorePixelPackState(); 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); 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 resetPixelPackState(); glReadPixels(0, 0, width, height, format, GL_UNSIGNED_BYTE, pixels); restorePixelPackState(); json.beginMember("__data__"); char *pngBuffer; int pngBufferSize; image::writePixelsToBuffer(pixels, width, height, channels, true, &pngBuffer, &pngBufferSize); //std::cerr <<" Before = "<<(width * height * channels * sizeof *pixels) // <<", after = "<= GL_COLOR_ATTACHMENT0 && draw_buffer < GL_COLOR_ATTACHMENT0 + max_color_attachments) { attachment = draw_buffer; } else { std::cerr << "warning: unexpected GL_DRAW_BUFFER" << i << " = " << draw_buffer << "\n"; attachment = GL_COLOR_ATTACHMENT0; } GLint alpha_size = 0; glGetFramebufferAttachmentParameteriv(target, attachment, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, &alpha_size); GLenum format = alpha_size ? GL_RGBA : GL_RGB; dumpFramebufferAttachment(json, target, attachment, format); } } glReadBuffer(read_buffer); 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) { 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); } 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, 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, GL_PIXEL_PACK_BUFFER_BINDING, GL_PIXEL_UNPACK_BUFFER_BINDING, GL_TEXTURE_BINDING_1D, GL_TEXTURE_BINDING_2D, GL_TEXTURE_BINDING_3D, GL_TEXTURE_BINDING_RECTANGLE, GL_TEXTURE_BINDING_CUBE_MAP, GL_DRAW_FRAMEBUFFER_BINDING, GL_READ_FRAMEBUFFER_BINDING, GL_RENDERBUFFER_BINDING, GL_DRAW_BUFFER0, GL_DRAW_BUFFER1, GL_DRAW_BUFFER2, GL_DRAW_BUFFER3, GL_DRAW_BUFFER4, GL_DRAW_BUFFER5, GL_DRAW_BUFFER6, GL_DRAW_BUFFER7, }; #define NUM_BINDINGS sizeof(bindings)/sizeof(bindings[0]) void dumpCurrentContext(std::ostream &os) { JSONWriter json(os); #ifndef NDEBUG GLint old_bindings[NUM_BINDINGS]; for (unsigned i = 0; i < NUM_BINDINGS; ++i) { old_bindings[i] = 0; glGetIntegerv(bindings[i], &old_bindings[i]); } #endif dumpParameters(json); dumpShadersUniforms(json); dumpTextures(json); dumpFramebuffer(json); #ifndef NDEBUG for (unsigned i = 0; i < NUM_BINDINGS; ++i) { GLint new_binding = 0; glGetIntegerv(bindings[i], &new_binding); if (new_binding != old_bindings[i]) { std::cerr << "warning: " << enumToString(bindings[i]) << " was clobbered\n"; } } #endif } } /* namespace glstate */