Unresolved Symbol Error when OptixDenoiser used in seperate class

Hello,

I just came across a strange error: I’m trying to use the OptixDenoiser when generating soft shadows (which takes place in a separate rendering class OptixRenderer). So I wanted to separate the OptixDenoiser from the rendering class in a separate class:

#include <optix.h>

#include <optix/CUDABuffer.h> // defines a class that manages cuda memory and offers the device ptr and the size of the buffer
#include <optix/LaunchParams.h> // defines frame information such as the float4* frame buffer, an integer vec2 with the size of the current frame and an int as the frame_id

class Denoiser
{
	bool denoise;
	bool accumulate;

	OptixDenoiser denoiser = nullptr;

	CUDABuffer denoiser_scratch;
	CUDABuffer denoiser_state;


public:
	Denoiser();
	~Denoiser();

	bool is_denoising();
	bool is_accumulating();

	void on_render(const FrameInfo& frame, const CUDABuffer& render_buffer, const CUDABuffer& denoised_buffer);
	void on_resize(const OptixDeviceContext& device_context, const glm::ivec2& new_size);
};

And the implementation looks like this:

#include <optix/Denoiser.h>
#include <optix/OptixUtil.h> // contains functions for CUDA_CHECK and CUDA_SYNC

cgbv::optix::Denoiser::Denoiser()
{
    denoise = false;
    accumulate = false;
}

cgbv::optix::Denoiser::~Denoiser()
{
    if (denoiser)
        optix::error::check(optixDenoiserDestroy(denoiser));
}

bool cgbv::optix::Denoiser::is_denoising()
{
	return denoise;
}

bool cgbv::optix::Denoiser::is_accumulating()
{
	return accumulate;
}

void cgbv::optix::Denoiser::on_render(const FrameInfo& frame, const CUDABuffer& render_buffer, const CUDABuffer& denoised_buffer)
{
    OptixDenoiserParams denoiser_params;
    denoiser_params.denoiseAlpha = OPTIX_DENOISER_ALPHA_MODE_ALPHA_AS_AOV;
    denoiser_params.hdrIntensity = static_cast<CUdeviceptr>(0);

    denoiser_params.blendFactor = !accumulate ? 0.f : 1.f / static_cast<float>(frame.frame_id);
    
    OptixImage2D input_layer;
    input_layer.data = render_buffer.get_device_pointer();
    input_layer.width = frame.size.x;
    input_layer.height = frame.size.y;
    input_layer.rowStrideInBytes = frame.size.x * sizeof(float4);
    input_layer.pixelStrideInBytes = sizeof(float4);
    input_layer.format = OPTIX_PIXEL_FORMAT_FLOAT4;
    
    OptixImage2D output_layer;
    output_layer.data = denoised_buffer.get_device_pointer();
    output_layer.width = frame.size.x;
    output_layer.height = frame.size.y;
    output_layer.rowStrideInBytes = frame.size.x * sizeof(float4);
    output_layer.pixelStrideInBytes = sizeof(float4);
    output_layer.format = OPTIX_PIXEL_FORMAT_FLOAT4;

    if (denoise) 
    {
        OptixDenoiserGuideLayer denoiser_guide_layer = {};

        OptixDenoiserLayer denoiser_layer = {};
        denoiser_layer.input = input_layer;
        denoiser_layer.output = output_layer;

        optix::error::check(optixDenoiserInvoke(denoiser, 0, &denoiser_params, denoiser_state.get_device_pointer(), denoiser_state.get_size_in_bytes(), &denoiser_guide_layer, &denoiser_layer, 1, 0, 0, denoiser_scratch.get_device_pointer(), denoiser_scratch.get_size_in_bytes()));
    }
    else 
    {
        cudaMemcpy((void*)output_layer.data, (void*)input_layer.data, output_layer.width * output_layer.height * sizeof(float4), cudaMemcpyDeviceToDevice);
    }
}

void cgbv::optix::Denoiser::on_resize(const OptixDeviceContext& device_context, const glm::ivec2& new_size)
{
    if (denoiser) 
        optix::error::check(optixDenoiserDestroy(denoiser));

    OptixDenoiserOptions denoiser_options = {};

    optix::error::check(optixDenoiserCreate(device_context, OPTIX_DENOISER_MODEL_KIND_LDR, &denoiser_options, &denoiser));

    OptixDenoiserSizes denoiser_return_sizes;

    optix::error::check(optixDenoiserComputeMemoryResources(denoiser, new_size.x, new_size.y, &denoiser_return_sizes));

    denoiser_scratch.resize(std::max(denoiser_return_sizes.withOverlapScratchSizeInBytes, denoiser_return_sizes.withoutOverlapScratchSizeInBytes));

    denoiser_state.resize(denoiser_return_sizes.stateSizeInBytes);

    optix::error::check(optixDenoiserSetup(denoiser, 0, new_size.x, new_size.y, denoiser_state.get_device_pointer(), denoiser_state.get_size_in_bytes(), denoiser_scratch.get_device_pointer(), denoiser_scratch.get_size_in_bytes()));
}

So, if I encapsulate the OptixDenoiser like that, the linker gives me errors for unresolved symbols for basically any optixDenoiser function called (like optixDenoiserSetup, optixDenoiserComputeMemoryResources, optixDenoiserCreate etc.), however, if I place it directly inside the OptixRenderer code, everything is fine.

I’m wondering why linking fails when I use the separate class instead of the directly nested code. It’s the same project with the same path settings for all files and since they are both *.cpp files, they are not compiled with nvcc but with a standard msvc.

Am I missing something regarding the compilation of OptixDenoiser-related functions?

Thank you and kind regards,

Markus

The OptiX API entry point functions are loaded dynamically from the driver module into the OptixFunctionTable structure and do not actually exist inside the global namespace unless you used that OptiX SDK helper header optix_stubs.h which wraps and lifts them into the global namespace.
These functions do not exist inside the global namespace when not including that header.

That’s not the only way to call these functions. In my OptiX 7 examples I load them explicitly per device into an OptixFuctionTable m_api here: https://github.com/NVIDIA/OptiX_Apps/blob/master/apps/rtigo10/src/Device.cpp#L579

Alright, thank you. Fixed the issue.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.