Mass Effect 2

Publicado el Martes 10 agosto 2010 en Videojuegos por Thund

Para ubicar al lector, debo poner por delante mi enorme pasión por la saga Mass Effect y su universo, de manera que pueda leer el texto desde la perspectiva adecuada. Quedé absolutamente cautivado por la riqueza de un mundo futurista totalmente reinventado. Los artífices de tal maravilla consiguieron encontrar un filón de creatividad en una mina ya agotada años atrás, lo cual me hizo darle un valor aún mayor. No importa cuál de sus ingredientes analice, cada uno es una perla en sí mismo. La forma de presentar la acción con un juego de cámaras sublime conseguía exprimir al máximo la narración. Por otro lado, nadie había dado jamás un toque tan sutil y personal a una interfaz de usuario, no sé realmente explicarlo, el menú principal resultaba relajante. La banda sonora, sin duda, fue el pilar básico de distinción, con esa música electrónica sosegada, de ondas cuadradas reverberantes, para momentos de poca o ninguna acción; y remezclada con una contundente orquestación de tinte militar para los acontecimientos bélicos o heróicos. Ni que decir tiene que ninguno de estos componentes tendría sentido sin un guión sobresaliente que los uniese, plagado de diálogos sustanciosos custodiados por un amplio abanico de decisiones que el jugador debía tomar. Recuerdo especialmente dos momentos: el primero, cuando visité la Ciudadela por primera vez, fui deslumbrado por una sencuencia de video envuelta de una sinfonía apabullante; el segundo es la recta final, las 2 grandes batallas, una espacial y otra, con Shepard a la cabeza, en el interior de la Ciudadela. Un broche épico.

Conocer que se esperaba una segunda parte supuso para mí una gran ilusión y un gran miedo, como suele ocurrir con las secuelas de obras maestras, difícilmente superables o aún incluso equiparables. A fin de que no se malinterprete mi análisis, adelantaré mi veredicto: no mejora ni alcanza a la primera parte, pero es…

La Atmósfera

Cuando uno adquiere un Mass Effect no espera ver tiros y explosiones, ni aporrear botones, ni romperse la cabeza una y otra vez contra puzzles irresolubles… eso está bien para juegos de esos de echar la tarde o pasar el rato. Lo que Mass Effect ofrece va mucho más allá del mero entretenimiento: es verdadero cine interactivo. La trama comienza siendo algo confusa e inesperada, me costó bastante acostumbrarme a los nuevos aliados y no echar de menos a los de la primera entrega aunque, según avanzaba, volvía a tener esa sensación de soltura, de pertenencia al mundo en el que se desarrolla la historia, volvía a ser absorbido por Mass Effect. Sin duda, en esta secuela el elemento que actúa como envolvente son los compañeros de Shepard, tanto los nuevos como los veteranos, a los que se ha dotado de un trasfondo insondable. En éste sentido sólo podría compararlo con un Final Fantasy, no se me ocurre ahora mismo otro juego que dé tanta vida a personajes secundarios. Cada uno tiene un pasado que conocemos mediante 2 misiones, principalmente, al reclutarlos y al hacerles un favor personal antes de que se embarquen en la empresa suicida final. Cada misión encierra oscuros secretos y giros de guión totalmente impredecibles, y nos permite descubrir un trozo más de la Vía Láctea que los de Bioware han creado para nosotros. En uno de estos quiebros argumentales podemos incluso ver morir a más de un amigo, de forma permanente (sí, ya, puedes cargar la partida), haciendo consciente al jugador de que cualquier cosa puede ocurrir. Y es que Mass Effect 2 es mucho más sombrío que su predecesor, tendremos que tratar con mercenarios sin escrúpulos y alienígenas sanguinarios; participaremos en ajustes de cuentas, presenciaremos asesinatos a sangre fría (con la posibilidad de evitar alguno que otro), intenciones obscenas y conductas deleznables, y todo ello, bajo la eterna duda de a quién estamos prestando servicio, si a una organización de nobles ideales o, como dicen los de la Alianza, a unos viles terroristas comandados por un ricachón insaciable.

Hay muchos más detalles argumentales que aumentan la credibilidad del universo del juego. Mencionaré uno en concreto (de lo que me acuerdo), que me hizo bastante gracia: en un bar hay sentados un turiano, un humano y un salariano, mientras una asari baila sobre la mesa para el salariano; el pobre, que no entiende nada, pregunta a sus colegas, con tono de fastidio y aburrimiento, por qué los humanos celebran así su despedida de soltero y qué pinta él allí si es salariano. A propósito, un 10 para las animaciones de las bailarinas asari. Prestando un poco de atención podemos darnos cuenta de cómo la personalidad de cada compañero de equipo afecta incluso a su forma de combatir (nada que ver la vehemencia de Jack con la técnica de Miranda). Me ha gustado ver otras cosas más sutiles, como la posibilidad de desmembrar mecas, o las reacciones de los enemigos cuando les disparas en una mano. Algo casi imperceptible y que me gustaría mencionar es la diferencia entre las despedidas de Shepard cuando seleccionas "Adiós" nada más empezar la conversación y cuando te despides después de charlar, las expresiones encajan en el contexto; para un ejemplo claro de esto, se puede ir a la Ciudadela y hablar con la recepcionista.

