view src/windows/native/sun/java2d/windows/GDIWindowSurfaceData.cpp @ 3261:a06412e13bf7

6962318: Update copyright year Reviewed-by: xdono
author ohair
date Tue, 28 Dec 2010 15:53:50 -0800
parents 90dcea60577e
children c98afec1bf86
line wrap: on
line source
/*
 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "sun_java2d_windows_GDIWindowSurfaceData.h"

#include "GDIWindowSurfaceData.h"
#include "GraphicsPrimitiveMgr.h"
#include "Region.h"
#include "Disposer.h"
#include "WindowsFlags.h"
#include "awt_Component.h"
#include "awt_Palette.h"
#include "awt_Win32GraphicsDevice.h"
#include "gdefs.h"
#include "Trace.h"
#include "Devices.h"

#include "jni_util.h"

static LockFunc GDIWinSD_Lock;
static GetRasInfoFunc GDIWinSD_GetRasInfo;
static UnlockFunc GDIWinSD_Unlock;
static DisposeFunc GDIWinSD_Dispose;
static SetupFunc GDIWinSD_Setup;
static GetDCFunc GDIWinSD_GetDC;
static ReleaseDCFunc GDIWinSD_ReleaseDC;
static InvalidateSDFunc GDIWinSD_InvalidateSD;

static HBRUSH   nullbrush;
static HPEN     nullpen;

static jclass xorCompClass;

static jboolean beingShutdown = JNI_FALSE;
static volatile LONG timeStamp = 0;
extern CriticalSection windowMoveLock;

extern "C"
{
GeneralDisposeFunc DisposeThreadGraphicsInfo;
jobject JNI_GetCurrentThread(JNIEnv *env);
int threadInfoIndex = TLS_OUT_OF_INDEXES;

static jclass threadClass = NULL;
static jmethodID currentThreadMethodID = NULL;

void SetupThreadGraphicsInfo(JNIEnv *env, GDIWinSDOps *wsdo) {
    J2dTraceLn(J2D_TRACE_INFO, "SetupThreadGraphicsInfo");

    // REMIND: handle error when creation fails
    ThreadGraphicsInfo *info =
        (ThreadGraphicsInfo*)TlsGetValue(threadInfoIndex);
    if (info == NULL) {
        info = new ThreadGraphicsInfo;
        ZeroMemory(info, sizeof(ThreadGraphicsInfo));
        TlsSetValue(threadInfoIndex, (LPVOID)info);
        J2dTraceLn2(J2D_TRACE_VERBOSE,
                    "  current batch limit for for thread 0x%x is %d",
                     GetCurrentThreadId(), ::GdiGetBatchLimit());
        J2dTraceLn(J2D_TRACE_VERBOSE, "  setting to the limit to 1");
        // Fix for bug 4374079
        ::GdiSetBatchLimit(1);

        Disposer_AddRecord(env, JNI_GetCurrentThread(env),
                           DisposeThreadGraphicsInfo,
                           ptr_to_jlong(info));
    }

    HDC oldhDC = info->hDC;
    // the hDC is NULL for offscreen surfaces - we don't store it
    // in TLS as it must be created new every time.

    if( ((oldhDC == NULL) && wsdo->window != NULL) ||
         (info->wsdo != wsdo) ||
         (info->wsdoTimeStamp != wsdo->timeStamp) )
    {

        // Init graphics state, either because this is our first time
        // using it in this thread or because this thread is now
        // dealing with a different window than it was last time.

        //check extra condition:
        //(info->wsdoTimeStamp != wsdo->timeStamp).
        //Checking memory addresses (info->wsdo != wsdo) will not detect
        //that wsdo points to a newly allocated structure in case
        //that structure just got allocated at a "recycled" memory location
        //which previously was pointed by info->wsdo
        //see bug# 6859086

        // Release cached DC. We use deferred DC releasing mechanism because
        // the DC is associated with cached wsdo and component peer,
        // which may've been disposed by this time, and we have
        // no means of checking against it.
        if (oldhDC != NULL) {
            MoveDCToPassiveList(oldhDC);
            info->hDC = NULL;
        }

        if (wsdo->window != NULL){
            HDC hDC;
            // This is a window surface
            // First, init the HDC object
            AwtComponent *comp = GDIWindowSurfaceData_GetComp(env, wsdo);
            if (comp == NULL) {
                return;
            }
            hDC = comp->GetDCFromComponent();
            if (hDC != NULL) {
                ::SelectObject(hDC, nullbrush);
                ::SelectObject(hDC, nullpen);
                ::SelectClipRgn(hDC, (HRGN) NULL);
                ::SetROP2(hDC, R2_COPYPEN);
                wsdo->device->SelectPalette(hDC);
                // Note that on NT4 we don't need to do a realize here: the
                // palette-sharing takes care of color issues for us.  But
                // on win98 if we don't realize a DC's palette, that
                // palette does not appear to have correct access to the
                // logical->system mapping.
                wsdo->device->RealizePalette(hDC);

                // Second, init the rest of the graphics state
                ::GetClientRect(wsdo->window, &info->bounds);
                // Make window-relative from client-relative
                ::OffsetRect(&info->bounds, wsdo->insets.left, wsdo->insets.top);
                //Likewise, translate GDI calls from client-relative to window-relative
                ::OffsetViewportOrgEx(hDC, -wsdo->insets.left, -wsdo->insets.top, NULL);
            }

            // Finally, set these new values in the info for this thread
            info->hDC = hDC;
        }

        // cached brush and pen are not associated with any DC, and can be
        // reused, but have to set type to 0 to indicate that no pen/brush
        // were set to the new hdc
        info->type = 0;

        if (info->clip != NULL) {
            env->DeleteWeakGlobalRef(info->clip);
        }
        info->clip = NULL;

        if (info->comp != NULL) {
            env->DeleteWeakGlobalRef(info->comp);
        }
        info->comp = NULL;

        info->xorcolor = 0;
        info->patrop = PATCOPY;

        //store the address and time stamp of newly allocated GDIWinSDOps structure
        info->wsdo = wsdo;
        info->wsdoTimeStamp = wsdo->timeStamp;
    }
}

/**
 * Releases native data stored in Thread local storage.
 * Called by the Disposer when the associated thread dies.
 */
