1/1 Alumne: José Manuel Solís Rejas Director/Ponent

Transcripción

1/1 Alumne: José Manuel Solís Rejas Director/Ponent
Títol: Extensión a Direct3D del driver de un simulador de GPU
Volum: 1/1
Alumne: José Manuel Solís Rejas
Director/Ponent: Agustín Fernández Jiménez
Departament: Arquitectura de Computadors
Data: 27 de Junio del 2007
DADES DEL PROJECTE
Títol del Projecte: Extensión a Direct3D del driver de un simulador de GPU
Nom de l'estudiant: José Manuel Solís Rejas
Titulació: Enginyeria Informàtica
Crèdits: 37’5
Director/Ponent: Agustín Fernández Jiménez
Departament: Arquitectura de Computadors
MEMBRES DEL TRIBUNAL (nom i signatura)
President: Leandro Navarro Moldes
Vocal: Juan Trias Pairo
Secretari: Agustín Fernández Jiménez
QUALIFICACIÓ
Qualificació numèrica:
Qualificació descriptiva:
Data:
3
Tabla de contenidos
1. Presentación.............................................................................................................................. 7
2. Conceptos previos................................................................................................................... 11
2.1 Introducción al rendering interactivo................................................................................................13
2.1.1 Definición de rendering. ............................................................................................................13
2.1.2 La ecuación general del rendering.............................................................................................13
2.1.3 Modelos de iluminación. ...........................................................................................................15
Modelos de iluminación globales. ..................................................................................................15
Modelos de iluminación locales .....................................................................................................16
Modelo de iluminación de Phong ...............................................................................................17
Modelo de iluminación de Blinn – Phong. .................................................................................19
2.1.4 Rendering interactivo y rendering no interactivo. .....................................................................19
2.1.5Modelado de escenas ..................................................................................................................20
2.1.6 Sistemas de coordenadas y transformaciones geométricas........................................................20
Cambio de coordenadas directo......................................................................................................21
Cambio de coordenadas inverso .....................................................................................................24
Coordenadas homogéneas ..............................................................................................................25
2.1.7 Utilización del rendering en aplicaciones: API’s 3D.................................................................27
2.1.8 Soporte hardware al rendering interactivo: Graphics Processing Units.....................................28
Concepto de batch ..........................................................................................................................29
2.2 Direct3D ...........................................................................................................................................31
2.2.1 Direct3D Rendering pipeline.....................................................................................................31
Fixed Function Pipeline y Programmable Pipeline ........................................................................35
Fixed function pipeline...................................................................................................................35
Streaming....................................................................................................................................35
Fixed Function Vertex Shading ..................................................................................................38
Transformación.......................................................................................................................38
Transformación de modelo .................................................................................................38
Transformación de observador. ..........................................................................................40
Transformación de proyección. ..........................................................................................40
Iluminación.............................................................................................................................43
Geometry Processing..................................................................................................................45
Texture sampling ........................................................................................................................48
Fixed function pixel shading ......................................................................................................51
Pixel processing..........................................................................................................................54
Programmable pipeline...................................................................................................................58
Lenguaje de shading ...................................................................................................................59
Registros.................................................................................................................................60
Instrucciones...........................................................................................................................61
Modificadores.........................................................................................................................64
High Level Shading Language ...............................................................................................65
2.2.2 Direct3D API.............................................................................................................................66
Modelo COM .................................................................................................................................66
Interfaces Direct3D ........................................................................................................................67
Una aplicación de ejemplo .............................................................................................................70
2.3 El simulador ATTILA ......................................................................................................................75
2.3.1 El pipeline del simulador...........................................................................................................75
Streaming........................................................................................................................................76
Vertex Shading ...............................................................................................................................76
Geometry Processing......................................................................................................................77
Fragment Shading...........................................................................................................................78
Pixel Rendering ..............................................................................................................................78
Texture Sampling ...........................................................................................................................79
Lenguaje de shading .......................................................................................................................79
2.3.2 El hardware simulado ................................................................................................................81
5
2.3.3 La interfaz del simulador...........................................................................................................82
2.3.4 Uso del simulador......................................................................................................................83
3. Trabajo realizado..................................................................................................................... 85
3.1 Captura, reproducción y análisis de trazas........................................................................................87
3.1.1 Subsistema de captura: Aplicación PIX.....................................................................................88
Aplicación PIX ...............................................................................................................................88
Examen del formato de traza ..........................................................................................................89
Localización de las llamadas al API Direct3D ...............................................................................91
Interpretación de una llamada: Packed Call Package .....................................................................91
Llamadas Lock/Unlock ..................................................................................................................92
3.1.2 Subsistema de reproducción: D3DPixRunPlayer ......................................................................93
Tratamiento de llamadas.................................................................................................................94
Objetos originales y sustitutos........................................................................................................96
Tratamiento de las operaciones lock/unlock...................................................................................97
Reproducción a nivel de batch........................................................................................................98
Reproducción con configuraciones alternativas .............................................................................99
Diseño de PixRunPlayer...............................................................................................................100
Pruebas de uso ..............................................................................................................................101
3.1.3 Subsistema de análisis de trazas ..............................................................................................102
Informes mediante PERL .............................................................................................................103
3.2 Implementación de Direct3D sobre el simulador ATTILA............................................................105
3.2.1 Método de trabajo....................................................................................................................107
3.2.2 Arquitectura.............................................................................................................................108
3.2.3 Subsistema D3DProgrammablePipeline..................................................................................109
Comunicación con el simulador ...................................................................................................110
Interfaz con el exterior..................................................................................................................111
Patrón singleton ........................................................................................................................112
Relación entre el estado Direct3D y el estado del simulador. ......................................................112
Recursos Direct3D y búferes del simulador .................................................................................113
Depuración interactiva..................................................................................................................115
3.2.4 Subsistema D3DShaderTranslation .........................................................................................117
Proceso de traducción...................................................................................................................118
Tratamiento de versiones..............................................................................................................120
Estudio de requisitos.....................................................................................................................121
Análisis del bytecode....................................................................................................................123
Representación intermedia ...........................................................................................................125
Diseño del traductor .....................................................................................................................126
Pruebas de uso ..............................................................................................................................127
3.2.5 Subsistema FixedFunctionGeneration .....................................................................................128
Generación de shaders de función fija..........................................................................................130
Emulador de función fija ..............................................................................................................135
Pruebas interactivas ......................................................................................................................138
4. Conclusión............................................................................................................................. 139
Análisis del tiempo empleado y coste económico del proyecto. ..........................................................141
Futuras líneas de trabajo.......................................................................................................................141
Bibliografía...........................................................................................................................................143
6
1. Presentación
El crecimiento de la industria del videojuego ha impulsado la investigación y desarrollo
de arquitecturas hardware especializadas en representar gráficos tridimensionales en
tiempo real. Esto ha posibilitado que la CPU tradicional delegue el costoso proceso de
los gráficos en un procesador especializado, la Graphics Processing Unit (GPU).
Podemos encontrarlas en equipos de escritorio, portátiles, consolas e incluso en
dispositivos móviles.
En el Departamento de Arquitectura de Computadores existe una línea de
investigación dedicada al estudio de arquitecturas gráficas. La línea dispone del
simulador de GPU ATTILA. El simulador es altamente configurable, permite simular a
nivel de micro arquitectura y tomar multitud de estadísticas.
Capture
Verify
Simulate
Application
GLInterceptor
Trace
GLPlayer
GLPlayer
Trace stats
OpenGL Driver
OpenGL Driver
ATTILA OpenGL Driver
API stats
Real GPU
Real GPU
ATTILA Simulated GPU
GPU stats
Rendered
Frames
Rendered
Frames
Rendered
Frames
Es esencial estudiar las diferentes configuraciones del simulador en condiciones
similares a las de una GPU física. Por este motivo las operaciones que forman la
entrada de la simulación se obtienen de aplicaciones reales, típicamente videojuegos.
Como es habitual, las aplicaciones no operan sobre el hardware gráfico directamente
sino a través de librerías que les abstraen de los detalles de bajo nivel, conocidas
como API’s 3D. El procedimiento que se utiliza consiste en monitorizar en tiempo de
7
ejecución las operaciones que invoca el videojuego sobre el API, serializándolas en un
fichero. A este proceso lo llamamos captura y al fichero resultante la traza, que será la
entrada para el simulador.
Durante la simulación las operaciones almacenadas en la traza se deserializan y se
ejecutan de nuevo. Son procesadas por una implementación del API que las traduce
en operaciones sencillas sobre el hardware simulado, como escrituras de registros y
memoria.
Actualmente el simulador ATTILA dispone de una implementación del API OpenGL.
Esto significa que puede simular trazas obtenidas a partir de aplicaciones que utilicen
este API. OpenGL se vincula a ámbitos científicos y académicos y muchas
aplicaciones desarrolladas en dichos ámbitos lo utilizan, sin embargo la mayoría de
videojuegos disponibles para PC’s utilizan el API Direct3D de Microsoft. Disponer de
una implementación de este API supondría poder simular un gran número de
videojuegos.
Capture
Verify
Simulate
Application
Direct3D Capture
Trace
Direct3D Player
Direct3D Player
Trace stats
Direct3D
Direct3D
ATTILA Direct3D driver
API stats
Real GPU
Real GPU
ATTILA Simulated GPU
GPU stats
Rendered
Frames
Rendered
Frames
Rendered
Frames
El objetivo principal de este proyecto es dar soporte a la simulación de videojuegos
Direct3D en el simulador ATTILA. Esto supone realizar una implementación del API
Direct3D y también proveer de un mecanismo para capturar y reproducir las trazas.
Alrededor del simulador ATTILA se han desarrollado varios proyectos, de forma que es
importante delimitar en qué medida este proyecto se relaciona con ellos. En concreto:
8
La aplicación DXCodegen, un framework para la generación automática de código se
ha utilizado en la implementación de algunos componentes. Esta aplicación la ha
desarrollado el alumno de la FIB David Abella como parte de su PFC, cuya temática
también está relacionada con Direct3D y ATTILA.
Parte del código utilizado en la extensión de estadísticas del reproductor de trazas
forma parte del repositorio de la línea de investigación en arquitecturas gráficas del
DAC y fue programada por uno de sus miembros, Jordi Roca.
El propio simulador de GPU ha sido desarrollado por el profesor Víctor Moya como
parte de su tesis doctoral.
El resto de la memoria consiste en una exposición de conceptos previos, la
descripción del trabajo realizado y el capítulo de conclusiones. Está pensada para
leerse en orden.
9
2. Conceptos previos
11
2.1 Introducción al rendering interactivo.
2.1.1 Definición de rendering.
En el campo de los gráficos por computador se conoce como rendering al proceso de
representación del modelo de una escena tridimensional (3D) como imagen raster
bidimensional (2D).
El modelo se recibe como una estructura de datos que almacena las características de
la escena. En el caso de una escena del mundo real, podría almacenar datos como la
forma de los objetos y la intensidad de las fuentes de luz.
Las imágenes raster por su lado consisten en una matriz de píxeles. En el proceso de
rendering cada uno de ellos almacena la intensidad de la luz para cada posición 2D
representada.
2.1.2 La ecuación general del rendering.
La base teórica del rendering se formula en la ecuación general del rendering de
James T. Kajiya.
La ecuación general describe, basándose en la física de la luz y en la conservación de
la energía, cómo la luz viaja a través de cualquier punto de la superficie de los objetos
de una escena.
13
Es la cantidad de luz que sale del punto x en dirección w. Si este punto fuera visible
ésta sería la luz que recibiríamos de él. Esta luz se descompone en una suma de luz
emitida y reflejada.
Es la cantidad de luz emitida por el punto x en dirección w. Si este término no es cero
significa que el punto está actuando como una fuente de luz.
Representa la luz reflejada, que se obtiene integrando sobre todas las direcciones
posibles w’ en que se puede recibir luz entrante.
Es la luz entrante al punto x desde la dirección w’. Hay dos factores que influyen en la
cantidad de luz que finalmente se reflejará en la dirección w.
Es la función de distribución define la cantidad de luz que el punto x refleja en
dirección w cuando la recibe desde la dirección w’.
Es la atenuación por el ángulo en que incide la luz entrante sobre la superficie. De su
expresión se deduce que la atenuación es menor cuanto más perpendicularmente
incida la luz.
14
Computando esta ecuación es posible obtener una representación perfecta de todos
los objetos visibles en una escena, obteniendo la cantidad de luz que llega desde cada
punto de sus superficies hacia la posición desde la que se quiere observar.
Sin embargo el coste computacional lo hace impracticable para escenas con un
mínimo de complejidad, ya que es una ecuación recursiva: Para calcular el valor para
un punto x hemos de calcular la cantidad de luz que entra proveniente de todas las
demás direcciones, lo cual implica calcular de nuevo la ecuación para todos los puntos
visibles desde x.
Dado que el coste temporal de la solución es exponencial, ninguna técnica de
rendering realiza un cómputo completo de la ecuación general. Cada técnica adopta
un compromiso entre fidelidad de la representación y coste temporal.
2.1.3 Modelos de iluminación.
Modelos de iluminación globales.
Los modelos de iluminación globales tienen en cuenta tanto la luz que refleja una
superficie proveniente directamente de una fuente de iluminación como la que recibe
indirectamente reflejada por el resto de objetos. Su coste computacional es elevado
pero practicable, ya que establecen un límite en el número de interacciones de la luz
con los objetos. El grado de realismo que se obtiene es muy elevado, se suelen utilizar
en técnicas de rendering no interactivas.
15
Modelos de iluminación locales
Los modelos de iluminación locales tan sólo tienen en cuenta la luz que recibe una
objeto directamente desde las fuentes de luz. De este modo no es necesario calcular
la contribución de la luz que se recibe de forma indirecta en una superficie, reflejada
por el resto de objetos de la escena.
Esta simplificación implica un menor coste computacional, ya que eliminan el
componente recursivo de la ecuación general del rendering.
Cada punto es
independiente de los demás. Esto hace que de los modelos de iluminación locales se
utilicen para las técnicas de rendering interactivo, como se verá más adelante.
16
Modelo de iluminación de Phong
El modelo de reflexión de Phong establece que la intensidad de la luz que recibe un
observador (viewer) reflejado desde un punto de una superficie se puede dividir en tres
componentes: Ambiente, especular y difuso.
La intensidad de luz ambiente Ia es una propiedad de la escena, y representa la luz
proveniente indirectamente del resto de superficies. Es una representación muy
simplificada respecto a la ecuación general: El modelo asume un valor constante que
reciben todas las superficies. La proporción en que esta luz es reflejada por la
superficie se representa por el parámetro Ka, que es una propiedad de la superficie.
17
Para cada una de las luces presentes en la escena se considera que emite dos tipos
de luz: Una que se refleja en forma difusa Id y otra que se refleja en forma especular
Is.
La reflexión difusa es aquella en la que la luz se distribuye en todas la direcciones por
igual. Se expresa como el producto escalar de N y L. El componente difuso proviene
de la aplicación del modelo de reflexión de Lambert.
La reflexión especular es aquella que se da preferentemente en la dirección de
reflexión R. La proporción que recibe el observador depende de la distancia angular
entre el vector V y R, siendo máxima cuando coinciden. El exponente alfa representa
una función de distribución de la luz alrededor de R. Cuanto mayor es alfa, más
concentrada está luz en la dirección R.
18
Modelo de iluminación de Blinn – Phong.
El modelo de iluminación de Blinn – Phong añade una simplificación al modelo de
Phong de cara a un menor coste computacional.
La modificación consiste en utilizar el half vector H en sustitución del vector reflejado
R. Ajustando debidamente el exponente los resultados son similares al modelo de
Phong.
2.1.4 Rendering interactivo y rendering no interactivo.
Las técnicas de rendering interactivas pretenden representar escenas de forma lo
suficientemente rápida para permitir la interacción por parte de una persona. Esto
supone producir del orden de decenas de imágenes (frames) por segundo, medida
que se conoce como framerate. Por este motivo las técnicas de rendering interactivas
suelen utilizar modelos de iluminación local y algoritmos deliberadamente sencillos.
Por el contrario las técnicas no interactivas suelen estar enfocadas a la calidad de la
imagen final. Su coste computacional es elevado, pues suelen utilizar modelos de
iluminación global. Ejemplos de estas técnicas son el raytracing, que asume que las
superficies se comportan de modo especular puro, reflejando la luz en una sola
19
dirección y recibiéndola de un número limitado de direcciones y
la técnica de
Radiosity, que asume que las superficies presentan tan sólo reflexión difusa.
2.1.5Modelado de escenas
Modelo
Vértices y primitivas
Textura
En muchos sistemas de rendering el modelo de un objeto se da a través de la
descripción de su superficie. Esta descripción se compone de vértices, primitivas y
texturas:
Un vértice en el contexto del rendering es una posición de una superficie para la que
se conocen los valores exactos de algunas propiedades. Estas propiedades se llaman
las componentes del vértice. Adicionalmente a la posición los vértices pueden incluir
otros componentes como el color de la superficie y el vector normal, que se indica en
la ilustración. El color usualmente se describe como una serie de valores de intensidad
luminosa para de rojo, verde y azul. Para indicar la transparencia se utiliza un cuarto
valor, conocido como canal alfa.
Una primitiva es una región de la superficie definida mediante sus vértices. La primitiva
más utilizada es el triángulo, porque presenta la ventaja de que sus tres vértices
siempre definen un plano.
Una textura es una matriz que almacena los valores de una propiedad de la superficie
en los puntos interiores de las primitivas. Es una manera de obtener un mayor nivel de
detalle.
2.1.6 Sistemas de coordenadas y transformaciones geométricas.
La posición de un punto en el espacio 3D se puede determinar utilizando sus
coordenadas respecto a un sistema de referencia.
20
Cambio de coordenadas directo
a
P1
a
P1
Un cambio de coordenadas directo consiste en encontrar a partir de las coordenadas
de un punto P1 en un sistema de referencia A un nuevo punto P2 que tenga las
mismas coordenadas en otro sistema de referencia B.
a
P1
Un caso sencillo sería que el sistema B tuviera la misma orientación que A. En este
caso bastaría con sumar el vector de la posición respecto a A del punto de origen del
sistema B.
21
Bw
Bv
Bw
Bv
Cuando los sistemas tienen orientaciones diferentes se utiliza la matriz de cambio de
coordenadas Mab, que consiste en expresar los vectores del sistema de referencia B
en el sistema de referencia A.
Bv
Bu
P2
P 3a
Bw
P3
Bu
SR A
Bv
Bw
Ao
P3a = MBA * P1a
Multiplicando el vector de coordenadas de en A del punto P1 por la matriz Mab se
obtienen las coordenadas de un punto P3 respecto a un sistema de referencia
centrado en el origen de A con misma orientación que B.
22
P2
AB
P2
a
P3
SR A
Ao
P2a = MBA * P1a + AB
De este modo ahora podemos proceder como en el caso anterior, ya que P3 y P2
están expresados en sistemas de referencia con la misma orientación.
Existen diferentes matrices de cambio de coordenadas para algunos casos básicos
como la rotación sobre un eje, translación, escalado, etc.
La utilidad de esta operación es encontrar la posición que ocupa un punto de la
geometría de un objeto cuando éste se traslada a otro lugar.
23
Cambio de coordenadas inverso
Un mismo punto puede definirse utilizando diferentes sistemas de referencia. Un
cambio de coordenadas inverso consiste en encontrar las coordenadas de un punto en
un sistema de referencia B a partir de sus coordenadas en otro sistema de referencia
A.
P
Pb
Pa
Bo
AB
SR B
Ao
SR A
Pb = Pa - AB
En el caso sencillo en que A y B tengan la misma orientación bastará con restar el
vector de la posición en A del origen del sistema de referencia B.
24
Si no tienen la misma orientación previamente se multiplica por la matriz de cambio de
coordenadas MAB. Es importante observar que MAB es la inversa de MBA.
La utilidad de esta operación consiste en que nos permite describir fácilmente la
geometría del objeto desde el sistema de coordenadas nuevo, viéndolo desde otro
lugar a modo de una cámara.
Coordenadas homogéneas
A pesar de que las escenas que se representan son 3D, lo habitual es utilizar un
espacio proyectivo 3D. En este tipo de espacio los puntos tienen coordenadas 4D,
llamadas coordenadas homogéneas, y se proyectan en puntos 3D. Un punto de
coordenadas homogéneas (x, y, z, 1) se proyecta en el punto 3D (x, y, z) y en general
dividiendo por la componente w obtenemos el punto 3D proyectado, por lo que los
puntos de la forma (aw, bw, cw, w) son equivalentes.
25
Rotación y translación 3D:
P2 = R * P1 + T
P2
w
R
=
0
0
T
0 1
*
P1
1
Rotación y translación 4D:
P2 = M * P1
La ventaja de utilizar este sistema es que se puede representar un cambio de
coordenadas mediante una única matriz 4x4. Se añade al punto 3D a transformar una
cuarta componente con valor 1 y se multiplica por la matriz 4x4. Para recuperar el
punto 3D transformado se divide por la cuarta componente w si es diferente de 1 y se
descarta.
Varias transformaciones 4D
P4 = M4 * P2
P3 = M3 * P2
P2 = M2 * P1
Sustituyendo
P4 = M4 * M3 * M2 * P1
Combinando las matrices
M432 = M4 * M3 * M2
P4 = M432 * P1
Otra ventaja es que una serie de cambios de coordenadas se expresan como una
serie de multiplicaciones de matrices, pudiendo combinarse en una única matriz que
multiplicaremos por las coordenadas homogéneas del punto.
26
Los puntos con w = 0 no tienen proyección en el espacio 3D se sitúan en el plano del
infinito del espacio 4D. Utilizando estos puntos se pueden representar conceptos como
el de eje de rotación.
2.1.7 Utilización del rendering en aplicaciones: API’s 3D
Las API’s 3D ofrecen funcionalidades de rendering ocultando los detalles de
implementación. Esto supone una gran ventaja a la hora de crear aplicaciones que
realicen rendering, ya que realizar una implementación de los procesos de rendering
suele ser un tarea compleja. Otro beneficio es que favorece la portabilidad de las
aplicaciones.
El API OpenGL es una especificación para un sistema de rendering interactivo
inicialmente propuesta por Silicon Graphics Inc. Existen implementaciones de este
API en multitud de lenguajes y plataformas. Se utiliza en todo tipo de aplicaciones,
pero especialmente en aplicaciones de CAD, visualización científica y realidad virtual.
Es popular en ámbitos académicos.
Direct3D es la API para rendering interactivo de la empresa Microsoft. Su primera
versión se incorporó en el sistema operativo Windows 95 para posibilitar la
programación de videojuegos en esta plataforma. Es un API orientada a ofrecer
acceso a las capacidades del hardware gráfico, por lo que ha evolucionado en paralelo
a través de sus diferentes versiones al desarrollo de éste. Está disponible en la
mayoría de equipos que disponen de sistemas operativos Windows. Se utiliza
fundamentalmente para la programación de videojuegos.
Las API’s suelen especificar un proceso que consta de varios bloques funcionales
conocido como rendering pipeline. Los datos del modelo fluyen de un bloque a otro
sufriendo diferentes transformaciones hasta formar la imagen renderizada.
27
2.1.8 Soporte hardware al rendering interactivo: Graphics
Processing Units
Los equipos actuales disponen de procesadores específicos para el procesamiento de
gráficos conocidos como Graphics Processing Units (GPU’s). De este modo la CPU
suele delegar total o parcialmente las tareas de rendering interactivo en la GPU.
La motivación del desarrollo de hardware específico para el procesamiento de gráficos
es doble. Por un lado tenemos que muchas aplicaciones de usuario, especialmente
videojuegos y aplicaciones de diseño gráfico, requieren rendering interactivo con la
mayor calidad posible. Por otro lado tenemos que una CPU es un procesador de
propósito general y el procesamiento de gráficos tiene características específicas.
Supone procesar un gran número de elementos de un mismo tipo, sin embargo no hay
dependencias entre elementos: El proceso de un elemento es independiente del
procesamiento de los demás. Tomando el caso del modelo de iluminación local,
observamos que la iluminación de un punto no depende de la iluminación de los
demás.
El proceso que a cada elemento es sencillo. Los algoritmos que se usan utilizan en
rendering interactivo suelen ser simples y poco costosos.
28
Los elementos suelen ser magnitudes vectoriales reales. Tanto la posición como el
color y otros elementos geométricos se representan cómodamente mediante vectores.
Los algoritmos utilizan operaciones vectoriales como suma, multiplicación, producto
escalar.
En consecuencia la GPU se diseña como un Stream Processor. Estos procesadores
están especializados en procesar gran cantidad de elementos en paralelo,
distribuyéndolos en pequeñas unidades de proceso. Las unidades disponen de
operaciones para tratar con vectores de forma eficiente y algunas son programables.
Concepto de batch
La GPU no se suele utilizar para dibujar la escena de golpe, sino que se dibuja un
grupo de primitivas cada vez. Esta unidad de trabajo se llama batch.
Utilicemos un ejemplo. Las primitivas de este personaje tienen diferentes materiales
(texturas y propiedades ópticas) y la GPU se ha de configurar de diferente forma para
cada uno. El modo habitual es establecer una configuración de la GPU para un
material y renderizar todas las primitivas que tienen ese material y proceder así con el
resto. De esta forma se usarían cuatro batches para renderizar el personaje.
Si no se renderizan todas las primitivas de un material juntas habrá que reconfigurar la
GPU más veces, esto supone una penalización de rendimiento doble. Por un lado
supone más llamadas al API 3D. Por otro lado y más importante: la GPU está
diseñada para procesar batches, es capaz de procesar un gran número de primitivas
29
en paralelo si no cambian los parámetros de rendering. La razón es que, en esas
condiciones, el cálculo de cada píxel de la imagen renderizada es totalmente
independiente del cálculo de los demás.
30
2.2 Direct3D
2.2.1 Direct3D Rendering pipeline.
Vertex Data
Streaming
Assembled Vertexes
Vertex Shading
Programmable
Vertex Shading
Primitive Data
Fixed Function
Vertex Shading
Shaded Vertexes
Geometry Processing
Texture Data
Interpolated Pixels
Pixel Shading
Texture Sampling
Programmable
Pixel Shading
Fixed Function
Pixel Shading
Shaded Pixels
Pixel Processing
Rendered Pixels
Rendered Image
Direct3D define un proceso en diferentes etapas, o rendering pipeline. Como entrada
a dicho proceso tenemos los datos del modelo de la escena: Vértices, Primitivas y
Texturas. Como resultado obtenemos la imagen renderizada o frame.
31
La etapa de Streaming se encarga de extraer los vértices del batch que se va a dibujar
a partir de las estructuras de datos que almacenan la geometría de la escena.
La etapa de Vertex Shading realiza modificaciones en las componentes de los
vértices. Existen dos variantes excluyentes:
El Fixed Function Vertex Shading consiste en una serie de cálculos predefinidos.
Calcula una nueva posición para los vértices que corresponde a su proyección en la
imagen 2D final y además modifica las componentes de color del vértice, simulando
que éste está iluminado utilizando un modelo de iluminación local.
32
El Programmable Vertex Shading es más flexible, permitiendo a la aplicación
especificar el procedimiento a aplicar al vértice expresándolo mediante un código
ensamblador. Este pequeño programa se llama Vertex Shader.
La etapa de Geometry Processing se encarga de las primitivas. A partir de los vértices
de sus extremos genera píxeles que representan los puntos interiores de la superficie.
Estos píxeles también tienen componentes, como los vértices, pero sus valores se
obtienen por interpolación a partir de los vértices de la primitiva.
El Pixel Shading consiste en calcular la componente final de color para los píxeles.
Nuevamente tenemos dos versiones:
33
Texture
Interpolated Pixels
Fixed Function Pixel Shading
Shaded Pixels
El Fixed Function Pixel Shading utiliza una secuencia de cálculos con los valores de
color del píxel, típicamente mezclándolos con colores obtenidos de las texturas.
El Programmable Pixel Shading permite calcular el valor del píxel mediante código, de
forma que hablamos también de un Pixel Shader.
La etapa de Texture Sampling recupera valores de las Texturas a petición de la etapa
de Pixel Shading. Ésta le indica que posición de la textura se requiere y le devuelve el
valor, aplicando mecanismos de filtrado de imagen.
En el Pixel Processing los píxeles, ya con su valor de color calculado, se someten a
una serie de pruebas que determinarán si finalmente aparecerán en la imagen
renderizada o no. Por ejemplo los píxeles de la pata de la mesa que queda oculta han
34
sido calculados como todos los otros, sin embargo no aparecerán en la imagen final
siendo descartados en favor de los más cercanos.
Fixed Function Pipeline y Programmable Pipeline
Como hemos visto las etapas de Vertex y Pixel Shading disponen de dos variantes
cada una. Se habla de Fixed Function Pipeline para referirse al Pipeline cuando se
utilizan las versiones fijas. En contraposición el Programmable Pipeline es el nombre
que se utiliza cuando se usan shaders. El Fixed Function Pipeline no se utiliza
demasiado hoy en día, su origen se encuentra en épocas en que el hardware gráfico
no era programable.
Fixed function pipeline
Streaming
Una vista en detalle de la etapa de Streaming nos permitirá entender cómo la
aplicación provee los vértices del modelo y cómo esta etapa es capaz de recuperar los
que se van a renderizar en el batch.
Los Vertex Buffers son objetos que almacenan una serie de Vértices. Son la manera
preferente de proveer de vértices al pipeline. Para que el pipeline acceda a ellos es
necesario asociarlos a uno de los canales, o Streams, disponibles. Usaremos un
sencillo ejemplo de una aplicación que dibuja un triángulo.
35
2
0
1
La primera opción que mostramos es utilizar un sólo Vertex Buffer, podemos observar
en su contenidos que los Vértices poseen componentes de posición y color.
Como segunda opción la aplicación podría utilizar dos Vertex Buffers, uno en el que
almacena la posición y otro en que almacena el color. En este caso los vértices tienen
sus componentes repartidas.
36
Observando este último ejemplo se entiende el sentido de ensamblar un vértice. La
tarea de la etapa de streaming consiste en ir recogiendo, para cada vértice que se va a
dibujar, sus componentes repartidas en los vertex buffers y ensamblándolas para
formar un Vértice con todas estas componentes.
La información sobre cómo está distribuido cada vértice en los diferentes streams se
provee en el objeto Vertex Declaration. Simplemente consiste en una lista de
declaraciones, una por componente, en que se especifica su tipo y el offset en que se
encuentra.
Típicamente en los modelos un mismo vértice forma parte de varias primitivas. Para
evitar almacenar varias veces el vértice se utiliza un Index Buffer. Éste simplemente
contiene una serie de índices que hacen referencia a los vértices.
37
Fixed Function Vertex Shading
En esta etapa las componentes del Vértice se someten al proceso conocido como
Transform & Lighting.
Transformación
Transformación de modelo
Consideremos una escena en la que hay que ubicar unos objetos. Cada objeto tiene
una descripción que dice que se ha de ubicar en una cierta posición y orientación
respecto a algún punto de referencia. La trasformación de modelo consiste
precisamente en esto, desplazar los vértices del objeto a su posición
final en la
escena a partir de una matriz de transformación que describe donde se han de
colocar.
38
World Transform
World Coordinate System
(WCS)
La posición de un vértice del modelo se recibe como unas coordenadas en un sistema
de referencia propio del modelo. Típicamente se querrá situar al modelo en alguna
posición y orientación respecta a un sistema de referencia absoluto. Para ello se
realizarán diversos cambios de coordenadas directos como translaciones y rotaciones.
Como hemos expuesto, estas transformaciones se pueden expresar de forma
combinada en una única matriz. Multiplicando la posición del vértice que recibimos del
modelo por esta matriz obtendremos su ubicación en la escena.
39
Transformación de observador.
Una vez el objeto está situado en la escena1 se tiene en cuenta desde donde se va a
observar. Es como situar una cámara en la escena, geométricamente, un sistema de
referencia desde el cual describirla.
Por tanto la siguiente transformación consiste en cambiar el sistema de coordenadas
en que están definidos los vértices al sistema de referencia del observador. Este es un
cambio de sistema de referencia inverso, ya que queremos las coordenadas del
mismo punto respecto a otro sistema. De hecho, se pueden realizar una serie de
cambios de coordenadas inversos hasta alcanzar el sistema de referencia que se
desee. Nuevamente estos cambios se expresan de forma combinada en una matriz.
Transformación de proyección.
Siguiendo con el símil hemos colocado la cámara en la escena, pero al igual que en
una fotografía la escena se representa proyectada en un rectángulo, y existen muchas
1
En este apartado hablare de “objeto” aunque propiamente debería
hablar del “grupo de primitivas que forman el batch”.
40
formas de realizar esta proyección. La transformación de proyección, más compleja
que las anteriores, define cómo se realizará.
El volumen de visión es la parte de la escena que puede llegar a aparecer en la
representación 2D. En este volumen de visión se sitúa un plano Near y un plano Far
que limitan los objetos más cercanos y lejanos que se representarán. La proyección
consiste en transformar las coordenadas de los vértices a un nuevo espacio de
coordenadas llamado de clipping en el que las coordenadas están normalizadas entre
-1 y 1. Según como se defina el volumen de visión tenemos varias proyecciones.
41
Viewing Volume
Znear plane
(Screen)
Clipping Space
En la proyección ortográfica la escena se observa desde una distancia infinita, de esta
forma se conservan las dimensiones y orientaciones relativas de los objetos. Es útil,
por ejemplo, en planos industriales.
En la proyección en perspectiva el volumen de visión es un tronco de pirámide, y los
objetos cercanos parecen más grandes.
42
Iluminación
La iluminación de Direct3D se basa en el modelo de iluminación local de Blinn – Phong
introduciendo algunas modificaciones para lograr un resultado más realista. Los
cálculos utilizan como entrada diversos componentes del vértice, entre ellos posición,
normal, color difuso y especular. Como resultado se computan unas nuevas
componentes de color difuso y especular para el vértice, representando la luz difusa y
especular que refleja.
Las fuentes de iluminación, luces, son más sofisticadas. Además de luz difusa y
especular contribuyen a la luz ambiente.
Además se incluyen coeficientes de
atenuación, que hacen que la intensidad de la luz no sea uniforme en el espacio.
Existen varios tipos de luz:
La luz ambient es una luz cuyo efecto es uniforme para toda la escena. Tan sólo
contribuye en forma de luz ambiente.
La luz direccional es una luz para la que no se define una posición, sino tan sólo la
dirección en la que se recibe, es decir el vector L. El ejemplo típico de este tipo de luz
43
es el Sol, cuya luz se recibe prácticamente en un mismo ángulo al estar situado a gran
distancia.
La luz posicional se sitúa en una posición del espacio y emite luz en todas las
direcciones, de modo similar a una bombilla. El vector L, por tanto, se calculará por
cada vértice a partir de su posición respecto a la luz. El efecto de una luz posicional
disminuye con la distancia, simulando la atenuación de la radiación por el medio en
que se propaga. Esta atenuación se produce en una curva descendente cuya forma se
puede ajustar.
Una luz spot se comporta como una luz posicional que emite luz tan sólo en algunas
direcciones. Se puede pensar en ella como en un foco. Adicionalmente a los
parámetros de una luz posicional su luz se atenúa alrededor de una cierta dirección
formando un cono de luz. Cuanto más coincidente sea el vector L con esta dirección
mayor será la luz que reciba el vértice.
44
Las constantes superficiales del modelo de iluminación de Blinn-Phong forman parte
del material. Éste tiene unos valores globales para la superficie, sin embargo los
coeficientes difuso y especular se pueden tomar de las componentes del vértice.
Como detalle adicional el material incluye además una constante emisiva, que permite
simular superficies fluorescentes.
Geometry Processing
Una vez que los vértices de la primitiva han pasado por el proceso de shading se tiene
en cuenta a la primitiva como tal. Dado que hasta aquí llegan todas las primitivas de la
escena, una primera tarea es eliminar las que no vayan a ser visibles, de cara a un
menor coste computacional.
45
El test de face culling consiste en descartar las primitivas, que son superficies
orientadas, en función de si están de cara al observador o no. Generalmente el test se
configura para que las que no miran al observador se descarten.
Before frustrum
clipping
After frustrum
clipping
Rendered faces
El proceso de frustrum clipping consiste en descartar las primitivas que quedan fuera
del espacio de coordenadas normalizado. De esta forma las primitivas que continúan
son sólo aquellas que estaban dentro del volumen de visión.
46
After Rasterization
Screen Space
Culling Space
Before Rasterization
VERTEXES
PIXELS
La Rasterización es uno de los procesos más importantes de esta etapa. Si antes de
ella hablábamos de vértices y primitivas ahora pasamos a hablar de píxeles. Para una
primitiva, se genera uno de estos píxeles2 por cada píxel de la imagen final que esta
cubra.
Estos
píxeles son similares a los vértices en cuanto tienen posición, incluida la
coordenada z, y otras componentes como normal y color. La diferencia es que
representan zonas interiores de la primitiva. Su propósito es servir para calcular el
color final del píxel correspondiente en la imagen renderizada.
2
No hay que confundir los píxeles que se generan en la rasterización con los píxeles de la imagen final,
que solo tienen posición 2D y color. De hecho, para evitar esta confusión, en el API OpenGL se les llama
fragmentos a los primeros.
47
Flat Shading
Gouraud Shading
Una vez generados los píxeles es necesario dar valor a sus componentes. Al no contar
en este punto con más datos que los vértices las componentes de los píxeles se
calculan a partir de las componentes de los vértices.
La técnica de Flat Shading consiste en copiar los componentes del primer vértice de la
primitiva en todos los píxeles. Esto da un aspecto uniforme a la primitiva, por lo que los
objetos aparentan estar formados de caras planas.
Como alternativa, la técnica de Gouraud Shading consiste en interpolar el valor de las
componentes del píxel a partir de las componentes de los vértices ponderándolas
según las coordenadas baricéntricas del punto. Esto da un aspecto suavizado a la
primitiva, ya que los píxeles interiores tienen componentes con valores graduales.
Texture sampling
Además de los métodos de shading es posible incorporar un mayor nivel de detalle en
los píxeles interiores de las primitivas a través de las técnicas de texturización. Éstas
permiten definir valores precisos para sus componentes.
Una textura almacena los valores de una propiedad en el espacio para unas
posiciones concretas llamadas téxeles. Un caso sencillo sería una textura 2D, que
define estas propiedades para un área rectangular a través de una matriz 2D de
téxeles.
48
Las texturas se aplican a las primitivas mediante la inclusión de coordenadas de
textura en los vértices. Estos componentes definen qué posición de la textura
corresponde al vértice. Usualmente los píxeles interiores generados en la rasterización
tienen estas coordenadas interpoladas por el método de Gouraud, de forma que cada
uno de ellos se corresponde a un punto diferente de la textura.
Calcular el valor de una textura para una cierta coordenada, tomar un sample, no
siempre es algo directo. La mayor calidad se obtiene cuando existe una
correspondencia exacta entre el tamaño del píxel y el tamaño del téxel. Habitualmente
este no es el caso, por ello hablaremos aquí de técnicas de sampling.
El filtro de minificación es método para calcular el sample cuando el téxel es menor
que el píxel.
49
El filtro de magnificación es el caso contrario, es decir, el téxel es mayor que el píxel.
El mipmapping consiste en definir varios mipmaps para una única textura. Éstos son
matrices de téxeles que utilizan resoluciones cada vez menores, dividiendo entre dos
la resolución base.
50
Al representar una escena, en los píxeles que presenten minificación se utilizará el
mipmap cuyo tamaño de téxel se adapte mejor al píxel. Los objetos más alejados en la
figura utilizarán los mipmaps más pequeños.
Fixed function pixel shading
En esta etapa cada uno de los píxeles generados en la etapa anterior se somete a un
proceso en el que se establece su color final a partir de sus componentes. Éstos
pueden ser un color especular, un color difuso y un número determinado de
coordenadas de textura.
51
El proceso se realiza en diferentes etapas llamadas texture stages. Tomando como
valor inicial el color difuso del píxel cada etapa lo combina con un sample de su
textura, que obtiene a través del sampler utilizando los componentes de coordenadas
de textura correspondientes. Como paso final se añade el color especular.
Una texture stage realiza una operación de combinación sobre sus argumentos. Estas
operaciones son vectoriales, y se realizan para cada componente del color. En las
fórmulas se tiene en cuenta así mismo la componente de transparencia.
52
DIFFUSE
Texture Stage
Texture Stage
MULTIPLY
ADD
MODULATE
De esta forma las texture stages se configuran para obtener diferentes efectos
visuales. Típicamente se utilizarán de la manera expuesta, aunque admiten un gran
número de configuraciones alternativas.
53
Pixel processing
En esta etapa se realizan las operaciones finales del pipeline, que darán como
resultado la imagen representada de la escena. El píxel que se recibe de la etapa
anterior ya tiene su color final calculado. Inicialmente se somete a una serie de tests
de aceptación o rechazo. Éstos determinan si este píxel se utilizará en los cálculos
posteriores. Finalmente los píxeles que superen todos los tests se utilizan para
calcular el color del píxel de la imagen representada, posiblemente combinando varios
de ellos en el proceso de alpha blend.
54
El Z Test descarta los píxeles en función de su coordenada z. Típicamente de todos
los que correspondan a una misma posición de la imagen final el test sólo deja pasar
aquel que tenga la coordenada z menor.
55
Hidden surface removal
Conceptualmente es un test de determinación de visibilidad, ya que selecciona el píxel
más cercano al observador. Los píxeles descartados corresponden a áreas de las
primitivas que quedan ocultas por otras más cercanas.
56
Scene without reflections
Reflected geometry
Stencil Buffer
Rendered image
El stencil test permite definir áreas de la imagen de forma arbitraria y descartar los
píxeles en función de si están dentro de un área o no. Este test se utiliza para simular
efectos como sombras y espejos planos.
Otros tests son el scissor test, que descarta aquellos píxeles que quedan fuera de un
área rectangular de la imagen final y el alpha test, que descarta los píxeles según su
transparencia.
57
La última operación que se realiza es el alpha blending. Cuando las primitivas son
transparentes varios píxeles pueden corresponder a un mismo píxel en la imagen final.
La configuración de este proceso determina en qué medida contribuye cada píxel al
píxel final.
Programmable pipeline
Interpolated Pixel
Sampler 0
Pixel Shader ALU
Const Regs
Temporal Registers
Constant Registers
Sampler 7
Temporal Registers
...
Samp Regs
Input Registers
Output Registers
Shaded Pixel
En el Fixed Function Pipeline el procedimiento a aplicar es predeterminado. Tomando
como ejemplo la etapa de iluminación vemos que se pueden utilizar diferentes
parámetros para las luces, pero no utilizar un modelo de iluminación diferente del de
Blinn-Phong.
58
Las aplicaciones que requieren de esta clase de expresividad utilizan el Programmable
Pipeline. El procedimiento que se quiere aplicar a vértices o píxeles se especifica en
programas llamados respectivamente Vertex Shader y Píxel Shader. De esta forma
pueden implementarse no sólo modelos de iluminación alternativos sino multitud de
efectos especiales.
Lenguaje de shading
Los shaders consisten en un código ensamblador que se ejecutará una vez por cada
Vértice o Píxel. Atendiendo únicamente a su entrada y su salida su efecto es computar
para cada vértice o píxel los valores finales de sus componentes de salida a partir de
sus componentes de entrada.
59
Los registros que utilizan los vertex shaders son vectores de cuatro componentes. De
esta forma se puede almacenar valores como color, posición, normal de forma directa.
Adicionalmente las instrucciones usualmente operan sobre todos los componentes del
vector en paralelo. Los juegos de instrucciones que presentan estas características se
conocen como Single Instruction Multiple Data (SIMD).
Existen juegos de instrucciones y registros separados para vertex y píxel shaders,
aunque son similares en apariencia y funcionalidad. Las explicaremos conjuntamente,
aunque en rigor dos instrucciones con el mismo nombre y funcionalidad, una del juego
de instrucciones del vertex shader y otra del píxel shader, son diferentes.
Por otro lado existen diferentes versiones, cada una de ellas refleja las capacidades
del hardware gráfico en una determinada época. Estas versiones se conocen como
modelos de shader.
Registros
Los registros de entrada almacenan los componentes del vértice o píxel tal como se
reciben de la etapa anterior. Son sólo de lectura y se han de declarar al principio del
shader.
Los registros de salida se utilizan para guardar las componentes del vértice o píxel
calculadas que se pasarán a la etapa siguiente. Se han de declarar y son sólo de
escritura.
Para los cálculos intermedios se utilizan registros temporales. Lógicamente estos
registros no conservan su valor más allá de la ejecución del shader y admiten tanto
lectura como escritura.
Los registros constantes o, simplemente, las constantes se utilizan para que la
aplicación provea parámetros para la función implementada por el shader.
Se
denominan constantes porque su valor es el mismo para todos los vértices o píxeles
del batch.
60
Por ejemplo, un shader que deforme un objeto aplicando una onda sinusoidal podría
utilizar una constante para almacenar la amplitud de dicha onda.
Los sampler registers dan acceso a los samplers. De esta forma el shader es capaz de
utilizar valores recuperados de las texturas en sus cálculos. Es importante darse
cuenta de que esto permite utilizar las texturas no sólo para almacenar datos de color,
sino para almacenar cualquier tipo de información.
Existen otros registros para usos muy específicos. Un ejemplo es el registro de
dirección, cuya utilidad es permitir acceso indexado a las constantes. Esto quiere decir
que podemos obtener a partir de una constante base la constante cuya posición
relativa corresponda con el valor de este registro.
Instrucciones
Una instrucción se compone de un identificador y posiblemente un parámetro de
destino y varios de origen.
61
Name
Description
abs - vs
Absolute value
add - vs
Add two vectors
break - vs
Break out of a loop - vs...endloop - vs or rep...endrep block
break_comp - vs
Conditionally break out of a loop - vs...endloop - vs or rep...endrep block, with a comparison
breakp - vs
Break out of a loop - vs...endloop - vs or rep...endrep block, based on a predicate
call - vs
Call a subroutine
callnz bool - vs
Call a subroutine if a Boolean register is not zero
callnz pred - vs
Call a subroutine if a predicate register is not zero
crs - vs
Cross product
dcl_usage input register - vs Declare input vertex registers (see Registers - vs_3_0)
dcl_samplerType - vs
Declare the texture dimension for a sampler
def - vs
Define constants
defb - vs
Declare a Boolean constant
defi - vs
Declare an integer constant
dp3 - vs
Three-component dot product
dp4 - vs
Four-component dot product
dst - vs
Distance
else - vs
Begin an else block
endif - vs
End an if bool - vs...else block
endloop - vs
End of a loop - vs block
endrep - vs
End of a repeat block
exp - vs
Full precision 2x
exp - vs
Partial precision 2x
frc - vs
Fractional component
if bool - vs
Begin an if bool - vs block (using a Boolean condition)
if_comp - vs
Begin an if bool - vs block, with a comparison
if pred - vs
Begin an if bool - vs block with a predicate condition
label - vs
Label
lit - vs
Calculate lighting
log - vs
Full precision log2(x)
logp - vs
Partial precision log2(x)
loop - vs
Loop
lrp - vs
Linear interpolation
m3x2 - vs
3x2 multiply
m3x3 - vs
3x3 multiply
m3x4 - vs
3x4 multiply
m4x3 - vs
4x3 multiply
m4x4 - vs
4x4 multiply
mad - vs
Multiply and add
max - vs
Maximum
min - vs
Minimum
mov - vs
Move
mova - vs
Move data from a floating point register to an integer register
mul - vs
Multiply
nop - vs
No operation
nrm - vs
Normalize
pow - vs
xy
rcp - vs
Reciprocal
rep - vs
Repeat
ret - vs
End of a subroutine
rsq - vs
Reciprocal square root
setp_comp - vs
Set the predicate register
sge - vs
Greater than or equal compare
sgn - vs
Sign
sincos - vs
Sine and cosine
slt - vs
Less than compare
sub - vs
Subtract
texldl - vs
Texture load with user-adjustable level-of-detail
vs
Version
El juego de instrucciones está adaptado especialmente al tratamiento de gráficos. Lo
componen unas decenas de instrucciones. En la figura podemos ver el juego de
instrucciones para un vertex shader 3.0, se pueden clasificar en varios grupos.
62
Las instrucciones de declaración o directivas se sitúan al principio del shader.
Los shaders se inician declarando su versión. El primer número, o versión mayor,
corresponde al modelo de shader. El segundo corresponde a la versión menor, que
refleja típicamente pequeños cambios, como un mayor número de registros
temporales disponibles.
La instrucción de declaración de entrada asocia un registro de entrada con la
componente del vértice o píxel que se especifique como sufijo. La instrucción de
declaración de salida hace lo propio con los registros de salida.
La instrucción de declaración de constante permite dar un valor inicial a una constante.
Un uso típico sería almacenar el valor de PI.
Las instrucciones aritméticas permiten una amplia variedad de operaciones sobre
vectores, algunas muy comunes como el producto escalar. Otras son específicas del
dominio de los gráficos, como el cálculo del exponente de Phong.
Las instrucciones de textura tienen como parámetros de origen un registro de sampler
y unas coordenadas de textura. A partir de ellos almacenan en el parámetro de destino
un sample de la textura.
63
Las instrucciones de control de flujo permiten realizar saltos en la secuencia de
instrucciones. Se soportan construcciones comunes como salto condicional y bucles e
incluso llamadas a subrutinas.
Modificadores
Tanto los parámetros de origen como los de destino admiten modificadores. Estos
afectan al valor que se escribe o se lee del registro.
El modificador de Swizziling permite que las componentes del parámetro de origen
tomen su valor de las componentes del registro que se especifiquen. De esta forma se
pueden obtener un valor en un orden distinto o incluso con algunas componentes
replicadas.
64
add r0.xy, v0, r1
Operation result Destination register
X
X
Y
Y
Z
Z
W
W
El modificador de Write Masking permite controlar en qué componentes del registro de
destino es posible escribir. Se utiliza, por ejemplo, para trabajar con vectores de dos
dimensiones.
Del resto de modificadores destacamos el de negación, que permite utilizar un
parámetro de entrada que sea el negado de un registro; el de valor absoluto, que
fuerza a positivo el signo de las componentes del registro y el de saturación, que
controla que los valores que se escriban en un registro no sobrepasen el valor uno,
truncando el resultado si es necesario. Una utilidad de éste último es impedir que las
componentes de los registros que representen colore superen el valor máximo para
una componente de color, que es precisamente uno.
High Level Shading Language
65
El High Level Shading Language (HLSL) es un lenguaje de alto nivel para la
programación de shaders de sintaxis similar a C. Microsoft pone a disposición de los
programadores un compilador que permite obtener el shader equivalente en
ensamblador.
Es importante notar que el Direct3D sólo recibe shaders en ensamblador, por lo que a
efectos de realizar un implementación del API no es necesario dar soporte al HLSL.
2.2.2 Direct3D API
Modelo COM
Un componente es simplemente una entidad software que realiza un servicio y de la
cual únicamente disponemos de una interfaz. Ésta nos permite solicitar los servicios
pero oculta completamente no sólo la implementación sino también la naturaleza de la
entidad que lo realiza. Es un concepto importado de los componentes electrónicos.
66
El Component Object Model (COM) es una plataforma de componentes de Microsoft.
Siguiendo sus especificaciones es posible crear componentes software que puedan
ser utilizados desde cualquier lenguaje que soporte el modelo COM.
Las aplicaciones utilizan los interfaces mediante referencias a ellos. Típicamente las
aplicaciones solicitan una o más referencias a un interfaz e invocan sus servicios a
través de éstas, cuando no necesitan los servicios de la interfaz liberan las referencias.
Los componentes mantienen una cuenta de referencias, de forma que pueden
liberarse recursos cuando un componente no es referenciado por ninguna aplicación.
interface IUnknown
{
virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) = 0;
virtual ULONG AddRef(void) = 0;
virtual ULONG Release(void) = 0;
};
Las interfaces especifican una serie de funciones propias y pueden heredar funciones
de otras interfaces. De esta forma amplían los servicios que se ofrecen en la interfaz
base.
El modelo COM especifica la interfaz base IUnknown de la que todos los
interfaces han de heredar.
Los métodos AddRef y Release se utilizan para informar al componente de cuantas
referencias se están utilizando.
Por otro lado el método QueryInterface sirve para solicitar un interfaz diferente para el
un mismo componente.
Interfaces Direct3D
Direct3D es una API que sigue el modelo COM. De esta forma existe un componente
Direct3D
en
los
sistemas
Windows
que
da
servicio
independientemente del lenguaje en que estén implementadas.
67
a
las
aplicaciones
VertexDeclaration
VertexBuffer
Device
IndexBuffer
Resource
VertexShader
PixelShader
Direct3D
BaseTexture
VolumeTexture
Texture
Surface
Surface
CubeTexture
Surface
Direct3D define una serie de interfaces relacionados entre sí. Observamos como
Direct3D es un objeto COM, por lo que tiene su propia interfaz. A partir de ésta
podemos acceder al interfaz Device, que da acceso al componente que realiza el
rendering. Es el interfaz más amplio, dispone de decenas de funciones:
A través de éste interfaz es posible crear instancias del resto de componentes, que
representan otras entidades del rendering pipeline.
68
Para definir que entidades se utilizarán durante la renderización se ofrecen funciones
para relacionarlas con el Device.
Por último existen varias funciones para renderizar batches.
Un grupo de interfaces son aquellos que representan recursos. Los recursos son
entidades que tienen asignada una o más áreas de memoria. El acceso a la memoria
asignada a un recurso para, por ejemplo, escribir nuevos contenidos se realiza
mediante unas funciones llamadas lock y unlock.
69
Una aplicación de ejemplo
Presentaremos para finalizar el apartado el código de una aplicación mínima en
Direct3D. Primero veremos la versión de función fija y luego la programable. En ambos
casos el resultado será un sólo triángulo.
IDIRECT3D9 *
D3D
= NULL;
IDIRECT3DDEVICE9 *
Device
= NULL;
D3D = Direct3DCreate9( D3D_SDK_VERSION );
D3DPRESENT_PARAMETERS PresentParameters;
ZeroMemory( &PresentParameters, sizeof(PresentParameters) );
PresentParameters.Windowed = TRUE;
PresentParameters.Width = 400;
PresentParameters.Height = 400;
PresentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
PresentParameters.BackBufferFormat = D3DFMT_A8R8G8B8;
D3D->CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&PresentParameters,
&Device );
El primer paso que realiza una aplicación es obtener una referencia al componente
Direct3D y utilizarlo para crear un Device. La configuración del device se especifica
mediante la estructura PresentationParameters.
70
A continuación la aplicación crea los recursos que va a necesitar para el rendering, en
este caso un vertex buffer. Para establecer sus contenidos realiza una operación lock,
que le devuelve un puntero a un buffer de memoria. Tras copiar a este buffer los
vértices indica que los contenidos están actualizados mediante la operación unlock.
D3DXMATRIXA16 matWorld;
D3DXMatrixIdentity( &matWorld);
Device->SetTransform( D3DTS_WORLD, &matWorld );
D3DXMATRIXA16 matView;
D3DXMatrixIdentity( &matView);
Device->SetTransform( D3DTS_VIEW, &matView );
D3DXMATRIXA16 matProj;
D3DXMatrixPerspectiveFovLH(&matProj, 3.14f/4.0f, 1.0f, 1.0f, 100.0f );
Device->SetTransform( D3DTS_PROJECTION, &matProj );
Device->SetRenderState( D3DRS_LIGHTING, FALSE );
Como parte de la configuración de función fija se establecen las matrices de
transformación y se desactiva la iluminación, de forma que los colores de los vértices
no se alterarán.
71
Device->Clear( 0, NULL, D3DCLEAR_TARGET, 0xff0000ff, 0 );
Device->BeginScene();
Device->SetStreamSource(0, VertexBuffer, 0, sizeof(CUSTOMVERTEX));
Device->SetFVF( D3DFVF_CUSTOMVERTEX );
Device->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );
Device->EndScene();
Device->Present( NULL, NULL, NULL, NULL );
En este punto se inicializa el back buffer con el color inicial. Después se procede al
dibujado de las primitivas y finalmente los contenidos del back buffer se promueven al
front buffer.
Por último la aplicación indica que no va a necesitar más los interfaces.
Una aplicación que utilice el programmable pipeline inicializa el device y los recursos
de forma idéntica, creando adicionalmente un píxel y un vertex shader. El código
ensamblador ha de proveer como un bytecode, no como una cadena de texto. Por
este motivo la aplicación ensambla el shader utilizando la librería auxiliar D3DX.
72
La aplicación crea un interfaz VertexShader y un interfaz PixelShader con el bytecode
que ha obtenido previamente.
Device->SetVertexShader(VertexShader);
Device->SetPixelShader(VertexShader);
En el momento de configurar el pipeline no se realiza la configuración de función fija.
En su sustitución se asocia el VertexShader y el PixelShader. Las operaciones
siguientes son idénticas, adicionalmente se liberan el VertexShader y el PixelShader.
73
2.3 El simulador ATTILA
2.3.1 El pipeline del simulador
A nivel funcional el simulador se comporta como un pipeline programable.
75
Streaming
La primera etapa del pipeline es el Streaming. El simulador cuenta con una serie de
streams a los que se pueden asociar búferes de memoria a través de su dirección.
Los vértices se almacenan en estos búferes, aunque para el simulador son áreas de
memoria. El simulador soporta modo indexado, sin embargo no hay un stream
dedicado para índices, sino que se usa uno de los streams genéricos. El vértice se
ensambla indicando para cada uno de sus atributos en qué stream está situado. La
configuración de esta etapa, como la de las siguientes, se realiza a través de registros.
Vertex Shading
Una vez ensamblado el vértice se somete al proceso de shading. Para ello se ejecuta
un vertex shader almacenado en un buffer de memoria. La unidad de shading realiza
así un cálculo sobre los atributos del vértice que recibe en y deja los resultados en los
registros de salida, que corresponden al vértice transformado.
76
Geometry Processing
La etapa de geometry processing comienza ensamblando la primitiva, que en este
caso siempre es un triangulo, una vez se han procesado sus tres vértices. La primitiva
se somete entonces a un test de clipping muy sencillo, que la descarta sólo si está
completamente fuera del espacio de clipping. El siguiente test es un test de culling, es
decir, descarta la primitiva en función de si está de cara o no al observador.
La primitiva se rasteriza generando fragmentos3 a partir de sus vértices, sin embargo
estos fragmentos resultantes sólo tienen interpolada su posición, no el resto de
atributos.
Si se dan ciertas condiciones, una de ellas que se esté renderizando sin tener en
cuenta la transparencia, es posible descartar el fragmento en este punto a través de
tests de visibilidad. El Hierarchical Z aplica un sofisticado método de división espacial
mientras que el Early Z es un Z Test corriente. De esta forma se evita realizar cálculos
sobre un fragmento que luego se descartará en el Z Test.
A los fragmentos que superan estos tests se les interpolan el resto de atributos y se
dirigen a la siguiente etapa.
3
El simulador ATTILA sigue la terminología de OpenGL, y llama
fragmentos a los elementos que resultan de la pasterización.
Recordemos también que Direct3D les llama píxeles.
77
Fragment Shading
El fragmento se procesa a través del fragment shader, de forma análoga al vertex
shader el proceso consiste en calcular los atributos de salida a partir de los de entrada.
Pixel Rendering
Una vez conocidos los valores finales de los atributos el fragmento pasa por el Alpha,
Z (si no lo había hecho ya) y Stencil Test. Si no es descartado se realiza el Alpha
blending y se actualiza el píxel correspondiente en el back buffer.
78
Texture Sampling
El simulador soporta texturas 2D, 3D y cúbicas, así como mipmapping. Una textura
ocupa así varios búferes, uno por mipmap. Los téxeles no se sitúan en memoria
siguiendo una ordenación lineal, sino que utilizan un orden especial en zigzag
conocido como orden de Morton.
Las texturas se asocian a Texture Units que se encargan del sampling, aplicando los
filtrados correspondientes. Las Shader Units realizan peticiones a las Texture Units
cuando el shader que ejecutan lo requiere.
Lenguaje de shading
Físicamente el simulador dispone de un conjunto de pequeños procesadores
vectoriales, llamados Shader Units, que pueden realizar las funciones de una Vertex
Shader Unit o una Fragment Shader Unit. Esto se conoce como modelo de shader
unificado. Una consecuencia importante es que los registros y el juego de
instrucciones disponibles para la etapa de Vertex Shading y la de Fragment Shading
son prácticamente iguales. Una diferencia notable es que en la etapa de Vertex
Shading no se puede acceder a texturas.
79
Opcode
NOP
ADD
ARA
ARL
ARR
COS
DP3
DP4
DPH
DST
EX2
EXP
FLR
FRC
LG2
LIT
LOG
MAD
MAX
MIN
MOV
MUL
RCC
RCP
RSQ
SEQ
SFL
SGE
SGT
SIN
SLE
SLT
SNE
SSG
STR
TEX
TXB
TXP
KIL
CMP
END
Description
no operation
add
address register add
address register load
address register load (rounded)
cosine
3-component dot product
4-component dot product
Homogeneous dot product
distance vector
exponential base 2 (full precission)
exponential base 2 (partial precission)
floor
fractional part
full precission log 2
light coefficients
logarithm base 2
multiply and add
maximum
minimum
move
multiply
reciprocal (clamped)
reciprocal
reciprocal square root
set on equal
set on false
set on greater equal than
set on greater
sine
set on less or equal
set on less than
set on non equal
set sign
set on true
texture lookup
texture lookup with LOD bias
projective texture lookup
kill fragment
compare
program end
El juego de instrucciones está basado en el Nvidia Shader Model 1. Disponemos de
instrucciones SIMD aritméticas, lógicas y de acceso a texturas. En sus parámetros
admiten modificadores de Swizzling, Masking, saturación, negación y valor absoluto.
Los registros disponibles son vectoriales de cuatro componentes, que se organizan en
varios bancos de registros: de entrada, de salida, de textura, parámetros constantes y
temporales. Los registros de textura representan a las unidades de textura. Así mismo
existen dos registros de dirección para acceder a las constantes de forma relativa.
80
2.3.2 El hardware simulado
El simulador de GPU ATTILA está formado por diferentes componentes que
podríamos encontrar en la micro arquitectura de una GPU real.
Todos estos
componentes están implementados en software, pero nos referiremos a ellos como si
fueran un hardware físico.
81
Streaming stage registers
VERTEX_ATTRIBUTE_MAP
VERTEX_ATTRIBUTE_DEFAULT_
VALUE
STREAM_ADDRESS
STREAM_STRIDE
STREAM_DATA
STREAM_START
STREAM_COUNT
INDEX_MODE
INDEX_STREAM
stores the stream where each vertex attribute is located
default value for an attribute used if not available from a stream
memory address of vertex/index data for the stream
offset in bytes between indexes/vertices for each stream
type of data to stream
first vertex/index to be streamed in a batch
count of vertexes/indexes to be streamed
enables or disables indexed mode
stream to be used for indexes
El simulador dispone de una serie de registros que controlan el funcionamiento de sus
diferentes componentes. Para almacenar los recursos que se utilizan en el rendering el
simulador dispone de su propia memoria gráfica.
2.3.3 La interfaz del simulador.
El simulador recibe como entrada transacciones. Éstas representan las órdenes de
bajo nivel que se transmitirían a una GPU real a través del bus al que estuviera
conectada.
Transaction Types
WRITE
REG_WRITE
COMMAND
Commands
DRAW
SWAPBUFFERS
CLEARZSTENCILBUFFER
CLEARCOLORBUFFER
LOAD_VERTEX_PROGRAM
LOAD_FRAGMENT_PROGRAM
write from system memory (system to local).
write access to GPU registers
control command to the GPU Command Processor
Draw a batch of vertexes.
Swap the front and back buffer.
clear z/stencil buffer to the current clear value
clear color buffer with the current clear color
Load a vertex program into the vertex instruction memory.
Load a fragment program into the fragment instruction memory.
Las transacciones permiten establecer los valores necesarios en los registros del
simulador, de esta forma se determina el comportamiento de las diferentes etapas. Los
datos, por ejemplo los vértices, se proveen mediante transacciones de escritura de
memoria. Finalmente se pueden enviar órdenes para la unidad de control del
simulador, que representan operaciones de un nivel más alto. Dos ordenes básicas
son DRAW, que ordena la renderización de un batch y SWAPBUFFERS, que produce
la imagen final.
Disponemos de un pequeño driver que ofrece servicios para generar transacciones de
forma cómoda, así como una gestión de memoria básica que permite reservar áreas
82
de memoria local o de sistema y referirnos a ellas mediante identificadores,
abstrayéndonos de las direcciones físicas.
2.3.4 Uso del simulador
Capture
Verify
Simulate
Application
GLInterceptor
Trace
GLPlayer
GLPlayer
Trace stats
OpenGL Driver
OpenGL Driver
ATTILA OpenGL Driver
API stats
Real GPU
Real GPU
ATTILA Simulated GPU
GPU stats
Rendered
Frames
Rendered
Frames
Rendered
Frames
Para realizar una simulación que sea fiel a las condiciones en que trabaja una GPU
real se utilizan aplicaciones reales, típicamente juegos. Estos utilizan los servicios de
rendering a través de API’s como OpenGL o Direct3D. El simulador actualmente da un
soporte completo a OpenGL.
La etapa de captura tiene como objetivo conocer exactamente que operaciones invoca
una aplicación sobre el API y registrarlas en un fichero, llamado traza. El método que
se utiliza es interponer un software entre la aplicación y el API dedicado a interceptar
el tráfico de llamadas. De esta forma la traza almacena la parte del comportamiento de
la aplicación que interesa estudiar.
Una vez se dispone de una traza ésta se puede reproducir esto significa volver a
ejecutar las operaciones que están almacenadas en ella. En una primera etapa y como
comprobación se ejecutan sobre la implementación del API en que se realizó la
captura.
Finalmente se realiza la simulación propiamente dicha, reproduciendo la traza sobre la
implementación del API propia del simulador. Como resultado se obtienen los frames
renderizados, así como ficheros de estadísticas para su posterior análisis.
83
3. Trabajo realizado
85
3.1 Captura, reproducción y análisis de trazas.
Capture
Verify
Simulate
Application
D3D Capture
Trace
D3D Player
D3D Player
Trace stats
Windows D3D
Windows D3D
ATTILA D3D Driver
API stats
Real GPU
Real GPU
ATTILA Simulated GPU
GPU stats
Rendered
Frames
Rendered
Frames
Rendered
Frames
Es necesario probar el simulador en condiciones cercanas a las de una GPU física.
Para ello el trabajo que debe realizar se extrae de ejecuciones de videojuegos4, una
de las aplicaciones que utiliza con más intensidad la GPU. Los videojuegos se
comunican con la GPU a través del API gráfico 3D, en este caso Direct3D. La
secuencia de operaciones que realizan sobre el API es la traza del videojuego.
Esta traza, almacenada en un fichero, se usará como entrada para el simulador. Un
software reproductor recuperará las operaciones de la traza y las ejecutará de nuevo.
Antes de llegar a la GPU simulada las operaciones pasan por el driver Direct3D del
simulador.
La traza se analiza, para conocer qué demandas tiene el videojuego. De esta forma se
puede caracterizar el uso del API y conocer a priori qué funciones del driver han de ser
operativas para poder ejecutarla. Mantenemos un grupo de trazas de referencia, que
corresponden a varios videojuegos representativos.
4
Propiamente debería hablar de “aplicaciones que usen el API
Direct3D”. Pero la mayoría de las que estudiamos son videojuegos, así
que las llamaré así.
87
3.1.1 Subsistema de captura: Aplicación PIX
La captura se realiza en un PC corriente equipado con una tarjeta gráfica de altas
prestaciones y el sistema operativo Windows. Los videojuegos acceden al componente
Direct3D de Windows a través de una librería dinámica que expone los interfaces de
Direct3D. El objetivo es, pues detectar las llamadas a esta librería. Me referiré a la
implementación de Direct3D de Microsoft como el componente Direct3D de Windows,
para distinguirlo de nuestra implementación de Direct3D.
La tarea de captura justificaría de hecho un proyecto aparte, para evitar esto y tras
evaluar las diferentes alternativas se optó por utilizar como capturador el Performance
Investigator for DirectX en adelante PIX, una utilidad propietaria de Microsoft y que se
distribuye dentro del Standard Development Kit de DirectX.
PIX genera un fichero de traza binario que contiene todos los datos que necesitamos,
pero su formato no es público. Por tanto el problema de la captura se reduce a
descubrir el formato del fichero de traza que usa PIX.
Aplicación PIX
A diferencia de las aplicaciones de ofimática en el campo de las aplicaciones gráficas
interactivas el rendimiento es muy importante, ya que los usuarios perciben con
facilidad las caídas en el número de frames por segundo que se producen cuando la
88
GPU se satura. Por este motivo existe un gran interés en utilizar lo más eficientemente
los recursos disponibles, dando lugar a aplicaciones como PIX, que se utiliza para que
los desarrolladores puedan evaluar el rendimiento de las aplicaciones Direct3D.
Sin embargo de cara a este proyecto el único aspecto que interesa de PIX es su
capacidad para guardar en un fichero toda la interacción de una aplicación con el API
Direct3D en un fichero que utilizaremos como traza.
1: INTERCEPT
2: PROPAGATE
DrawPrimitive(prim_type, prim_count)
DrawPrimitive(prim_type, prim_count)
PIX Direct3D
Impostor
Component
3D Application
result
3: STORE
result
Direct3D
Windows
Component
Store(draw_primitive_id, prim_type, prim_count, result)
PixRun
Trace File
En esencia el método que utiliza PIX para realizar la captura es colocar una librería
dinámica impostora que expone las mismas operaciones que Direct3D. La aplicación
realiza las operaciones sobre la impostora y ésta las realiza a su vez sobre Direct3D.
Una vez la operación se ha ejecutado se almacena en el fichero de traza y se
devuelve el control a la aplicación.
Examen del formato de traza
El formato de fichero que usa PIX es binario, y afortunadamente no está encriptado ni
usa compresión. A partir de un examen visual utilizando un editor de archivos binarios
se determinó que el archivo seguía una organización jerárquica en diferentes
estructuras y campos, por lo que se decidió que podía determinarse el formato en un
tiempo razonable.
El tamaño de los ficheros de traza que produce PIX para un videojuego es del orden
de gigabytes. Por este motivo comencé programando una serie de aplicaciones muy
sencillas que utilizan un conjunto controlado de operaciones de Direct3D. Estas
aplicaciones se limitan a realizar unas decenas de operaciones del API, de este modo
89
los ficheros de traza resultantes son muy pequeños y pueden examinarse con
comodidad con un editor de ficheros binarios.
Tamaño de sección
Tipo de sección
Fichero Binario PixRun aplicando
la plantilla desarrollada
Fichero Binario PixRun
Examinando directamente la representación hexadecimal encontramos una estructura
en secciones. Cada sección comienza con un campo que indica su tamaño y otro
campo que indica el tipo. Los datos que completan la sección se interpretan según el
tipo.
Examinar el fichero de este modo es incómodo (y daña la vista). Como otras
aplicaciones parecidas el editor que uso permite el uso de plantillas, que permiten
estructurar el formato binario mediante un sencillo lenguaje de expresiones regulares.
Escribí una plantilla que fui completando con las diferentes aplicaciones de test hasta
dar sentido a los campos que me interesaban.
90
Localización de las llamadas al API Direct3D
PIX guarda en el fichero de traza los Eventos que detecta durante la captura. Éstos
incluyen llamadas a Direct3D que haya realizado la aplicación, pero no se limitan a
ello.
SECTION
HEADER
ATTRIBUTE
DESCRIPTOR
EVENT
DESCRIPTOR
EVENT
ASYNC
ATTRIBUTE
D3DCALL
FOOTER
Size
Type
Data
=0x03E8
=0x03E9
ADID
Unknown
Zero
Name
Format
=0x03EA
EDID
Unknown
Name
attributesCount
ADID
Initialization
…
=0x03EB
EDID
EID
data
=0x03EC
EID
ADID
data
=0x03EC
=0x03ED
=0x13
size
CID
one
return
parameters
El fichero de traza contiene meta datos en sus secciones iniciales. Las secciones de
tipo Event Descriptor contienen la descripción de los atributos que PIX almacena para
cada evento. Las secciones de tipo Attribute Descriptor describen estos atributos.
Examinar estas secciones ayuda a hacerse una idea general de cómo se estructura el
resto del fichero.
Las secciones de tipo Event vienen a continuación y son las más abundantes. El tipo
de Evento que se utiliza para las llamadas a Direct3D se llama D3DCall, y entre sus
atributos encontramos uno llamado Packed Call Package (PCP), que es justo lo que
buscábamos.
Interpretación de una llamada: Packed Call Package
Un Packed Call Package (PCP) es un atributo que almacena una llamada a una
operación de Direct3D. Su estructura incluye el Call Id, que identifica la operación, el
91
valor de retorno y el resto de parámetros. Relacionando el identificador con la
operación del API Direct3D que representa podemos conocer la sintaxis de la
operación e interpretar los parámetros.
En este caso se trata de la llamada CreateDevice del interfaz Direct3D. La
interpretación de los parámetros, siguiendo el orden de la figura, comienza por la
dirección del objeto interfaz. De los cuatro parámetros siguientes se almacena su
valor, ya que son tipos simples. El siguiente es un tipo estructurado que se pasa como
un puntero y recibe un tratamiento diferente, primero se guarda la dirección de la
estructura y a continuación los contenidos de ésta.
Como indica su nombre el efecto de esta llamada es crear un objeto device, de este
modo el último parámetro es de salida. El primer valor es la dirección de la variable
puntero y a continuación se encuentra su valor después de la llamada, es decir la
dirección del objeto device que se creó.
Llamadas Lock/Unlock
En Direct3D el acceso a recursos se realiza mediante las operaciones Lock y Unlock.
La operación Lock retorna un puntero a un área de memoria donde la aplicación puede
leer o escribir. La aplicación indica que ha terminado de trabajar con los contenidos del
92
área de memoria mediante la operación Unlock. Para registrar el acceso a recursos el
fichero de traza almacena los contenidos del área de memoria en este tipo de
operaciones.
Este PCP corresponde a una aplicación que ha terminado de actualizar un vertex
buffer. La aplicación ha invocado previamente una operación inicial Lock y mediante
esta llamada indica que ya ha escrito los vértices en memoria. En la figura pueden
verse como PIX almacena los contenidos del área de memoria que se ha utilizado en
la actualización.
3.1.2 Subsistema de reproducción: D3DPixRunPlayer
La reproducción de trazas consiste en deserializar las operaciones almacenadas en el
fichero de traza PIX y ejecutarlas de nuevo sobre Direct3D. El responsable de esta
reproducción es el subsistema D3DPixRunPlayer.
Es importante señalar que, mientras que la captura se realiza en un PC corriente
equipado con Windows la reproducción se ha de poder realizar tanto en sistemas
Windows como en Linux. Por este motivo el subsistema de reproducción de trazas ha
de ser portable.
93
Tratamiento de llamadas
Una de las principales dificultades que encontré es que cada operación tiene una
sintaxis diferente. Al no encontrar esa meta información en el fichero, es necesario
tratar cada operación mediante un código diferente.
El bucle de reproducción lee el identificador de llamada y ejecuta el tratamiento
correspondiente a cada una. Hay cientos de ramas condicionales, y cada una de ellas
tiene un procedimiento único para obtener los parámetros de entrada, ejecutar la
operación y tratar los parámetros de salida.
94
Como ejemplo, para leer los parámetros de entrada de la operación CreateDevice es
necesario deserializar cada uno de ellos en orden, distinguiendo entre si son valores,
estructuras u objetos.
La clave es que procedimiento de lectura puede expresar de forma genérica para
cualquier operación, ya que su sintaxis determina la secuencia de tipos que se
encuentran en el fichero de traza. Para particular este procedimiento sólo necesito
disponer en algún tipo de estructura de datos de la sintaxis de cada operación.
95
DXCodegen
(clase PixRunPlayerGenerator)
Tratamiento
Genérico
de las
operaciones
Tratamientos
Específicos
Preprocesador
C++
Código
PixRunPlayer
Código de
usuario
La aplicación DXCodegen incluye clases que recuperan la sintaxis de las operaciones
a partir de los ficheros de cabecera del API Direct3D. Esta aplicación me viene dada,
ya que forma parte de otro proyecto. Utilizando DXCodegen como entorno de trabajo
implementé las clases que generan el código5 del reproductor de trazas PixRunPlayer.
El código generado implementa el tratamiento genérico de las operaciones y sigue
unas pautas similares a las del código para la lectura de parámetros que he
comentado. Sin embargo seguir unas reglas generales no es suficiente ya que entre
los cientos de operaciones se encuentran algunas que suponen una excepción a estas
reglas, un ejemplo serían las operaciones Lock/Unlock. Por este motivo es necesario
incorporar al código generado automáticamente un mecanismo para incluir código
específico para las operaciones que lo requieran.
La técnica que se utiliza es generar para cada operación varias constantes del
preprocesador de C++. De este modo se pueden definir las constantes con el código
que realice el tratamiento específico de la operación. Por otro lado es posible incluir
con esta técnica código de usuario para cualquier otro propósito, un ejemplo es la
obtención de estadísticas.
Objetos originales y sustitutos
Las direcciones que encontramos en el fichero de traza corresponden a cada objeto de
la interfaz de Direct3D que la aplicación utilizó durante la captura. Estos objetos ya no
existen, por lo que sus direcciones no son válidas.
5
Resulta muy curioso escribir en C++ un generador de código C++.
96
VertexBuffer2
VertexBuffer2
VertexBuffer1
VertexBuffer1
Device
Direct3D
Device
Texture1
Texture2
1
3D Application
PIX Direct3D
Impostor
Component
Surface1
Surface1
Direct3D
Texture1
Surface2
Texture2
3
Surface3
2
Direct3D Pixrun
Player
PixRun
Trace File
Surface2
Surface3
4
PixRun
Trace File
Los objetos interfaz de Direct3D forman una red de relaciones entre ellos, por ejemplo
durante la captura existía un interfaz textura relacionado con un interfaz surface.
Durante la reproducción esta red de objetos se recrea y cuando la aplicación interroga
al interfaz textura sobre su surface asociado este devuelve una dirección diferente que
la que hubiera devuelto durante la captura. Por tanto durante la reproducción
hablamos de unos objetos sustitutos, presentes en memoria para recibir las
operaciones que se invocaron sobre los objetos originales que existían durante la
captura. Una de las funciones del reproductor será mantener la asociación entre las
direcciones de los objetos interfaz originales y la de los sustitutos.
Tratamiento de las operaciones lock/unlock
Durante la reproducción es necesario actualizar áreas de memoria como resultado de
las operaciones de acceso a recursos Lock/Unlock. Actualizar un área de memoria no
es simplemente copiar los contenidos originales, ya que la disposición de la memoria
puede ser diferente y depende de la arquitectura sobre la que opere Direct3D.
97
Como ejemplo al acceder a la matriz de téxeles de una textura encontramos que las
filas no se encuentran forzosamente seguidas en memoria, y su separación es
variable. Esta separación, llamada pitch para distinguirla del ancho de la fila, depende
de cómo se almacene la textura en memoria y es propia de cada arquitectura gráfica.
Reproducción a nivel de batch
Para favorecer la capacidad de depuración la reproducción puede realizarse a
diferentes granularidades. A nivel de frame, es decir, cuando se actualiza el
98
frontbuffer; a nivel de batch, cuando se dibuja un conjunto de primitivas o una llamada
cada vez. En el ejemplo se reproduce la traza a nivel de batch sobre el componente
Direct3D de Windows. Se aprecia cómo el frame se construye progresivamente.
Reproducción con configuraciones alternativas
[SubstituteDevice]
; This section allows to override some original presentation parameters to
; run the trace with a different configuration for the device.
; Windowed = true
; BackBufferWidth = 1280
; BackBufferHeight = 1024
; BackBufferCount = 1
; D3DSWAPEFFECT_DISCARD -> 1
; D3DSWAPEFFECT_FLIP -> 2
; D3DSWAPEFFECT_COPY -> 3
;
; SwapEffect = 3
; D3DDEVTYPE_HAL = 1,
; D3DDEVTYPE_REF = 2,
;
; DeviceType = 2
; D3DCREATE_SOFTWARE_VERTEXPROCESSING = 0x20
; D3DCREATE_MIXED_VERTEXPROCESSING = 0x80
; D3DCREATE_HARDWARE_VERTEXPROCESSING = 0x40
;
; BehaviorFlags = 0x20
; D3DMULTISAMPLE_NONE = 0,
; D3DMULTISAMPLE_NONMASKABLE = 1,
; D3DMULTISAMPLE_2_SAMPLES = 2,
; D3DMULTISAMPLE_3_SAMPLES = 3,
; D3DMULTISAMPLE_4_SAMPLES = 4,
; D3DMULTISAMPLE_5_SAMPLES = 5,
; D3DMULTISAMPLE_6_SAMPLES = 6,
; D3DMULTISAMPLE_7_SAMPLES = 7,
; D3DMULTISAMPLE_8_SAMPLES = 8,
; D3DMULTISAMPLE_9__SAMPLES = 9,
; D3DMULTISAMPLE_10_SAMPLES = 10,
; D3DMULTISAMPLE_11_SAMPLES = 11,
; D3DMULTISAMPLE_12_SAMPLES = 12,
; D3DMULTISAMPLE_13_SAMPLES = 13,
; D3DMULTISAMPLE_14_SAMPLES = 14,
; D3DMULTISAMPLE_15_SAMPLES = 15,
; D3DMULTISAMPLE_16_SAMPLES = 16,
;
; MultiSampleType = 0
; MultiSampleQuality = 0
; FullScreen_RefreshRateInHz = 0
; PresentationInterval = 0
Por otro lado en ocasiones resulta conveniente alterar las condiciones de la
reproducción. Para ello se invocan operaciones de Direct3D que no están presentes
en la traza o se alteran los parámetros de algunas operaciones. Existen diferentes
propósitos para hacer esto. Un caso típico sería reproducir utilizando una resolución
diferente en un equipo que no soporte la resolución requerida por la traza. Por
conveniencia, este comportamiento se controla a través de un fichero de
configuración.
99
Diseño de PixRunPlayer
Player
PixRunReader
1
Configuration
PixRunPlayer
Status
1
*
LockUpdater
SurfaceLockUpdater
VolumeLockUpdater
VertexBufferLockUpdater
IndexBufferLockUpdater
PixRunReader localiza las estructuras y campos en que están almacenadas las
operaciones dentro del fichero PIX. Las operaciones que ofrece son de bajo nivel,
actuando en ciertos aspectos como un analizador léxico.
PixRunPlayer es la clase central, generada automáticamente en gran parte. Esta clase
conoce la secuencia de parámetros que forman cada operación y utiliza el lector para
recuperarlos. Se comporta en este aspecto como un analizador sintáctico.
Status mantiene la asociación entre los componentes de Direct3D originales y
sustitutos y es responsable del tratamiento específico de las operaciones Lock/Unlock.
La actualización también se realiza de forma diferente según el tipo recurso, por este
motivo se incluyen diferentes tipos de LockUpdater, especializados en cado uno de
ellos.
Configuration se encarga de leer el fichero de configuración.
100
Pruebas de uso
El reproductor reproduce correctamente todas las trazas de referencia que utilizamos,
provenientes de videojuegos comerciales y demostraciones técnicas.
101
3.1.3 Subsistema de análisis de trazas
El análisis de trazas es útil para conocer cómo los videojuegos usan el API Direct3D.
Dado que los recursos de que disponemos no permiten realizar una implementación
completa de cada una de las características que ofrece Direct3D, el análisis nos sirve
para averiguar qué funcionalidad es necesaria para una determinada traza.
Para este fin añadí clases para el tratamiento de estadísticas al reproductor, algunas
de ellas disponibles ya en el departamento. La comunicación con estas clases se
realiza mediante código de usuario. Durante la reproducción se registran las
estadísticas en una serie de ficheros de texto, la mayoría de ellos consisten en datos
tabulados.
102
Informes mediante PERL
Para obtener informes a partir de los ficheros de estadísticas decidí utilizar PERL, un
lenguaje especializado en este tipo de procesamiento.
103
Una ampliación a este mecanismo que se realicé por motivos no directamente
relacionados con este proyecto fue la generación automatizada de gráficas a partir de
los ficheros de estadísticas utilizando la aplicación Microsoft Excel. Aproveché que
Excel utiliza el modelo COM, lo que permite programarlo desde PERL utilizando un
módulo de interoperabilidad.
104
Dependiente de la
plataforma nativa
Independiente de la
plataforma nativa
3.2 Implementación de Direct3D sobre el simulador ATTILA
La función del driver Direct3D, en adelante el driver, es realizar las operaciones de
este API utilizando al simulador como plataforma nativa. Direct3D es un API muy
extensa. Por tanto una cuestión importante es: ¿Hasta que punto se requiere una
implementación completa? Claramente escribir una implementación completa
sobrepasaría el tiempo y los recursos de que dispongo. Por este motivo se establecen
unos criterios generales que limitan el alcance de la implementación:
El criterio básico es que si una funcionalidad no se necesita aún, no se implementa.
Me refiero a las funcionalidades que requieren los videojuegos que se quieren simular.
Más concretamente, si una funcionalidad no aparece en las trazas, no se implementa.
Es la manera de concentrar el esfuerzo y guiar el desarrollo, sin invertir recursos en
previsión de lo que se pueda necesitar ni perderse entre la multitud de funcionalidades
disponibles en Direct3D.
No es necesario implementar funcionalidades de consulta. El driver Direct3D del
simulador ejecuta las operaciones almacenadas en una traza. En realidad no hay un
videojuego tomando decisiones al otro lado. El videojuego realizó consultas mientras
se capturaba la traza, el componente Direct3D de Windows ya le proporcionó las
respuestas.
105
No es necesario validar los parámetros de las operaciones ni controlar las numerosas
situaciones de error que podrían darse. El driver sólo ejecutará trazas correctas, es
decir, trazas originadas por una ejecución exitosa del videojuego, libres de errores.
Cuando llega el momento de implementar una funcionalidad necesito las
especificaciones: ¿Qué ha de hacer exactamente esta operación? Lo ideal sería
disponer de una especificación formal de las operaciones. Sin embargo Microsoft no
publica las especificaciones formales de su API Direct3D, precisamente para evitar
implementaciones de terceros6. Por fortuna Microsoft pone a disposición de los
desarrolladores una completísima documentación que uso como especificación. En
ocasiones me he encontrado con ambigüedades, viéndome obligado a programar
pequeñas aplicaciones 3D para conocer cómo reacciona el componente Direct3D de
Windows en casos muy concretos.
Otras cuestiones tienen que ver con aspectos no funcionales del driver:
Es necesario que el driver sea portable. El desarrollo lo hago en un PC corriente
equipado con el sistema operativo Windows, en este equipo la simulación de un frame
con unos pocos triángulos puede suponer un minuto. Un videojuego puede utilizar
hasta diez millones de triángulos por frame, para este tipo de simulación se utilizan los
supercomputadores del laboratorio de cálculo del departamento, que utilizan el
sistema operativo Linux.
No es necesario optimizar demasiado la implementación. El driver realiza una pequeña
parte del trabajo, el grueso corresponde al simulador: Una sola operación a nivel del
driver se corresponde con miles de ciclos en el simulador. Desde la perspectiva del
hardware el driver se utiliza sólo durante un ciclo, aplicando la Ley de Amdahl es de
los últimos elementos a optimizar.
Un requisito que he aportado es que el driver se pueda depurar de forma interactiva,
mi idea es que se pueda consultar con facilidad aspectos como el código fuente del
vertex shader actual mientras ejecuto las pruebas. Trabajando en programación
gráfica he observado y experimentado lo difícil de depurar que puede llegar a ser una
aplicación 3D atendiendo solo al frame final que produce. Un caso que me he
encontrado es programar una aplicación que maneja una cámara virtual a través de un
escenario y encontrar frames completamente negros. Tras dedicar mucho tiempo a
6
El caso contrario es el de OpenGL que es, de hecho, una
especificación y no una implementación concreta.
106
buscar errores en el código que dibuja el escenario encuentro que el error está en la
orientación de la cámara, que está mirando en una dirección en que no hay nada.
Está claro que el driver ha de tener parte de la funcionalidad del componente Direct3D
de Windows pero eso no quiere decir que su comportamiento interno sea el mismo. El
objetivo es simular la GPU, no simular cómo funciona la implementación oficial de
Direct3D, cuyos detalles son privados.
El driver ha de usar la GPU de forma lo más eficiente posible. Sino los resultados de
las simulaciones no representarían las condiciones en que, suponemos, trabaja una
GPU real.
3.2.1 Método de trabajo
El objetivo es que se puedan ejecutar trazas de videojuegos comerciales sobre el
driver. Para llegar a ese punto el driver pasará por muchas versiones intermedias, que
ejecutarán trazas de aplicaciones 3D cada vez más complejas.
Esta aproximación es parecida a la que se utiliza en metodologías como el Open
Unified Process, que me ha servido como guía. Se trata de producir una versión
funcional del software cada poco tiempo, en períodos que denominan iteraciones. Las
iteraciones se agrupan en fases, en cada fase se persigue un objetivo diferente.
En las iteraciones de incepción el énfasis es entender el objetivo que se persigue, por
lo que el producto son simplemente prototipos de usar y tirar.
Las iteraciones de elaboración tienen como objetivo encontrar la arquitectura de la
solución final, esto es, un software cuya estructura sea estable y suficiente para llegar
a la versión definitiva.
107
Superada esta fase se realizan las iteraciones de construcción, en las que la
arquitectura es estable y sobre ella el software se va ampliando con nuevas
capacidades hasta que es operativo.
Las últimas iteraciones son las de transición. En ellas las tareas se centran en los
destinatarios del software, por ejemplo ofreciéndoles cursos de formación o realizando
pruebas beta.
3.2.2 Arquitectura
D3DPlayer
D3DShaderTranslator
D3DProgrammablePipeline
D3DFixedFunctionEmulator
GPUDriver
La arquitectura del driver divide las responsabilidades entre varios subsistemas.
D3DProgrammablePipeline es el subsistema central y modela entidades propias del
pipeline programable. Actúa como interfaz del driver y recibe todas las operaciones del
API Direct3D, pero reparte algunas responsabilidades: Las propias del pipeline de
función fija recaen en D3DFixedFunctionEmulator y las de traducción de shaders en
D3DShaderTranslator. Es el único subsistema que se comunica con el driver del
simulador.
D3DShaderTranslator está dedicado a traducir los shaders de Direct3D en shaders
ejecutables por el simulador, es decir, shaders que utilicen el juego de instrucciones
nativo. Su papel es parecido al de un compilador.
D3DFixedFuncionEmulator está dedicado a generar shaders D3D que permitan que el
pipeline programable Direct3D emule un pipeline de función fija. Es un concepto que
108
puede costar de entender al principio, pero es fundamental y lo trataré con detalle más
adelante.
3.2.3 Subsistema D3DProgrammablePipeline
El subsistema D3DProgrammablePipeline modela el pipeline de referencia del driver.
Es un pipeline de tipo programable, ya que pienso que de esta forma es más sencillo
establecer una relación con los elementos del pipeline del simulador, que es
109
puramente programable. Actúa también como interfaz del driver, por lo que recibe
también operaciones del pipeline de función fija. Las operaciones del pipeline de
función fija las redirige a D3DFixedFunctionEmulator.
He modelado entidades que son propias del pipeline Direct3D descrito previamente,
por lo que también pueden deducirse sus responsabilidades dentro de las diferentes
etapas que implementan. Junto a éstas se encuentran entidades que son propias del
simulador. Para distinguirlas éstas últimas llevan el prefijo native, dando a entender
que representan la plataforma hardware nativa.
Comunicación con el simulador
La comunicación con el simulador se realiza a través de las entidades nativas, que
constituyen un pequeño subsistema orientado a objetos que envuelve al GPUDriver, la
capa inferior. El NativeDevice representa a la GPU del simulador, recibe los comandos
de alto nivel y da acceso al resto de entidades nativas.
Los registros y búferes nativos actúan como representantes de los registros y búferes
del simulador. Aprovechando que gran parte de la comunicación pasa por ellos se
pueden realizar optimizaciones de cara a un uso eficiente de la GPU. Una de las más
sencillas es evitar escrituras de un mismo valor en un registro, comparando el último
valor escrito con el actual. Otra optimización sencilla es limitar la parte del búfer que se
ha de escribir en la memoria del simulador. Adicionalmente ofrecen servicios de
depuración para poder consultar externamente el estado de los registros o la memoria.
110
Interfaz con el exterior
Algunas de las entidades modeladas se corresponden exactamente con uno de los
interfaces que presenta el API Direct3D, por lo que recibirán las llamadas de la traza
cuando las ejecute el reproductor. Esta interfaz se define a través de clases virtuales
puras C++ definidas en los ficheros de cabecera del API, de las que heredan las
entidades. Para tratar el requerimiento de portabilidad se han creado versiones
portables de estos ficheros. La versión para Linux de estos ficheros incluye la
declaración de todas las clases y estructuras propias del sistema operativo Windows
que utiliza Direct3D.
Implementación de streaming básico
Implementación inicial
Llamadas D3D
Intefaces no implementados
Interfaces implementados
VertexDeclaration 1
VertexDeclaration 1
VertexDeclaration 2
VertexBuffer 1
VertexBuffer 1
VertexBuffer 2
Buffer 1
Buffer 2
VertexBuffer 3
IndexBuffer 1
Buffer 3
Device 1
VertexShader 1
Interfaces no implementados
PixelShader 1
Buffer común
IndexBuffer 1
Texture 1
VertexShader 1
Surface 1
PixelShader 1
Buffer común
Device 1
Texture 1
Surface 1
Una dificultad de implementación es que toda la interfaz ha de estar presente aunque
sólo una parte esté implementada. Por este motivo parto como base de una
implementación inicial donde todos los interfaces tienen métodos válidos, aunque
estén vacíos o semivacíos.
Esta interfaz no operativa tiene cientos de métodos por lo que, al igual que con el
reproductor de trazas, recurro al uso de código generado automáticamente. Otro
problema similar al del reproductor es que hay que dar direcciones válidas para los
objetos interfaz. Este aspecto lo soluciono mediante la creación de una única instancia
para cada interfaz no implementada. Por otro lado creo un búfer común que sirva para
simular que los recursos tienen datos internamente.
111
Patrón singleton
Para los objetos de instancia única, que utilizo para la interfaz del driver y también
entre sistemas, utilizo el patrón de diseño singleton. Los objetos Singleton disponen de
un constructor privado y mantienen una sola instancia de sí mismos. Un método de
clase sirve para acceder a esta instancia única.
Relación entre el estado Direct3D y el estado del simulador.
Cada registro nativo representa un estado del pipeline del simulador, es decir, un
parámetro que determina cómo se realizará el rendering.
Los atributos de las
entidades propias de Direct3D se corresponden a estados del pipeline Direct3D.
La idea es que el pipeline del simulador se mantenga en un estado semánticamente
equivalente al del pipeline Direct3D. Para conseguirlo cada entidad propia de Direct3D
asigna valores a un grupo de registros nativos.
Modos de culling en Direct3D
D3DCULL_NONE
Modos de culling del simulador
CULL_NONE
D3DCULL_CW
CULL_FRONT
D3DCULL_CCW
CULL_BACK
Semántica
Dejar pasar todas las
primitivas.
Descartar las primitivas que
miren al observador.
Descartar las primitivas que
no miren al observador.
Hay casos sencillos en que la correspondencia entre el estado Direct3D y el del
simulador es directa. Por ejemplo los modos de culling disponibles en Direct3D tienen
un modo semánticamente equivalente en el simulador. En este caso mantener el
112
estado equivalente significa asignar el valor correspondiente en el registro que controla
el modo de culling en el simulador.
Los casos más complicados se dan cuando no se da esta correspondencia directa. Es
el caso de la etapa de streaming del simulador, que presenta unos streams más
sencillos que los de Direct3D.
En la figura el VertexBuffer contiene naturales para el color y reales para la posición.
En Direct3D se puede configurar un único stream para recuperar los vértices, ya que
los streams pueden leer valores de tipos distintos sin restricciones. Sin embargo los
streams del simulador no tienen esa capacidad. Por este motivo una vez se conoce la
distribución de los componentes del vértice es necesario relacionar varios streams
nativos, uno por tipo de datos, a un solo stream de Direct3D. Esto justifica la relación
uno a varios que existe entre la entidad Stream y NativeStream. De esta forma, en
nuestro ejemplo, un stream nativo se utiliza para color y otro para posición.
Recursos Direct3D y búferes del simulador
Mientras que externamente el API Direct3D expone los recursos en un formato
independiente del hardware cómodo para los programadores, internamente cada
hardware tiene su propia manera de almacenar los recursos. Por este motivo en el
driver las entidades que modelan recursos de Direct3D realizan una conversión al
formato que usa el simulador cuando los almacenan en el búfer nativo.
113
Surface
NativeBuffer
Formato Direct3D (Lineal)
Formato nativo (Ordenación de Morton)
Un ejemplo de estas conversiones de formato son las texturas. La entidad Surface,
cuando modela un nivel de mipmap de una textura, expone los téxeles siguiendo una
ordenación lineal mientras que ha de almacenarlas para el simulador en un orden
especial.
Esta ordenación especial, conocida como ordenación de Morton está diseñada para
favorecer la localidad espacial en las cachees internas del simulador. La razón de que
114
funcione es que el acceso a una textura se realiza para un área rectangular, en orden
de Morton es más probable encontrar los téxeles próximos en memoria.
Los shaders de Direct3D, aunque no se consideran recursos en un sentido estricto,
tienen la tarea de conversión más compleja: Convertir un shader Direct3D en un
shader nativo. Por este motivo estas clases delegan en el subsistema dedicado a la
traducción de shaders.
Depuración interactiva
En el diseño de la mayoría de entidades se incluyen operaciones específicas para la
depuración, que permiten examinar su estado.
115
Para consultar esta información de forma interactiva he implementado una sencilla
interfaz de usuario. Ésta permite controlar la reproducción de la traza, visualizar el
historial de llamadas Direct3D, visualizar los comandos recibidos por la GPU así como
el estado de los registros y la memoria, ofreciendo varias vistas para ésta última. Un
ejemplo notable es que podemos consultar el código ensamblador del vertex shader
que se está ejecutando. Mediante esta aplicación se detectan errores con facilidad y el
ahorro de tiempo de desarrollo es considerable.
116
3.2.4 Subsistema D3DShaderTranslation
shader D3D
shader traducido
vs_3_0
dcl_position v3
dcl_position o8
add r7, v3, c2
m4x4 r6, r7, c5
mov o8, r6
add t0, i0, c0
dp4 t1.x, t0, c1
dp4 t1.y, t0, c2
dp4 t1.z, t0, c3
dp4 t1.w, t0, c4
mov o0, t1
end
En Direct3D los shaders asumen un juego de instrucciones y una disponibilidad de
registros que no ha de corresponderse forzosamente con las capacidades de la
arquitectura en que se ejecute. La traducción de un shader de Direct3D consiste en
crear un shader equivalente pero que utilice el juego de instrucciones y registros7
nativo, en este caso los disponibles en las shader units del simulador.
Propiamente un traductor debería hacer comprobaciones de tipo y comprobaciones de
semántica. En otras palabras se verificaría que el programa está correctamente
escrito. Sin embargo los shaders que traduciremos pertenecen a aplicaciones que en
su diseño realizaron este tipo de comprobación y uno de nuestros criterios es
asumimos que sólo tratamos con trazas correctas.
7
Cuando hablo de shaders “registro” se refiere a los registros de las
shader units del simulador.
117
Proceso de traducción
El primer aspecto de la traducción son las instrucciones. Hablamos de soporte nativo
cuando tenemos una instrucción en el simulador con la misma funcionalidad que la
instrucción del shader Direct3D. Cuando no se dispone de dicha instrucción pero sí de
una serie de instrucciones nativas que realizan la misma función lo calificamos como
soporte emulado.
118
El segundo aspecto son los registros. Una forma sencilla de realizar la traducción es
asignar según vayan siendo necesarios registros nativos del simulador para que
jueguen el papel de los registros del shader Direct3D, en una correspondencia uno a
uno.
Declaración del shader traducido
Entrada
position
i0
Salida
position
o0
Constantes
c2
c5
c0
c1
Por último el traductor ha de adjuntar al código traducido la declaración de parte de las
asignaciones de registros que ha realizado. Esto es de utilidad para las etapas del
pipeline sepan dónde espera el driver que se le provean los datos de entrada y dónde
va a dejar los datos de salida.
119
En el ejemplo, el registro constante D3D c2 está asignado al registro constante del
simulador c0. Esto significa que si la aplicación invoca al API Direct3D para modificar
el valor de la constante D3D c2 la clase responsable de la asignación ha de asignar el
valor al registro del simulador c0. Esta clase, por tanto, ha de disponer de un
mecanismo para conocer la asignación de registros del shader traducido.
En el caso los registros de entrada basta con declarar qué componente del vértice
almacenan, pues esta es la información que necesita la etapa de streaming para saber
en qué registro tiene que asignar las diferentes componentes para cada vértice.
Tratamiento de versiones
Sin ser complicado en su implementación este subsistema no ha sido fácil de
especificar. Para entender mejor la tarea de especificación de este subsistema es
necesario entrar en detalle en aspectos detallados del modelo de shading de Direct3D.
Hay que tener en cuenta que aparte de la clasificación en dos tipos de shaders de
Direct3D, Vertex Shaders y Pixel Shaders, existen varias versiones para cada uno de
ellos y, peor aún, no hay restricciones en el uso de diferentes versiones en una misma
traza.
Cada nueva versión especifica las capacidades que tendrá el procesador de shaders
idealizado: Los cambios de versión mayor suponen cambios de arquitectura,
incorporar o eliminar instrucciones y tipos de registro. Los cambios de versión menor,
con alguna excepción, suponen una diferencia en la cantidad de registros disponibles.
Una de las tareas de especificación respecto a la versión mayor consiste en reducir a
un único modelo de shader unit los tres modelos existentes. Esto es, cada versión
mayor describe una maquina virtual diferente para ejecutar los shaders, pero cada una
de estas máquinas es más general que la anterior, por lo que el sistema de traducción
trabajará según el último modelo.
120
Estudio de requisitos
Observando por un lado el procesador de shaders que describe Direct3D y por otro los
procesadores de shaders del simulador vemos que éste último es mucho más sencillo.
Es cierto que, en su versión actual, no pueden implementarse todas las capacidades
requeridas por el modelo de shader de Direct3D, especialmente las referentes a
control de flujo.
Sin embargo recordemos que uno de los requerimientos es sólo implementar aquellas
características que se utilicen realmente. Por ello se realizó un estudio con los shaders
utilizados por las trazas de referencia.
Instrucción
Abs
Add
Break
break_gt
break_lt
break_ge
break_le
break_eq
break_ne
Crs
dcl_position
dcl_blendweight
dcl_blendindices
dcl_normal
dcl_psize
dcl_texcoord
dcl_tangent
dcl_binormal
dcl_tessfactor
dcl_positiont
dcl_color
dcl_fog
dcl_depth
dcl_sample
dcl_2d
dcl_cube
dcl_volume
Def
Defb
Defi
dp3
dp4
Dst
Else
Endif
Endloop
Endrep
Exp
Exp
Frc
Total occurrences
142
4816
0
0
0
0
0
0
0
0
672
174
246
444
0
618
374
374
0
0
260
0
0
0
1508
26
0
2272
0
0
10872
5470
0
0
0
0
0
0
0
398
Instruction
if
if_gt
if_lt
if_ge
if_le
if_eq
if_ne
if
label
lit
log
logp
loop
lrp
m3x2
m3x3
m3x4
m4x3
m4x4
mad
max
min
mov
mova
mul
nop
nrm
pow
rcp
rep
ret
rsq
setp_gt
setp_lt
setp_ge
setp_le
setp_eq
setp_ne
sge
sgn
sincos
slt
sub
texldl
vs
Total occurrences
0
0
0
0
0
0
0
0
0
0
0
0
0
118
0
0
0
0
0
5572
608
484
4946
224
4726
0
1088
78
2088
0
0
1452
0
0
0
0
0
0
46
0
56
72
0
0
0
Estos datos que corresponden a un videojuego comercial son representativos del resto
de trazas de referencia. Prácticamente ningún videojuego de referencia utiliza
instrucciones de control de flujo. La explicación a estos resultados proviene del método
de desarrollo que se utiliza para escribir los shaders.
121
ASM Shader
float4 Gold: register(c1);
float BlurScale: register(c0);
sampler renderTexture: register(s0);
const float2 offsets[12] = {
-0.326212, -0.405805,
-0.840144, -0.073580,
-0.695914, 0.457137,
-0.203345, 0.620716,
0.962340, -0.194983,
0.473434, -0.480026,
0.519456, 0.767022,
0.185461, -0.893124,
0.507431, 0.064425,
0.896420, 0.412458,
-0.321940, -0.932615,
-0.791559, -0.597705,
};
// Simple blur filter
float4 main(float2 texCoord: TEXCOORD0) : COLOR {
float4 sum = tex2D(renderTexture, texCoord);
for (int i = 0; i < 12; i++){
sum += tex2D(renderTexture, texCoord + BlurScale * offsets[i]);
}
return Gold * sum / 13;
}
ps_2_0
def c14, 0.0769230798, 0, 0, 0
dcl t0.xy
dcl_2d s0
mov r11.w, c0.x
mad r0.xy, r11.w, c2, t0
mad r9.xy, r11.w, c3, t0
mad r8.xy, r11.w, c4, t0
mad r7.xy, r11.w, c5, t0
mad r6.xy, r11.w, c6, t0
mad r5.xy, r11.w, c7, t0
mad r4.xy, r11.w, c8, t0
mad r3.xy, r11.w, c9, t0
mad r2.xy, r11.w, c10, t0
mad r1.xy, r11.w, c11, t0
texld r10, r0, s0
texld r0, t0, s0
texld r9, r9, s0
texld r8, r8, s0
texld r7, r7, s0
texld r6, r6, s0
texld r5, r5, s0
texld r4, r4, s0
texld r3, r3, s0
texld r2, r2, s0
texld r1, r1, s0
add r0, r10, r0
add r0, r9, r0
add r0, r8, r0
add r0, r7, r0
add r0, r6, r0
add r0, r5, r0
add r0, r4, r0
add r0, r3, r0
add r0, r2, r0
add r0, r1, r0
mad r2.xy, r11.w, c12, t0
mad r1.xy, r11.w, c13, t0
texld r2, r2, s0
texld r1, r1, s0
add r0, r0, r2
add r0, r1, r0
mul r0, r0, c1
mul r0, r0, c14.x
mov oC0, r0
Los desarrolladores de videojuegos utilizan el lenguaje de alto nivel HLSL, de forma
que gran parte del código ensamblador es resultado de este compilador. El compilador
de HLSL de Microsoft realiza una serie de optimizaciones que en muchas ocasiones
permiten evitar el uso de instrucciones de salto. Los shaders de estos últimos años
suelen ser cortos y sencillos, lo que favorece que se apliquen casi siempre las
optimizaciones. En el ejemplo el bucle se desenrolla en la serie de instrucciones
secuenciales equivalente, esto es posible porque el número de iteraciones se conoce
a priori.
Otra conclusión de este estudio es confirmar que las aplicaciones utilizan las versiones
con una cierta libertad, creando shaders de varias versiones durante una misma
ejecución. Incluso pueden utilizar simultáneamente, aunque no es una práctica
recomendada, un Vertex Shader y un Pixel Shader de versiones diferentes.
122
Para finalizar el estudio de requisitos asigné una categoría a cada instrucción utilizada
por los videojuegos como nativa, emulada o no soportada, según dispusiera de una
instrucción equivalente en el simulador, existiera una vía para emularla o esta
emulación fuera imposible o desconocida.
Análisis del bytecode
El código ensamblador de los shaders se recibe como un bytecode. Un primer
problema fue determinar su formato. Afortunadamente existe documentación acerca
del formato del bytecode.
123
Sin embargo el nivel de detalle requerido es mayor que el que ofrecía en la
documentación por lo que se realizó una tarea adicional de análisis hasta determinar
completamente el significado de cada bit. Fueron de especial ayuda las utilidades de
C++ para el acceso a bits, como los campos de bits.
El compilador de D3DX genera el bytecode del shader como una serie de tokens de 32
bits. El primero es siempre un token de versión, tras éste cada instrucción del shader
genera un token de instrucción y un número variable de tokens de parámetro, según
124
su sintaxis. Existen tipos adicionales de tokens, por ejemplo los relacionados con
almacenar comentarios.
Representación intermedia
El método elegido para la traducción es típico dentro de la construcción de
compiladores y traductores. Considerando los tokens como unidades léxicas se
construye un abstract syntax tree un tipo de árbol cuyos nodos reflejan la estructura
sintáctica de las instrucciones. Este árbol es nuestra representación intermedia (siglas
IR, en inglés). Una vez construida la IR se realizan recorridos sobre ella con distintos
propósitos, uno de ellos generar el shader traducido.
125
Diseño del traductor
Gran parte de las clases del traductor se corresponden a los subtipos de nodos del
árbol sintáctico. Probablemente en un futuro se vayan incorporando más subtipos, por
este motivo el patrón de diseño que uso es el patrón visitor, que minimiza el impacto
de agregar una nueva subclase de nodo. Este patrón establece una clase base para
los objetos que realizan los recorridos, en este caso es IRVisitor.
126
El objeto IRBuilder se encarga de crear la IR a partir del bytecode. Para ello se ayuda
de una clase auxiliar que almacena la sintaxis de las operaciones para cada versión de
vertex shader o pixel shader.
IRTranslator realiza el recorrido de la IR generando el código nativo. Internamente
mantiene tablas que almacenan los registros de que dispone para la traducción,
también según versión y tipo de shader, asignándolos según el proceso de traducción
que he descrito. Como he comentado existen varias versiones de pixel shader y vertex
shader, sin embargo el tratamiento es bastante homogéneo por lo que no hay
subclases para el tratamiento de versiones particulares por separado.
IRPrinter ayuda a la depuración del código recorriendo la IR e imprimiendo sus
contenidos en una cadena de texto.
TranslatedShader es el resultado del proceso de traducción y almacena el código
nativo junto con la declaración de registros utilizados. Así mismo incluye información
de depuración.
Pruebas de uso
De cara a la depuración he desarrollado una aplicación de prueba que permite editar
shaders Direct3D y examinar el proceso de traducción interactivamente. Al tratarse de
127
un bytecode los editores binarios también son útiles, sin embargo el ciclo de pruebas
se acorta al disponer en un único entorno del ensamblador de D3DX, el bytecode y el
traductor.
Los shaders que utilizo para realizar pruebas de este sistema los obtengo de los
videojuegos de referencia. El reproductor de trazas, a través de su extensión de
estadísticas, vuelca a ficheros de texto todos los shaders utilizados.
3.2.5 Subsistema FixedFunctionGeneration
El pipeline implementado por el simulador es un pipeline programable, como en la
mayoría de las GPU’s actuales. La necesidad de flexibilidad que requieren las
aplicaciones modernas hace que se opte este pipeline, donde uno puede programar la
GPU para obtener gran variedad de efectos. La fixed function, el proceso
predeterminado de transformación e iluminación de vértices y multi texturización de
píxeles/fragmentos, se sigue soportando por compatibilidad con aplicaciones antiguas.
128
Antiguamente la función fija se soportaba directamente por hardware. Esto significa
que había componente físicos especializados en cada tarea: la transformación de
modelo, de vista, de perspectiva, las texture stages, etc. Hoy en día los drivers la
soportan de forma emulada, programando la GPU con un vertex shader y un pixel
shader equivalentes que realicen por software los mismos cálculos que anteriormente
se hacían por hardware. Éste es el método que seguiremos para soportar la fixed
function de Direct3D.
La función fija se activa cuando no se provee el vertex shader o el pixel shader, en
estos casos el driver Direct3D utilizará su propio vertex shader o pixel shader. Una
implicación es que se puede utilizar la función fija en una etapa y en la otra la versión
programable.
129
Siguiendo el requisito de implementar sólo lo que se utilice nuestra implementación es
muy limitada, ya que en el momento actual las aplicaciones tan sólo utilizan la función
fija para elementos muy concretos y sencillos, por ejemplo menús y marcadores.
Los elementos que soportaremos serán parte de los expuestos en el apartado de
función fija: Transformación de modelo, vista y perspectiva, iluminación con los tres
tipos de luces mediante el modelo de blinn-phong y Texture Stages que implementen
las operaciones básicas.
Una de las primeras decisiones es si el shader equivalente será un shader Direct3D o
directamente un shader nativo. He optado por realizar un shader Direct3D por haber
documentación disponible sobre la emulación de función fija y también porque de este
modo es más fácil que interoperen las etapas de procesamiento de vértices y píxeles
cuando una trabaje en modo programable y la otra con función fija.
Respecto a la versión generaremos un vertex shader y un píxel shader del modelo 3,
por ser el más general y claro. Lógicamente, evitaremos utilizar instrucciones que no
se soporten en el simulador, ya que el shader pasará por el proceso de traducción
como cualquier otro shader D3D.
Generación de shaders de función fija
Recordemos que la función fija es parametrizable, es decir, pueden establecerse
valores diferentes manteniendo el mismo proceso, pensemos por ejemplo en un
cambio de matriz de proyección. El estado de función fija es el conjunto de valores de
todas aquellas variables internas que determinan el comportamiento del pipeline de
función fija. Estas variables no sólo comprenden parámetros, sino también otras que
130
determinan el procedimiento a aplicar, por ejemplo como parte del estado de función
fija encontramos variables que determinan si se aplicarán o no los cálculos de
iluminación.
tuple VertexIn
position: Vector4F,
normal: Vector4F
end;
tuple VertexOut
position: Vector4F,
diffuse: Color,
specular: Color
end;
worldViewIT: Matrix4x4;
viewIT: Matrix4x4;
worldViewProj: Matrix4x4;
material: Material;
lights: vector[1..8]: Light;
ambient: Color;
enabledLights: vector[1..8] : Bool;
funcion vertex_shader(input: VertexIn) : VertexOut
output: VertexOut;
P: Vector4F;
L: Vector4F;
N: Vector4F;
V: Vector4F;
H: Vector4F;
output.position := multiply(input.position, worlViewProj);
N := multiply(input.normal, worldViewIT);
P := multiply(input.position, worldView);
V := - normalize(P);
output.diffuse := ambient;
output.specular := Color(0);
for i := 1 to 8 do
if enabledLights[i] then
L := mul(matViewIT, - normalize( lights[i].direction );
output.color := output.color + dot(N, L) * material.diffuse;
H := normalize( L + V );
output.specular := output.specular +
pow(max(0, dot(H, N)), material.power) *
lights[i].specular;
end
end
return VertexOut;
end
Tomemos un ejemplo expresado en algún lenguaje de alto nivel que cubre una etapa
de iluminación simplificada para un algoritmo de función fija. En este algoritmo el
vertex shader recibe una estructura vértice con una serie de componentes y produce
un vértice como resultado con un color difuso y especular resultado de aplicar la
131
iluminación. El algoritmo realiza un bucle para las hasta ocho luces activas y aplica los
cálculos propios de una luz direccional en el modelo de Phong-Lambert modificado
que usa Direct3D.
Tomando este procedimiento como base la función del generador de función fija es
similar al problema de la traducción de shaders8 en el sentido de que hemos de
implementar un algoritmo expresado en un lenguaje que no podemos ejecutar
directamente, sino que hemos de adaptarlo a otro lenguaje más limitado. En este caso
disponemos de las instrucciones y registros disponibles los shaders D3D. En este
aspecto, el generador actúa como el compilador de HLSL.
La primera parte del algoritmo, los componentes del vértice de entrada y salida se
corresponden directamente con la declaración del vertex shader.
8
O incluso más al de la generación de PixRunPlayer en C++.
132
Algunas de las variables globales se corresponden a parámetros de la función fija que
expresaremos en el shader equivalente mediante constantes. El generador construye
una tabla que registra la asignación de las constantes a los parámetros que se han
necesitado. Los valores de estas constantes los establecerá el emulador de función
fija.
Para las variables locales del algoritmo se asignan registros temporales en el shader
equivalente.
Respecto a las instrucciones de cálculo se genera una serie de instrucciones
equivalente, los parámetros se determinan según las tablas de asignación de
variables.
133
Instrucciones
H := normalize( L + V );
Código con resultados intermedios
add r5, r1, r3
dp3 r6.w, r5, r5
rsq r7.w, r6.w
mul r4, r7.w, r5
En ocasiones el código equivalente necesita de variables temporales para los cálculos
intermedios. En este caso la suma de L y V se almacena en un registro temporal.
Estos registros temporales se reservan durante la generación de este segmento de
código, sin embargo su valor final no tiene utilidad por lo que se vuelven a estar
disponibles para la generación del resto de código.
Instrucciones y variables de control de flujo
enabledLights: vector[1..8] : bool
for i := 1 to 8 do
if enabledLights[i] then
...
end
end
Generación condicional de código
for(int i = 0; i < 8; i ++) {
if(ffstate.enabledLights[i]) {
generateCodeLight(i);
}
}
Respecto al control de flujo en el algoritmo no podemos expresarlo directamente en el
shader ya que, aunque el shader D3D modelo 3.0 soporta control de flujo el simulador
carece de este soporte. Lo que haremos será generar un shader diferente según el
estado de las variables globales que afecten al control de flujo, es decir, estas
variables pasarán a afectar al comportamiento del generador. El efecto en este caso
es que generamos un código secuencial en que el bucle se desenrolla y sólo se
genera código para las luces que estén activas.
134
Emulador de función fija
El subsistema de emulación de función fija tiene la responsabilidad de mantener el
pipeline programable actualizado, con un vertex y píxel shader y los valores de sus
constantes, de forma que el efecto sea el equivalente a que se estuviera calculando la
función fija. Sus clases principales son:
FFStatus almacena todas las variables que determinan el estado de función fija. Hay
dos instancias de este estado, una que representa el estado actual y otra que
representa el último estado que se ha aplicado al pipeline programable.
FFShaderGenerator es el responsable de generar un píxel o un vertex shader de
función fija para un estado dado. Implementa el procedimiento de generación descrito
y tiene como atributos las tablas de reserva de registros.
FFConstantsDeclaration indica al emulador qué constantes se han asociado al los
parámetros de función fija.
135
D3DProgrammablePipeline
set_render_state(LIGHTING, TRUE)
D3DFixedFunctionEmulator
set_render_state(LIGHTING, TRUE)
ToCommitFFStat
CommitedFFStat
set_lighting(TRUE)
commit()
FFShaderGenerator
generate_ff_vertex_shader(ToCommitFFState)
create_vertex_shader(ffshader)
ffshader
shader
set_vertex_shader(shader)
shader
Cuando se recibe una llamada que afecta al estado de función fija el pipeline
programable la delega sin más en el emulador de función fija. Éste recibe el cambio y
lo en el estado actual. A la hora de dibujar, o cuando se decida establecer la
configuración del pipeline del simulador, el pipeline programable lo notifica al emulador
de función fija. En este momento el emulador evalúa si los cambios del estado
requieren un nuevo vertex o píxel shader. En este caso el cambio lo justifica y se
genera el código para un nuevo vertex shader. Éste se crea como un vertex shader
más en el pipeline programable y se establece como actual.
136
D3DProgrammablePipeline
D3DFixedFunctionEmulator
set_light(LIGHT_0, params)
ToCommitFFStat
CommitedFFStat
set_light(0, params)
commit()
FFConstantsDeclaration
get_constants(LIGHT_0)
set_vs_constant(constants, params)
constants
En otras ocasiones el cambio de estado sólo afecta a los parámetros de la función fija
y no requiere generar un nuevo shader. En el ejemplo se cambian las propiedades de
la luz, pero no se requiere un código diferente para tratarla. En estos casos el
emulador detecta el cambio, pero se limita a cambiar el valor de las constantes
asociadas a la luz, utilizando la declaración de constantes para encontrarlas.
137
Pruebas interactivas
El shader que emula la función fija es un shader Direct3d corriente. Por este motivo es
posible programar una aplicación que lo utilice con el componente Direct3D de
Windows. Esta aplicación permite comparar el frame producido por la función fija real
respecto al resultado del shader que la emula.
138
4. Conclusión
El trabajo relacionado con la captura y reproducción de trazas se ha completado con
éxito. Tanto PIX, una vez determinado su formato de traza, como el software de
reproducción y análisis realizan los requerimientos que se esperaba de ellos. El
desarrollo ha sido relativamente rápido y actualmente su grado de estabilidad es lo
suficientemente grande como para utilizarse con trazas del orden de gigabytes de
videojuegos comerciales sin dificultades.
Se ha determinado el formato de fichero de traza usado por la aplicación Microsoft
PIX. Esto ha supuesto una tarea de ingeniería inversa de un formato binario cerrado.
El análisis ha llegado hasta un nivel suficiente como para producir el mismo resultado
que la propia aplicación.
Se ha construido un reproductor de trazas PIX capaz de procesar individualmente
centenares de operaciones Direct3D con sintaxis únicas. El diseño permite que para
cada operación se aplique un tratamiento general y un posible tratamiento
excepcional. Las operaciones se remiten al API Direct3D, pudiendo ejecutarse sin
alteraciones o modificando parámetros para ajustarse a las capacidades del equipo.
Así mismo se puede asociar código arbitrario a cualquier operación para el propósito
que se desee. El reproductor ha sido probado con trazas de videojuegos comerciales y
es portable entre Linux y Windows.
Como extensión al reproductor se ha añadido código destinado al análisis de la traza.
De esta forma se pueden conocer a priori diversas estadísticas de uso del API
Direct3D. Estas estadísticas se procesan con scripts PERL, extrayendo informes tanto
textuales como gráficos, mediante la interacción con la aplicación Microsoft Excel.
El driver Direct3D ha superado la primera fase de su desarrollo. En esta fase el énfasis
ha sido delimitar con precisión las especificaciones y estudiar las tecnologías
implicadas.
Se ha estudiado el API Direct3D hasta entender su funcionalidad con la profundidad
necesaria
para
realizar
una
implementación.
Esto
ha
supuesto
consultar
documentación especializada en del desarrollo de drivers gráficos adicionalmente a
adquirir los conocimientos que se esperan de un programador del API.
Por otro lado he estudiado el simulador ATTILA y el funcionamiento de las GPU’s en
general. Esto me ha permitido conocer un campo que no he tratado en las asignaturas
de gráficos durante la carrera y que está cobrando gran importancia en los últimos
años. Así mismo me he familiarizado con el trabajo que realizamos en la línea de
investigación de gráficos del Departamento de Arquitectura de Computadores.
Dada la extensión del API Direct3D se han delimitado las funcionalidades esenciales a
través de un extenso estudio de requisitos utilizando las herramientas de análisis
desarrolladas. Se ha estudiado el workload de videojuegos de última generación que
guían el desarrollo del driver Direct3D.
Dentro de esta fase se han construido varios prototipos que una vez validados con
trazas sencillas han servido para comprender el papel del driver Direct3D dentro del
proyecto ATTILA.
Actualmente se está completando la segunda fase. Esto significa que se ha diseñado
una arquitectura cercana a la definitiva y que el diseño de los diferentes subsistemas
es suficiente y ampliable. De esta forma en un futuro irá asumiendo nuevas
funcionalidades según sean necesarias.
Se ha desarrollado una versión programable del pipeline de Direct3D, con
representación para cada una de las etapas y funcionalidades esenciales. Este
pipeline se comunica directamente con el simulador ATTILA, de forma que éste refleja
su estado.
Contamos con un traductor capaz de producir shaders que utilizan el juego de
instrucciones nativo del simulador a partir de cualquiera de las diversas versiones de
shaders de Direct3D.
Contamos con un soporte básico para el pipeline de función fija, con un subsistema
dedicado a generar shaders Direct3D que imiten el comportamiento una vez aplicados
sobre el pipeline programable.
Los subsistemas disponen de interfaces gráficos de usuario para realizar pruebas
consultando interactivamente datos de depuración. La depuración se ha tenido en
cuenta desde el principio por lo que todos los componentes disponen de métodos para
su examen.
140
El driver es capaz de ejecutar trazas básicas en su configuración actual y se está
trabajando en dar soporte a trazas de nivel medio.
Análisis del tiempo empleado y coste económico del proyecto.
Tarea
Estudio del API Direct3D
Análisis del formato de traza PIX
Construcción del reproductor y la extensión
de análisis
Análisis de requerimientos del driver
Estudio del simulador ATTILA
Prototipos iniciales del driver
Estudio de shaders Direct3D
Desarrollo del traductor de shaders
Construcción del pipeline programable
Estudio de la función fija Direct3D
Desarrollo del emulador de función fija.
Documentación
Total
Analista
100
60
20
30
80
10
50
10
30
30
20
150
455
Desarrollador
Tester
50
30
20
10
30
10
50
60
20
20
40
20
250
110
Esta tabla resume la estimación del tiempo que emplearía un equipo formado por un
analista, un desarrollador y una persona encargada de realizar las pruebas. El orden
de las tareas es cronológico. Con esta estimación del tiempo podemos hacer una
estimación del coste económico.
Analista / Hora
50 €
Coste analista
22750 €
Desarrollador /Hora
40 €
Coste desarrollador
10000 €
Coste tester
2750 €
Tester /Hora
25 €
Coste total
35500 €
Estimando el salario así como costes indirectos propios de cada tarea podemos
estimar el coste total del proyecto.
Futuras líneas de trabajo
Una vez sentadas las bases con este proyecto el desarrollo del driver Direct3D
continuará en la línea de investigación de arquitecturas gráficas. Lógicamente no
podemos compararnos en medios con el equipo de desarrollo de Direct3D de
Microsoft, por lo que nunca completaremos todas las funcionalidades posibles. Sin
embargo confiamos en que soportaremos lo necesario para ejecutar videojuegos
comerciales Direct3D en el simulador. La propia API Direct3D está evolucionando y
recientemente ha sufrido una renovación importantísima en su última versión, de forma
141
que ya se están haciendo los primeros movimientos para que el simulador ATTILA no
se quede atrás.
142
Bibliografía
Centro de recursos oficial de DirectX,
http://msdn.microsoft.com/directx/
Website oficial del simulador ATTILA
http://attila.ac.upc.edu
Red de recursos para el desarrollo de videojuegos
http://www.gamedev.net
Frank D. Luna, Introduction to 3D Game Programming with DirectX 9.0, Wordware,
2003.
Victor Moya, ATTILA: A Cycle-Level Execution-Driven Simulator for Modern GPU
Architectures, ISPASS, 2006
Ron Foster, Real-Time Shader Programming, Morgan Kauffman, 2003.
Sebastien St-Laurent, The Complete HLSL Reference, Paradoxal Press, 2005
Kelly Dempski, Real Time Rendering Tricks and Techniques in DirectX, Thomson
Course Technology, 2002
Matthias Wloka, Where is that instruction? How to implement “missing” Vertex Shader
Instructions, NVidia Corporation, 2001
http://developer.nvidia.com/object/Implementation_Missing_Instructions.html
Jason Mitchell, Shader Models Tutorial GDC 2004, ATI Research, 2004
https://attila.ac.upc.edu/wiki/images/a/af/D3d9_shader_models_tutorial.pps
Craig Larman, Applying UML and Patterns – An introduction to object oriented analysis
and design and the RUP, Prentice Hall, 2002
Pat Brown, Vertex Program Specification, NVIDIA Corporation, 2004
http://www.opengl.org/registry/specs/NV/fragment_program.txt
143
Mark J. Kilgard, NVidia Vertex Program Specification, NVIDIA Corporation, 2004
http://www.opengl.org/registry/specs/NV/vertex_program.txt
Richard Thompson, The Direct3D Graphics Pipeline, no publicado
http://www.xmission.com/~legalize/book/index.html
Wikipedia contributors, 'Rendering equation', Wikipedia, The Free Encyclopedia, 2007
Wikipedia
contributors,
'Blinn–Phong
shading
model',
Encyclopedia, 2007
David Blythe, The Direct3D 10 system, SIGGRAPH 2006
144
Wikipedia,
The
Free