Aunque he dedicado una sección precisamente para todos aquellos aspectos que no me han gustado, iré adelantando alguno. Igual que hay puntos a favor de la inmersión, también los hay en contra; odio cuando terminas de hablar con algún personaje que, por ejemplo, te acaba de dar una misión, y de pronto te das la vuelta y ya no está; eso queda muy feo, podrían haberle puesto una contestación por defecto hasta que completes la misión o algo, pero no una abducción en toda regla. El otro caso que mencionaré está relacionado con las pruebas de virtud / rebeldía; resulta que, después de resolver los líos de familia de Miranda, ésta se volvió leal, hasta ahí todo normal. El caso es que, más tarde, tiene una acalorada discusión con Jack en el puente de mando, momento en el cual, por falta de virtud, tuve que elegir entre dar la razón a una o a otra. Bien, pues parece ser que arriesgar la vida para salvar a un familiar suyo tiene el mismo valor que hacerla quedar mal delante de una ex-convicta porque, desde ese momento, perdió su lealtad :S. Me parece absurdo.

Terminando este apartado, me referiré a las ya mencionadas cualidades artísticas de este juego. Perfectamente podría ponerme a ver jugar a otra persona mientras devoro un cubo de palomitas, como si fuera una película, más larga y más interesante que la mayoría. Uno no pierde detalle de lo que ocurre en cada escena. Incuso, más de una vez, la cámara te indica el camino a seguir o el evento a activar, sin desorientarte. Hay algunos planos en los que la composición de la imagen es impresionante, tómese de ejemplo cuando, en una de las explicaciones a Shepard, Jack habla a contraluz sobre un fondo naranja. Han cuidado la iluminación al máximo en cada área y cada toma (para mí, una de las cosas más difíciles de hacer bien). Admito que he flipado con esos detalles.

El Espacio y el Tiempo

El principal motivo por el que Mass Effect 2 no acaba en el momento en que "vences a los malos" es la vasta extensión del mundo de juego, la cantidad de planetas y ciudades, cada cual con su historia, la variedad de sociedades y especies… hay mucho por descubrir y sería una putada no poder hacerlo por haber acabado el guión principal. Hay muchísimas misiones secundarias (he de decir que varias de ellas son clones), algunas más originales y otras más típicas. Cada misión nos dará puntos de experiencia, dinero por parte de Cerberus y materiales que consigamos durante su realización; a veces, incluso se nos dará otro objetivo, formando cadenas. No obstante, dejando a un lado el número de horas jugadas, he tenido la sensación de que se me ha hecho muy corto. Quizá ha sido por conocer todo el camino a seguir desde el principio, es decir, habiendo avanzado muy poco en la trama, ya sabemos que tenemos que ir a reclutar a X personas, dónde están y a dónde hay que llevarlas (relé Omega 4). Es más, no hace falta ir a ningún otro sitio (vale sí, a un par) para acabar en el campo de batalla final. No me ha gustado ese planteamiento tan explícitamente lineal. Ojo, que no digo que Mass Effect 2 sea lineal, hay mucha libertad de acción y casi todos los acontecimientos pueden desencadenarse en cualquier orden. Personalmente, uno de los lugares que más me gusta visitar son los bares (como la vida misma); tienen un diseño muy orginal, ponen buena música y están llenos de intrigas…

El Control

Las opciones de movimiento de Shepard no han cambiado mucho. Vamos, intento recordar ahora mismo alguna diferencia con la primera entrega y no lo consigo. Sí han modificado la interfaz de usuario, tanto el menú principal como la interfaz de combate. En mi opinión, ambas han empeorado. El menú principal (cuando carga el juego) es un poco… feucho, simplón, oscuro. Los indicadores de combate, aunque ocupan menos, son también menos funcionales (me costaba bastante darme cuenta de qué aliado había perdido el escudo o estaba escaso de salud, por ejemplo). Ya no hay botones para dar órdenes de despliegue al equipo, sino que se utiliza el teclado directamente; a día de hoy, no sé cómo hacer para que un aliado ataque a un enemigo concreto con su arma de fuego que, por cierto, está limitada a 2 tipos en la mayoría de los casos (pistola y escopeta, rifle y pistola, etc.). Y ya que hablamos del combate, que alguien me explique la diferencia entre Mass Effect 2 y Gears Of War; sólo faltan los fusiles con sierra. No es que sea algo malo, pero ya podrían haber innovado un poquito al respecto. Lo siguiente sí que es sangrante… ¿a nadie más le ha pasado que el personaje se ha cubierto tras una caja cuando lo que quería era salir pitando de allí? Como si no hubiera más teclas que la puta barra de espacio. La usan para todo: correr, cubrirse, descubrirse, activar puertas y otros objetos, hablar… Son demasiadas funciones para una sola entrada. Me ha causado montones de problemas y varios cabreos, no costaba nada separar el correr del cubrirse, como mínimo. Otra crítica negativa y bien merecida se la tiene que llevar el dibujo de venas que enmarca la imagen cuando la salud escasea, ¡es horrible! Prefiero cien veces el de Clive Barker's Jericho.

Como males menores, citaré 2 fallos, uno de animación del modelo de Shepard y otro de diseño de escenarios. El primero consiste en que, si miramos hacia un punto cualquiera y de pronto echamos a correr, la cabeza de Shepard seguirá mirando en tal dirección aunque movamos la cámara. El segundo, ocurre cuando Shepard camina muy cerca de ciertas mesas (creo recordar que pasaba en algún laboratorio); el modelo pasa de caminar por el suelo a caminar sobre los muebles, siendo muy difícil hacerle bajar.

El Recuerdo