void DisposeThreadGraphicsInfo(JNIEnv *env, jlong tgi) {
    J2dTraceLn(J2D_TRACE_INFO, "DisposeThreadGraphicsInfo");
    ThreadGraphicsInfo *info = (ThreadGraphicsInfo*)jlong_to_ptr(tgi);
    if (info != NULL) {
        if (info->hDC != NULL) {
            // move the DC from the active dcs list to
            // the passive dc list to be released later
            MoveDCToPassiveList(info->hDC);
        }

        if (info->clip != NULL) {
            env->DeleteWeakGlobalRef(info->clip);
        }
        if (info->comp != NULL) {
            env->DeleteWeakGlobalRef(info->comp);
        }

        if (info->brush != NULL) {
            info->brush->Release();
        }
        if (info->pen != NULL) {
            info->pen->Release();
        }

        delete info;
    }
}

/**
 * Returns current Thread object.
 */
jobject
JNI_GetCurrentThread(JNIEnv *env) {
    return env->CallStaticObjectMethod(threadClass, currentThreadMethodID);
} /* JNI_GetCurrentThread() */

/**
 * Return the data associated with this thread.
 * NOTE: This function assumes that the SetupThreadGraphicsInfo()
 * function has already been called for this situation (thread,
 * window, etc.), so we can assume that the thread info contains
 * a valid hDC.  This should usually be the case since GDIWinSD_Setup
 * is called as part of the GetOps() process.
 */
ThreadGraphicsInfo *GetThreadGraphicsInfo(JNIEnv *env,
                                          GDIWinSDOps *wsdo) {
    return (ThreadGraphicsInfo*)TlsGetValue(threadInfoIndex);
}

__inline HDC GetThreadDC(JNIEnv *env, GDIWinSDOps *wsdo) {
    ThreadGraphicsInfo *info =
        (ThreadGraphicsInfo *)GetThreadGraphicsInfo(env, wsdo);
    if (!info) {
        return (HDC) NULL;
    }
    return info->hDC;
}

} // extern "C"

/**
 * This source file contains support code for loops using the
 * SurfaceData interface to talk to a Win32 drawable from native
 * code.
 */

static BOOL GDIWinSD_CheckMonitorArea(GDIWinSDOps *wsdo,
                                     SurfaceDataBounds *bounds,
                                     HDC hDC)
{
    HWND hW = wsdo->window;
    BOOL retCode = TRUE;

    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_CheckMonitorArea");
    int numScreens;
    {
        Devices::InstanceAccess devices;
        numScreens = devices->GetNumDevices();
    }
    if( numScreens > 1 ) {

        LPMONITORINFO miInfo;
        RECT rSect ={0,0,0,0};
        RECT rView ={bounds->x1, bounds->y1, bounds->x2, bounds->y2};
        retCode = FALSE;

        miInfo = wsdo->device->GetMonitorInfo();

        POINT ptOrig = {0, 0};
        ::ClientToScreen(hW, &ptOrig);
        ::OffsetRect(&rView,
            (ptOrig.x), (ptOrig.y));

        ::IntersectRect(&rSect,&rView,&(miInfo->rcMonitor));

        if( FALSE == ::IsRectEmpty(&rSect) ) {
            if( TRUE == ::EqualRect(&rSect,&rView) ) {
                retCode = TRUE;
            }
        }
    }
    return retCode;
}

