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