d3d11_interop.cpp 16.5 KB
Newer Older
1 2 3 4 5 6 7 8
/*
// Sample demonstrating interoperability of OpenCV UMat with Direct X surface
// At first, the data obtained from video file or camera and
// placed onto Direct X surface,
// following mapping of this Direct X surface to OpenCV UMat and call cv::Blur
// function. The result is mapped back to Direct X surface and rendered through
// Direct X API.
*/
9
#define WIN32_LEAN_AND_MEAN
10 11 12
#include <windows.h>
#include <d3d11.h>

13 14 15 16 17 18 19
#include "opencv2/core.hpp"
#include "opencv2/core/directx.hpp"
#include "opencv2/core/ocl.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"

#include "d3dsample.hpp"
20

21
#pragma comment (lib, "d3d11.lib")
22 23


24 25 26 27
using namespace std;
using namespace cv;

class D3D11WinApp : public D3DSample
28
{
29
public:
30 31 32 33
    D3D11WinApp(int width, int height, std::string& window_name, cv::VideoCapture& cap)
    : D3DSample(width, height, window_name, cap),
      m_nv12_available(false)
    {}
34 35

    ~D3D11WinApp() {}
36 37


38
    int create(void)
39
    {
40 41
        // base initialization
        D3DSample::create();
42

43 44
        // initialize DirectX
        HRESULT r;
45

46
        DXGI_SWAP_CHAIN_DESC scd;
47

48
        ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));
49

50 51 52 53 54 55 56 57 58 59 60 61
        scd.BufferCount       = 1;                               // one back buffer
        scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;      // use 32-bit color
        scd.BufferDesc.Width  = m_width;                         // set the back buffer width
        scd.BufferDesc.Height = m_height;                        // set the back buffer height
        scd.BufferUsage       = DXGI_USAGE_RENDER_TARGET_OUTPUT; // how swap chain is to be used
        scd.OutputWindow      = m_hWnd;                          // the window to be used
        scd.SampleDesc.Count  = 1;                               // how many multisamples
        scd.Windowed          = TRUE;                            // windowed/full-screen mode
        scd.SwapEffect        = DXGI_SWAP_EFFECT_DISCARD;
        scd.Flags             = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // allow full-screen switching

        r = ::D3D11CreateDeviceAndSwapChain(
Vladimir Dudnik's avatar
Vladimir Dudnik committed
62 63 64 65 66 67 68 69 70 71 72 73
            NULL,
            D3D_DRIVER_TYPE_HARDWARE,
            NULL,
            0,
            NULL,
            0,
            D3D11_SDK_VERSION,
            &scd,
            &m_pD3D11SwapChain,
            &m_pD3D11Dev,
            NULL,
            &m_pD3D11Ctx);
74 75
        if (FAILED(r))
        {
76
            throw std::runtime_error("D3D11CreateDeviceAndSwapChain() failed!");
77 78
        }

79
#if defined(_WIN32_WINNT_WIN8) && _WIN32_WINNT >= _WIN32_WINNT_WIN8
Vladimir Dudnik's avatar
Vladimir Dudnik committed
80 81
        UINT fmt = 0;
        r = m_pD3D11Dev->CheckFormatSupport(DXGI_FORMAT_NV12, &fmt);
82
        if (SUCCEEDED(r))
Vladimir Dudnik's avatar
Vladimir Dudnik committed
83
        {
84
            m_nv12_available = true;
Vladimir Dudnik's avatar
Vladimir Dudnik committed
85
        }
86
#endif
Vladimir Dudnik's avatar
Vladimir Dudnik committed
87

88 89 90
        r = m_pD3D11SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&m_pBackBuffer);
        if (FAILED(r))
        {
91
            throw std::runtime_error("GetBufer() failed!");
92 93 94 95
        }

        r = m_pD3D11Dev->CreateRenderTargetView(m_pBackBuffer, NULL, &m_pRenderTarget);
        if (FAILED(r))