extern "C" {

void
initThreadInfoIndex()
{
    if (threadInfoIndex == TLS_OUT_OF_INDEXES) {
        threadInfoIndex = TlsAlloc();
    }
}


/**
 * Utility function to make sure that native and java-level
 * surface depths are matched.  They can be mismatched when display-depths
 * change, either between the creation of the Java surfaceData structure
 * and the native ddraw surface, or later when a surface is automatically
 * adjusted to be the new display depth (even if it was created in a different
 * depth to begin with)
 */
BOOL SurfaceDepthsCompatible(int javaDepth, int nativeDepth)
{
    if (nativeDepth != javaDepth) {
        switch (nativeDepth) {
        case 0: // Error condition: something is wrong with the surface
        case 8:
        case 24:
            // Java and native surface depths should match exactly for
            // these cases
            return FALSE;
            break;
        case 16:
            // Java surfaceData should be 15 or 16 bits
            if (javaDepth < 15 || javaDepth > 16) {
                return FALSE;
            }
            break;
        case 32:
            // Could have this native depth for either 24- or 32-bit
            // Java surfaceData
            if (javaDepth != 24 && javaDepth != 32) {
                return FALSE;
            }
            break;
        default:
            // should not get here, but if we do something is odd, so
            // just register a failure
            return FALSE;
        }
    }
    return TRUE;
}


/*
 * Class:     sun_java2d_windows_GDIWindowSurfaceData
 * Method:    initIDs
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIWindowSurfaceData_initIDs(JNIEnv *env, jclass wsd,
                                                 jclass XORComp)
{
    jclass tc;
    J2dTraceLn(J2D_TRACE_INFO, "GDIWindowSurfaceData_initIDs");
    nullbrush = (HBRUSH) ::GetStockObject(NULL_BRUSH);
    nullpen = (HPEN) ::GetStockObject(NULL_PEN);

    initThreadInfoIndex();

    xorCompClass = (jclass)env->NewGlobalRef(XORComp);

    tc = env->FindClass("java/lang/Thread");
    DASSERT(tc != NULL);
    threadClass = (jclass)env->NewGlobalRef(tc);
    DASSERT(threadClass != NULL);
    currentThreadMethodID =
        env->GetStaticMethodID(threadClass,
                               "currentThread",  "()Ljava/lang/Thread;");
    DASSERT(currentThreadMethodID != NULL);
}

/*
 * Class:     sun_java2d_windows_GDIWindowSurfaceData
 * Method:    initOps
 * Signature: (Ljava/lang/Object;IIIIII)V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIWindowSurfaceData_initOps(JNIEnv *env, jobject wsd,
                                                 jobject peer, jint depth,
                                                 jint redMask, jint greenMask,
                                                 jint blueMask, jint screen)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIWindowSurfaceData_initOps");
    GDIWinSDOps *wsdo = (GDIWinSDOps *)SurfaceData_InitOps(env, wsd, sizeof(GDIWinSDOps));
    if (wsdo == NULL) {
        JNU_ThrowOutOfMemoryError(env, "Initialization of SurfaceData failed.");
        return;
    }
    wsdo->timeStamp = InterlockedIncrement(&timeStamp); //creation time stamp
    wsdo->sdOps.Lock = GDIWinSD_Lock;
    wsdo->sdOps.GetRasInfo = GDIWinSD_GetRasInfo;
    wsdo->sdOps.Unlock = GDIWinSD_Unlock;
    wsdo->sdOps.Dispose = GDIWinSD_Dispose;
    wsdo->sdOps.Setup = GDIWinSD_Setup;
    wsdo->GetDC = GDIWinSD_GetDC;
    wsdo->ReleaseDC = GDIWinSD_ReleaseDC;
    wsdo->InvalidateSD = GDIWinSD_InvalidateSD;
    wsdo->invalid = JNI_FALSE;
    wsdo->lockType = WIN32SD_LOCK_UNLOCKED;
    wsdo->peer = env->NewWeakGlobalRef(peer);
    wsdo->depth = depth;
    wsdo->pixelMasks[0] = redMask;
    wsdo->pixelMasks[1] = greenMask;
    wsdo->pixelMasks[2] = blueMask;
    // Init the DIB pixelStride and pixel masks according to
    // the pixel depth. In the 8-bit case, there are no
    // masks as a palette DIB is used instead. Likewise
    // in the 24-bit case, Windows doesn't expect the masks
    switch (depth) {
        case 8:
            wsdo->pixelStride = 1;
            break;
        case 15: //555
            wsdo->pixelStride = 2;
            break;
        case 16: //565
            wsdo->pixelStride = 2;
            break;
        case 24:
            wsdo->pixelStride = 3;
            break;
        case 32: //888
            wsdo->pixelStride = 4;
            break;
    }
    // GDIWindowSurfaceData_GetWindow will throw NullPointerException
    // if wsdo->window is NULL
    wsdo->window = GDIWindowSurfaceData_GetWindow(env, wsdo);
    J2dTraceLn2(J2D_TRACE_VERBOSE, "  wsdo=0x%x wsdo->window=0x%x",
                wsdo, wsdo->window);

    {
        Devices::InstanceAccess devices;
        wsdo->device = devices->GetDeviceReference(screen, FALSE);
    }
    if (wsdo->device == NULL ||
        !SurfaceDepthsCompatible(depth, wsdo->device->GetBitDepth()))
    {
        if (wsdo->device != NULL) {
            J2dTraceLn2(J2D_TRACE_WARNING,
                        "GDIWindowSurfaceData_initOps: Surface depth mismatch: "\
                        "wsdo->depth=%d device depth=%d. Surface invalidated.",
                        wsdo->depth, wsdo->device->GetBitDepth());
        } else {
            J2dTraceLn1(J2D_TRACE_WARNING,
                        "GDIWindowSurfaceData_initOps: Incorrect "\
                        "screen number (screen=%d). Surface invalidated.",
                        screen);
        }

        wsdo->invalid = JNI_TRUE;
    }
    wsdo->surfaceLock = new CriticalSection();
    wsdo->bitmap = NULL;
    wsdo->bmdc = NULL;
    wsdo->bmCopyToScreen = FALSE;
}

JNIEXPORT GDIWinSDOps * JNICALL
GDIWindowSurfaceData_GetOps(JNIEnv *env, jobject sData)
{
    SurfaceDataOps *ops = SurfaceData_GetOps(env, sData);
    // REMIND: There was originally a condition check here to make sure
    // that we were really dealing with a GDIWindowSurfaceData object, but
    // it did not allow for the existence of other win32-accelerated
    // surface data objects (e.g., Win32OffScreenSurfaceData).  I've
    // removed the check for now, but we should replace it with another,
    // more general check against Win32-related surfaces.
    return (GDIWinSDOps *) ops;
}

JNIEXPORT GDIWinSDOps * JNICALL
GDIWindowSurfaceData_GetOpsNoSetup(JNIEnv *env, jobject sData)
{
    // use the 'no setup' version of GetOps
    SurfaceDataOps *ops = SurfaceData_GetOpsNoSetup(env, sData);
    return (GDIWinSDOps *) ops;
}

JNIEXPORT AwtComponent * JNICALL
GDIWindowSurfaceData_GetComp(JNIEnv *env, GDIWinSDOps *wsdo)
{
    PDATA pData;
    jobject localObj = env->NewLocalRef(wsdo->peer);

    if (localObj == NULL || (pData = JNI_GET_PDATA(localObj)) == NULL) {
        J2dTraceLn1(J2D_TRACE_WARNING,
                    "GDIWindowSurfaceData_GetComp: Null pData? pData=0x%x",
                    pData);
        if (beingShutdown == JNI_TRUE) {
            wsdo->invalid = JNI_TRUE;
            return (AwtComponent *) NULL;
        }
        try {
            AwtToolkit::GetInstance().VerifyActive();
        } catch (awt_toolkit_shutdown&) {
            beingShutdown = JNI_TRUE;
            wsdo->invalid = JNI_TRUE;
            return (AwtComponent *) NULL;
        }
        if (wsdo->invalid == JNI_TRUE) {
            SurfaceData_ThrowInvalidPipeException(env,
                "GDIWindowSurfaceData: bounds changed");
        } else {
            JNU_ThrowNullPointerException(env, "component argument pData");
        }
        return (AwtComponent *) NULL;
    }
    return static_cast<AwtComponent*>(pData);
}

JNIEXPORT HWND JNICALL
GDIWindowSurfaceData_GetWindow(JNIEnv *env, GDIWinSDOps *wsdo)
{
    HWND window = wsdo->window;

    if (window == (HWND) NULL) {
        AwtComponent *comp = GDIWindowSurfaceData_GetComp(env, wsdo);
        if (comp == NULL) {
            J2dTraceLn(J2D_TRACE_WARNING,
                   "GDIWindowSurfaceData_GetWindow: null component");
            return (HWND) NULL;
        }
        comp->GetInsets(&wsdo->insets);
        window = comp->GetHWnd();
        if (::IsWindow(window) == FALSE) {
            J2dRlsTraceLn(J2D_TRACE_ERROR,
                          "GDIWindowSurfaceData_GetWindow: disposed component");
            JNU_ThrowNullPointerException(env, "disposed component");
            return (HWND) NULL;
        }
        wsdo->window = window;
    }

    return window;
}

} /* extern "C" */

static jboolean GDIWinSD_SimpleClip(JNIEnv *env, GDIWinSDOps *wsdo,
                                   SurfaceDataBounds *bounds,
                                   HDC hDC)
{
    RECT rClip;

    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_SimpleClip");
    if (hDC == NULL) {
        return JNI_FALSE;
    }

    int nComplexity = ::GetClipBox(hDC, &rClip);

    switch (nComplexity) {
    case COMPLEXREGION:
        {
            J2dTraceLn(J2D_TRACE_VERBOSE,
                       "  complex clipping region");
            // if complex user/system clip, more detailed testing required
            // check to see if the view itself has a complex clip.
            // ::GetClipBox is only API which returns overlapped window status
            // so we set the rView as our clip, and then see if resulting
            // clip is complex.
            // Only other way to figure this out would be to walk the
            // overlapping windows (no API to get the actual visible clip
            // list).  Then we'd still have to merge that info with the
            // clip region for the dc (if it exists).
            // REMIND: we can cache the CreateRectRgnIndirect result,
            // and only override with ::SetRectRgn

            // First, create a region handle (need existing HRGN for
            // the following call).
            HRGN rgnSave = ::CreateRectRgn(0, 0, 0, 0);
            int  clipStatus = ::GetClipRgn(hDC, rgnSave);
            if (-1 == clipStatus) {
                J2dTraceLn(J2D_TRACE_WARNING,
                           "GDIWinSD_SimpleClip: failed due to clip status");
                ::DeleteObject(rgnSave);
                return JNI_FALSE;
            }
            HRGN rgnBounds = ::CreateRectRgn(
                bounds->x1 - wsdo->insets.left,
                bounds->y1 - wsdo->insets.top,
                bounds->x2 - wsdo->insets.left,
                bounds->y2 - wsdo->insets.top);
            ::SelectClipRgn(hDC, rgnBounds);
            nComplexity = ::GetClipBox(hDC, &rClip);
            ::SelectClipRgn(hDC, clipStatus? rgnSave: NULL);
            ::DeleteObject(rgnSave);
            ::DeleteObject(rgnBounds);

            // Now, test the new clip box.  If it's still not a
            // SIMPLE region, then our bounds must intersect part of
            // the clipping article
            if (SIMPLEREGION != nComplexity) {
                J2dTraceLn(J2D_TRACE_WARNING,
                           "GDIWinSD_SimpleClip: failed due to complexity");
                return JNI_FALSE;
            }
        }
        // NOTE: No break here - we want to fall through into the
        // SIMPLE case, adjust our bounds by the new rClip rect
        // and make sure that our locking bounds are not empty.
    case SIMPLEREGION:
        J2dTraceLn(J2D_TRACE_VERBOSE, "  simple clipping region");
        // Constrain the bounds to the given clip box
        if (bounds->x1 < rClip.left) {
            bounds->x1 = rClip.left;
        }
        if (bounds->y1 < rClip.top) {
            bounds->y1 = rClip.top;
        }
        if (bounds->x2 > rClip.right) {
            bounds->x2 = rClip.right;
        }
        if (bounds->y2 > rClip.bottom) {
            bounds->y2 = rClip.bottom;
        }
        // If the bounds are 0 or negative, then the bounds have
        // been obscured by the clip box, so return FALSE
        if ((bounds->x2 <= bounds->x1) ||
            (bounds->y2 <= bounds->y1)) {
            // REMIND: We should probably do something different here
            // instead of simply returning FALSE.  Since the bounds are
            // empty we won't end up drawing anything, so why spend the
            // effort of returning false and having GDI do a LOCK_BY_DIB?
            // Perhaps we need a new lock code that will indicate that we
            // shouldn't bother drawing?
            J2dTraceLn(J2D_TRACE_WARNING,
                       "GDIWinSD_SimpleClip: failed due to empty bounds");
            return JNI_FALSE;
        }
        break;
    case NULLREGION:
    case ERROR:
    default:
        J2dTraceLn1(J2D_TRACE_ERROR,
                   "GDIWinSD_SimpleClip: failed due to incorrect complexity=%d",
                    nComplexity);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

static jint GDIWinSD_Lock(JNIEnv *env,
                         SurfaceDataOps *ops,
                         SurfaceDataRasInfo *pRasInfo,
                         jint lockflags)
{
    GDIWinSDOps *wsdo = (GDIWinSDOps *) ops;
    int ret = SD_SUCCESS;
    HDC hDC;
    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_Lock");

    /* This surfaceLock replaces an earlier implementation which used a
    monitor associated with the peer.  That implementation was prone
    to deadlock problems, so it was replaced by a lock that does not
    have dependencies outside of this thread or object.
    However, this lock doesn't necessarily do all that we want.
    For example, a user may issue a call which results in a DIB lock
    and another call which results in a DDraw Blt.  We can't guarantee
    what order these operations happen in (they are driver and
    video-card dependent), so locking around the issue of either of
    those calls won't necessarily guarantee a particular result.
    The real solution might be to move away from mixing our
    rendering API's.  That is, if we only used DDraw, then we could
    guarantee that all rendering operations would happen in a given
    order.  Similarly for GDI.  But by mixing them, we leave our
    code at the mercy of driver bugs.*/
    wsdo->surfaceLock->Enter();
    if (wsdo->invalid == JNI_TRUE) {
        J2dTraceLn(J2D_TRACE_WARNING, "GDIWinSD_Lock: surface is invalid");
        wsdo->surfaceLock->Leave();
        if (beingShutdown != JNI_TRUE) {
            SurfaceData_ThrowInvalidPipeException(env,
                "GDIWindowSurfaceData: bounds changed");
        }
        return SD_FAILURE;
    }
    if (wsdo->lockType != WIN32SD_LOCK_UNLOCKED) {
        wsdo->surfaceLock->Leave();
        if (!safe_ExceptionOccurred(env)) {
            JNU_ThrowInternalError(env, "Win32 LockRasData cannot nest locks");
        }
        return SD_FAILURE;
    }

    hDC = wsdo->GetDC(env, wsdo, 0, NULL, NULL, NULL, 0);
    if (hDC == NULL) {
        wsdo->surfaceLock->Leave();
        if (beingShutdown != JNI_TRUE) {
            JNU_ThrowNullPointerException(env, "HDC for component");
        }
        return SD_FAILURE;
    }

    if (lockflags & SD_LOCK_RD_WR) {
        // Do an initial clip to the client region of the window
        RECT crect;
        ::GetClientRect(wsdo->window, &crect);

        // Translate to window coords
        crect.left += wsdo->insets.left;
        crect.top += wsdo->insets.top;
        crect.right += wsdo->insets.left;
        crect.bottom += wsdo->insets.top;

        SurfaceDataBounds *bounds = &pRasInfo->bounds;

        if (bounds->x1 < crect.left) {
            bounds->x1 = crect.left;
        }
        if (bounds->y1 < crect.top) {
            bounds->y1 = crect.top;
        }
        if (bounds->x2 > crect.right) {
            bounds->x2 = crect.right;
        }
        if (bounds->y2 > crect.bottom) {
            bounds->y2 = crect.bottom;
        }

        if (bounds->x2 > bounds->x1 && bounds->y2 > bounds->y1) {
            wsdo->lockType = WIN32SD_LOCK_BY_DIB;
            if (lockflags & SD_LOCK_FASTEST) {
                ret = SD_SLOWLOCK;
            }
            J2dTraceLn(J2D_TRACE_VERBOSE, " locked by DIB");
        } else {
            wsdo->ReleaseDC(env, wsdo, hDC);
            wsdo->lockType = WIN32SD_LOCK_UNLOCKED;
            wsdo->surfaceLock->Leave();
            ret = SD_FAILURE;
            J2dTraceLn(J2D_TRACE_ERROR,
                       "GDIWinSD_Lock: error locking by DIB");
        }
    } else {
        J2dTraceLn(J2D_TRACE_VERBOSE, "GDIWinSD_Lock: surface wasn't locked");
        /* They didn't lock for anything - we won't give them anything */
        wsdo->ReleaseDC(env, wsdo, hDC);
        wsdo->lockType = WIN32SD_LOCK_UNLOCKED;
        wsdo->surfaceLock->Leave();
        ret = SD_FAILURE;
    }

    wsdo->lockFlags = lockflags;
    return ret;
}

static void GDIWinSD_GetRasInfo(JNIEnv *env,
                               SurfaceDataOps *ops,
                               SurfaceDataRasInfo *pRasInfo)
{
    GDIWinSDOps *wsdo = (GDIWinSDOps *) ops;
    jint lockflags = wsdo->lockFlags;
    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_GetRasInfo");
    HDC hDC = GetThreadDC(env, wsdo);

    if (wsdo->lockType == WIN32SD_LOCK_UNLOCKED) {
        memset(pRasInfo, 0, sizeof(*pRasInfo));
        return;
    }

    if (wsdo->lockType == WIN32SD_LOCK_BY_DIB) {
        int x, y, w, h;
        int pixelStride = wsdo->pixelStride;
        // do not subtract insets from x,y as we take care of it in SD_GetDC
        x = pRasInfo->bounds.x1;
        y = pRasInfo->bounds.y1;
        w = pRasInfo->bounds.x2 - x;
        h = pRasInfo->bounds.y2 - y;

        struct tagBitmapheader  {
            BITMAPINFOHEADER bmiHeader;
            union {
                DWORD           dwMasks[3];
                RGBQUAD         palette[256];
            } colors;
        } bmi;

        // Need to create bitmap if we don't have one already or
        // if the existing one is not large enough for this operation
        // or if we are in 8 bpp display mode (because we need to
        // make sure that the latest palette info gets loaded into
        // the bitmap)
        // REMIND: we should find some way to dynamically force bitmap
        // recreation only when the palette changes
        if (pixelStride == 1 || !wsdo->bitmap || (w > wsdo->bmWidth) ||
            (h > wsdo->bmHeight))
        {
            if (wsdo->bitmap) {
                // delete old objects
                J2dTraceLn(J2D_TRACE_VERBOSE,
                           "GDIWinSD_GetRasInfo: recreating GDI bitmap");
                if (wsdo->bmdc) {   // should not be null
                    ::SelectObject(wsdo->bmdc, wsdo->oldmap);
                    ::DeleteDC(wsdo->bmdc);
                    wsdo->bmdc = 0;
                }
                ::DeleteObject(wsdo->bitmap);
                wsdo->bitmap = 0;
            }
            bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
            bmi.bmiHeader.biWidth = w;
            bmi.bmiHeader.biHeight = -h;
            wsdo->bmWidth = w;
            wsdo->bmHeight = h;
            bmi.bmiHeader.biPlanes = 1;
            bmi.bmiHeader.biBitCount = pixelStride * 8;
            // 1,3 byte use BI_RGB, 2,4 byte use BI_BITFIELD...
            bmi.bmiHeader.biCompression =
                (pixelStride & 1)
                    ? BI_RGB
                    : BI_BITFIELDS;
            bmi.bmiHeader.biSizeImage = 0;
            bmi.bmiHeader.biXPelsPerMeter = 0;
            bmi.bmiHeader.biYPelsPerMeter = 0;
            bmi.bmiHeader.biClrUsed = 0;
            bmi.bmiHeader.biClrImportant = 0;
            if (pixelStride == 1) {
                // we can use systemEntries here because
                // RGBQUAD is xRGB and systemEntries are stored as xRGB
                memcpy(bmi.colors.palette, wsdo->device->GetSystemPaletteEntries(),
                       sizeof(bmi.colors.palette));
            } else {
                // For non-index cases, init the masks for the pixel depth
                for (int i = 0; i < 3; i++) {
                    bmi.colors.dwMasks[i] = wsdo->pixelMasks[i];
                }
            }

            // REMIND: This would be better if moved to the Lock function
            // so that errors could be dealt with.
            wsdo->bitmap = ::CreateDIBSection(hDC, (BITMAPINFO *) &bmi,
                                              DIB_RGB_COLORS, &wsdo->bmBuffer, NULL, 0);
            if (wsdo->bitmap != 0) {
                // scanStride is cached along with reuseable bitmap
                // Round up to the next DWORD boundary
                wsdo->bmScanStride = (wsdo->bmWidth * pixelStride + 3) & ~3;
                wsdo->bmdc = ::CreateCompatibleDC(hDC);
                if (wsdo->bmdc == 0) {
                    ::DeleteObject(wsdo->bitmap);
                    wsdo->bitmap = 0;
                } else {
                    wsdo->oldmap = (HBITMAP) ::SelectObject(wsdo->bmdc,
                                                            wsdo->bitmap);
                }
            }
        }
        if (wsdo->bitmap != 0) {
            if (lockflags & SD_LOCK_NEED_PIXELS) {
                int ret = ::BitBlt(wsdo->bmdc, 0, 0, w, h,
                                   hDC, x, y, SRCCOPY);
                ::GdiFlush();
            }
            wsdo->x = x;
            wsdo->y = y;
            wsdo->w = w;
            wsdo->h = h;
            pRasInfo->rasBase = (char *)wsdo->bmBuffer - (x*pixelStride +
                                y*wsdo->bmScanStride);
            pRasInfo->pixelStride = pixelStride;
            pRasInfo->pixelBitOffset = 0;
            pRasInfo->scanStride = wsdo->bmScanStride;
            if (lockflags & SD_LOCK_WRITE) {
                // If the user writes to the bitmap then we should
                // copy the bitmap to the screen during Unlock
                wsdo->bmCopyToScreen = TRUE;
            }
        } else {
            pRasInfo->rasBase = NULL;
            pRasInfo->pixelStride = 0;
            pRasInfo->pixelBitOffset = 0;
            pRasInfo->scanStride = 0;
        }
    } else {
        /* They didn't lock for anything - we won't give them anything */
        pRasInfo->rasBase = NULL;
        pRasInfo->pixelStride = 0;
        pRasInfo->pixelBitOffset = 0;
        pRasInfo->scanStride = 0;
    }
    if (wsdo->lockFlags & SD_LOCK_LUT) {
        pRasInfo->lutBase =
            (long *) wsdo->device->GetSystemPaletteEntries();
        pRasInfo->lutSize = 256;
    } else {
        pRasInfo->lutBase = NULL;
        pRasInfo->lutSize = 0;
    }
    if (wsdo->lockFlags & SD_LOCK_INVCOLOR) {
        pRasInfo->invColorTable = wsdo->device->GetSystemInverseLUT();
        ColorData *cData = wsdo->device->GetColorData();
        pRasInfo->redErrTable = cData->img_oda_red;
        pRasInfo->grnErrTable = cData->img_oda_green;
        pRasInfo->bluErrTable = cData->img_oda_blue;
    } else {
        pRasInfo->invColorTable = NULL;
        pRasInfo->redErrTable = NULL;
        pRasInfo->grnErrTable = NULL;
        pRasInfo->bluErrTable = NULL;
    }
    if (wsdo->lockFlags & SD_LOCK_INVGRAY) {
        pRasInfo->invGrayTable =
            wsdo->device->GetColorData()->pGrayInverseLutData;
    } else {
        pRasInfo->invGrayTable = NULL;
    }
}

static void GDIWinSD_Setup(JNIEnv *env,
                          SurfaceDataOps *ops)
{
    // Call SetupTGI to ensure that this thread already has a DC that is
    // compatible with this window.  This means that we won't be calling
    // ::SendMessage(GETDC) in the middle of a lock procedure, which creates
    // a potential deadlock situation.
    // Note that calling SetupTGI here means that anybody needing a DC
    // later in this rendering process need only call GetTGI, which
    // assumes that the TGI structure is valid for this thread/window.
    SetupThreadGraphicsInfo(env, (GDIWinSDOps*)ops);
}


static void GDIWinSD_Unlock(JNIEnv *env,
                           SurfaceDataOps *ops,
                           SurfaceDataRasInfo *pRasInfo)
{
    GDIWinSDOps *wsdo = (GDIWinSDOps *) ops;
    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_Unlock");
    HDC hDC = GetThreadDC(env, wsdo);

    if (wsdo->lockType == WIN32SD_LOCK_UNLOCKED) {
        if (!safe_ExceptionOccurred(env)) {
            JNU_ThrowInternalError(env,
                                   "Unmatched unlock on Win32 SurfaceData");
        }
        return;
    }

    if (wsdo->lockType == WIN32SD_LOCK_BY_DIB) {
        if (wsdo->lockFlags & SD_LOCK_WRITE) {
            J2dTraceLn(J2D_TRACE_VERBOSE,
                       "GDIWinSD_Unlock: do Blt of the bitmap");
            if (wsdo->bmCopyToScreen && ::IsWindowVisible(wsdo->window)) {
                // Don't bother copying to screen if our window has gone away
                // or if the bitmap was not actually written to during this
                // Lock/Unlock procedure.
                ::BitBlt(hDC, wsdo->x, wsdo->y, wsdo->w, wsdo->h,
                    wsdo->bmdc, 0, 0, SRCCOPY);
                ::GdiFlush();
            }
            wsdo->bmCopyToScreen = FALSE;
        }
        wsdo->lockType = WIN32SD_LOCK_UNLOCKED;
        wsdo->ReleaseDC(env, wsdo, hDC);
    }
    wsdo->surfaceLock->Leave();
}

/*
 * REMIND: This mechanism is just a prototype of a way to manage a
 * small cache of DC objects.  It is incomplete in the following ways:
 *
 * - It is not thread-safe!  It needs appropriate locking and release calls
 *   (perhaps the AutoDC mechanisms from Kestrel)
 * - It does hardly any error checking (What if GetDCEx returns NULL?)
 * - It cannot handle printer DCs and their resolution
 * - It should probably "live" in the native SurfaceData object to allow
 *   alternate implementations for printing and embedding
 * - It doesn't handle XOR
 * - It caches the client bounds to determine if clipping is really needed
 *   (no way to invalidate the cached bounds and there is probably a better
 *    way to manage clip validation in any case)
 */

#define COLORFOR(c)     (PALETTERGB(((c)>>16)&0xff,((c)>>8)&0xff,((c)&0xff)))

COLORREF CheckGrayColor(GDIWinSDOps *wsdo, int c) {
    if (wsdo->device->GetGrayness() != GS_NOTGRAY) {
        int g = (77 *(c & 0xFF) +
                 150*((c >> 8) & 0xFF) +
                 29 *((c >> 16) & 0xFF) + 128) / 256;
        c = g | (g << 8) | (g << 16);
    }
    return COLORFOR(c);
}

static HDC GDIWinSD_GetDC(JNIEnv *env, GDIWinSDOps *wsdo,
                         jint type, jint *patrop,
                         jobject clip, jobject comp, jint color)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_GetDC");

    if (wsdo->invalid == JNI_TRUE) {
        if (beingShutdown != JNI_TRUE) {
            SurfaceData_ThrowInvalidPipeException(env, "bounds changed");
        }
        return (HDC) NULL;
    }

    ThreadGraphicsInfo *info = GetThreadGraphicsInfo(env, wsdo);
    GDIWinSD_InitDC(env, wsdo, info, type, patrop, clip, comp, color);
    return info->hDC;
}

JNIEXPORT void JNICALL
GDIWinSD_InitDC(JNIEnv *env, GDIWinSDOps *wsdo, ThreadGraphicsInfo *info,
               jint type, jint *patrop,
               jobject clip, jobject comp, jint color)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_InitDC");

    // init clip
    if (clip == NULL) {
        if (info->type & CLIP) {
            ::SelectClipRgn(info->hDC, (HRGN) NULL);
            info->type ^= CLIP;
        }
        if (info->clip != NULL) {
            env->DeleteWeakGlobalRef(info->clip);
            info->clip = NULL;
        }
    } else if (!env->IsSameObject(clip, info->clip)) {
        SurfaceDataBounds span;
        RegionData clipInfo;
        if (Region_GetInfo(env, clip, &clipInfo)) {
            // return; // REMIND: What to do here?
        }

        if (Region_IsEmpty(&clipInfo)) {
            HRGN hrgn = ::CreateRectRgn(0, 0, 0, 0);
            ::SelectClipRgn(info->hDC, hrgn);
            ::DeleteObject(hrgn);
            info->type |= CLIP;
        } else if (Region_IsRectangular(&clipInfo)) {
            if (clipInfo.bounds.x1 <= info->bounds.left &&
                clipInfo.bounds.y1 <= info->bounds.top &&
                clipInfo.bounds.x2 >= info->bounds.right &&
                clipInfo.bounds.y2 >= info->bounds.bottom)
            {
                if (info->type & CLIP) {
                    ::SelectClipRgn(info->hDC, (HRGN) NULL);
                    info->type ^= CLIP;
                }
            } else {
                // Make the window-relative rect a client-relative
                // one for Windows
                HRGN hrgn =
                    ::CreateRectRgn(clipInfo.bounds.x1 - wsdo->insets.left,
                                    clipInfo.bounds.y1 - wsdo->insets.top,
                                    clipInfo.bounds.x2 - wsdo->insets.left,
                                    clipInfo.bounds.y2 - wsdo->insets.top);
                ::SelectClipRgn(info->hDC, hrgn);
                ::DeleteObject(hrgn);
                info->type |= CLIP;
            }
        } else {
            int leftInset = wsdo->insets.left;
            int topInset = wsdo->insets.top;
            Region_StartIteration(env, &clipInfo);
            jint numrects = Region_CountIterationRects(&clipInfo);
            DWORD nCount = sizeof(RGNDATAHEADER) + numrects * sizeof(RECT);
            RGNDATA *lpRgnData = (RGNDATA *) safe_Malloc(nCount);
            lpRgnData->rdh.dwSize = sizeof(RGNDATAHEADER);
            lpRgnData->rdh.iType = RDH_RECTANGLES;
            lpRgnData->rdh.nCount = numrects;
            lpRgnData->rdh.nRgnSize = 0;
            lpRgnData->rdh.rcBound.left = clipInfo.bounds.x1 - leftInset;
            lpRgnData->rdh.rcBound.top = clipInfo.bounds.y1 - topInset;
            lpRgnData->rdh.rcBound.right = clipInfo.bounds.x2 - leftInset;
            lpRgnData->rdh.rcBound.bottom = clipInfo.bounds.y2 - topInset;
            RECT *pRect = (RECT *) &(((RGNDATA *)lpRgnData)->Buffer);
            while (Region_NextIteration(&clipInfo, &span)) {
                pRect->left = span.x1 - leftInset;
                pRect->top = span.y1 - topInset;
                pRect->right = span.x2 - leftInset;
                pRect->bottom = span.y2 - topInset;
                pRect++;
            }
            Region_EndIteration(env, &clipInfo);
            HRGN hrgn = ::ExtCreateRegion(NULL, nCount, lpRgnData);
            free(lpRgnData);
            ::SelectClipRgn(info->hDC, hrgn);
            ::DeleteObject(hrgn);
            info->type |= CLIP;
        }
        if (info->clip != NULL) {
            env->DeleteWeakGlobalRef(info->clip);
        }
        info->clip = env->NewWeakGlobalRef(clip);
    }

    // init composite
    if ((comp == NULL) || !env->IsInstanceOf(comp, xorCompClass)) {
        if (info->comp != NULL) {
            env->DeleteWeakGlobalRef(info->comp);
            info->comp = NULL;
            info->patrop = PATCOPY;
            ::SetROP2(info->hDC, R2_COPYPEN);
        }
    } else {
        if (!env->IsSameObject(comp, info->comp)) {
            info->xorcolor = GrPrim_CompGetXorColor(env, comp);
            if (info->comp != NULL) {
                env->DeleteWeakGlobalRef(info->comp);
            }
            info->comp = env->NewWeakGlobalRef(comp);
            info->patrop = PATINVERT;
            ::SetROP2(info->hDC, R2_XORPEN);
        }
        color ^= info->xorcolor;
    }

    if (patrop != NULL) {
        *patrop = info->patrop;
    }

    // init brush and pen
    if (type & BRUSH) {
        if (info->brushclr != color || (info->brush == NULL)) {
            if (info->type & BRUSH) {
                ::SelectObject(info->hDC, nullbrush);
                info->type ^= BRUSH;
            }
            if (info->brush != NULL) {
                info->brush->Release();
            }
            info->brush = AwtBrush::Get(CheckGrayColor(wsdo, color));
            info->brushclr = color;
        }
        if ((info->type & BRUSH) == 0) {
            ::SelectObject(info->hDC, info->brush->GetHandle());
            info->type ^= BRUSH;
        }
    } else if (type & NOBRUSH) {
        if (info->type & BRUSH) {
            ::SelectObject(info->hDC, nullbrush);
            info->type ^= BRUSH;
        }
    }
    if (type & PEN) {
        if (info->penclr != color || (info->pen == NULL)) {
            if (info->type & PEN) {
                ::SelectObject(info->hDC, nullpen);
                info->type ^= PEN;
            }
            if (info->pen != NULL) {
                info->pen->Release();
            }
            info->pen = AwtPen::Get(CheckGrayColor(wsdo, color));
            info->penclr = color;
        }
        if ((info->type & PEN) == 0) {
            ::SelectObject(info->hDC, info->pen->GetHandle());
            info->type ^= PEN;
        }
    } else if (type & NOPEN) {
        if (info->type & PEN) {
            ::SelectObject(info->hDC, nullpen);
            info->type ^= PEN;
        }
    }
}

static void GDIWinSD_ReleaseDC(JNIEnv *env, GDIWinSDOps *wsdo, HDC hDC)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_ReleaseDC");
    // Don't actually do anything here: every thread holds its own
    // wsdo-specific DC until the thread goes away or the wsdo
    // is disposed.
}


