/******************************Module*Header*******************************\ * Module Name: wndobj.cxx * * WNDOBJ support routines. * * Created: 22-Sep-1993 17:42:20 * Author: Wendy Wu [wendywu] * Hock San Lee [hockl] * * Copyright (c) 1993-1999 Microsoft Corporation \**************************************************************************/ #include "precomp.hxx" #if DBG long glDebugLevel = 0; #define DBGINFO(str) if (glDebugLevel >= 2) DbgPrint("GLSRVL: " str) #define DBGENTRY(str) if (glDebugLevel >= 8) DbgPrint("GLSRVL: " str) #else #define DBGINFO(str) #define DBGENTRY(str) #endif // Global tracking object (TRACKOBJ) pointer. // If this is non-null, we are tracking some WNDOBJs in the system. PTRACKOBJ gpto = (PTRACKOBJ)NULL; // Global that indicates whether to notify driver with the new WNDOBJ // states following a WNDOBJ creation. User has not changed the window // states but this is required to initialize the driver. The update is // done in the parent gdi functions (e.g. SetPixelFormat) that allow // the DDI to create a WNDOBJ. BOOL gbWndobjUpdate; // The following is a global uniqueness that gets bumped up anytime USER // changes anyone's VisRgn: ULONG giVisRgnUniqueness = 0; // Maximum region rectangle RECTL grclMax = { MIN_REGION_COORD, MIN_REGION_COORD, MAX_REGION_COORD, MAX_REGION_COORD }; // Here is a brief description of the semaphore usage. // // There are 3 semaphores that this module uses/references. // // 1. User critical section. // The user critical section ensures that no window moves when a new // update occurs. It is only relevent to the display DCs to ensure a // consistent window client regions state. For example, GreSetClientRgn // assumes that no window can move until GreClientRgnUpdated is called. // // 2. Display devlock and DC/surface locks. // The display devlock must be entered when a WNDOBJ is being used or // updated. This prevents a WNDOBJ from being modified when it is // being used in the DDI. The display devlock applies to the display // DCs only. For memory and printer DCs, the surface is locked when // a WNDOBJ is used. This is the current GDI design to prevent a // different thread from deleting a surface while it is being used. // Note that this precludes any multi-thread access to the printer or // memory DCs. // // 3. Window object semaphore. // The window object semaphore is used to protect access to the window // object data structures. Note that a semaphore is used instead of // a mutex here to allow for process cleanup of the semaphore. The // process cleanup is done in the user process cleanup code. // // The above 3 semaphores must be entered in the given order. Otherwise, // a deadlock may occur. /******************************Member*Function*****************************\ * VOID TRACKOBJ::vUpdateDrvDelta * * Update driver function for delta regions. fl must have * WOC_RGN_CLIENT_DELTA or WOC_RGN_SURFACE_DELTA set. * Note that if the delta is empty, we do not need to call the driver. * The compiler does not allow this function to be inline because or forward * reference. * * History: * Thu Jan 13 09:55:23 1994 -by- Hock San Lee [hockl] * Wrote it. \**************************************************************************/ VOID TRACKOBJ::vUpdateDrvDelta(EWNDOBJ *pwo, FLONG fl) { ASSERTGDI(fl & (WOC_RGN_CLIENT_DELTA|WOC_RGN_SURFACE_DELTA), "TRACKOBJ::vUpdateDrvDelta, Bad flags\n"); if (!pwo->erclExclude().bEmpty()) (*pfn)((WNDOBJ *)pwo, fl); } /******************************Public*Function*****************************\ * EngCreateWnd * * Create a WNDOBJ from a HWND. This function should only be called * when the calling thread has the usercrit and devlock in that order. * GDI will ensure that the thread acquires both locks before calling the * DDIs that allow EngCreateWnd to be called. The driver should only call * this function from those DDI entry points. Currently, GreSetPixelFormat * acquires both locks before calling DrvSetPixelFormat; GreExtEscape for * WNDOBJ_SETUP escape also acquires both locks before calling DrvEscape. * * This function allows tracking multiple surfaces (screen, bitmaps and * printers). For each display surface being tracked, it further allows * multiple TRACKOBJs to be created for each driver function. The TRACKOBJs * on a device surface are identified by unique pfn function pointers. * This allows a live video driver and an OpenGL driver to track windows * independently of each other. The only restriction is that a window on * a surface cannot be tracked by more than one TRACKOBJs on that surface. * * A WNDOBJ has an associated pixel format. Once a WNDOBJ is created with * a given pixel format, it cannot be set to a different pixel format. If * there is no pixel format associated with a WNDOBJ, e.g. live video, * it should be set to zero. * * The WNDOBJ created in this function does not have the current states * until the first driver update function is called. However, the driver * may immediately associate its own data with the WNDOBJ by calling the * WNDOBJ_vSetConsumer function. * * Once a WNDOBJ is created, it cannot be deleted by the driver. Gdi will * will notifiy the driver of the deletion when the window goes away or * when the associated surface is deleted. * * The given hwnd identifies the user window to be tracked. It must be 0 * if the surface is a printer or memory bitmap. * * Returns the new WNDOBJ pointer if a WNDOBJ is created; 0 if an error * occurs; -1 if hwnd is already being tracked by this driver function. * * History: * Thu Jan 13 09:55:23 1994 -by- Hock San Lee [hockl] * Rewrote it. * 27-Sep-1993 -by- Wendy Wu [wendywu] * Wrote it. \**************************************************************************/ // This is a private clean up class for this function. class WO_CLEANUP { private: BOOL bKeep; // TRUE if resouces should not be freed PTRACKOBJ pto; PEWNDOBJ pwoSurf; PEWNDOBJ pwoClient; PREGION prgnSurf; PREGION prgnClient; HSEMAPHORE hsemClient; public: WO_CLEANUP() { bKeep = FALSE; pto = (PTRACKOBJ)NULL; pwoSurf = (PEWNDOBJ)NULL; pwoClient = (PEWNDOBJ)NULL; prgnSurf = (PREGION)NULL; prgnClient = (PREGION)NULL; hsemClient = NULL; } VOID vSetTrackobj(PTRACKOBJ pto1) { pto = pto1; } VOID vSetSurfWndobj(PEWNDOBJ pwo) { pwoSurf = pwo; } VOID vSetClientWndobj(PEWNDOBJ pwo) { pwoClient = pwo; } VOID vSetSurfRegion(RGNMEMOBJ &rmo) { prgnSurf = rmo.prgnGet();} VOID vSetClientRegion(RGNMEMOBJ &rmo) { prgnClient = rmo.prgnGet();} VOID vSetClientSem(HSEMAPHORE hsem) { hsemClient = hsem; } PTRACKOBJ ptoGet() { return(pto); } VOID vKeepAll() { bKeep = TRUE; } ~WO_CLEANUP() { if (bKeep) return; DBGINFO("EngCreateWnd: no WNDOBJ created\n"); if (pto) { pto->ident = 0; VFREEMEM(pto); } if (pwoSurf) { pwoSurf->ident = 0; VFREEMEM(pwoSurf); } if (pwoClient) { pwoClient->ident = 0; VFREEMEM(pwoClient); } if (prgnSurf) prgnSurf->vDeleteREGION(); if (prgnClient) prgnClient->vDeleteREGION(); if (hsemClient) GreDeleteSemaphore(hsemClient); } }; WNDOBJ * APIENTRY EngCreateWnd ( SURFOBJ *pso, HWND hwnd, WNDOBJCHANGEPROC pfn, FLONG fl, int iPixelFormat ) { WO_CLEANUP cleanup; // prepare for clean up PEWNDOBJ pwoClient; PEWNDOBJ pwoSurf; PEWNDOBJ pwo; PTRACKOBJ pto; PEWNDOBJ pwoGenericSibling = NULL; DBGENTRY("EngCreateWnd\n"); PSURFACE pSurf = SURFOBJ_TO_SURFACE(pso); // Assert that we are in user critical section and also hold the devlock. // This ensures that no one is updating the hwnd. if (!UserIsUserCritSecIn()) { RIP("Driver may call EngCreateWnd only from WNDOBJ_SETUP escape\n" "(or from OpenGL MCD or ICD escapes)"); return(NULL); } CHECKUSERCRITIN; if (hwnd) { CHECKDEVLOCKIN2(pSurf); } // Validate flags. #if ((WO_VALID_FLAGS & WO_INTERNAL_VALID_FLAGS) != 0) #error "bad WO_INTERNAL_VALID_FLAGS" #endif if ((fl & ~WO_VALID_FLAGS) != 0) return((WNDOBJ *)0); // If this is the first time we need to track window object, create the // semaphore for synchronization. We use a semaphore instead of mutex // so that we can perform process cleanup of the semaphore. // Enter the semphore for window object. SEMOBJ so(ghsemWndobj); // If the window is already being tracked by the same TRACKOBJ and the // pixel format is the same, return -1. If the window is being tracked // by a different TRACKOBJ, return 0. There may be multiple TRACKOBJs // on the same device surface but a window can be tracked by only one // TRACKOBJ. In addition, pixel format in a window cannot be modified // once it is set. for (pto = gpto; pto; pto = pto->ptoNext) { for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext) { if (pwo->hwnd == hwnd) { KdPrint(("Failing EngCreateWnd -- hwnd already has a WNDOBJ\n")); if (pto->pfn == pfn && pwo->ipfd == iPixelFormat) return((WNDOBJ *)-1); // return -1 else return((WNDOBJ *)0); // tracked by a diff TRACKOBJ } } } // If this is the first time we track a device surface for this driver // function, we have to allocate space for the TRACKOBJ. We determine // if this is a new TRACKOBJ by comparing the pfn with those of the // existing ones. This allows the live video, installable opengl, and // generic opengl windows to be tracked separately. for (pto = gpto; pto; pto = pto->ptoNext) if (pto->pSurface == pSurf && pto->pfn == pfn) break; if (!pto) { // Allocate a new TRACKOBJ. if (!(pto = (PTRACKOBJ) PALLOCMEM(sizeof(TRACKOBJ), 'dnwG'))) return((WNDOBJ *)0); cleanup.vSetTrackobj(pto); pto->ident = TRACKOBJ_IDENTIFIER; // pto->ptoNext pto->pwoSurf = (PEWNDOBJ)NULL; pto->pwo = (PEWNDOBJ)NULL; pto->pSurface = pSurf; pto->pfn = pfn; pto->fl = fl; pto->erclSurf.left = 0; pto->erclSurf.top = 0; pto->erclSurf.right = pSurf->sizl().cx; pto->erclSurf.bottom = pSurf->sizl().cy; // Create a surface WNDOBJ for the TRACKOBJ if it requests WO_RGN_SURFACE or // WO_RGN_SURFACE_DELTA. if (fl & (WO_RGN_SURFACE|WO_RGN_SURFACE_DELTA)) { if (!(pwoSurf = (PEWNDOBJ) PALLOCMEM(sizeof(EWNDOBJ), 'dnwG'))) return((WNDOBJ *)0); cleanup.vSetSurfWndobj(pwoSurf); // Create a surface client region that is the entire surface. RGNMEMOBJ rmoSurf((BOOL)FALSE); if (!rmoSurf.bValid()) return((WNDOBJ *)0); cleanup.vSetSurfRegion(rmoSurf); rmoSurf.vSet((RECTL *)&pto->erclSurf); // Initialize the surface WNDOBJ. pwoSurf->pto = pto; // pto used by vSetClip rmoSurf.prgnGet()->vStamp(); // init iUniq pwoSurf->vSetClip(rmoSurf.prgnGet(), pto->erclSurf); pwoSurf->pvConsumer = 0; pwoSurf->psoOwner = pSurf->pSurfobj(); pwoSurf->ident = EWNDOBJ_IDENTIFIER; pwoSurf->pwoNext = (PEWNDOBJ)NULL; // no next pointer pwoSurf->hwnd = 0; // no hwnd pwoSurf->fl = fl | WO_SURFACE; pwoSurf->ipfd = 0; // no pixel format // Add WNDOBJ to the TRACKOBJ. pto->pwoSurf = pwoSurf; } } // The tracking flags must be consistent. if ((pto->fl & ~WO_INTERNAL_VALID_FLAGS) != fl) return((WNDOBJ *)0); // Allocate a new client WNDOBJ. if (!(pwoClient = (PEWNDOBJ) PALLOCMEM(sizeof(EWNDOBJ), 'dnwG'))) return((WNDOBJ *)0); cleanup.vSetClientWndobj(pwoClient); // Create an empty window client region. The client region is still being // created. The driver window region update will be done in the parent gdi // function. ERECTL erclClient(0,0,0,0); RGNMEMOBJ rmoClient((BOOL)FALSE); if (!rmoClient.bValid()) return((WNDOBJ *)0); cleanup.vSetClientRegion(rmoClient); rmoClient.vSet((RECTL *)&erclClient); // Initialize the per-WNDOBJ semaphore once per window. { pwoClient->hsem = GreCreateSemaphore(); if (pwoClient->hsem == NULL) { return NULL; } cleanup.vSetClientSem(pwoClient->hsem); fl |= WO_HSEM_OWNER; } // Initialize the WNDOBJ. pwoClient->pto = pto; // pto used by vSetClip rmoClient.prgnGet()->vStamp(); // init iUniq pwoClient->vSetClip(rmoClient.prgnGet(), erclClient); pwoClient->pvConsumer = 0; // to be set by the driver pwoClient->psoOwner = pSurf->pSurfobj(); pwoClient->ident = EWNDOBJ_IDENTIFIER; pwoClient->hwnd = hwnd; pwoClient->fl = fl; pwoClient->ipfd = iPixelFormat; // Add WNDOBJ to TRACKOBJ. pwoClient->pwoNext = pto->pwo; pto->pwo = pwoClient; ASSERTGDI(offsetof(EWNDOBJ, pvConsumer) == offsetof(WNDOBJ, pvConsumer), "EngCreateWnd: rclClient wrong offset\n"); // Add TRACKOBJ to global linked list. if (cleanup.ptoGet()) { pto->ptoNext = gpto; gpto = pto; } // If hwnd is given, attach the WNDOBJ to the window in user. // Otherwise, it is a printer surface or memory bitmap. Attach it to // the surface. if (hwnd) { UserAssociateHwnd(hwnd, (PVOID) pwoClient); } else { // Only one WNDOBJ per memory bitmap or printer surface. ASSERTGDI(!pSurf->pwo(), "EngCreateWnd: multiple WNDOBJs unexpected in memory DCs\n"); pSurf->pwo(pwoClient); } // Inform the parent gdi function that it needs to update the new WNDOBJ // in the driver. pto->fl |= WO_NEW_WNDOBJ; pwoClient->fl |= WO_NEW_WNDOBJ; gbWndobjUpdate = TRUE; // Don't free the PDEV until the WNDOBJ is destroyed. PDEVOBJ po(pSurf->hdev()); po.vReferencePdev(); // Everything is golden. Keep the created objects and return the new WNDOBJ. cleanup.vKeepAll(); return((WNDOBJ *)pwoClient); } /******************************Public*Function*****************************\ * GreDeleteWnd * * This function is called when the window that is being tracked is deleted * in user, or when the device surface (printer or memory bitmap) that * is begin tracked is deleted. It deletes the WNDOBJ and notifies the * driver that the WNDOBJ is going away. * * This function does not update the driver with the new client regions * following the WNDOBJ deletion. It assumes that if the deletion is a * window, user will update or has updated the client regions; and if the * deleteion is a printer or memory bitmap, the TRACKOBJ is going away * and therefore no need to notify driver. * * If the deletion is a window, the calling thread must have the usercrit. * * History: * Thu Jan 13 09:55:23 1994 -by- Hock San Lee [hockl] * Wrote it. \**************************************************************************/ VOID APIENTRY GreDeleteWnd(PVOID _pwoDelete) { PEWNDOBJ pwoDelete = (PEWNDOBJ)_pwoDelete; PEWNDOBJ pwo; PTRACKOBJ pto; DBGENTRY("GreDeleteWnd\n"); // Validate pwoDelete. if (!pwoDelete->bValid()) { ASSERTGDI(FALSE, "GreDeleteWnd: Invalid pwoDelete\n"); return; } // If hwnd is non 0, the user calling thread must hold the usercrit. // This ensures that no one is updating the hwnd. if (pwoDelete->hwnd) { CHECKUSERCRITIN; CHECKDEVLOCKIN2(pwoDelete->pto->pSurface); } pto = pwoDelete->pto; // Acquire the device lock. Note that this may be different from the // device lock that USER is holding if the PDEV is marked as 'deleted': PDEVOBJ po(pto->pSurface->hdev()); { DEVLOCKOBJ dlo(po); // Enter the semaphore for window object. ASSERTGDI(ghsemWndobj, "GreDeleteWnd: bad ghsemWndobj\n"); SEMOBJ so(ghsemWndobj); // Notify driver that the WNDOBJ is going away. // Hold the WNDOBJ stable while doing so by grabbing the per-WNDOBJ semaphore. { SEMOBJ soClient(pwoDelete->hsem); pto->vUpdateDrv(pwoDelete, WOC_DELETE); } // Unlink pwoDelete from chain. if (pto->pwo == pwoDelete) pto->pwo = pwoDelete->pwoNext; else for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext) { if (pwo->pwoNext == pwoDelete) { pwo->pwoNext = pwoDelete->pwoNext; break; } } // Free pwoDelete. pwoDelete->bDelete(); // delete RGNOBJ pwoDelete->ident = 0; VFREEMEM(pwoDelete); // free memory // Delete the tracking object if there are no more windows to track. if (pto->pwo == (PEWNDOBJ)NULL) { // Unlink pto from chain. if (pto == gpto) gpto = pto->ptoNext; else for (PTRACKOBJ ptoTmp = gpto; ptoTmp; ptoTmp = ptoTmp->ptoNext) { if (ptoTmp->ptoNext == pto) { ptoTmp->ptoNext = pto->ptoNext; break; } } // Delete the pwoSurf if it exists. if (pto->pwoSurf) { ASSERTGDI(pto->fl & (WO_RGN_SURFACE|WO_RGN_SURFACE_DELTA), "GreDeleteWnd: WO_RGN_SURFACE or WO_RGN_SURFACE_DELTA not set\n"); pto->pwoSurf->bDelete(); // delete RGNOBJ pto->pwoSurf->ident = 0; VFREEMEM(pto->pwoSurf); // free memory } pto->ident = 0; VFREEMEM(pto); } // Inform the sprite code that a WNDOBJ has been deleted. vSpWndobjChange(po.hdev(), NULL); } // Remove the reference to the PDEV. po.vUnreferencePdev(); } /******************************Public*Function*****************************\ * EngDeleteWnd * * Driver-callable entry point to delete a WNDOBJ. * \**************************************************************************/ VOID EngDeleteWnd(WNDOBJ* _pwoDelete) { EWNDOBJ* pwoDelete = (EWNDOBJ*)_pwoDelete; if (!UserIsUserCritSecIn()) { RIP("Driver may call EngDeleteWnd only from WNDOBJ_SETUP escape\n" "(or from OpenGL MCD or ICD escapes)"); } else { // Tell USER to disassociate this WNDOBJ from its window: if (pwoDelete->hwnd) { UserAssociateHwnd(pwoDelete->hwnd, NULL); } GreDeleteWnd(pwoDelete); } } /******************************Public*Routine******************************\ * VOID vChangeWndObjs * * Transfers ownership of WNDOBJs between PDEVs. * * History: * 8-Feb-1996 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/ VOID vChangeWndObjs( SURFACE* pSurfaceOld, HDEV hdevOld, SURFACE* pSurfaceNew, HDEV hdevNew) { TRACKOBJ* pto; EWNDOBJ* pwo; EWNDOBJ* pwoNext; CHECKUSERCRITIN; SEMOBJ so(ghsemWndobj); // Note that we shouldn't use pSurfaceOld->hdev() or pSurfaceNew->hdev() // becuase they haven't been updated yet: PDEVOBJ poOld(hdevOld); PDEVOBJ poNew(hdevNew); // Changing drivers. Delete all WNDOBJs belonging to the old // surface: for (pto = gpto; pto != NULL; pto = pto->ptoNext) { if (pto->pSurface == pSurfaceOld) { // For every WNDOBJ belonging to this TRACKOBJ, transfer // ownership to the new PDEV: for (pwo = pto->pwo; pwo != NULL; pwo = pwo->pwoNext) { ASSERTGDI(pwo->psoOwner == pSurfaceOld->pSurfobj(), "Old psoOwner mismatch"); poNew.vReferencePdev(); poOld.vUnreferencePdev(); } } else if (pto->pSurface == pSurfaceNew) { for (pwo = pto->pwo; pwo != NULL; pwo = pwo->pwoNext) { ASSERTGDI(pwo->psoOwner == pSurfaceNew->pSurfobj(), "New psoOwner mismatch"); poOld.vReferencePdev(); poNew.vUnreferencePdev(); } } } } /******************************Public*Routine******************************\ * VOID vTransferWndObjs * * Transfers ownership of WNDOBJs from PDEV to other PDEV. * * History: * 2-16-1999 -by- Hideyuki Nagase [hideyukn] * Wrote it. \**************************************************************************/ VOID vTransferWndObjs( SURFACE* pSurface, HDEV hdevOld, HDEV hdevNew) { TRACKOBJ* pto; EWNDOBJ* pwo; EWNDOBJ* pwoNext; CHECKUSERCRITIN; SEMOBJ so(ghsemWndobj); PDEVOBJ poOld(hdevOld); PDEVOBJ poNew(hdevNew); for (pto = gpto; pto != NULL; pto = pto->ptoNext) { if (pto->pSurface == pSurface) { for (pwo = pto->pwo; pwo != NULL; pwo = pwo->pwoNext) { ASSERTGDI(pwo->psoOwner == pSurface->pSurfobj(), "New psoOwner mismatch"); KdPrint(("Transfer wndobj %x - hdevNew %x hdevOld %x \n", pwo,hdevNew,hdevOld)); poNew.vReferencePdev(); poOld.vUnreferencePdev(); } } } } /******************************Public*Function*****************************\ * vForceClientRgnUpdate * * This function is called by gdi to force an update of the new WNDOBJ * that is just created. * * This function should only be called when the calling thread has the * usercrit and devlock in that order. Currently, it is called from * GreSetPixelFormat and GreExtEscape for WNDOBJ_SETUP escape after * they detected that the driver has created a new WNDOBJ. * * History: * Thu Jan 13 09:55:23 1994 -by- Hock San Lee [hockl] * Wrote it. \**************************************************************************/ VOID vForceClientRgnUpdate() { PTRACKOBJ pto; PEWNDOBJ pwo = (PEWNDOBJ)NULL; DBGENTRY("vForceClientRgnUpdate\n"); // Assert that we are in user critical section and also hold the devlock. // This ensures that no one is updating the hwnd. CHECKUSERCRITIN; // Update the new WNDOBJ that was just created. // User has not changed the client regions because we are still in the // user critical section. We are updating the client regions ourselves. { // Enter the semphore for window object. SEMOBJ so(ghsemWndobj); // Find the newly created WNDOBJ. for (pto = gpto; pto; pto = pto->ptoNext) { if (!(pto->fl & WO_NEW_WNDOBJ)) continue; pto->fl &= ~WO_NEW_WNDOBJ; pto->fl |= WO_NOTIFIED; for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext) { if (!(pwo->fl & WO_NEW_WNDOBJ)) continue; pwo->fl &= ~WO_NEW_WNDOBJ; pwo->fl |= WO_NOTIFIED; break; // found it } break; // found it } if (!pwo) { ASSERTGDI(FALSE, "vForceClientRgnUpdate: no new WNDOBJ found\n"); return; } // We need to ensure that the caller holds the devlock before calling this // function. Otherwise, some other threads may be drawing into the wrong // client region. We do the check here because we don't have any pSurf // information earlier. if (pwo->hwnd) { CHECKDEVLOCKIN2(pto->pSurface); } // If hwnd exists, get the client region from user. HRGN hrgnClient; ERECTL erclClient; if (pwo->hwnd) { hrgnClient = UserGetClientRgn(pwo->hwnd, (LPRECT)&erclClient, pwo->fl & WO_RGN_WINDOW); } else { // If hwnd does not exist, this is a memory bitmap or printer surface. // The client region is the whole surface. erclClient = pto->erclSurf; hrgnClient = GreCreateRectRgnIndirect((LPRECT)&erclClient); } if (!hrgnClient) { ASSERTGDI(FALSE, "vForceClientRgnUpdate: hwnd has no rgn\n"); return; } // Update client region in the WNDOBJ. GreSetRegionOwner(hrgnClient, OBJECT_OWNER_PUBLIC); RGNOBJAPI roClient(hrgnClient,FALSE); ASSERTGDI(roClient.bValid(), "vForceClientRgnUpdate: invalid hrgnClient\n"); #ifdef OPENGL_MM // Under Multi-mon we need to adjust the client region and // client rectangle by the offset of the surface. // Additionally we need to clip the client region to the // surface of the TRACKOBJ. if (!(pwo->fl & WO_RGN_DESKTOP_COORD)) { PDEVOBJ pdo(pwo->pto->pSurface->hdev()); if (pdo.bValid()) { if (pdo.bPrimary(pwo->pto->pSurface)) { POINTL ptlOrigin; ptlOrigin.x = -pdo.pptlOrigin()->x; ptlOrigin.y = -pdo.pptlOrigin()->y; if ((ptlOrigin.x != 0) || (ptlOrigin.y != 0)) { // offset the region and window rect if necessary roClient.bOffset(&ptlOrigin); erclClient += ptlOrigin; /* this offsets by ptl */ } } } RGNMEMOBJTMP rmoTmp; RGNMEMOBJTMP rmoRcl; if (rmoTmp.bValid() && rmoRcl.bValid()) { // this clips the client region to the pto surface rmoRcl.vSet((RECTL *) &pto->erclSurf); rmoTmp.bCopy(roClient); roClient.iCombine(rmoTmp, rmoRcl, RGN_AND); if (rmoTmp.iCombine(roClient,rmoRcl,RGN_AND) != ERROR) { roClient.bSwap(&rmoTmp); } } } #endif // OPENGL_MM // We're going to modify the WNDOBJ now. Grab the per-WNDOBJ semaphore to // keep it stable. Don't release until after the driver is called. SEMOBJ soClient(pwo->hsem); roClient.bSwap(pwo); pwo->prgn->vStamp(); // init iUniq pwo->vSetClip(pwo->prgn, erclClient); roClient.bDeleteRGNOBJAPI(); // delete handle too // Call driver with the new WNDOBJ. if (pto->fl & WO_RGN_CLIENT_DELTA) pto->vUpdateDrvDelta(pwo, WOC_RGN_CLIENT_DELTA); if (pto->fl & WO_RGN_CLIENT) pto->vUpdateDrv(pwo, WOC_RGN_CLIENT); // Let the sprite code know that there's a new WNDOBJ, now completely // formed. vSpWndobjChange(pto->pSurface->hdev(), pwo); } // Update the remaining window client regions. GreClientRgnUpdated(GCR_WNDOBJEXISTS); } /******************************Member*Function*****************************\ * GreWindowInsteadOfClient * * Returns TRUE if the window area instead of the client area should be * used for the WNDOBJ regions. * \**************************************************************************/ BOOL GreWindowInsteadOfClient(PVOID _pwo) { PEWNDOBJ pwo = (PEWNDOBJ) _pwo; return(pwo->fl & WO_RGN_WINDOW); } /******************************Member*Function*****************************\ * GreClientRgnUpdated * * User calls this function after having updated all the vis/client * region changes and before releasing the devlock. We have to complete * the remaining client region update operation. * * Gdi calls vForceClientRgnUpdate and this function after a new WNDOBJ * is created to update the driver. * * This function should only be called when the calling thread has the * usercrit and devlock in that order. * * If the GCR_DELAYFINALUPDATE flag is specified, the caller must * subsequently call GreClientRgnDone. If the driver specified * the WO_DRAW_NOTIFY flag, the GCR_DELAYFINALUPDATE will suppress * the WOC_DRAWN notification until GreClientRgnDone is called (or * the next time GreClientRgnUpdated is called without * GCR_DELAYFINALUPDATE). * * History: * Thu Jan 13 09:55:23 1994 -by- Hock San Lee [hockl] * Rewrote it. * 11-Nov-1993 -by- Wendy Wu [wendywu] * Wrote it. \**************************************************************************/ VOID APIENTRY GreClientRgnUpdated(FLONG flUpdate) { PTRACKOBJ pto; PEWNDOBJ pwo; DBGENTRY("GreClientRgnUpdated\n"); // Since we are holding the DEVLOCK to the active display, we can // increment the VisRgn count here without doing an atomic increment. giVisRgnUniqueness++; // Go any further only if some WNDOBJs exist. if (!(flUpdate & GCR_WNDOBJEXISTS)) { return; } // Assert that we are in user critical section and also hold the devlock. // This ensures that no one is updating the hwnd. CHECKUSERCRITIN; // Enter the semphore for window object. SEMOBJ so(ghsemWndobj); // The surface client regions have changed. Complete the remaining // client region update. for (pto = gpto; pto; pto = pto->ptoNext) { if (!(pto->fl & WO_NOTIFIED)) continue; pto->fl &= ~WO_NOTIFIED; // We need to ensure that user holds the devlock before calling this function. // Otherwise, some other threads may be drawing into the wrong client region. // We do the check here because we don't have any pSurface information earlier. // // Also, we exclude the entire screen since we are going to touch windows all // over the place and end with calling driver with WOC_COMPLETE which // can have an effect anywhere on the screen. DEVEXCLUDEOBJ dxo; if (pto->pwo->hwnd) { RECTL rclSurf; HDEV hdev = pto->pSurface->hdev(); PDEVOBJ po(hdev); CHECKDEVLOCKIN2(pto->pSurface); ASSERTGDI(po.bValid(), "GreClientRgnUpdated: invalid pdevobj\n"); if (po.bValid() && !po.bDisabled()) { rclSurf.left = 0; rclSurf.top = 0; rclSurf.right = pto->pSurface->sizl().cx; rclSurf.bottom = pto->pSurface->sizl().cy; dxo.vExclude(hdev, &rclSurf, (ECLIPOBJ *) NULL); } } // Traverse the chain and call the driver with un-changed windows if // the WO_RGN_UPDATE_ALL and WO_RGN_CLIENT flags are set. if ((pto->fl & (WO_RGN_CLIENT|WO_RGN_UPDATE_ALL)) == (WO_RGN_CLIENT|WO_RGN_UPDATE_ALL)) { for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext) if (pwo->fl & WO_NOTIFIED) pwo->fl &= ~WO_NOTIFIED; else { // Make WNDOBJ stable by holding the per-WNDOBJ semaphore // while we call the driver. SEMOBJ soClient(pwo->hsem); pto->vUpdateDrv(pwo, WOC_RGN_CLIENT); } } // Update the surface WNDOBJ if requested. if (pto->fl & (WO_RGN_SURFACE|WO_RGN_SURFACE_DELTA)) { PEWNDOBJ pwoSurf = pto->pwoSurf; RGNMEMOBJTMP rmoTmp((BOOL)FALSE); RGNMEMOBJTMP rmoSurfNew((BOOL)FALSE); if (rmoTmp.bValid() && rmoSurfNew.bValid()) { // Construct the new surface region which is the entire surface minus // the combined client regions. rmoSurfNew.vSet(&pwoSurf->rclClient); for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext) { RGNOBJ ro(pwo->prgn); if (rmoTmp.iCombine(rmoSurfNew, ro, RGN_DIFF) != ERROR) rmoSurfNew.bSwap(&rmoTmp); } // If WO_RGN_SURFACE_DELTA is set, update the driver with the new surface delta. if (pto->fl & WO_RGN_SURFACE_DELTA) { RGNOBJ roSurf(pwoSurf->prgn); if (rmoTmp.iCombine(rmoSurfNew, roSurf, RGN_DIFF) != ERROR) { pwoSurf->bSwap(&rmoTmp); pwoSurf->prgn->vStamp(); // new iUniq pwoSurf->vSetClip(pwoSurf->prgn, *(ERECTL *)&pwoSurf->rclClient); pto->vUpdateDrvDelta(pwoSurf, WOC_RGN_SURFACE_DELTA); } } // Save the new surface region. // The surface region may be the same as previous one here. This code can be // optimized a little. pwoSurf->bSwap(&rmoSurfNew); pwoSurf->prgn->vStamp(); // new iUniq pwoSurf->vSetClip(pwoSurf->prgn, *(ERECTL *)&pwoSurf->rclClient); // Give the driver the new surface region. if (pto->fl & WO_RGN_SURFACE) pto->vUpdateDrv(pwoSurf, WOC_RGN_SURFACE); } } // if (pto->fl & (WO_RGN_SURFACE|WO_RGN_SURFACE_DELTA)) // Send down the WOC_CHANGED to signify notification complete. pto->vUpdateDrv((PEWNDOBJ)NULL, WOC_CHANGED); // Need WOC_DRAWN notification if requested. Notification is delayed // if GCR_DELAYFINALUPDATE is set. Delayed notification is signaled // by setting the WO_NEED_DRAW_NOTIFY flag in the trackobj and is // handled in GreClientRgnDone. if (pto->fl & WO_DRAW_NOTIFY) { if (flUpdate & GCR_DELAYFINALUPDATE) { pto->fl |= WO_NEED_DRAW_NOTIFY; } else { pto->vUpdateDrv((PEWNDOBJ)NULL, WOC_DRAWN); pto->fl &= ~WO_NEED_DRAW_NOTIFY; } } } // for (pto = gpto; pto; pto = pto->ptoNext) } /******************************Public*Routine******************************\ * GreClientRgnDone * * If USER calls GreClientRgnUpdated with the GCR_DELAYFINALUPDATE flag * set, it must subsequently call this function to complete the update. * * If the driver calls EngCreateWnd with the WO_NEED_DRAW_NOTIFY flag, * the GreClientRgnUpdated function will complete the notification with * a WOC_CHANGED followed by a WOC_DRAWN. However, if the * GCR_DELAYFINALUPDATE flag is specified when calling GreClientRgnUpdated, * the WOC_DRAWN message is suppressed until GreClientRgnDone is called. * * If the driver does not call EngCreateWnd with the WO_NEED_DRAW_NOTIFY * flag, GreClientRgnDone will only send the WOC_CHANGED notification and * this function will have no effect. * * This function should only be called when the calling thread has the * usercrit and devlock in that order. * * Note: Currently this function does not do anything if GCR_WNDOBJEXISTS * is not set (GreClientRgnUpdated will update the vis rgn uniqneness and * so must be called even if there are not WNDOBJs). Therefore, for now * it is acceptable for the caller to skip GreClientRgnDone if no WNDOBJs * exist. * * History: * 19-Dec-1997 -by- Gilman Wong [gilmanw] * Wrote it. \**************************************************************************/ VOID APIENTRY GreClientRgnDone(FLONG flUpdate) { PTRACKOBJ pto; PEWNDOBJ pwo; DBGENTRY("GreClientRgnDone\n"); // Go any further only if some WNDOBJs exist. if (!(flUpdate & GCR_WNDOBJEXISTS)) { return; } // Assert that we are in user critical section. // This ensures that no one is updating the hwnd. CHECKUSERCRITIN; // Enter the semphore for window object. SEMOBJ so(ghsemWndobj); // Check which tracking objects need WOC_DRAWN notification. for (pto = gpto; pto; pto = pto->ptoNext) { if (pto->pwo->hwnd) { CHECKDEVLOCKIN2(pto->pSurface); } if (pto->fl & WO_NEED_DRAW_NOTIFY) { pto->fl &= ~WO_NEED_DRAW_NOTIFY; pto->vUpdateDrv((PEWNDOBJ)NULL, WOC_DRAWN); } // Inform the sprite code of the change in the WNDOBJ. for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext) { vSpWndobjChange(pto->pSurface->hdev(), pwo); } } // for (pto = gpto; pto; pto = pto->ptoNext) } /******************************Public*Function*****************************\ * GreSetClientRgn * * User calls this function to update the client region in a WNDOBJ. * After all the regions have been updated, user must call GreClientRgnUpdated * to complete the update. * * User creates a new region to give to this function. This function must * delete the region before it returns! * * This function should only be called when the calling thread has the * usercrit and devlock in that order. * * History: * Thu Jan 13 09:55:23 1994 -by- Hock San Lee [hockl] * Wrote it. \**************************************************************************/ VOID GreSetClientRgn(PVOID _pwoClient, HRGN hrgnClient, LPRECT prcClient) { PEWNDOBJ pwoClient = (PEWNDOBJ)_pwoClient; DBGENTRY("GreSetClientRgn\n"); // Assert that we are in user critical section and also hold the devlock. // This ensures that no one is updating the hwnd. CHECKUSERCRITIN; if (pwoClient->hwnd) { CHECKDEVLOCKIN2(pwoClient->pto->pSurface); } // Validate hrgnClient. if (!hrgnClient) { ASSERTGDI(FALSE, "GreSetClientRgn: hrgnClient is NULL\n"); return; } // Validate pwoClient. if (!pwoClient->bValid()) { ASSERTGDI(FALSE, "GreSetClientRgn: Invalid pwoClient\n"); bDeleteRegion(hrgnClient); return; } // The WNDOBJ should only be modified once per update. A complete // update includes a call to GreClientRgnUpdated. #if DBG if ((pwoClient->fl & (WO_RGN_CLIENT|WO_RGN_UPDATE_ALL)) == (WO_RGN_CLIENT|WO_RGN_UPDATE_ALL)) if (pwoClient->fl & WO_NOTIFIED) DbgPrint("GreSetClientRgn: WNDOBJ updated more than once!\n"); #endif // DBG // Get new and old regions. GreSetRegionOwner(hrgnClient, OBJECT_OWNER_PUBLIC); RGNOBJAPI roClient(hrgnClient,FALSE); ASSERTGDI(roClient.bValid(), "GreSetClientRgn: invalid hrgnClient\n"); RGNOBJ roOld(pwoClient->prgn); ERECTL erclClient(prcClient->left, prcClient->top, prcClient->right, prcClient->bottom); #ifdef OPENGL_MM // Under Multi-mon we need to adjust the client region and // client rectangle by the offset of the surface. // Additionally we need to clip the client region to the // surface of the TRACKOBJ. if (!(pwoClient->fl & WO_RGN_DESKTOP_COORD)) { PDEVOBJ pdo(pwoClient->pto->pSurface->hdev()); if (pdo.bValid()) { if (pdo.bPrimary(pwoClient->pto->pSurface)) { POINTL ptlOrigin; ptlOrigin.x = -pdo.pptlOrigin()->x; ptlOrigin.y = -pdo.pptlOrigin()->y; if ((ptlOrigin.x != 0) || (ptlOrigin.y != 0)) { // offset the region and window rect if necessary roClient.bOffset(&ptlOrigin); erclClient += ptlOrigin; /* this offsets by ptl */ } } } RGNMEMOBJTMP rmoTmp; RGNMEMOBJTMP rmoRcl; if (rmoTmp.bValid() && rmoRcl.bValid()) { // this clips the client region to the pto surface rmoRcl.vSet((RECTL *) &pwoClient->pto->erclSurf); rmoTmp.bCopy(roClient); roClient.iCombine(rmoTmp,rmoRcl,RGN_AND); if (rmoTmp.iCombine(roClient,rmoRcl,RGN_AND) != ERROR) { roClient.bSwap(&rmoTmp); } } } #endif // OPENGL_MM // If the regions are equal, no need to notify driver here. if (roOld.bEqual(roClient) && ((ERECTL *)&pwoClient->rclClient)->bEqual(erclClient)) { roClient.bDeleteRGNOBJAPI(); // delete handle too return; } // Enter the semphore for window object. SEMOBJ so(ghsemWndobj); // Now the WNDOBJ is going to get updated. Hold the per-WNDOBJ semaphore // and keep it until we are done modifying and the driver update call has // been made. SEMOBJ soClient(pwoClient->hsem); // Give the driver the client region delta. // The delta is valid for this call only! if (pwoClient->fl & WO_RGN_CLIENT_DELTA) { RGNMEMOBJTMP rmoDiff((BOOL)FALSE); if (rmoDiff.bValid() && (rmoDiff.iCombine(roClient, roOld, RGN_DIFF) != ERROR)) { pwoClient->bSwap(&rmoDiff); pwoClient->prgn->vStamp(); // new iUniq pwoClient->vSetClip(pwoClient->prgn, erclClient); pwoClient->pto->vUpdateDrvDelta(pwoClient, WOC_RGN_CLIENT_DELTA); } } // Update the new client region in WNDOBJ. roClient.bSwap(pwoClient); pwoClient->prgn->vStamp(); // new iUniq pwoClient->vSetClip(pwoClient->prgn, erclClient); roClient.bDeleteRGNOBJAPI(); // delete handle too // Give the driver the new client region. if (pwoClient->fl & WO_RGN_CLIENT) { pwoClient->pto->vUpdateDrv(pwoClient, WOC_RGN_CLIENT); } // Mark that we have visited this WNDOBJ and TRACKOBJ. pwoClient->fl |= WO_NOTIFIED; pwoClient->pto->fl |= WO_NOTIFIED; return; } /******************************Member*Function*****************************\ * WNDOBJ_cEnumStart * * Start the window client region enumeration for the window object. * * This function can be called from the wndobjchangeproc that is passed to * EngCreateWnd. It can also be called from DDI function where a WNDOBJ * is given. * * This function should only be called when the calling thread has the * devlock to ensure that there is no client region change. * * In future, we may want to add the pvConsumer and WNDOBJ pointer to the * CLIPOBJ that is passed to the existing DDI. In this way, the WNDOBJ * is always available to the DDI instead of the selected few. * * History: * Thu Jan 13 09:55:23 1994 -by- Hock San Lee [hockl] * Rewrote it. * 11-Nov-1993 -by- Wendy Wu [wendywu] * Wrote it. \**************************************************************************/ extern "C" ULONG WNDOBJ_cEnumStart( WNDOBJ *pwo, ULONG iType, ULONG iDir, ULONG cLimit) { DBGENTRY("WNDOBJ_cEnumStart\n"); ASSERTGDI(((PEWNDOBJ)pwo)->bValid(), "WNDOBJ_cEnumStart: Invalid pwo\n"); // We need to ensure that the caller holds the devlock before calling this // function. Otherwise, some other threads may be drawing into the wrong // client region. if (((PEWNDOBJ)pwo)->hwnd) { CHECKDEVLOCKIN2(((PEWNDOBJ)pwo)->pto->pSurface); } return (*(XCLIPOBJ *)pwo).cEnumStart(TRUE, iType, iDir, cLimit); } /******************************Member*Function*****************************\ * WNDOBJ_bEnum * * Enumerate the client region object in the window object. * * This function can be called from the wndobjchangeproc that is passed to * EngCreateWnd. It can also be called from DDI function where a WNDOBJ * is given. * * This function should only be called when the calling thread has the * devlock to ensure that there is no client region change. * * In future, we may want to add the pvConsumer and WNDOBJ pointer to the * CLIPOBJ that is passed to the existing DDI. In this way, the WNDOBJ * is always available to the DDI instead of the selected few. * * History: * Thu Jan 13 09:55:23 1994 -by- Hock San Lee [hockl] * Rewrote it. * 11-Nov-1993 -by- Wendy Wu [wendywu] * Wrote it. \**************************************************************************/ extern "C" BOOL WNDOBJ_bEnum( WNDOBJ *pwo, ULONG cj, ULONG *pul) { DBGENTRY("WNDOBJ_bEnum\n"); ASSERTGDI(((PEWNDOBJ)pwo)->bValid(), "WNDOBJ_bEnum: Invalid pwo\n"); // We need to ensure that the caller holds the devlock before calling this // function. Otherwise, some other threads may be drawing into the wrong // client region. if (((PEWNDOBJ)pwo)->hwnd) { CHECKDEVLOCKIN2(((PEWNDOBJ)pwo)->pto->pSurface); } return (*(XCLIPOBJ *)pwo).bEnum(cj, (VOID *)pul); } /******************************Member*Function*****************************\ * WNDOBJ_vSetConsumer * * Set the driver pvConsumer value in the window object. It should be * used to modify the existing pvConsumer value. * * This function can be called from the wndobjchangeproc that is passed to * EngCreateWnd. It can also be called from DDI function where a WNDOBJ * is given. * * This function should only be called when the calling thread has the * devlock to ensure that there is no client region change. * * History: * Thu Jan 13 09:55:23 1994 -by- Hock San Lee [hockl] * Wrote it. \**************************************************************************/ extern "C" VOID WNDOBJ_vSetConsumer( WNDOBJ *_pwo, PVOID pvConsumer) { PEWNDOBJ pwo = (PEWNDOBJ)_pwo; DBGENTRY("WNDOBJ_vSetConsumer\n"); ASSERTGDI(pwo->bValid(), "WNDOBJ_vSetConsumer: Invalid pwo\n"); // We need to ensure that the caller holds the devlock before calling this // function. Otherwise, some other threads may be drawing into the wrong // client region. if (pwo->hwnd) { CHECKDEVLOCKIN2(pwo->pto->pSurface); } // Do not allow changes to surface wndobj. One reason is that there is // no delete notification for surface wndobj. if (pwo == pwo->pto->pwoSurf) { KdPrint(("WNDOBJ_vSetConsumer: cannot modify surface wndobj!\n")); return; } pwo->pvConsumer = pvConsumer; } /******************************Member*Function*****************************\ * EWNDOBJ::vOffset * * Offset the WNDOBJ by some vector. \**************************************************************************/ VOID EWNDOBJ::vOffset(LONG x, LONG y) { if ((x != 0) || (y != 0)) { EPOINTL eptl(x, y); bOffset(&eptl); *((ERECTL*) &rclBounds) += eptl; *((ERECTL*) &rclClient) += eptl; } }