96
        {
97
            throw std::runtime_error("CreateRenderTargetView() failed!");
98 99
        }

100 101 102 103 104 105 106 107 108 109 110
        m_pD3D11Ctx->OMSetRenderTargets(1, &m_pRenderTarget, NULL);

        D3D11_VIEWPORT viewport;
        ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

        viewport.Width    = (float)m_width;
        viewport.Height   = (float)m_height;
        viewport.MinDepth = 0.0f;
        viewport.MaxDepth = 0.0f;

        m_pD3D11Ctx->RSSetViewports(1, &viewport);
111

Vladimir Dudnik's avatar
Vladimir Dudnik committed
112 113
        m_pSurfaceRGBA = 0;
        m_pSurfaceNV12 = 0;
114
        m_pSurfaceNV12_cpu_copy = 0;
Vladimir Dudnik's avatar
Vladimir Dudnik committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

        D3D11_TEXTURE2D_DESC desc_rgba;

        desc_rgba.Width              = m_width;
        desc_rgba.Height             = m_height;
        desc_rgba.MipLevels          = 1;
        desc_rgba.ArraySize          = 1;
        desc_rgba.Format             = DXGI_FORMAT_R8G8B8A8_UNORM;
        desc_rgba.SampleDesc.Count   = 1;
        desc_rgba.SampleDesc.Quality = 0;
        desc_rgba.BindFlags          = D3D11_BIND_SHADER_RESOURCE;
        desc_rgba.Usage              = D3D11_USAGE_DYNAMIC;
        desc_rgba.CPUAccessFlags     = D3D11_CPU_ACCESS_WRITE;
        desc_rgba.MiscFlags          = 0;

        r = m_pD3D11Dev->CreateTexture2D(&desc_rgba, 0, &m_pSurfaceRGBA);
131
        if (FAILED(r))
132
        {
Vladimir Dudnik's avatar
Vladimir Dudnik committed
133 134 135
            throw std::runtime_error("Can't create DX texture");
        }

136
#if defined(_WIN32_WINNT_WIN8) && _WIN32_WINNT >= _WIN32_WINNT_WIN8
Vladimir Dudnik's avatar
Vladimir Dudnik committed
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
        if(m_nv12_available)
        {
            D3D11_TEXTURE2D_DESC desc_nv12;

            desc_nv12.Width              = m_width;
            desc_nv12.Height             = m_height;
            desc_nv12.MipLevels          = 1;
            desc_nv12.ArraySize          = 1;
            desc_nv12.Format             = DXGI_FORMAT_NV12;
            desc_nv12.SampleDesc.Count   = 1;
            desc_nv12.SampleDesc.Quality = 0;
            desc_nv12.BindFlags          = D3D11_BIND_SHADER_RESOURCE;
            desc_nv12.Usage              = D3D11_USAGE_DEFAULT;
            desc_nv12.CPUAccessFlags     = 0;
            desc_nv12.MiscFlags          = D3D11_RESOURCE_MISC_SHARED;

            r = m_pD3D11Dev->CreateTexture2D(&desc_nv12, 0, &m_pSurfaceNV12);
            if (FAILED(r))
            {
                throw std::runtime_error("Can't create DX NV12 texture");
            }

            D3D11_TEXTURE2D_DESC desc_nv12_cpu_copy;

            desc_nv12_cpu_copy.Width              = m_width;
            desc_nv12_cpu_copy.Height             = m_height;
            desc_nv12_cpu_copy.MipLevels          = 1;
            desc_nv12_cpu_copy.ArraySize          = 1;
            desc_nv12_cpu_copy.Format             = DXGI_FORMAT_NV12;
            desc_nv12_cpu_copy.SampleDesc.Count   = 1;
            desc_nv12_cpu_copy.SampleDesc.Quality = 0;
            desc_nv12_cpu_copy.BindFlags          = 0;
            desc_nv12_cpu_copy.Usage              = D3D11_USAGE_STAGING;
170
            desc_nv12_cpu_copy.CPUAccessFlags     = /*D3D11_CPU_ACCESS_WRITE | */D3D11_CPU_ACCESS_READ;
Vladimir Dudnik's avatar
Vladimir Dudnik committed
171 172 173 174 175 176 177
            desc_nv12_cpu_copy.MiscFlags          = 0;

            r = m_pD3D11Dev->CreateTexture2D(&desc_nv12_cpu_copy, 0, &m_pSurfaceNV12_cpu_copy);
            if (FAILED(r))
            {
                throw std::runtime_error("Can't create DX NV12 texture");
            }
178
        }
