Yes! It is possible! \o/
Thank you Epic for such a well commented code and for letting us see it! And, mostly, for it being C++!
Disclaimer
My intent was to use these textures with Kinect (on Windows) buffers for updating each frame, so I focused on dynamic textures for D3D11 only. I’m not trying to improve the engine with new nor portable features as my C++ level is not good enough for it. Also, I wasn’t very eager to alter the source code as with every new engine version, I would have a lot of work re-doing my changes.
I’m using this texture with pixel formats PF_B8G8R8A8, PF_G8 and PF_G16 without problems, just like it was with UTexture2D.
How I did it
First, I copied from the UTexture2DDynamic class. Everything in it, as I didn’t know how to create a working UTexture child class from scratch and that class is really simple compared to UTexture2D.
Then, I saw it used Texture2DDynamicResource to issue the actual texture creation on GPU from RHI. So, I also made my own TextureResource child class.
The Texture2DDynamicResource::InitRHI method was calling the global method RHICreateTexture2D and, looking at the source, that was when it created the ID3D11Texture2D with D3D11_USAGE_DEFAULT. So, that was the method I had to recreate in order for it to do as I wanted without altering the engine’s source.
Most of that method’s inner calls were static methods easily found in Public headers. No big deal there. Although, some other methods called were either from D3D11RHIPrivate or private members of the original method’s class. Those, I copied as globals on my own headers. It was not a problem compiling, but when packaging the project, it gave me Linker problems, as there were DLL exported globals with the same name on the D3D11RHI module. The solution was simple: put my modified D3D11RHI methods inside a namespace. Also, for insurance, I added _mod to their names.
How to use it
I leave attached in this answer the .h and .cpp files necessary to use my DynamicTexture2D Frankenstein’d class. You’ll only have to adapt the first .cpp #include to your project/plugin PrivatePCH.
The code to create a texture from it is very similar to that of creating a UTexture2D. The updating part that is totally new, as Unreal does it using ID3DDeviceContext::UpdateSubresource and it seem to not work with D3D11_USAGE_DYNAMIC. (for Unreal standard ways of updating textures in real time, read this). For ease of use, I left a method in the class just for updating the texture.
#include "DynamicTexture2D.h"
void UYourClassName::CreateTexture(int32 Width, int32 Height, EPixelFormat PixelFormat)
{
// Texture is a UDynamicTexture2D*
Texture = UDynamicTexture2D::Create(Width, Height, PixelFormat);
#if WITH_EDITORONLY_DATA
Texture->MipGenSettings = TMGS_NoMipmaps;
#endif // WITH_EDITORONLY_DATA
Texture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
Texture->AddToRoot();
Texture->UpdateResource();
}
void UYourClassName::UpdateTexture()
{
// must do it on render thread!
ENQUEUE_UNIQUE_RENDER_COMMAND(
UpdateTextureData,
{
uint8* Buffer = /* get your CPU buffer address */;
/* Your buffer size in BYTES.
Eg., if your format is PF_B8G8R8A8, it will be width * height * 4 */
SIZE_T BufferSize = /* Buffer size in BYTES */;
if (Buffer)
{
Texture->UpdateContents(Buffer, BufferSize);
}
}
);
}
Don’t forget!
If you use the attached files, you must add the RenderCore, RHI and D3D11RHI modules to your project/plugin Build.cs file in order for all the headers included to work. Below is that part from my Build.cs:
PublicDependencyModuleNames.AddRange(new string[] {
"Core", "CoreUObject", "Engine", "RHI", "RenderCore", "D3D11RHI"
};
I’m not really certain about RenderCore and RHI, but I did use some globals from RHI.h (without the need to include it in any .h).
#Updated for 4.11
As Epic has changed the way their game and render threads worked (for the better) there’s a small change to make in the RHICreateDynamicTexture2D method, inside D3D11Modifications.h. Just replace
if (GRHIThread)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_RHIMETHOD_CreateTexture2D_WaitRHI);
FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::WaitForRHIThread);
}
with
FScopedRHIThreadStaller StallRHIThread(FRHICommandListExecutor::GetImmediateCommandList());
And, inside the same method, in the end, you’ll add some lines. It’ll be like this:
Texture2D->ResourceInfo.VRamAllocation = VRamAllocation;
if (Flags & TexCreate_RenderTargetable)
{
Texture2D->SetCurrentGPUAccess(EResourceTransitionAccess::EWritable);
}
D3D11TextureAllocated(*Texture2D);
return Texture2D;