codekingpro/portable-devtools
114k
1/*
2* Copyright 2017-2024 NVIDIA Corporation. All rights reserved.
3*
4* Please refer to the NVIDIA end user license agreement (EULA) associated
5* with this source code for terms and conditions that govern your use of
6* this software. Any use, reproduction, disclosure, or distribution of
7* this software and related documentation outside the terms of the EULA
8* is strictly prohibited.
9*
10*/
11#pragma once
12
13#include <iostream>
14#include <mutex>
15#include <thread>
16#include <d3d11.h>
17#include <cuda.h>
18#include <cudaD3D11.h>
19#include "FramePresenterD3D.h"
20#include "../Utils/NvCodecUtils.h"
21
22/**
23* @brief D3D11 presenter class derived from FramePresenterD3D
24*/
25class FramePresenterD3D11 : public FramePresenterD3D
26{
27public:
28 /**
29 * @brief FramePresenterD3D11 constructor. This will launch a rendering thread which will be fed with decoded frames
30 * @param cuContext - CUDA context handle
31 * @param nWidth - Width of D3D surface
32 * @param nHeight - Height of D3D surface
33 */
34 FramePresenterD3D11(CUcontext cuContext, int nWidth, int nHeight) :
35 FramePresenterD3D(cuContext, nWidth, nHeight)
36 {
37 pthMsgLoop = new std::thread(ThreadProc, this);
38 while (!bReady) {
39 std::this_thread::sleep_for(std::chrono::milliseconds(1));
40 }
41 hTimerQueue = CreateTimerQueue();
42 hPresentEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
43 }
44
45 /**
46 * @brief FramePresenterD3D11 destructor.
47 */
48 ~FramePresenterD3D11() {
49 if (hTimerQueue)
50 {
51 DeleteTimerQueue(hTimerQueue);
52 }
53 if (hPresentEvent)
54 {
55 CloseHandle(hPresentEvent);
56 }
57 bQuit = true;
58 pthMsgLoop->join();
59 delete pthMsgLoop;
60 }
61
62 /**
63 * @brief Presents a frame present in host memory. More specifically, it copies the host surface
64 * data to a d3d staging texture and then copies it to the swap chain backbuffer for presentation
65 * @param pData - pointer to host surface data
66 * @param nBytes - number of bytes to copy
67 * @return true on success
68 * @return false when the windowing thread is not ready to be served
69 */
70 bool PresentHostFrame(BYTE *pData, int nBytes) {
71 mtx.lock();
72 if (!bReady) {
73 mtx.unlock();
74 return false;
75 }
76
77 D3D11_MAPPED_SUBRESOURCE mappedTexture;
78 ck(pContext->Map(pStagingTexture, 0, D3D11_MAP_WRITE, 0, &mappedTexture));
79 memcpy(mappedTexture.pData, pData, min(nWidth * nHeight * 4, nBytes));
80 pContext->Unmap(pStagingTexture, 0);
81 pContext->CopyResource(pBackBuffer, pStagingTexture);
82 ck(pSwapChain->Present(0, 0));
83 mtx.unlock();
84 return true;
85 }
86
87 bool PresentDeviceFrame(unsigned char *dpBgra, int nPitch, int64_t delay) {
88 mtx.lock();
89 if (!bReady) {
90 mtx.unlock();
91 return false;
92 }
93 CopyDeviceFrame(dpBgra, nPitch);
94 if (!CreateTimerQueueTimer(&hTimer, hTimerQueue,
95 (WAITORTIMERCALLBACK)PresentRoutine, this, (DWORD)delay, 0, 0))
96 {
97 std::cout << "Problem in createtimer" << std::endl;
98 }
99 while (WaitForSingleObject(hPresentEvent, 0) != WAIT_OBJECT_0)
100 {
101 }
102 if (hTimer)
103 {
104 DeleteTimerQueueTimer(hTimerQueue, hTimer, nullptr);
105 }
106 mtx.unlock();
107 return true;
108 }
109
110private:
111 /**
112 * @brief Launches the windowing functionality
113 * @param This - pointer to FramePresenterD3D11 object
114 */
115 static void ThreadProc(FramePresenterD3D11 *This) {
116 This->Run();
117 }
118 /**
119 * @brief Callback called by D3D runtime. This callback is registered during call to
120 * CreateTimerQueueTimer in PresentDeviceFrame. In CreateTimerQueueTimer we also
121 * set a timer. When this timer expires this callback is called. This functionality
122 * is present to facilitate timestamp based presentation.
123 * @param lpParam - void pointer to client data
124 * @param TimerOrWaitFired - TRUE for this callback as this is a Timer based callback (Refer:https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms687066(v=vs.85))
125 */
126 static VOID CALLBACK PresentRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired)
127 {
128 if (!lpParam) return;
129 FramePresenterD3D11* presenter = (FramePresenterD3D11 *)lpParam;
130 presenter->pSwapChain->Present(1, 0);
131 SetEvent(presenter->hPresentEvent);
132 }
133
134 /**
135 * @brief This function is on a thread spawned during FramePresenterD3D11 construction.
136 * It creates the D3D window and monitors window messages in a loop. This function
137 * also creates swap chain for presentation and also registers the swap chain backbuffer
138 * with cuda.
139 */
140 void Run() {
141 HWND hwndMain = CreateAndShowWindow(nWidth, nHeight);
142
143 DXGI_SWAP_CHAIN_DESC sc = { 0 };
144 sc.BufferCount = 1;
145 sc.BufferDesc.Width = nWidth;
146 sc.BufferDesc.Height = nHeight;
147 sc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
148 sc.BufferDesc.RefreshRate.Numerator = 0;
149 sc.BufferDesc.RefreshRate.Denominator = 1;
150 sc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
151 sc.OutputWindow = hwndMain;
152 sc.SampleDesc.Count = 1;
153 sc.SampleDesc.Quality = 0;
154 sc.Windowed = TRUE;
155
156 ID3D11Device *pDevice = NULL;
157 ck(D3D11CreateDeviceAndSwapChain(GetAdapterByContext(cuContext), D3D_DRIVER_TYPE_UNKNOWN,
158 NULL, 0, NULL, 0, D3D11_SDK_VERSION, &sc, &pSwapChain, &pDevice, NULL, &pContext));
159 ck(pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer));
160
161 D3D11_TEXTURE2D_DESC td;
162 pBackBuffer->GetDesc(&td);
163 td.BindFlags = 0;
164 td.Usage = D3D11_USAGE_STAGING;
165 td.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
166 ck(pDevice->CreateTexture2D(&td, NULL, &pStagingTexture));
167
168 ck(cuCtxPushCurrent(cuContext));
169 ck(cuGraphicsD3D11RegisterResource(&cuResource, pBackBuffer, CU_GRAPHICS_REGISTER_FLAGS_NONE));
170 ck(cuGraphicsResourceSetMapFlags(cuResource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_WRITE_DISCARD));
171 ck(cuCtxPopCurrent(NULL));
172
173 bReady = true;
174 MSG msg = { 0 };
175 while (!bQuit && msg.message != WM_QUIT) {
176 if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
177 TranslateMessage(&msg);
178 DispatchMessage(&msg);
179 }
180 }
181
182 mtx.lock();
183 bReady = false;
184 ck(cuCtxPushCurrent(cuContext));
185 ck(cuGraphicsUnregisterResource(cuResource));
186 ck(cuCtxPopCurrent(NULL));
187 pStagingTexture->Release();
188 pBackBuffer->Release();
189 pContext->Release();
190 pDevice->Release();
191 pSwapChain->Release();
192 DestroyWindow(hwndMain);
193 mtx.unlock();
194 }
195
196 /**
197 * @brief Gets the DXGI adapter on which the given cuda context is current
198 * @param CUcontext - handle to cuda context
199 * @return pAdapter - pointer to DXGI adapter
200 * @return NULL - In case there is no adapter corresponding to the supplied cuda context
201 */
202 static IDXGIAdapter *GetAdapterByContext(CUcontext cuContext) {
203 CUdevice cuDeviceTarget;
204 ck(cuCtxPushCurrent(cuContext));
205 ck(cuCtxGetDevice(&cuDeviceTarget));
206 ck(cuCtxPopCurrent(NULL));
207
208 IDXGIFactory1 *pFactory = NULL;
209 ck(CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void **)&pFactory));
210 IDXGIAdapter *pAdapter = NULL;
211 for (unsigned i = 0; pFactory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; i++) {
212 CUdevice cuDevice;
213 ck(cuD3D11GetDevice(&cuDevice, pAdapter));
214 if (cuDevice == cuDeviceTarget) {
215 pFactory->Release();
216 return pAdapter;
217 }
218 pAdapter->Release();
219 }
220 pFactory->Release();
221 return NULL;
222 }
223
224private:
225 bool bReady = false;
226 bool bQuit = false;
227 std::mutex mtx;
228 std::thread *pthMsgLoop = NULL;
229
230 IDXGISwapChain *pSwapChain = NULL;
231 ID3D11DeviceContext *pContext = NULL;
232 ID3D11Texture2D *pBackBuffer = NULL, *pStagingTexture = NULL;
233 HANDLE hTimer;
234 HANDLE hTimerQueue;
235 HANDLE hPresentEvent;
236};
237 