Bug 1249813 - part 1 - revise nsShmImage to allow draw targets anywhere inside its bounds. r=jrmuizel
authorLee Salzman <lsalzman@mozilla.com>
Thu, 25 Feb 2016 14:38:05 -0500
changeset 308600 a90066c54f7cf0755a8c2982ddf4279ebba4b1c4
parent 308599 d78875efa55acb8f8849336e264bee955ce6a460
child 308601 1c4e6d47312a5418d39435635c2ab06ef381f9e3
push id9214
push userraliiev@mozilla.com
push dateMon, 07 Mar 2016 14:25:21 +0000
treeherdermozilla-aurora@8849dd1a4a79 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1249813
milestone47.0a1
Bug 1249813 - part 1 - revise nsShmImage to allow draw targets anywhere inside its bounds. r=jrmuizel
gfx/layers/basic/BasicCompositor.cpp
widget/gtk/nsWindow.cpp
widget/nsShmImage.cpp
widget/nsShmImage.h
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -181,21 +181,24 @@ BasicCompositor::CreateRenderTargetForWi
 
   if (aRect.width * aRect.height == 0) {
     return nullptr;
   }
 
   MOZ_ASSERT(mDrawTarget);
 
   // Adjust bounds rect to account for new origin at (0, 0).
-  IntRect rect(0, 0, aRect.XMost(), aRect.YMost());
-  RefPtr<BasicCompositingRenderTarget> rt = new BasicCompositingRenderTarget(mDrawTarget, rect);
+  IntRect windowRect = aRect;
+  if (aRect.Size() != mDrawTarget->GetSize()) {
+    windowRect.ExpandToEnclose(IntPoint(0, 0));
+  }
+  RefPtr<BasicCompositingRenderTarget> rt = new BasicCompositingRenderTarget(mDrawTarget, windowRect);
 
   if (aInit == INIT_MODE_CLEAR) {
-    mDrawTarget->ClearRect(gfx::Rect(aRect));
+    mDrawTarget->ClearRect(Rect(aRect - rt->GetOrigin()));
   }
 
   return rt.forget();
 }
 
 already_AddRefed<DataTextureSource>
 BasicCompositor::CreateDataTextureSource(TextureFlags aFlags)
 {
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -2231,40 +2231,41 @@ nsWindow::OnExposeEvent(cairo_t *cr)
     }
 
     BufferMode layerBuffering = BufferMode::BUFFERED;
     RefPtr<DrawTarget> dt = GetDrawTarget(region, &layerBuffering);
     if (!dt) {
         return FALSE;
     }
     RefPtr<gfxContext> ctx;
+    IntRect boundsRect = region.GetBounds().ToUnknownRect();
+    IntPoint offset(0, 0);
+    if (dt->GetSize() == boundsRect.Size()) {
+      offset = boundsRect.TopLeft();
+      dt->SetTransform(Matrix::Translation(-offset));
+    }
 
 #ifdef MOZ_X11
-    nsIntRect boundsRect; // for shaped only
-
     if (shaped) {
         // Collapse update area to the bounding box. This is so we only have to
         // call UpdateTranslucentWindowAlpha once. After we have dropped
         // support for non-Thebes graphics, UpdateTranslucentWindowAlpha will be
         // our private interface so we can rework things to avoid this.
-        boundsRect = region.GetBounds().ToUnknownRect();
         dt->PushClipRect(Rect(boundsRect));
-    } else {
-        gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
-    }
-
-    if (shaped) {
+
         // The double buffering is done here to extract the shape mask.
         // (The shape mask won't be necessary when a visual with an alpha
         // channel is used on compositing window managers.)
         layerBuffering = BufferMode::BUFFER_NONE;
         RefPtr<DrawTarget> destDT = dt->CreateSimilarDrawTarget(boundsRect.Size(), SurfaceFormat::B8G8R8A8);
         ctx = new gfxContext(destDT, boundsRect.TopLeft());
     } else {
-        ctx = new gfxContext(dt);
+        gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
+
+        ctx = new gfxContext(dt, offset);
     }
 
 #if 0
     // NOTE: Paint flashing region would be wrong for cairo, since
     // cairo inflates the update region, etc.  So don't paint flash
     // for cairo.
 #ifdef DEBUG
     // XXX aEvent->region may refer to a newly-invalid area.  FIXME
