|
|
#include "precomp.h"
//
// OA.C
// Order Accumulation, both cpi32 and display driver sides
//
// Copyright(c) Microsoft 1997-
//
//
// OA_DDProcessRequest - see oa.h
//
BOOL OA_DDProcessRequest ( UINT fnEscape, LPOSI_ESCAPE_HEADER pRequest, DWORD cbRequest ) { BOOL rc;
DebugEntry(OA_DDProcessRequest);
//
// Get the request number.
//
switch (fnEscape) { case OA_ESC_FLOW_CONTROL: { if (cbRequest != sizeof(OA_FLOW_CONTROL)) { ERROR_OUT(("OA_DDProcessRequest: Invalid size %d for OA_ESC_FLOW_CONTROL", cbRequest)); rc = FALSE; DC_QUIT; }
//
// Save new order accum throughput value
//
g_oaFlow = ((LPOA_FLOW_CONTROL)pRequest)->oaFlow; rc = TRUE; } break;
default: { ERROR_OUT(("Unrecognized OA escape")); rc = FALSE; } break; }
DC_EXIT_POINT: DebugExitBOOL(OA_DDProcessRequest, rc); return(rc); }
//
//
// OA_DDAddOrder(..)
//
// Adds an order to the queue for transmission.
//
// If the new order is completetly covered by the current SDA then
// it is spoilt.
//
// If the order is opaque and overlaps earlier orders it may clip
// or spoil them.
//
// Called by the GDI interception code.
//
//
void OA_DDAddOrder(LPINT_ORDER pNewOrder, void FAR * pExtraInfo) { RECT SDARects[BA_NUM_RECTS*2]; UINT cBounds; UINT spoilingBounds; UINT totalBounds; UINT i; RECT SrcRect; RECT tmpRect; BOOL gotBounds = FALSE; int dx; int dy; RECT IntersectedSrcRect; RECT InvalidDstRect; LPINT_ORDER pTmpOrder; LPEXTTEXTOUT_ORDER pExtTextOut; LPOA_SHARED_DATA lpoaShared; LPOA_FAST_DATA lpoaFast;
DebugEntry(OA_DDAddOrder);
lpoaShared = OA_SHM_START_WRITING; lpoaFast = OA_FST_START_WRITING;
//
// Accumulate order accumulation rate. We are interested in how
// quickly orders are being added to the buffer, so that we can tell
// DCS scheduling whether frequent sends are advisable
//
SHM_CheckPointer(&lpoaFast->ordersAccumulated); lpoaFast->ordersAccumulated++;
//
// If the order is a private one, then we just add it to the Order
// List and return immediately.
//
// Private Orders are used to send bitmap cache information (bitmap
// bits and color tables).
//
// Private Orders never spoil any others and must never be spoilt.
//
if (pNewOrder->OrderHeader.Common.fOrderFlags & OF_PRIVATE) { TRACE_OUT(("Add private order (%lx)", pNewOrder)); OADDAppendToOrderList(lpoaShared, pNewOrder); DC_QUIT; }
//
// If this order is spoilable and its is completely enclosed by the
// current screen data area, we can spoil it. Unless...
//
// PM - Performance
//
// We have observed in usability testing that clipping orders always
// degrades the end-user's perceived performance. This is because the
// orders flow much faster than the screendata and tend to relate to
// text, which is what the user really wants to see. For example, text
// overwriting a bitmap will be delayed because we want to send the
// bitmap as screendata.
//
// Also, word documents tend to contain sections of screendata due to
// mismatched fonts, intelliquotes, spelling annotation, current line
// memblit. Nothing we can do about this, but if we page down two or
// three times, or down and up again we get an accumulation of the
// screendata on all the pages spoiling the orders and the end result
// is that we have to wait longer than we would if we had not spoiled
// the orders.
//
// So, what we can do instead is leave the text orders in and overwrite
// them with screendata when it gets through. However, to make this
// really effective what we also do is convert any transparent text
// (as WEB browsers tend to use) into opaque text on a default
// background.
//
//
if ((pNewOrder->OrderHeader.Common.fOrderFlags & OF_SPOILABLE) != 0) { //
// Get the driver's current bounds.
//
BA_CopyBounds(SDARects, &cBounds, FALSE); gotBounds = TRUE;
for (i = 0; i < cBounds ; i++) { if ( OADDCompleteOverlapRect(&pNewOrder->OrderHeader.Common.rcsDst, &(SDARects[i])) ) { //
// The destination of the order is completely covered by
// the SDA. Check for a text order.
//
pExtTextOut = (LPEXTTEXTOUT_ORDER)pNewOrder->abOrderData; if (pExtTextOut->type == ORD_EXTTEXTOUT_TYPE) { //
// The order is going to be completely overwritten so
// we can play around with it all we like.
// Just make it opaque so the user can read it while
// waiting for the screendata to follow on.
//
pExtTextOut->fuOptions |= ETO_OPAQUE;
//
// pExtTextOut->rectangle is a TSHR_RECT32
//
pExtTextOut->rectangle.left = pNewOrder->OrderHeader.Common.rcsDst.left; pExtTextOut->rectangle.top = pNewOrder->OrderHeader.Common.rcsDst.top; pExtTextOut->rectangle.right = pNewOrder->OrderHeader.Common.rcsDst.right; pExtTextOut->rectangle.bottom = pNewOrder->OrderHeader.Common.rcsDst.bottom;
TRACE_OUT(("Converted text order to opaque")); break; } else { TRACE_OUT(("Spoiling order by SDA")); OA_DDFreeOrderMem(pNewOrder); DC_QUIT; } } } }
//
// Pass the order onto the Bitmap Cache Controller to try to cache the
// src bitmap.
//
if (ORDER_IS_MEMBLT(pNewOrder) || ORDER_IS_MEM3BLT(pNewOrder)) { if (!SBC_DDCacheMemScreenBlt(pNewOrder, pExtraInfo)) { //
// The memory to screen blt cannot be handled as an order (the
// source bitmap could not cached). Add the destination of the
// blt into the SDA and discard the order.
//
TRACE_OUT(("Failed to cache mem->screen blt")); TRACE_OUT(("Add rect to SDA (%d,%d)(%d,%d)", pNewOrder->OrderHeader.Common.rcsDst.left, pNewOrder->OrderHeader.Common.rcsDst.top, pNewOrder->OrderHeader.Common.rcsDst.right, pNewOrder->OrderHeader.Common.rcsDst.bottom ));
RECT_FROM_TSHR_RECT16(&tmpRect, pNewOrder->OrderHeader.Common.rcsDst); OA_DDFreeOrderMem(pNewOrder); BA_AddScreenData(&tmpRect); DC_QUIT; } else { TRACE_OUT(("Cached MEMBLT targetted at (%d,%d)(%d,%d)", pNewOrder->OrderHeader.Common.rcsDst.left, pNewOrder->OrderHeader.Common.rcsDst.top, pNewOrder->OrderHeader.Common.rcsDst.right, pNewOrder->OrderHeader.Common.rcsDst.bottom )); } }
if (ORDER_IS_SCRBLT(pNewOrder)) { //
//
// Handle Screen to Screen (SS) bitblts.
//
// The basic plan
// --------------
//
// If the source of a screen to screen blt intersects with the
// current SDA then we have to do some additional work because all
// orders are always executed before the SDA is copied. This means
// that the data within the SDA will not be available at the time
// we want to do the SS blt.
//
// In this situation we adjust the SS blt to remove all overlap
// from the src rectangle. The destination rectangle is adjusted
// accordingly. The area removed from the destination rectangle is
// added into the SDA.
//
//
TRACE_OUT(("Handle SS blt(%lx)", pNewOrder));
//
// Make the order non-spoilable because we don't want the adding
// of screen data to delete the order.
//
pNewOrder->OrderHeader.Common.fOrderFlags &= ~OF_SPOILABLE;
//
// Calculate the src rect.
//
SrcRect.left = ((LPSCRBLT_ORDER)&pNewOrder->abOrderData)->nXSrc; SrcRect.right = SrcRect.left + ((LPSCRBLT_ORDER)&pNewOrder->abOrderData)->nWidth - 1; SrcRect.top = ((LPSCRBLT_ORDER)&pNewOrder->abOrderData)->nYSrc; SrcRect.bottom = SrcRect.top + ((LPSCRBLT_ORDER)&pNewOrder->abOrderData)->nHeight - 1;
//
//
// ORIGINAL SCRBLT SCHEME
// ----------------------
//
// If the source rectangle intersects the current Screen Data Area
// (SDA) then the src rectangle is modified so that no there is no
// intersection with the SDA, and the dst rectangle adjusted
// accordingly (this is the theory - in practice the operation
// remains the same and we just adjust the dst clip rectangle).
// The destination area that is removed is added into the SDA.
//
// The code works, but can result in more screen data being sent
// than is required.
//
// e.g.
//
// Operation:
//
// SSSSSS DDDDDD
// SSSSSS -> DDDDDD
// SSSSSS DDDDDD
// SxSSSS DDDDDD
//
// S - src rect
// D - dst rect
// x - SDA overlap
//
// The bottom edge of the blt is trimmed off, and the corresponding
// destination area added into the SDA.
//
// SSSSSS DDDDDD
// SSSSSS -> DDDDDD
// SSSSSS DDDDDD
// xxxxxx
//
//
//
// NEW SCRBLT SCHEME
// ------------------
//
// The new scheme does not modify the blt rectangles, and just
// maps the SDA overlap to the destination rect and adds that
// area back into the SDA.
//
// e.g. (as above)
//
// Operation:
//
// SSSSSS DDDDDD
// SSSSSS -> DDDDDD
// SSSSSS DDDDDD
// SxSSSS DDDDDD
//
// S - src rect
// D - dst rect
// x - SDA overlap
//
// The blt operation remains the same, but the overlap area is
// mapped to the destination rectangle and added into the SDA.
//
// SSSSSS DDDDDD
// SSSSSS -> DDDDDD
// SSSSSS DDDDDD
// SxSSSS DxDDDD
//
//
// This scheme results in a smaller SDA area. However, this scheme
// does blt potentially invalid data to the destination - which
// may briefly be visible at the remote machine (because orders
// are replayed before Screen Data). This has not (yet) proved to
// be a problem.
//
// The main benefit of the new scheme is when scrolling an area
// that includes a small SDA.
//
// new old
// scheme scheme
//
// AAAAAAAA AAAAAAAA AAAAAAAA
// AAAAAAAA AAAxAAAA xxxxxxxx
// AAAAAAAA scroll up 3 times -> AAAxAAAA xxxxxxxx
// AAAAAAAA AAAxAAAA xxxxxxxx
// AAAxAAAA AAAxAAAA xxxxxxxx
//
//
//
if (!gotBounds) { //
// Get the driver's current bounds.
//
BA_CopyBounds(SDARects, &cBounds, FALSE); }
//
// Now get any bounds which the share core is currently processing.
// We have to include these bounds when we are doing the above
// processing to avoid a situation where the core grabs the screen
// data from the source of a ScrBlt after the source has been
// updated by another order.
//
// e.g. If there is no driver SDA, but the core is processing the
// area marked 'c'...
//
// If we ignore the core SDA, we queue a ScrBlt order which does
// the following.
//
// SSSSSS DDDDDD
// SccccS -> DDDDDD
// SccccS DDDDDD
// SSSSSS DDDDDD
//
// However, if another order (marked 'N') is accumulated before
// the core grabs the SDA, we end up with the shadow doing the
// following
//
// SSSSSS DDDDDD
// ScNNcS -> DDNNDD
// ScNNcS DDNNDD
// SSSSSS DDDDDD
//
// i.e. the new order gets copied to the destination of the ScrBlt.
// So, the ScrBlt order must be processed as
//
// SSSSSS DDDDDD
// SccccS -> DxxxxD
// SccccS DxxxxD
// SSSSSS DDDDDD
//
//
BA_QuerySpoilingBounds(&SDARects[cBounds], &spoilingBounds); totalBounds = cBounds + spoilingBounds;
//
//
// This is the new SCRBLT handler.
//
//
for (i = 0; i < totalBounds ; i++) { if ( (SrcRect.left >= SDARects[i].left) && (SrcRect.right <= SDARects[i].right) && (SrcRect.top >= SDARects[i].top) && (SrcRect.bottom <= SDARects[i].bottom) ) { //
// The src of the SS blt is completely within the SDA. We
// must add in the whole destination rectangle into the SDA
// and spoil the SS blt.
//
TRACE_OUT(("SS blt src within SDA - spoil it"));
RECT_FROM_TSHR_RECT16(&tmpRect, pNewOrder->OrderHeader.Common.rcsDst); OA_DDFreeOrderMem(pNewOrder); BA_AddScreenData(&tmpRect); DC_QUIT; }
//
// Intersect the src rect with the SDA rect.
//
IntersectedSrcRect.left = max( SrcRect.left, SDARects[i].left ); IntersectedSrcRect.right = min( SrcRect.right, SDARects[i].right ); IntersectedSrcRect.top = max( SrcRect.top, SDARects[i].top ); IntersectedSrcRect.bottom = min( SrcRect.bottom, SDARects[i].bottom );
dx = ((LPSCRBLT_ORDER)&pNewOrder->abOrderData)->nLeftRect - ((LPSCRBLT_ORDER)&pNewOrder->abOrderData)->nXSrc; dy = ((LPSCRBLT_ORDER)&pNewOrder->abOrderData)->nTopRect - ((LPSCRBLT_ORDER)&pNewOrder->abOrderData)->nYSrc;
InvalidDstRect.left = IntersectedSrcRect.left + dx; InvalidDstRect.right = IntersectedSrcRect.right + dx; InvalidDstRect.top = IntersectedSrcRect.top + dy; InvalidDstRect.bottom = IntersectedSrcRect.bottom + dy;
//
// Intersect the invalid destination rectangle with the
// destination clip rectangle.
//
InvalidDstRect.left = max( InvalidDstRect.left, pNewOrder->OrderHeader.Common.rcsDst.left ); InvalidDstRect.right = min( InvalidDstRect.right, pNewOrder->OrderHeader.Common.rcsDst.right ); InvalidDstRect.top = max( InvalidDstRect.top, pNewOrder->OrderHeader.Common.rcsDst.top ); InvalidDstRect.bottom = min( InvalidDstRect.bottom, pNewOrder->OrderHeader.Common.rcsDst.bottom );
if ( (InvalidDstRect.left <= InvalidDstRect.right) && (InvalidDstRect.top <= InvalidDstRect.bottom) ) { //
// Add the invalid area into the SDA.
//
BA_AddScreenData(&InvalidDstRect); }
} // for (i = 0; i < totalBounds ; i++)
//
// Make the order spoilable again (this assumes that all SS blts
// are spoilable.
//
pNewOrder->OrderHeader.Common.fOrderFlags |= OF_SPOILABLE;
} // if (ORDER_IS_SCRBLT(pNewOrder))
else if ((pNewOrder->OrderHeader.Common.fOrderFlags & OF_DESTROP) != 0) { //
// This is the case where the output of the order depends on the
// existing contents of the target area (e.g. an invert).
//
// What we have to do here is to add any parts of the destination
// of this order which intersect the SDA which the share core is
// processing to the driver SDA. The reason for this is the same
// as the SCRBLT case - the share core may grab the data from the
// screen after we have applied this order (e.g. after we have
// inverted an area of the screen), then send the order as well
// (re-inverting the area of the screen).
//
// Note that we only have to worry about the SDA which the share
// core is processing - we can ignore the driver's SDA.
//
TRACE_OUT(("Handle dest ROP (%#.8lx)", pNewOrder));
BA_QuerySpoilingBounds(SDARects, &spoilingBounds); for (i = 0; i < spoilingBounds ; i++) { //
// Intersect the dest rect with the share core SDA rect.
//
InvalidDstRect.left = max( SDARects[i].left, pNewOrder->OrderHeader.Common.rcsDst.left ); InvalidDstRect.right = min( SDARects[i].right, pNewOrder->OrderHeader.Common.rcsDst.right ); InvalidDstRect.top = max( SDARects[i].top, pNewOrder->OrderHeader.Common.rcsDst.top ); InvalidDstRect.bottom = min( SDARects[i].bottom, pNewOrder->OrderHeader.Common.rcsDst.bottom );
if ( (InvalidDstRect.left <= InvalidDstRect.right) && (InvalidDstRect.top <= InvalidDstRect.bottom) ) { //
// Add the invalid area into the SDA.
//
TRACE_OUT(("Adding to SDA (%d,%d) (%d,%d)", InvalidDstRect.left, InvalidDstRect.top, InvalidDstRect.right, InvalidDstRect.bottom)); BA_AddScreenData(&InvalidDstRect); } } }
//
// Add the new order to the end of the Order List.
//
OADDAppendToOrderList(lpoaShared, pNewOrder); TRACE_OUT(("Append order(%lx) to list", pNewOrder));
//
// Now see if this order spoils any existing orders
//
if ((pNewOrder->OrderHeader.Common.fOrderFlags & OF_SPOILER) != 0) { //
// Its a spoiler, so try to spoil with it.
//
// We have to pass in the bounding rectangle of the order, and the
// first order to try to spoil to OADDSpoilFromOrder. The first
// order to try to spoil is the one before the new order.
//
RECT_FROM_TSHR_RECT16(&tmpRect, pNewOrder->OrderHeader.Common.rcsDst);
pTmpOrder = COM_BasedListPrev(&lpoaShared->orderListHead, pNewOrder, FIELD_OFFSET(INT_ORDER, OrderHeader.list));
OADDSpoilFromOrder(lpoaShared, pTmpOrder, &tmpRect); }
//
// This is where the Win95 product would call DCS_TriggerEarlyTimer.
//
DC_EXIT_POINT: OA_FST_STOP_WRITING; OA_SHM_STOP_WRITING; DebugExitVOID(OA_DDAddOrder); }
//
//
// FUNCTION: OA_DDAllocOrderMem
//
// DESCRIPTION:
//
// Allocates memory for an internal order structure from our own private
// Order Heap.
//
// Allocates any Additional Order Memory from global memory. A pointer to
// the Additional Order Memory is stored within the allocated order's
// header (pOrder->OrderHeader.pAdditionalOrderData).
//
//
// PARAMETERS:
//
// cbOrderDataLength - length in bytes of the order data to be allocated
// from the Order Heap.
//
// cbAdditionalOrderDataLength - length in bytes of additional order data
// to be allocated from Global Memory. If this parameter is zero no
// additional order memory is allocated.
//
//
// RETURNS:
//
// A pointer to the allocated order memory. NULL if the memory allocation
// failed.
//
//
//
LPINT_ORDER OA_DDAllocOrderMem(UINT cbOrderDataLength, UINT cbAdditionalOrderDataLength) { LPINT_ORDER pOrder = NULL; LPINT_ORDER pFirstOrder; LPINT_ORDER pTailOrder; RECT tferRect; int targetSize; UINT moveOffset; UINT moveBytes; LPINT_ORDER pColorTableOrder = NULL; LPBYTE pNextOrderPos; LPOA_SHARED_DATA lpoaShared;
DebugEntry(OA_DDAllocOrderMem);
lpoaShared = OA_SHM_START_WRITING;
//
// PM Performance
//
// Although turning order accumulation off does clear the pipe, ready
// for us to get the screendata over the wire as soon as we can, it
// actually hinders end-user responsiveness because they see a longer
// interval when nothing is happening, rather than getting feedback
// that we are busy and the whole thing taking longer!
//
// So, what we do when we fill up the order buffer is we discard half
// the orders in the buffer, adding them to the screendata. In this
// way we will always keep between 50 and 100% of the orders for the
// final updates to the window, which hopefully will be what the user
// really wants to see.
//
// If the orders keep coming then we will keep on accumulating some,
// sending them, discarding others until things quiet down, at which
// point we will flush out our order buffer.
//
// When we come to flush the order buffer we also spoil the early ones
// against screendata, so that we only have the final set of orders to
// replay. We control the size of this final non-spoiled set depending
// on whether we are running over a high or low speed connection.
// Also, if we did not encounter any back pressure during the session
// then we do not purge any orders at all, preferring to send
// everything we possibly can as orders.
//
// Note that this approach assumes that we do not spoil all orders
// against screendata on the fly because that leads to us generally
// sending out-of-data orders followed by up-to-date screendata, which
// is exactly what we do not want to see.
//
//
//
// First check that we have not already exceeded our high water mark
// recommended by flow control. If we have then purge half the queue
// so we have space to accumulate the later, more valuable, orders
//
// Note that this does not guarantee that we will have less orders
// accumulated than the limit set by flow control. However, if enough
// orders are generated, we will come through this branch on each order
// and finally reduce to below the imposed limit.
//
SHM_CheckPointer(&lpoaShared->totalOrderBytes); if (g_oaPurgeAllowed && (lpoaShared->totalOrderBytes > (DWORD)(g_oaFlow == OAFLOW_FAST ? OA_FAST_HEAP : OA_SLOW_HEAP))) { RECT aRects[BA_NUM_RECTS]; UINT numRects; UINT i;
WARNING_OUT(("Purging orders; total 0x%08x is greater than heap 0x%08x", lpoaShared->totalOrderBytes, (g_oaFlow == OAFLOW_FAST ? OA_FAST_HEAP : OA_SLOW_HEAP)));
//
//
// If we need to make room for the new order then purge half the
// current queue. We do this so we end up with the most recent
// orders on the queue, rather than the oldest.
//
SHM_CheckPointer(&lpoaShared->totalOrderBytes); targetSize = lpoaShared->totalOrderBytes / 2; TRACE_OUT(("Target size %ld", targetSize));
//
// Iterate through the list until we have found the first order
// beyond the limit to be destroyed. Once we have got this order,
// we can shuffle the list over the useless orders.
//
SHM_CheckPointer(&lpoaShared->orderListHead); pOrder = COM_BasedListFirst(&lpoaShared->orderListHead, FIELD_OFFSET(INT_ORDER, OrderHeader.list));
pTailOrder = (LPINT_ORDER)COM_BasedPrevListField(&lpoaShared->orderListHead);
//
// If we hit this condition, we have to have at least one order
// pending, so these both must be non NULL.
//
SHM_CheckPointer(pOrder); SHM_CheckPointer(pTailOrder);
TRACE_OUT(("Order 0x%08lx, tail 0x%08lx", pOrder, pTailOrder));
//
// Disable spoiling of existing orders by screen data while we do
// the purge otherwise we may try to spoil an order which we are
// purging !
//
g_baSpoilByNewSDAEnabled = FALSE;
while ((pOrder != NULL) && (targetSize > 0)) { //
// Can't check at end; COM_BasedListNext may return NULL and
// SHM_CheckPointer doesn't like that.
//
SHM_CheckPointer(pOrder);
//
// Check to see if this is an internal color table order. If
// it is, the OF_INTERNAL flag will be set.
//
// MemBlt orders rely on being preceeded by a color table order
// to set up the colors correctly. If we purge all the color
// table orders, the following Mem(3)Blts will get the wrong
// colors. So, we have to keep track of the last color table
// order to be purged and then add it back into the order heap
// later.
//
if ((pOrder->OrderHeader.Common.fOrderFlags & OF_INTERNAL) != 0) { TRACE_OUT(("Found color table order at %#.8lx", pOrder)); pColorTableOrder = pOrder; } else { //
// Add the order to the Screen Data Area
//
TRACE_OUT(("Purging orders. Add rect to SDA (%d,%d)(%d,%d)", pOrder->OrderHeader.Common.rcsDst.left, pOrder->OrderHeader.Common.rcsDst.top, pOrder->OrderHeader.Common.rcsDst.right, pOrder->OrderHeader.Common.rcsDst.bottom));
RECT_FROM_TSHR_RECT16(&tferRect, pOrder->OrderHeader.Common.rcsDst); BA_AddScreenData(&tferRect); }
//
// Keep track of how much data still needs removing.
//
targetSize -= INT_ORDER_SIZE(pOrder); lpoaShared->totalHeapOrderBytes -= INT_ORDER_SIZE(pOrder); lpoaShared->totalOrderBytes -= MAX_ORDER_SIZE(pOrder);
//
// If the order is a Mem(3)Blt, we have to tell SBC that we are
// getting rid of it.
//
if (ORDER_IS_MEMBLT(pOrder) || ORDER_IS_MEM3BLT(pOrder)) { SBC_DDOrderSpoiltNotification(pOrder); }
//
// Get the next order to be removed.
//
pOrder = COM_BasedListNext(&lpoaShared->orderListHead, pOrder, FIELD_OFFSET(INT_ORDER, OrderHeader.list)); }
TRACE_OUT(("Stopped at order %#.8lx", pOrder));
//
// Orders have been transferred to SDA, so now we have to
// - move the last purged color table order (if there is one) to
// the start of the order heap
// - shuffle up the heap
// - reset the pointers.
//
// pOrder points to the first non-purged order.
//
if (pOrder != NULL) { SHM_CheckPointer(&lpoaShared->orderHeap); SHM_CheckPointer(&lpoaShared->orderListHead);
pNextOrderPos = lpoaShared->orderHeap;
//
// If we purged (at least) one color table order, move the last
// color table order to the start of the order heap.
//
if (pColorTableOrder != NULL) { TRACE_OUT(("Moving color table from %#.8lx to start", pColorTableOrder));
RtlMoveMemory(pNextOrderPos, pColorTableOrder, INT_ORDER_SIZE(pColorTableOrder));
pColorTableOrder = (LPINT_ORDER)pNextOrderPos; lpoaShared->totalHeapOrderBytes += INT_ORDER_SIZE(pColorTableOrder); lpoaShared->totalOrderBytes += MAX_ORDER_SIZE(pColorTableOrder); pNextOrderPos += INT_ORDER_SIZE(pColorTableOrder);
//
// Chain the order into the start of the order list. Just
// do the pointers to and from the list head for now, we
// will do the rest later.
//
lpoaShared->orderListHead.next = PTRBASE_TO_OFFSET(pColorTableOrder, &lpoaShared->orderListHead);
pColorTableOrder->OrderHeader.list.prev = PTRBASE_TO_OFFSET(&lpoaShared->orderListHead, pColorTableOrder); }
//
// Move the heap up to the top of the buffer. The following
// diagram illustrates how the order heap is split up at the
// moment.
//
// OA_SHM_NEXTORDER
// |<���������������������������������������>|
//
// moveOffset moveBytes
// |<���������������>|<�����������������>|
//
// ���������������������������������������������������������ͻ
// � � � � �
// � � purged � remaining � unused �
// � � orders � orders � �
// � � � � � �
// ���������������������������������������������������������ͼ
// ^ � ^ ^
// � � � �
// � � � �
// � � � ��� pOrder
// � � �
// � � ���� pNextOrderPos
// � �
// � ������ color table order
// �
// �������� lpoaShared->orderHeap (pColorTableOrder)
//
// If there is no color table order, pNextOrderPos is equal to
// lpoaShared->orderHeap.
//
// moveOffset is the number of bytes to move the remaining
// orders by.
//
// moveBytes is the number of bytes to be moved.
//
//
moveOffset = (UINT)((UINT_PTR)pOrder - (UINT_PTR)pNextOrderPos); moveBytes = (UINT)(lpoaShared->nextOrder - moveOffset - (pNextOrderPos - lpoaShared->orderHeap));
TRACE_OUT(("Moving %d bytes", moveBytes));
RtlMoveMemory(pNextOrderPos, pOrder, moveBytes);
//
// Update the head and tail pointers to reflect their new
// positions.
//
pFirstOrder = (LPINT_ORDER)pNextOrderPos; pTailOrder = (LPINT_ORDER)((LPBYTE)pTailOrder - moveOffset); SHM_CheckPointer(pFirstOrder); SHM_CheckPointer(pTailOrder);
TRACE_OUT(("New first unpurged %#.8lx, tail %#.8lx", pFirstOrder, pTailOrder));
//
// Since the offsets are relative to the order pointer, we only
// need to modify the start and end offsets.
//
// Unfortunately, the possibility of a color table order at the
// start of the heap complicates the chaining of pFirstOrder.
// If there is a color table order, we chain pFirstOrder to the
// color table order, otherwise we chain it to the start of the
// order list.
//
lpoaShared->orderListHead.prev = PTRBASE_TO_OFFSET(pTailOrder, &lpoaShared->orderListHead); pTailOrder->OrderHeader.list.next = PTRBASE_TO_OFFSET(&lpoaShared->orderListHead, pTailOrder);
if (pColorTableOrder != NULL) { pColorTableOrder->OrderHeader.list.next = PTRBASE_TO_OFFSET(pFirstOrder, pColorTableOrder); pFirstOrder->OrderHeader.list.prev = PTRBASE_TO_OFFSET(pColorTableOrder, pFirstOrder); } else { lpoaShared->orderListHead.next = PTRBASE_TO_OFFSET(pFirstOrder, &lpoaShared->orderListHead); pFirstOrder->OrderHeader.list.prev = PTRBASE_TO_OFFSET(&lpoaShared->orderListHead, pFirstOrder); }
//
// Sort out where the next order to be allocated will go
//
lpoaShared->nextOrder -= moveOffset; } else { //
// No orders left - this happens if we've had lots of spoiling.
// We have now cleared out all the valid orders so let's
// re-initialise the heap for next time.
//
OA_DDResetOrderList(); }
//
// Now re-enable the spoiling of orders by SDA.
//
g_baSpoilByNewSDAEnabled = TRUE;
WARNING_OUT(("Purged orders, total is now 0x%08x", lpoaShared->totalOrderBytes));
//
// Lastly, spoil the remaining orders by the screen data.
// If we've gotten this far, there's a lot of data being sent
// and/or we're slow. So nuke 'em.
//
BA_CopyBounds(aRects, &numRects, FALSE);
for (i = 0; i < numRects; i++) { OA_DDSpoilOrdersByRect(aRects+i); }
WARNING_OUT(("Spoiled remaining orders by SDA, total is now 0x%08x", lpoaShared->totalOrderBytes));
TRACE_OUT(("Next 0x%08lx", lpoaShared->nextOrder)); TRACE_OUT(("Head 0x%08lx", lpoaShared->orderListHead.next)); TRACE_OUT(("Tail 0x%08lx", lpoaShared->orderListHead.prev)); TRACE_OUT(("Total heap bytes 0x%08lx", lpoaShared->totalHeapOrderBytes)); TRACE_OUT(("Total order bytes 0x%08lx", lpoaShared->totalOrderBytes)); }
pOrder = OADDAllocOrderMemInt(lpoaShared, cbOrderDataLength, cbAdditionalOrderDataLength); if ( pOrder != NULL ) { //
// Update the count of total order data.
//
SHM_CheckPointer(&lpoaShared->totalHeapOrderBytes); lpoaShared->totalHeapOrderBytes += sizeof(INT_ORDER_HEADER) + cbOrderDataLength;
SHM_CheckPointer(&lpoaShared->totalAdditionalOrderBytes); lpoaShared->totalAdditionalOrderBytes += cbAdditionalOrderDataLength;
} TRACE_OUT(("Alloc order, addr %lx, size %u", pOrder, cbOrderDataLength));
OA_SHM_STOP_WRITING; DebugExitPVOID(OA_DDAllocOrderMem, pOrder); return(pOrder); }
//
//
// FUNCTION: OA_DDFreeOrderMem
//
//
// DESCRIPTION:
//
// Frees order memory from our own private heap.
// Frees any Additional Order Memory associated with this order.
//
//
// PARAMETERS:
//
// pOrder - pointer to the order to be freed.
//
//
// RETURNS:
//
// Nothing.
//
//
void OA_DDFreeOrderMem(LPINT_ORDER pOrder) { LPOA_SHARED_DATA lpoaShared;
DebugEntry(OA_DDFreeOrderMem);
ASSERT(pOrder);
lpoaShared = OA_SHM_START_WRITING;
TRACE_OUT(("Free order %lx", pOrder));
//
// Update the data totals.
//
SHM_CheckPointer(&lpoaShared->totalHeapOrderBytes); lpoaShared->totalHeapOrderBytes -= (sizeof(INT_ORDER_HEADER) + pOrder->OrderHeader.Common.cbOrderDataLength);
SHM_CheckPointer(&lpoaShared->totalAdditionalOrderBytes); lpoaShared->totalAdditionalOrderBytes -= pOrder->OrderHeader.cbAdditionalOrderDataLength;
//
// Do the work.
//
OADDFreeOrderMemInt(lpoaShared, pOrder);
OA_SHM_STOP_WRITING; DebugExitVOID(OA_DDFreeOrderMem); }
//
//
// FUNCTION: OA_DDResetOrderList
//
//
// DESCRIPTION:
//
// Frees all Orders and Additional Order Data in the Order List.
// Frees up the Order Heap memory.
//
//
// PARAMETERS:
//
// None.
//
//
// RETURNS:
//
// Nothing.
//
//
void OA_DDResetOrderList(void) { LPOA_SHARED_DATA lpoaShared;
DebugEntry(OA_DDResetOrderList);
lpoaShared = OA_SHM_START_WRITING;
//
// First free all the orders on the list.
//
OADDFreeAllOrders(lpoaShared);
//
// Ensure that the list pointers are NULL.
//
SHM_CheckPointer(&lpoaShared->orderListHead); if ((lpoaShared->orderListHead.next != 0) || (lpoaShared->orderListHead.prev != 0)) { ERROR_OUT(("Non-NULL list pointers (%lx)(%lx)", lpoaShared->orderListHead.next, lpoaShared->orderListHead.prev));
SHM_CheckPointer(&lpoaShared->orderListHead); COM_BasedListInit(&lpoaShared->orderListHead); }
OA_SHM_STOP_WRITING; DebugExitVOID(OA_DDResetOrderList); }
//
// OA_DDSyncUpdatesNow
//
// Called when a sync operation is required.
//
// Discards all outstanding orders.
//
void OA_DDSyncUpdatesNow(void) { DebugEntry(OA_SyncUpdatesNow);
OADDFreeAllOrders(g_poaData[g_asSharedMemory->displayToCore.currentBuffer]);
DebugExitVOID(OA_DDSyncUpdatesNow); }
//
//
// OA_DDRemoveListOrder(..)
//
// Removes the specified order from the Order List by marking it as spoilt.
//
// Returns:
// Pointer to the order following the removed order.
//
//
LPINT_ORDER OA_DDRemoveListOrder(LPINT_ORDER pCondemnedOrder) { LPINT_ORDER pSaveOrder; LPOA_SHARED_DATA lpoaShared;
DebugEntry(OA_DDRemoveListOrder);
TRACE_OUT(("Remove list order (%lx)", pCondemnedOrder));
lpoaShared = OA_SHM_START_WRITING;
SHM_CheckPointer(pCondemnedOrder);
//
// Check for a valid order.
//
if (pCondemnedOrder->OrderHeader.Common.fOrderFlags & OF_SPOILT) { TRACE_OUT(("Invalid order")); DC_QUIT; }
//
// Get the offset value of this order.
//
SHM_CheckPointer(&lpoaShared->orderHeap);
//
// Mark the order as spoilt.
//
pCondemnedOrder->OrderHeader.Common.fOrderFlags |= OF_SPOILT;
//
// Update the count of bytes currently in the Order List.
//
SHM_CheckPointer(&lpoaShared->totalOrderBytes); lpoaShared->totalOrderBytes -= (UINT)MAX_ORDER_SIZE(pCondemnedOrder);
//
// SAve the order so we can remove it from the linked list after having
// got the next element in the chain.
//
pSaveOrder = pCondemnedOrder;
//
// Return the next order in the list.
//
SHM_CheckPointer(&lpoaShared->orderListHead);
pCondemnedOrder = COM_BasedListNext(&lpoaShared->orderListHead, pCondemnedOrder, FIELD_OFFSET(INT_ORDER, OrderHeader.list));
if (pSaveOrder == pCondemnedOrder) { ERROR_OUT(("Order list has gone circular !")); }
//
// Delete the unwanted order from the linked list.
//
COM_BasedListRemove(&pSaveOrder->OrderHeader.list);
//
// Check that the list is still consistent with the total number of
// order bytes.
//
if ( (lpoaShared->orderListHead.next != 0) && (lpoaShared->orderListHead.prev != 0) && (lpoaShared->totalOrderBytes == 0) ) { ERROR_OUT(("List head wrong: %ld %ld", lpoaShared->orderListHead.next, lpoaShared->orderListHead.prev)); COM_BasedListInit(&lpoaShared->orderListHead); pCondemnedOrder = NULL; }
DC_EXIT_POINT: OA_SHM_STOP_WRITING;
DebugExitPVOID(OA_DDRemoveListOrder, pCondemnedOrder); return(pCondemnedOrder); }
//
// OA_DDSpoilOrdersByRect - see oa.h
//
void OA_DDSpoilOrdersByRect(LPRECT pRect) { LPOA_SHARED_DATA lpoaShared; LPINT_ORDER pOrder;
DebugEntry(OA_DDSpoilOrdersByRect);
lpoaShared = OA_SHM_START_WRITING;
//
// We want to start spoiling from the newest order i.e. the one at the
// end of the order list.
//
pOrder = COM_BasedListLast(&lpoaShared->orderListHead, FIELD_OFFSET(INT_ORDER, OrderHeader.list));
if (pOrder != NULL) { OADDSpoilFromOrder(lpoaShared, pOrder, pRect); }
OA_SHM_STOP_WRITING;
DebugExitVOID(OA_DDSpoilOrdersByRect); }
//
//
// OADDAppendToOrderList(..)
//
// Commits an allocated order to the end of the Order List. The order must
// NOT be freed once it has been added. The whole list must be invalidated
// to free the committed orders.
//
//
void OADDAppendToOrderList(LPOA_SHARED_DATA lpoaShared, LPINT_ORDER pNewOrder) { DebugEntry(OADDAppendToOrderList);
//
// Chain entry is already set up so all we do is keep track of
// committed orders.
//
//
// Store the total number of order bytes used.
//
SHM_CheckPointer(&lpoaShared->totalOrderBytes); lpoaShared->totalOrderBytes += (UINT)MAX_ORDER_SIZE(pNewOrder);
DebugExitVOID(OADDAppendToOrderList); }
//
//
// FUNCTION: OADDAllocOrderMemInt
//
// DESCRIPTION:
//
// Allocates memory for an internal order structure from our order heap.
//
//
// PARAMETERS:
//
// cbOrderDataLength - length in bytes of the order data to be allocated
// from the Order Heap.
//
// cbAdditionalOrderDataLength - length in bytes of additional order data
// to be allocated. If this parameter is zero no additional order memory
// is allocated.
//
//
// RETURNS:
//
// A pointer to the allocated order memory. NULL if the memory allocation
// failed.
//
//
//
LPINT_ORDER OADDAllocOrderMemInt ( LPOA_SHARED_DATA lpoaShared, UINT cbOrderDataLength, UINT cbAdditionalOrderDataLength ) { LPINT_ORDER pOrder = NULL; UINT cbOrderSize;
DebugEntry(OADDAllocOrderMemInt);
//
// If the additional data will take us over our Additional Data Limit
// then fail the memory allocation.
//
SHM_CheckPointer(&lpoaShared->totalAdditionalOrderBytes); if ((lpoaShared->totalAdditionalOrderBytes + cbAdditionalOrderDataLength) > MAX_ADDITIONAL_DATA_BYTES) { TRACE_OUT(("Hit Additional Data Limit, current %lu addint %u", lpoaShared->totalAdditionalOrderBytes, cbAdditionalOrderDataLength)); DC_QUIT; }
//
// Calculate the number of bytes we need to allocate (including the
// order header). Round up to the nearest 4 bytes to keep the 4 byte
// alignment for the next order.
//
cbOrderSize = sizeof(INT_ORDER_HEADER) + cbOrderDataLength; cbOrderSize = (cbOrderSize + 3) & 0xFFFFFFFC;
//
// Make sure we don't overrun our heap limit
//
SHM_CheckPointer(&lpoaShared->nextOrder); if (lpoaShared->nextOrder + cbOrderSize > OA_HEAP_MAX) { TRACE_OUT(("Heap limit hit")); DC_QUIT; }
//
// Construct a far pointer to the allocated memory, and fill in the
// length field in the Order Header.
//
SHM_CheckPointer(&lpoaShared->orderHeap); pOrder = (LPINT_ORDER)(lpoaShared->orderHeap + lpoaShared->nextOrder); pOrder->OrderHeader.Common.cbOrderDataLength = (TSHR_UINT16)cbOrderDataLength;
//
// Update the order header to point to the next section of free heap.
//
SHM_CheckPointer(&lpoaShared->nextOrder); lpoaShared->nextOrder += cbOrderSize;
//
// Allocate any Additional Order Memory from Global Memory.
//
if (cbAdditionalOrderDataLength > 0) { //
// Make sure we don't overrun our heap limit
//
SHM_CheckPointer(&lpoaShared->nextOrder); if (lpoaShared->nextOrder + cbAdditionalOrderDataLength > OA_HEAP_MAX) { TRACE_OUT(("Heap limit hit for additional data"));
//
// Clear the allocated order and quit.
//
SHM_CheckPointer(&lpoaShared->nextOrder); lpoaShared->nextOrder -= cbOrderSize; pOrder = NULL; DC_QUIT; }
//
// Store the space for the additional data.
//
SHM_CheckPointer(&lpoaShared->nextOrder); pOrder->OrderHeader.additionalOrderData = lpoaShared->nextOrder; pOrder->OrderHeader.cbAdditionalOrderDataLength = (WORD)cbAdditionalOrderDataLength;
//
// Update the next order pointer to point to the next 4-byte
// boundary.
//
SHM_CheckPointer(&lpoaShared->nextOrder); lpoaShared->nextOrder += cbAdditionalOrderDataLength + 3; lpoaShared->nextOrder &= 0xFFFFFFFC; } else { pOrder->OrderHeader.additionalOrderData = 0; pOrder->OrderHeader.cbAdditionalOrderDataLength = 0; }
//
// Create the chain entry.
//
SHM_CheckPointer(&lpoaShared->orderListHead); COM_BasedListInsertBefore(&lpoaShared->orderListHead, &pOrder->OrderHeader.list);
DC_EXIT_POINT: DebugExitPVOID(OADDAllocOrderMemInit, pOrder); return(pOrder); }
//
//
// FUNCTION: OADDFreeOrderMemInt
//
//
// DESCRIPTION:
//
// Frees order memory from our orders heap. Frees any Additional Order
// Memory associated with this order. This must NOT be used on an order
// that has been committed to the order list.
//
//
// PARAMETERS:
//
// pOrder - pointer to the order to be freed.
//
//
// RETURNS:
//
// Nothing.
//
//
void OADDFreeOrderMemInt(LPOA_SHARED_DATA lpoaShared, LPINT_ORDER pOrder) { LPINT_ORDER pOrderTail;
DebugEntry(OADDFreeOrderMemInt);
//
// The order heap is real a misnomer. We know that the memory is only
// allocated in a purely sequential manner and deallocated as one large
// lump of memory.
//
// So we do not need to implement a full memory heap allocation
// mechanism. Instead, we just need to keep track of where the
// previous high water mark was before this order was freed.
//
//
// Find the tail of the current chain.
//
pOrderTail = COM_BasedListLast(&lpoaShared->orderListHead, FIELD_OFFSET(INT_ORDER, OrderHeader.list)); SHM_CheckPointer(pOrderTail);
//
// We wont necessarily be freeing the last item in the order heap.
//
if (pOrder == pOrderTail) { //
// This is the last item in the heap, so we can set the pointer to
// the next order to be used back to the start of the order being
// freed.
//
SHM_CheckPointer(&lpoaShared->nextOrder); lpoaShared->nextOrder = (LONG)((LPBYTE)pOrder - (LPBYTE)(lpoaShared->orderHeap)); } else { //
// This is not the last item in the heap - we must not reset the
// pointer to the next item to be used.
//
TRACE_OUT(("Not resetting next order (not last item in heap)")); }
//
// Delete the item from the chain.
//
COM_BasedListRemove(&pOrder->OrderHeader.list);
DebugExitVOID(OADDFreeOrderMemInt); }
//
// OADDFreeAllOrders
//
// Free the all the individual orders on the orders list, without
// discarding the list itself.
//
void OADDFreeAllOrders(LPOA_SHARED_DATA lpoaShared) { DebugEntry(OADDFreeAllOrders);
//
// Simply clear the list head.
//
COM_BasedListInit(&lpoaShared->orderListHead); SHM_CheckPointer(&lpoaShared->orderListHead);
lpoaShared->totalHeapOrderBytes = 0; lpoaShared->totalOrderBytes = 0; lpoaShared->totalAdditionalOrderBytes = 0; lpoaShared->nextOrder = 0;
DebugExitVOID(OADDFreeAllOrders); }
//
//
// OADDOrderIsValid(..)
//
// Determines if a pointer points to a valid order.
//
// Returns:
// TRUE if valid order, FALSE if invalid.
//
//
BOOL OADDOrderIsValid(LPINT_ORDER pOrder) { BOOL rc;
DebugEntry(OADDOrderIsValid);
//
// Check the order is not already spoilt
//
rc = ((pOrder->OrderHeader.Common.fOrderFlags & OF_SPOILT) == 0);
DebugExitBOOL(OADDOrderIsValid, rc); return(rc); }
BOOL OADDCompleteOverlapRect(LPTSHR_RECT16 prcsSrc, LPRECT prcsOverlap) { //
// Return TRUE if the source is completely enclosed by the overlap
// rectangle.
//
return( (prcsSrc->left >= prcsOverlap->left) && (prcsSrc->right <= prcsOverlap->right) && (prcsSrc->top >= prcsOverlap->top) && (prcsSrc->bottom <= prcsOverlap->bottom) ); }
//
// Name: OADDSpoilFromOrder
//
// Purpose: Remove any orders from the order heap which should be spoiled
// by a given rectangle..
//
// Returns: Nothing
//
// Params: IN pTargetOrder - Pointer to the first order to try to
// spoil.
// IN pRect - Pointer to the spoiling rectangle.
//
// Operation: pTargetOrder may be spoiled by this function, so be careful
// on return.
//
void OADDSpoilFromOrder ( LPOA_SHARED_DATA lpoaShared, LPINT_ORDER pTargetOrder, LPRECT pSpoilRect ) { UINT nonProductiveScanDepth = 0; UINT scanExitDepth; BOOL reachedBlocker = FALSE;
DebugEntry(OADDSpoilFromOrder);
TRACE_OUT(("Spoiling rect is {%d, %d, %d, %d}", pSpoilRect->left, pSpoilRect->top, pSpoilRect->right, pSpoilRect->bottom));
//
// Work out how deep we will scan if the spoiling is non-productive.
// We go further for bigger orders over PSTN. (ie Irrespective of the
// bandwidth we do not want to do much work when the app is blasting
// out a lot of single pel orders!)
//
if (((pSpoilRect->right - pSpoilRect->left) < FULL_SPOIL_WIDTH) && ((pSpoilRect->bottom - pSpoilRect->top) < FULL_SPOIL_HEIGHT)) { TRACE_OUT(("Small order so reducing spoil depth")); scanExitDepth = OA_FAST_SCAN_DEPTH; } else { //
// Use the current default scan depth (this is based on the
// current network throughput).
//
scanExitDepth = (g_oaFlow == OAFLOW_FAST) ? OA_FAST_SCAN_DEPTH : OA_SLOW_SCAN_DEPTH; }
//
// Loop backwards from the base order until we have one of the
// following occurs.
// - We spoil all the preceeding orders.
// - We reach a blocker which we can't spoil.
// - We find scanExitDepth orders which we can't spoil.
//
while ((pTargetOrder != NULL) && !reachedBlocker && (nonProductiveScanDepth < scanExitDepth)) { //
// We do not exit immediately when we reach a blocker because it is
// possible that we will spoil it. If we do spoil it, then we can
// quite happily try spoiling the orders which preceed it.
//
// So, just set a flag here which we will reset if we spoil the
// order.
//
reachedBlocker = ((pTargetOrder->OrderHeader.Common.fOrderFlags & OF_BLOCKER) != 0);
//
// Only try to spoil spoilable orders.
//
if ((pTargetOrder->OrderHeader.Common.fOrderFlags & OF_SPOILABLE) != 0) { if (OADDCompleteOverlapRect( &pTargetOrder->OrderHeader.Common.rcsDst, pSpoilRect)) { //
// The order can be spoilt. If the order is a MemBlt or a
// Mem3Blt, we have to notify SBC to allow it to free up
// associated data.
//
if (ORDER_IS_MEMBLT(pTargetOrder) || ORDER_IS_MEM3BLT(pTargetOrder)) { SBC_DDOrderSpoiltNotification(pTargetOrder); }
TRACE_OUT(("Spoil by order (%hd, %hd) (%hd, %hd)", pTargetOrder->OrderHeader.Common.rcsDst.left, pTargetOrder->OrderHeader.Common.rcsDst.top, pTargetOrder->OrderHeader.Common.rcsDst.right, pTargetOrder->OrderHeader.Common.rcsDst.bottom));
pTargetOrder = OA_DDRemoveListOrder(pTargetOrder);
//
// Reset the blocker flag - we spoiled the order, so if it
// was a blocker we can now try to spoil earlier orders.
//
reachedBlocker = FALSE; } else { nonProductiveScanDepth++; } } else { nonProductiveScanDepth++; }
//
// Get the previous order in the list. We have to be careful
// because we may have just removed the last item in the list, in
// which case pTargetOrder will be NULL.
//
if (pTargetOrder == NULL) { pTargetOrder = COM_BasedListLast(&lpoaShared->orderListHead, FIELD_OFFSET(INT_ORDER, OrderHeader.list)); } else { pTargetOrder = COM_BasedListPrev(&lpoaShared->orderListHead, pTargetOrder, FIELD_OFFSET(INT_ORDER, OrderHeader.list)); } }
DebugExitVOID(OADDSpoilFromOrder); }
|