Se supone que han pasado 4 años desde la desaparición de Shepard e, irremediablemente, han cambiado muchas cosas. La fortuna ha favorecido más a unos excompañeros que a otros… mientras que Wrex se ha erigido como guía para su tribu, Garrus se ha convertido en una sombra errante que se toma la justicia por su mano. Sin duda, el paso de Shepard por sus vidas los marcó, como también afectó a muchos otros personajes con sus decisiones. Tal y como prometían, las acciones realizadas durante Mass Effect 1 tienen consecuencias en Mass Effect 2, aunque la relevancia de las mismas en la historia es nimia. Nos encontraremos con una gran cantidad de personajes de la primera parte, tanto ex-camaradas como tripulantes de la Normandía, así como antiguos enemigos o meros personajes secundarios. De la misma manera, las decisiones tomadas en este juego tendrán su eco en la tercera entrega. Iba a ponerme a comparar capítulos, pero sólo mencionaré un cambio, a mi parecer innecesario, que le hace perder un minipunto a Mass Effect 2: ¡echo de menos el contundente sonido del antiguo fusil de asalto!

La Contraparte

Y ahora la sección de las cagadas. Sí, voy a tirar algunas piedras más sobre esta gloriosa obra. Luego me autolesionaré jugando al WOW o alguna moñada de esas… no os preocupéis.

  • Cuando empieza una nueva partida y tiene lugar el ataque a la Normandía, los efectos de los rayos / lasers son horribles. Vaya primera impresión que me llevé.
  • Cuando reaniman a Shepard después de desaparecer, en la nave de Cerberus, tiene lugar un asalto y Shepard debe huir. Justo en la puerta de la primera sala podemos ver una ¿roca? flotando, totalmente inmóvil, en la puerta.
  • Continuamente, sobre todo en primeros planos, se nota un defecto en el sombreado de las caras de los modelos, como una especie de z-fighting.
  • En las instalaciones donde experimentaban con Jack no para de llover. Lo raro es que si nos ponemos a cubierto, a un metro de la parte descubierta, sigue lloviendo a través del techo :S .
  • Al final se pierden muchísimos momentos emotivos. En determinados finales mueren compañeros y da la sensación de que a Shepard se la suda. Da igual que sea Tali, Garrus o Legion, no hay problema en dejar a los caídos atrás. Me parece que ante un hecho tan trascendental para la historia no le han dado un trato mínimamente digno.
  • Por último, haciendo referencia de nuevo al final del juego, la explicación que dan acerca de la forma del segador… la forma de matarlo… el hecho de activar la bomba y que no corra el tiempo mientras lo matan… en fin. Una gran decepción, nada convincente.

Conclusión

Sé que me dejo cosas en el tintero (actos de virtud / rebeldía, desaparición del coche, minería…) pero no quiero acabar escribiendo un libro. Mass Effect 2 conserva la magia que hizo grande a su predecesor, dejando ese regusto de haber aprovechado el tiempo en los paladares más exigentes. Si bien tiene algunos fallos, éstos son de sobra compensados por las viejas virtudes y las nuevas sorpresas argumentales.

Esperando con ansias el cierre de la trilogía :D .

 

[Grooveshark]Banda sonora Mass Effect 2

[Grooveshark]Banda sonora Mass Effect 2: Club Dark Star

[Grooveshark]Banda sonora Mass Effect 2: Club Afterlife

 

[Youtube]Cachondeo freak: Legion bailando

[Youtube]Cachondeo freak: Creación de personaje Michael Jackson


Etiquetas: ,

Star Craft II – Released!

Publicado el Martes 27 julio 2010 en Noticias por Thund

Por fin, después de más de 10 años de espera, llega a nosotros, pobres mortales, la segunda entrega de una de las sagas épicas más aclamadas en el mundo de los RTS. He de decir que, aunque ilusionado (joder, llevo jugando al SC1 desde que salió y sigo adorando sus 256 colores), miro con gran excepticismo el trabajo de la Blizzard actual, no sólo porque SC1 dejara el listón por las nubes (quizá pegaría más decir "por el espacio exterior"), sino porque la onda expansiva del amado/odiado WOW parece haber salpicado al resto de títulos de la desarrolladora, contaminando sus particulares atmósferas. Guardaré casi todas mis valoraciones hasta que pruebe con mis propios sentidos esta nueva obra, pero no puedo evitar decir que veo a SC2 con muy malos ojos, de entrada, por razones relacionadas con el aspecto gráfico, la jugabilidad (al menos en la Beta) y el diseño de las unidades. Mi mayor esperanza es que el argumento sea tan absorbente como en la primera entrega y que las cinemáticas sean espectaculares.

Ojalá pueda catarlo pronto para salir de dudas.  En cuanto eso ocurra publicaré mis conclusiones, palabra de zerg…

Etiquetas: ,

Inicializando Direct3D – Parte 2/2

Publicado el Lunes 26 julio 2010 en DirectX por Thund

En la primera mitad de este post presenté las nuevas interfaces de Direct3D y DXGI necesarias para empezar a mostrar imágenes por pantalla. Ahora voy a mostrar cómo se utilizan, de forma básica.

Paso 0: Dependencias

Para este ejemplo, necesitaremos estas librerías de DirectX 11: d3d11.lib y dxgi.lib

Incluiremos en nuestro código el siguiente archivo de cabecera: d3d11.h

#include <d3d11.h>

Nótese que tal archivo de cabecera ya incluye a dxgi.h (además de d3d10_1.h, para quien le interese saberlo).

Paso 1: En busca del adaptador

