- Home /
Directx12 CreateExternalTexture
Hi Unity developers and advanced users!
I'm working on a plugin which uses native rendering and needs to create a texture in C++ code, which then used inside Unity.
For DX11 i'm using ID3D11ShaderResourceView as a parameter for Texture2D.CreateExternalTexture(), however there is no documentation on what should be passed in DX12 case: https://docs.unity3d.com/ScriptReference/Texture2D.CreateExternalTexture.html
Here is my current C++ code:
void* getTextureHandleForUnityDX12(ID3D12Resource* src_tex)
{
D3D12_RESOURCE_DESC src_tex_desc;
src_tex_desc = src_tex->GetDesc();
// Once the texture is created, we must create a shader resource view of it
// so that shaders may use it. In general, the view description will match
// the texture description.
D3D12_SHADER_RESOURCE_VIEW_DESC textureViewDesc;
ZeroMemory(&textureViewDesc, sizeof(textureViewDesc));
textureViewDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
textureViewDesc.Format = src_tex_desc.Format;
textureViewDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
textureViewDesc.Texture2D.MipLevels = src_tex_desc.MipLevels;
textureViewDesc.Texture2D.MostDetailedMip = 0;
// create shader resource view and constant buffer view descriptor heap
D3D12_DESCRIPTOR_HEAP_DESC descHeapCbvSrv = {};
descHeapCbvSrv.NumDescriptors = 1;
descHeapCbvSrv.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
descHeapCbvSrv.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
auto hr = m_device->CreateDescriptorHeap(
&descHeapCbvSrv, __uuidof(ID3D12DescriptorHeap), (void**)&m_descriptorHeap);
if (FAILED(hr)) { return TranslateReturnCode(hr); }
// https://software.intel.com/en-us/articles/introduction-to-resource-binding-in-microsoft-directx-12
CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle0(m_descriptorHeap->GetCPUDescriptorHandleForHeapStart());
m_device->CreateShaderResourceView(src_tex, &textureViewDesc, srvHandle0);
// Doesn't work: the texture in Unity remains black
return (void*)&srvHandle0;
}
Maybe you could add an example of that to https://bitbucket.org/Unity-Technologies/graphicsdemos as well, I think some people will find it quite useful.
Thanks, Lev
Answer by Baroque · Jan 07, 2017 at 05:31 PM
In the native rendering plugin example it's not immediately obvious what type to use but if you dig into the implementation of RenderAPI_D3D12
you'll see the following bit of code in EndModifyTexture
:
void RenderAPI_D3D12::EndModifyTexture(void* textureHandle, int textureWidth, int textureHeight, int rowPitch, void* dataPtr)
{
ID3D12Device* device = s_D3D12->GetDevice();
const UINT64 kDataSize = textureWidth * textureHeight * 4;
ID3D12Resource* upload = GetUploadResource(kDataSize);
upload->Unmap(0, NULL);
ID3D12Resource* resource = (ID3D12Resource*)textureHandle;
D3D12_RESOURCE_DESC desc = resource->GetDesc();
assert(desc.Width == textureWidth);
assert(desc.Height == textureHeight);
You can see textureHandle
is cast to ID3D12Resource
. That's what Unity will return if you call GetNativePtr on a Texture2D in DirectX12, so that's what you should use to create a Unity texture from scratch.
Answer by leavittx · Jan 29, 2017 at 07:43 PM
Hey. I tried passing ID3D12Resource* to Texture2D.CreateExternalTexture(), but it didn't work at all.
I will post my createTexture2D() method code:
Result createTexture2D(void **dst_tex, int width, int height, TextureFormat format, ResourceFlags flags)
{
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
heap.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heap.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heap.CreationNodeMask = 1;
heap.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
desc.Alignment = 0;
desc.Width = (UINT64)width;
desc.Height = (UINT)height;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.Format = GetDXGIFormat(format);
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
// texture can't be created with D3D12_HEAP_TYPE_UPLOAD / D3D12_HEAP_TYPE_READBACK heap type.
// ResourceFlags::CPU_Write / CPU_Read flag is ignored.
ID3D12Resource *tex = nullptr;
auto hr = m_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&tex) );
if ( FAILED( hr ) ) { return TranslateReturnCode(hr); }
*dst_tex = tex;
return Result::OK;
}
Along with writeTexture2D() code:
Result writeTexture2D(void *dst_tex_, int width, int height, TextureFormat format, const void *src, size_t write_size)
{
if (write_size == 0) { return Result::OK; }
if (!dst_tex_ || !src) { return Result::InvalidParameter; }
auto *dst_tex = (ID3D12Resource*)dst_tex_;
D3D12_RESOURCE_DESC dst_desc = dst_tex->GetDesc();
D3D12_PLACED_SUBRESOURCE_FOOTPRINT dst_layout;
UINT dst_num_rows;
UINT64 dst_row_size;
UINT64 dst_required_size;
m_device->GetCopyableFootprints(&dst_desc, 0, 1, 0, &dst_layout, &dst_num_rows, &dst_row_size, &dst_required_size);
auto write_proc = [](ID3D12Resource *dst_tex, int width, int height, TextureFormat format, const void *src, size_t write_size, D3D12_PLACED_SUBRESOURCE_FOOTPRINT& dst_layout) {
void *mapped_data = nullptr;
auto hr = dst_tex->Map(0, nullptr, &mapped_data);
if (FAILED(hr)) { return hr; }
int dst_pitch = dst_layout.Footprint.RowPitch;
int src_pitch = width * GetTexelSize(format);
int num_rows = std::min<int>(std::min<int>(height, dst_layout.Footprint.Height),
ceildiv<size_t>(write_size, src_pitch));
CopyRegion(mapped_data, dst_pitch, src, src_pitch, num_rows);
dst_tex->Unmap(0, nullptr);
return S_OK;
};
// try direct access
auto hr = write_proc(dst_tex, width, height, format, src, write_size, dst_layout);
if (SUCCEEDED(hr)) { return Result::OK; }
// try copy-via-staging
auto staging = createStagingBuffer(dst_required_size, StagingFlag::Upload);
if (!staging) { return Result::OutOfMemory; }
hr = write_proc(staging.Get(), width, height, format, src, write_size, dst_layout);
if (FAILED(hr)) { return TranslateReturnCode(hr); }
hr = executeCommands([&](ID3D12GraphicsCommandList *clist) {
CD3DX12_TEXTURE_COPY_LOCATION dst_region(dst_tex, 0);
CD3DX12_TEXTURE_COPY_LOCATION src_region(staging.Get(), dst_layout);
clist->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(dst_tex, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST));
clist->CopyTextureRegion(&dst_region, 0, 0, 0, &src_region, nullptr);
clist->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(dst_tex, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON));
});
if (FAILED(hr)) { return TranslateReturnCode(hr); }
return Result::OK;
}
Maybe someone can point me to possible problems with this code.
One good thing is that now I can see that the writeTexture2D() code is rather a DX11 copy-paste with some changes applied to make it compile with DX12 API being used, then a DX12-oriented solution. Should I try something with GetUploadResource() like GraphicsDemos does ?