static void GDIWinSD_InvalidateSD(JNIEnv *env, GDIWinSDOps *wsdo)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_InvalidateSD");
    J2dTraceLn2(J2D_TRACE_VERBOSE, "  wsdo=0x%x wsdo->window=0x%x",
                wsdo, wsdo->window);

    wsdo->invalid = JNI_TRUE;
}



/*
 * Method:    GDIWinSD_Dispose
 */
static void
GDIWinSD_Dispose(JNIEnv *env, SurfaceDataOps *ops)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIWinSD_Dispose");
    // ops is assumed non-null as it is checked in SurfaceData_DisposeOps
    GDIWinSDOps *wsdo = (GDIWinSDOps*)ops;
    if (wsdo->bitmap) {
        // delete old objects
        J2dTraceLn(J2D_TRACE_VERBOSE, "  disposing the GDI bitmap");
        if (wsdo->bmdc) {   // should not be null
            ::SelectObject(wsdo->bmdc, wsdo->oldmap);
            ::DeleteDC(wsdo->bmdc);
            wsdo->bmdc = 0;
        }
        ::DeleteObject(wsdo->bitmap);
        wsdo->bitmap = 0;
    }
    env->DeleteWeakGlobalRef(wsdo->peer);
    if (wsdo->device != NULL) {
        wsdo->device->Release();
        wsdo->device = NULL;
    }
    delete wsdo->surfaceLock;
}


/*
 * Class:     sun_java2d_windows_GDIWindowSurfaceData
 * Method:    invalidateSD
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_sun_java2d_windows_GDIWindowSurfaceData_invalidateSD(JNIEnv *env, jobject wsd)
{
    J2dTraceLn(J2D_TRACE_INFO, "GDIWindowSurfaceData_invalidateSD");
    GDIWinSDOps *wsdo = GDIWindowSurfaceData_GetOpsNoSetup(env, wsd);
    if (wsdo != NULL) {
        wsdo->InvalidateSD(env, wsdo);
    }
}