Resize surface on receipt of WM_SIZE to avoid corruption during resize. We hook WM_SIZE using window subclassing.

This is a continuation of http://codereview.appspot.com/3038042/


Review URL: http://codereview.appspot.com/3122041

git-svn-id: http://angleproject.googlecode.com/svn/trunk@486 736b8ea6-26fd-11df-bfd4-992fa37f6226
diff --git a/samples/gles2_book/Common/Win32/esUtil_win32.c b/samples/gles2_book/Common/Win32/esUtil_win32.c
index 01ef1ce..c27131e 100644
--- a/samples/gles2_book/Common/Win32/esUtil_win32.c
+++ b/samples/gles2_book/Common/Win32/esUtil_win32.c
@@ -40,6 +40,16 @@
       case WM_CREATE:
          break;
 
+      case WM_SIZE:
+         {
+            ESContext *esContext = (ESContext*)(LONG_PTR) GetWindowLongPtr ( hWnd, GWL_USERDATA );
+            if ( esContext ) {
+               esContext->width = LOWORD( lParam );
+               esContext->height = HIWORD( lParam );
+               InvalidateRect( esContext->hWnd, NULL, FALSE );
+            }
+         }
+
       case WM_PAINT:
          {
             ESContext *esContext = (ESContext*)(LONG_PTR) GetWindowLongPtr ( hWnd, GWL_USERDATA );
@@ -47,7 +57,8 @@
             if ( esContext && esContext->drawFunc )
                esContext->drawFunc ( esContext );
             
-            ValidateRect( esContext->hWnd, NULL );
+            if ( esContext )
+              ValidateRect( esContext->hWnd, NULL );
          }
          break;
 
@@ -103,7 +114,7 @@
    if (!RegisterClass (&wndclass) ) 
       return FALSE; 
 
-   wStyle = WS_VISIBLE | WS_POPUP | WS_BORDER | WS_SYSMENU | WS_CAPTION;
+   wStyle = WS_VISIBLE | WS_POPUP | WS_BORDER | WS_SYSMENU | WS_CAPTION | WS_SIZEBOX;
    
    // Adjust the window rectangle so that the client area has
    // the correct number of pixels
diff --git a/src/libEGL/Surface.cpp b/src/libEGL/Surface.cpp
index 3b5add4..8b54ccc 100644
--- a/src/libEGL/Surface.cpp
+++ b/src/libEGL/Surface.cpp
@@ -34,11 +34,13 @@
     mSwapInterval = -1;
     setSwapInterval(1);
 
+    subclassWindow();
     resetSwapChain();
 }
 
 Surface::~Surface()
 {
+    unsubclassWindow();
     release();
 }
 
@@ -89,6 +91,18 @@
 
 void Surface::resetSwapChain()
 {
+    RECT windowRect;
+    if (!GetClientRect(getWindowHandle(), &windowRect))
+    {
+      ASSERT(false);
+      return;
+    }
+
+    resetSwapChain(windowRect.right - windowRect.left, windowRect.bottom - windowRect.top);
+}
+
+void Surface::resetSwapChain(int backbufferWidth, int backbufferHeight)
+{
     IDirect3DDevice9 *device = mDisplay->getDevice();
 
     if (device == NULL)
@@ -109,16 +123,8 @@
     presentParameters.PresentationInterval = mPresentInterval;
     presentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
     presentParameters.Windowed = TRUE;
-
-    RECT windowRect;
-    if (!GetClientRect(getWindowHandle(), &windowRect))
-    {
-        ASSERT(false);
-        return;
-    }
-
-    presentParameters.BackBufferWidth = windowRect.right - windowRect.left;
-    presentParameters.BackBufferHeight = windowRect.bottom - windowRect.top;
+    presentParameters.BackBufferWidth = backbufferWidth;
+    presentParameters.BackBufferHeight = backbufferHeight;
 
     IDirect3DSwapChain9 *swapChain = NULL;
     HRESULT result = device->CreateAdditionalSwapChain(&presentParameters, &swapChain);
@@ -199,6 +205,8 @@
 
     mPresentIntervalDirty = false;
 
+    InvalidateRect(mWindow, NULL, FALSE);
+
     // The flip state block recorded mFlipTexture so it is now invalid.
     releaseRecordedState(device);
 }
