Search code examples
c++cudadirect3dnvdeccuda-arrays

Direct write to D3D texture from kernel


I am playing around with NVDEC H.264 decoder from NVIDIA CUDA samples, one thing I've found out is once frame is decoded, it's converted from NV12 to BGRA buffer which is allocated on CUDA's side, then this buffer is copied to D3D BGRA texture.

I find this not very efficient in terms of memory usage, and want to convert NV12 frame directly to D3D texture with this kernel:

void Nv12ToBgra32(uint8_t *dpNv12, int nNv12Pitch, uint8_t *dpBgra, int nBgraPitch, int nWidth, int nHeight, int iMatrix)

So, create D3D texture (BGRA, D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS, D3D11_CPU_ACCESS_WRITE, 1 mipmap), then register and write it on CUDA side:

//Register
ck(cuGraphicsD3D11RegisterResource(&cuTexResource, textureResource, CU_GRAPHICS_REGISTER_FLAGS_NONE));

...
//Write output:
CUarray retArray;
ck(cuGraphicsMapResources(1, &cuTexResource, 0));
ck(cuGraphicsSubResourceGetMappedArray(&retArray, cuTexResource, 0, 0));

/*
yuvFramePtr (NV12) is uint8_t* from decoded frame,
it's stored within CUDA memory I believe
*/

Nv12ToBgra32(yuvFramePtr, w, (uint8_t*)retArray, 4 * w, w, h);

ck(cuGraphicsUnmapResources(1, &cuTexResource, 0));

Once kernel is called, I get crash. May be because of misusing CUarray, can anybody please clarify how to use output of cuGraphicsSubResourceGetMappedArray to write texture memory from CUDA kernel? (since writing raw memory is only needed, there is no need to handle correct clamp, filtering and value scaling)


Solution

  • Ok, for anyone who struggling on question "How to write D3D11 texture from CUDA kernel", here is how:

    Create D3D texture with D3D11_BIND_UNORDERED_ACCESS. Then, register resource:

    //ID3D11Texture2D *textureResource from D3D texture
    CUgraphicsResource cuTexResource;
    ck(cuGraphicsD3D11RegisterResource(&cuTexResource, textureResource, CU_GRAPHICS_REGISTER_FLAGS_NONE));
    
    //You can also add write-discard if texture will be fully written by kernel
    ck(cuGraphicsResourceSetMapFlags(cuTexResource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_WRITE_DISCARD));
    

    Once texture is created and registered we can use it as write surface.

    ck(cuGraphicsMapResources(1, &cuTexResource, 0));
    
    //Get array for first mip-map
    CUArray retArray;
    ck(cuGraphicsSubResourceGetMappedArray(&retArray, cuTexResource, 0, 0));
    
    //Create surface from texture
    CUsurfObject surf;
    CUDA_RESOURCE_DESC surfDesc{};
    surfDesc.res.array.hArray = retArray;
    surfDesc.resType = CU_RESOURCE_TYPE_ARRAY;
    ck(cuSurfObjectCreate(&surf, &surfDesc));
    
    /* 
    Kernel declaration is:
    void Nv12ToBgra32Surf(uint8_t* dpNv12, int nNv12Pitch, cudaSurfaceObject_t surf, int nBgraPitch, int nWidth, int nHeight, int iMatrix)
    
    Surface write:
    surf2Dwrite<uint>(VALUE, surf, x * sizeof(uint), y);
    For BGRA surface we are writing uint, X offset is in bytes,
    so multiply it with byte-size of type.
    
    Run kernel:
    */
    Nv12ToBgra32Surf(yuvFramePtr, w, /*out*/surf, 4 * w, w, h);
    
    ck(cuGraphicsUnmapResources(1, &cuTexResource, 0));
    ck(cuSurfObjectDestroy(surf));