Can't access buffer from callable program

Hi there, I’m implementing lights as bindless callable programs in my renderer and I’m now trying to use buffers (in this case the buffers hold cdfs for doing importance sampling of the environment map for an IBL).

Everything compiles fine, and I can create the buffers and set them to the appropriate variables on my program without error (in my implementation every optix call is checked and throws if not RT_SUCCESS). However, when I validate the context immediately before launching I get the following error:

Context::validate(): Invalid value (Details: Function "RTresult _rtContextValidate(RTcontext)" caught exception: Non-initialized variable buf_marginal:  Buffer(1d, 8 byte element))

After stripping down, the code for the light is essentially the following:

rtDeclareVariable(Matrix4x4, light_to_world, , );
rtDeclareVariable(Matrix4x4, world_to_light, , );
rtDeclareVariable(float3, L_e, , );
rtDeclareVariable(int, tex_id, , );
rtBuffer<float2, 2> buf_conditional;
rtBuffer<float2, 1> buf_marginal;
RT_CALLABLE_PROGRAM LightSample light_sample(ShadingFrame frame, const float u1,
                                             const float u2) {
#if 1
    LightSample ls;
    float i = buf_marginal[32].x;
    ls.L_i = make_float3(i, i, i);
    ls.pdf = 1.0f;
    ls.omega_i = make_float3(0.0f, 1.0f, 0.0f);
    return ls;
    // old code just doing cosine sampling, looking up the texture and transforming the result into world space from the shading frame using the matrices declared at the top

I’ve checked that the buffer is being set correctly by validating the buffer and then checking the RTobject held by the buf_marginal variable on the program is the same as the RTbuffer both immediately after setting it and just before validating the context.

If I switch out the #if, the old code which does texture accesses and uses matrices set by the host works perfectly. It’s just as soon as I try to use either buffer it fails.

I’m not really sure what to try next to debug this.



I’m doing almost the same and that shouldn’t happen.

>>I’ve checked that the buffer is being set correctly by validating the buffer and then checking the RTobject held by the buf_marginal variable on the program is the same as the RTbuffer both immediately after setting it and just before validating the context.<<

A bindless callable program has the OptiX context and itself, the program, as variable scope.
You say you declared the variable at the program scope.
If you have only a single environment light, you can also declare those buffers at the context (global) level.
Then it should be something like m_context[“buf_marginal”]->setBuffer(m_bufferMarginal); on the host.

If that works, it would be interesting to get a reproducer for the failing case to investigate why that isn’t working.

I’m curious why your buffers are float2. My CDF buffers are just float, I store the environment color inside a texture, and have another float variable for the integral.

Hi Detlef, thanks. I can confirm that just setting it on the context works fine. I’m afraid generating a simple repo will be problematic as my renderer’s a viewport renderer for Maya currently. I do really need to start making some tests, so I’ll make sure to prioritize a repro for this issue as part of that.

I’m using float2’s as I’m just using PBRT’s algorithm, where they store the CDF and PDF tables side-by-side.

Incidentally, is it safe to pass pointers around to buffer contents? I’m accessing rows of the 2D conditional buffer by passing it to a function like so:

sample_continuous_1d(&buf_conditional[make_uint2(0, offset1)],/*...*/);

where sample_continuous1d takes a float2* as its first argument.

Hmm, my implementation was also based on PBRT, version 2, that is.
I just use the formula at the bottom of page 728 (in a smarter order, check sinTheta first!)
lightPdf = mapPdf / (2.0f * M_PIf * M_PIf * sinTheta); // with mapPdf = intensity(emission) / environmentIntegral;

>>Incidentally, is it safe to pass pointers around to buffer contents?<<

Good question. How to access buffers correctly is a very important detail often overlooked in the docs.

Mind that the only legal way to index into any buffer is with operator which gives you access to exactly one element in that buffer, whatever element type that is, including any user defined structure.
See #define rtBuffer device optix::buffer in the OptiX API Reference.

You must not use any pointer arithmetic on buffers!
Getting the address to some element of a buffer and then adding an offset to that pointer to access another element is illegal!
Don’t do that even if it works on your system. That’s implementation dependent behaviour.
Buffer element accesses must always be operator calls with the proper indices everywhere.
Pointers to a such a single element work, but might not allow all possible optimizations inside OptiX.

I’m almost entirely using const references for such arguments to avoid that error possibility.
For the light sampling code, I simply moved the sample_continuous_1d() function into my environment light sampling function. It was the only place where I used it, so no need for a function there.

Thanks for the info! It does indeed seem to work perfectly on OSX with cuda/optix 8.0/4.02. Guess I just got lucky…

About the reproducer for the failing case, you probably don’t need to write a standalone application for that.
OptiX allows to dump an API trace which we can debug.

Have a look at the last this description how to enable that:

Hi Detlef, here’s OAC for the failure case (just a single IBL in the scene):

and here’s a comparison where I’ve changed the buffers to be set on the context rather than the program, which works, for comparison:

Thanks, downloaded.

I can reproduce the validation failure with the unresolved reference to variable buf_marginal from light_sample.

From looking over the trace.oac text file, the problem seems to be that you create the light_sample program twice, in line 11 and in line 432, but only set the buf_marginal variable on the second of the two program objects in line 590.
Similarly light_eval and light_pdf are created twice from the same light_infinite_area.ptx.

The fix should be to create all three environment light functions only once and set all variables they access.

I’m creating all identical programs only once at the beginning of the renderer setup and store them inside a map. I can then find, reuse, and delete all programs objects by their name. I’m not using variables at program scope at all. That can be useful for some cases, but I didn’t need that. All data my bindless callable programs use come from arguments and context global variables.

Hi Detlef, thanks. I think I maybe have some confusion about how I ought to be using programs then. What’s happening is that my Scene class constructs a default InfiniteAreaLight that’s just a white environment, so that if the user doesn’t create a lightsource you get essentially an ambient occlusion render of the scene. That’s the one at line 11. The one at line 432 is the one that’s actually in the scene which has an environment map on it, and should be the only one which is actually bound to the surface program.

So I guess my confusion is, what does a program represent? In my mind the actual program file is like a class definition (only one) and the rtProgram object is like a class instance (potentially many), and each “instance” can have its own variables etc. Is that not the case? And if it’s not the case, how come I can set the texture id, transformation matrices and color and intensity variables just fine, just not the buffers?



Your assumptions are all correct.
The only thing which went wrong is that you always must declare and set all variables you access somewhere in the code or validation will fail.

You’re generating two different program objects from the exact same program code.
If you look inside the trace, you see the below calls. The program hdl is your program object.
When having variables defined at the program scope, that’s the argument used to declare those variables, first argument in rtProgramQuery/DeclareVariable() below.

You must setup the buffers accordingly to represent a CDF for a white environment. You cannot simply leave variables uninitialized which are accessed somewhere inside the program’s code, even if that code isn’t reached. You missed two variables on one of the programs and the first variable fired the validation exception. That’s all.

// Line 432
rtProgramCreateFromPTXFile( 0x7ff5b98e0fc0, /Users/anders/code/rtvp/build/light_infinite_area.ptx, light_sample, 0x7fff51c660a0 )
  file = oac.ptx.000000.potx
  res = 0
  hdl = 0x7ff59a4d6e50
// Line 588
rtProgramQueryVariable( 0x7ff59a4d6e50, buf_marginal, 0x7fff51c661c8 )
  res = 0
rtProgramDeclareVariable( 0x7ff59a4d6e50, buf_marginal, 0x7fff51c65ef8 )
  res = 0
  hdl = 0x7ff59a800590

Now when doing that with variables at the program scope, you wouldn’t actually need to create two identical programs with different variable values. If there is ever really only one environment light active in your scene, you only need that program once and could just change the variable values, which then also don’t really need to be at the program scope but could all be context global because they exist only once. Your choice.

Another way would be to use different programs for different environment lights. Though then the miss program would need to be different as well. I’m not switching that dynamically. I’m only selecting the environment lighting once at program start (my renderer is a proof of concept) and have a miss_null (black, not a light), miss_constant (white, not importance sampled), miss_environment (spherical HDR enviroment texture, importance sampled, multiple predefined HDRs also a white one with HDR brightness for debugging) and the matching light_constant and light_environment sampling functions (and a light_mesh for emissive materials on objects, but that’s a different story).

Shameless plug: My renderer architecture looked like this last year:
In the meantime everything is using bindless callable programs which allowed some algorithmic improvements. I’m going to present some of the changes on the next GTC in three weeks.

Thanks Detlef, so basically regardless of whether that program is actually called, just by virtue of the fact that it exists on the context, I must make sure that all variables are set. That makes sense.

I’ve already read those slides with interest, thanks. It’s a shame there’s not more material like that out there. Do you plan to release the source for your renderer at any point? It would be very helpful for those like me who are blindly fumbling through optix for the first time :)

I look forward to seeing what you present at GTC this year too. I won’t be attending, but I hope your slides at least will be made available online?