Depuración de aplicaciones de Ray Tracing con NVIDIA OptiX Toolkit

El NVIDIA OptiX Toolkit (OTK) proporciona un conjunto de utilidades con licencia BSD de 3 cláusulas, que incluye macros robustas de verificación de errores y mecanismos de impresión de depuración en el dispositivo (device-side), diseñados para abordar los desafíos comunes en el trazado de rayos (ray tracing) mediante el framework NVIDIA OptiX.
OTK ofrece una verificación de errores unificada y con macros mínimas a través de las APIs de OptiX, CUDA runtime y CUDA driver. Utiliza funciones de plantilla (template functions) en línea y encabezados especializados para generar mensajes de diagnóstico con la ubicación de la fuente, nombres simbólicos de errores y descripciones legibles para humanos, optimizando el desarrollo y la depuración.
El mecanismo DebugLocation permite una salida de depuración interactiva y de grano fino en el dispositivo dentro de los pipelines de OptiX, soportando volcados de depuración de un solo disparo (one-shot), marcadores visuales y la integración con frameworks de interfaz de usuario como ImGui, tal como se demuestra en el ejemplo DemandPbrtScene incluido en el toolkit.
El motor de trazado de rayos NVIDIA OptiX es un framework de aplicaciones para lograr un rendimiento óptimo de ray tracing en la GPU. Las aplicaciones que utilizan OptiX pueden fallar de maneras difíciles de diagnosticar: un argumento de API inválido, un cuadro negro (black frame) o un error del lado de la GPU enterrado bajo miles de hilos concurrentes.
Las instalaciones de depuración en el NVIDIA OptiX Toolkit (OTK) pueden ser de gran ayuda. OTK es un repositorio de GitHub que contiene un conjunto de utilidades que soportan flujos de trabajo comunes en aplicaciones de ray tracing para GPU. OTK tiene una licencia de estilo BSD de 3 cláusulas, por lo que eres libre de copiar y modificar cualquier parte del código.
Esta publicación cubre dos instalaciones de depuración de OTK: la verificación consistente de códigos de error de las APIs de OptiX y CUDA, y la impresión de depuración dirigida en el lado del dispositivo. OTK incluye un programa de ejemplo, DemandPbrtScene, que muestra la impresión de depuración en el dispositivo utilizada en contexto.
¿Cómo funcionan el registro y la validación en OptiX?