@@ -2299,17 +2300,17 @@ nsWindow::OnExposeEvent(cairo_t *cr)
         }
     }
 
     ctx = nullptr;
     dt->PopClip();
 
 #  ifdef MOZ_HAVE_SHMIMAGE
     if (mShmImage && MOZ_LIKELY(!mIsDestroyed)) {
-      mShmImage->Put(mXDisplay, mXWindow, region);
+      mShmImage->Put(region);
     }
 #  endif  // MOZ_HAVE_SHMIMAGE
 #endif // MOZ_X11
 
     listener->DidPaintWindow();
 
     // Synchronously flush any new dirty areas
 #if (MOZ_WIDGET_GTK == 2)
@@ -6482,37 +6483,38 @@ nsWindow::GetSurfaceForGdkDrawable(GdkDr
 
     return result.forget();
 }
 #endif
 
 already_AddRefed<DrawTarget>
 nsWindow::GetDrawTarget(const LayoutDeviceIntRegion& aRegion, BufferMode* aBufferMode)
 {
-  if (!mGdkWindow) {
-    return nullptr;
-  }
-
-  LayoutDeviceIntRect bounds = aRegion.GetBounds();
-  LayoutDeviceIntSize size(bounds.XMost(), bounds.YMost());
-  if (size.width <= 0 || size.height <= 0) {
+  if (!mGdkWindow || aRegion.IsEmpty()) {
     return nullptr;
   }
 
   RefPtr<DrawTarget> dt;
 
 #ifdef MOZ_X11
 #  ifdef MOZ_HAVE_SHMIMAGE
   if (nsShmImage::UseShm()) {
-    dt = nsShmImage::EnsureShmImage(size,
-                                    mXDisplay, mXVisual, mXDepth, mShmImage);
+    if (!mShmImage) {
+      mShmImage = new nsShmImage(mXDisplay, mXWindow, mXVisual, mXDepth);
+    }
+    dt = mShmImage->CreateDrawTarget(aRegion);
     *aBufferMode = BufferMode::BUFFER_NONE;
+    if (!dt) {
+      mShmImage = nullptr;
+    }
   }
 #  endif  // MOZ_HAVE_SHMIMAGE
   if (!dt) {
+    LayoutDeviceIntRect bounds = aRegion.GetBounds();
+    LayoutDeviceIntSize size(bounds.XMost(), bounds.YMost());
     RefPtr<gfxXlibSurface> surf = new gfxXlibSurface(mXDisplay, mXWindow, mXVisual, size.ToUnknownSize());
     if (!surf->CairoStatus()) {
       dt = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surf.get(), surf->GetSize());
       *aBufferMode = BufferMode::BUFFERED;
     }
   }
 #endif // MOZ_X11
 
@@ -6530,17 +6532,17 @@ nsWindow::EndRemoteDrawingInRegion(DrawT
                                    LayoutDeviceIntRegion& aInvalidRegion)
 {
 #ifdef MOZ_X11
 #  ifdef MOZ_HAVE_SHMIMAGE
   if (!mGdkWindow || !mShmImage) {
     return;
   }
 
-  mShmImage->Put(mXDisplay, mXWindow, aInvalidRegion);
+  mShmImage->Put(aInvalidRegion);
 #  endif // MOZ_HAVE_SHMIMAGE
 #endif // MOZ_X11
 }
 
 // Code shared begin BeginMoveDrag and BeginResizeDrag
 bool
 nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent,
                       GdkWindow** aWindow, gint* aButton,
--- a/widget/nsShmImage.cpp
+++ b/widget/nsShmImage.cpp
@@ -1,21 +1,14 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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/. */
 
-#if defined(MOZ_WIDGET_GTK)
-#include <gtk/gtk.h>
-#include <gdk/gdkx.h>
-#elif defined(MOZ_WIDGET_QT)
-#include <QWindow>
-#endif
-
 #include "nsShmImage.h"
 #ifdef MOZ_WIDGET_GTK
 #include "gfxPlatformGtk.h"
 #endif
 
 #ifdef MOZ_HAVE_SHMIMAGE
 #include "mozilla/X11Util.h"
 
@@ -30,31 +23,31 @@ using namespace mozilla::ipc;
 using namespace mozilla::gfx;
 
 // If XShm isn't available to our client, we'll try XShm once, fail,
 // set this to false and then never try again.
 static bool gShmAvailable = true;
 bool nsShmImage::UseShm()
 {
 #ifdef MOZ_WIDGET_GTK
-    return (gShmAvailable && !gfxPlatformGtk::GetPlatform()->UseXRender());
+  return (gShmAvailable && !gfxPlatformGtk::GetPlatform()->UseXRender());
 #else
-    return gShmAvailable;
+  return gShmAvailable;
 #endif
 }
 
 #ifdef MOZ_WIDGET_GTK
 static int gShmError = 0;
 
 static int
 TrapShmError(Display* aDisplay, XErrorEvent* aEvent)
 {
-    // store the error code and ignore the error
-    gShmError = aEvent->error_code;
-    return 0;
+  // store the error code and ignore the error
+  gShmError = aEvent->error_code;
+  return 0;
 }
 #endif
 
 bool
 nsShmImage::CreateShmSegment()
 {
   if (!mImage) {
     return false;
@@ -63,26 +56,29 @@ nsShmImage::CreateShmSegment()
   size_t size = SharedMemory::PageAlignedSize(mImage->bytes_per_line * mImage->height);
 
   mInfo.shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
   if (mInfo.shmid == -1) {
     return false;
   }
 
   mInfo.shmaddr = (char *)shmat(mInfo.shmid, nullptr, 0);
+
+  // Mark the handle removed so that it will destroy the segment when unmapped.
+  shmctl(mInfo.shmid, IPC_RMID, nullptr);
+
   if (mInfo.shmaddr == (void *)-1) {
+    // Since mapping failed, the segment is already destroyed.
+    mInfo.shmid = -1;
+
     nsPrintfCString warning("shmat(): %s (%d)\n", strerror(errno), errno);
     NS_WARNING(warning.get());
     return false;
   }
 
-  // Mark the handle as deleted so that, should this process go away, the
-  // segment is cleaned up.
-  shmctl(mInfo.shmid, IPC_RMID, 0);
-
 #ifdef DEBUG
   struct shmid_ds info;
   if (shmctl(mInfo.shmid, IPC_STAT, &info) < 0) {
     return false;
   }
 
   MOZ_ASSERT(size <= info.shm_segsz,
              "Segment doesn't have enough space!");
@@ -100,164 +96,146 @@ nsShmImage::DestroyShmSegment()
 {
   if (mInfo.shmid != -1) {
     shmdt(mInfo.shmaddr);
     mInfo.shmid = -1;
   }
 }
 
 bool
-nsShmImage::CreateImage(const LayoutDeviceIntSize& aSize,
-                        Display* aDisplay, Visual* aVisual, unsigned int aDepth)
+nsShmImage::CreateImage(const IntSize& aSize)
 {
-  mDisplay = aDisplay;
-  mImage = XShmCreateImage(aDisplay, aVisual, aDepth,
-                           ZPixmap, nullptr,
-                           &mInfo,
-                           aSize.width, aSize.height);
-  if (!mImage || !CreateShmSegment()) {
-    return false;
-  }
+  MOZ_ASSERT(mDisplay && mVisual);
 
-#if defined(MOZ_WIDGET_GTK)
-  gShmError = 0;
-  XErrorHandler previousHandler = XSetErrorHandler(TrapShmError);
-  Status attachOk = XShmAttach(aDisplay, &mInfo);
-  XSync(aDisplay, False);
-  XSetErrorHandler(previousHandler);
-  if (gShmError) {
-    attachOk = 0;
-  }
-#elif defined(MOZ_WIDGET_QT)
-  Status attachOk = XShmAttach(aDisplay, &mInfo);
-#endif
-
-  if (!attachOk) {
-    // Assume XShm isn't available, and don't attempt to use it
-    // again.
-    gShmAvailable = false;
-    return false;
-  }
-
-  mXAttached = true;
-  mSize = aSize;
   mFormat = SurfaceFormat::UNKNOWN;
-  switch (mImage->depth) {
+  switch (mDepth) {
   case 32:
-    if ((mImage->red_mask == 0xff0000) &&
-        (mImage->green_mask == 0xff00) &&
-        (mImage->blue_mask == 0xff)) {
+    if (mVisual->red_mask == 0xff0000 &&
+        mVisual->green_mask == 0xff00 &&
+        mVisual->blue_mask == 0xff) {
       mFormat = SurfaceFormat::B8G8R8A8;
     }
     break;
   case 24:
     // Only support the BGRX layout, and report it as BGRA to the compositor.
     // The alpha channel will be discarded when we put the image.
-    if ((mImage->red_mask == 0xff0000) &&
-        (mImage->green_mask == 0xff00) &&
-        (mImage->blue_mask == 0xff)) {
+    if (mVisual->red_mask == 0xff0000 &&
+        mVisual->green_mask == 0xff00 &&
+        mVisual->blue_mask == 0xff) {
       mFormat = SurfaceFormat::B8G8R8A8;
     }
     break;
   case 16:
     mFormat = SurfaceFormat::R5G6B5_UINT16;
     break;
   }
 
   if (mFormat == SurfaceFormat::UNKNOWN) {
     NS_WARNING("Unsupported XShm Image format!");
     gShmAvailable = false;
     return false;
   }
 
+  mImage = XShmCreateImage(mDisplay, mVisual, mDepth,
+                           ZPixmap, nullptr,
+                           &mInfo,
+                           aSize.width, aSize.height);
+  if (!mImage || !CreateShmSegment()) {
+    DestroyImage();
+    return false;
+  }
+
+#ifdef MOZ_WIDGET_GTK
+  gShmError = 0;
+  XErrorHandler previousHandler = XSetErrorHandler(TrapShmError);
+  Status attachOk = XShmAttach(mDisplay, &mInfo);
+  XSync(mDisplay, False);
+  XSetErrorHandler(previousHandler);
+  if (gShmError) {
+    attachOk = 0;
+  }
+#else
+  Status attachOk = XShmAttach(mDisplay, &mInfo);
+#endif
+
+  if (!attachOk) {
+    DestroyShmSegment();
+    DestroyImage();
+
+    // Assume XShm isn't available, and don't attempt to use it
+    // again.
+    gShmAvailable = false;
+    return false;
+  }
+
   return true;
 }
 
-nsShmImage::~nsShmImage()
+void
+nsShmImage::DestroyImage()
 {
   if (mImage) {
     mozilla::FinishX(mDisplay);
-    if (mXAttached) {
+    if (mInfo.shmid != -1) {
       XShmDetach(mDisplay, &mInfo);
     }
     XDestroyImage(mImage);
+    mImage = nullptr;
   }
   DestroyShmSegment();
 }
 
 already_AddRefed<DrawTarget>
-nsShmImage::CreateDrawTarget()
+nsShmImage::CreateDrawTarget(const LayoutDeviceIntRegion& aRegion)
 {
+  // Due to bug 1205045, we must avoid making GTK calls off the main thread to query window size.
+  // Instead we just track the largest offset within the image we are drawing to and grow the image
+  // to accomodate it. Since usually the entire window is invalidated on the first paint to it,
+  // this should grow the image to the necessary size quickly without many intermediate reallocations.
+  IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+  IntSize size(bounds.XMost(), bounds.YMost());
+  if (!mImage || size.width > mImage->width || size.height > mImage->height) {
+    DestroyImage();
+    if (!CreateImage(size)) {
+      return nullptr;
+    }
+  }
+
   return gfxPlatform::GetPlatform()->CreateDrawTargetForData(
-    reinterpret_cast<unsigned char*>(mImage->data),
-    mSize.ToUnknownSize(),
+    reinterpret_cast<unsigned char*>(mImage->data)
+      + bounds.y * mImage->bytes_per_line + bounds.x * BytesPerPixel(mFormat),
+    bounds.Size(),
     mImage->bytes_per_line,
     mFormat);
 }
 
-#ifdef MOZ_WIDGET_GTK
 void
-nsShmImage::Put(Display* aDisplay, Drawable aWindow,
-                const LayoutDeviceIntRegion& aRegion)
-{
-    GC gc = XCreateGC(aDisplay, aWindow, 0, nullptr);
-    LayoutDeviceIntRegion bounded;
-    bounded.And(aRegion,
-                LayoutDeviceIntRect(0, 0, mImage->width, mImage->height));
-    for (auto iter = bounded.RectIter(); !iter.Done(); iter.Next()) {
-        const LayoutDeviceIntRect& r = iter.Get();
-        XShmPutImage(aDisplay, aWindow, gc, mImage,
-                     r.x, r.y,
-                     r.x, r.y,
-                     r.width, r.height,
-                     False);
-    }
-
-    XFreeGC(aDisplay, gc);
-
-    // FIXME/bug 597336: we need to ensure that the shm image isn't
-    // scribbled over before all its pending XShmPutImage()s complete.
-    // However, XSync() is an unnecessarily heavyweight
-    // synchronization mechanism; other options are possible.  If this
-    // XSync is shown to hurt responsiveness, we need to explore the
-    // other options.
-    XSync(aDisplay, False);
-}
-
-#elif defined(MOZ_WIDGET_QT)
-void
-nsShmImage::Put(QWindow* aWindow, QRect& aRect)
+nsShmImage::Put(const LayoutDeviceIntRegion& aRegion)
 {
-    Display* dpy = gfxQtPlatform::GetXDisplay(aWindow);
-    Drawable d = aWindow->winId();
-
-    GC gc = XCreateGC(dpy, d, 0, nullptr);
-    // Avoid out of bounds painting
-    QRect inter = aRect.intersected(aWindow->geometry());
-    XShmPutImage(dpy, d, gc, mImage,
-                 inter.x(), inter.y(),
-                 inter.x(), inter.y(),
-                 inter.width(), inter.height(),
-                 False);
-    XFreeGC(dpy, gc);
-}
-#endif
+  if (!mImage) {
+    return;
+  }
 
-already_AddRefed<DrawTarget>
-nsShmImage::EnsureShmImage(const LayoutDeviceIntSize& aSize,
-                           Display* aDisplay, Visual* aVisual, unsigned int aDepth,
-                           RefPtr<nsShmImage>& aImage)
-{
-  if (!aImage || aImage->Size() != aSize) {
-    // Because we XSync() after XShmAttach() to trap errors, we
-    // know that the X server has the old image's memory mapped
-    // into its address space, so it's OK to destroy the old image
-    // here even if there are outstanding Puts.  The Detach is
-    // ordered after the Puts.
-    aImage = new nsShmImage;
-    if (!aImage->CreateImage(aSize, aDisplay, aVisual, aDepth)) {
-      aImage = nullptr;
-    }
+  GC gc = XCreateGC(mDisplay, mWindow, 0, nullptr);
+  LayoutDeviceIntRegion bounded;
+  bounded.And(aRegion,
+              LayoutDeviceIntRect(0, 0, mImage->width, mImage->height));
+  for (auto iter = bounded.RectIter(); !iter.Done(); iter.Next()) {
+    const LayoutDeviceIntRect& r = iter.Get();
+    XShmPutImage(mDisplay, mWindow, gc, mImage,
+                 r.x, r.y,
+                 r.x, r.y,
+                 r.width, r.height,
+                 False);
   }
-  return !aImage ? nullptr : aImage->CreateDrawTarget();
+
+  XFreeGC(mDisplay, gc);
+
+  // FIXME/bug 597336: we need to ensure that the shm image isn't
+  // scribbled over before all its pending XShmPutImage()s complete.
+  // However, XSync() is an unnecessarily heavyweight
+  // synchronization mechanism; other options are possible.  If this
+  // XSync is shown to hurt responsiveness, we need to explore the
+  // other options.
+  XSync(mDisplay, False);
 }
 
 #endif  // MOZ_HAVE_SHMIMAGE
--- a/widget/nsShmImage.h
+++ b/widget/nsShmImage.h
@@ -15,62 +15,58 @@
 
 #include "mozilla/gfx/2D.h"
 #include "nsIWidget.h"
 #include "nsAutoPtr.h"
 
 #include <X11/Xlib.h>
 #include <X11/extensions/XShm.h>
 
-#ifdef MOZ_WIDGET_QT
-class QRect;
-class QWindow;
-#endif
-
 class nsShmImage {
-    // bug 1168843, compositor thread may create shared memory instances that are destroyed by main thread on shutdown, so this must use thread-safe RC to avoid hitting assertion
-    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsShmImage)
+  // bug 1168843, compositor thread may create shared memory instances that are destroyed by main thread on shutdown, so this must use thread-safe RC to avoid hitting assertion
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsShmImage)
 
 public:
-    static bool UseShm();
-    static already_AddRefed<mozilla::gfx::DrawTarget>
-        EnsureShmImage(const mozilla::LayoutDeviceIntSize& aSize,
-                       Display* aDisplay, Visual* aVisual, unsigned int aDepth,
-                       RefPtr<nsShmImage>& aImage);
+  static bool UseShm();
 
-    already_AddRefed<mozilla::gfx::DrawTarget> CreateDrawTarget();
+  already_AddRefed<mozilla::gfx::DrawTarget>
+    CreateDrawTarget(const mozilla::LayoutDeviceIntRegion& aRegion);
+
+  void Put(const mozilla::LayoutDeviceIntRegion& aRegion);
 
-#ifdef MOZ_WIDGET_GTK
-    void Put(Display* aDisplay, Drawable aWindow,
-             const mozilla::LayoutDeviceIntRegion& aRegion);
-#elif defined(MOZ_WIDGET_QT)
-    void Put(QWindow* aWindow, QRect& aRect);
-#endif
-
-    mozilla::LayoutDeviceIntSize Size() const { return mSize; }
+  nsShmImage(Display* aDisplay,
+             Drawable aWindow,
+             Visual* aVisual,
+             unsigned int aDepth)
+    : mImage(nullptr)
+    , mDisplay(aDisplay)
+    , mWindow(aWindow)
+    , mVisual(aVisual)
+    , mDepth(aDepth)
+    , mFormat(mozilla::gfx::SurfaceFormat::UNKNOWN)
+  {
+    mInfo.shmid = -1;
+  }
 
 private:
-    nsShmImage()
-        : mImage(nullptr)
-        , mDisplay(nullptr)
-        , mFormat(mozilla::gfx::SurfaceFormat::UNKNOWN)
-        , mXAttached(false)
-    { mInfo.shmid = -1; }
+  ~nsShmImage()
+  {
+    DestroyImage();
+  }
 
-    ~nsShmImage();
+  bool CreateShmSegment();
+  void DestroyShmSegment();
 
-    bool CreateShmSegment();
-    void DestroyShmSegment();
-
-    bool CreateImage(const mozilla::LayoutDeviceIntSize& aSize,
-                     Display* aDisplay, Visual* aVisual, unsigned int aDepth);
+  bool CreateImage(const mozilla::gfx::IntSize& aSize);
+  void DestroyImage();
 
-    XImage*                      mImage;
-    Display*                     mDisplay;
-    XShmSegmentInfo              mInfo;
-    mozilla::LayoutDeviceIntSize mSize;
-    mozilla::gfx::SurfaceFormat  mFormat;
-    bool                         mXAttached;
+  XImage*                      mImage;
+  Display*                     mDisplay;
+  Drawable                     mWindow;
+  Visual*                      mVisual;
+  unsigned int                 mDepth;
+  XShmSegmentInfo              mInfo;
+  mozilla::gfx::SurfaceFormat  mFormat;
 };
 
 #endif // MOZ_HAVE_SHMIMAGE
 
 #endif