CPlusPlus TOP (CUDA): Is it possible to use more than one input?

Hi everyone,

I’m currently working on a custom CPlusPlus TOP using CUDA and I’m trying to figure out whether it’s possible to access more than one input TOP inside the plugin.

In my test the Cuda array for the second input is always null.

The CPlusPlus TOP documentation page only lists Input 0, and I don’t see any mention of supporting multiple inputs in the API reference:
https://docs.derivative.ca/CPlusPlus_TOP

My questions:

  • Is a CPlusPlus TOP limited to a single wired input by design?

  • If multiple inputs are supported, how are they accessed in the C++/CUDA API?

My goal is to process two (or more) textures directly inside CUDA without having to merge them upstream unless that’s the only option.

Am I missing something?

Any clarification would be greatly appreciated.

Thanks a lot!

Hey, yes this should be possible. Can you paste your code so I can take a look at that?

Hi Malcom,

after having trouble with multiple inputs I started again with a fresh CudaTop example.

In my header i just added a second surfaceobject:
cudaSurfaceObject_t myInputSurface;
cudaSurfaceObject_t myInputSurface2;

And then i added a bunch of debug messages to my execute:

void CudaTOP::execute(TOP_Output* output, const OP_Inputs* inputs, void* reserved){

myError = nullptr;
myExecuteCount++;

TOP_CUDAOutputInfo info;
info.textureDesc.width = 256;
info.textureDesc.height = 256;
info.textureDesc.texDim = OP_TexDim::e2D;
info.textureDesc.pixelFormat = OP_PixelFormat::BGRA8Fixed;
info.stream = myStream;

float ratio = static_cast<float>(info.textureDesc.height) / static_cast<float>(info.textureDesc.width);

const OP_CUDAArrayInfo* inputArray = nullptr;
const OP_CUDAArrayInfo* inputArray2 = nullptr;

std::cout << "Number of inputs: " << inputs->getNumInputs() << std::endl;

if (inputs->getNumInputs() > 0)
{
	const OP_TOPInput* topInput = inputs->getInputTOP(0);
	
	if (!topInput)
	{
		std::cout << "Input 1 TOP is null" << std::endl;
	}
	else
	{
		std::cout << "Input 1 TOP acquired, width: " << topInput->textureDesc.width
		          << " height: " << topInput->textureDesc.height << std::endl;

		// Make our output texture match our input texture.
		info.textureDesc = topInput->textureDesc;

		OP_CUDAAcquireInfo acquireInfo;
		acquireInfo.stream = myStream;
		inputArray = topInput->getCUDAArray(acquireInfo, nullptr);
		
		// Validate first input CUDA array acquisition
		if (!inputArray)
		{
			std::cout << "Failed to acquire CUDA array for input 1" << std::endl;
		}
		else
		{
			std::cout << "Input 1 CUDA array acquired (cudaArray will be filled after beginCUDAOperations)" << std::endl;
		}
	}
}

// Get second input if available
if (inputs->getNumInputs() > 1)
{
	const OP_TOPInput* topInput2 = inputs->getInputTOP(1);
	
	if (!topInput2)
	{
		std::cout << "Input 2 TOP is null" << std::endl;
	}
	else
	{
		std::cout << "Input 2 TOP acquired, width: " << topInput2->textureDesc.width
		          << " height: " << topInput2->textureDesc.height << std::endl;

		OP_CUDAAcquireInfo acquireInfo2;
		acquireInfo2.stream = myStream;
		inputArray2 = topInput2->getCUDAArray(acquireInfo2, nullptr);
		
		// Validate second input CUDA array acquisition
		if (!inputArray2)
		{
			std::cout << "Failed to acquire CUDA array for input 2" << std::endl;
		}
		else
		{
			std::cout << "Input 2 CUDA array acquired (cudaArray will be filled after beginCUDAOperations)" << std::endl;
		}
	}
}

// Primary output
const OP_CUDAArrayInfo* outputInfo = output->createCUDAArray(info, nullptr);
if (!outputInfo)
	return;

// Output to a second color buffer, with a different resolution. Use a Render Select TOP
// to get this output.
TOP_CUDAOutputInfo auxInfo;
auxInfo.textureDesc.pixelFormat = OP_PixelFormat::BGRA8Fixed;
auxInfo.textureDesc.width = 1280;
auxInfo.textureDesc.height = 720;
auxInfo.textureDesc.texDim = OP_TexDim::e2D;
auxInfo.colorBufferIndex = 1;
auxInfo.stream = myStream;
const OP_CUDAArrayInfo* auxOutputInfo = output->createCUDAArray(auxInfo, nullptr);
if (!auxOutputInfo)
	return;

// All calls to the 'inputs' need to be made before beginCUDAOperations() is called
double color1[3];
inputs->getParDouble3("Color1", color1[0], color1[1], color1[2]);
double color2[3];
inputs->getParDouble3("Color2", color2[0], color2[1], color2[2]);

// Now that we have gotten all of the pointers to the OP_CUDAArrayInfos that we may want, we can tell the context
// that we are going to start doing CUDA operations. This will cause the cudaArray members of the OP_CUDAArrayInfo
// to get filled in with valid addresses.
if (!myContext->beginCUDAOperations(nullptr))
	return;

// Now validate the CUDA arrays after beginCUDAOperations has filled them in
if (inputArray)
{
	if (!inputArray->cudaArray)
	{
		std::cout << "WARNING: Input 1 cudaArray is null after beginCUDAOperations" << std::endl;
		// Set to nullptr so we don't try to use it
		inputArray = nullptr;
	}
	else
	{
		std::cout << "Input 1 cudaArray is valid after beginCUDAOperations" << std::endl;
	}
}

if (inputArray2)
{
	if (!inputArray2->cudaArray)
	{
		std::cout << "WARNING: Input 2 cudaArray is null after beginCUDAOperations - will not be used" << std::endl;
		// Set to nullptr so we don't try to use it
		//inputArray2 = nullptr;
	}
	else
	{
		std::cout << "Input 2 cudaArray is valid after beginCUDAOperations" << std::endl;
	}
}

// Setup output surface and validate
if (outputInfo && outputInfo->cudaArray)
{
	setupCudaSurface(&myOutputSurfaces[0], outputInfo->cudaArray);
	if (!myOutputSurfaces[0])
	{
		std::cout << "Failed to create surface object for primary output" << std::endl;
	}
	else
	{
		std::cout << "Primary output surface created successfully" << std::endl;
	}
}
else
{
	std::cout << "Failed to create output CUDA array" << std::endl;
	myError = "Failed to create output CUDA array";
	return;
}

// Setup first input surface if available and validate
if (inputArray)
{
	setupCudaSurface(&myInputSurface, inputArray->cudaArray);
	if (!myInputSurface)
	{
		std::cout << "Failed to create surface object for input 1" << std::endl;
	}
	else
	{
		std::cout << "Input 1 surface created successfully" << std::endl;
	}
}
else
{
	if (myInputSurface)
	{
		cudaDestroySurfaceObject(myInputSurface);
		myInputSurface = 0;
	}
}

// Setup second input surface if available and validate
if (inputArray2)
{
	setupCudaSurface(&myInputSurface2, inputArray2->cudaArray);
	if (!myInputSurface2)
	{
		std::cout << "Failed to create surface object for input 2" << std::endl;
	}
	else
	{
		std::cout << "Input 2 surface created successfully" << std::endl;
	}
}
else
{
	if (myInputSurface2)
	{
		cudaDestroySurfaceObject(myInputSurface2);
		myInputSurface2 = 0;
	}
}

// Validate surfaces before passing to kernel
if (!myOutputSurfaces[info.colorBufferIndex])
{
	std::cout << "Invalid output surface for primary buffer" << std::endl;
	myError = "Invalid output surface for primary buffer";
	return;
}
	
float4 c;
c.x = (float)color1[0];
c.y = (float)color1[1];
c.z = (float)color1[2];
c.w = 1.0f;

doCUDAOperation(info.textureDesc.width, info.textureDesc.height, info.textureDesc.depth, info.textureDesc.texDim, c, myInputSurface, myInputSurface2,  myOutputSurfaces[info.colorBufferIndex], myStream);

// Setup auxiliary output surface and validate
if (auxOutputInfo && auxOutputInfo->cudaArray)
{
	setupCudaSurface(&myOutputSurfaces[auxInfo.colorBufferIndex], auxOutputInfo->cudaArray);
	if (!myOutputSurfaces[auxInfo.colorBufferIndex])
	{
		std::cout << "Failed to create surface object for auxiliary output" << std::endl;
		myError = "Invalid output surface for auxiliary buffer";
		return;
	}
	else
	{
		std::cout << "Auxiliary output surface created successfully" << std::endl;
	}
}
else
{
	std::cout << "Failed to create auxiliary output CUDA array" << std::endl;
	myError = "Failed to create auxiliary output CUDA array";
	return;
}
	
c.x = (float)color2[0];
c.y = (float)color2[1];
c.z = (float)color2[2];
c.w = 1.0f;

//doCUDAOperation(auxInfo.textureDesc.width, auxInfo.textureDesc.height, auxInfo.textureDesc.depth, auxInfo.textureDesc.texDim, c, 0, myOutputSurfaces[auxInfo.colorBufferIndex], myStream);

myContext->endCUDAOperations(nullptr);
}

My Output is as follows:

Number of inputs: 2
Input 1 TOP acquired, width: 1280 height: 720
Input 1 CUDA array acquired (cudaArray will be filled after beginCUDAOperations)
Input 2 TOP acquired, width: 1280 height: 720
Input 2 CUDA array acquired (cudaArray will be filled after beginCUDAOperations)
Input 1 cudaArray is valid after beginCUDAOperations
WARNING: Input 2 cudaArray is null after beginCUDAOperations - will not be used
Primary output surface created successfully
Input 1 surface created successfully
Failed to create surface object for input 2
Auxiliary output surface created successfully

I have done multiple output CudaTop multiple times, this is my first try with multiple inputs.

I dont know what i am missing.

Thanks a lot for taking a look!!!

Thanks for the example and report. This bug will be fixed in builds 2025.32401 and later.

Great, thanks a lot!