Optix 9.1 illegal memory access

Hi,

I’m new to raytracing and I am building a simple renderer for a university project. I created a single OptixModule with one Pipeline and was able to trace Radiance and Shadow Rays.

However now i wanted to create a second Module and Pipeline which Creates Photon rays from my light sources into the world.

At the top of my file I created:

OptixModule optixModule = nullptr;
OptixModule photonModule = nullptr;
OptixPipeline pipeline = nullptr;
OptixPipeline photonPipeline = nullptr;
OptixProgramGroup raygenPG = nullptr;
OptixProgramGroup missPG = nullptr;
OptixProgramGroup hitgroupPG = nullptr;
OptixProgramGroup shadowMissPG = nullptr;
OptixProgramGroup shadowHitgroupPG = nullptr;
OptixProgramGroup photonRaygenPG = nullptr;
OptixProgramGroup photonMissPG = nullptr;
OptixProgramGroup photonHitgroupPG = nullptr

And in my init function I do:

CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&p_d_params), sizeof(PhotonLaunchParams)));
CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&d_params), sizeof(LaunchParams)));

and call:

void initOptixPipeline() {
    // 1. Compile & Link Options
    OptixModuleCompileOptions moduleCompileOptions = {};
    moduleCompileOptions.maxRegisterCount = OPTIX_COMPILE_DEFAULT_MAX_REGISTER_COUNT;
    moduleCompileOptions.optLevel = OPTIX_COMPILE_OPTIMIZATION_DEFAULT;
    moduleCompileOptions.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_DEFAULT;

    OptixPipelineCompileOptions pipelineCompileOptions = {};
    pipelineCompileOptions.usesMotionBlur = false;
    pipelineCompileOptions.traversableGraphFlags = OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING;
	pipelineCompileOptions.numPayloadValues = 3; // p0, p1, p2 for rgb
    pipelineCompileOptions.numAttributeValues = 2; // Barycentrics
    pipelineCompileOptions.exceptionFlags = OPTIX_EXCEPTION_FLAG_NONE;
    pipelineCompileOptions.pipelineLaunchParamsVariableName = "params";

    OptixPipelineCompileOptions photonPipelineCompileOptions = {};
    photonPipelineCompileOptions.usesMotionBlur = false;
    photonPipelineCompileOptions.traversableGraphFlags = OPTIX_TRAVERSABLE_GRAPH_FLAG_ALLOW_SINGLE_LEVEL_INSTANCING;
    photonPipelineCompileOptions.numPayloadValues = 1; // just p0
    photonPipelineCompileOptions.numAttributeValues = 2;
    photonPipelineCompileOptions.exceptionFlags = OPTIX_EXCEPTION_FLAG_NONE;
    photonPipelineCompileOptions.pipelineLaunchParamsVariableName = "pparams";

    // 2. PTX to Module
    std::string ptxCode = readPtxFile("out/build/x64-Debug/raytrace.ptx");

    char log[2048];
    size_t sizeof_log = sizeof(log);

    OPTIX_CHECK(optixModuleCreate(
        optixContext,
        &moduleCompileOptions,
        &pipelineCompileOptions,
        ptxCode.c_str(),
        ptxCode.size(),
        log, &sizeof_log,
        &optixModule
    ));

    std::string photonPtx = readPtxFile("out/build/x64-Debug/photons.ptx");

    OPTIX_CHECK(optixModuleCreate(
        optixContext,
        &moduleCompileOptions,
        &photonPipelineCompileOptions,
        photonPtx.c_str(),
        photonPtx.size(),
        log,
        &sizeof_log,
        &photonModule
    ));

    // 3. Program Groups
    OptixProgramGroupOptions pgOptions = {};

    // Raygen PG
    sizeof_log = sizeof(log);
    OptixProgramGroupDesc raygenDesc = {};
    raygenDesc.kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN;
    raygenDesc.raygen.module = optixModule;
    raygenDesc.raygen.entryFunctionName = "__raygen__render";
    OPTIX_CHECK(optixProgramGroupCreate(optixContext, &raygenDesc, 1, &pgOptions, log, &sizeof_log, &raygenPG));

    // Miss PG
    sizeof_log = sizeof(log);
    OptixProgramGroupDesc missDesc = {};
    missDesc.kind = OPTIX_PROGRAM_GROUP_KIND_MISS;
    missDesc.miss.module = optixModule;
    missDesc.miss.entryFunctionName = "__miss__radiance";
    OPTIX_CHECK(optixProgramGroupCreate(optixContext, &missDesc, 1, &pgOptions, log, &sizeof_log, &missPG));

    // Closest Hit
    sizeof_log = sizeof(log);
    OptixProgramGroupDesc hitDesc = {};
    hitDesc.kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP;
    hitDesc.hitgroup.moduleCH = optixModule;
    hitDesc.hitgroup.entryFunctionNameCH = "__closesthit__radiance";
    OPTIX_CHECK(optixProgramGroupCreate(optixContext, &hitDesc, 1, &pgOptions, log, &sizeof_log, &hitgroupPG));

    sizeof_log = sizeof(log);
    OptixProgramGroupDesc shadowMissDesc = {};
    shadowMissDesc.kind = OPTIX_PROGRAM_GROUP_KIND_MISS;
    shadowMissDesc.miss.module = optixModule;
    shadowMissDesc.miss.entryFunctionName = "__miss__shadow";
    OPTIX_CHECK(optixProgramGroupCreate(optixContext, &shadowMissDesc, 1, &pgOptions, log, &sizeof_log, &shadowMissPG));

    sizeof_log = sizeof(log);
    OptixProgramGroupDesc shadowHitDesc = {};
    shadowHitDesc.kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP;
    shadowHitDesc.hitgroup.moduleAH = optixModule;
    shadowHitDesc.hitgroup.entryFunctionNameAH = "__anyhit__shadow";
    OPTIX_CHECK(optixProgramGroupCreate(optixContext, &shadowHitDesc, 1, &pgOptions, log, &sizeof_log, &shadowHitgroupPG));

	// Photon Raygen PG
    sizeof_log = sizeof(log);
    OptixProgramGroupDesc photonRaygenDesc = {};
    photonRaygenDesc.kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN;
    photonRaygenDesc.raygen.module = photonModule;
    photonRaygenDesc.raygen.entryFunctionName = "__raygen__photon";
    OPTIX_CHECK(optixProgramGroupCreate(optixContext, &photonRaygenDesc, 1, &pgOptions, log, &sizeof_log, &photonRaygenPG));

    // Photon Miss PG
    sizeof_log = sizeof(log);
    OptixProgramGroupDesc photonMissDesc = {};
    photonMissDesc.kind = OPTIX_PROGRAM_GROUP_KIND_MISS;
    photonMissDesc.miss.module = photonModule;
    photonMissDesc.miss.entryFunctionName = "__miss__photon";
    OPTIX_CHECK(optixProgramGroupCreate(optixContext, &photonMissDesc, 1, &pgOptions, log, &sizeof_log, &photonMissPG));

    // Photon Closest Hit
    sizeof_log = sizeof(log);
    OptixProgramGroupDesc photonHitDesc = {};
    photonHitDesc.kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP;
    photonHitDesc.hitgroup.moduleCH = photonModule;
    photonHitDesc.hitgroup.entryFunctionNameCH = "__closesthit__photon";
    // Wir brauchen keinen Any-Hit oder Intersection-Shader für einfache Dreiecke
    OPTIX_CHECK(optixProgramGroupCreate(optixContext, &photonHitDesc, 1, &pgOptions, log, &sizeof_log, &photonHitgroupPG));


    // 4. Pipelines verknüpfen
    sizeof_log = sizeof(log);
    OptixProgramGroup programGroups[] = { raygenPG, missPG, shadowMissPG, hitgroupPG, shadowHitgroupPG };
	OptixProgramGroup photonProgramGroups[] = { photonRaygenPG, photonMissPG, photonHitgroupPG };

    OptixPipelineLinkOptions pipelineLinkOptions = {};
    pipelineLinkOptions.maxTraceDepth = 3; // Wie oft ein Strahl reflektieren darf

    OPTIX_CHECK(optixPipelineCreate(
        optixContext,
        &pipelineCompileOptions,
        &pipelineLinkOptions,
        programGroups, sizeof(programGroups) / sizeof(programGroups[0]),
        log, &sizeof_log,
        &pipeline
    ));

	OPTIX_CHECK(optixPipelineCreate(
		optixContext,
		&photonPipelineCompileOptions,
		&pipelineLinkOptions,
		photonProgramGroups, sizeof(photonProgramGroups) / sizeof(photonProgramGroups[0]),
		log, &sizeof_log,
		&photonPipeline
	));

    OptixStackSizes stackSizes = {};
    for (OptixProgramGroup programGroup : programGroups) {
        OPTIX_CHECK(optixUtilAccumulateStackSizes(programGroup, &stackSizes, pipeline));
    }

    unsigned int directCallableStackSizeFromTraversal = 0;
    unsigned int directCallableStackSizeFromState = 0;
    unsigned int continuationStackSize = 0;

    OPTIX_CHECK(optixUtilComputeStackSizes(
        &stackSizes,
        pipelineLinkOptions.maxTraceDepth,
        0,
        0,
        &directCallableStackSizeFromTraversal,
        &directCallableStackSizeFromState,
        &continuationStackSize
    ));

    OPTIX_CHECK(optixPipelineSetStackSize(
        pipeline,
        directCallableStackSizeFromTraversal,
        directCallableStackSizeFromState,
        continuationStackSize,
        2
    ));
}

