Embree: потоковый режим - как работает сбор и разброс и что такое pid и tid?

Я пытаюсь обновить свое приложение с пересечения одного луча до пересечения потока.

Я не совсем понимаю, как это возможно, gatherи функции, показанные в обучающих программах, даже работают

В примере определяется настраиваемая расширенная структура лучей.

      struct Ray2
{
  Ray ray;

  // ray extensions
  float transparency; //!< accumulated transparency value

  // we remember up to 16 hits to ignore duplicate hits
  unsigned int firstHit, lastHit;
  unsigned int hit_geomIDs[HIT_LIST_LENGTH];
  unsigned int hit_primIDs[HIT_LIST_LENGTH];
};

затем он определяет массив этих структур:

      Ray2 primary_stream[TILE_SIZE_X*TILE_SIZE_Y];

этот массив устанавливается как userRayExt перед вызовом метода пересечения:

      primary_context.userRayExt = &primary_stream;
rtcIntersect1M(data.g_scene,&primary_context.context,(RTCRayHit*)&primary_stream,N,sizeof(Ray2));

теперь для каждого пучка лучей, пересекающегося с геометрией, вызывается обратный вызов фильтра:

      /* intersection filter function for streams of general packets */
void intersectionFilterN(const RTCFilterFunctionNArguments* args)
{
  int* valid = args->valid;
  const IntersectContext* context = (const IntersectContext*) args->context;
  struct RTCRayHitN* rayN = (struct RTCRayHitN*)args->ray;
  //struct RTCHitN* hitN = args->hit;
  const unsigned int N = args->N;
                                  
  /* avoid crashing when debug visualizations are used */
  if (context == nullptr) return;

  /* iterate over all rays in ray packet */
  for (unsigned int ui=0; ui<N; ui+=1)
  {
    /* calculate loop and execution mask */
    unsigned int vi = ui+0;
    if (vi>=N) continue;

    /* ignore inactive rays */
    if (valid[vi] != -1) continue;

    /* read ray/hit from ray structure */
    RTCRayHit rtc_ray = rtcGetRayHitFromRayHitN(rayN,N,ui);
    Ray* ray = (Ray*)&rtc_ray;

    /* calculate transparency */
    Vec3fa h = ray->org + ray->dir  * ray->tfar;
    float T = transparencyFunction(h);

    /* ignore hit if completely transparent */
    if (T >= 1.0f) 
      valid[vi] = 0;
    /* otherwise accept hit and remember transparency */
    else
    {
      /* decode ray IDs */
      const unsigned int pid = ray->id / 1;
      const unsigned int rid = ray->id % 1;
      Ray2* ray2 = (Ray2*) context->userRayExt;
      assert(ray2);
      scatter(ray2->transparency,sizeof(Ray2),pid,rid,T);
    }
  }
}

последняя строка этого метода - это то, что я не понимаю

      scatter(ray2->transparency,sizeof(Ray2),pid,rid,T);

Я понимаю, что ДОЛЖНО делать. Он должен обновить свойство прозрачности Ray2 что соответствует лучу, прорисованному с помощью T. Но я не понимаю, почему и как это работает, поскольку реализация scatter выглядит так:

      inline void scatter(float& ptr, const unsigned int stride, const unsigned int pid, const unsigned int rid, float v) {
  ((float*)(((char*)&ptr) + pid*stride))[rid] = v;
}

Я немного переформулирую эту функцию, чтобы лучше задать свой вопрос (но он должен быть полностью эквивалентен, если я не ошибаюсь):

      inline void scatter(float& ptr, const unsigned int stride, const unsigned int pid, const unsigned int rid, float v) {
  float* uptr = ((float*)(((char*)&ptr) + pid*stride));
  uptr[rid] = v;
}

Итак, первая строка все еще имеет для меня смысл. Создается указатель на поле прозрачности первой структуры Ray2, который затем увеличивается на tid * sizeof(Ray2) - это имеет смысл, поскольку он приземлится на другой transparency поле, так как оно увеличивается на кратное

но затем следующая строка

      uptr[rid] = v;

Я вообще не понимаю. uptr- указатель с плавающей точкой, указывающий на поле прозрачности. Так что если сам по себе не является кратным sizeof(Ray2), это вообще не будет указывать на поле прозрачности одного из лучей.

и рассчитываются как

        const unsigned int pid = ray->id / 1;
  const unsigned int rid = ray->id % 1;

что я нахожу странным. Разве это не всегда то же самое, что

        const unsigned int pid = ray->id;
  const unsigned int rid = 0;

?

что pid и rid и почему они так вычисляются?

1 ответ

Не написав этот пример самостоятельно, трудно догадаться, каково было его первоначальное намерение, но я думаю , что разгадка заключается именно в вашем наблюдении, что для вычислений rid и pid деление/по модулю на «1» бессмысленно.

Таким образом, если rid в конечном итоге всегда оказывается равным «0» (поскольку каждое значение по модулю 1 будет равно 0 :-/), тогда uptr[rid] = ...эквивалентно *uptr = ..., что на самом деле правильно, поскольку вы сами указали, что всегда указывает на действительную прозрачность.

Теперь о том, почему код делает эту запутанную вещь pid/rid? Если бы мне пришлось угадывать по названию «Ray2», я бы предположил, что в другой версии этого образца, возможно, использовались два луча и две прозрачности в этой структуре ray2, а затем использовалась штука rid/pid, чтобы всегда выбирать правильный из пара.

Тем не менее, что касается первоначального вопроса «почему это вообще работает»: rid всегда оценивается как 0, поэтому он всегда записывает прямо в значение прозрачности, которое uptrуказывает на.

Другие вопросы по тегам