Это в основном потому, что не все графические процессоры могут поддерживать вызовы функций - и даже если они могут, вызовы функций могут быть довольно медленными или иметь ограничения, такие как очень малая глубина стека.
Код шейдера и код вычислений на GPU могут иметь повсеместные вызовы функций, но при нормальных обстоятельствах все они на 100% встроены компилятором. Машинный код, выполняемый графическим процессором, содержит ветви и циклы, но не вызывает функции. Однако рекурсивные вызовы функций не могут быть встроены по очевидным причинам. (Если только некоторые аргументы не являются константами времени компиляции, таким образом, что компилятор может сложить их и встроить все дерево вызовов.)
Чтобы реализовать настоящие вызовы функций, вам нужен стек. В большинстве случаев код шейдера вообще не использует стек - графические процессоры имеют большие регистровые файлы, и шейдеры могут хранить все свои данные в регистрах все время. Трудно заставить стек работать, потому что (а) вам потребуется много места в стеке для обеспечения всех перекосов, которые могут быть в полете за раз, и (б) система памяти графического процессора оптимизирована для пакетирования вместе транзакций в памяти для достижения высокой пропускной способности, но это происходит за счет задержки, поэтому я предполагаю, что операции со стеком, такие как сохранение / восстановление локальных переменных, будут ужасно медленными.
Исторически, вызовы функций аппаратного уровня не были слишком полезны в GPU, так как имело больше смысла встроить все в компилятор. Поэтому архитекторы GPU не сосредоточились на том, чтобы сделать их быстрыми Вероятно, могут быть достигнуты некоторые другие компромиссы, если в будущем возникнет потребность в эффективных вызовах на аппаратном уровне, но (как и во всем в инженерном деле) это повлечет за собой затраты где-то еще.
Что касается трассировки лучей, то люди обычно обрабатывают подобные вещи, создавая очереди лучей, которые находятся в процессе отслеживания. Вместо повторения вы добавляете луч в очередь, и где-то на высоком уровне у вас есть цикл, который продолжает обрабатываться до тех пор, пока все очереди не станут пустыми. Это требует значительной реорганизации вашего кода рендеринга, если вы начинаете с классического рекурсивного raytracer. Для получения дополнительной информации, хорошая статья, чтобы прочитать об этом, является Wavefront Path Tracing .