After that in my render loop I call

// Photon Pipeline Parameter
PhotonLaunchParams photonParams = {};
photonParams.traversable = iasHandle;

photonParams.lightCount = static_cast<int>(lights.size());
for (int i = 0; i < photonParams.lightCount; ++i) {
    photonParams.lights[i] = lights[i];
}
photonParams.photonCount = 1000000; // Anzahl der Photonen, die wir abfeuern wollen

photonParams.lvData = reinterpret_cast<float*>(d_volumeData);
photonParams.lvDim = make_uint3(32, 32, 32);
photonParams.lvMin = make_float3(-18.0f, -7.0f, -18.0f);
photonParams.lvMax = make_float3(18.0f, 10.0f, 18.0f);


CUDA_CHECK(cudaMemcpy(reinterpret_cast<void*>(p_d_params), &photonParams, sizeof(PhotonLaunchParams), cudaMemcpyHostToDevice));

// Photon aufrufen
OPTIX_CHECK(optixLaunch(
    photonPipeline,
    0,             // CUDA stream
    p_d_params,      // Pointer zu unseren gemappten LaunchParams
    sizeof(PhotonLaunchParams),
    &sbt,          // Shader Binding Table
    photonParams.photonCount,  // Threads in X 
    photonParams.lightCount, // Threads in Y
    1              // Threads in Z
));
CUDA_CHECK(cudaDeviceSynchronize());
CUDA_CHECK(cudaGetLastError());

