TextureSubPlugin is a Unity low-level native plugin for uploading data to chunks (i.e., subregions or bricks) of a 2D/3D Texture. The plugin can also be used to create 3D/2D textures which is quite handy for circumventing Unity's Texture2D/3D 2GBs size limitation.
This repository is adapted from Unity native rendering plugin repository. Rather than having multiple build files, this project makes use of a single build generator system using CMake.
-
build the plugin using CMake. Assuming you are in the same folder as this README:
mkdir build cd build export UNITY_EDITOR_PATH=<path-to-your-installed-unity-editor> cmake .. --preset <preset> cmake --build . --config Release cmake --install . --prefix <path-to-unity-project-plugins-folder>
UNITY_EDITOR_PATH is the path to your Unity editor. You can get it by going to your Unity Hub > Installs > pick which installed editor you want to use > Show File in File Browser.
preset is the target you are building this plugin for. Check
CMakePresets.txt
for the available presets and configuration. preset can be:-
windows - in case SUPPORT_OPENGL_CORE is set, GLAD_PATH has to be filled with the path to glad
-
linux - in case SUPPORT_VULKAN is set, Vulkan SDK has to be installed
-
android - not possible to build for this target on Windows. Make sure that your Unity editor has the module Android SDK & NDK Tools installed. Additionally, ANDROID_ABI, ANDROID_PLATFORM, and ANDROID_STL have to be populated (go to CMakePresets.txt and populate them for your target) see NDK cmake guide.
-
magicleap2 - same as android preset but with populated NDK arguments.
-
-
both the libray files (TextureSubPlugin.so and libTextureSubPlugin.so) should now be available in the provided installation directory (has to be in Assets/Plugins for the plugin to be found and loaded)
-
in your Unity project, click on the installed .so/.dll native plugin file and fill the platform settings
For textures larger than 2GBs and when using OpenGL or Vulkan, using Unity's Texture3D/2D constructor outputs the following error:
Texture3D (WIDTHxHEIGHTxDEPTH) is too large, currently up to 2GB is allowed
Direct3D11/12 impose a 2GB limit per resource but OpenGL and Vulkan don't.
To bypass this, create the texture using the provided CreateTexture3D
(see TextureSubPluginAPI.h):
// create a command buffer in which graphics commands will be submitted
CommandBuffer cmd_buffer = new();
// create and keep track of a unique texture ID - this is used later on to
// retrieve the create texture
UInt32 texture_id = 0;
// allocate the plugin call's arguments struct
CreateTexture3DParams args = new() {
texture_id = texture_id,
width = ...,
height = ...,
depth = ...,
format = ...,
};
IntPtr p_args = Marshal.AllocHGlobal(Marshal.SizeOf<CreateTexture3DParams>());
Marshal.StructureToPtr(args, p_args, false);
cmd_buffer.IssuePluginEventAndData(TextureSubPlugin.API.GetRenderEventFunc(),
(int)TextureSubPlugin.Event.CreateTexture3D, p_args);
// execute the command buffer immediately
Graphics.ExecuteCommandBuffer(cmd_buffer);
// we can be sure that the texture has been updated
yield return new WaitForEndOfFrame();
Marshal.FreeHGlobal(p_args);
// retrieve the create texture using the assigned texture ID
IntPtr native_tex_ptr = API.RetrieveCreatedTexture3D(texture_id);
if (native_tex_ptr == IntPtr.Zero) {
throw new NullReferenceException("native texture pointer is nullptr. " +
"Make sure that your platform supports native code plugins");
}
// create the actual Unity Texture 3D object by supplying a pointer
// to the externaly create texture
Texture3D tex = Texture3D.CreateExternalTexture(..., ..., ..., ...,
mipChain: ..., nativeTex: native_tex_ptr);
// finally, make sure to overwrite native_tex_ptr
// this has to be overwritten for Vulkan to work because Unity expects a
// VkImage* for the nativeTex paramerter not a VkImage. GetNativeTexturePtr does
// not actually return a VkImage* as it claims but rather a VkImage
// => this is probably a Unity bug.
// (see https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Texture3D.CreateExternalTexture.html)
native_tex_ptr = m_brick_cache.GetNativeTexturePtr();
If you have used CreateTexture3D
to create a native texture then you
should also call DestroyTexture3D
to destroy it. Not doing so will result
in a GPU memory leak (e.g., your GPU memory usage will increase each time
you toggle the play mode in Unity editor):
// create a command buffer in which graphics commands will be submitted
CommandBuffer cmd_buffer = new();
DestroyTexture3DParams args = new () {
texture_handle = tex.GetNativeTexturePtr()
};
IntPtr p_args = Marshal.AllocHGlobal(Marshal.SizeOf<CreateTexture3DParams>());
Marshal.StructureToPtr(args, p_args, false);
cmd_buffer.IssuePluginEventAndData(TextureSubPlugin.API.GetRenderEventFunc(),
(int)TextureSubPlugin.Event.DestroyTexture3D, p_args);
// execute the command buffer immediately
Graphics.ExecuteCommandBuffer(cmd_buffer);
// only free allocated resources in next frame
yield return new WaitForEndOfFrame();
Marshal.FreeHGlobal(p_args);
Again, if the graphics API is Direct3D11/12, there is (probably) no good reason
to use CreateTexture3D
.
The following example illustrates how to update a subregion of a 3D texture using this plugin:
// wait until the rendering thread is done working
yield return new WaitForEndOfFrame();
// create a command buffer in which graphics commands will be submitted
CommandBuffer cmd_buffer = new();
// data is the sub-region data array
byte[] data = new byte[DATA_LENGTH];
// data is a managed object and we want to pass it to unmanaged code. The GC
// could in theory move the data's object data around in memory. To avoid this,
// a GCHandle has to be created for data that instructs GC to keep the data
// pinned to the same memory address location. When we are done we the data, we
// have to inform the GC that it is free to manage the object as it wants.
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
// allocate the plugin call's arguments struct
TextureSubImage3DParams args = new() {
texture_handle = tex.GetNativeTexturePtr(),
xoffset = ...,
yoffset = ...,
zoffset = ...,
width = ...,
height = ...,
depth = ...,
data_ptr = handle.AddrOfPinnedObject(),
level = ...,
format = ...
};
IntPtr p_args = Marshal.AllocHGlobal(Marshal.SizeOf<TextureSubImage3DParams>());
Marshal.StructureToPtr(args, p_args, false);
cmd_buffer.IssuePluginEventAndData(TextureSubPlugin.API.GetRenderEventFunc(),
(int)TextureSubPlugin.Event.TextureSubImage3D, p_args);
// execute the command buffer immediately
Graphics.ExecuteCommandBuffer(cmd_buffer);
// we can be sure that the texture has been updated
yield return new WaitForEndOfFrame();
Marshal.FreeHGlobal(p_args);
// hey GC, you are again free to manage the data object
handle.Free();
If you get a DllNotFoundException when trying your Unity build on your target, the reasons might be:
-
you compiled the plugin using a different compiler/toolchain than what you compiled your Unity project, that uses the .so/.dll plugin, with. Make sure that you use the same compiler/toolchain. This is simply done by setting UNITY_EDITOR_PATH to that of the same editor you are using to build your Unity project.
-
refrain from renaming the build .so/.dll file and make sure that it is put somewhere Unity can find it (usually Assets/Plugins).
-
make sure that in Unity Editor, the installed .so/.dll has the correct platform settings filled (CPU, OS, Include Platforms, etc.).
MIT License. Read license.txt
file.