179
#endif
180

181 182 183 184 185
        // initialize OpenCL context of OpenCV lib from DirectX
        if (cv::ocl::haveOpenCL())
        {
            m_oclCtx = cv::directx::ocl::initializeContextFromD3D11Device(m_pD3D11Dev);
        }
186

187 188 189
        m_oclDevName = cv::ocl::useOpenCL() ?
            cv::ocl::Context::getDefault().device(0).name() :
            "No OpenCL device";
190

191 192
        return 0;
    } // create()
193 194


195
    // get media data on DX surface for further processing
Vladimir Dudnik's avatar
Vladimir Dudnik committed
196
    int get_surface(ID3D11Texture2D** ppSurface, bool use_nv12)
197 198 199 200
    {
        HRESULT r;

        if (!m_cap.read(m_frame_bgr))
Vladimir Dudnik's avatar
Vladimir Dudnik committed
201
            return -1;
202

Vladimir Dudnik's avatar
Vladimir Dudnik committed
203 204 205
        if (use_nv12)
        {
            cv::cvtColor(m_frame_bgr, m_frame_i420, CV_BGR2YUV_I420);
206

Vladimir Dudnik's avatar
Vladimir Dudnik committed
207
            convert_I420_to_NV12(m_frame_i420, m_frame_nv12, m_width, m_height);
208

Vladimir Dudnik's avatar
Vladimir Dudnik committed
209
            m_pD3D11Ctx->UpdateSubresource(m_pSurfaceNV12, 0, 0, m_frame_nv12.data, (UINT)m_frame_nv12.step[0], (UINT)m_frame_nv12.total());
210
        }
Vladimir Dudnik's avatar
Vladimir Dudnik committed
211 212 213 214 215 216 217 218 219 220 221 222 223
        else
        {
            cv::cvtColor(m_frame_bgr, m_frame_rgba, CV_BGR2RGBA);

            // process video frame on CPU
            UINT subResource = ::D3D11CalcSubresource(0, 0, 1);

            D3D11_MAPPED_SUBRESOURCE mappedTex;
            r = m_pD3D11Ctx->Map(m_pSurfaceRGBA, subResource, D3D11_MAP_WRITE_DISCARD, 0, &mappedTex);
            if (FAILED(r))
            {
                throw std::runtime_error("surface mapping failed!");
            }
224

Vladimir Dudnik's avatar
Vladimir Dudnik committed
225 226
            cv::Mat m(m_height, m_width, CV_8UC4, mappedTex.pData, mappedTex.RowPitch);
            m_frame_rgba.copyTo(m);
227

Vladimir Dudnik's avatar
Vladimir Dudnik committed
228 229
            m_pD3D11Ctx->Unmap(m_pSurfaceRGBA, subResource);
        }
230

Vladimir Dudnik's avatar
Vladimir Dudnik committed
231
        *ppSurface = use_nv12 ? m_pSurfaceNV12 : m_pSurfaceRGBA;
232 233 234 235 236 237 238 239 240 241 242 243 244

        return 0;
    } // get_surface()


    // process and render media data
    int render()
    {
        try
        {
            if (m_shutdown)
                return 0;

Vladimir Dudnik's avatar
Vladimir Dudnik committed
245 246 247
            // capture user input once
            MODE mode = (m_mode == MODE_GPU_NV12 && !m_nv12_available) ? MODE_GPU_RGBA : m_mode;

248
            HRESULT r;
249
            ID3D11Texture2D* pSurface = 0;
250

Vladimir Dudnik's avatar
Vladimir Dudnik committed
251
            r = get_surface(&pSurface, mode == MODE_GPU_NV12);
252 253
            if (FAILED(r))
            {
254
                throw std::runtime_error("get_surface() failed!");
255 256
            }

257 258
            m_timer.start();

Vladimir Dudnik's avatar
Vladimir Dudnik committed
259
            switch (mode)
260
            {
Vladimir Dudnik's avatar
Vladimir Dudnik committed
261 262 263 264 265 266 267 268
            case MODE_CPU:
            {
                // process video frame on CPU
                UINT subResource = ::D3D11CalcSubresource(0, 0, 1);

                D3D11_MAPPED_SUBRESOURCE mappedTex;
                r = m_pD3D11Ctx->Map(pSurface, subResource, D3D11_MAP_WRITE_DISCARD, 0, &mappedTex);
                if (FAILED(r))
269
                {
Vladimir Dudnik's avatar
Vladimir Dudnik committed
270 271
                    throw std::runtime_error("surface mapping failed!");
                }
272

Vladimir Dudnik's avatar
Vladimir Dudnik committed
273
                cv::Mat m(m_height, m_width, CV_8UC4, mappedTex.pData, (int)mappedTex.RowPitch);
274

Vladimir Dudnik's avatar
Vladimir Dudnik committed
275 276 277 278 279
                if (m_demo_processing)
                {
                    // blur data from D3D11 surface with OpenCV on CPU
                    cv::blur(m, m, cv::Size(15, 15), cv::Point(-7, -7));
                }
280

Vladimir Dudnik's avatar
Vladimir Dudnik committed
281 282 283 284
                cv::String strMode = cv::format("mode: %s", m_modeStr[MODE_CPU].c_str());
                cv::String strProcessing = m_demo_processing ? "blur frame" : "copy frame";
                cv::String strTime = cv::format("time: %4.1f msec", m_timer.time(Timer::UNITS::MSEC));
                cv::String strDevName = cv::format("OpenCL device: %s", m_oclDevName.c_str());
285

Vladimir Dudnik's avatar
Vladimir Dudnik committed
286 287 288 289
                cv::putText(m, strMode, cv::Point(0, 16), 1, 0.8, cv::Scalar(0, 0, 0));
                cv::putText(m, strProcessing, cv::Point(0, 32), 1, 0.8, cv::Scalar(0, 0, 0));
                cv::putText(m, strTime, cv::Point(0, 48), 1, 0.8, cv::Scalar(0, 0, 0));
                cv::putText(m, strDevName, cv::Point(0, 64), 1, 0.8, cv::Scalar(0, 0, 0));
290

Vladimir Dudnik's avatar
Vladimir Dudnik committed
291
                m_pD3D11Ctx->Unmap(pSurface, subResource);
292

Vladimir Dudnik's avatar
Vladimir Dudnik committed
293 294
                break;
            }
295

Vladimir Dudnik's avatar
Vladimir Dudnik committed
296 297 298 299 300 301 302
            case MODE_GPU_RGBA:
            case MODE_GPU_NV12:
            {
                // process video frame on GPU
                cv::UMat u;

                cv::directx::convertFromD3D11Texture2D(pSurface, u);
303

Vladimir Dudnik's avatar
Vladimir Dudnik committed
304
                if (m_demo_processing)
305
                {
Vladimir Dudnik's avatar
Vladimir Dudnik committed
306 307 308 309 310 311 312 313 314 315 316 317 318
                    // blur data from D3D11 surface with OpenCV on GPU with OpenCL
                    cv::blur(u, u, cv::Size(15, 15), cv::Point(-7, -7));
                }

                cv::String strMode = cv::format("mode: %s", m_modeStr[mode].c_str());
                cv::String strProcessing = m_demo_processing ? "blur frame" : "copy frame";
                cv::String strTime = cv::format("time: %4.1f msec", m_timer.time(Timer::UNITS::MSEC));
                cv::String strDevName = cv::format("OpenCL device: %s", m_oclDevName.c_str());

                cv::putText(u, strMode, cv::Point(0, 16), 1, 0.8, cv::Scalar(0, 0, 0));
                cv::putText(u, strProcessing, cv::Point(0, 32), 1, 0.8, cv::Scalar(0, 0, 0));
                cv::putText(u, strTime, cv::Point(0, 48), 1, 0.8, cv::Scalar(0, 0, 0));
                cv::putText(u, strDevName, cv::Point(0, 64), 1, 0.8, cv::Scalar(0, 0, 0));
319

Vladimir Dudnik's avatar
Vladimir Dudnik committed
320
                cv::directx::convertToD3D11Texture2D(u, pSurface);
321

Vladimir Dudnik's avatar
Vladimir Dudnik committed
322 323 324 325 326 327
                if (mode == MODE_GPU_NV12)
                {
                    // just for rendering, we need to convert NV12 to RGBA.
                    m_pD3D11Ctx->CopyResource(m_pSurfaceNV12_cpu_copy, m_pSurfaceNV12);

                    // process video frame on CPU
328
                    {
Vladimir Dudnik's avatar
Vladimir Dudnik committed
329 330 331 332 333 334 335 336 337 338 339 340 341
                        UINT subResource = ::D3D11CalcSubresource(0, 0, 1);

                        D3D11_MAPPED_SUBRESOURCE mappedTex;
                        r = m_pD3D11Ctx->Map(m_pSurfaceNV12_cpu_copy, subResource, D3D11_MAP_READ, 0, &mappedTex);
                        if (FAILED(r))
                        {
                            throw std::runtime_error("surface mapping failed!");
                        }

                        cv::Mat frame_nv12(m_height + (m_height / 2), m_width, CV_8UC1, mappedTex.pData, mappedTex.RowPitch);
                        cv::cvtColor(frame_nv12, m_frame_rgba, CV_YUV2RGBA_NV12);

                        m_pD3D11Ctx->Unmap(m_pSurfaceNV12_cpu_copy, subResource);
342 343
                    }

Vladimir Dudnik's avatar
Vladimir Dudnik committed
344 345
                    {
                        UINT subResource = ::D3D11CalcSubresource(0, 0, 1);
346

Vladimir Dudnik's avatar
Vladimir Dudnik committed
347 348 349 350 351 352
                        D3D11_MAPPED_SUBRESOURCE mappedTex;
                        r = m_pD3D11Ctx->Map(m_pSurfaceRGBA, subResource, D3D11_MAP_WRITE_DISCARD, 0, &mappedTex);
                        if (FAILED(r))
                        {
                            throw std::runtime_error("surface mapping failed!");
                        }
353

Vladimir Dudnik's avatar
Vladimir Dudnik committed
354 355
                        cv::Mat m(m_height, m_width, CV_8UC4, mappedTex.pData, mappedTex.RowPitch);
                        m_frame_rgba.copyTo(m);
356

Vladimir Dudnik's avatar
Vladimir Dudnik committed
357 358 359 360
                        m_pD3D11Ctx->Unmap(m_pSurfaceRGBA, subResource);
                    }

                    pSurface = m_pSurfaceRGBA;
361 362
                }

Vladimir Dudnik's avatar
Vladimir Dudnik committed
363 364 365
                break;
            }

366 367
            } // switch

368
            m_timer.stop();
369 370 371 372 373 374 375 376 377 378

            // traditional DX render pipeline:
            //   BitBlt surface to backBuffer and flip backBuffer to frontBuffer
            m_pD3D11Ctx->CopyResource(m_pBackBuffer, pSurface);

            // present the back buffer contents to the display
            // switch the back buffer and the front buffer
            r = m_pD3D11SwapChain->Present(0, 0);
            if (FAILED(r))
            {
379
                throw std::runtime_error("switch betweem fronat and back buffers failed!");
380 381 382 383 384 385
            }
        } // try

        catch (cv::Exception& e)
        {
            std::cerr << "Exception: " << e.what() << std::endl;
Vladimir Dudnik's avatar
Vladimir Dudnik committed
386
            cleanup();
387 388 389
            return 10;
        }

390 391 392
        catch (const std::exception& e)
        {
            std::cerr << "Exception: " << e.what() << std::endl;
Vladimir Dudnik's avatar
Vladimir Dudnik committed
393
            cleanup();
394 395 396
            return 11;
        }

397 398 399 400 401 402
        return 0;
    } // render()


    int cleanup(void)
    {
Vladimir Dudnik's avatar
Vladimir Dudnik committed
403 404 405
        SAFE_RELEASE(m_pSurfaceRGBA);
        SAFE_RELEASE(m_pSurfaceNV12);
        SAFE_RELEASE(m_pSurfaceNV12_cpu_copy);
406 407 408 409 410 411 412 413 414
        SAFE_RELEASE(m_pBackBuffer);
        SAFE_RELEASE(m_pD3D11SwapChain);
        SAFE_RELEASE(m_pRenderTarget);
        SAFE_RELEASE(m_pD3D11Dev);
        SAFE_RELEASE(m_pD3D11Ctx);
        D3DSample::cleanup();
        return 0;
    } // cleanup()

