1
\$\begingroup\$

I'm working on a Unity native plugin that runs a gstreamer pipeline in the background, decodes it using hardware decoding, then copies the texture over to a render texture to be displayed in Unity.

I have my GStreamer already working, I can run it from Unity and it creates a window with the camera output, all decoded on the GPU. From what I can see, GStreamer has a parameter in the d3d11videosink to "draw-on-shared-texture" of format DXGI_FORMAT_R8G8B8A8_UNORM. It also has an signal that gets called when gstreamer is about to draw, and a draw command to draw on a "shared handle".

I've only seen code out there to copy a decoded texture on the CPU and copying it back on the GPU, but never GPU => GPU copies. How do I go about it?

Also, I know that unity recommends doing all graphic operations in the reader thread using GL.IssuePluginEvent, but how am I supposed to do that when GStreamer doesn't let me access the texture when I want, only during the begin_draw callback.

I don't know much about DX11 or native unity plugin in general, and I am very lost. Any help would be appreciated.

Here's some code for my failed attempt at trying to get GStreamer to draw onto my ID3D11Resource pointer given by unity using RenderTexture.GetNativePtr():

static GstFlowReturn begin_draw_callback(GstElement* videosink, gpointer data)
{
    GstApp* app = (GstApp*)data;
    ID3D11Resource* resPtr = app->GetResourcePtr();
    if (resPtr == nullptr) {
        return GST_FLOW_OK;
    }

    // Your custom code here
    Debug::Log("BEGIN DRAW");

    HANDLE handle;
    IDXGIResource1* pResource;
    HRESULT res1 = resPtr->QueryInterface(__uuidof(IDXGIResource1), (void**)&pResource);
    HRESULT res2 = pResource->CreateSharedHandle(
        nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr, &handle);

    std::string res1Str = HRESULTToString(res1);
    std::string res2Str = HRESULTToString(res2);
    Debug::Log(res1Str);
    Debug::Log(res2Str);

    gpointer shard_handle = (gpointer)pResource;
    guint texture_misc_flags = D3D11_RESOURCE_MISC_FLAG::D3D11_RESOURCE_MISC_SHARED;
    guint64 acquire_key = NULL;
    guint64 release_key = NULL;

    g_signal_emit_by_name(videosink, "draw", shard_handle, texture_misc_flags, acquire_key, release_key, nullptr);

    return GST_FLOW_OK;
}
\$\endgroup\$
1
  • \$\begingroup\$ Render textures created in unity can't be shared to a different device, this might be why CreateSharedHandle isn't working. Maybe consider creating a sharable texture in your plugin using Unity's provided device, and once GStreamer's draw action_signal is called, copy back the result to the unity render texture? \$\endgroup\$ Oct 20, 2023 at 16:42

1 Answer 1

1
\$\begingroup\$

Your issue here is that you're trying to create a shared handle from a resource created by unity. Textures created by unity won't have the flags you're looking for.

// Create the shared texture
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = 1920;  // Replace with your desired width
desc.Height = 1080; // Replace with your desired height
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
desc.CPUAccessFlags = 0;
desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;

hr = device->CreateTexture2D(&desc, nullptr, &(instance->tempTexture));
if (FAILED(hr)) {
    Debug::Log("Couldnt create texture");
    return false;
}

// Share the texture
IDXGIResource* pDXGITexture = nullptr;
hr = (instance->tempTexture)->QueryInterface(__uuidof(IDXGIResource), (void**)&pDXGITexture);
if (FAILED(hr)) {
    Debug::Log("Couldnt DXGI res");
    return false;
}

HANDLE sharedHandle = nullptr;
hr = pDXGITexture->GetSharedHandle(&sharedHandle);
if (FAILED(hr)) {
    Debug::Log("Couldnt create shared handle");
    pDXGITexture->Release();
    return false;
}

Consider creating a texture yourself inside your plugin, and using making it a Unity texture by using the ID3D11Texture2D* inside Texture2D.CreateExternalTexture().

Your GStreamer callback shouldn't look more complicated than this:

static GstFlowReturn begin_draw_callback(GstElement* videosink, gpointer data)
{

    GstApp* app = (GstApp*)data;
    HANDLE tempSharedHandle = app->GetTempSharedPtr();
    
    if (tempSharedHandle == nullptr) {
        //Debug::Log("Invalid shared handle?");
        return GST_FLOW_OK;
    }

    gpointer shard_handle = (gpointer)tempSharedHandle;
    guint texture_misc_flags = D3D11_RESOURCE_MISC_FLAG::D3D11_RESOURCE_MISC_SHARED;
    guint64 acquire_key = NULL;
    guint64 release_key = NULL;

    g_signal_emit_by_name(videosink, "draw", shard_handle, texture_misc_flags, acquire_key, release_key, nullptr);

    return GST_FLOW_OK;
}

If you want example of how to write clean Unity plugins that interacts with the graphic devices, consider looking at KlakSpout which achieve roughly the same, but with a different video framework.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .