如何使用 CreateExternalTexture 和从 C++ 库传递的指针创建 Texture2D?

How to create Texture2D with CreateExternalTexture and a pointer passed from C++ library?

我正在尝试了解如何在 C++ DirectX11 代码和 Unity 引擎之间建立通信。

我想做的是在 C++ 中创建一个非常简单的 ID3D11Texture2D,然后将其传递给 Unity,通过 CreateExternalTexture 方法创建它的 Texture2D

所以,我有一个 C++ DLL VS 项目,其中有一个函数:

ID3D11ShaderResourceView* GetTexture()
{
    ofstream myfile;
    myfile.open("GetTexture.log");

    const int width = 640;
    const int height = 480;

    unsigned char* pixels = new unsigned char[width * height * 4];
    unsigned char* pixelsP = pixels;


    int numPixels = (int)(width * height);

    ID3D11Texture2D* tex = nullptr;
    ID3D11ShaderResourceView* shaderView = nullptr;

    ID3D11Device* device = CreateDX11device();

    if (!device)
    {
        myfile << "Cannot create DX11 device" << endl;
        myfile.close();
        return 0;
    }
    else
    {
        myfile << "Successfully created DX11 device" << endl;
    }

    for (int i = 0; i < numPixels; i++)
    {
        *(pixelsP) = 255;
        *(pixelsP + 1) = 0;
        *(pixelsP + 2) = 0;
        *(pixelsP + 3) = 122;
        pixelsP += 4;
    }

    D3D11_SUBRESOURCE_DATA initData = { 0 };
    initData.pSysMem = (const void*)pixels;
    initData.SysMemPitch = width * 4;
    initData.SysMemSlicePitch = width * height * 4;

    D3D11_TEXTURE2D_DESC desc = {};
    desc.Width = width;
    desc.Height = height;
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    desc.SampleDesc.Count = 1;
    desc.Usage = D3D11_USAGE_DYNAMIC;//D3D11_USAGE_IMMUTABLE;
    desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;// | D3D11_BIND_RENDER_TARGET;//D3D11_BIND_SHADER_RESOURCE;
    desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

    HRESULT hr = device->CreateTexture2D(&desc, &initData, &tex);

    if (FAILED(hr))
    {
        myfile << "FAILED TO CREATE DX TEXTURE" << endl;
        myfile << hr << endl;
        myfile.close();
        return nullptr;
    }
    else
    {
        myfile << "Successfully Created DX Texture" << endl;
    }

    myfile.close();

    D3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;
    ZeroMemory(&shaderResourceViewDesc, sizeof(shaderResourceViewDesc));
    
    shaderResourceViewDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    shaderResourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    shaderResourceViewDesc.Texture2D.MostDetailedMip = 0;
    shaderResourceViewDesc.Texture2D.MipLevels = 1;

    device->CreateShaderResourceView(tex, &shaderResourceViewDesc, &shaderView);

    return shaderView;
}

下面是我创建 DX11 设备的方法:

ID3D11Device* CreateDX11device()
{
    ID3D11Device* device = nullptr;
    HRESULT hr = S_OK;
    UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG;

    D3D_DRIVER_TYPE driverTypes[] = {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_WARP,
        D3D_DRIVER_TYPE_REFERENCE,
    };

    UINT numDriverTypes = ARRAYSIZE(driverTypes);

    D3D_FEATURE_LEVEL featureLevels[] = {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_1
    };

    ID3D11DeviceContext* context = nullptr;

    UINT numFeatureLevels = ARRAYSIZE(featureLevels);
    
    hr = D3D11CreateDevice(nullptr,
        D3D_DRIVER_TYPE_HARDWARE,
        nullptr,
        createDeviceFlags,
        featureLevels,
        numFeatureLevels,
        D3D11_SDK_VERSION,
        &device,
        nullptr,
        &context);

    if (FAILED(hr)) {
        cout << "Could not create DX11 device" << endl;
        return nullptr;
    }

    cout << "DX Device Created" << endl;

    return device;
}

现在,函数 GetTexture 仅从 Unity 中的 C# 代码调用,结果被视为 IntPtr。然后我开始创建 Texture2D 并设置纹理以在 Unity 中查看结果:

[DllImport("TestDX11.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetTexture();

void Start()
{
    IntPtr ptr = GetTexture();

    Texture2D texture = Texture2D.CreateExternalTexture(640, 480, TextureFormat.RGBA32, false, false, ptr);
    GetComponent<RawImage>().texture = texture;
}

问题是我希望在 Unity 应用程序中看到一个红色图像,但我什么也没看到,纹理是空白的,就好像我没有在里面放任何东西一样。为了确保我尝试从 DLL 输出接收到的指针并且它是非空的,所以我确实在纹理上得到了一个指针。在纹理创建过程中尝试使用不同的标志时,我感到很迷茫,但没有任何帮助。

我也尝试发送 ID3D11Texture2D* 而不是 ID3D11ResourceShaderView* 正如 this link 指出的那样,但是当 CreateExternalTexture 方法被调用时由于某种原因它崩溃了 Unity。

如有任何帮助,我将不胜感激!

在您的情况下,您正在使用由 D3D11CreateDevice 创建的设备创建纹理。

这是一个不同于Unity使用的设备,所以当你调用Texture2D.CreateExternalTexture时,无论是Unity还是Unity使用的设备都会检测到它不拥有这个资源,所以它不能使用它。

为了让 Unity 可以访问此纹理,您需要使用 Unity 本身使用的设备创建纹理。

为此,您需要使用 Unity 的 Low-level native plug-in interface

您需要在您的 dll 中声明一些特定的函数,以便 Unity 可以检测到它们然后调用。

在您的情况下,您可以使用 UnityPluginLoad“回调,并从那里检索设备(为简单起见,下面的代码不按照使用 Direct3D12 或其他图形的 Unity 处理情况 API)。

extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API 
UnityPluginLoad(IUnityInterfaces* interfaces)
{
    ID3D11Device* dxDevice;
    ID3D11DeviceContext* dxDeviceContext;
    dxDevice = interfaces->Get<IUnityGraphicsD3D11>()->GetDevice();
    dxDevice ->GetImmediateContext(&dxDeviceContext);
}

可以从 Texture2D.CreateExternalTexture

访问使用此设备创建的任何纹理

注意:由于 Unity Direct3D11 设备(当时我在 Unity 中做了一些项目,那是几年前的事了,我不确定最新版本),使用的是 D3D11_CREATE_DEVICE_FLAG::D3D11_CREATE_DEVICE_SINGLETHREADED),你需要在标准 Unity 线程中创建你的纹理,否则你可能(将)有潜在的崩溃(在我们的例子中,我们必须启动 unity 和我们的应用程序,使用自定义命令行来禁用这个标志)。