Vladimir Dudnik's avatar
Vladimir Dudnik committed
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
protected:
    void convert_I420_to_NV12(cv::Mat& i420, cv::Mat& nv12, int width, int height)
    {
        nv12.create(i420.rows, i420.cols, CV_8UC1);

        unsigned char* pSrcY = i420.data;
        unsigned char* pDstY = nv12.data;
        size_t srcStep = i420.step[0];
        size_t dstStep = nv12.step[0];

        {
            unsigned char* src;
            unsigned char* dst;

            // copy Y plane
            for (int i = 0; i < height; i++)
            {
                src = pSrcY + i*srcStep;
                dst = pDstY + i*dstStep;

                for (int j = 0; j < width; j++)
                {
                    dst[j] = src[j];
                }
            }
        }

        {
            // copy U/V planes to UV plane
            unsigned char* pSrcU;
            unsigned char* pSrcV;
            unsigned char* pDstUV;

            size_t uv_offset = height * dstStep;

            for (int i = 0; i < height / 2; i++)
            {
                pSrcU = pSrcY + height*width + i*(width / 2);
                pSrcV = pSrcY + height*width + (height / 2) * (width / 2) + i*(width / 2);

                pDstUV = pDstY + uv_offset + i*dstStep;

                for (int j = 0; j < width / 2; j++)
                {
                    pDstUV[j*2 + 0] = pSrcU[j];
                    pDstUV[j*2 + 1] = pSrcV[j];
                }
            }
        }

        return;
    }

468 469 470 471 472
private:
    ID3D11Device*           m_pD3D11Dev;
    IDXGISwapChain*         m_pD3D11SwapChain;
    ID3D11DeviceContext*    m_pD3D11Ctx;
    ID3D11Texture2D*        m_pBackBuffer;
Vladimir Dudnik's avatar
Vladimir Dudnik committed
473 474 475
    ID3D11Texture2D*        m_pSurfaceRGBA;
    ID3D11Texture2D*        m_pSurfaceNV12;
    ID3D11Texture2D*        m_pSurfaceNV12_cpu_copy;
476 477 478 479
    ID3D11RenderTargetView* m_pRenderTarget;
    cv::ocl::Context        m_oclCtx;
    cv::String              m_oclPlatformName;
    cv::String              m_oclDevName;
Vladimir Dudnik's avatar
Vladimir Dudnik committed
480 481 482
    bool                    m_nv12_available;
    cv::Mat                 m_frame_i420;
    cv::Mat                 m_frame_nv12;
483
};
484 485


486
// main func
487 488 489 490 491
int main(int argc, char** argv)
{
    std::string title = "D3D11 interop sample";
    return d3d_app<D3D11WinApp>(argc, argv, title);
}