1ac8b72f4f
always use GL_RGBA, GL_UNSIGNED_BYTE for screen capture and make sure to handle GL_BGRA_EXT used on some gpu. Change-Id: If9c973677fec8a5c4e72be22e7ef7d4bf5f008f4
428 lines
14 KiB
C++
428 lines
14 KiB
C++
/*
|
|
** 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<int>(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
|