|
|
/******************************Module*Header*******************************\
* Module Name: ddraw.c * * Implements all the DirectDraw components for the driver. * * Copyright (c) 1995-1996 Microsoft Corporation \**************************************************************************/
#include "precomp.h"
#define VBLANK_IS_ACTIVE(pjBase) \
(READ_PORT_UCHAR(pjBase + VGA_BASE + IN_STAT_1) & 0x8)
#define DISPLAY_IS_ACTIVE(pjBase) \
(!(READ_PORT_UCHAR(pjBase + VGA_BASE + IN_STAT_1) & 0x1))
#define START_ADDRESS_HIGH 0x0C // Index for Frame Buffer Start
/******************************Public*Routine******************************\
* VOID vGetDisplayDuration * * Get the length, in EngQueryPerformanceCounter() ticks, of a refresh cycle. * * If we could trust the miniport to return back and accurate value for * the refresh rate, we could use that. Unfortunately, our miniport doesn't * ensure that it's an accurate value. * \**************************************************************************/
#define NUM_VBLANKS_TO_MEASURE 1
#define NUM_MEASUREMENTS_TO_TAKE 8
VOID vGetDisplayDuration( PDEV* ppdev) { BYTE* pjBase; LONG i; LONG j; LONGLONG li; LONGLONG liFrequency; LONGLONG liMin; LONGLONG aliMeasurement[NUM_MEASUREMENTS_TO_TAKE + 1];
pjBase = ppdev->pjBase;
memset(&ppdev->flipRecord, 0, sizeof(ppdev->flipRecord));
// Warm up EngQUeryPerformanceCounter to make sure it's in the working
// set:
EngQueryPerformanceCounter(&li);
// Unfortunately, since NT is a proper multitasking system, we can't
// just disable interrupts to take an accurate reading. We also can't
// do anything so goofy as dynamically change our thread's priority to
// real-time.
//
// So we just do a bunch of short measurements and take the minimum.
//
// It would be 'okay' if we got a result that's longer than the actual
// VBlank cycle time -- nothing bad would happen except that the app
// would run a little slower. We don't want to get a result that's
// shorter than the actual VBlank cycle time -- that could cause us
// to start drawing over a frame before the Flip has occured.
//
// Skip a couple of vertical blanks to allow the hardware to settle
// down after the mode change, to make our readings accurate:
for (i = 2; i != 0; i--) { while (VBLANK_IS_ACTIVE(pjBase)) ; while (!(VBLANK_IS_ACTIVE(pjBase))) ; }
for (i = 0; i < NUM_MEASUREMENTS_TO_TAKE; i++) { // We're at the start of the VBlank active cycle!
EngQueryPerformanceCounter(&aliMeasurement[i]);
// Okay, so life in a multi-tasking environment isn't all that
// simple. What if we had taken a context switch just before
// the above EngQueryPerformanceCounter call, and now were half
// way through the VBlank inactive cycle? Then we would measure
// only half a VBlank cycle, which is obviously bad. The worst
// thing we can do is get a time shorter than the actual VBlank
// cycle time.
//
// So we solve this by making sure we're in the VBlank active
// time before and after we query the time. If it's not, we'll
// sync up to the next VBlank (it's okay to measure this period --
// it will be guaranteed to be longer than the VBlank cycle and
// will likely be thrown out when we select the minimum sample).
// There's a chance that we'll take a context switch and return
// just before the end of the active VBlank time -- meaning that
// the actual measured time would be less than the true amount --
// but since the VBlank is active less than 1% of the time, this
// means that we would have a maximum of 1% error approximately
// 1% of the times we take a context switch. An acceptable risk.
//
// This next line will cause us wait if we're no longer in the
// VBlank active cycle as we should be at this point:
while (!(VBLANK_IS_ACTIVE(pjBase))) ;
for (j = 0; j < NUM_VBLANKS_TO_MEASURE; j++) { while (VBLANK_IS_ACTIVE(pjBase)) ; while (!(VBLANK_IS_ACTIVE(pjBase))) ; } }
EngQueryPerformanceCounter(&aliMeasurement[NUM_MEASUREMENTS_TO_TAKE]);
// Use the minimum:
liMin = aliMeasurement[1] - aliMeasurement[0];
DISPDBG((1, "Refresh count: %li - %li", 1, (ULONG) liMin));
for (i = 2; i <= NUM_MEASUREMENTS_TO_TAKE; i++) { li = aliMeasurement[i] - aliMeasurement[i - 1];
DISPDBG((1, " %li - %li", i, (ULONG) li));
if (li < liMin) liMin = li; }
// Round the result:
ppdev->flipRecord.liFlipDuration = (DWORD) (liMin + (NUM_VBLANKS_TO_MEASURE / 2)) / NUM_VBLANKS_TO_MEASURE; ppdev->flipRecord.bFlipFlag = FALSE; ppdev->flipRecord.fpFlipFrom = 0;
// We need the refresh rate in Hz to query the S3 miniport about the
// streams parameters:
EngQueryPerformanceFrequency(&liFrequency);
DISPDBG((1, "Frequency %li.%03li Hz", (ULONG) (EngQueryPerformanceFrequency(&li), li / ppdev->flipRecord.liFlipDuration), (ULONG) (EngQueryPerformanceFrequency(&li), ((li * 1000) / ppdev->flipRecord.liFlipDuration) % 1000))); }
/******************************Public*Routine******************************\
* HRESULT ddrvalUpdateFlipStatus * * Checks and sees if the most recent flip has occurred. * * Unfortunately, the hardware has no ability to tell us whether a vertical * retrace has occured since the flip command was given other than by * sampling the vertical-blank-active and display-active status bits. * \**************************************************************************/
HRESULT ddrvalUpdateFlipStatus( PDEV* ppdev, FLATPTR fpVidMem) { BYTE* pjBase; LONGLONG liTime;
pjBase = ppdev->pjBase;
if ((ppdev->flipRecord.bFlipFlag) && ((fpVidMem == (FLATPTR) -1) || (fpVidMem == ppdev->flipRecord.fpFlipFrom))) { if (VBLANK_IS_ACTIVE(pjBase)) { if (ppdev->flipRecord.bWasEverInDisplay) { ppdev->flipRecord.bHaveEverCrossedVBlank = TRUE; } } else if (DISPLAY_IS_ACTIVE(pjBase)) { if (ppdev->flipRecord.bHaveEverCrossedVBlank) { ppdev->flipRecord.bFlipFlag = FALSE; return(DD_OK); } ppdev->flipRecord.bWasEverInDisplay = TRUE; }
// It's pretty unlikely that we'll happen to sample the vertical-
// blank-active at the first vertical blank after the flip command
// has been given. So to provide better results, we also check the
// time elapsed since the flip. If it's more than the duration of
// one entire refresh of the display, then we know for sure it has
// happened:
EngQueryPerformanceCounter(&liTime);
if (liTime - ppdev->flipRecord.liFlipTime <= ppdev->flipRecord.liFlipDuration) { return(DDERR_WASSTILLDRAWING); }
ppdev->flipRecord.bFlipFlag = FALSE; }
return(DD_OK); }
/******************************Public*Routine******************************\
* DWORD DdMapMemory * * This is a new DDI call specific to Windows NT that is used to map * or unmap all the application modifiable portions of the frame buffer * into the specified process's address space. * \**************************************************************************/
DWORD DdMapMemory( PDD_MAPMEMORYDATA lpMapMemory) { PDEV* ppdev;
ppdev = (PDEV*) lpMapMemory->lpDD->dhpdev;
// By returning DDHAL_DRIVER_NOTHANDLED and setting 'bMap' to -1, we
// have GDI take care of mapping the section that is our 'shadow buffer'
// directly into the application's address space. We tell GDI our kernel
// mode address by sticking it in 'fpProcess':
lpMapMemory->fpProcess = (FLATPTR) ppdev->pjScreen; lpMapMemory->bMap = (BOOL) -1;
return(DDHAL_DRIVER_NOTHANDLED); }
/******************************Public*Routine******************************\
* DWORD DdWaitForVerticalBlank * * 3-Dec-1995 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/
DWORD DdWaitForVerticalBlank( PDD_WAITFORVERTICALBLANKDATA lpWaitForVerticalBlank) { PDEV* ppdev; BYTE* pjBase;
ppdev = (PDEV*) lpWaitForVerticalBlank->lpDD->dhpdev; pjBase = ppdev->pjBase;
lpWaitForVerticalBlank->ddRVal = DD_OK;
switch (lpWaitForVerticalBlank->dwFlags) { case DDWAITVB_I_TESTVB: lpWaitForVerticalBlank->bIsInVB = (VBLANK_IS_ACTIVE(pjBase) != 0); break;
case DDWAITVB_BLOCKBEGIN: while (VBLANK_IS_ACTIVE(pjBase)) ; while (!VBLANK_IS_ACTIVE(pjBase)) ; break;
case DDWAITVB_BLOCKEND: while (!VBLANK_IS_ACTIVE(pjBase)) ; while (VBLANK_IS_ACTIVE(pjBase)) ; break; }
return(DDHAL_DRIVER_HANDLED); }
/******************************Public*Routine******************************\
* DWORD DdLock * \**************************************************************************/
DWORD DdLock( PDD_LOCKDATA lpLock) { PDEV* ppdev; DD_SURFACE_LOCAL* lpSurfaceLocal;
ppdev = (PDEV*) lpLock->lpDD->dhpdev; lpSurfaceLocal = lpLock->lpDDSurface;
if (lpSurfaceLocal->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE) { // If the application is locking the currently visible flip
// surface, remember the bounds of its lock so that we can
// use it at Unlock time to update the physical display:
ppdev->cLocks++;
if ((ppdev->cLocks == 1) && (lpLock->bHasRect)) { ppdev->rclLock = lpLock->rArea; } else { // If we were real keen, we would union the new area with
// the old. But we're not:
ppdev->rclLock.top = 0; ppdev->rclLock.left = 0; ppdev->rclLock.right = ppdev->cxScreen; ppdev->rclLock.bottom = ppdev->cyScreen; } }
return(DDHAL_DRIVER_NOTHANDLED); }
/******************************Public*Routine******************************\
* DWORD DdUnlock * * 3-Dec-1995 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/
DWORD DdUnlock( PDD_UNLOCKDATA lpUnlock) { PDEV* ppdev; DD_SURFACE_LOCAL* lpSurfaceLocal;
ppdev = (PDEV*) lpUnlock->lpDD->dhpdev; lpSurfaceLocal = lpUnlock->lpDDSurface;
// If this flip buffer is visible, then we have to update the physical
// screen with the shadow contents.
if (lpSurfaceLocal->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE) { vUpdate(ppdev, &ppdev->rclLock, NULL);
ppdev->cLocks--;
ASSERTDD(ppdev->cLocks >= 0, "Invalid lock count"); }
return(DDHAL_DRIVER_NOTHANDLED); }
/******************************Public*Routine******************************\
* DWORD DdFlip * * 3-Dec-1995 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/
DWORD DdFlip( PDD_FLIPDATA lpFlip) { PDEV* ppdev; BYTE* pjBase; HRESULT ddrval; ULONG cDwordsPerPlane; BYTE* pjSourceStart; BYTE* pjDestinationStart; BYTE* pjSource; BYTE* pjDestination; LONG iPage; LONG i; ULONG ul; FLATPTR fpVidMem;
ppdev = (PDEV*) lpFlip->lpDD->dhpdev; pjBase = ppdev->pjBase;
// Is the current flip still in progress?
//
// Don't want a flip to work until after the last flip is done,
// so we ask for the general flip status and ignore the vmem.
ddrval = ddrvalUpdateFlipStatus(ppdev, (FLATPTR) -1); if (ddrval != DD_OK) { lpFlip->ddRVal = DDERR_WASSTILLDRAWING; return(DDHAL_DRIVER_HANDLED); }
// Make the following page the current back-buffer. We always flip
// between three pages, so watch for our limit:
ppdev->cjVgaOffset += ppdev->cjVgaPageSize; if (++ppdev->iVgaPage == ppdev->cVgaPages) { ppdev->iVgaPage = 0; ppdev->cjVgaOffset = 0; }
// Copy from the DIB surface to the current VGA back-buffer. We have
// to convert to planar format on the way:
pjDestinationStart = ppdev->pjVga + ppdev->cjVgaOffset; fpVidMem = lpFlip->lpSurfTarg->lpGbl->fpVidMem; pjSourceStart = ppdev->pjScreen + fpVidMem; cDwordsPerPlane = ppdev->cDwordsPerPlane;
// Remember what DirectDraw surface is currently 'visible':
ppdev->fpScreenOffset = fpVidMem;
// Now do the blt!
WRITE_PORT_UCHAR(pjBase + VGA_BASE + SEQ_ADDR, SEQ_MAP_MASK);
for (iPage = 0; iPage < 4; iPage++, pjSourceStart++) { WRITE_PORT_UCHAR(pjBase + VGA_BASE + SEQ_DATA, 1 << iPage);
#if defined(_X86_)
_asm { mov esi,pjSourceStart mov edi,pjDestinationStart mov ecx,cDwordsPerPlane
PixelLoop: mov al,[esi+8] mov ah,[esi+12] shl eax,16 mov al,[esi] mov ah,[esi+4]
mov [edi],eax add edi,4 add esi,16
dec ecx jnz PixelLoop }
#else
pjSource = pjSourceStart; pjDestination = pjDestinationStart;
for (i = cDwordsPerPlane; i != 0; i--) { ul = (*(pjSource)) | (*(pjSource + 4) << 8) | (*(pjSource + 8) << 16) | (*(pjSource + 12) << 24);
WRITE_REGISTER_ULONG((ULONG*) pjDestination, ul);
pjDestination += 4; pjSource += 16; }
#endif
}
// Now flip to the page we just updated:
WRITE_PORT_USHORT((USHORT*) (pjBase + VGA_BASE + CRTC_ADDR), (USHORT) ((ppdev->cjVgaOffset) & 0xff00) | START_ADDRESS_HIGH);
// Remember where and when we were when we did the flip:
EngQueryPerformanceCounter(&ppdev->flipRecord.liFlipTime);
ppdev->flipRecord.bFlipFlag = TRUE; ppdev->flipRecord.bHaveEverCrossedVBlank = FALSE; ppdev->flipRecord.bWasEverInDisplay = FALSE;
ppdev->flipRecord.fpFlipFrom = lpFlip->lpSurfCurr->lpGbl->fpVidMem;
lpFlip->ddRVal = DD_OK; return(DDHAL_DRIVER_HANDLED); }
/******************************Public*Routine******************************\
* BOOL DrvGetDirectDrawInfo * * 3-Dec-1995 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/
BOOL DrvGetDirectDrawInfo( DHPDEV dhpdev, DD_HALINFO* pHalInfo, DWORD* pdwNumHeaps, VIDEOMEMORY* pvmList, // Will be NULL on first call
DWORD* pdwNumFourCC, DWORD* pdwFourCC) // Will be NULL on first call
{ PDEV* ppdev;
ppdev = (PDEV*) dhpdev;
pHalInfo->dwSize = sizeof(*pHalInfo);
// Current primary surface attributes. Since HalInfo is zero-initialized
// by GDI, we only have to fill in the fields which should be non-zero:
pHalInfo->vmiData.dwDisplayWidth = ppdev->cxScreen; pHalInfo->vmiData.dwDisplayHeight = ppdev->cyScreen; pHalInfo->vmiData.lDisplayPitch = ppdev->lScreenDelta; pHalInfo->vmiData.pvPrimary = ppdev->pjScreen;
pHalInfo->vmiData.ddpfDisplay.dwSize = sizeof(DDPIXELFORMAT); pHalInfo->vmiData.ddpfDisplay.dwFlags = DDPF_RGB | DDPF_PALETTEINDEXED8;
pHalInfo->vmiData.ddpfDisplay.dwRGBBitCount = 8;
// These masks will be zero at 8bpp:
pHalInfo->vmiData.ddpfDisplay.dwRBitMask = 0; pHalInfo->vmiData.ddpfDisplay.dwGBitMask = 0; pHalInfo->vmiData.ddpfDisplay.dwBBitMask = 0; pHalInfo->vmiData.ddpfDisplay.dwRGBAlphaBitMask = 0;
*pdwNumHeaps = 0; if (ppdev->cyMemory != ppdev->cyScreen) { *pdwNumHeaps = 1; if (pvmList != NULL) { pvmList->dwFlags = VIDMEM_ISRECTANGULAR; pvmList->fpStart = ppdev->cyScreen * ppdev->lScreenDelta; pvmList->dwWidth = ppdev->lScreenDelta; pvmList->dwHeight = ppdev->cyMemory - ppdev->cyScreen; pvmList->ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; } }
// Capabilities supported:
pHalInfo->ddCaps.dwFXCaps = 0; pHalInfo->ddCaps.dwCaps = 0; pHalInfo->ddCaps.dwCKeyCaps = 0; pHalInfo->ddCaps.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP;
// Required alignments of the scan lines for each kind of memory:
pHalInfo->vmiData.dwOffscreenAlign = 4;
// FourCCs supported:
*pdwNumFourCC = 0;
return(TRUE); }
/******************************Public*Routine******************************\
* BOOL DrvEnableDirectDraw * * 3-Dec-1995 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/
BOOL DrvEnableDirectDraw( DHPDEV dhpdev, DD_CALLBACKS* pCallBacks, DD_SURFACECALLBACKS* pSurfaceCallBacks, DD_PALETTECALLBACKS* pPaletteCallBacks) { pCallBacks->WaitForVerticalBlank = DdWaitForVerticalBlank; pCallBacks->MapMemory = DdMapMemory; pCallBacks->dwFlags = DDHAL_CB32_WAITFORVERTICALBLANK | DDHAL_CB32_MAPMEMORY;
pSurfaceCallBacks->Flip = DdFlip; pSurfaceCallBacks->Lock = DdLock; pSurfaceCallBacks->Unlock = DdUnlock; pSurfaceCallBacks->dwFlags = DDHAL_SURFCB32_FLIP | DDHAL_SURFCB32_LOCK | DDHAL_SURFCB32_UNLOCK;
return(TRUE); }
/******************************Public*Routine******************************\
* VOID DrvDisableDirectDraw * * 3-Dec-1995 -by- J. Andrew Goossen [andrewgo] * Wrote it. \**************************************************************************/
VOID DrvDisableDirectDraw( DHPDEV dhpdev) { }
/******************************Public*Routine******************************\
* BOOL bEnableDirectDraw * * This function is called by enable.c when the mode is first initialized, * right after the miniport does the mode-set. * \**************************************************************************/
BOOL bEnableDirectDraw( PDEV* ppdev) { // Calculate the total number of dwords per plane for flipping:
ppdev->cDwordsPerPlane = (ppdev->cyScreen * ppdev->lVgaDelta) >> 2;
// We only program the high byte of the VGA offset, so the page size must
// be a multiple of 256:
ppdev->cjVgaPageSize = ((ppdev->cyScreen * ppdev->lVgaDelta) + 255) & ~255;
// VGAs can address only 64k of memory, so that limits the number of
// page-flip buffers we can have:
ppdev->cVgaPages = 64 * 1024 / ppdev->cjVgaPageSize;
// Accurately measure the refresh rate for later:
vGetDisplayDuration(ppdev);
return(TRUE); }
/******************************Public*Routine******************************\
* VOID vAssertModeDirectDraw * * This function is called by enable.c when entering or leaving the * DOS full-screen character mode. * \**************************************************************************/
VOID vAssertModeDirectDraw( PDEV* ppdev, BOOL bEnable) { }
/******************************Public*Routine******************************\
* VOID vDisableDirectDraw * * This function is called by enable.c when the driver is shutting down. * \**************************************************************************/
VOID vDisableDirectDraw( PDEV* ppdev) { ASSERTDD(ppdev->cLocks == 0, "Invalid lock count"); }
|