Commit bae8e7b4 authored by Tim Biedert's avatar Tim Biedert Committed by Utkarsh Ayachit

RT wrapper, VisRTX backend

parent 24f213c3
......@@ -15,3 +15,7 @@ project.xcworkspace
# Exclude VSCode settings and configuration folder
*.vscode*
# Exclude Visual Studio stuff
.vs*
CMakeSettings.json
#
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
# Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
......@@ -26,14 +26,10 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Locate the OptiX distribution. Search relative to the SDK first, then look in the system.
# Our initial guess will be within the SDK.
set(OptiX_INSTALL_DIR "${CMAKE_SOURCE_DIR}/../" CACHE PATH "Path to OptiX installed location.")
# The distribution contains both 32 and 64 bit libraries. Adjust the library
# search path based on the bit-ness of the build. (i.e. 64: bin64, lib64; 32:
# bin, lib). Note that on Mac, the OptiX library is a universal binary, so we
# Adjust the library search path based on the bit-ness of the build.
# (i.e. 64: bin64, lib64; 32: bin, lib).
# Note that on Mac, the OptiX library is a universal binary, so we
# only need to look in lib and not lib64 for 64 bit builds.
if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND NOT APPLE)
set(bit_dest "64")
......@@ -41,6 +37,8 @@ else()
set(bit_dest "")
endif()
set(OptiX_INSTALL_DIR "" CACHE PATH "Path to OptiX installed location.")
macro(OPTIX_find_api_library name version)
find_library(${name}_LIBRARY
NAMES ${name}.${version} ${name}
......@@ -62,9 +60,42 @@ macro(OPTIX_find_api_library name version)
endif()
endmacro()
OPTIX_find_api_library(optix 1)
OPTIX_find_api_library(optixu 1)
OPTIX_find_api_library(optix_prime 1)
# OptiX SDKs before 5.1.0 named the library optix.1.lib
# Linux handles this via symlinks and doesn't need changes to the library name.
set(OptiX_version "1")
# The OptiX library is named with the major and minor digits since OptiX 5.1.0.
# Dynamically find the matching library name by parsing the OptiX_INSTALL_DIR.
# This only works if the installation retained the original folder format "OptiX SDK major.minor.micro".
# We want the concatenated major and minor numbers if the version is greater or equal to 5.1.0.
if(WIN32)
if(OptiX_INSTALL_DIR)
string(REGEX REPLACE " |-" ";" OptiX_install_dir_list ${OptiX_INSTALL_DIR})
list(LENGTH OptiX_install_dir_list OptiX_install_dir_list_length)
if(${OptiX_install_dir_list_length} GREATER 0)
# Get the last list element, something like "5.1.0".
list(GET OptiX_install_dir_list -1 OptiX_version_string)
# Component-wise integer version number comparison (version format is major[.minor[.patch[.tweak]]]).
# Starting with OptiX 6.0.0, the full version number is used to avoid the Windows DLL hell.
if(${OptiX_version_string} VERSION_GREATER_EQUAL "6.0.0")
set(OptiX_version ${OptiX_version_string})
elseif(${OptiX_version_string} VERSION_GREATER_EQUAL "5.1.0")
set(OptiX_version "")
string(REPLACE "." ";" OptiX_major_minor_micro_list ${OptiX_version_string})
foreach(index RANGE 0 1)
list(GET OptiX_major_minor_micro_list ${index} number)
string(APPEND OptiX_version ${number})
endforeach()
endif()
endif()
endif()
endif(WIN32)
OPTIX_find_api_library(optix ${OptiX_version})
OPTIX_find_api_library(optixu ${OptiX_version})
OPTIX_find_api_library(optix_prime ${OptiX_version})
# Include
find_path(OptiX_INCLUDE
......@@ -88,13 +119,13 @@ function(OptiX_report_error error_message required)
endfunction()
if(NOT optix_LIBRARY)
OptiX_report_error("optix library not found. Please locate before proceeding." TRUE)
OptiX_report_error("OptiX library not found. Please set OptiX_INSTALL_DIR to locate it automatically." TRUE)
endif()
if(NOT OptiX_INCLUDE)
OptiX_report_error("OptiX headers (optix.h and friends) not found. Please locate before proceeding." TRUE)
OptiX_report_error("OptiX headers (optix.h and friends) not found. Please set OptiX_INSTALL_DIR to locate them automatically." TRUE)
endif()
if(NOT optix_prime_LIBRARY)
OptiX_report_error("optix Prime library not found. Please locate before proceeding." FALSE)
OptiX_report_error("OptiX Prime library not found. Please set OptiX_INSTALL_DIR to locate it automatically." FALSE)
endif()
# Macro for setting up dummy targets
......@@ -135,8 +166,8 @@ endfunction()
# Sets up a dummy target
OptiX_add_imported_library(optix "${optix_LIBRARY}" "${optix_DLL}" "${OPENGL_LIBRARIES}")
OptiX_add_imported_library(optixu "${optixu_LIBRARY}" "${optixu_DLL}" "")
OptiX_add_imported_library(optix_prime "${optix_prime_LIBRARY}" "${optix_prime_DLL}" "")
OptiX_add_imported_library(optixu "${optixu_LIBRARY}" "${optixu_DLL}" "")
OptiX_add_imported_library(optix_prime "${optix_prime_LIBRARY}" "${optix_prime_DLL}" "")
macro(OptiX_check_same_path libA libB)
if(_optix_path_to_${libA})
......
......@@ -16,6 +16,12 @@ set(classes
vtkOSPRayVolumeNode
vtkOSPRayWindowNode)
set(RTWrapper_sources
RTWrapper/RTWrapper.cxx)
set(RTWrapper_headers
RTWrapper/RTWrapper.h)
vtk_object_factory_declare(
BASE vtkOSPRayVolumeInterface
OVERRIDE vtkOSPRayVolumeMapper)
......@@ -25,36 +31,66 @@ vtk_object_factory_configure(
HEADER_FILE vtk_object_factory_header
EXPORT_MACRO "VTKRENDERINGOSPRAY_EXPORT")
# OSPRay
option(VTK_ENABLE_OSPRAY "Enable OSPRay rendering backend" ON)
set(OSPRAY_INSTALL_DIR "" CACHE PATH "Install location of OSPRay")
mark_as_advanced(OSPRAY_INSTALL_DIR)
if (VTK_ENABLE_OSPRAY)
# TODO: Send patches upstream to export targets from ospray.
find_package(ospray 1.8 REQUIRED HINTS ${OSPRAY_INSTALL_DIR})
option(VTKOSPRAY_ENABLE_DENOISER "Build OSPRay Renderer using OpenImageDenoise")
if (VTKOSPRAY_ENABLE_DENOISER)
find_package(OpenImageDenoise 0.8 REQUIRED)
add_definitions(-DVTKOSPRAY_ENABLE_DENOISER)
set(OPENIMAGEDENIOSE OpenImageDenoise)
endif()
endif()
# VisRTX
option(VTK_ENABLE_VISRTX "Enable VisRTX rendering backend" ON)
set(VISRTX_INSTALL_DIR "" CACHE PATH "Install location of VisRTX")
mark_as_advanced(VISRTX_INSTALL_DIR)
if (VTK_ENABLE_VISRTX)
find_package(VisRTX CONFIG REQUIRED HINTS ${VISRTX_INSTALL_DIR})
endif()
if (VisRTX_FOUND)
list(APPEND RTWrapper_sources RTWrapper/VisRTX/VisRTXBackend.cxx)
endif()
if (NOT ospray_FOUND AND NOT VisRTX_FOUND)
message("Warning: Ray tracing requires OSPRay and/or VisRTX, turn VTK_ENABLE_OSPRAY and/or VTK_ENABLE_VISRTX ON.")
endif()
vtk_module_add_module(VTK::RenderingOSPRay
CLASSES ${classes}
SOURCES ${vtk_object_factory_source}
PRIVATE_HEADERS ${vtk_object_factory_header})
option(VTKOSPRAY_ENABLE_DENOISER "build OSPRay Renderer using OpenImageDenoise")
if (VTKOSPRAY_ENABLE_DENOISER)
find_package(OpenImageDenoise 0.8 REQUIRED)
add_definitions(-DVTKOSPRAY_ENABLE_DENOISER)
set(OPENIMAGEDENIOSE OpenImageDenoise)
endif()
SOURCES ${vtk_object_factory_source} ${RTWrapper_sources}
PRIVATE_HEADERS ${vtk_object_factory_header} ${RTWrapper_headers})
# TODO: Send patches upstream to export targets from ospray.
find_package(ospray 1.8 REQUIRED)
vtk_module_link(VTK::RenderingOSPRay
PUBLIC
${OSPRAY_LIBRARIES}
PRIVATE
${OPENIMAGEDENIOSE}
)
vtk_module_include(VTK::RenderingOSPRay
PUBLIC
${OSPRAY_INCLUDE_DIRS}
)
# TODO: FindOSPRay should do this.
# OSPRay_Core uses MMTime which is in it's own special library.
if (WIN32)
if (ospray_FOUND)
vtk_module_link(VTK::RenderingOSPRay
PUBLIC
${OSPRAY_LIBRARIES}
PRIVATE
Winmm)
endif ()
${OPENIMAGEDENIOSE})
vtk_module_include(VTK::RenderingOSPRay
PUBLIC
${OSPRAY_INCLUDE_DIRS})
# TODO: FindOSPRay should do this.
# OSPRay_Core uses MMTime which is in it's own special library.
if (WIN32)
vtk_module_link(VTK::RenderingOSPRay
PRIVATE
Winmm)
endif ()
vtk_module_definitions(VTK::RenderingOSPRay PRIVATE VTK_ENABLE_OSPRAY)
endif()
if (VisRTX_FOUND)
vtk_module_link(VTK::RenderingOSPRay PUBLIC VisRTX_DynLoad)
vtk_module_definitions(VTK::RenderingOSPRay PRIVATE VTK_ENABLE_VISRTX)
endif()
#pragma once
#include "Types.h"
namespace RTW
{
class Backend
{
public:
virtual ~Backend() = default;
public:
virtual RTWError Init(int *argc, const char **argv) = 0;
virtual void Shutdown() = 0;
virtual bool IsSupported(RTWFeature feature) const = 0;
virtual RTWData NewData(size_t numItems, RTWDataType, const void *source, const uint32_t dataCreationFlags = 0) = 0;
virtual RTWGeometry NewGeometry(const char *type) = 0;
virtual RTWTexture NewTexture(const char* type) = 0;
virtual RTWLight NewLight(RTWRenderer, const char *type) = 0;
virtual RTWLight NewLight2(const char *renderer_type, const char *light_type) = 0;
virtual RTWLight NewLight3(const char *light_type) = 0;
virtual RTWMaterial NewMaterial(RTWRenderer, const char *material_type) = 0;
virtual RTWMaterial NewMaterial2(const char *renderer_type, const char *material_type) = 0;
virtual RTWVolume NewVolume(const char *type) = 0;
virtual RTWTransferFunction NewTransferFunction(const char *type) = 0;
virtual RTWRenderer NewRenderer(const char *type) = 0;
virtual RTWCamera NewCamera(const char *type) = 0;
virtual RTWModel NewModel() = 0;
virtual RTWGeometry NewInstance(RTWModel modelToInstantiate, const rtw::affine3f &transform) = 0;
virtual RTWFrameBuffer NewFrameBuffer(const rtw::vec2i &size, const RTWFrameBufferFormat format, const uint32_t frameBufferChannels) = 0;
virtual void Release(RTWObject) = 0;
virtual void AddGeometry(RTWModel, RTWGeometry) = 0;
virtual void AddVolume(RTWModel, RTWVolume) = 0;
virtual void SetString(RTWObject, const char *id, const char *s) = 0;
virtual void SetObject(RTWObject, const char *id, RTWObject other) = 0;
virtual void SetData(RTWObject, const char *id, RTWData) = 0;
virtual void SetMaterial(RTWGeometry, RTWMaterial) = 0;
virtual void Set1i(RTWObject, const char *id, int32_t x) = 0;
virtual void Set2i(RTWObject, const char *id, int32_t x, int32_t y) = 0;
virtual void Set1f(RTWObject, const char *id, float x) = 0;
virtual void Set2f(RTWObject, const char *id, float x, float y) = 0;
virtual void Set3i(RTWObject, const char *id, int x, int y, int z) = 0;
virtual void Set3f(RTWObject, const char *id, float x, float y, float z) = 0;
virtual void Set4f(RTWObject, const char *id, float x, float y, float z, float w) = 0;
virtual RTWError SetRegion(RTWVolume, void *source, const rtw::vec3i &regionCoords, const rtw::vec3i &regionSize) = 0;
virtual void Commit(RTWObject) = 0;
virtual float RenderFrame(RTWFrameBuffer, RTWRenderer, const uint32_t frameBufferChannels) = 0;
virtual void FrameBufferClear(RTWFrameBuffer, const uint32_t frameBufferChannels) = 0;
virtual const void* MapFrameBuffer(RTWFrameBuffer, const RTWFrameBufferChannel) = 0;
virtual void UnmapFrameBuffer(const void *mapped, RTWFrameBuffer) = 0;
virtual void SetDepthNormalizationGL(RTWFrameBuffer frameBuffer, float clipMin, float clipMax) = 0;
virtual int GetColorTextureGL(RTWFrameBuffer frameBuffer) = 0;
virtual int GetDepthTextureGL(RTWFrameBuffer frameBuffer) = 0;
// Convenience functions (TODO remove)
inline void Setf(RTWObject object, const char *id, float x)
{
Set1f(object, id, x);
}
inline void Set3fv(RTWObject object, const char *id, const float *xyz)
{
Set3f(object, id, xyz[0], xyz[1], xyz[2]);
}
inline void SetVec2f(RTWObject object, const char *id, const rtw::vec2f &v)
{
Set2f(object, id, v.x, v.y);
}
};
}
#pragma once
#include <ospray/ospray.h>
#include "../Backend.h"
#include <stdlib.h>
#include <sstream>
#include <stdexcept>
#include <vector>
namespace RTW
{
OSPFrameBufferFormat convert(RTWFrameBufferFormat format)
{
switch (format)
{
case RTW_FB_RGBA8:
return OSP_FB_RGBA8;
default:
return OSP_FB_NONE;
}
}
OSPTextureFormat convert(RTWTextureFormat format)
{
switch (format)
{
case RTW_TEXTURE_RGBA8:
return OSP_TEXTURE_RGBA8;
case RTW_TEXTURE_RGBA32F:
return OSP_TEXTURE_RGBA32F;
case RTW_TEXTURE_RGB8:
return OSP_TEXTURE_RGB8;
case RTW_TEXTURE_RGB32F:
return OSP_TEXTURE_RGB32F;
case RTW_TEXTURE_R8:
return OSP_TEXTURE_R8;
case RTW_TEXTURE_R32F:
return OSP_TEXTURE_R32F;
}
}
/*
* Simple pass-through backend for OSPRay.
*/
class OSPRayBackend : public Backend
{
public:
RTWError Init(int *, const char **)
{
RTWError ret = RTW_UNKNOWN_ERROR;
int ac = 1;
const char* envArgs = getenv("VTKOSPRAY_ARGS");
if (envArgs)
{
std::stringstream ss(envArgs);
std::string arg;
std::vector<std::string> args;
while (ss >> arg)
{
args.push_back(arg);
}
ac = static_cast<int>(args.size() + 1);
const char** av = new const char*[ac];
av[0] = "pvOSPRay";
for (int i = 1; i < ac; i++)
{
av[i] = args[i - 1].c_str();
}
try
{
ret = static_cast<RTWError>(ospInit(&ac, av));
}
catch (std::runtime_error &)
{
Shutdown();
}
delete[] av;
}
else
{
const char* av[] = { "pvOSPRay\0" };
try
{
ret = static_cast<RTWError>(ospInit(&ac, av));
}
catch (std::runtime_error &)
{
Shutdown();
}
}
return ret;
}
void Shutdown()
{
ospShutdown();
}
bool IsSupported(RTWFeature feature) const
{
switch (feature)
{
case RTW_DEPTH_NORMALIZATION:
return false;
case RTW_OPENGL_INTEROP:
return false;
case RTW_ANIMATED_PARAMETERIZATION:
return false;
case RTW_INSTANCING:
return true;
case RTW_DENOISER:
return false; // OpenImageDenoise is an external lib outside of the backend
case RTW_DEPTH_COMPOSITING:
return true;
}
return false;
}
RTWData NewData(size_t numElements, RTWDataType dataType, const void *source, const uint32_t dataCreationFlags)
{
return reinterpret_cast<RTWData>(ospNewData(numElements, static_cast<OSPDataType>(dataType), source, dataCreationFlags));
}
RTWGeometry NewGeometry(const char *type)
{
return reinterpret_cast<RTWGeometry>(ospNewGeometry(type));
}
RTWTexture NewTexture(const char* type)
{
return reinterpret_cast<RTWTexture>(ospNewTexture(type));
}
RTWLight NewLight(RTWRenderer renderer, const char *type)
{
return reinterpret_cast<RTWLight>(ospNewLight(reinterpret_cast<OSPRenderer>(renderer), type));
}
RTWLight NewLight2(const char *renderer_type, const char *light_type)
{
return reinterpret_cast<RTWLight>(ospNewLight2(renderer_type, light_type));
}
RTWLight NewLight3(const char *light_type)
{
return reinterpret_cast<RTWLight>(ospNewLight3(light_type));
}
RTWMaterial NewMaterial(RTWRenderer renderer, const char *material_type)
{
return reinterpret_cast<RTWMaterial>(ospNewMaterial(reinterpret_cast<OSPRenderer>(renderer), material_type));
}
RTWMaterial NewMaterial2(const char *renderer_type, const char *material_type)
{
return reinterpret_cast<RTWMaterial>(ospNewMaterial2(renderer_type, material_type));
}
RTWVolume NewVolume(const char *type)
{
return reinterpret_cast<RTWVolume>(ospNewVolume(type));
}
RTWTransferFunction NewTransferFunction(const char *type)
{
return reinterpret_cast<RTWTransferFunction>(ospNewTransferFunction(type));
}
RTWRenderer NewRenderer(const char *type)
{
return reinterpret_cast<RTWRenderer>(ospNewRenderer(type));
}
RTWCamera NewCamera(const char *type)
{
return reinterpret_cast<RTWCamera>(ospNewCamera(type));
}
RTWModel NewModel()
{
return reinterpret_cast<RTWModel>(ospNewModel());
}
RTWGeometry NewInstance(RTWModel modelToInstantiate, const rtw::affine3f &transform)
{
osp::affine3f xfm;
memcpy(&xfm, &transform, sizeof(osp::affine3f));
return reinterpret_cast<RTWGeometry>(ospNewInstance(reinterpret_cast<OSPModel>(modelToInstantiate), xfm));
}
RTWFrameBuffer NewFrameBuffer(const rtw::vec2i &size, const RTWFrameBufferFormat format, const uint32_t frameBufferChannels)
{
return reinterpret_cast<RTWFrameBuffer>(ospNewFrameBuffer(osp::vec2i{ size.x, size.y }, convert(format), frameBufferChannels));
}
void Release(RTWObject object)
{
ospRelease(reinterpret_cast<OSPObject>(object));
}
void AddGeometry(RTWModel model, RTWGeometry geometry)
{
ospAddGeometry(reinterpret_cast<OSPModel>(model), reinterpret_cast<OSPGeometry>(geometry));
}
void AddVolume(RTWModel model, RTWVolume volume)
{
ospAddVolume(reinterpret_cast<OSPModel>(model), reinterpret_cast<OSPVolume>(volume));
}
void SetString(RTWObject object, const char *id, const char *s)
{
ospSetString(reinterpret_cast<OSPObject>(object), id, s);
}
void SetObject(RTWObject object, const char *id, RTWObject other)
{
ospSetObject(reinterpret_cast<OSPObject>(object), id, reinterpret_cast<OSPObject>(other));
}
void SetData(RTWObject object, const char *id, RTWData data)
{
ospSetData(reinterpret_cast<OSPObject>(object), id, reinterpret_cast<OSPData>(data));
}
void SetMaterial(RTWGeometry geometry, RTWMaterial material)
{
ospSetMaterial(reinterpret_cast<OSPGeometry>(geometry), reinterpret_cast<OSPMaterial>(material));
}
void Set1i(RTWObject object, const char *id, int32_t x)
{
ospSet1i(reinterpret_cast<OSPObject>(object), id, x);
}
void Set1f(RTWObject object, const char *id, float x)
{
ospSet1f(reinterpret_cast<OSPObject>(object), id, x);
}
void Set2f(RTWObject object, const char *id, float x, float y)
{
ospSet2f(reinterpret_cast<OSPObject>(object), id, x, y);
}
void Set2i(RTWObject object, const char *id, int x, int y)
{
ospSet2i(reinterpret_cast<OSPObject>(object), id, x, y);
}
void Set3i(RTWObject object, const char *id, int x, int y, int z)
{
ospSet3i(reinterpret_cast<OSPObject>(object), id, x, y, z);
}
void Set3f(RTWObject object, const char *id, float x, float y, float z)
{
ospSet3f(reinterpret_cast<OSPObject>(object), id, x, y, z);
}
void Set4f(RTWObject object, const char *id, float x, float y, float z, float w)
{
ospSet4f(reinterpret_cast<OSPObject>(object), id, x, y, z, w);
}
RTWError SetRegion(RTWVolume volume, void *source, const rtw::vec3i &regionCoords, const rtw::vec3i &regionSize)
{
return static_cast<RTWError>(ospSetRegion(reinterpret_cast<OSPVolume>(volume), source,
osp::vec3i{ regionCoords.x, regionCoords.y, regionCoords.z },
osp::vec3i{ regionSize.x, regionSize.y, regionSize.z }));
}
void Commit(RTWObject object)
{
ospCommit(reinterpret_cast<OSPObject>(object));
}
float RenderFrame(RTWFrameBuffer frameBuffer, RTWRenderer renderer, const uint32_t frameBufferChannels)
{
return ospRenderFrame(reinterpret_cast<OSPFrameBuffer>(frameBuffer), reinterpret_cast<OSPRenderer>(renderer), frameBufferChannels);
}
void FrameBufferClear(RTWFrameBuffer frameBuffer, const uint32_t frameBufferChannels)
{
ospFrameBufferClear(reinterpret_cast<OSPFrameBuffer>(frameBuffer), frameBufferChannels);
}
const void* MapFrameBuffer(RTWFrameBuffer frameBuffer, const RTWFrameBufferChannel channel)
{
return ospMapFrameBuffer(reinterpret_cast<OSPFrameBuffer>(frameBuffer), static_cast<OSPFrameBufferChannel>(channel));
}
void UnmapFrameBuffer(const void *mapped, RTWFrameBuffer frameBuffer)
{
ospUnmapFrameBuffer(mapped, reinterpret_cast<OSPFrameBuffer>(frameBuffer));
}
void SetDepthNormalizationGL(RTWFrameBuffer /*frameBuffer*/, float /*clipMin*/, float /*clipMax*/)
{
// not supported
}
int GetColorTextureGL(RTWFrameBuffer /*frameBuffer*/)
{
// not supported
return 0;
}
int GetDepthTextureGL(RTWFrameBuffer /*frameBuffer*/)
{
// not supported
return 0;
}
};
}
#include "Backend.h"
#ifdef VTK_ENABLE_OSPRAY
#include "OSPRay/OSPRayBackend.h"
#endif
#ifdef VTK_ENABLE_VISRTX
#include "VisRTX/VisRTXBackend.h"
#endif
#include "RTWrapper.h"
#include <iostream>
#include <sstream>
#include <algorithm>
#include <map>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <set>
#ifdef VTK_ENABLE_VISRTX
RTW::VisRTXBackend* rtwVisRTXBackend = nullptr;
#endif
#ifdef VTK_ENABLE_OSPRAY
RTW::OSPRayBackend* rtwOSPRayBackend = nullptr;
#endif
RTW::Backend *rtwInit(int *argc, const char **argv)
{
if (!strcmp(argv[0], "optix pathtracer"))
{
#ifdef VTK_ENABLE_VISRTX
if (!rtwVisRTXBackend)
{
rtwVisRTXBackend = new RTW::VisRTXBackend();
if (rtwVisRTXBackend->Init(argc, argv) != RTW_NO_ERROR)
{
std::cerr << "WARNING: Failed to initialize RTW VisRTX backend.\n";
rtwVisRTXBackend->Shutdown();
delete rtwVisRTXBackend;
rtwVisRTXBackend = nullptr;
}
}
return rtwVisRTXBackend;
#endif