before I call the OptixLaunch for my raytracing pipeline.

Somehow it gives me

cudaDeviceSynchronize() : an illegal memory access was encountered

right there and if I start it with the compute sanitizer it says this:

========= Program hit cudaErrorUnknown (error 999) due to “unknown error” on CUDA API call to cudaDeviceSynchronize.
========= Saved host backtrace up to driver entry point at error
========= Host Frame: cuProfilerStop [0x7ffed6c610d2] in nvcuda64.dll
========= Host Frame: _cudaGetProcAddress [0x7ffef4cb195f] in nvcudart_hybrid64.dll
========= Host Frame: MainApp::render in main.cpp:1342 [0x12eb2d] in cgintro_animation.exe
========= Host Frame: App::run in app.cpp:238 [0x139c70] in cgintro_animation.exe
========= Host Frame: main in main.cpp:1426 [0x525c6] in cgintro_animation.exe

cudaDeviceSynchronize() : unknown error

So I can’t really figure out why this is happening and any attempts to fix it didn’t succeed or just created more errors.

I would really appreciate help :)

Illegal memory access means your device code is reading from or writing to some device memory at the wrong address. That could be many things. Hard to tell without seeing all code.

You did not include code which calculates the stack size for the photon pipeline. The internal stack size calculation in OptiX is not working for all pipeline setups, but you did not use callables, so it might have worked. It’s recommended to always calculate and set the OptiX stack size for all your pipelines to the minimum necessary size.

You did not provide any code about the shader binding table setup. Make sure the records are correct for either pipeline.

The launch parameters reside in constant memory which is limited to 64kB. Depending on the number of lights, it might make sense to put them into their own global memory and only store their device pointer inside the launch params.

You did not provide any allocation information about the result output buffers. Make sure these are properly sized for each pipeline.

Only during debugging, enable the OptiX validation mode and provide an exception program which prints the exception codes and enable the exceptionFlags inside the pipeline compile options. Both these things cost performance. Do not leave those enabled in the released program. (Find example code inside the OptiX 7 Advanced Examples on github in the sticky posts.)