Antes de llegar a la asistencia proporcionada por OTK, sería útil conocer algunos antecedentes sobre el registro (log) de OptiX. Cuando creas un contexto de dispositivo OptiX, suministras una estructura de opciones que te permite configurar una devolución de llamada (callback) de registro y un modo de validación. OptiX puede ayudar a validar las entradas de las funciones de la API cuando el modo de validación se establece en OPTIX_DEVICE_CONTEXT_VALIDATION_MODE_ALL.
OptiX escribe mensajes legibles por humanos para errores de validación en el registro. La salida del registro debe ser el primer lugar donde busques errores de API como OPTIX_ERROR_INVALID_VALUE. La validación impone un costo adicional en la API. Se recomienda habilitar toda la validación en compilaciones de depuración y prueba, y omitir la validación para las compilaciones de lanzamiento (release). Para más detalles, consulta los ejemplos del SDK de OptiX.
¿Cómo verificar consistentemente los códigos de retorno de error?
Es mejor detectar los errores lo antes posible, como se detalla en esta sección, antes de que fallos posteriores oculten el problema original.
Mecanismos de API
La mayoría de las funciones en OptiX devuelven un código de error OptixResult que, cuando es distinto de cero, indica un error. La API de tiempo de ejecución de CUDA y la API de controlador de CUDA siguen un patrón similar. Las tres APIs soportan lo siguiente:
- Un tipo enumerado distinto para el código de error; por ejemplo
OptixResult.
- Una función para devolver un nombre simbólico para un código de error como una cadena; por ejemplo
OPTIX_ERROR_INVALID_VALUE.
- Una función para devolver un mensaje de error legible por humanos para un código de error; por ejemplo, "Invalid value".
Las firmas de estas funciones son ligeramente diferentes para las tres APIs, pero los mecanismos son los mismos.
Política de verificación de errores
Manejar estos códigos de error manualmente en cada sitio de llamada de la API es tedioso y propenso a errores. Es mejor usar macros o llamadas a funciones para imponer una política consistente para manejar errores. OTK proporciona macros que implementan dos políticas cuando se detecta un error:
- OTK_ERROR_CHECK( expr ): Lanza una excepción.
- OTK_ERROR_CHECK_NOTHROW( expr ): Imprime un mensaje en
std::cerry continúa.
Otras políticas se implementan fácilmente reutilizando parte de la maquinaria proporcionada y creando una macro apropiada.
Uso mínimo de maquinaria de macros
Este mecanismo de verificación de errores utiliza macros en una medida mínima y delega en funciones en línea para realizar el trabajo real. Puedes establecer puntos de interrupción (breakpoints) en las definiciones de las funciones en línea para que el depurador detenga la ejecución cuando la función detecte un error.
Las macros existen para proporcionar información de diagnóstico sobre el código que causó el error:
expr: Una forma de cadena del argumento suministrado a la macro. Esta es la expresión que se evalúa como el código de error.
__FILE__: El nombre del archivo fuente donde se invocó la macro.
__LINE__: El número de línea dentro del archivo fuente donde se invocó la macro.
Esta información del sitio de llamada de la macro se pasa a la función en línea que realiza la verificación de errores real.
Verificación de errores unificada entre APIs
La función de plantilla en línea checkError verifica el código de estado en busca de un error y crea un mensaje de diagnóstico si detecta un fallo. En el caso de estas tres APIs, una simple conversión (cast) del código de error a bool es suficiente para indicar un error. Las tres APIs utilizan un código de estado de cero para indicar éxito y distinto de cero para indicar fallo.
Los mensajes de error tienen el siguiente formato:
file(line): expr failed with error nnn (name): messageDonde expr es la expresión evaluada, nnn es el resultado de convertir el código de estado a un int, name es el nombre simbólico del código de estado y message es el mensaje de error legible por humanos. Si el nombre o el mensaje están vacíos, la función los omite.
La función de plantilla en línea makeErrorString es responsable de construir este mensaje. Llama a las funciones de plantilla en línea getErrorName y getErrorMessage para construir el mensaje combinado.
Cada API tiene un tipo distinto para los códigos de estado de API, por lo que puedes especializar las funciones de plantilla para realizar la llamada a la API adecuada y obtener la información de error extendida.
Uso
OTK proporciona un encabezado por API que ofrece las especializaciones necesarias de las funciones de plantilla descritas (Tabla 1).
Simplemente incluye los encabezados para las APIs que estás utilizando y usa la macro única OTK_ERROR_CHECK alrededor de todos los sitios de llamada. El siguiente ejemplo utiliza las tres APIs.
OTK_ERROR_CHECK( cudaSetDevice( m_deviceIndex ) );
OTK_ERROR_CHECK( cuCtxGetCurrent( &m_cudaContext ) );
OTK_ERROR_CHECK( cuStreamCreate( &m_stream, CU_STREAM_DEFAULT ) );
OTK_ERROR_CHECK( optixInit() );¿Cómo realizar una impresión de depuración dirigida en el lado del dispositivo?
El problema con las aplicaciones gráficas es que hay demasiadas formas de programar una pantalla negra.
Para depurar problemas en tu código de dispositivo OptiX, puedes tomar varios enfoques. Un par de opciones obvias vienen a la mente:
- Usar el depurador de CUDA en una compilación de depuración del código del dispositivo.
- Obtener información de una compilación de lanzamiento del código del dispositivo con
printf.
Muchas aplicaciones se ejecutan demasiado lento cuando se compilan en modo de depuración, lo que dificulta el uso de depuradores interactivos.
La principal dificultad con la depuración de estilo printf es que hay tantos hilos ejecutándose simultáneamente en la GPU que puedes terminar ahogado en un flujo masivo de salida. Además, podría ser que el problema solo ocurra bajo condiciones específicas de carga.
Vía NVIDIA Developer.




