| /* |
| * 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 |