/* ** Copyright 2011, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #include "header.h" extern "C" { #include "liblzf/lzf.h" } namespace android { static pthread_key_t dbgEGLThreadLocalStorageKey = -1; static pthread_mutex_t gThreadLocalStorageKeyMutex = PTHREAD_MUTEX_INITIALIZER; DbgContext * getDbgContextThreadSpecific() { return (DbgContext*)pthread_getspecific(dbgEGLThreadLocalStorageKey); } DbgContext::DbgContext(const unsigned version, const gl_hooks_t * const hooks, const unsigned MAX_VERTEX_ATTRIBS) : lzf_buf(NULL), lzf_readIndex(0), lzf_refSize(0), lzf_refBufSize(0) , version(version), hooks(hooks) , MAX_VERTEX_ATTRIBS(MAX_VERTEX_ATTRIBS) , readBytesPerPixel(4) , captureSwap(0), captureDraw(0) , vertexAttribs(new VertexAttrib[MAX_VERTEX_ATTRIBS]) , hasNonVBOAttribs(false), indexBuffers(NULL), indexBuffer(NULL) , program(0), maxAttrib(0) { lzf_ref[0] = lzf_ref[1] = NULL; for (unsigned i = 0; i < MAX_VERTEX_ATTRIBS; i++) vertexAttribs[i] = VertexAttrib(); memset(&expectResponse, 0, sizeof(expectResponse)); } DbgContext::~DbgContext() { delete vertexAttribs; free(lzf_buf); free(lzf_ref[0]); free(lzf_ref[1]); } DbgContext* CreateDbgContext(const unsigned version, const gl_hooks_t * const hooks) { pthread_mutex_lock(&gThreadLocalStorageKeyMutex); if (dbgEGLThreadLocalStorageKey == -1) pthread_key_create(&dbgEGLThreadLocalStorageKey, NULL); pthread_mutex_unlock(&gThreadLocalStorageKeyMutex); assert(version < 2); assert(GL_NO_ERROR == hooks->gl.glGetError()); GLint MAX_VERTEX_ATTRIBS = 0; hooks->gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &MAX_VERTEX_ATTRIBS); DbgContext* dbg = new DbgContext(version, hooks, MAX_VERTEX_ATTRIBS); glesv2debugger::Message msg, cmd; msg.set_context_id(reinterpret_cast(dbg)); msg.set_expect_response(false); msg.set_type(msg.Response); msg.set_function(msg.SETPROP); msg.set_prop(msg.GLConstant); msg.set_arg0(GL_MAX_VERTEX_ATTRIBS); msg.set_arg1(MAX_VERTEX_ATTRIBS); Send(msg, cmd); GLint MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0; hooks->gl.glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &MAX_COMBINED_TEXTURE_IMAGE_UNITS); msg.set_arg0(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS); msg.set_arg1(MAX_COMBINED_TEXTURE_IMAGE_UNITS); Send(msg, cmd); pthread_setspecific(dbgEGLThreadLocalStorageKey, dbg); return dbg; } void dbgReleaseThread() { delete getDbgContextThreadSpecific(); } unsigned GetBytesPerPixel(const GLenum format, const GLenum type) { switch (type) { case GL_UNSIGNED_SHORT_5_6_5: case GL_UNSIGNED_SHORT_4_4_4_4: case GL_UNSIGNED_SHORT_5_5_5_1: return 2; case GL_UNSIGNED_BYTE: break; default: LOGE("GetBytesPerPixel: unknown type %x", type); } switch (format) { case GL_ALPHA: case GL_LUMINANCE: return 1; case GL_LUMINANCE_ALPHA: return 2; case GL_RGB: return 3; case GL_RGBA: case 0x80E1: // GL_BGRA_EXT return 4; default: LOGE("GetBytesPerPixel: unknown format %x", format); } return 1; // in doubt... } void DbgContext::Fetch(const unsigned index, std::string * const data) const { // VBO data is already on client, just send user pointer data for (unsigned i = 0; i < maxAttrib; i++) { if (!vertexAttribs[i].enabled) continue; if (vertexAttribs[i].buffer > 0) continue; const char * ptr = (const char *)vertexAttribs[i].ptr; ptr += index * vertexAttribs[i].stride; data->append(ptr, vertexAttribs[i].elemSize); } } void DbgContext::Compress(const void * in_data, unsigned int in_len, std::string * const outStr) { if (!lzf_buf) lzf_buf = (char *)malloc(LZF_CHUNK_SIZE); assert(lzf_buf); const uint32_t totalDecompSize = in_len; outStr->append((const char *)&totalDecompSize, sizeof(totalDecompSize)); for (unsigned int i = 0; i < in_len; i += LZF_CHUNK_SIZE) { uint32_t chunkSize = LZF_CHUNK_SIZE; if (i + LZF_CHUNK_SIZE > in_len) chunkSize = in_len - i; const uint32_t compSize = lzf_compress((const char *)in_data + i, chunkSize, lzf_buf, LZF_CHUNK_SIZE); outStr->append((const char *)&chunkSize, sizeof(chunkSize)); outStr->append((const char *)&compSize, sizeof(compSize)); if (compSize > 0) outStr->append(lzf_buf, compSize); else // compressed chunk bigger than LZF_CHUNK_SIZE (and uncompressed) outStr->append((const char *)in_data + i, chunkSize); } } unsigned char * DbgContext::Decompress(const void * in, const unsigned int inLen, unsigned int * const outLen) { assert(inLen > 4 * 3); if (inLen < 4 * 3) return NULL; *outLen = *(uint32_t *)in; unsigned char * const out = (unsigned char *)malloc(*outLen); unsigned int outPos = 0; const unsigned char * const end = (const unsigned char *)in + inLen; for (const unsigned char * inData = (const unsigned char *)in + 4; inData < end; ) { const uint32_t chunkOut = *(uint32_t *)inData; inData += 4; const uint32_t chunkIn = *(uint32_t *)inData; inData += 4; if (chunkIn > 0) { assert(inData + chunkIn <= end); assert(outPos + chunkOut <= *outLen); outPos += lzf_decompress(inData, chunkIn, out + outPos, chunkOut); inData += chunkIn; } else { assert(inData + chunkOut <= end); assert(outPos + chunkOut <= *outLen); memcpy(out + outPos, inData, chunkOut); inData += chunkOut; outPos += chunkOut; } } return out; } void * DbgContext::GetReadPixelsBuffer(const unsigned size) { if (lzf_refBufSize < size + 8) { lzf_refBufSize = size + 8; lzf_ref[0] = (unsigned *)realloc(lzf_ref[0], lzf_refBufSize); assert(lzf_ref[0]); memset(lzf_ref[0], 0, lzf_refBufSize); lzf_ref[1] = (unsigned *)realloc(lzf_ref[1], lzf_refBufSize); assert(lzf_ref[1]); memset(lzf_ref[1], 0, lzf_refBufSize); } if (lzf_refSize != size) // need to clear unused ref to maintain consistency { // since ref and src are swapped each time memset((char *)lzf_ref[0] + lzf_refSize, 0, lzf_refBufSize - lzf_refSize); memset((char *)lzf_ref[1] + lzf_refSize, 0, lzf_refBufSize - lzf_refSize); } lzf_refSize = size; lzf_readIndex ^= 1; return lzf_ref[lzf_readIndex]; } void DbgContext::CompressReadPixelBuffer(std::string * const outStr) { assert(lzf_ref[0] && lzf_ref[1]); unsigned * const ref = lzf_ref[lzf_readIndex ^ 1]; unsigned * const src = lzf_ref[lzf_readIndex]; for (unsigned i = 0; i < lzf_refSize / sizeof(*ref) + 1; i++) ref[i] ^= src[i]; Compress(ref, lzf_refSize, outStr); } char * DbgContext::GetBuffer() { if (!lzf_buf) lzf_buf = (char *)malloc(LZF_CHUNK_SIZE); assert(lzf_buf); return lzf_buf; } unsigned int DbgContext::GetBufferSize() { if (!lzf_buf) lzf_buf = (char *)malloc(LZF_CHUNK_SIZE); assert(lzf_buf); if (lzf_buf) return LZF_CHUNK_SIZE; else return 0; } void DbgContext::glUseProgram(GLuint program) { while (GLenum error = hooks->gl.glGetError()) LOGD("DbgContext::glUseProgram(%u): before glGetError() = 0x%.4X", program, error); this->program = program; maxAttrib = 0; if (program == 0) return; GLint activeAttributes = 0; hooks->gl.glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &activeAttributes); maxAttrib = 0; GLint maxNameLen = -1; hooks->gl.glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxNameLen); char * name = new char [maxNameLen + 1]; name[maxNameLen] = 0; // find total number of attribute slots used for (unsigned i = 0; i < activeAttributes; i++) { GLint size = -1; GLenum type = -1; hooks->gl.glGetActiveAttrib(program, i, maxNameLen + 1, NULL, &size, &type, name); GLint slot = hooks->gl.glGetAttribLocation(program, name); assert(slot >= 0); switch (type) { case GL_FLOAT: case GL_FLOAT_VEC2: case GL_FLOAT_VEC3: case GL_FLOAT_VEC4: slot += size; break; case GL_FLOAT_MAT2: slot += size * 2; break; case GL_FLOAT_MAT3: slot += size * 3; break; case GL_FLOAT_MAT4: slot += size * 4; break; default: assert(0); } if (slot > maxAttrib) maxAttrib = slot; } delete name; while (GLenum error = hooks->gl.glGetError()) LOGD("DbgContext::glUseProgram(%u): after glGetError() = 0x%.4X", program, error); } static bool HasNonVBOAttribs(const DbgContext * const ctx) { bool need = false; for (unsigned i = 0; !need && i < ctx->maxAttrib; i++) if (ctx->vertexAttribs[i].enabled && ctx->vertexAttribs[i].buffer == 0) need = true; return need; } void DbgContext::glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr) { assert(GL_NO_ERROR == hooks->gl.glGetError()); assert(indx < MAX_VERTEX_ATTRIBS); vertexAttribs[indx].size = size; vertexAttribs[indx].type = type; vertexAttribs[indx].normalized = normalized; switch (type) { case GL_FLOAT: vertexAttribs[indx].elemSize = sizeof(GLfloat) * size; break; case GL_INT: case GL_UNSIGNED_INT: vertexAttribs[indx].elemSize = sizeof(GLint) * size; break; case GL_SHORT: case GL_UNSIGNED_SHORT: vertexAttribs[indx].elemSize = sizeof(GLshort) * size; break; case GL_BYTE: case GL_UNSIGNED_BYTE: vertexAttribs[indx].elemSize = sizeof(GLbyte) * size; break; default: assert(0); } if (0 == stride) stride = vertexAttribs[indx].elemSize; vertexAttribs[indx].stride = stride; vertexAttribs[indx].ptr = ptr; hooks->gl.glGetVertexAttribiv(indx, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, (GLint *)&vertexAttribs[indx].buffer); hasNonVBOAttribs = HasNonVBOAttribs(this); } void DbgContext::glEnableVertexAttribArray(GLuint index) { if (index >= MAX_VERTEX_ATTRIBS) return; vertexAttribs[index].enabled = true; hasNonVBOAttribs = HasNonVBOAttribs(this); } void DbgContext::glDisableVertexAttribArray(GLuint index) { if (index >= MAX_VERTEX_ATTRIBS) return; vertexAttribs[index].enabled = false; hasNonVBOAttribs = HasNonVBOAttribs(this); } void DbgContext::glBindBuffer(GLenum target, GLuint buffer) { if (GL_ELEMENT_ARRAY_BUFFER != target) return; if (0 == buffer) { indexBuffer = NULL; return; } VBO * b = indexBuffers; indexBuffer = NULL; while (b) { if (b->name == buffer) { assert(GL_ELEMENT_ARRAY_BUFFER == b->target); indexBuffer = b; break; } b = b->next; } if (!indexBuffer) indexBuffer = indexBuffers = new VBO(buffer, target, indexBuffers); } void DbgContext::glBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage) { if (GL_ELEMENT_ARRAY_BUFFER != target) return; assert(indexBuffer); assert(size >= 0); indexBuffer->size = size; indexBuffer->data = realloc(indexBuffer->data, size); memcpy(indexBuffer->data, data, size); } void DbgContext::glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data) { if (GL_ELEMENT_ARRAY_BUFFER != target) return; assert(indexBuffer); assert(size >= 0); assert(offset >= 0); assert(offset + size <= indexBuffer->size); memcpy((char *)indexBuffer->data + offset, data, size); } void DbgContext::glDeleteBuffers(GLsizei n, const GLuint *buffers) { for (unsigned i = 0; i < n; i++) { for (unsigned j = 0; j < MAX_VERTEX_ATTRIBS; j++) if (buffers[i] == vertexAttribs[j].buffer) { vertexAttribs[j].buffer = 0; vertexAttribs[j].enabled = false; } VBO * b = indexBuffers, * previous = NULL; while (b) { if (b->name == buffers[i]) { assert(GL_ELEMENT_ARRAY_BUFFER == b->target); if (indexBuffer == b) indexBuffer = NULL; if (previous) previous->next = b->next; else indexBuffers = b->next; free(b->data); delete b; break; } previous = b; b = b->next; } } hasNonVBOAttribs = HasNonVBOAttribs(this); } }; // namespace android