windows-nt/Source/XPSP1/NT/drivers/video/ms/modex/disp/ddraw.c
2020-09-26 16:20:57 +08:00

663 lines
20 KiB
C

/******************************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");
}