To isolate the error inside the device code, you could reduce the launch dimensions to 1x1 and if that still fails, sprinkle CUDA printf calls at strategic places to see how far your code gets. If it needs bigger launch dimensions to fail, the printf output of many threads could become unwieldy and might need to be limited to a single failing launch index.

You could also try building the host and device code with full debug information which only works when targeting OptiX-IR instead of PTX. (Note that OptiX-IR is a binary format and needs a different read file mechanism.) Then change the pipeline compilation options for debugging, and debug that with Nsight Visual Studio Edition resp. GDB. That might be able to halt on the illegal memory access with source code reference.

(As a general note, make very sure all your device memory accesses fulfill the CUDA data alignment requirements, though that would be a different CUDA error about misaligned accesses.

Also usesMotionBlur fields are int, not bool. Bad OptiX SDK example code. :-))

Hi, thanks for the answer!

I indeed missed stack size calculation and SBT for the second pipeline. But the error still persists.

This is my SBT setup now:

    void buildSBT() {
        // 1. Raygen Record
        SbtRecord<int> rgRecord = {};
        optixSbtRecordPackHeader(raygenPG, &rgRecord);
        CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&d_raygenRecord), sizeof(rgRecord)));
        CUDA_CHECK(cudaMemcpy(reinterpret_cast<void*>(d_raygenRecord), &rgRecord, sizeof(rgRecord), cudaMemcpyHostToDevice));
        sbt.raygenRecord = d_raygenRecord;

        SbtRecord<int> photonrgRecord = {};
        optixSbtRecordPackHeader(photonRaygenPG, &photonrgRecord);
        CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&p_d_raygenRecord), sizeof(photonrgRecord)));
        CUDA_CHECK(cudaMemcpy(reinterpret_cast<void*>(p_d_raygenRecord), &photonrgRecord, sizeof(photonrgRecord), cudaMemcpyHostToDevice));
        photonSbt.raygenRecord = p_d_raygenRecord;

        // 2. Miss Record
        std::vector<SbtRecord<int>> missRecords(2);
        optixSbtRecordPackHeader(missPG, &missRecords[0]);        // Radiance miss
        optixSbtRecordPackHeader(shadowMissPG, &missRecords[1]);  // Shadow miss

        CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&d_missRecord), sizeof(SbtRecord<int>) * missRecords.size()));
        CUDA_CHECK(cudaMemcpy(reinterpret_cast<void*>(d_missRecord), missRecords.data(), sizeof(SbtRecord<int>) * missRecords.size(), cudaMemcpyHostToDevice));

        sbt.missRecordBase = d_missRecord;
        sbt.missRecordStrideInBytes = sizeof(SbtRecord<int>);
        sbt.missRecordCount = static_cast<unsigned>(missRecords.size());

		SbtRecord<int> photonMissRecord = {};
		optixSbtRecordPackHeader(photonMissPG, &photonMissRecord);
		CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&p_d_missRecord), sizeof(photonMissRecord)));
        CUDA_CHECK(cudaMemcpy(reinterpret_cast<void*>(p_d_missRecord), &photonMissRecord, sizeof(photonMissRecord), cudaMemcpyHostToDevice));
		photonSbt.missRecordBase = p_d_missRecord;
		photonSbt.missRecordStrideInBytes = sizeof(SbtRecord<int>);
        photonSbt.missRecordCount = 1;

        // 3. Hitgroup Record
        hitRecords.clear();
        hitRecords.reserve(sceneObjects.size() * 2);

        for (size_t i = 0; i < sceneObjects.size(); ++i) {
            const auto& obj = sceneObjects[i];

            // Radiance record
            SbtRecord<HitgroupData> radianceRec = {};
            optixSbtRecordPackHeader(hitgroupPG, &radianceRec);
            radianceRec.data.vertices = reinterpret_cast<float3*>(obj.mesh.d_vertices);
            radianceRec.data.indices = reinterpret_cast<uint3*>(obj.mesh.d_indices);
            radianceRec.data.color = make_float3(obj.color.x, obj.color.y, obj.color.z);
			radianceRec.data.reflectivity = obj.reflectivity;
            hitRecords.push_back(radianceRec);

            // Shadow record
            SbtRecord<HitgroupData> shadowRec = {};
            optixSbtRecordPackHeader(shadowHitgroupPG, &shadowRec);
            shadowRec.data.vertices = reinterpret_cast<float3*>(obj.mesh.d_vertices);
            shadowRec.data.indices = reinterpret_cast<uint3*>(obj.mesh.d_indices);
            shadowRec.data.color = make_float3(obj.color.x, obj.color.y, obj.color.z);
            shadowRec.data.reflectivity = 0.0f;
            hitRecords.push_back(shadowRec);
        }

        CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&d_hitgroupRecord), sizeof(SbtRecord<HitgroupData>) * hitRecords.size()));

        CUDA_CHECK(cudaMemcpy(reinterpret_cast<void*>(d_hitgroupRecord), hitRecords.data(), sizeof(SbtRecord<HitgroupData>) * hitRecords.size(), cudaMemcpyHostToDevice));

        sbt.hitgroupRecordBase = d_hitgroupRecord;
        sbt.hitgroupRecordStrideInBytes = sizeof(SbtRecord<HitgroupData>);
        sbt.hitgroupRecordCount = static_cast<unsigned>(hitRecords.size());

        SbtRecord<int> photonHitRecord = {};
        optixSbtRecordPackHeader(photonHitgroupPG, &photonHitRecord);
        CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&p_d_hitgroupRecord), sizeof(photonHitRecord)));
        CUDA_CHECK(cudaMemcpy(reinterpret_cast<void*>(p_d_hitgroupRecord), &photonHitRecord, sizeof(photonHitRecord), cudaMemcpyHostToDevice));
        photonSbt.hitgroupRecordBase = p_d_hitgroupRecord;
        photonSbt.hitgroupRecordStrideInBytes = sizeof(SbtRecord<int>);
        photonSbt.hitgroupRecordCount = 1;

    }