@@ -336,6 +344,52 @@
         mPreFlipState = NULL;
     }
 }
+#define kSurfaceProperty L"Egl::SurfaceOwner"
+#define kParentWndProc L"Egl::SurfaceParentWndProc"
+
+static LRESULT CALLBACK SurfaceWindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
+  if (message == WM_SIZE) {
+      Surface* surf = reinterpret_cast<Surface*>(GetProp(hwnd, kSurfaceProperty));
+      if(surf) {
+        surf->checkForOutOfDateSwapChain();
+      }
+  }
+  WNDPROC prevWndFunc = reinterpret_cast<WNDPROC >(GetProp(hwnd, kParentWndProc));
+  return CallWindowProc(prevWndFunc, hwnd, message, wparam, lparam);
+}
+
+void Surface::subclassWindow()
+{
+  SetLastError(0);
+  LONG oldWndProc = SetWindowLong(mWindow, GWL_WNDPROC, reinterpret_cast<LONG>(SurfaceWindowProc));
+  if(oldWndProc == 0 && GetLastError() != ERROR_SUCCESS) {
+    mWindowSubclassed = false;
+    return;
+  }
+
+  SetProp(mWindow, kSurfaceProperty, reinterpret_cast<HANDLE>(this));
+  SetProp(mWindow, kParentWndProc, reinterpret_cast<HANDLE>(oldWndProc));
+  mWindowSubclassed = true;
+}
+
+void Surface::unsubclassWindow()
+{
+  if(!mWindowSubclassed)
+    return;
+  // Check the windowproc is still SurfaceWindowProc.
+  // If this assert fails, then it is likely the application has subclassed the
+  // hwnd as well and did not unsubclass before destroying its EGL context. The
+  // application should be modified to either subclass before initializing the
+  // EGL context, or to unsubclass before destroying the EGL context.
+  ASSERT(GetWindowLong(mWindow, GWL_WNDPROC) == reinterpret_cast<LONG>(SurfaceWindowProc));
+
+  // un-subclass
+  LONG prevWndFunc = reinterpret_cast<LONG>(GetProp(mWindow, kParentWndProc));
+  SetWindowLong(mWindow, GWL_WNDPROC, prevWndFunc);
+  RemoveProp(mWindow, kSurfaceProperty);
+  RemoveProp(mWindow, kParentWndProc);
+  mWindowSubclassed = false;
+}
 
 bool Surface::checkForOutOfDateSwapChain()
 {
@@ -346,10 +400,14 @@
         return false;
     }
 