Lo primero que haremos será seleccionar un dispositivo gráfico de entre los que tengamos instalados en nuestro ordenador. Como la mayoría somos mileuristas, lo normal es que sólo tengamos uno, así que elegiremos el primero que aparezca. La forma de hacerlo es usando una de las interfaces más importantes en la API de DXGI: IDXGIFactory. Ésta interfaz de nombre tan original se encarga de la creación de swap chains y adaptadores; para instanciarla se utiliza la función global CreateDXGIFactory. Un adaptador gráfico físico está representado por la interfaz IDXGIAdapter, y se obtiene mediante el método CreateSoftwareAdapter de IDXGIFactory en el caso de necesitar un adaptador por software, o a través de la enumeración de todos los adaptadores hardware disponibles, como es lo habitual, con el método EnumAdapter de la misma interfaz.

IDXGIFactory* pFactory = NULL;
CreateDXGIFactory(__uuidof(IDXGIFactory), reinterpret_cast<void**>(&pFactory));

IDXGIAdapter* pAdapter = NULL;
pFactory->EnumAdapters(0, &pAdapter);

Más de uno estará ahora mismo pensando qué cojones es esa función de nombre tan típico de Microsoft… os ahorraré la búsqueda. La función __uuidof se encarga de obtener un GUID (Globally Unique IDentifier) a partir de un tipo, una referencia, un puntero, una variable, un array o una plantilla especializada; en nuestro caso, necesitamos pasar como primer parámetro el GUID del tipo IDXGIFactory.

El método EnumAdapters rellena el puntero con la dirección del IDXGIAdapter que indiquemos con el índice del primer parámetro; aquí usamos el 0 (cero) puesto que sabemos que sólo tenemos un dispositivo. En caso de que pasemos un índice igual o superior al número de adaptadores, el método devolverá el código de error DXGI_ERROR_NOT_FOUND. Cuando necesitemos enumerar todos los adaptadores, nos valdremos de tal resultado como condición para iterar.

Éste no es un paso totalmente necesario, como podrá verse más abajo, pero en cierto modo es ilustrativo.

Paso 2: Swap chain a medida

Antes de montar la swap chain hay que definirla, esto es, determinar las dimensiones de los buffers, el formato de sus texels, la frecuencia del swapping (intercambio)… Exactamente lo mismo que hacíamos con DirectX 9, como ya mencioné, cuando usábamos la estructura D3DPRESENT_PARAMETERS, que ahora se llama DXGI_SWAP_CHAIN_DESC. Pasaré ahora a describir brevemente los atributos:

DXGI_SWAP_CHAIN_DESC swapChainDesc;

swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.Height = 600;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapChainDesc.BufferDesc.Width = 800;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 1;
swapChainDesc.OutputWindow = windowsHelper->GetWindowHandler();
swapChainDesc.Windowed = true;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
swapChainDesc.Flags = 0;

El formato (aributo BufferDesc.Format) está formado, normalmente, por 32 bits divididos en grupos de 8 con el orden RGBA. El alto y el ancho (BufferDesc.Height y BufferDesc.Width) se refieren a la resolución de los buffers, en píxeles. La tasa de refresco (BufferDesc.RefreshRate) es un número racional del cual especificamos el numerador y el denominador; como mi monitor es un TFT, he puesto 60 / 1 = 60 hz. Tenemos que elegir cómo se dibujará la imagen en el monitor cuando la resolución del mismo sea mayor que la del front buffer: si cambiará su tamaño para ajustarse (DXGI_MODE_SCALING_STRETCHED) o si aparecerá el recuadro centrado en la pantalla (DXGI_MODE_SCALING_CENTERED). Como me la suda lo que ocurra, he dejado el atributo BufferDesc.Scaling sin especificar (DXGI_MODE_SCALING_UNSPECIFIED). El siguiente atributo (BufferDesc.ScalineOrdering), he de reconocer que no conozco su utilidad exacta, aunque se puede deducir que tiene que ver con el orden con que el rasterizador dibuja las líneas de píxeles horizontales que forman la imagen. No vamos a usar antialiasing por lo que ajustamos el número de multisamples por píxel (SampleDesc.Count) a 1 y la calidad (SampleDesc.Quality) del mismo a 0 (cero). Las texturas que actúan como buffers serán usadas (BufferUsage) como objetivos para la renderización, por lo que elegimos el valor DXGI_USAGE_RENDER_TARGET_OUTPUT (la otra única opción posible es DXGI_USAGE_SHADER_INPUT). Cuando vi esto último quedé extrañado, estaba convencido de que habría que usar el valor DXGI_USAGE_BACK_BUFFER, pero da la impresión de que su uso es muy específico. Indicamos ahora la cantidad de buffers (BufferCount) que compondrán nuestra swap chain; aquí habría que darle una colleja a alguno de Microsoft, porque la documentación está mal. Claramente dice que en el número hay que incluir al front buffer, o sea, front buffer + back buffer = 2 buffers. Pues no, si sólo vamos a usar un back buffer, el resultado es 1; el front buffer no hay que contarlo para nada. Cada swap chain está vinculada desde su creación a una ventana y un dispositivo y deberá ser creada de nuevo si se quiere enlazar a otros; le pasamos el manejador de la única ventana que tenemos ahora mismo. Establecemos que queremos renderizar en modo ventana (Windowed), que es más fácil depurar. El efecto del intercambio (SwapEffect) es lo que ocurrirá con el back buffer cuando llamemos al método Present; nosotros no vamos a hacer nada en especial con él, por lo que lo descartamos (DXGI_SWAP_EFFECT_DISCARD). Por último, no queremos ajustar ninguna opción especial de la swap chain, así que ponemos los Flags a 0 (cero).

