É basicamente porque nem todas as GPUs podem suportar chamadas de função - e mesmo que possam, as chamadas de função podem ser bastante lentas ou ter limitações, como uma profundidade de pilha muito pequena.
O código de sombreador e o código de computação da GPU podem parecer ter chamadas de função em todo o lugar, mas, em circunstâncias normais, eles são 100% incorporados pelo compilador. O código da máquina executado pela GPU contém ramificações e loops, mas nenhuma chamada de função. No entanto, chamadas de função recursivas não podem ser incorporadas por razões óbvias. (A menos que alguns dos argumentos sejam constantes em tempo de compilação, de forma que o compilador possa dobrá-los e alinhar toda a árvore de chamadas).
Para implementar chamadas de função verdadeiras, você precisa de uma pilha. Na maioria das vezes, o código shader não usa uma pilha - as GPUs possuem grandes arquivos de registro e os shaders podem manter todos os seus dados em registros o tempo todo. É difícil fazer uma pilha funcionar porque (a) você precisaria de muito espaço na pilha para fornecer todos os warps que podem estar em vôo por vez e (b) o sistema de memória da GPU é otimizado para agrupar muito de transações de memória para obter alta taxa de transferência, mas isso ocorre às custas da latência, portanto, acho que operações de pilha, como salvar / restaurar variáveis locais, seriam muito lentas.
Historicamente, as chamadas de função no nível do hardware não são muito úteis na GPU, pois faz mais sentido alinhar tudo no compilador. Portanto, os arquitetos de GPU não se concentraram em torná-los rápidos. Provavelmente, algumas compensações diferentes poderiam ser feitas, se houver uma demanda por chamadas eficientes no nível de hardware no futuro, mas (como acontece com tudo na engenharia), haverá um custo em outro lugar.
No que diz respeito ao traçado de raios, o modo como as pessoas geralmente lidam com esse tipo de coisa é criando filas de raios que estão sendo rastreados. Em vez de repetir, você adiciona um raio a uma fila e, em algum nível alto, possui um loop que continua processando até que todas as filas estejam vazias. Requer uma reorganização significativa do seu código de renderização, se você estiver iniciando com um raytracer recursivo clássico. Para obter mais informações, um bom artigo para ler sobre isso é o Wavefront Path Tracing .