mirror of
https://github.com/VirtualDrivers/Virtual-Display-Driver.git
synced 2026-10-21 22:22:27 -05:00
557 lines
16 KiB
C++
557 lines
16 KiB
C++
/******************************Module*Header*******************************\
|
|
* Module Name: blthw.cxx
|
|
*
|
|
* Sample display driver functions for a HW blt simulation. This file is
|
|
* only provided to simulate how a real hardware-accelerated display-only
|
|
* driver functions, and should not be used in a real driver.
|
|
*
|
|
* Copyright (c) 2011 Microsoft Corporation
|
|
\**************************************************************************/
|
|
|
|
#include "BDD.hxx"
|
|
|
|
typedef struct
|
|
{
|
|
CONST DXGKRNL_INTERFACE* DxgkInterface;
|
|
DXGKARGCB_NOTIFY_INTERRUPT_DATA NotifyInterrupt;
|
|
} SYNC_NOTIFY_INTERRUPT;
|
|
|
|
KSYNCHRONIZE_ROUTINE SynchronizeVidSchNotifyInterrupt;
|
|
|
|
BOOLEAN SynchronizeVidSchNotifyInterrupt(_In_opt_ PVOID params)
|
|
{
|
|
// This routine is non-paged code called at the device interrupt level (DIRQL)
|
|
// to notify VidSch and schedule a DPC. It is meant as a demonstration of handling
|
|
// a real hardware interrupt, even though it is actually called from asynchronous
|
|
// present worker threads in this sample.
|
|
SYNC_NOTIFY_INTERRUPT* pParam = reinterpret_cast<SYNC_NOTIFY_INTERRUPT*>(params);
|
|
|
|
// The context is known to be non-NULL
|
|
__analysis_assume(pParam != NULL);
|
|
|
|
// Update driver information related to fences
|
|
switch(pParam->NotifyInterrupt.InterruptType)
|
|
{
|
|
case DXGK_INTERRUPT_DISPLAYONLY_VSYNC:
|
|
case DXGK_INTERRUPT_DISPLAYONLY_PRESENT_PROGRESS:
|
|
break;
|
|
default:
|
|
NT_ASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Callback OS to report about the interrupt
|
|
pParam->DxgkInterface->DxgkCbNotifyInterrupt(pParam->DxgkInterface->DeviceHandle,&pParam->NotifyInterrupt);
|
|
|
|
// Now queue a DPC for this interrupt (to callback schedule at DCP level and let it do more work there)
|
|
// DxgkCbQueueDpc can return FALSE if there is already a DPC queued
|
|
// this is an acceptable condition
|
|
pParam->DxgkInterface->DxgkCbQueueDpc(pParam->DxgkInterface->DeviceHandle);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#pragma code_seg("PAGE")
|
|
|
|
KSTART_ROUTINE HwContextWorkerThread;
|
|
|
|
struct DoPresentMemory
|
|
{
|
|
PVOID DstAddr;
|
|
UINT DstStride;
|
|
ULONG DstBitPerPixel;
|
|
UINT SrcWidth;
|
|
UINT SrcHeight;
|
|
BYTE* SrcAddr;
|
|
LONG SrcPitch;
|
|
ULONG NumMoves; // in: Number of screen to screen moves
|
|
D3DKMT_MOVE_RECT* Moves; // in: Point to the list of moves
|
|
ULONG NumDirtyRects; // in: Number of direct rects
|
|
RECT* DirtyRect; // in: Point to the list of dirty rects
|
|
D3DKMDT_VIDPN_PRESENT_PATH_ROTATION Rotation;
|
|
BOOLEAN SynchExecution;
|
|
D3DDDI_VIDEO_PRESENT_SOURCE_ID SourceID;
|
|
HANDLE hAdapter;
|
|
PMDL Mdl;
|
|
BDD_HWBLT* DisplaySource;
|
|
};
|
|
|
|
void
|
|
HwExecutePresentDisplayOnly(
|
|
HANDLE Context);
|
|
|
|
NTSTATUS
|
|
StartHwBltPresentWorkerThread(
|
|
_In_ PKSTART_ROUTINE StartRoutine,
|
|
_In_ _When_(return==0, __drv_aliasesMem) PVOID StartContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates the worker thread to execute a single present
|
|
command. Creating a new thread on every asynchronous present is not
|
|
efficient, but this file is only meant as a simulation, not an example
|
|
of implementation.
|
|
|
|
Arguments:
|
|
|
|
StartRoutine - start routine
|
|
StartContext - start context
|
|
|
|
Return Value:
|
|
|
|
Status
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
InitializeObjectAttributes(&ObjectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
HANDLE hWorkerThread = NULL;
|
|
|
|
// copy data from the context which is need here, as it will be deleted in separate thread
|
|
DoPresentMemory* ctx = reinterpret_cast<DoPresentMemory*>(StartContext);
|
|
BDD_HWBLT* displaySource = ctx->DisplaySource;
|
|
|
|
NTSTATUS Status = PsCreateSystemThread(
|
|
&hWorkerThread,
|
|
THREAD_ALL_ACCESS,
|
|
&ObjectAttributes,
|
|
NULL,
|
|
NULL,
|
|
StartRoutine,
|
|
StartContext);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
|
|
// wait for thread to start - infinite wait -
|
|
// need to make sure the tread is running before OS stars submitting the work items to it
|
|
KeWaitForSingleObject(&displaySource->m_hThreadStartupEvent, Executive, KernelMode, FALSE, NULL);
|
|
|
|
// Handle is passed to the parent object which must close it
|
|
displaySource->SetPresentWorkerThreadInfo(hWorkerThread);
|
|
|
|
// Resume context thread, this is done by setting the event the thread is waiting on
|
|
KeSetEvent(&displaySource->m_hThreadSuspendEvent, 0, FALSE);
|
|
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
BDD_HWBLT::BDD_HWBLT():m_DevExt (NULL),
|
|
m_SynchExecution(TRUE),
|
|
m_hPresentWorkerThread(NULL),
|
|
m_pPresentWorkerThread(NULL)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
KeInitializeEvent(&m_hThreadStartupEvent, NotificationEvent, FALSE);
|
|
KeInitializeEvent(&m_hThreadSuspendEvent, SynchronizationEvent, FALSE);
|
|
|
|
}
|
|
|
|
|
|
BDD_HWBLT::~BDD_HWBLT()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine waits on present worker thread to exit before
|
|
destroying the object
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
// make sure the worker thread has exited
|
|
SetPresentWorkerThreadInfo(NULL);
|
|
}
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable:26135) // The function doesn't lock anything
|
|
|
|
void
|
|
BDD_HWBLT::SetPresentWorkerThreadInfo(
|
|
HANDLE hWorkerThread)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The method is updating present worker information
|
|
It is called in following cases:
|
|
- In ExecutePresent to update worker thread information
|
|
- In Dtor to wait on worker thread to exit
|
|
|
|
Arguments:
|
|
|
|
hWorkerThread - handle of the present worker thread
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
if (m_pPresentWorkerThread)
|
|
{
|
|
// Wait for thread to exit
|
|
KeWaitForSingleObject(m_pPresentWorkerThread, Executive, KernelMode,
|
|
FALSE, NULL);
|
|
// Dereference thread object
|
|
ObDereferenceObject(m_pPresentWorkerThread);
|
|
m_pPresentWorkerThread = NULL;
|
|
|
|
NT_ASSERT(m_hPresentWorkerThread);
|
|
ZwClose(m_hPresentWorkerThread);
|
|
m_hPresentWorkerThread = NULL;
|
|
}
|
|
|
|
if (hWorkerThread)
|
|
{
|
|
// Make sure that thread's handle would be valid even if the thread exited
|
|
ObReferenceObjectByHandle(hWorkerThread, THREAD_ALL_ACCESS, NULL,
|
|
KernelMode, &m_pPresentWorkerThread, NULL);
|
|
NT_ASSERT(m_pPresentWorkerThread);
|
|
m_hPresentWorkerThread = hWorkerThread;
|
|
}
|
|
|
|
}
|
|
#pragma warning(pop)
|
|
|
|
NTSTATUS
|
|
BDD_HWBLT::ExecutePresentDisplayOnly(
|
|
_In_ BYTE* DstAddr,
|
|
_In_ UINT DstBitPerPixel,
|
|
_In_ BYTE* SrcAddr,
|
|
_In_ UINT SrcBytesPerPixel,
|
|
_In_ LONG SrcPitch,
|
|
_In_ ULONG NumMoves,
|
|
_In_ D3DKMT_MOVE_RECT* Moves,
|
|
_In_ ULONG NumDirtyRects,
|
|
_In_ RECT* DirtyRect,
|
|
_In_ D3DKMDT_VIDPN_PRESENT_PATH_ROTATION Rotation)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The method creates present worker thread and provides context
|
|
for it filled with present commands
|
|
|
|
Arguments:
|
|
|
|
DstAddr - address of destination surface
|
|
DstBitPerPixel - color depth of destination surface
|
|
SrcAddr - address of source surface
|
|
SrcBytesPerPixel - bytes per pixel of source surface
|
|
SrcPitch - source surface pitch (bytes in a row)
|
|
NumMoves - number of moves to be copied
|
|
Moves - moves' data
|
|
NumDirtyRects - number of rectangles to be copied
|
|
DirtyRect - rectangles' data
|
|
Rotation - roatation to be performed when executing copy
|
|
CallBack - callback for present worker thread to report execution status
|
|
|
|
Return Value:
|
|
|
|
Status
|
|
|
|
--*/
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
SIZE_T sizeMoves = NumMoves*sizeof(D3DKMT_MOVE_RECT);
|
|
SIZE_T sizeRects = NumDirtyRects*sizeof(RECT);
|
|
SIZE_T size = sizeof(DoPresentMemory) + sizeMoves + sizeRects;
|
|
|
|
DoPresentMemory* ctx = reinterpret_cast<DoPresentMemory*>
|
|
(new (PagedPool) BYTE[size]);
|
|
|
|
if (!ctx)
|
|
{
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
RtlZeroMemory(ctx,size);
|
|
|
|
const CURRENT_BDD_MODE* pModeCur = m_DevExt->GetCurrentMode(m_SourceId);
|
|
|
|
ctx->DstAddr = DstAddr;
|
|
ctx->DstBitPerPixel = DstBitPerPixel;
|
|
ctx->DstStride = pModeCur->DispInfo.Pitch;
|
|
ctx->SrcWidth = pModeCur->SrcModeWidth;
|
|
ctx->SrcHeight = pModeCur->SrcModeHeight;
|
|
ctx->SrcAddr = NULL;
|
|
ctx->SrcPitch = SrcPitch;
|
|
ctx->Rotation = Rotation;
|
|
ctx->NumMoves = NumMoves;
|
|
ctx->Moves = Moves;
|
|
ctx->NumDirtyRects = NumDirtyRects;
|
|
ctx->DirtyRect = DirtyRect;
|
|
ctx->SourceID = m_SourceId;
|
|
ctx->hAdapter = m_DevExt;
|
|
ctx->Mdl = NULL;
|
|
ctx->DisplaySource = this;
|
|
|
|
// Alternate between synch and asynch execution, for demonstrating
|
|
// that a real hardware implementation can do either
|
|
m_SynchExecution = !m_SynchExecution;
|
|
|
|
ctx->SynchExecution = m_SynchExecution;
|
|
|
|
{
|
|
// Map Source into kernel space, as Blt will be executed by system worker thread
|
|
UINT sizeToMap = SrcBytesPerPixel*pModeCur->SrcModeWidth*pModeCur->SrcModeHeight;
|
|
|
|
PMDL mdl = IoAllocateMdl((PVOID)SrcAddr, sizeToMap, FALSE, FALSE, NULL);
|
|
if(!mdl)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
KPROCESSOR_MODE AccessMode = static_cast<KPROCESSOR_MODE>(( SrcAddr <=
|
|
(BYTE* const) MM_USER_PROBE_ADDRESS)?UserMode:KernelMode);
|
|
__try
|
|
{
|
|
// Probe and lock the pages of this buffer in physical memory.
|
|
// We need only IoReadAccess.
|
|
MmProbeAndLockPages(mdl, AccessMode, IoReadAccess);
|
|
}
|
|
#pragma prefast(suppress: __WARNING_EXCEPTIONEXECUTEHANDLER, "try/except is only able to protect against user-mode errors and these are the only errors we try to catch here");
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
Status = GetExceptionCode();
|
|
IoFreeMdl(mdl);
|
|
return Status;
|
|
}
|
|
|
|
// Map the physical pages described by the MDL into system space.
|
|
// Note: double mapping the buffer this way causes lot of system
|
|
// overhead for large size buffers.
|
|
ctx->SrcAddr = reinterpret_cast<BYTE*>
|
|
(MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority ));
|
|
|
|
if(!ctx->SrcAddr) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
MmUnlockPages(mdl);
|
|
IoFreeMdl(mdl);
|
|
return Status;
|
|
}
|
|
|
|
// Save Mdl to unmap and unlock the pages in worker thread
|
|
ctx->Mdl = mdl;
|
|
}
|
|
|
|
BYTE* rects = reinterpret_cast<BYTE*>(ctx+1);
|
|
|
|
// copy moves and update pointer
|
|
if (Moves)
|
|
{
|
|
memcpy(rects,Moves,sizeMoves);
|
|
ctx->Moves = reinterpret_cast<D3DKMT_MOVE_RECT*>(rects);
|
|
rects += sizeMoves;
|
|
}
|
|
|
|
// copy dirty rects and update pointer
|
|
if (DirtyRect)
|
|
{
|
|
memcpy(rects,DirtyRect,sizeRects);
|
|
ctx->DirtyRect = reinterpret_cast<RECT*>(rects);
|
|
}
|
|
|
|
|
|
if (m_SynchExecution)
|
|
{
|
|
HwExecutePresentDisplayOnly((PVOID)ctx);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
// Create a worker thread to perform the present asynchronously
|
|
// Ctx will be deleted in worker thread (on exit)
|
|
return StartHwBltPresentWorkerThread(HwContextWorkerThread,(PVOID)ctx);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ReportPresentProgress(
|
|
_In_ HANDLE Adapter,
|
|
_In_ D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId,
|
|
_In_ BOOLEAN CompletedOrFailed)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine runs a fake interrupt routine in order to tell the OS about the present progress.
|
|
|
|
Arguments:
|
|
|
|
Adapter - Handle to the adapter (Device Extension)
|
|
VidPnSourceId - Video present source id for the callback
|
|
CompletedOrFailed - Present progress status for the source
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
BASIC_DISPLAY_DRIVER* pDevExt =
|
|
reinterpret_cast<BASIC_DISPLAY_DRIVER*>(Adapter);
|
|
|
|
SYNC_NOTIFY_INTERRUPT SyncNotifyInterrupt = {};
|
|
SyncNotifyInterrupt.DxgkInterface = pDevExt->GetDxgkInterface();
|
|
SyncNotifyInterrupt.NotifyInterrupt.InterruptType = DXGK_INTERRUPT_DISPLAYONLY_PRESENT_PROGRESS;
|
|
SyncNotifyInterrupt.NotifyInterrupt.DisplayOnlyPresentProgress.VidPnSourceId = VidPnSourceId;
|
|
|
|
SyncNotifyInterrupt.NotifyInterrupt.DisplayOnlyPresentProgress.ProgressId =
|
|
(CompletedOrFailed)?DXGK_PRESENT_DISPLAYONLY_PROGRESS_ID_COMPLETE:
|
|
DXGK_PRESENT_DISPLAYONLY_PROGRESS_ID_FAILED;
|
|
|
|
// Execute the SynchronizeVidSchNotifyInterrupt function at the interrupt
|
|
// IRQL in order to fake a real present progress interrupt
|
|
BOOLEAN bRet = FALSE;
|
|
NT_VERIFY(NT_SUCCESS(pDevExt->GetDxgkInterface()->DxgkCbSynchronizeExecution(
|
|
pDevExt->GetDxgkInterface()->DeviceHandle,
|
|
(PKSYNCHRONIZE_ROUTINE)SynchronizeVidSchNotifyInterrupt,
|
|
(PVOID)&SyncNotifyInterrupt,0,&bRet)));
|
|
NT_ASSERT(bRet);
|
|
}
|
|
|
|
|
|
void
|
|
HwContextWorkerThread(
|
|
HANDLE Context)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DoPresentMemory* ctx = reinterpret_cast<DoPresentMemory*>(Context);
|
|
BDD_HWBLT* displaySource = ctx->DisplaySource;
|
|
|
|
// Signal event to indicate that the tread has started
|
|
KeSetEvent(&displaySource->m_hThreadStartupEvent, 0, FALSE);
|
|
|
|
// Suspend context thread, do this by waiting on the suspend event
|
|
KeWaitForSingleObject(&displaySource->m_hThreadSuspendEvent, Executive, KernelMode, FALSE, NULL);
|
|
|
|
HwExecutePresentDisplayOnly(Context);
|
|
}
|
|
|
|
|
|
void
|
|
HwExecutePresentDisplayOnly(
|
|
HANDLE Context)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The routine executes present's commands and report progress to the OS
|
|
|
|
Arguments:
|
|
|
|
Context - Context with present's command
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DoPresentMemory* ctx = reinterpret_cast<DoPresentMemory*>(Context);
|
|
|
|
// Set up destination blt info
|
|
BLT_INFO DstBltInfo;
|
|
DstBltInfo.pBits = ctx->DstAddr;
|
|
DstBltInfo.Pitch = ctx->DstStride;
|
|
DstBltInfo.BitsPerPel = ctx->DstBitPerPixel;
|
|
DstBltInfo.Offset.x = 0;
|
|
DstBltInfo.Offset.y = 0;
|
|
DstBltInfo.Rotation = ctx->Rotation;
|
|
DstBltInfo.Width = ctx->SrcWidth;
|
|
DstBltInfo.Height = ctx->SrcHeight;
|
|
|
|
// Set up source blt info
|
|
BLT_INFO SrcBltInfo;
|
|
SrcBltInfo.pBits = ctx->SrcAddr;
|
|
SrcBltInfo.Pitch = ctx->SrcPitch;
|
|
SrcBltInfo.BitsPerPel = 32;
|
|
SrcBltInfo.Offset.x = 0;
|
|
SrcBltInfo.Offset.y = 0;
|
|
SrcBltInfo.Rotation = D3DKMDT_VPPR_IDENTITY;
|
|
if (ctx->Rotation == D3DKMDT_VPPR_ROTATE90 ||
|
|
ctx->Rotation == D3DKMDT_VPPR_ROTATE270)
|
|
{
|
|
SrcBltInfo.Width = DstBltInfo.Height;
|
|
SrcBltInfo.Height = DstBltInfo.Width;
|
|
}
|
|
else
|
|
{
|
|
SrcBltInfo.Width = DstBltInfo.Width;
|
|
SrcBltInfo.Height = DstBltInfo.Height;
|
|
}
|
|
|
|
|
|
// Copy all the scroll rects from source image to video frame buffer.
|
|
for (UINT i = 0; i < ctx->NumMoves; i++)
|
|
{
|
|
BltBits(&DstBltInfo,
|
|
&SrcBltInfo,
|
|
1, // NumRects
|
|
&ctx->Moves[i].DestRect);
|
|
}
|
|
|
|
// Copy all the dirty rects from source image to video frame buffer.
|
|
for (UINT i = 0; i < ctx->NumDirtyRects; i++)
|
|
{
|
|
|
|
BltBits(&DstBltInfo,
|
|
&SrcBltInfo,
|
|
1, // NumRects
|
|
&ctx->DirtyRect[i]);
|
|
}
|
|
|
|
// Unmap unmap and unlock the pages.
|
|
if (ctx->Mdl)
|
|
{
|
|
MmUnlockPages(ctx->Mdl);
|
|
IoFreeMdl(ctx->Mdl);
|
|
}
|
|
|
|
if(ctx->SynchExecution)
|
|
{
|
|
// This code simulates Blt executed synchronously
|
|
// nothing should be done here, just exit
|
|
;
|
|
}
|
|
else
|
|
{
|
|
// TRUE == completed
|
|
// This code is emulates interrupt which HW should generate
|
|
ReportPresentProgress(ctx->hAdapter,ctx->SourceID,TRUE);
|
|
}
|
|
|
|
delete [] reinterpret_cast<BYTE*>(ctx);
|
|
}
|