Paso 3: Enhorabuena, ha tenido usted trillizos

Una vez rellenado el bloque de configuración de la swap chain, aún quedan más parámetros que definir. Qué coñazo ¿no? Hay que elegir el tipo de driver, la capa que queremos activar, algunos flags… y el nivel de funcionalidad (feature level). ¿Qué carajo es eso de los feature levels? Pues bien, son un gran avance en la cohesión entre proveedores de hardware y los desarrolladores de APIs gráficas de bajo nivel, impulsado desde DirectX 10 (incluso antes, desde la aparición del Shader Model 3.0 en DirectX 9). Consiste en cargarse los flags de los Device Capabilities (a.k.a. CAPS) con los que había que pelearse en DirectX 9, y reemplazarlos por una especie de "contratos" que aseguran que un determinado dispositivo provee de un paquete de funcionalidades, según la versión de DirectX que las soporta. Así, cuando compramos una tarjeta gráfica con la etiqueta "DirectX 11" en la caja, quiere decir que implementa el conjunto mínimo establecido de las características que ofrece DirectX 11. Aún tendremos que hacer ciertas comprobaciones sobre la capacidad del hardware mediante ciertos métodos de la interfaz IDXGIOutput, como veremos en posts posteriores, pero ya no hay que ponerse a enmascarar ristras de bits para saber si la tarjeta soporta T&L, ciertos modos de presentación, algunas operaciones del stencil buffer, etc. Hay que decir que la selección explícita del nivel de funcionalidad es algo nuevo en DirectX 11 que permite orientar la implementación de nuestro programa a un determinado conjunto de hardware. Cuando especificamos que queremos crear un manejador de dispositivo para DirectX 9, lo que ocurrirá es que las instrucciones que demos con interfaces de DirectX 11 se traducirán a las que lanzaría DirectX 9 al dispositivo (lo cual conlleva una penalización de rendimiento que no tendríamos si usáramos la API de DirectX 9 directamente). Ésto que parece tan bonito también tiene su aquél, ya que hay algunas características que no pueden ser traducidas, pues no existen en el hardware, como el soporte de arrays de texturas o el uso de DirectCompute. De modo que habrá que seguir haciendo comprobaciones durante el desarrollo para evitar que ciertas cosas les exploten en la cara a los usuarios de tarjetas de video obsoletas. Podéis encontrar una tabla con todas las incompatibilidades en la documentación.

ID3D11Device* pDevice = NULL;
IDXGISwapChain* pSwapChain = NULL;
ID3D11DeviceContext* pImmediateContext = NULL;

D3D_FEATURE_LEVEL featureLevels[1] = { D3D_FEATURE_LEVEL_10_1 };

D3D_FEATURE_LEVEL* pFeatureLevelsOut = NULL;

D3D11CreateDeviceAndSwapChain(	pAdapter,
								D3D_DRIVER_TYPE_UNKNOWN,
								NULL,
								D3D11_CREATE_DEVICE_DEBUG,
								featureLevels,
								1,
								D3D11_SDK_VERSION,
								&swapChainDesc,
								&pSwapChain,
								&pDevice,
								pFeatureLevelsOut,
								&pImmediateContext );

El código precedente comienza declarando e inicializando punteros a las 3 interfaces de los elementos principales que necesitamos para renderizar: el dispositivo, la swap chain y el contexto del dispositivo que, como puede deducirse del nombre de la variable, será un contexto inmediato. A continuación, rellenamos un array de niveles de funcionalidad con un único elemento, que hace referencia a la versión 10.1 de DirectX. Es un nivel que funciona en mi máquina, puede cambiarse por cualquier otro o incluso usar un puntero nulo en lugar de un array, lo que significa que se usará la versión más avanzada compatible con el sistema. La siguiente declaración es un puntero que será asignado internamente, en el momento de la creación de los elementos, a una lista de niveles de funcionalidad soportados por el dispositivo, es decir, actuará como parámetro de salida. Por fin, usamos la función global D3D11CreateDeviceAndSwapChain para reunirlo todo y crear los 3 objetos ya mencionados. Podría crear el manejador del dispositivo y el contexto por un lado (función D3D11CreateDevice), y la swap chain por otro (método CreateSwapChain de la interfaz IDXGIFactory), pero ya que se nos facilita un todo-en-uno ahorraremos líneas.

Voy a comentar cada uno de los parámetros de la función refiriéndome al número de línea en el que aparecen, por facilidad. Como primer parámetro, en la línea #09 utilizo el puntero a la interfaz del adaptador que seleccionamos en el paso 1; podría pasar un puntero nulo, en cuyo caso se usaría el adaptador por defecto. El siguiente parámetro, en la línea #10, es el tipo de driver que será creado (hardware, WARP, de referencia…); tiene como restricción que, si hemos pasado un puntero no nulo como adaptador, el tipo de driver debe ser "desconocido" (D3D_DRIVER_TYPE_UNKNOWN); si el puntero era nulo, forzosamente deberemos elegir entre un driver de tipo hardware (D3D_DRIVER_TYPE_HARDWARE) o software (D3D_DRIVER_TYPE_SOFTWARE). En la línea #11 hay que proporcionar un manejador de la DLL del rasterizador por software, si es que vamos a usar un driver de tipo software; si el driver era de tipo distinto de software, obligatoriamente hay que usar un puntero nulo. Quiero disponer de toda la información posible durante el proceso de desarrollo y depuración, por lo que, mediante el flag de la línea #12, activo la capa de depuración de DirectX. Seguidamente, establezco los niveles de funcionalidad (#13) y el número de elementos de tal array (#14); si pasara un puntero nulo, se usaría internamente un array con todos los niveles (el número de elementos tendría que ser cero). Tras indicar la versión del SDK (#15), suministramos la estructura con las características de la swap chain (#16) que rellenamos en el paso 2 y, a continuación, todos los parámetros de salida. Si todo sale bien, la función devolverá el código S_OK.

