blob: b564d5e5e08362281648dc3cc8bd459833dd489e [file] [log] [blame]
/*
* Copyright (C) 2016 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 "connection_header.h"
#include "connection_stream.h"
#include "gles_exports.h"
#include "spy.h"
#include <gapic/encoder.h>
#include <gapic/gl/formats.h>
#include <gapic/lock.h>
#include <gapic/log.h>
#include <gapic/target.h>
#include <gapii/gles_constants.h>
#include <vector>
#if TARGET_OS == GAPID_OS_WINDOWS
#include "windows/wgl.h"
#endif // TARGET_OS
using namespace gapii::GLenum;
namespace {
typedef uint32_t EGLint;
const EGLint EGL_TRUE = 0x0001;
const EGLint EGL_ALPHA_SIZE = 0x3021;
const EGLint EGL_BLUE_SIZE = 0x3022;
const EGLint EGL_GREEN_SIZE = 0x3023;
const EGLint EGL_RED_SIZE = 0x3024;
const EGLint EGL_DEPTH_SIZE = 0x3025;
const EGLint EGL_STENCIL_SIZE = 0x3026;
const EGLint EGL_CONFIG_ID = 0x3028;
const EGLint EGL_NONE = 0x3038;
const EGLint EGL_HEIGHT = 0x3056;
const EGLint EGL_WIDTH = 0x3057;
const EGLint EGL_SWAP_BEHAVIOR = 0x3093;
const EGLint EGL_BUFFER_PRESERVED = 0x3094;
const EGLint EGL_CONTEXT_MAJOR_VERSION_KHR = 0x3098;
const uint32_t GLX_WIDTH = 0x801D;
const uint32_t GLX_HEIGHT = 0x801E;
const uint32_t kCGLCPSurfaceBackingSize = 304;
inline bool isLittleEndian() {
union {
uint32_t i;
char c[4];
} u;
u.i = 0x01020304;
return u.c[0] == 4;
}
gapic::Mutex gMutex; // Guards gSpy.
std::unique_ptr<gapii::Spy> gSpy;
} // anonymous namespace
namespace gapii {
Spy* Spy::get() {
gapic::Lock<gapic::Mutex> lock(&gMutex);
if (!gSpy) {
GAPID_INFO("Constructing spy...");
gSpy.reset(new Spy());
GAPID_INFO("Registering spy symbols...");
for (int i = 0; kGLESExports[i].mName != NULL; ++i) {
gSpy->RegisterSymbol(kGLESExports[i].mName, kGLESExports[i].mFunc);
}
}
return gSpy.get();
}
Spy::Spy()
: mNumFrames(0)
, mNumDraws(0)
, mNumDrawsPerFrame(0)
, mObserveFrameFrequency(0)
, mObserveDrawFrequency(0)
, mDisablePrecompiledShaders(false)
, mRecordGLErrorState(false) {
#if TARGET_OS == GAPID_OS_ANDROID
// Use a "localabstract" pipe on Android to prevent depending on the traced application
// having the INTERNET permission set, required for opening and listening on a TCP socket.
auto conn = ConnectionStream::listenPipe("gapii", true);
#else // TARGET_OS
auto conn = ConnectionStream::listenSocket("127.0.0.1", "9286");
#endif // TARGET_OS
GAPID_INFO("Connection made");
ConnectionHeader header;
if (header.read(conn.get())) {
mObserveFrameFrequency = header.mObserveFrameFrequency;
mObserveDrawFrequency = header.mObserveDrawFrequency;
mDisablePrecompiledShaders =
(header.mFlags & ConnectionHeader::FLAG_DISABLE_PRECOMPILED_SHADERS) != 0;
mRecordGLErrorState =
(header.mFlags & ConnectionHeader::FLAG_RECORD_ERROR_STATE) != 0;
} else {
GAPID_WARNING("Failed to read connection header");
}
GAPID_INFO("GAPII connection established. Settings:");
GAPID_INFO("Observe framebuffer every %d frames", mObserveFrameFrequency);
GAPID_INFO("Observe framebuffer every %d draws", mObserveDrawFrequency);
GAPID_INFO("Disable precompiled shaders: %s", mDisablePrecompiledShaders ? "true" : "false");
mEncoder = std::shared_ptr<gapic::Encoder>(new gapic::Encoder(conn));
mEncoder->String("GapiiTraceFile_V1.1");
GlesSpy::init(mEncoder);
GlesSpy::architecture(alignof(void*), sizeof(void*), sizeof(int), isLittleEndian());
}
void Spy::resolveImports() {
GlesSpy::mImports.resolve();
}
EGLBoolean Spy::eglInitialize(EGLDisplay dpy, EGLint* major, EGLint* minor) {
EGLBoolean res = GlesSpy::eglInitialize(dpy, major, minor);
if (res != 0) {
resolveImports(); // Imports may have changed. Re-resolve.
}
return res;
}
EGLContext Spy::eglCreateContext(EGLDisplay display, EGLConfig config,
EGLContext share_context, EGLint* attrib_list) {
EGLContext res = GlesSpy::eglCreateContext(display, config, share_context, attrib_list);
for (int i = 0; attrib_list != nullptr && attrib_list[i] != EGL_NONE; i += 2) {
if (attrib_list[i] == EGL_CONTEXT_MAJOR_VERSION_KHR) {
GAPID_INFO("eglCreateContext requested GL version %i", attrib_list[i+1]);
}
}
return res;
}
#define EGL_QUERY_SURFACE(name, var) \
if (GlesSpy::mImports.eglQuerySurface(display, draw, name, var) != EGL_TRUE) { \
GAPID_WARNING("eglQuerySurface(0x%p, 0x%p, " #name ", " #var ") failed", display, draw); \
}
#define EGL_GET_CONFIG_ATTRIB(name, var) \
if (GlesSpy::mImports.eglGetConfigAttrib(display, config, name, var) != EGL_TRUE) { \
GAPID_WARNING("eglGetConfigAttrib(0x%p, 0x%p, " #name ", " #var ") failed", display, config); \
}
EGLBoolean Spy::eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context) {
EGLBoolean res = GlesSpy::eglMakeCurrent(display, draw, read, context);
if (res != 0 && draw != nullptr) {
EGLint width = 0;
EGLint height = 0;
EGLint swapBehavior = 0;
EGL_QUERY_SURFACE(EGL_WIDTH, &width);
EGL_QUERY_SURFACE(EGL_HEIGHT, &height);
EGL_QUERY_SURFACE(EGL_SWAP_BEHAVIOR, &swapBehavior);
// Get the backbuffer formats.
uint32_t backbufferColorFmt = GL_RGBA8;
uint32_t backbufferDepthFmt = GL_DEPTH24_STENCIL8;
uint32_t backbufferStencilFmt = GL_DEPTH24_STENCIL8;
bool usingDefaults = true;
EGLint configId = 0;
if (GlesSpy::mImports.eglQueryContext(display, context, EGL_CONFIG_ID, &configId) == EGL_TRUE) {
GAPID_INFO("Active context ID: %d", configId);
EGLint attribs[] = { EGL_CONFIG_ID, configId, EGL_NONE };
EGLConfig config;
EGLint count = 0;
if (GlesSpy::mImports.eglChooseConfig(display, attribs, &config, 1, &count) == EGL_TRUE) {
EGLint r = 0, g = 0, b = 0, a = 0, d = 0, s = 0;
EGL_GET_CONFIG_ATTRIB(EGL_RED_SIZE, &r);
EGL_GET_CONFIG_ATTRIB(EGL_GREEN_SIZE, &g);
EGL_GET_CONFIG_ATTRIB(EGL_BLUE_SIZE, &b);
EGL_GET_CONFIG_ATTRIB(EGL_ALPHA_SIZE, &a);
EGL_GET_CONFIG_ATTRIB(EGL_DEPTH_SIZE, &d);
EGL_GET_CONFIG_ATTRIB(EGL_STENCIL_SIZE, &s);
GAPID_INFO("Framebuffer config: R%d G%d B%d A%d D%d S%d", r, g, b, a, d, s);
// Get the formats from the bit depths.
if (!gapic::gl::getColorFormat(r, g, b, a, backbufferColorFmt)) {
GAPID_WARNING("getColorFormat(%d, %d, %d, %d) failed", r, g, b, a);
}
if (!gapic::gl::getDepthStencilFormat(d, s, backbufferDepthFmt, backbufferStencilFmt)) {
GAPID_WARNING("getDepthStencilFormat(%d, %d) failed", d, s);
}
usingDefaults = false;
} else {
GAPID_WARNING("eglChooseConfig() failed for config ID %d. Assuming defaults.", configId);
}
} else {
GAPID_WARNING("eglQueryContext(0x%p, 0x%p, EGL_CONFIG_ID, &configId) failed. "
"Assuming defaults.", display, context);
}
bool resetViewportScissor = true;
bool preserveBuffersOnSwap = swapBehavior == EGL_BUFFER_PRESERVED;
setContextInfo(width, height,
backbufferColorFmt, backbufferDepthFmt, backbufferStencilFmt,
resetViewportScissor,
preserveBuffersOnSwap);
}
return res;
}
#undef EGL_QUERY_SURFACE
#undef EGL_GET_CONFIG_ATTRIB
BOOL Spy::wglMakeCurrent(HDC hdc, HGLRC hglrc) {
BOOL res = GlesSpy::wglMakeCurrent(hdc, hglrc);
if (res != 0 && hglrc != nullptr) {
resolveImports(); // Imports may have changed. Re-resolve.
#if TARGET_OS == GAPID_OS_WINDOWS
wgl::FramebufferInfo info;
wgl::getFramebufferInfo(hdc, info);
setContextInfo(info.width, info.height,
info.colorFormat, info.depthFormat, info.stencilFormat,
/* resetViewportScissor */ true,
/* preserveBuffersOnSwap */ false);
#endif // TARGET_OS
}
return res;
}
CGLError Spy::CGLSetCurrentContext(CGLContextObj ctx) {
CGLError err = GlesSpy::CGLSetCurrentContext(ctx);
if (err == 0 && ctx != nullptr) {
CGSConnectionID cid;
CGSWindowID wid;
CGSSurfaceID sid;
double bounds[4] = {0, 0, 0, 0};
if (GlesSpy::mImports.CGLGetSurface(ctx, &cid, &wid, &sid) == 0) {
GlesSpy::mImports.CGSGetSurfaceBounds(cid, wid, sid, bounds);
} else {
GAPID_WARNING("Could not get CGL surface");
}
int width = bounds[2] - bounds[0]; // size.x - origin.x
int height = bounds[3] - bounds[1]; // size.y - origin.y
// TODO: Probe formats
setContextInfo(width, height,
GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8,
/* resetViewportScissor */ true,
/* preserveBuffersOnSwap */ false);
}
return err;
}
Bool Spy::glXMakeContextCurrent(void* display, GLXDrawable draw, GLXDrawable read, GLXContext ctx) {
Bool res = GlesSpy::glXMakeContextCurrent(display, draw, read, ctx);
if (res != 0 && display != nullptr) {
int width = 0;
int height = 0;
GlesSpy::mImports.glXQueryDrawable(display, draw, GLX_WIDTH, &width);
GlesSpy::mImports.glXQueryDrawable(display, draw, GLX_HEIGHT, &height);
// TODO: Probe formats
setContextInfo(width, height,
GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8,
/* resetViewportScissor */ true,
/* preserveBuffersOnSwap */ false);
}
return res;
}
Bool Spy::glXMakeCurrent(void* display, GLXDrawable drawable, GLXContext ctx) {
Bool res = GlesSpy::glXMakeCurrent(display, drawable, ctx);
if (res != 0 && display != nullptr) {
int width = 0;
int height = 0;
GlesSpy::mImports.glXQueryDrawable(display, drawable, GLX_WIDTH, &width);
GlesSpy::mImports.glXQueryDrawable(display, drawable, GLX_HEIGHT, &height);
// TODO: Probe formats
setContextInfo(width, height,
GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8,
/* resetViewportScissor */ true,
/* preserveBuffersOnSwap */ false);
}
return res;
}
void Spy::onPostDrawCall() {
if (mObserveDrawFrequency != 0 && (mNumDraws % mObserveDrawFrequency == 0)) {
GAPID_INFO("Observe framebuffer after draw call %d", mNumDraws);
observeFramebuffer();
}
mNumDraws++;
mNumDrawsPerFrame++;
}
void Spy::onPreEndOfFrame() {
if (mObserveFrameFrequency != 0 && (mNumFrames % mObserveFrameFrequency == 0)) {
GAPID_INFO("Observe framebuffer after frame %d", mNumFrames);
observeFramebuffer();
}
GAPID_INFO("NumFrames:%d NumDraws:%d NumDrawsPerFrame:%d",
mNumFrames, mNumDraws, mNumDrawsPerFrame);
mNumFrames++;
mNumDrawsPerFrame = 0;
}
// observeFramebuffer captures the currently bound framebuffer, and writes
// it to a FramebufferObservation atom.
void Spy::observeFramebuffer() {
uint32_t w = 0;
uint32_t h = 0;
if (!getFramebufferAttachmentSize(w, h)) {
return; // Could not get the framebuffer size.
}
uint32_t size = w * h * 4;
uint8_t* data = new uint8_t[size];
if (data != nullptr) {
GlesSpy::mImports.glReadPixels(0, 0, int32_t(w), int32_t(h),
GL_RGBA, GL_UNSIGNED_BYTE, data);
gapic::coder::atom::FramebufferObservation coder(w, h, gapic::Vector<uint8_t>(data, size));
mEncoder->Variant(&coder);
delete [] data;
} else {
GAPID_WARNING("Failed to allocate buffer to observe framebuffer of size %ux%u", w, h);
}
}
// TODO: When gfx api macros produce functions instead of inlining, move this logic
// to the gles.api file.
bool Spy::getFramebufferAttachmentSize(uint32_t& width, uint32_t& height) {
std::shared_ptr<Context> ctx = GlesSpy::Contexts[GlesSpy::CurrentThread];
if (ctx == nullptr) {
return false;
}
auto framebufferID = ctx->mBoundFramebuffers.find(GL_READ_FRAMEBUFFER);
if (framebufferID == ctx->mBoundFramebuffers.end()) {
return false;
}
auto framebuffer = ctx->mInstances.mFramebuffers.find(framebufferID->second);
if (framebuffer == ctx->mInstances.mFramebuffers.end()) {
return false;
}
auto attachment = framebuffer->second->mAttachments.find(GL_COLOR_ATTACHMENT0);
if (attachment == framebuffer->second->mAttachments.end()) {
return false;
}
switch (attachment->second.mObjectType) {
case GL_TEXTURE: {
auto t = ctx->mInstances.mTextures.find(attachment->second.mObjectName);
if (t == ctx->mInstances.mTextures.end()) {
return false;
}
switch (t->second->mKind) {
case TextureKind::TEXTURE2D: {
auto l = t->second->mTexture2D.find(attachment->second.mTextureLevel);
if (l == t->second->mTexture2D.end()) {
return false;
}
width = uint32_t(l->second.mWidth);
height = uint32_t(l->second.mHeight);
return true;
}
case TextureKind::CUBEMAP: {
auto l = t->second->mCubemap.find(attachment->second.mTextureLevel);
if (l == t->second->mCubemap.end()) {
return false;
}
auto f = l->second.mFaces.find(attachment->second.mTextureCubeMapFace);
if (f == l->second.mFaces.end()) {
return false;
}
width = uint32_t(f->second.mWidth);
height = uint32_t(f->second.mHeight);
return true;
}
}
}
case GL_RENDERBUFFER: {
auto r = ctx->mInstances.mRenderbuffers.find(attachment->second.mObjectName);
if (r == ctx->mInstances.mRenderbuffers.end()) {
return false;
}
width = uint32_t(r->second->mWidth);
height = uint32_t(r->second->mHeight);
return true;
}
}
return false;
}
void Spy::maybeAddErrorStateExtra(gapic::Vector<gapic::Encodable*>& extras) {
if (mRecordGLErrorState) {
std::shared_ptr<Context> ctx = this->Contexts[this->CurrentThread];
auto es = mScratch.create<gapic::coder::gles::ErrorState>();
es->mTraceDriversGlError = GlesSpy::mImports.glGetError();
es->mInterceptorsGlError = getAndClearAnErrorFlag(ctx);
extras.append(es);
// glGetError() cleared the error in the driver.
// Fake it the next time the user calls glGetError().
if (es->mTraceDriversGlError != 0) {
setFakeGlError(es->mTraceDriversGlError);
}
}
if (mAborted != nullptr) {
extras.append(mAborted);
}
}
void Spy::setFakeGlError(GLenum_Error error) {
std::shared_ptr<Context> ctx = this->Contexts[this->CurrentThread];
if (ctx) {
GLenum_Error& fakeGlError = this->mFakeGlError[ctx->mIdentifier];
if (fakeGlError == 0) {
fakeGlError = error;
}
}
}
uint32_t Spy::glGetError() {
std::shared_ptr<Context> ctx = this->Contexts[this->CurrentThread];
if (ctx) {
GLenum_Error& fakeGlError = this->mFakeGlError[ctx->mIdentifier];
if (fakeGlError != 0) {
gapic::coder::gles::GlGetError coder(mScratch.vector<gapic::Encodable*>(kMaxExtras), fakeGlError);
coder.mextras.append(&mObservations);
mEncoder->Variant(&coder);
GLenum_Error err = fakeGlError;
fakeGlError = 0;
return err;
}
}
return GlesSpy::glGetError();
}
void Spy::onThreadSwitched(uint64_t threadID) {
GlesSpy::switchThread(threadID);
}
} // namespace gapii