And my Stack size calculation for the Photon pipeline

OptixStackSizes stackSizes = {};
for (OptixProgramGroup programGroup : programGroups) {
    OPTIX_CHECK(optixUtilAccumulateStackSizes(programGroup, &stackSizes, pipeline));
}
OptixStackSizes photonstackSizes = {};
for (OptixProgramGroup programGroup : photonProgramGroups) {
    OPTIX_CHECK(optixUtilAccumulateStackSizes(programGroup, &photonstackSizes, photonPipeline));
}

unsigned int directCallableStackSizeFromTraversal = 0;
unsigned int directCallableStackSizeFromState = 0;
unsigned int continuationStackSize = 0;
unsigned int photonDirectCallableStackSizeFromTraversal = 0;
unsigned int photonDirectCallableStackSizeFromState = 0;
unsigned int photonContinuationStackSize = 0;

OPTIX_CHECK(optixUtilComputeStackSizes(
    &stackSizes,
    pipelineLinkOptions.maxTraceDepth,
    0,
    0,
    &directCallableStackSizeFromTraversal,
    &directCallableStackSizeFromState,
    &continuationStackSize
));

OPTIX_CHECK(optixPipelineSetStackSize(
    pipeline,
    directCallableStackSizeFromTraversal,
    directCallableStackSizeFromState,
    continuationStackSize,
    2
));

OPTIX_CHECK(optixUtilComputeStackSizes(
    &photonstackSizes,
    pipelineLinkOptions.maxTraceDepth,
    0,
    0,
    &photonDirectCallableStackSizeFromTraversal,
    &photonDirectCallableStackSizeFromState,
    &photonContinuationStackSize
));

OPTIX_CHECK(optixPipelineSetStackSize(
    photonPipeline,
    photonDirectCallableStackSizeFromTraversal,
    photonDirectCallableStackSizeFromState,
    photonContinuationStackSize,
    2
));

I did enable the debug mode but it didn’t show any more specific information just like the compute sanitizer didn’t.

Right now I didn’t build it as OptiX-IR but if nothing else helps I might try.

My full code is on

on the “Photonrendering” branch if you want to take a look.

Thank you

  1. You are launching the photon pipeline with the shader binding table of the raytracing pipeline. I saw that in your initial post but without the SBT code couldn’t tell if that was the right one.
  2. Your closesthit radiance function does not limit the recursion of optixTrace on reflective hits.
  3. For fully opaque objects without cutout opacity, there exists a faster method to implement shadow rays by using the “terminate on first hit” ray flag which avoids using the anyhit program and only needs a miss program.

