/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */ #include "LayerScope.h" #include "Composer2D.h" #include "Effects.h" #include "mozilla/TimeStamp.h" #include "mozilla/Preferences.h" #include "TexturePoolOGL.h" #include "mozilla/layers/TextureHostOGL.h" #include "gfxColor.h" #include "gfxContext.h" #include "gfxUtils.h" #include "gfxPlatform.h" #include "nsIWidget.h" #include "GLContext.h" #include "GLContextProvider.h" #include "GLReadTexImageHelper.h" #include "nsIServiceManager.h" #include "nsIConsoleService.h" #include #include "mozilla/Compression.h" #include "mozilla/LinkedList.h" #include "nsThreadUtils.h" #include "nsISocketTransport.h" #include "nsIServerSocket.h" #include "nsNetCID.h" #include "nsIOutputStream.h" #include "nsIEventTarget.h" #include "nsProxyRelease.h" #ifdef __GNUC__ #define PACKED_STRUCT __attribute__((packed)) #else #define PACKED_STRUCT #endif namespace mozilla { namespace layers { using namespace mozilla::Compression; using namespace mozilla::gfx; using namespace mozilla::gl; using namespace mozilla; class DebugDataSender; static bool gDebugConnected = false; static nsCOMPtr gDebugServerSocket; static nsCOMPtr gDebugSenderThread; static nsCOMPtr gDebugSenderTransport; static nsCOMPtr gDebugStream; static nsCOMPtr gCurrentSender; class DebugGLData : public LinkedListElement { public: typedef enum { FrameStart, FrameEnd, TextureData, ColorData } DataType; virtual ~DebugGLData() { } DataType GetDataType() const { return mDataType; } intptr_t GetContextAddress() const { return mContextAddress; } int64_t GetValue() const { return mValue; } DebugGLData(DataType dataType) : mDataType(dataType), mContextAddress(0), mValue(0) { } DebugGLData(DataType dataType, GLContext* cx) : mDataType(dataType), mContextAddress(reinterpret_cast(cx)), mValue(0) { } DebugGLData(DataType dataType, GLContext* cx, int64_t value) : mDataType(dataType), mContextAddress(reinterpret_cast(cx)), mValue(value) { } virtual bool Write() { if (mDataType != FrameStart && mDataType != FrameEnd) { NS_WARNING("Unimplemented data type!"); return false; } DebugGLData::BasicPacket packet; packet.type = mDataType; packet.ptr = static_cast(mContextAddress); packet.value = mValue; return WriteToStream(&packet, sizeof(packet)); } static bool WriteToStream(void *ptr, uint32_t size) { uint32_t written = 0; nsresult rv; while (written < size) { uint32_t cnt; rv = gDebugStream->Write(reinterpret_cast(ptr) + written, size - written, &cnt); if (NS_FAILED(rv)) return false; written += cnt; } return true; } protected: DataType mDataType; intptr_t mContextAddress; int64_t mValue; public: // the data packet formats; all packed #ifdef _MSC_VER #pragma pack(push, 1) #endif typedef struct { uint32_t type; uint64_t ptr; uint64_t value; } PACKED_STRUCT BasicPacket; typedef struct { uint32_t type; uint64_t ptr; uint64_t layerref; uint32_t color; uint32_t width; uint32_t height; } PACKED_STRUCT ColorPacket; typedef struct { uint32_t type; uint64_t ptr; uint64_t layerref; uint32_t name; uint32_t width; uint32_t height; uint32_t stride; uint32_t format; uint32_t target; uint32_t dataFormat; uint32_t dataSize; } PACKED_STRUCT TexturePacket; #ifdef _MSC_VER #pragma pack(pop) #endif }; class DebugGLTextureData : public DebugGLData { public: DebugGLTextureData(GLContext* cx, void* layerRef, GLuint target, GLenum name, gfxImageSurface* img) : DebugGLData(DebugGLData::TextureData, cx), mLayerRef(layerRef), mTarget(target), mName(name), mImage(img) { } void *GetLayerRef() const { return mLayerRef; } GLuint GetName() const { return mName; } gfxImageSurface* GetImage() const { return mImage; } GLenum GetTextureTarget() const { return mTarget; } virtual bool Write() { DebugGLData::TexturePacket packet; char* dataptr = nullptr; uint32_t datasize = 0; std::auto_ptr compresseddata; packet.type = mDataType; packet.ptr = static_cast(mContextAddress); packet.layerref = reinterpret_cast(mLayerRef); packet.name = mName; packet.format = 0; packet.target = mTarget; packet.dataFormat = LOCAL_GL_RGBA; if (mImage) { packet.width = mImage->Width(); packet.height = mImage->Height(); packet.stride = mImage->Stride(); packet.dataSize = mImage->GetDataSize(); dataptr = (char*) mImage->Data(); datasize = mImage->GetDataSize(); compresseddata = std::auto_ptr((char*) moz_malloc(LZ4::maxCompressedSize(datasize))); if (compresseddata.get()) { int ndatasize = LZ4::compress(dataptr, datasize, compresseddata.get()); if (ndatasize > 0) { datasize = ndatasize; dataptr = compresseddata.get(); packet.dataFormat = (1 << 16) | packet.dataFormat; packet.dataSize = datasize; } } } else { packet.width = 0; packet.height = 0; packet.stride = 0; packet.dataSize = 0; } // write the packet header data if (!WriteToStream(&packet, sizeof(packet))) return false; // then the image data if (!WriteToStream(dataptr, datasize)) return false; // then pad out to 4 bytes if (datasize % 4 != 0) { static char buf[] = { 0, 0, 0, 0 }; if (!WriteToStream(buf, 4 - (datasize % 4))) return false; } return true; } protected: void* mLayerRef; GLenum mTarget; GLuint mName; nsRefPtr mImage; }; class DebugGLColorData : public DebugGLData { public: DebugGLColorData(void* layerRef, const gfxRGBA& color, int width, int height) : DebugGLData(DebugGLData::ColorData), mColor(color.Packed()), mSize(width, height) { } void *GetLayerRef() const { return mLayerRef; } uint32_t GetColor() const { return mColor; } const nsIntSize& GetSize() const { return mSize; } virtual bool Write() { DebugGLData::ColorPacket packet; packet.type = mDataType; packet.ptr = static_cast(mContextAddress); packet.layerref = reinterpret_cast(mLayerRef); packet.color = mColor; packet.width = mSize.width; packet.height = mSize.height; return WriteToStream(&packet, sizeof(packet)); } protected: void *mLayerRef; uint32_t mColor; nsIntSize mSize; }; static bool CheckSender() { if (!gDebugConnected) return false; // At some point we may want to support sending // data in between frames. #if 1 if (!gCurrentSender) return false; #else if (!gCurrentSender) gCurrentSender = new DebugDataSender(); #endif return true; } class DebugListener : public nsIServerSocketListener { public: NS_DECL_THREADSAFE_ISUPPORTS DebugListener() { } virtual ~DebugListener() { } /* nsIServerSocketListener */ NS_IMETHODIMP OnSocketAccepted(nsIServerSocket *aServ, nsISocketTransport *aTransport) { printf_stderr("*** LayerScope: Accepted connection\n"); gDebugConnected = true; gDebugSenderTransport = aTransport; aTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(gDebugStream)); return NS_OK; } NS_IMETHODIMP OnStopListening(nsIServerSocket *aServ, nsresult aStatus) { return NS_OK; } }; NS_IMPL_ISUPPORTS1(DebugListener, nsIServerSocketListener); class DebugDataSender : public nsIRunnable { public: NS_DECL_THREADSAFE_ISUPPORTS DebugDataSender() { mList = new LinkedList(); } virtual ~DebugDataSender() { Cleanup(); } void Append(DebugGLData *d) { mList->insertBack(d); } void Cleanup() { if (!mList) return; DebugGLData *d; while ((d = mList->popFirst()) != nullptr) delete d; delete mList; mList = nullptr; } /* nsIRunnable impl; send the data */ NS_IMETHODIMP Run() { DebugGLData *d; nsresult rv = NS_OK; // If we got closed while trying to write some stuff earlier, just // throw away everything. if (!gDebugStream) { Cleanup(); return NS_OK; } while ((d = mList->popFirst()) != nullptr) { std::auto_ptr cleaner(d); if (!d->Write()) { rv = NS_ERROR_FAILURE; break; } } Cleanup(); if (NS_FAILED(rv)) { gDebugSenderTransport->Close(rv); gDebugConnected = false; gDebugStream = nullptr; gDebugServerSocket = nullptr; } return NS_OK; } protected: LinkedList *mList; }; NS_IMPL_ISUPPORTS1(DebugDataSender, nsIRunnable); void LayerScope::CreateServerSocket() { if (!Preferences::GetBool("gfx.layerscope.enabled", false)) { return; } if (!gDebugSenderThread) { NS_NewThread(getter_AddRefs(gDebugSenderThread)); } if (!gDebugServerSocket) { gDebugServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID); int port = Preferences::GetInt("gfx.layerscope.port", 23456); gDebugServerSocket->Init(port, false, -1); gDebugServerSocket->AsyncListen(new DebugListener); } } void LayerScope::DestroyServerSocket() { gDebugConnected = false; gDebugStream = nullptr; gDebugServerSocket = nullptr; } void LayerScope::BeginFrame(GLContext* aGLContext, int64_t aFrameStamp) { if (!gDebugConnected) return; #if 0 // if we're sending data in between frames, flush the list down the socket, // and start a new one if (gCurrentSender) { gDebugSenderThread->Dispatch(gCurrentSender, NS_DISPATCH_NORMAL); } #endif gCurrentSender = new DebugDataSender(); gCurrentSender->Append(new DebugGLData(DebugGLData::FrameStart, aGLContext, aFrameStamp)); } void LayerScope::EndFrame(GLContext* aGLContext) { if (!CheckSender()) return; gCurrentSender->Append(new DebugGLData(DebugGLData::FrameEnd, aGLContext)); gDebugSenderThread->Dispatch(gCurrentSender, NS_DISPATCH_NORMAL); gCurrentSender = nullptr; } static void SendColor(void* aLayerRef, const gfxRGBA& aColor, int aWidth, int aHeight) { if (!CheckSender()) return; gCurrentSender->Append( new DebugGLColorData(aLayerRef, aColor, aWidth, aHeight)); } static void SendTextureSource(GLContext* aGLContext, void* aLayerRef, TextureSourceOGL* aSource, bool aFlipY) { GLenum textureTarget = aSource->GetTextureTarget(); int shaderProgram = (int) ShaderProgramFromTargetAndFormat(textureTarget, aSource->GetFormat()); aSource->BindTexture(LOCAL_GL_TEXTURE0); GLuint textureId = 0; // This is horrid hack. It assumes that aGLContext matches the context // aSource has bound to. if (textureTarget == LOCAL_GL_TEXTURE_2D) { aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &textureId); } else if (textureTarget == LOCAL_GL_TEXTURE_EXTERNAL) { aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL, &textureId); } else if (textureTarget == LOCAL_GL_TEXTURE_RECTANGLE) { aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_RECTANGLE, &textureId); } gfx::IntSize size = aSource->GetSize(); // By sending 0 to ReadTextureImage rely upon aSource->BindTexture binding // texture correctly. textureId is used for tracking in DebugGLTextureData. nsRefPtr img = aGLContext->ReadTexImageHelper()->ReadTexImage(0, textureTarget, gfxIntSize(size.width, size.height), shaderProgram, aFlipY); gCurrentSender->Append( new DebugGLTextureData(aGLContext, aLayerRef, textureTarget, textureId, img)); } static void SendTexturedEffect(GLContext* aGLContext, void* aLayerRef, const TexturedEffect* aEffect) { TextureSourceOGL* source = aEffect->mTexture->AsSourceOGL(); if (!source) return; bool flipY = false; SendTextureSource(aGLContext, aLayerRef, source, flipY); } static void SendYCbCrEffect(GLContext* aGLContext, void* aLayerRef, const EffectYCbCr* aEffect) { TextureSource* sourceYCbCr = aEffect->mTexture; if (!sourceYCbCr) return; const int Y = 0, Cb = 1, Cr = 2; TextureSourceOGL* sourceY = sourceYCbCr->GetSubSource(Y)->AsSourceOGL(); TextureSourceOGL* sourceCb = sourceYCbCr->GetSubSource(Cb)->AsSourceOGL(); TextureSourceOGL* sourceCr = sourceYCbCr->GetSubSource(Cr)->AsSourceOGL(); bool flipY = false; SendTextureSource(aGLContext, aLayerRef, sourceY, flipY); SendTextureSource(aGLContext, aLayerRef, sourceCb, flipY); SendTextureSource(aGLContext, aLayerRef, sourceCr, flipY); } void LayerScope::SendEffectChain(GLContext* aGLContext, const EffectChain& aEffectChain, int aWidth, int aHeight) { if (!CheckSender()) return; const Effect* primaryEffect = aEffectChain.mPrimaryEffect; switch (primaryEffect->mType) { case EFFECT_BGRX: case EFFECT_RGBX: case EFFECT_BGRA: case EFFECT_RGBA: { const TexturedEffect* texturedEffect = static_cast(primaryEffect); SendTexturedEffect(aGLContext, aEffectChain.mLayerRef, texturedEffect); } break; case EFFECT_YCBCR: { const EffectYCbCr* yCbCrEffect = static_cast(primaryEffect); SendYCbCrEffect(aGLContext, aEffectChain.mLayerRef, yCbCrEffect); } case EFFECT_SOLID_COLOR: { const EffectSolidColor* solidColorEffect = static_cast(primaryEffect); gfxRGBA color(solidColorEffect->mColor.r, solidColorEffect->mColor.g, solidColorEffect->mColor.b, solidColorEffect->mColor.a); SendColor(aEffectChain.mLayerRef, color, aWidth, aHeight); } break; case EFFECT_COMPONENT_ALPHA: case EFFECT_RENDER_TARGET: default: break; } //const Effect* secondaryEffect = aEffectChain.mSecondaryEffects[EFFECT_MASK]; // TODO: } } /* layers */ } /* mozilla */