/* -*- Mode: C++; tab-width: 4; 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 "nsAppRunner.h" #include "Composer2D.h" #include "Effects.h" #include "mozilla/EndianUtils.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Preferences.h" #include "mozilla/TimeStamp.h" #include "TexturePoolOGL.h" #include "mozilla/layers/CompositorOGL.h" #include "mozilla/layers/CompositorThread.h" #include "mozilla/layers/LayerManagerComposite.h" #include "mozilla/layers/TextureHostOGL.h" #include "gfxContext.h" #include "gfxUtils.h" #include "gfxPrefs.h" #include "nsIWidget.h" #include "GLContext.h" #include "GLContextProvider.h" #include "GLReadTexImageHelper.h" #include "nsIServiceManager.h" #include "nsIConsoleService.h" #include #include "mozilla/LinkedList.h" #include "mozilla/Base64.h" #include "mozilla/SHA1.h" #include "mozilla/StaticPtr.h" #include "nsThreadUtils.h" #include "nsISocketTransport.h" #include "nsIServerSocket.h" #include "nsReadLine.h" #include "nsNetCID.h" #include "nsIOutputStream.h" #include "nsIAsyncInputStream.h" #include "nsIEventTarget.h" #include "nsProxyRelease.h" #include // Undo the damage done by mozzconf.h #undef compress #include "mozilla/Compression.h" // Protocol buffer (generated automatically) #include "protobuf/LayerScopePacket.pb.h" namespace mozilla { namespace layers { using namespace mozilla::Compression; using namespace mozilla::gfx; using namespace mozilla::gl; using namespace mozilla; using namespace layerscope; class DebugDataSender; class DebugGLData; /* * Manage Websocket connections */ class LayerScopeWebSocketManager { public: LayerScopeWebSocketManager(); ~LayerScopeWebSocketManager(); void RemoveAllConnections() { MOZ_ASSERT(NS_IsMainThread()); MutexAutoLock lock(mHandlerMutex); mHandlers.Clear(); } bool WriteAll(void *ptr, uint32_t size) { for (int32_t i = mHandlers.Length() - 1; i >= 0; --i) { if (!mHandlers[i]->WriteToStream(ptr, size)) { // Send failed, remove this handler RemoveConnection(i); } } return true; } bool IsConnected() { // This funtion can be called in both main thread and compositor thread. MutexAutoLock lock(mHandlerMutex); return (mHandlers.Length() != 0) ? true : false; } void AppendDebugData(DebugGLData *aDebugData); void CleanDebugData(); void DispatchDebugData(); private: void AddConnection(nsISocketTransport *aTransport) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aTransport); MutexAutoLock lock(mHandlerMutex); RefPtr temp = new SocketHandler(); temp->OpenStream(aTransport); mHandlers.AppendElement(temp.get()); } void RemoveConnection(uint32_t aIndex) { // TBD: RemoveConnection is executed on the compositor thread and // AddConntection is executed on the main thread, which might be // a problem if a user disconnect and connect readlly quickly at // viewer side. // We should dispatch RemoveConnection onto main thead. MOZ_ASSERT(aIndex < mHandlers.Length()); MutexAutoLock lock(mHandlerMutex); mHandlers.RemoveElementAt(aIndex); } friend class SocketListener; class SocketListener : public nsIServerSocketListener { public: NS_DECL_THREADSAFE_ISUPPORTS SocketListener() { } /* nsIServerSocketListener */ NS_IMETHOD OnSocketAccepted(nsIServerSocket *aServ, nsISocketTransport *aTransport) override; NS_IMETHOD OnStopListening(nsIServerSocket *aServ, nsresult aStatus) override { return NS_OK; } private: virtual ~SocketListener() { } }; /* * This class handle websocket protocol which included * handshake and data frame's header */ class SocketHandler : public nsIInputStreamCallback { public: NS_DECL_THREADSAFE_ISUPPORTS SocketHandler() : mState(NoHandshake) , mConnected(false) { } void OpenStream(nsISocketTransport* aTransport); bool WriteToStream(void *aPtr, uint32_t aSize); // nsIInputStreamCallback NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream *aStream) override; private: virtual ~SocketHandler() { CloseConnection(); } void ReadInputStreamData(nsTArray& aProtocolString); bool WebSocketHandshake(nsTArray& aProtocolString); void ApplyMask(uint32_t aMask, uint8_t *aData, uint64_t aLen); bool HandleDataFrame(uint8_t *aData, uint32_t aSize); void CloseConnection(); nsresult HandleSocketMessage(nsIAsyncInputStream *aStream); nsresult ProcessInput(uint8_t *aBuffer, uint32_t aCount); private: enum SocketStateType { NoHandshake, HandshakeSuccess, HandshakeFailed }; SocketStateType mState; nsCOMPtr mOutputStream; nsCOMPtr mInputStream; nsCOMPtr mTransport; bool mConnected; }; nsTArray > mHandlers; nsCOMPtr mDebugSenderThread; RefPtr mCurrentSender; nsCOMPtr mServerSocket; // Keep mHandlers accessing thread safe. Mutex mHandlerMutex; }; NS_IMPL_ISUPPORTS(LayerScopeWebSocketManager::SocketListener, nsIServerSocketListener); NS_IMPL_ISUPPORTS(LayerScopeWebSocketManager::SocketHandler, nsIInputStreamCallback); class DrawSession { public: DrawSession() : mOffsetX(0.0) , mOffsetY(0.0) , mRects(0) { } float mOffsetX; float mOffsetY; gfx::Matrix4x4 mMVMatrix; size_t mRects; gfx::Rect mLayerRects[4]; gfx::Rect mTextureRects[4]; std::list mTexIDs; }; class ContentMonitor { public: using THArray = nsTArray; // Notify the content of a TextureHost was changed. void SetChangedHost(const TextureHost* host) { if (THArray::NoIndex == mChangedHosts.IndexOf(host)) { mChangedHosts.AppendElement(host); } } // Clear changed flag of a host. void ClearChangedHost(const TextureHost* host) { if (THArray::NoIndex != mChangedHosts.IndexOf(host)) { mChangedHosts.RemoveElement(host); } } // Return true iff host is a new one or the content of it had been changed. bool IsChangedOrNew(const TextureHost* host) { if (THArray::NoIndex == mSeenHosts.IndexOf(host)) { mSeenHosts.AppendElement(host); return true; } if (decltype(mChangedHosts)::NoIndex != mChangedHosts.IndexOf(host)) { return true; } return false; } void Empty() { mSeenHosts.SetLength(0); mChangedHosts.SetLength(0); } private: THArray mSeenHosts; THArray mChangedHosts; }; /* * Hold all singleton objects used by LayerScope. */ class LayerScopeManager { public: void CreateServerSocket() { // WebSocketManager must be created on the main thread. if (NS_IsMainThread()) { mWebSocketManager = mozilla::MakeUnique(); } else { // Dispatch creation to main thread, and make sure we // dispatch this only once after booting static bool dispatched = false; if (dispatched) { return; } DebugOnly rv = NS_DispatchToMainThread(new CreateServerSocketRunnable(this)); MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to dispatch WebSocket Creation to main thread"); dispatched = true; } } void DestroyServerSocket() { // Destroy Web Server Socket if (mWebSocketManager) { mWebSocketManager->RemoveAllConnections(); } } LayerScopeWebSocketManager* GetSocketManager() { return mWebSocketManager.get(); } ContentMonitor* GetContentMonitor() { if (!mContentMonitor.get()) { mContentMonitor = mozilla::MakeUnique(); } return mContentMonitor.get(); } void NewDrawSession() { mSession = mozilla::MakeUnique(); } DrawSession& CurrentSession() { return *mSession; } void SetPixelScale(double scale) { mScale = scale; } double GetPixelScale() const { return mScale; } LayerScopeManager() : mScale(1.0) { } private: friend class CreateServerSocketRunnable; class CreateServerSocketRunnable : public Runnable { public: explicit CreateServerSocketRunnable(LayerScopeManager *aLayerScopeManager) : mLayerScopeManager(aLayerScopeManager) { } NS_IMETHOD Run() override { mLayerScopeManager->mWebSocketManager = mozilla::MakeUnique(); return NS_OK; } private: LayerScopeManager* mLayerScopeManager; }; mozilla::UniquePtr mWebSocketManager; mozilla::UniquePtr mSession; mozilla::UniquePtr mContentMonitor; double mScale; }; LayerScopeManager gLayerScopeManager; /* * The static helper functions that set data into the packet * 1. DumpRect * 2. DumpFilter */ template static void DumpRect(T* aPacketRect, const Rect& aRect) { aPacketRect->set_x(aRect.x); aPacketRect->set_y(aRect.y); aPacketRect->set_w(aRect.width); aPacketRect->set_h(aRect.height); } static void DumpFilter(TexturePacket* aTexturePacket, const SamplingFilter aSamplingFilter) { switch (aSamplingFilter) { case SamplingFilter::GOOD: aTexturePacket->set_mfilter(TexturePacket::GOOD); break; case SamplingFilter::LINEAR: aTexturePacket->set_mfilter(TexturePacket::LINEAR); break; case SamplingFilter::POINT: aTexturePacket->set_mfilter(TexturePacket::POINT); break; default: MOZ_ASSERT(false, "Can't dump unexpected mSamplingFilter to texture packet!"); break; } } /* * DebugGLData is the base class of * 1. DebugGLFrameStatusData (Frame start/end packet) * 2. DebugGLColorData (Color data packet) * 3. DebugGLTextureData (Texture data packet) * 4. DebugGLLayersData (Layers Tree data packet) * 5. DebugGLMetaData (Meta data packet) */ class DebugGLData: public LinkedListElement { public: explicit DebugGLData(Packet::DataType aDataType) : mDataType(aDataType) { } virtual ~DebugGLData() { } virtual bool Write() = 0; protected: static bool WriteToStream(Packet& aPacket) { if (!gLayerScopeManager.GetSocketManager()) return true; uint32_t size = aPacket.ByteSize(); auto data = MakeUnique(size); aPacket.SerializeToArray(data.get(), size); return gLayerScopeManager.GetSocketManager()->WriteAll(data.get(), size); } Packet::DataType mDataType; }; class DebugGLFrameStatusData final: public DebugGLData { public: DebugGLFrameStatusData(Packet::DataType aDataType, int64_t aValue) : DebugGLData(aDataType), mFrameStamp(aValue) { } explicit DebugGLFrameStatusData(Packet::DataType aDataType) : DebugGLData(aDataType), mFrameStamp(0) { } virtual bool Write() override { Packet packet; packet.set_type(mDataType); FramePacket* fp = packet.mutable_frame(); fp->set_value(static_cast(mFrameStamp)); fp->set_scale(gLayerScopeManager.GetPixelScale()); return WriteToStream(packet); } protected: int64_t mFrameStamp; }; class DebugGLTextureData final: public DebugGLData { public: DebugGLTextureData(GLContext* cx, void* layerRef, GLenum target, GLuint name, DataSourceSurface* img, bool aIsMask, UniquePtr aPacket) : DebugGLData(Packet::TEXTURE), mLayerRef(reinterpret_cast(layerRef)), mTarget(target), mName(name), mContextAddress(reinterpret_cast(cx)), mDatasize(0), mIsMask(aIsMask), mPacket(Move(aPacket)) { // pre-packing // DataSourceSurface may have locked buffer, // so we should compress now, and then it could // be unlocked outside. pack(img); } virtual bool Write() override { return WriteToStream(*mPacket); } private: void pack(DataSourceSurface* aImage) { mPacket->set_type(mDataType); TexturePacket* tp = mPacket->mutable_texture(); tp->set_layerref(mLayerRef); tp->set_name(mName); tp->set_target(mTarget); tp->set_dataformat(LOCAL_GL_RGBA); tp->set_glcontext(static_cast(mContextAddress)); tp->set_ismask(mIsMask); if (aImage) { tp->set_width(aImage->GetSize().width); tp->set_height(aImage->GetSize().height); tp->set_stride(aImage->Stride()); mDatasize = aImage->GetSize().height * aImage->Stride(); auto compresseddata = MakeUnique(LZ4::maxCompressedSize(mDatasize)); if (compresseddata) { int ndatasize = LZ4::compress((char*)aImage->GetData(), mDatasize, compresseddata.get()); if (ndatasize > 0) { mDatasize = ndatasize; tp->set_dataformat((1 << 16 | tp->dataformat())); tp->set_data(compresseddata.get(), mDatasize); } else { NS_WARNING("Compress data failed"); tp->set_data(aImage->GetData(), mDatasize); } } else { NS_WARNING("Couldn't new compressed data."); tp->set_data(aImage->GetData(), mDatasize); } } else { tp->set_width(0); tp->set_height(0); tp->set_stride(0); } } protected: uint64_t mLayerRef; GLenum mTarget; GLuint mName; intptr_t mContextAddress; uint32_t mDatasize; bool mIsMask; // Packet data UniquePtr mPacket; }; class DebugGLColorData final: public DebugGLData { public: DebugGLColorData(void* layerRef, const Color& color, int width, int height) : DebugGLData(Packet::COLOR), mLayerRef(reinterpret_cast(layerRef)), mColor(color.ToABGR()), mSize(width, height) { } virtual bool Write() override { Packet packet; packet.set_type(mDataType); ColorPacket* cp = packet.mutable_color(); cp->set_layerref(mLayerRef); cp->set_color(mColor); cp->set_width(mSize.width); cp->set_height(mSize.height); return WriteToStream(packet); } protected: uint64_t mLayerRef; uint32_t mColor; IntSize mSize; }; class DebugGLLayersData final: public DebugGLData { public: explicit DebugGLLayersData(UniquePtr aPacket) : DebugGLData(Packet::LAYERS), mPacket(Move(aPacket)) { } virtual bool Write() override { mPacket->set_type(mDataType); return WriteToStream(*mPacket); } protected: UniquePtr mPacket; }; class DebugGLMetaData final: public DebugGLData { public: DebugGLMetaData(Packet::DataType aDataType, bool aValue) : DebugGLData(aDataType), mComposedByHwc(aValue) { } explicit DebugGLMetaData(Packet::DataType aDataType) : DebugGLData(aDataType), mComposedByHwc(false) { } virtual bool Write() override { Packet packet; packet.set_type(mDataType); MetaPacket* mp = packet.mutable_meta(); mp->set_composedbyhwc(mComposedByHwc); return WriteToStream(packet); } protected: bool mComposedByHwc; }; class DebugGLDrawData final: public DebugGLData { public: DebugGLDrawData(float aOffsetX, float aOffsetY, const gfx::Matrix4x4& aMVMatrix, size_t aRects, const gfx::Rect* aLayerRects, const gfx::Rect* aTextureRects, const std::list aTexIDs, void* aLayerRef) : DebugGLData(Packet::DRAW), mOffsetX(aOffsetX), mOffsetY(aOffsetY), mMVMatrix(aMVMatrix), mRects(aRects), mTexIDs(aTexIDs), mLayerRef(reinterpret_cast(aLayerRef)) { for (size_t i = 0; i < mRects; i++){ mLayerRects[i] = aLayerRects[i]; mTextureRects[i] = aTextureRects[i]; } } virtual bool Write() override { Packet packet; packet.set_type(mDataType); DrawPacket* dp = packet.mutable_draw(); dp->set_layerref(mLayerRef); dp->set_offsetx(mOffsetX); dp->set_offsety(mOffsetY); auto element = reinterpret_cast(&mMVMatrix); for (int i = 0; i < 16; i++) { dp->add_mvmatrix(*element++); } dp->set_totalrects(mRects); MOZ_ASSERT(mRects > 0 && mRects < 4); for (size_t i = 0; i < mRects; i++) { // Vertex DumpRect(dp->add_layerrect(), mLayerRects[i]); // UV DumpRect(dp->add_texturerect(), mTextureRects[i]); } for (GLuint texId: mTexIDs) { dp->add_texids(texId); } return WriteToStream(packet); } protected: float mOffsetX; float mOffsetY; gfx::Matrix4x4 mMVMatrix; size_t mRects; gfx::Rect mLayerRects[4]; gfx::Rect mTextureRects[4]; std::list mTexIDs; uint64_t mLayerRef; }; class DebugDataSender { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DebugDataSender) // Append a DebugData into mList on mThread class AppendTask: public nsIRunnable { public: NS_DECL_THREADSAFE_ISUPPORTS AppendTask(DebugDataSender *host, DebugGLData *d) : mData(d), mHost(host) { } NS_IMETHOD Run() override { mHost->mList.insertBack(mData); return NS_OK; } private: virtual ~AppendTask() { } DebugGLData *mData; // Keep a strong reference to DebugDataSender to prevent this object // accessing mHost on mThread, when it's been destroyed on the main // thread. RefPtr mHost; }; // Clear all DebugData in mList on mThead. class ClearTask: public nsIRunnable { public: NS_DECL_THREADSAFE_ISUPPORTS explicit ClearTask(DebugDataSender *host) : mHost(host) { } NS_IMETHOD Run() override { mHost->RemoveData(); return NS_OK; } private: virtual ~ClearTask() { } RefPtr mHost; }; // Send all DebugData in mList via websocket, and then, clean up // mList on mThread. class SendTask: public nsIRunnable { public: NS_DECL_THREADSAFE_ISUPPORTS explicit SendTask(DebugDataSender *host) : mHost(host) { } NS_IMETHOD Run() override { // Sendout all appended debug data. DebugGLData *d = nullptr; while ((d = mHost->mList.popFirst()) != nullptr) { UniquePtr cleaner(d); if (!d->Write()) { gLayerScopeManager.DestroyServerSocket(); break; } } // Cleanup. mHost->RemoveData(); return NS_OK; } private: virtual ~SendTask() { } RefPtr mHost; }; explicit DebugDataSender(nsIThread *thread) : mThread(thread) { } void Append(DebugGLData *d) { mThread->Dispatch(new AppendTask(this, d), NS_DISPATCH_NORMAL); } void Cleanup() { mThread->Dispatch(new ClearTask(this), NS_DISPATCH_NORMAL); } void Send() { mThread->Dispatch(new SendTask(this), NS_DISPATCH_NORMAL); } protected: virtual ~DebugDataSender() {} void RemoveData() { MOZ_ASSERT(NS_GetCurrentThread() == mThread); if (mList.isEmpty()) return; DebugGLData *d; while ((d = mList.popFirst()) != nullptr) delete d; } // We can only modify or aceess mList on mThread. LinkedList mList; nsCOMPtr mThread; }; NS_IMPL_ISUPPORTS(DebugDataSender::AppendTask, nsIRunnable); NS_IMPL_ISUPPORTS(DebugDataSender::ClearTask, nsIRunnable); NS_IMPL_ISUPPORTS(DebugDataSender::SendTask, nsIRunnable); /* * LayerScope SendXXX Structure * 1. SendLayer * 2. SendEffectChain * 1. SendTexturedEffect * -> SendTextureSource * 2. SendMaskEffect * -> SendTextureSource * 3. SendYCbCrEffect * -> SendTextureSource * 4. SendColor */ class SenderHelper { // Sender public APIs public: static void SendLayer(LayerComposite* aLayer, int aWidth, int aHeight); static void SendEffectChain(gl::GLContext* aGLContext, const EffectChain& aEffectChain, int aWidth = 0, int aHeight = 0); static void SetLayersTreeSendable(bool aSet) {sLayersTreeSendable = aSet;} static void SetLayersBufferSendable(bool aSet) {sLayersBufferSendable = aSet;} static bool GetLayersTreeSendable() {return sLayersTreeSendable;} static void ClearSentTextureIds(); // Sender private functions private: static void SendColor(void* aLayerRef, const Color& aColor, int aWidth, int aHeight); static void SendTextureSource(GLContext* aGLContext, void* aLayerRef, TextureSourceOGL* aSource, bool aFlipY, bool aIsMask, UniquePtr aPacket); static void SetAndSendTexture(GLContext* aGLContext, void* aLayerRef, TextureSourceOGL* aSource, const TexturedEffect* aEffect); static void SendTexturedEffect(GLContext* aGLContext, void* aLayerRef, const TexturedEffect* aEffect); static void SendMaskEffect(GLContext* aGLContext, void* aLayerRef, const EffectMask* aEffect); static void SendYCbCrEffect(GLContext* aGLContext, void* aLayerRef, const EffectYCbCr* aEffect); static GLuint GetTextureID(GLContext* aGLContext, TextureSourceOGL* aSource); static bool HasTextureIdBeenSent(GLuint aTextureId); // Data fields private: static bool sLayersTreeSendable; static bool sLayersBufferSendable; static std::vector sSentTextureIds; }; bool SenderHelper::sLayersTreeSendable = true; bool SenderHelper::sLayersBufferSendable = true; std::vector SenderHelper::sSentTextureIds; // ---------------------------------------------- // SenderHelper implementation // ---------------------------------------------- void SenderHelper::ClearSentTextureIds() { sSentTextureIds.clear(); } bool SenderHelper::HasTextureIdBeenSent(GLuint aTextureId) { return std::find(sSentTextureIds.begin(), sSentTextureIds.end(), aTextureId) != sSentTextureIds.end(); } void SenderHelper::SendLayer(LayerComposite* aLayer, int aWidth, int aHeight) { MOZ_ASSERT(aLayer && aLayer->GetLayer()); if (!aLayer || !aLayer->GetLayer()) { return; } switch (aLayer->GetLayer()->GetType()) { case Layer::TYPE_COLOR: { EffectChain effect; aLayer->GenEffectChain(effect); LayerScope::DrawBegin(); LayerScope::DrawEnd(nullptr, effect, aWidth, aHeight); break; } case Layer::TYPE_IMAGE: case Layer::TYPE_CANVAS: case Layer::TYPE_PAINTED: { // Get CompositableHost and Compositor CompositableHost* compHost = aLayer->GetCompositableHost(); Compositor* comp = compHost->GetCompositor(); // Send EffectChain only for CompositorOGL if (LayersBackend::LAYERS_OPENGL == comp->GetBackendType()) { CompositorOGL* compOGL = comp->AsCompositorOGL(); EffectChain effect; // Generate primary effect (lock and gen) AutoLockCompositableHost lock(compHost); aLayer->GenEffectChain(effect); LayerScope::DrawBegin(); LayerScope::DrawEnd(compOGL->gl(), effect, aWidth, aHeight); } break; } case Layer::TYPE_CONTAINER: default: break; } } void SenderHelper::SendColor(void* aLayerRef, const Color& aColor, int aWidth, int aHeight) { gLayerScopeManager.GetSocketManager()->AppendDebugData( new DebugGLColorData(aLayerRef, aColor, aWidth, aHeight)); } GLuint SenderHelper::GetTextureID(GLContext* aGLContext, TextureSourceOGL* aSource) { GLenum textureTarget = aSource->GetTextureTarget(); aSource->BindTexture(LOCAL_GL_TEXTURE0, gfx::SamplingFilter::LINEAR); GLuint texID = 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, &texID); } else if (textureTarget == LOCAL_GL_TEXTURE_EXTERNAL) { aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL, &texID); } else if (textureTarget == LOCAL_GL_TEXTURE_RECTANGLE) { aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_RECTANGLE, &texID); } return texID; } void SenderHelper::SendTextureSource(GLContext* aGLContext, void* aLayerRef, TextureSourceOGL* aSource, bool aFlipY, bool aIsMask, UniquePtr aPacket) { MOZ_ASSERT(aGLContext); if (!aGLContext) { return; } GLuint texID = GetTextureID(aGLContext, aSource); if (HasTextureIdBeenSent(texID)) { return; } GLenum textureTarget = aSource->GetTextureTarget(); ShaderConfigOGL config = ShaderConfigFromTargetAndFormat(textureTarget, aSource->GetFormat()); int shaderConfig = config.mFeatures; gfx::IntSize size = aSource->GetSize(); // By sending 0 to ReadTextureImage rely upon aSource->BindTexture binding // texture correctly. texID is used for tracking in DebugGLTextureData. RefPtr img = aGLContext->ReadTexImageHelper()->ReadTexImage(0, textureTarget, size, shaderConfig, aFlipY); gLayerScopeManager.GetSocketManager()->AppendDebugData( new DebugGLTextureData(aGLContext, aLayerRef, textureTarget, texID, img, aIsMask, Move(aPacket))); sSentTextureIds.push_back(texID); gLayerScopeManager.CurrentSession().mTexIDs.push_back(texID); } void SenderHelper::SetAndSendTexture(GLContext* aGLContext, void* aLayerRef, TextureSourceOGL* aSource, const TexturedEffect* aEffect) { // Expose packet creation here, so we could dump primary texture effect attributes. auto packet = MakeUnique(); layerscope::TexturePacket* texturePacket = packet->mutable_texture(); texturePacket->set_mpremultiplied(aEffect->mPremultiplied); DumpFilter(texturePacket, aEffect->mSamplingFilter); DumpRect(texturePacket->mutable_mtexturecoords(), aEffect->mTextureCoords); SendTextureSource(aGLContext, aLayerRef, aSource, false, false, Move(packet)); } void SenderHelper::SendTexturedEffect(GLContext* aGLContext, void* aLayerRef, const TexturedEffect* aEffect) { TextureSourceOGL* source = aEffect->mTexture->AsSourceOGL(); if (!source) { return; } // Fallback texture sending path. SetAndSendTexture(aGLContext, aLayerRef, source, aEffect); } void SenderHelper::SendMaskEffect(GLContext* aGLContext, void* aLayerRef, const EffectMask* aEffect) { TextureSourceOGL* source = aEffect->mMaskTexture->AsSourceOGL(); if (!source) { return; } // Expose packet creation here, so we could dump secondary mask effect attributes. auto packet = MakeUnique(); TexturePacket::EffectMask* mask = packet->mutable_texture()->mutable_mask(); mask->mutable_msize()->set_w(aEffect->mSize.width); mask->mutable_msize()->set_h(aEffect->mSize.height); auto element = reinterpret_cast(&(aEffect->mMaskTransform)); for (int i = 0; i < 16; i++) { mask->mutable_mmasktransform()->add_m(*element++); } SendTextureSource(aGLContext, aLayerRef, source, false, true, Move(packet)); } void SenderHelper::SendYCbCrEffect(GLContext* aGLContext, void* aLayerRef, const EffectYCbCr* aEffect) { TextureSource* sourceYCbCr = aEffect->mTexture; if (!sourceYCbCr) return; const int Y = 0, Cb = 1, Cr = 2; TextureSourceOGL *sources[] = { sourceYCbCr->GetSubSource(Y)->AsSourceOGL(), sourceYCbCr->GetSubSource(Cb)->AsSourceOGL(), sourceYCbCr->GetSubSource(Cr)->AsSourceOGL() }; for (auto source: sources) { SetAndSendTexture(aGLContext, aLayerRef, source, aEffect); } } void SenderHelper::SendEffectChain(GLContext* aGLContext, const EffectChain& aEffectChain, int aWidth, int aHeight) { if (!sLayersBufferSendable) return; const Effect* primaryEffect = aEffectChain.mPrimaryEffect; MOZ_ASSERT(primaryEffect); if (!primaryEffect) { return; } switch (primaryEffect->mType) { case EffectTypes::RGB: { const TexturedEffect* texturedEffect = static_cast(primaryEffect); SendTexturedEffect(aGLContext, aEffectChain.mLayerRef, texturedEffect); break; } case EffectTypes::YCBCR: { const EffectYCbCr* yCbCrEffect = static_cast(primaryEffect); SendYCbCrEffect(aGLContext, aEffectChain.mLayerRef, yCbCrEffect); break; } case EffectTypes::SOLID_COLOR: { const EffectSolidColor* solidColorEffect = static_cast(primaryEffect); SendColor(aEffectChain.mLayerRef, solidColorEffect->mColor, aWidth, aHeight); break; } case EffectTypes::COMPONENT_ALPHA: case EffectTypes::RENDER_TARGET: default: break; } if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { const EffectMask* effectMask = static_cast(aEffectChain.mSecondaryEffects[EffectTypes::MASK].get()); SendMaskEffect(aGLContext, aEffectChain.mLayerRef, effectMask); } } void LayerScope::ContentChanged(TextureHost *host) { if (!CheckSendable()) { return; } gLayerScopeManager.GetContentMonitor()->SetChangedHost(host); } // ---------------------------------------------- // SocketHandler implementation // ---------------------------------------------- void LayerScopeWebSocketManager::SocketHandler::OpenStream(nsISocketTransport* aTransport) { MOZ_ASSERT(aTransport); mTransport = aTransport; mTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(mOutputStream)); nsCOMPtr debugInputStream; mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(debugInputStream)); mInputStream = do_QueryInterface(debugInputStream); mInputStream->AsyncWait(this, 0, 0, NS_GetCurrentThread()); } bool LayerScopeWebSocketManager::SocketHandler::WriteToStream(void *aPtr, uint32_t aSize) { if (mState == NoHandshake) { // Not yet handshake, just return true in case of // LayerScope remove this handle return true; } else if (mState == HandshakeFailed) { return false; } if (!mOutputStream) { return false; } // Generate WebSocket header uint8_t wsHeader[10]; int wsHeaderSize = 0; const uint8_t opcode = 0x2; wsHeader[0] = 0x80 | (opcode & 0x0f); // FIN + opcode; if (aSize <= 125) { wsHeaderSize = 2; wsHeader[1] = aSize; } else if (aSize < 65536) { wsHeaderSize = 4; wsHeader[1] = 0x7E; NetworkEndian::writeUint16(wsHeader + 2, aSize); } else { wsHeaderSize = 10; wsHeader[1] = 0x7F; NetworkEndian::writeUint64(wsHeader + 2, aSize); } // Send WebSocket header nsresult rv; uint32_t cnt; rv = mOutputStream->Write(reinterpret_cast(wsHeader), wsHeaderSize, &cnt); if (NS_FAILED(rv)) return false; uint32_t written = 0; while (written < aSize) { uint32_t cnt; rv = mOutputStream->Write(reinterpret_cast(aPtr) + written, aSize - written, &cnt); if (NS_FAILED(rv)) return false; written += cnt; } return true; } NS_IMETHODIMP LayerScopeWebSocketManager::SocketHandler::OnInputStreamReady(nsIAsyncInputStream *aStream) { MOZ_ASSERT(mInputStream); if (!mInputStream) { return NS_OK; } if (!mConnected) { nsTArray protocolString; ReadInputStreamData(protocolString); if (WebSocketHandshake(protocolString)) { mState = HandshakeSuccess; mConnected = true; mInputStream->AsyncWait(this, 0, 0, NS_GetCurrentThread()); } else { mState = HandshakeFailed; } return NS_OK; } else { return HandleSocketMessage(aStream); } } void LayerScopeWebSocketManager::SocketHandler::ReadInputStreamData(nsTArray& aProtocolString) { nsLineBuffer lineBuffer; nsCString line; bool more = true; do { NS_ReadLine(mInputStream.get(), &lineBuffer, line, &more); if (line.Length() > 0) { aProtocolString.AppendElement(line); } } while (more && line.Length() > 0); } bool LayerScopeWebSocketManager::SocketHandler::WebSocketHandshake(nsTArray& aProtocolString) { nsresult rv; bool isWebSocket = false; nsCString version; nsCString wsKey; nsCString protocol; // Validate WebSocket client request. if (aProtocolString.Length() == 0) return false; // Check that the HTTP method is GET const char* HTTP_METHOD = "GET "; if (strncmp(aProtocolString[0].get(), HTTP_METHOD, strlen(HTTP_METHOD)) != 0) { return false; } for (uint32_t i = 1; i < aProtocolString.Length(); ++i) { const char* line = aProtocolString[i].get(); const char* prop_pos = strchr(line, ':'); if (prop_pos != nullptr) { nsCString key(line, prop_pos - line); nsCString value(prop_pos + 2); if (key.EqualsIgnoreCase("upgrade") && value.EqualsIgnoreCase("websocket")) { isWebSocket = true; } else if (key.EqualsIgnoreCase("sec-websocket-version")) { version = value; } else if (key.EqualsIgnoreCase("sec-websocket-key")) { wsKey = value; } else if (key.EqualsIgnoreCase("sec-websocket-protocol")) { protocol = value; } } } if (!isWebSocket) { return false; } if (!(version.EqualsLiteral("7") || version.EqualsLiteral("8") || version.EqualsLiteral("13"))) { return false; } if (!(protocol.EqualsIgnoreCase("binary"))) { return false; } if (!mOutputStream) { return false; } // Client request is valid. Start to generate and send server response. nsAutoCString guid("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); nsAutoCString res; SHA1Sum sha1; nsCString combined(wsKey + guid); sha1.update(combined.get(), combined.Length()); uint8_t digest[SHA1Sum::kHashSize]; // SHA1 digests are 20 bytes long. sha1.finish(digest); nsCString newString(reinterpret_cast(digest), SHA1Sum::kHashSize); rv = Base64Encode(newString, res); if (NS_FAILED(rv)) { return false; } nsCString response("HTTP/1.1 101 Switching Protocols\r\n"); response.AppendLiteral("Upgrade: websocket\r\n"); response.AppendLiteral("Connection: Upgrade\r\n"); response.Append(nsCString("Sec-WebSocket-Accept: ") + res + nsCString("\r\n")); response.AppendLiteral("Sec-WebSocket-Protocol: binary\r\n\r\n"); uint32_t written = 0; uint32_t size = response.Length(); while (written < size) { uint32_t cnt; rv = mOutputStream->Write(const_cast(response.get()) + written, size - written, &cnt); if (NS_FAILED(rv)) return false; written += cnt; } mOutputStream->Flush(); return true; } nsresult LayerScopeWebSocketManager::SocketHandler::HandleSocketMessage(nsIAsyncInputStream *aStream) { // The reading and parsing of this input stream is customized for layer viewer. const uint32_t cPacketSize = 1024; char buffer[cPacketSize]; uint32_t count = 0; nsresult rv = NS_OK; do { rv = mInputStream->Read((char *)buffer, cPacketSize, &count); // TODO: combine packets if we have to read more than once if (rv == NS_BASE_STREAM_WOULD_BLOCK) { mInputStream->AsyncWait(this, 0, 0, NS_GetCurrentThread()); return NS_OK; } if (NS_FAILED(rv)) { break; } if (count == 0) { // NS_BASE_STREAM_CLOSED CloseConnection(); break; } rv = ProcessInput(reinterpret_cast(buffer), count); } while (NS_SUCCEEDED(rv) && mInputStream); return rv; } nsresult LayerScopeWebSocketManager::SocketHandler::ProcessInput(uint8_t *aBuffer, uint32_t aCount) { uint32_t avail = aCount; // Decode Websocket data frame if (avail <= 2) { NS_WARNING("Packet size is less than 2 bytes"); return NS_OK; } // First byte, data type, only care the opcode // rsvBits: aBuffer[0] & 0x70 (0111 0000) uint8_t finBit = aBuffer[0] & 0x80; // 1000 0000 uint8_t opcode = aBuffer[0] & 0x0F; // 0000 1111 if (!finBit) { NS_WARNING("We cannot handle multi-fragments messages in Layerscope websocket parser."); return NS_OK; } // Second byte, data length uint8_t maskBit = aBuffer[1] & 0x80; // 1000 0000 int64_t payloadLength64 = aBuffer[1] & 0x7F; // 0111 1111 if (!maskBit) { NS_WARNING("Client to Server should set the mask bit"); return NS_OK; } uint32_t framingLength = 2 + 4; // 4 for masks if (payloadLength64 < 126) { if (avail < framingLength) return NS_OK; } else if (payloadLength64 == 126) { // 16 bit length field framingLength += 2; if (avail < framingLength) { return NS_OK; } payloadLength64 = aBuffer[2] << 8 | aBuffer[3]; } else { // 64 bit length framingLength += 8; if (avail < framingLength) { return NS_OK; } if (aBuffer[2] & 0x80) { // Section 4.2 says that the most significant bit MUST be // 0. (i.e. this is really a 63 bit value) NS_WARNING("High bit of 64 bit length set"); return NS_ERROR_ILLEGAL_VALUE; } // copy this in case it is unaligned payloadLength64 = NetworkEndian::readInt64(aBuffer + 2); } uint8_t *payload = aBuffer + framingLength; avail -= framingLength; uint32_t payloadLength = static_cast(payloadLength64); if (avail < payloadLength) { NS_WARNING("Packet size mismatch the payload length"); return NS_OK; } // Apply mask uint32_t mask = NetworkEndian::readUint32(payload - 4); ApplyMask(mask, payload, payloadLength); if (opcode == 0x8) { // opcode == 0x8 means connection close CloseConnection(); return NS_BASE_STREAM_CLOSED; } if (!HandleDataFrame(payload, payloadLength)) { NS_WARNING("Cannot decode payload data by the protocol buffer"); } return NS_OK; } void LayerScopeWebSocketManager::SocketHandler::ApplyMask(uint32_t aMask, uint8_t *aData, uint64_t aLen) { if (!aData || aLen == 0) { return; } // Optimally we want to apply the mask 32 bits at a time, // but the buffer might not be alligned. So we first deal with // 0 to 3 bytes of preamble individually while (aLen && (reinterpret_cast(aData) & 3)) { *aData ^= aMask >> 24; aMask = RotateLeft(aMask, 8); aData++; aLen--; } // perform mask on full words of data uint32_t *iData = reinterpret_cast(aData); uint32_t *end = iData + (aLen >> 2); NetworkEndian::writeUint32(&aMask, aMask); for (; iData < end; iData++) { *iData ^= aMask; } aMask = NetworkEndian::readUint32(&aMask); aData = (uint8_t *)iData; aLen = aLen % 4; // There maybe up to 3 trailing bytes that need to be dealt with // individually while (aLen) { *aData ^= aMask >> 24; aMask = RotateLeft(aMask, 8); aData++; aLen--; } } bool LayerScopeWebSocketManager::SocketHandler::HandleDataFrame(uint8_t *aData, uint32_t aSize) { // Handle payload data by protocol buffer auto p = MakeUnique(); p->ParseFromArray(static_cast(aData), aSize); if (!p->has_type()) { MOZ_ASSERT(false, "Protocol buffer decoding failed or cannot recongize it"); return false; } switch (p->type()) { case CommandPacket::LAYERS_TREE: if (p->has_value()) { SenderHelper::SetLayersTreeSendable(p->value()); } break; case CommandPacket::LAYERS_BUFFER: if (p->has_value()) { SenderHelper::SetLayersBufferSendable(p->value()); } break; case CommandPacket::NO_OP: default: NS_WARNING("Invalid message type"); break; } return true; } void LayerScopeWebSocketManager::SocketHandler::CloseConnection() { gLayerScopeManager.GetSocketManager()->CleanDebugData(); if (mInputStream) { mInputStream->AsyncWait(nullptr, 0, 0, nullptr); mInputStream = nullptr; } if (mOutputStream) { mOutputStream = nullptr; } if (mTransport) { mTransport->Close(NS_BASE_STREAM_CLOSED); mTransport = nullptr; } mConnected = false; } // ---------------------------------------------- // LayerScopeWebSocketManager implementation // ---------------------------------------------- LayerScopeWebSocketManager::LayerScopeWebSocketManager() : mHandlerMutex("LayerScopeWebSocketManager::mHandlerMutex") { NS_NewThread(getter_AddRefs(mDebugSenderThread)); mServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID); int port = gfxPrefs::LayerScopePort(); mServerSocket->Init(port, false, -1); mServerSocket->AsyncListen(new SocketListener); } LayerScopeWebSocketManager::~LayerScopeWebSocketManager() { mServerSocket->Close(); } void LayerScopeWebSocketManager::AppendDebugData(DebugGLData *aDebugData) { if (!mCurrentSender) { mCurrentSender = new DebugDataSender(mDebugSenderThread); } mCurrentSender->Append(aDebugData); } void LayerScopeWebSocketManager::CleanDebugData() { if (mCurrentSender) { mCurrentSender->Cleanup(); } } void LayerScopeWebSocketManager::DispatchDebugData() { MOZ_ASSERT(mCurrentSender.get() != nullptr); mCurrentSender->Send(); mCurrentSender = nullptr; } NS_IMETHODIMP LayerScopeWebSocketManager::SocketListener::OnSocketAccepted( nsIServerSocket *aServ, nsISocketTransport *aTransport) { if (!gLayerScopeManager.GetSocketManager()) return NS_OK; printf_stderr("*** LayerScope: Accepted connection\n"); gLayerScopeManager.GetSocketManager()->AddConnection(aTransport); gLayerScopeManager.GetContentMonitor()->Empty(); return NS_OK; } // ---------------------------------------------- // LayerScope implementation // ---------------------------------------------- /*static*/ void LayerScope::Init() { if (!gfxPrefs::LayerScopeEnabled() || XRE_IsGPUProcess()) { return; } gLayerScopeManager.CreateServerSocket(); } /*static*/ void LayerScope::DrawBegin() { if (!CheckSendable()) { return; } gLayerScopeManager.NewDrawSession(); } /*static*/ void LayerScope::SetRenderOffset(float aX, float aY) { if (!CheckSendable()) { return; } gLayerScopeManager.CurrentSession().mOffsetX = aX; gLayerScopeManager.CurrentSession().mOffsetY = aY; } /*static*/ void LayerScope::SetLayerTransform(const gfx::Matrix4x4& aMatrix) { if (!CheckSendable()) { return; } gLayerScopeManager.CurrentSession().mMVMatrix = aMatrix; } /*static*/ void LayerScope::SetDrawRects(size_t aRects, const gfx::Rect* aLayerRects, const gfx::Rect* aTextureRects) { if (!CheckSendable()) { return; } MOZ_ASSERT(aRects > 0 && aRects <= 4); MOZ_ASSERT(aLayerRects); gLayerScopeManager.CurrentSession().mRects = aRects; for (size_t i = 0; i < aRects; i++){ gLayerScopeManager.CurrentSession().mLayerRects[i] = aLayerRects[i]; gLayerScopeManager.CurrentSession().mTextureRects[i] = aTextureRects[i]; } } /*static*/ void LayerScope::DrawEnd(gl::GLContext* aGLContext, const EffectChain& aEffectChain, int aWidth, int aHeight) { // Protect this public function if (!CheckSendable()) { return; } // 1. Send textures. SenderHelper::SendEffectChain(aGLContext, aEffectChain, aWidth, aHeight); // 2. Send parameters of draw call, such as uniforms and attributes of // vertex adnd fragment shader. DrawSession& draws = gLayerScopeManager.CurrentSession(); gLayerScopeManager.GetSocketManager()->AppendDebugData( new DebugGLDrawData(draws.mOffsetX, draws.mOffsetY, draws.mMVMatrix, draws.mRects, draws.mLayerRects, draws.mTextureRects, draws.mTexIDs, aEffectChain.mLayerRef)); } /*static*/ void LayerScope::SendLayer(LayerComposite* aLayer, int aWidth, int aHeight) { // Protect this public function if (!CheckSendable()) { return; } SenderHelper::SendLayer(aLayer, aWidth, aHeight); } /*static*/ void LayerScope::SendLayerDump(UniquePtr aPacket) { // Protect this public function if (!CheckSendable() || !SenderHelper::GetLayersTreeSendable()) { return; } gLayerScopeManager.GetSocketManager()->AppendDebugData( new DebugGLLayersData(Move(aPacket))); } /*static*/ bool LayerScope::CheckSendable() { // Only compositor threads check LayerScope status MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || gIsGtest); if (!gfxPrefs::LayerScopeEnabled()) { return false; } if (!gLayerScopeManager.GetSocketManager()) { Init(); return false; } if (!gLayerScopeManager.GetSocketManager()->IsConnected()) { return false; } return true; } /*static*/ void LayerScope::CleanLayer() { if (CheckSendable()) { gLayerScopeManager.GetSocketManager()->CleanDebugData(); } } /*static*/ void LayerScope::SetHWComposed() { if (CheckSendable()) { gLayerScopeManager.GetSocketManager()->AppendDebugData( new DebugGLMetaData(Packet::META, true)); } } /*static*/ void LayerScope::SetPixelScale(double devPixelsPerCSSPixel) { gLayerScopeManager.SetPixelScale(devPixelsPerCSSPixel); } // ---------------------------------------------- // LayerScopeAutoFrame implementation // ---------------------------------------------- LayerScopeAutoFrame::LayerScopeAutoFrame(int64_t aFrameStamp) { // Do Begin Frame BeginFrame(aFrameStamp); } LayerScopeAutoFrame::~LayerScopeAutoFrame() { // Do End Frame EndFrame(); } void LayerScopeAutoFrame::BeginFrame(int64_t aFrameStamp) { if (!LayerScope::CheckSendable()) { return; } SenderHelper::ClearSentTextureIds(); gLayerScopeManager.GetSocketManager()->AppendDebugData( new DebugGLFrameStatusData(Packet::FRAMESTART, aFrameStamp)); } void LayerScopeAutoFrame::EndFrame() { if (!LayerScope::CheckSendable()) { return; } gLayerScopeManager.GetSocketManager()->AppendDebugData( new DebugGLFrameStatusData(Packet::FRAMEEND)); gLayerScopeManager.GetSocketManager()->DispatchDebugData(); } } // namespace layers } // namespace mozilla