Paso 4: Y se hizo la luz

Gracias al mecanismo que acabamos de construir, tenemos total control sobre el adaptador. Sin embargo, para poder enviar las órdenes de renderizado, hay que crear un canal que nos comunique con los recursos, esto es, una vista. Para este ejemplo, lo único que vamos a hacer es pintar la pantalla de un color, para lo cual crearemos una vista del back buffer.

ID3D11Texture2D* pBackBuffer = NULL;
pSwapChain->GetBuffer(0, __uuidof(*pBackBuffer), reinterpret_cast<void**>(&pBackBuffer));

ID3D11RenderTargetView* pRenderTargetView = NULL;
pDevice->CreateRenderTargetView( dynamic_cast<ID3D11Resource*>(pBackBuffer), NULL, &pRenderTargetView );

Como puede verse, primero extraemos la textura que hace de back buffer de nuestra swap chain y, a continuación, la usamos para generar una vista a la misma. Lo único que puedo remarcar aquí es que el índice del back buffer es el 0 (cero) debido a que es el único que hay y el efecto de intercambio de la swap chain no es secuencial (DXGI_SWAP_EFFECT_SEQUENTIAL).

Ya sólo queda enviar los comandos para que el dispositivo rellene el back buffer del color que queramos y lo presente al monitor en forma de ventana. Para ello hay que organizar el código del hilo perteneciente a la ventana, de manera que en el bucle de mensajes de Windows se llame a los procesos de renderización. Describir el código es trivial, pero por no perder las costumbres… Lo primero que hago es llamar al método ClearRenderTargetView del contexto inmediato, pasándole la vista del back buffer y el color, y luego, simplemente, ordeno a la swap chain que ponga el back buffer en el lugar del front buffer, enviándolo a la pantalla.

Editado 04/08/2010 – Ver Antiguo

En el caso que nos ocupa, se renderizará a toda máquina, sin límite de FPS (Frames Per Second), siempre que no llegue el mensaje WM_QUIT. Se usa PeekMessage en lugar de GetMessage debido a que ésta última accede a la cola de mensajes del hilo y bloquea la ejecución hasta que capture uno, mientras que PeekMessage vuelve inmediatamente, haya mensajes en la cola o no.

// Main message loop:
while ( msg.message != WM_QUIT )
{
    if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
    {
        // Render
        pImmediateContext->ClearRenderTargetView(pRenderTargetView, backColor);
        pSwapChain->Present(0, 0);
    }
}

Quisiera explicar por encima los parámetros del método Present y sus consecuencias. El primero se encarga de desactivar la sincronización vertical (Vsync) mediante el valor 0 (cero), o de activarla, especificando el número de refrescos del monitor que la swap chain esperará antes de presentar el back buffer. El segundo parámetro es un flag que tiene que ver con 2 cosas: una, el modo de presentar los buffers de la swap chain (renderizar un frame por buffer o sólo el primer buffer), y otra, el test de presentación. Ya os lo estaréis oliendo, el flag DXGI_PRESENT_TEST es el sustituto (en parte) del antiguo método TestCooperativeLevel. Cuando pasamos éste valor al método Present no se enviará nada a la pantalla y obtendremos un código de resultado que, según el caso, puede informarnos de que nuestra ventana está siendo totalmente eclipsada por otra (DXGI_STATUS_OCCLUDED), que se ha cambiado la resolución (DXGI_STATUS_MODE_CHANGED), o si ha ocurrido algo extraño y hay que volver a crear el manejador del dispositivo (DXGI_ERROR_DEVICE_RESET), por ejemplo. Una gran novedad respecto a éste último tipo de error es que ya no hay que partirse el pecho volviendo a crear todos los recursos dependientes de un dispositivo para cambiar de modo ventana a pantalla completa, ya no existen OnResetDevice y OnLostDevice, sino que ahora basta con llamar al método SetFullscreenState de la interfaz de la swap chain. Imagino, y remarco que esto es una suposición porque a día de hoy no lo he probado, que si se diera el error DXGI_ERROR_DEVICE_RESET, sí que habría que montar todo el chiringuito de nuevo. Me permito hacer especulaciones, aunque no me gusten, porque lo comprobaremos en breve. Hay que anotar en la cabeza que nuestra aplicación siempre debe tener un modo de espera, es decir, un estado en el que no se realice ningún tipo de tarea relacionada con la renderización, de manera que no se consuman recursos tontamente cuando, por ejemplo, nuestra ventana esté siendo ocultada por otra.

No nos olvidemos de que todos los objetos COM que hemos creado deben ser liberados mediante el correspondiente método Release de cada uno. Un detalle importante es que nunca debe liberarse la swap chain mientras el modo de visualización sea pantalla completa, pues eso causará un error de los chungos. Hay que cambiar a modo ventana antes de destruirla. Ahí va una espectacular captura del resultado del ejemplo:

[Imagen ventana verde]

Sumario de Portabilidad