-    if (getWidth() != client.right - client.left || getHeight() != client.bottom - client.top || mPresentIntervalDirty)
-    {
-        resetSwapChain();
+    // Grow the buffer now, if the window has grown. We need to grow now to avoid losing information.
+    int clientWidth = client.right - client.left;
+    int clientHeight = client.bottom - client.top;
+    bool sizeDirty = clientWidth != getWidth() || clientHeight != getHeight();
 
+    if (sizeDirty || mPresentIntervalDirty)
+    {
+        resetSwapChain(clientWidth, clientHeight);
         if (static_cast<egl::Surface*>(getCurrentDrawSurface()) == this)
         {
             glMakeCurrent(glGetCurrentContext(), static_cast<egl::Display*>(getCurrentDisplay()), this);
@@ -357,7 +415,6 @@
 
         return true;
     }
-
     return false;
 }
 
@@ -387,11 +444,6 @@
         IDirect3DSurface9 *renderTarget = mRenderTarget;
         renderTarget->AddRef();
 
-        EGLint oldWidth = mWidth;
-        EGLint oldHeight = mHeight;
-
-        checkForOutOfDateSwapChain();
-
         IDirect3DDevice9 *device = mDisplay->getDevice();
 
         IDirect3DSurface9 *textureSurface;
@@ -404,25 +456,26 @@
         applyFlipState(device);
         device->SetTexture(0, flipTexture);
 
-        float xscale = (float)mWidth / oldWidth;
-        float yscale = (float)mHeight / oldHeight;
-
         // Render the texture upside down into the back buffer
-        // Texcoords are chosen to pin a potentially resized image into the upper-left corner without scaling.
-        float quad[4][6] = {{     0 - 0.5f,       0 - 0.5f, 0.0f, 1.0f, 0.0f,   1.0f       },
-                            {mWidth - 0.5f,       0 - 0.5f, 0.0f, 1.0f, xscale, 1.0f       },
-                            {mWidth - 0.5f, mHeight - 0.5f, 0.0f, 1.0f, xscale, 1.0f-yscale},
-                            {     0 - 0.5f, mHeight - 0.5f, 0.0f, 1.0f, 0.0f,   1.0f-yscale}};   // x, y, z, rhw, u, v
+        // Texcoords are chosen to flip the renderTarget about its Y axis.
+        float w = static_cast<float>(getWidth());
+        float h = static_cast<float>(getHeight());
+        float quad[4][6] = {{0 - 0.5f, 0 - 0.5f, 0.0f, 1.0f, 0.0f, 1.0f},
+                            {w - 0.5f, 0 - 0.5f, 0.0f, 1.0f, 1.0f, 1.0f},
+                            {w - 0.5f, h - 0.5f, 0.0f, 1.0f, 1.0f, 0.0f},
+                            {0 - 0.5f, h - 0.5f, 0.0f, 1.0f, 0.0f, 0.0f}};   // x, y, z, rhw, u, v
 
         mDisplay->startScene();
         device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, 6 * sizeof(float));
 
+
         flipTexture->Release();
         textureSurface->Release();
 
         restoreState(device);
 
         mDisplay->endScene();
+
         HRESULT result = mSwapChain->Present(NULL, NULL, NULL, NULL, 0);
 
         if (result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY || result == D3DERR_DRIVERINTERNALERROR)
@@ -437,6 +490,7 @@
 
         ASSERT(SUCCEEDED(result));
 
+        checkForOutOfDateSwapChain();
     }
 
     return true;
diff --git a/src/libEGL/Surface.h b/src/libEGL/Surface.h
index 06bbca1..c9cecb3 100644
--- a/src/libEGL/Surface.h
+++ b/src/libEGL/Surface.h
@@ -42,8 +42,8 @@
     virtual IDirect3DSurface9 *getDepthStencil();
 
     void setSwapInterval(EGLint interval);
-
-  private:
+    bool checkForOutOfDateSwapChain(); // returns true if swapchain changed due to resize or interval update
+private:
     DISALLOW_COPY_AND_ASSIGN(Surface);
 
     Display *const mDisplay;
@@ -53,8 +53,9 @@
     IDirect3DSurface9 *mDepthStencil;
     IDirect3DTexture9 *mFlipTexture;
 
-    bool checkForOutOfDateSwapChain();
-
+    void subclassWindow();
+    void unsubclassWindow();
+    void resetSwapChain(int backbufferWidth, int backbufferHeight);
     static DWORD convertInterval(EGLint interval);
 
     void applyFlipState(IDirect3DDevice9 *device);
@@ -67,6 +68,7 @@
     IDirect3DSurface9 *mPreFlipDepthStencil;
 
     const HWND mWindow;            // Window that the surface is created for.
+    bool mWindowSubclassed;        // Indicates whether we successfully subclassed mWindow for WM_RESIZE hooking
     const egl::Config *mConfig;    // EGL config surface was created with
     EGLint mHeight;                // Height of surface
     EGLint mWidth;                 // Width of surface