Since you’re using glTF, the GLTF_renderer inside the OptiX 7 Advanced Examples demonstrates these ray types and supports skinning and animation on the GPU.
See https://forums.developer.nvidia.com/t/optix-advanced-samples-on-github/48410/15

  1. Oh right! I forgot to change that. Now it is running but if I point my moving head at the disco ball it crashes again with the same mysterious error.
  2. Since I only have one reflective object (disco ball) I assumed that rays could only reflect once and there would be no deeper recursions. Other than that I set
pipelineLinkOptions.maxTraceDepth = 3;

so shouldn’t it cancel after three traces no matter if it reflects again?

  1. I already have the OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT set when calling optixTrace for the shadow ray. How would you do that?

I took a look at the examples and I find it really hard to understand the tutorials since they use many nested helper functions in different files and I can’t really find the sections of my code that I look for.

  1. OK, then it’s time for you to do all other things I recommended for debugging.

  2. The maximum recursion depth you set inside the pipeline options is used to calculate the internal stack sizes. It does by no means limit what the device code is doing. Means you are responsible that your device code is never exceeding the maximum recursion depth which would be one way to get exceptions or illegal access errors due to running out of stack space.
    If there is only one convex(!) reflecting object inside the scene, then aside from self-intersections there shouldn’t be more than one reflective hit. Still the code is not protected enough for the general case.
    The maximum recursion depth is simply the number of optixTrace calls which can happen recursively. In your case 3 would be the right maximum for one inside the raygen program, one when hitting a reflective object, and one when shooting a shadow ray afterwards, but the reflective case must be protected to happen at most once.

  3. The forum has a search function in the upper right corner. That finds this thread explaining how to use OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT:
    https://forums.developer.nvidia.com/t/anyhit-program-as-shadow-ray-with-optix-7-2/181312

If you sync the OptiX 7 Advanced Samples from github and search the source code for OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT you find multiple use cases. The smallest examples using it, are rtigo10 and rtigo12. See these two links:
https://github.com/NVIDIA/OptiX_Apps/blob/master/apps/rtigo12/shaders/brdf_diffuse.cu#L180
https://github.com/NVIDIA/OptiX_Apps/blob/master/apps/rtigo12/shaders/miss.cu#L42
Note that these apps are not using anyhit programs at all because they do not implement cutout opacity. Read the README.md there for more information.

All examples there are unidirectional path tracers and if you ever need to support more complex scenes, it would make sense to architect your renderer to be iterative as well, esp. when depositing photons after zero or more reflections. That would require less stack space and is usually faster than continuation calls.

Hi @s37jmaie, please follow the previous advice, those Apps are totally worth your learning time.

In your app the discoball is innocent (removing it doesn’t fix the app). The setup of the photon-mapper’s SBT is indeed the problem. This is the fixed version:

   /* FIXED crash:
       Photon SBT can be indexed out of bounds. IAS instances use inst.sbtOffset = i * RAY_TYPE_COUNT, but 
       photonSbt.hitgroupRecordCount = 1. When the photon trace hits instance i > 0, OptiX applies the 
       instance SBT offset and can read past the one photon hit record.        
    */
    // The photon pipeline reuses the IAS, whose instances are spaced by RAY_TYPE_COUNT.
    std::vector<SbtRecord<int>> photonHitRecords(sceneObjects.size() * RAY_TYPE_COUNT);
    for (auto& photonHitRecord : photonHitRecords) {
        optixSbtRecordPackHeader(photonHitgroupPG, &photonHitRecord);
    }
    CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&p_d_hitgroupRecord), sizeof(SbtRecord<int>) * photonHitRecords.size()));
    CUDA_CHECK(cudaMemcpy(reinterpret_cast<void*>(p_d_hitgroupRecord), photonHitRecords.data(), sizeof(SbtRecord<int>) * photonHitRecords.size(), cudaMemcpyHostToDevice));
    photonSbt.hitgroupRecordBase = p_d_hitgroupRecord;
    photonSbt.hitgroupRecordStrideInBytes = sizeof(SbtRecord<int>);
    photonSbt.hitgroupRecordCount = static_cast<unsigned>(photonHitRecords.size());

Hi,

thanks for the support!

It tourns out I indeed did not use the SBT correct. I now decided to build a second IAS and SBT for the Photons which still use the same GAS.

I also enforced the maxTraceDepth in my device code so there will be maximum 3 Raytraces per Thread.

In the next days I will check out the rtigo examples to also optimize my shadow tracer. with OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT.

I definitely learned a lot from this and can only thank you again for helping me. Hope everyone has a great day :)