DirectX 9 DirectX 11 Comentario
Direct3DCreate9 D3D11CreateDeviceAndSwapChain  
IDirect3DDevice9 ID3D11Device  
IDirect3D9   No hay análogo.
IDirect3DSwapChain9 IDXGISwapChain  
IDirect3DTexture9 ID3D11Texture2D  
IDirect3DCubeTexture9 ID3D11Texture3D  
TestCooperativeLevel DXGI_PRESENT_TEST Es un flag del método IDXGISwapChain::Present.
D3DPRESENT_PARAMETERS DXGI_SWAP_CHAIN_DESC  
IDirect3DResource9 ID3D11Resource  
IDirect3DSwapChain9::GetBackBuffer IDXGISwapChain::GetBuffer  
IDirect3DDevice9::Present IDXGISwapChain::Present  
IDirect3DDevice9::Clear ID3D11DeviceContext::ClearRenderTargetView  

 

Descargas

Podéis descargar el código de ejemplo desde aquí:

EndlessGameDX11Init.zip (23 Kb)
 

Etiquetas: , ,

Inicializando Direct3D – Parte 1/2

Publicado el Martes 20 julio 2010 en DirectX por Thund

¡Por fin, con todos ustedes… el más esperado, el desconocido, el novedoso… DirectX 11! Estoy de acuerdo, vaya chorrada de presentación, pero tenía que hacerlo, lo juro. El presente post y muchos de los sucesivos estarán centrados en Direct3D y DXGI, con pinceladas de DirectWrite o Direct2D, y en ellos trataré de explicar cómo están estructurados, cómo interactúan y cómo se utilizan para distintos propósitos, intercalando teoría y práctica para hacerlo más llevadero y comprensible. Para no complicarlo mucho, el código presentado será el básico e indispensable (en el próximo post ya le añadiré comprobaciones para hacerlo más robusto). Empecemos pues con un corto recorrido alrededor de los principales componentes de Direct3D y los cambios que ésta API ha sufrido respecto a DirectX 9. Como muchos recordaréis, lo primero que se hacía, a parte de la creación de la ventana, era llamar a la función Direct3DCreate9 a la que pasábamos la versión del SDK para obtener un puntero a una interfaz IDirect3D9. Esta última se encargaba de crear el manejador del dispositivo gráfico (IDirect3DDevice9) y la swap chain, y nos surtía de una serie de funciones para comprobar las posibilidades que el hardware ofrecía, de cara a dicha creación. Pues bien, este diseño se ha ido al carajo. Ya no existe una interfaz de entrada a la librería y toda la responsabilidad de comprobación de hardware ha sido desplazada a la API de DXGI. La swap chain ha dejado de estar medio oculta al usuario (tenías que ir específicamente a buscarla para darte cuenta de que estaba ahí) y pasa a primer plano bajo control de interfaces IDXGISwapChain. Como se puede comprobar por el prefijo, también ha pasado a dominio de DXGI. La interfaz del dispositivo (ahora ID3D11Device) se genera gracias a la función global D3D11CreateDevice y ya no se ocupa del envío de instrucciones de dibujo, o al menos no directamente, sino que ha sido dividida en 2 partes: el dispositivo y el contexto del dispositivo (ID3D11DeviceContext); mientras que el primero representa el adaptador en sí y actúa como fábrica de ciertos objetos, el segundo maneja las órdenes de renderizado. Como colofón, hace falta crear, al menos, un render target que apunte al back buffer de nuestra swap chain, y que usaremos para indicarle al device context dónde tiene que dibujar. Sí, Microsoft ha complicado un pelín las cosas. Vamos a verlo en mayor detalle, que así parece muy enrevesado.

 

DirectX Graphics Infrastructure

Nació con DirectX 10 para, según Microsoft, agrupar aquellas porciones de la API gráfica encargadas de realizar operaciones a bajo nivel en una librería independiente. Tales tareas son, por ejemplo, la enumeración de los dispositivos gráficos, el envío de los frames renderizados a los dispositivos de salida (presentación), manejo de transiciones de pantalla completa, etc. Ciertamente, DXGI ha cambiado poco desde que se lanzó, lo único que han hecho ha sido añadir funciones y unas cuantas chorradas* más con la versión 1.1, que aquí ni veremos ni usaremos. Identificaremos fácilmente estas extensiones, ya que los chicos de Redmond han tomado la genial decisión de diseño de añadir un "1" como sufijo, y deberemos tener cuidado de no mezclarla con la versión 1.0, esto es, no se deben usar interfaces o funciones de una y a continuación invocar las de la otra, de lo contrario vendrá Steve Ballmer y os gritará "Developers!" en un oído. Sí bueno, es la típica historia de terror que se cuenta a los niños cuando no se tiene ni puta idea de qué es lo que pasa, como el coco o el efecto invernadero, y con eso hay que conformarse :) . A continuación el conocido esquema sobre DXGI, por cortesía (o no) de Microsoft:

[Diagrama DXGI]

ID3D11Device

