blob: 4be69a99e0deb2369a08788472f4c9578efb3ad7 [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 <cstdlib>
#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 kMaxFramebufferObservationWidth = 1920 / 2;
const uint32_t kMaxFramebufferObservationHeight = 1280 / 2;
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 {
#if TARGET_OS == GAPID_OS_ANDROID
/**
* Parses "[key]: [value]" pairs from getprop output and returns them as a map.
*/
static std::unordered_map<std::string, std::string> parseSystemProperties(FILE *f) {
std::unordered_map<std::string, std::string> result;
std::string key, val;
int m = 0; // Parsing state (m=0)[(m=1)key](m=2):(m=3)whitespace[(m=4)value](m=0)...
for (int x = getc(f); x != EOF; x = getc(f)) {
switch (m) {
case 0:
if (x == '[') {
key.clear(); m = 1;
break;
} else if (isspace(x)) {
break;
}
return result; // Unexpected value.
case 1:
if (x == ']') {
m = 2;
} else {
key.push_back((char) x);
}
break;
case 2:
if (x == ':') {
m = 3;
break;
}
return result; // Unexpected value.
case 3:
if (x == '[') {
val.clear(); m = 4;
break;
} else if (isspace(x)) {
break;
}
return result;
case 4:
if (x != '\n' && x != '\r') {
val.push_back((char) x);
} else {
if (val.back() == ']') {
val.pop_back();
}
result[key] = val;
m = 0;
}
break;
}
}
return result;
}
#endif
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();
}
// Returns the minimum alignment for the given type in a structure.
template<typename T>
uint32_t GetAlignment() {
struct AlignmentStruct {
char c;
T t;
};
return offsetof(AlignmentStruct, t);
}
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");
CallObserver observer(this);
mEncoder = std::shared_ptr<gapic::Encoder>(new gapic::Encoder(conn));
mEncoder->String("GapiiTraceFile_V1.1");
GlesSpy::init();
CoreSpy::init();
VulkanSpy::init();
SpyBase::init(&observer, mEncoder);
gapic::coder::atom::FieldAlignments alignments(
GetAlignment<char>(),
GetAlignment<int>(),
GetAlignment<uint32_t>(),
GetAlignment<uint64_t>(),
GetAlignment<void*>());
observer.addExtra(&alignments);
#if TARGET_OS == GAPID_OS_ANDROID
auto props = getDeviceProperties();
DeviceInfo deviceInfo;
deviceInfo.mProductManufacturer = props["ro.product.manufacturer"];
deviceInfo.mProductModel = props["ro.product.model"];
deviceInfo.mBuildDescription = props["ro.build.description"];
deviceInfo.mBuildVersionRelease = props["ro.build.version.release"];
deviceInfo.mBuildVersionSdk = props["ro.build.version.sdk"];
deviceInfo.mDebuggable = props["ro.debuggable"];
deviceInfo.mAbiList = props["ro.product.cpu.abilist"];
auto scratch = observer.getScratch();
observer.addExtra(scratch->make<DeviceInfo::CoderType>(deviceInfo.encodeable(*scratch)));
#endif
CoreSpy::architecture(&observer, alignof(void*), sizeof(void*), sizeof(int), isLittleEndian());
}
std::unordered_map<std::string, std::string> Spy::getDeviceProperties() {
std::unordered_map<std::string, std::string> result;
#if TARGET_OS == GAPID_OS_ANDROID
FILE* f = popen("/system/bin/getprop", "r");
if (f != nullptr) {
result = parseSystemProperties(f);
pclose(f);
}
#endif
return result;
}
void Spy::resolveImports() {
GlesSpy::mImports.resolve();
}
EGLBoolean Spy::eglInitialize(CallObserver* observer, EGLDisplay dpy, EGLint* major, EGLint* minor) {
EGLBoolean res = GlesSpy::eglInitialize(observer, dpy, major, minor);
if (res != 0) {
resolveImports(); // Imports may have changed. Re-resolve.
}
return res;
}
EGLContext Spy::eglCreateContext(CallObserver* observer, EGLDisplay display, EGLConfig config,
EGLContext share_context, EGLint* attrib_list) {
EGLContext res = GlesSpy::eglCreateContext(observer, 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); \
}
std::shared_ptr<StaticContextState> GlesSpy::GetEGLStaticContextState(CallObserver* observer, EGLDisplay display, EGLSurface draw, EGLContext context) {
if (draw == nullptr) {
return nullptr;
}
Constants constants;
getContextConstants(constants);
std::shared_ptr<StaticContextState> out(new StaticContextState(constants));
// Store the StaticContextState as an extra.
auto scratch = observer->getScratch();
auto extra = scratch->make<StaticContextState::CoderType>(out->encodeable(*scratch));
observer->getExtras().append(extra);
return out;
}
std::shared_ptr<DynamicContextState> GlesSpy::GetEGLDynamicContextState(CallObserver* observer, EGLDisplay display, EGLSurface draw, EGLContext context) {
if (draw == nullptr) {
return 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;
EGLint r = 0, g = 0, b = 0, a = 0, d = 0, s = 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) {
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;
std::shared_ptr<DynamicContextState> out(new DynamicContextState(
width, height,
backbufferColorFmt, backbufferDepthFmt, backbufferStencilFmt,
resetViewportScissor,
preserveBuffersOnSwap,
r, g, b, a, d, s
));
// Store the DynamicContextState as an extra.
auto scratch = observer->getScratch();
auto extra = scratch->make<DynamicContextState::CoderType>(out->encodeable(*scratch));
observer->getExtras().append(extra);
return out;
}
#undef EGL_QUERY_SURFACE
#undef EGL_GET_CONFIG_ATTRIB
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;
}
static bool downsamplePixels(uint8_t* srcData, uint32_t srcW, uint32_t srcH,
uint8_t** outData, uint32_t* outW, uint32_t* outH,
uint32_t maxW, uint32_t maxH) {
// Calculate the minimal scaling factor as integer fraction.
uint32_t mul = 1;
uint32_t div = 1;
if (mul*srcW > maxW*div) { // if mul/div > maxW/srcW
mul = maxW;
div = srcW;
}
if (mul*srcH > maxH*div) { // if mul/div > maxH/srcH
mul = maxH;
div = srcH;
}
// Calculate the final dimensions (round up) and allocate new buffer.
uint32_t dstW = (srcW*mul + div - 1) / div;
uint32_t dstH = (srcH*mul + div - 1) / div;
uint8_t* dstData = new uint8_t[dstW*dstH*4];
if (dstData == nullptr) {
GAPID_WARNING("Failed to allocate buffer to downsample framebuffer");
return false;
}
// Downsample the image by averaging the colours of neighbouring pixels.
uint8_t* dst = dstData;
for (uint32_t srcY = 0, y = 0, dstY = 0; dstY < dstH; srcY = y, dstY++) {
for (uint32_t srcX = 0, x = 0, dstX = 0; dstX < dstW; srcX = x, dstX++) {
uint32_t r = 0, g = 0, b = 0, a = 0, n = 0;
// We need to loop over srcX/srcY ranges several times, so we keep them in x/y,
// and we update srcX/srcY to the last x/y only once we are done with the pixel.
for (y = srcY; y*dstH < (dstY+1)*srcH; y++) { // while y*yScale < dstY+1
uint8_t* src = &srcData[(srcX + y*srcW) * 4];
for (x = srcX; x*dstW < (dstX+1)*srcW; x++) { // while x*xScale < dstX+1
r += *(src++);
g += *(src++);
b += *(src++);
a += *(src++);
n += 1;
}
}
*(dst++) = r / n;
*(dst++) = g / n;
*(dst++) = b / n;
*(dst++) = a / n;
}
}
*outW = dstW;
*outH = dstH;
*outData = dstData;
return true;
}
// 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.
}
uint8_t* data = new uint8_t[w * h * 4];
if (data == nullptr) {
GAPID_WARNING("Failed to allocate buffer to observe framebuffer of size %ux%u", w, h);
return;
}
GlesSpy::mImports.glReadPixels(0, 0, int32_t(w), int32_t(h),
GL_RGBA, GL_UNSIGNED_BYTE, data);
uint32_t downsampledW, downsampledH;
uint8_t* downsampledData = nullptr;
if (downsamplePixels(data, w, h,
&downsampledData, &downsampledW, &downsampledH,
kMaxFramebufferObservationWidth, kMaxFramebufferObservationHeight)) {
uint32_t downsampledSize = downsampledW * downsampledH * 4;
gapic::coder::atom::FramebufferObservation coder(
w, h, downsampledW, downsampledH, gapic::Vector<uint8_t>(downsampledData, downsampledSize));
mEncoder->Variant(&coder);
delete [] downsampledData;
}
delete [] data;
}
// 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 GLenum::GL_TEXTURE_2D: {
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 GLenum::GL_TEXTURE_CUBE_MAP: {
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::onPostFence(CallObserver* observer) {
if (mRecordGLErrorState) {
std::shared_ptr<Context> ctx = this->Contexts[this->CurrentThread];
auto es = observer->getScratch()->create<gapic::coder::gles::ErrorState>();
es->mTraceDriversGlError = GlesSpy::mImports.glGetError();
es->mInterceptorsGlError = observer->getError();
observer->addExtra(es);
// glGetError() cleared the error in the driver.
// Fake it the next time the user calls glGetError().
if (es->mTraceDriversGlError != 0) {
setFakeGlError(es->mTraceDriversGlError);
}
}
}
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(CallObserver* observer) {
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(observer->getScratch()->vector<gapic::Encodable*>(kMaxExtras), fakeGlError);
coder.mextras.append(observer->getExtras());
mEncoder->Variant(&coder);
GLenum_Error err = fakeGlError;
fakeGlError = 0;
return err;
}
}
return GlesSpy::glGetError(observer);
}
void Spy::onThreadSwitched(CallObserver* observer, uint64_t threadID) {
CoreSpy::switchThread(observer, threadID);
}
#if 0 // NON-EGL CONTEXTS ARE CURRENTLY NOT SUPPORTED
std::shared_ptr<ContextState> Spy::getWGLContextState(CallObserver*, HDC hdc, HGLRC hglrc) {
if (hglrc == nullptr) {
return nullptr;
}
#if TARGET_OS == GAPID_OS_WINDOWS
wgl::FramebufferInfo info;
wgl::getFramebufferInfo(hdc, info);
return getContextState(info.width, info.height,
info.colorFormat, info.depthFormat, info.stencilFormat,
/* resetViewportScissor */ true,
/* preserveBuffersOnSwap */ false);
#else // TARGET_OS
return nullptr;
#endif // TARGET_OS
}
std::shared_ptr<ContextState> Spy::getCGLContextState(CallObserver* observer, CGLContextObj ctx) {
if (ctx == nullptr) {
return 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
return getContextState(width, height,
GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8,
/* resetViewportScissor */ true,
/* preserveBuffersOnSwap */ false);
}
std::shared_ptr<ContextState> Spy::getGLXContextState(CallObserver* observer, void* display, GLXDrawable draw, GLXDrawable read, GLXContext ctx) {
if (display == nullptr) {
return 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
return getContextState(width, height,
GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8,
/* resetViewportScissor */ true,
/* preserveBuffersOnSwap */ false);
}
#endif // #if 0
} // namespace gapii