Realiza las mismas funciones que su antecesor, menos las relativas al renderizado, como Clear, Draw, Begin, End… Básicamente, es una fábrica de objetos o recursos asociados al dispositivo. Pueden generarse recursos compartidos entre varios dispositivos, pero sólo los podrá usar aquél que los creó. Echando un vistazo a su interfaz os daréis cuenta de que ya no están disponibles los métodos para la creación de vertex buffers e index buffers; de hecho, sí que están, lo que ocurre es que se han unificado los buffers en una única interfaz llamada ID3D11Buffer, que encapsula un objeto cuya funcionalidad puede configurarse en el momento de su creación, de la que se encarga el método CreateBuffer, para que se comporte como index, vertex o constant buffer. Un dispositivo puede dividirse internamente en 2 capas: la capa principal o núcleo (Core Layer), que existe siempre por defecto, y la capa de depuración (Debug Layer), que puede activarse mediante el flag D3D11_CREATE_DEVICE_DEBUG, cuando creamos el manejador del dispositivo. Dependiendo de cómo tengamos configurado el panel de control de DirectX, la capa de depuración nos mostrará una serie de mensajes muy últiles en la ventana de Output de Visual Studio. Cabe destacar que la interfaz ID3D11Device es free-threaded (o thread-safe, como más os guste), lo que significa que se puede llamar a sus métodos desde cualquier hilo simultáneamente (el número de concurrencias dependerá del driver). Al igual que en anteriores versiones, podemos crear un dispositivo de referencia que emulará mediante software todas las características de DirectX. Adicionalmente, existe otro tipo de dispositivo llamado WARP (Windows Advanced Rasterization Platform), que también implementa mediante software (con mucho mejor rendimiento, según tengo entendido) las capacidades de Direct3D 11. Para ver las limitaciones de ambos emuladores echad un ojo a la documentación.

ID3D11DeviceContext

La interfaz del dispositivo antes mencionada ha sido dividida en 2 partes, de manera que la responsabilidad del renderizado ahora recae en los llamados "contextos del dispositivo". Los contextos son creados por el propio dispositivo y pueden ser de 2 tipos: inmediatos y postergados (es mi traducción de deferred). El concepto de "contexto" se refiere al modo de tratar las órdenes de renderizado y cambios de estado que se envían al dispositivo. Mientras que en un contexto inmediato los comandos son transmitidos directamente al dispositivo, en un contexto postergado se genera una cola de comandos que serán ejecutados más tarde utilizando el contexto inmediato. Esto permite utilizar una arquitectura multihilo para renderizar en paralelo aunque, al final, acabe realizándose en serie. Sólo puede existir un contexto de tipo inmediato por dispositivo, e infinitos contextos postergados. Para saber a qué tipo pertenece un determinado contexto, podemos usar el método GetType. Al contrario que la interfaz ID3D11Device, ID3D11DeviceContext no es thread-safe, sólo puede usarse cada instancia desde un único hilo simultáneamente.

IDXGISwapChain

Anteriormente, para acceder a la cadena de intercambio de buffers de salida (swap chain en adelante), teníamos que utilizar el método GetSwapChain de la interfaz IDirect3DDevice9, obteniendo así un puntero a la interfaz IDirect3DSwapChain. Su existencia pasaba inadvertida, aunque la utilizáramos sin saberlo al suministrar la estructura D3DPRESENT_PARAMETERS (cuyo equivalente ahora es DXGI_SWAP_CHAIN_DESC) o al llamar al método Present. Con el diseño actual, la swap chain pasa a un discreto primer plano y no sólo eso sino que, como mencioné en varias ocasiones, las interfaces relacionadas con la misma han sido movidas a la librería de DXGI.

ID3D11RenderTargetView

Antes de explicar el significado de ésta interfaz en concreto, creo que es necesario dar a conocer algunos detalles acerca de la estructura de DirectX 11. Existen los llamados "recursos", que no son más que zonas de memoria con datos que son accesibles desde la pipeline de Direct3D. Tales recursos, encapsulados en derivadas de ID3D11Resource, son generados por la interfaz del dispositivo y normalmente contienen datos "en bruto", en forma de buffers (ID3D11Buffer), o texels, formando texturas (ID3D11Texture1D, ID3D11Texture2D ó ID3D11Texture3D); hay varios tipos de recurso más o, mejor dicho, subtipos, que han sido añadidos con la versión 5.0 de los shaders, tales como StructuredBuffer, que no describiré hasta que lo necesitemos para algo en el futuro. La cuestión es que, para manipular tales datos de memoria, necesitamos una "vista", que actúa como canal de acceso; en función del tipo de recurso, tendremos que elegir un tipo de vista u otro, esto es, una u otra interfaz derivada de ID3D11View. ¿Que por qué me pongo a hablar de esto aquí en vez de crear una sección de introducción para los recursos, otra para las vistas, etc.? Pues porque para fusilar y traducir la documentación de referencia ya tenéis la MSDN y el infalible traductor online de Microsoft. Volviendo a lo que nos ocupa, uno de los tipos de vista que nos permiten dibujar sobre una textura (nuestro back buffer es precisamente eso, una textura 2D) es el representado por ID3D11RenderTargetView. Para crearla hay que usar el método CreateRenderTargetView del dispositivo, al cual hay que suministrar la textura (nuestro back buffer, por ejemplo, obtenido mediante el método GetBuffer de la interfaz de la swap chain) y, opcionalmente, una estructura de configuración. Las vistas son objetos pasivos, tan sólo tienen un método o propiedad que nos devuelve sus descripciones. Veremos más tipos de vista en breve, cuando hable del depth y el stencil buffer.

Sé que el principio del post prometía sangre, pero dado que soy un cenutrio incapaz de condensar tanta información para hacer posts agradables y ligeros, me veo obligado a partirlo en 2. No tardaré mucho en publicar la segunda parte, que es la que contiene el ejemplo de código, que nadie se impaciente.

 

*Para los puristas, cuando digo "chorradas" quiero decir "funcionalidad de uso poco frecuente o de propósito muy específico", aunque suponga una puta revolución tecnológica.

Etiquetas: ,

« Página anteriorPágina siguiente »