Copyright

Tu video puede incluir el siguiente contenido protegido por derechos de autor:

  • "Balkanel-Dotakni Me usnama", grabación de sonido administrado por:
    Rebeat Digital GmbH
¿Qué significa esto?

Tu video sigue estando disponible en todo el mundo. En algunos casos, los anuncios podrán aparecer junto a tu video. Ten en cuenta que el estado del video puede cambiar si se modifican las políticas seleccionadas por los propietarios de contenido. 

FAQ de Rebeat:

1. Why do I receive a message like this from YouTube
Dear xxxx, 
Your video […] may have content that is owned or licensed by Rebeat Digital GmbH. No action is required on your part; however, if you are interested in learning how this affects your video, please visit the Content ID Matches section of your account for more information. 
Sincerely, - The YouTube Team 


Your video apparently contains audio content for which REBEAT Digital owns the digital distribution rights. These rights authorize REBEAT to sell the music on shops like iTunes, Amazon and others. Because REBEAT Digital is a contractual partner of YouTube, the music is also distributed to their service. YouTube scans their video catalogue for the music and sends a message out to those who uploaded a video which includes audio content to which REBEAT Digital owns the distribution rights. 

Un grupo músical Balkanel subió su música a Rebeat, una empresa administradora de contenido digital que tiene un convenio con YouTube. YT busca a aquellos videos que utilicen audio cuyo copyright esté gestionado por/subido en Rebeat y les agrega publicidad a los mismos (creo que links para comprar la canción). Lo curioso es que mi video no tiene esa música sino Kecharitomene de Loreena Mckennitt.

Por lo visto (o, mejor dicho, escuchado), la canción Dotakni Me Usnama de Balkanel es en realidad un robo a mano armada de la de Loreena (Kecharitomene y Dotakni Me Usnama). Kecharitomene es instrumental y lo que hizo Balkanel es agregarle una letra encima.

Dado que enviar un mail pidiendo que retiren el reclamo es engorroso (y que si vamos al caso yo no tengo los derechos sobre la canción de Loreena tampoco :P), resubo el video sin música.

Es una verdadera lástima tener que resetear el contador de visitas del video (ya había pasado las 100!).

Nuevo video:

SlimDX y DirectX 11 - Cache de shaders compilados

Dado que la solución estaba tardando casi 5 minutos en compilar por culpa de unos compute shaders que estoy usando para comparar performance, armé una pequeña clase que se encarga de cachear los shaders. Si el código fuente de un shader no fue modificado respecto del utilizado para compilarlo en la ejecución anterior, no se compila y se utiliza el bytecode almacenado.

El código fuente de la solución entera no lo subo porque tengo que ordenar varias partes, pero dejo todo el código de la clase ShaderCache al final del post.

Leer más

Charla muy interesante sobre realidad virtual y el dispositivo Oculus Rift (http://www.kickstarter.com/projects/1523379957/oculus-rift-step-into-the-game).

Carmack being Carmack:

I think lower level means something different to you than it does to us.

People forget this: when you look back to your fond memories of Doom or Quake, on there it’s all smoothed out and nicely antialiased in your memory. But if you go and boot the old computer back up and run the game there, you have this, somebody called them, bathroom tile sized pixels.

Demo de un prototipo viejo (E3 2012): http://www.youtube.com/watch?v=NYa8kirsUfg

Discografía para descargar en http://www.lukhash.com

SlimDX y DirectX 11 - Tessellation (Hull Shader y Domain Shader)

aTessellation

Código fuente y ejecutable de la solución

Tessellation

Subdivisión de un triángulo, un quad o una isolínea en triángulos (, puntos o líneas) más pequeños para generar un modelo de mayor cantidad de primitivas (msdn). Esto permite reducir el ancho de banda necesario al usar como input al pipeline un modelo low-poly, realizar cálculos por patch en lugar de por primitiva final (siempre y cuando se pueda interpolar después), adaptar la calidad del modelo en función de distancia a cámara o screen space, aplicar eficientemente técnicas como displacement mapping, etc.

Para implementarlo por hardware, DirectX 11 incorpora tres etapas más al pipeline: Hull Shader, Tessellation Stage y Domain Shader.

Pipeline

Hull Shader

Este shader recibe como inputs los puntos de control y genera como output puntos de control que definen un patch (superficie) a subdividir junto con parámetros constantes por cada patch relativos a la forma en que se realiza la subdivisión.

Patch Constant Function

Para definir los parámetros de tessellation o cualquier otro output constante por patch se utiliza una función que se ejecuta por patch y tiene como input todos los puntos de control originales. La información que posee cada punto y la cantidad de puntos que conforman al patch se define en el header de la función, más precisamente en el input del tipo InputPatch.

struct HS_OUTPATCH
{
	float Edges[3] : SV_TessFactor;
	float Inside : SV_InsideTessFactor;
};

HS_OUTPATCH HSPatchFunction(InputPatch<HS_INPOSTXR, 3> PatchPoints, uint PatchID : SV_PrimitiveID)
{
	HS_OUTPATCH Out;
	Out.Edges[0] = Out.Edges[1] = Out.Edges[2] = Out.Inside = tessFactor;

	return Out;
}

Mínimamente, la salida debe definir SV_TessFactor y SV_InsideTessFactor, y el índice de cada array asociado dependerá del dominio (tipo de patch). Por ejemplo, un triángulo requiere de 3 SV_TessFactor (uno por lado) y un SV_InsideTessFactor, mientras que un quad de 4 y 2 respectivamente.

Estos factores determinan la cantidad de subdivisiones a realizar. Cabe mencionar que si algún factor es igual a 0 el patch no pasa a las etapas siguientes, por lo que se puede realizar algún tipo de culling en esta etapa.

Backface culling

Si el patch/triángulo no se encuentra mirando a la cámara, se setea el factor de tessellation en 0 para que sea descartado en la próxima etapa y evitar subdividir algo que de todas formas no se vería finalmente. Básicamente, si el producto escalar entre la normal de la superficie y el vector superficie->cámara es mayor a 0 (en realidad un epsilon muy pequeño), se descarta. Google lo explica mejor.

HS_OUTPATCH HSPatchFunctionBFCulling(InputPatch<HS_INPOSTXR, 3> PatchPoints, uint PatchID : SV_PrimitiveID)
{
	HS_OUTPATCH Out;

	float3 patchNormal = normalize(cross(PatchPoints[1].Pos - PatchPoints[0].Pos, PatchPoints[2].Pos - PatchPoints[0].Pos));

	if (BackFaceCull(dot(patchNormal, normalize(PatchPoints[0].Pos - viewPos)), dot(patchNormal, normalize(PatchPoints[1].Pos - viewPos)), dot(patchNormal, normalize(PatchPoints[2].Pos - viewPos)), 0.1)) Out.Edges[0] = Out.Edges[1] = Out.Edges[2] = Out.Inside = 0;
	else Out.Edges[0] = Out.Edges[1] = Out.Edges[2] = Out.Inside = tessFactor;

	return Out;
}

Comparación entre Backface culling ON (izquierda) y OFF (derecha) seteando el rasterizer en CullNone:

Backface

Shader

EL hull shader propiamente dicho genera cada punto de control de salida a partir de la información relativa a todos los puntos de control de input y un ID del punto de control a generar (interno al patch). Además, se deben configurar parámetros requeridos por la Tessellation Stage que indican cómo realizar la subdivision.

[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("HSPatchFunction")]
HS_OUTPOINTPOSTXR HSTessellation(InputPatch<HS_INPOSTXR, 3> PatchPoints, uint PatchID : SV_PrimitiveID, uint PointID : SV_OutputControlPointID)
{
	HS_OUTPOINTPOSTXR Out;
	Out.Pos = PatchPoints[PointID].Pos;
	Out.Txr = PatchPoints[PointID].Txr;

	return Out;
}

domain indica el tipo de patch de entrada, partitioning el algoritmo a utilizar para subdividir, outputtopology el tipo de primitiva de salida, outputcontrolpoints la cantidad de control points de salida, y patchconstantfunc el nombre de la función que genera la información por patch.

Tessellator Stage

Esta etapa se encarga de realizar la subdivisión de los patchs. Es fixed, por lo que no se programa nada y sólo se setean los parámetros en el HS (msdn).

Domain Shader

A nivel funcionalidad se podría pensar como el Vertex Shader si las primitivas generadas hubiesen sido directamente mandadas como input del pipeline y no hubiese tessellation. En el DS se calcula la posición final de cada vértice (output point) generado por la TS utilizando como entrada la ubicación de los puntos de control (generada por el HS), la información constante per patch (generada por la patch constant function del HS), y las coordenadas del vértice generada dentro del patch.

Estas coordenadas se obtienen a partir de la system value SV_DomainLocation y su tipo de dato depende del tipo de patch. Para un quad, se utiliza un float2 que indica las coordenadas internas UV (símil coordenadas de textura), para un triángulo (el caso de ejemplo que hice) un float3 que representa las coordenadas baricéntricas, y para una línea un float2 que no sé que representa.

[domain("tri")]
PS_INPOSTXR DSTessellation (HS_OUTPATCH PatchData, float3 BariPos : SV_DomainLocation, const OutputPatch<HS_OUTPOINTPOSTXR, 3> PatchPoints)
{
	PS_INPOSTXR Out;
	//Interpolación baricéntrica en función de los 3 control points (vértices del triángulo/patch original)
	float4 pos = float4(PatchPoints[0].Pos * BariPos.x + PatchPoints[1].Pos * BariPos.y + PatchPoints[2].Pos * BariPos.z, 1.0f);
	Out.Pos = mul(pos, matViewProjection);
	Out.Txr = PatchPoints[0].Txr * BariPos.x + PatchPoints[1].Txr * BariPos.y + PatchPoints[2].Txr * BariPos.z;
	return Out;
}

Video

Coming up

Lo próximo que haga sea probablemente displacement mapping. Parece interesante y relativamente simple.

SlimDX y DirectX 11 - AMD GPUPerfAPI

GPA

Código fuente y ejecutable de la solución (para que GPA funcione, la aplicación debe ejecutarse como administrador)

Advertencia

Colgué un par de veces la PC mientras probaba algunos counters (muchos a la vez) con Fraps al mismo tiempo :P

GPUPerfAPI

Biblioteca de AMD para analizar performance en placas de video AMD/ATI. Overview y user guide (bastante completa).

GPAWrapper.cs

Importé todas las funciones que figuran en GPUPerfAPI.h y agregué una función para leer los datos de una sesión y formatearlos según su tipo y usage.

namespace Framework
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void GPA_Logging_Delegate(GPA_Logging_Type logType, string mensaje);

    ///
    /// Status enumerations
    ///
    public enum GPA_Status
    {
        GPA_STATUS_OK = 0,
        GPA_STATUS_ERROR_NULL_POINTER,
        GPA_STATUS_ERROR_COUNTERS_NOT_OPEN,
        GPA_STATUS_ERROR_COUNTERS_ALREADY_OPEN,
        GPA_STATUS_ERROR_INDEX_OUT_OF_RANGE,
        GPA_STATUS_ERROR_NOT_FOUND,
        GPA_STATUS_ERROR_ALREADY_ENABLED,
        GPA_STATUS_ERROR_NO_COUNTERS_ENABLED,
        GPA_STATUS_ERROR_NOT_ENABLED,
        GPA_STATUS_ERROR_SAMPLING_NOT_STARTED,
        GPA_STATUS_ERROR_SAMPLING_ALREADY_STARTED,
        GPA_STATUS_ERROR_SAMPLING_NOT_ENDED,
        GPA_STATUS_ERROR_NOT_ENOUGH_PASSES,
        GPA_STATUS_ERROR_PASS_NOT_ENDED,
        GPA_STATUS_ERROR_PASS_NOT_STARTED,
        GPA_STATUS_ERROR_PASS_ALREADY_STARTED,
        GPA_STATUS_ERROR_SAMPLE_NOT_STARTED,
        GPA_STATUS_ERROR_SAMPLE_ALREADY_STARTED,
        GPA_STATUS_ERROR_SAMPLE_NOT_ENDED,
        GPA_STATUS_ERROR_CANNOT_CHANGE_COUNTERS_WHEN_SAMPLING,
        GPA_STATUS_ERROR_SESSION_NOT_FOUND,
        GPA_STATUS_ERROR_SAMPLE_NOT_FOUND,
        GPA_STATUS_ERROR_SAMPLE_NOT_FOUND_IN_ALL_PASSES,
        GPA_STATUS_ERROR_COUNTER_NOT_OF_SPECIFIED_TYPE,
        GPA_STATUS_ERROR_READING_COUNTER_RESULT,
        GPA_STATUS_ERROR_VARIABLE_NUMBER_OF_SAMPLES_IN_PASSES,
        GPA_STATUS_ERROR_FAILED,
        GPA_STATUS_ERROR_HARDWARE_NOT_SUPPORTED,
    }

    ///
    /// Value type definitions
    ///
    public enum GPA_Type
    {
        GPA_TYPE_FLOAT32,             // Result will be a 32-bit float
        GPA_TYPE_FLOAT64,             // Result will be a 64-bit float
        GPA_TYPE_UINT32,              // Result will be a 32-bit unsigned int
        GPA_TYPE_UINT64,              // Result will be a 64-bit unsigned int
        GPA_TYPE_INT32,               // Result will be a 32-bit int
        GPA_TYPE_INT64,               // Result will be a 64-bit int
        GPA_TYPE__LAST                // Marker indicating last element
    }

    ///
    /// Result usage type definitions
    ///
    public enum GPA_Usage_Type
    {
        GPA_USAGE_TYPE_RATIO,         // Result is a ratio of two different values or types
        GPA_USAGE_TYPE_PERCENTAGE,    // Result is a percentage, typically within [0,100] range, but may be higher for certain counters
        GPA_USAGE_TYPE_CYCLES,        // Result is in clock cycles
        GPA_USAGE_TYPE_MILLISECONDS,  // Result is in milliseconds
        GPA_USAGE_TYPE_BYTES,         // Result is in bytes
        GPA_USAGE_TYPE_ITEMS,         // Result is a count of items or objects (ie, vertices, triangles, threads, pixels, texels, etc)
        GPA_USAGE_TYPE_KILOBYTES,     // Result is in kilobytes
        GPA_USAGE_TYPE__LAST          // Marker indicating last element
    }

    ///
    /// Logging type definitions
    ///
    public enum GPA_Logging_Type
    {
        GPA_LOGGING_NONE = 0,
        GPA_LOGGING_ERROR = 1,
        GPA_LOGGING_MESSAGE = 2,
        GPA_LOGGING_ERROR_AND_MESSAGE = 3,
        GPA_LOGGING_TRACE = 4,
        GPA_LOGGING_ERROR_AND_TRACE = 5,
        GPA_LOGGING_MESSAGE_AND_TRACE = 6,
        GPA_LOGGING_ERROR_MESSAGE_AND_TRACE = 7,
        GPA_LOGGING_ALL = 0xFF
    }

    ///
    /// See http://developer.amd.com/tools/GPUPerfAPI/assets/GPUPerfAPI-UserGuide.pdf
    ///
    public static class GPAWrapper
    {
        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_Initialize();

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_Destroy();

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_OpenContext(IntPtr pointerToDevice);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_CloseContext();

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_SelectContext(IntPtr pointerToDevice);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetNumCounters(out uint counterIndicesList);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetCounterName(uint counterIndex, out string counterName);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetCounterIndex(string counterName, out uint counterIndex);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetCounterDescription(uint counterIndex, out string counterDescription);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetCounterDataType(uint counterIndex, out GPA_Type counterDataType);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetDataTypeAsStr(GPA_Type counterDataType, out string counterDataTypeStr);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetCounterUsageType(uint counterIndex, out GPA_Usage_Type counterUsageType);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetUsageTypeAsStr(GPA_Usage_Type counterUsageType, out string counterUsageTypeStr);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_EnableCounter(uint counterIndex);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_EnableCounterStr(string counterName);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_EnableAllCounters();

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetEnabledCount(out uint enabledCount);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetEnabledIndex(uint enabledIndex, out uint counterIndex);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_IsCounterEnabled(uint counterIndex);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_DisableCounter(uint counterIndex);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_DisableCounterStr(string counterName);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_DisableAllCounters();

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetPassCount(out uint passCount);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_BeginSession(out uint sessionID);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_EndSession();

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_BeginPass();

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_EndPass();

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_BeginSample(uint sampleID);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_EndSample();

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_IsSessionReady(out bool isReady, uint sessionID);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_IsSampleReady(out bool isReady, uint sessionID, uint sampleID);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetSampleUInt32(uint sessionID, uint sampleID, uint counterIndex, out uint sampledValue);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetSampleUInt64(uint sessionID, uint sampleID, uint counterIndex, out ulong sampledValue);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetSampleFloat32(uint sessionID, uint sampleID, uint counterIndex, out float sampledValue);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetSampleFloat64(uint sessionID, uint sampleID, uint counterIndex, out double sampledValue);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_GetSampleCount(uint sessionID, out uint samplesCount);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern string GPA_GetStatusAsStr(GPA_Status gpaStatus);

        [DllImport("GPUPerfAPIDX11.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern GPA_Status GPA_RegisterLoggingCallback(GPA_Logging_Type gpaLoggingType, GPA_Logging_Delegate loggingFunction);

        ///
        /// Queries the last samples taken in a session
        ///
        ///Session ID
        ///Array of counter indices
        ///Array with the data type associated with each counter index of counters
        ///Array with the usage type associated with each counter index of counters
        /// An array of string formatted values
        public static string[] SamplesToString(uint sessionID, uint[] counters, GPA_Type[] types, GPA_Usage_Type[] usages)
        {
            string[] outputValues = new string[counters.Length];

            uint sampleIndex;
            GPAWrapper.GPA_GetSampleCount(sessionID, out sampleIndex);
            sampleIndex--;

            for (int j = 0; j < counters.Length; j++)
            {
                switch (types[j])
                {
                    case GPA_Type.GPA_TYPE_FLOAT32:
                        float floatValue;
                        GPAWrapper.GPA_GetSampleFloat32(sessionID, sampleIndex, counters[j], out floatValue);
                        outputValues[j] = string.Format("{0:0.###}", floatValue);
                        break;
                    case GPA_Type.GPA_TYPE_FLOAT64:
                        double doubleValue;
                        GPAWrapper.GPA_GetSampleFloat64(sessionID, sampleIndex, counters[j], out doubleValue);
                        outputValues[j] = string.Format("{0:0.###}", doubleValue);
                        break;
                    case GPA_Type.GPA_TYPE_UINT32:
                        uint uintValue;
                        GPAWrapper.GPA_GetSampleUInt32(sessionID, sampleIndex, counters[j], out uintValue);
                        outputValues[j] = uintValue.ToString();
                        break;
                    case GPA_Type.GPA_TYPE_UINT64:
                        ulong uint64Value;
                        GPAWrapper.GPA_GetSampleUInt64(sessionID, sampleIndex, counters[j], out uint64Value);
                        outputValues[j] = uint64Value.ToString();
                        break;
                }

                switch (usages[j])
                {
                    case GPA_Usage_Type.GPA_USAGE_TYPE_BYTES:
                        outputValues[j] += " bytes";
                        break;
                    case GPA_Usage_Type.GPA_USAGE_TYPE_CYCLES:
                        outputValues[j] += " cycles";
                        break;
                    case GPA_Usage_Type.GPA_USAGE_TYPE_ITEMS:
                        outputValues[j] += " items";
                        break;
                    case GPA_Usage_Type.GPA_USAGE_TYPE_KILOBYTES:
                        outputValues[j] += " Kbytes";
                        break;
                    case GPA_Usage_Type.GPA_USAGE_TYPE_MILLISECONDS:
                        outputValues[j] += " ms";
                        break;
                    case GPA_Usage_Type.GPA_USAGE_TYPE_PERCENTAGE:
                        outputValues[j] += " %";
                        break;
                    case GPA_Usage_Type.GPA_USAGE_TYPE_RATIO:
                        outputValues[j] += " ratio";
                        break;
                }
            }

            return outputValues;
        }
    }
}

GPA brinda la posibilidad de setear una callback function a la cual la biblioteca invoca con los distintos errores y mensajes de logueo. Para mi ejemplo, hice una función que graba en GPALog.txt sólo los errores y advertencias.

Cómo usarlo

Habilitar GPA en el checkbox Enable GPUPerfAPI y agregar los counters que uno quiera medir en el datagrid que está debajo.

Para otros proyectos, sólo hace falta copiar GPAWrapper.cs y llamar a la biblioteca como uno quiera (por ejemplo, leyendo la data después de terminada la aplicación para no disminuir la performance).

Limitaciones

Desde el punto de vista de funcionalidad importada, ninguna.

Por otro lado, algunos counters requieren de más de una pasada de exactamente el mismo frame para poder obtener sus resultados, por lo que si el usuario selecciona un conjunto de counters que requiere de más de una pasada para samplear los datos, se desactiva GPA y se muestra una advertencia.

SlimDX y DirectX 11 - Compute Shader, Raw Buffer y Unordered Access View

Código fuente y ejecutable de la solución

Colisión Box-Line segment

Antes que nada, fe de erratas del post anterior. Arreglé el testeo de colisión para que rebote como es debido :P El código de la función intersects es una adaptación de este.

Aclaración

Todo lo que escribo termina siendo un mini y propenso a errores resumen de lo que figura en msdn, por lo que para entender realmente las cosas recomiendo leer de ahí.
 
Compute Shader

El CS permite aprovechar el paralelismo y la capacidad de procesamiento de datos de la GPU para aplicaciones de todo tipo (GPGPU). De hecho, no figura como una etapa del graphics pipeline de DX 11 y lo único que comparte con los demás shaders es su sintaxis en HLSL.

Pipeline

Threads

La ejecución de un CS dispara la creación de threads agrupados en grupos (duplica) con un sector de memoria compartida. Cada thread tiene asociado un ID de 3 dimensiones que lo identifica dentro de su grupo, mientras que cada grupo tiene a su vez un ID de 3 dimensiones que lo distingue de los demás. El límite de threads por grupo es de 1024 (msdn).
Para definir sobre qué dato trabajará cada thread, probablemente se necesite tener acceso desde el código al ID del thread y/o grupo, por lo que existen System-Value Semantics que brindan esa información:

SV_GroupID: offset del grupo respecto del total dispatcheado, por dimensión.
SV_GroupThreadID: offset del thread respecto de su grupo, por dimensión.
SV_GroupIndex: offset del thread respecto de su grupo, unidimensional (de 0 a (numthreadsX * numthreadsY * numThreadsZ) – 1).
SV_DispatchThreadID: offset del thread respecto del total dispatcheado, por dimensión (SV_GroupID * dimensionesDeCadaGrupo + SV_GroupThreadID).

La siguiente imagen sacada de, como no podía ser de otra manera, msdn lo explica bastante bien:

Threads

Sintaxis

La sintaxis es simple y lo único notorio es cómo se define la cantidad de threads por grupo:

[numthreads(32, 32, 1)]
void BasicComputeShader(uint3 threadID : SV_DispatchThreadID)
{
    Cálculos...
}

Con numthreads estamos definiendo que se crearán 32 * 32 * 1 =  1024 threads y cómo se repartirán dentro del grupo según las dimensiones (SV_GroupThreadID será (0…31, 0…31, 0)).

Ejecución

contexto.Dispatch(16, 16, 1);

El método Context.Dispatch(cantGruposX, cantGruposY, cantGruposZ) invoca al CS que, considerando los valores de numthreads de arriba, se ejecutaría para 16 * 16 * 1 grupos y 32 * 32 * 1 threads por grupo dando un total de 262144 threads.

Raw Buffer o Byte Address Buffer

Un recurso visto como Raw Buffer no tiene un tipo de dato definido sino que es simplemente un array de bytes, por lo que se almacena y lee su información de a 4 bytes (uint) especificando el offset en bytes del uint a leer/escribir.

Creación

Es interesante mencionar que lo que define el acceso como Raw es la vista creada sobre el buffer, por lo que el mismo puede ser accedido como Raw en función de una vista y luego como cualquier otro tipo (excepto Structured) por otra vista.
Es decir, al momento de la creación del buffer uno sólo define la posibilidad de que el mismo sea accedido por una vista Raw de la siguiente forma:

unBuffer = new Buffer(..., ResourceOptionFlags.RawBuffer, ...);

Acceso

Las funciones más importantes son las de Load(uint address) y Store(uint address, uint value) y sus overloads para N-elementos. La primera lee 4 bytes según el offset en bytes dentro del buffer definido por el parámetro address y la segunda almacena un valor de 4 bytes (value) en la posición en bytes dada por address. Si uno necesita trabajar con otro tipo de dato que no sea uint, simplemente debe convertir los valores después de leerlos y antes de almacenarlos:

RWByteAddressBuffer Position;
RWByteAddressBuffer Velocity;

...

float3 position = asfloat(Position.Load3(buffersOffset));
float3 velocity = asfloat(Velocity.Load3(buffersOffset));
...
Position.Store3(buffersOffset, asuint(newPosition));
Velocity.Store3(buffersOffset, asuint(velocity));

Hay más funciones para trabajar sobre el buffer que yo no necesité y que se pueden revisar en RWByteAddressBuffer.

Habría estado lindo poder usar un Structured o Read/Write Buffer (es más simple su acceso), pero como el CS necesitaba tener acceso a buffers que luego eran utilizados como input del IA (es decir, habían sido creados con BindFlag.VertexBuffer), no fue posible. Ciertos BindFlags y ResourceOptionFlags son mutuamente excluyentes, como en el caso de BindFlag.VertexBuffer y ResourceOptionFlags.Structured.

Unordered Access View

Para aprovechar al máximo el paralelismo de los threads, es indispensable que los mismos trabajen sobre recursos que permitan un acceso simultáneo a los datos. DX 11 incorpora varios tipos de Resources y Views de los mismos según los permisos y la funcionalidad que uno requiera.

El tipo de vista Unordered Access View permite acceso de lectura/escritura simultáneo por distintos threads y sólo puede ser utilizado en Pixel Shaders o Compute Shaders.

Para que un recurso pueda ser asociado a una UAV, debe ser creado con el flag BindFlag.UnorderedAccess:

unBuffer = new Buffer(..., BindFlags.UnorderedAccess | BindFlags.VertexBuffer, ...);

Creación

Para crear una UAV hace falta definir el recurso sobre el cual recae la misma y la forma de interpretar los datos contenidos en el mismo a través de una descripción. Para el ejemplo, los recursos fueron creados con ResourceOptionFlags.RawBuffer y las vistas a crear serán también del tipo Raw.

UnorderedAccessViewDescription uavDesc = new UnorderedAccessViewDescription()
{
    Format = SlimDX.DXGI.Format.R32_Typeless,
    Dimension = UnorderedAccessViewDimension.Buffer,
    Flags = UnorderedAccessViewBufferFlags.RawData,
    FirstElement = 0,
    ElementCount = CANTQUADS * 3,
};
uavVertices = new UnorderedAccessView(Recursos.Device, vertexbuffer, uavDesc);
uavVelocity = new UnorderedAccessView(Recursos.Device, velocitybuffer, uavDesc);

Dimension define el tipo de recurso y, al trabajar con RawData, el Format debe ser R32_Typeless (coincide con el acceso de a 4 bytes). Hay más opciones que no vienen al caso (Slices).

Bindeo al shader

public static void SetearUAV(this Effect inEffect, string inParameter, UnorderedAccessView inValue)
{
    inEffect.GetVariableByName(inParameter).AsUnorderedAccessView().SetView(inValue);
}

CSParticlesAndCollisions2D

Este programa es el análogo a GSParticlesAndCollisions2D (no explico todo otra vez, sino sólo las diferencias por lo que puede hacer falta ver el post anterior), con la diferencia de que la actualización de la posición y velocidad de los puntos se realiza en un Compute Shader en lugar de un Geometry Shader.

SlimDX

Al no actualizar desde un GS (y, por ende, no utilizar Stream Output) no hace falta hacer ping pong entre distintos buffers por lo que sólo son necesarios los buffers vertexbuffer y velocitybuffer.

En cada frame, primero se actualizan las partículas usando el CS:

effectCS.SetearUAV("Position", uavVertices);
effectCS.SetearUAV("Velocity", uavVelocity);

int pass = effectCS.GetTechniqueByName("UpdatePoints").Description.PassCount;
for (int i = 0; i < pass; i++)
{
    effectCS.GetTechniqueByName("UpdatePoints").GetPassByIndex(i).Apply(contexto);
    ////Dado que levanto 1024 threads por grupo (definido en el .fx), la cantidad de grupos a levantar es la cantidad total de partículas dividida por la cantidad de partículas(=threads) por grupo
    contexto.Dispatch(CANTQUADS / 1024, 1, 1);
}
contexto.ComputeShader.SetUnorderedAccessViews(nullUAVs, 0, 2);

Es importante la última línea que desasocia las vistas del shader antes de pasar al renderizado y usar los buffers como input:

contexto.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
contexto.InputAssembler.InputLayout = layoutPointToQuad;
contexto.InputAssembler.SetVertexBuffers(0, bindingPointToQuad);
int passes = efectoPointToQuad.GetTechniqueByName("GSExpandPointToQuad").Description.PassCount;
for (int i = 0; i < passes; i++)
{
    efectoPointToQuad.GetTechniqueByName("GSExpandPointToQuad").GetPassByIndex(i).Apply(contexto);
    contexto.Draw(CANTQUADS, 0);
}
contexto.InputAssembler.SetVertexBuffers(0, nullbinding);

Shader de Update

El efecto se compila dentro de una technique11 como los demás, sólo que no puede haber otra stage en la técnica:

technique11 UpdatePoints
{
    pass
    {
        SetComputeShader(CompileShader(cs_5_0, CSUpdatePoints()));
    }
}

En cuanto al código, cada buffer almacena un vector de 3 dimensiones (uno posición y otro velocidad), por lo que es necesario leer de a 12 bytes (tamaño de 3 floats) usando la función Load3():

RWByteAddressBuffer Position;
RWByteAddressBuffer Velocity;

[numthreads(32, 32, 1)]
void BasicComputeShader(uint3 threadID : SV_DispatchThreadID)
{
    int buffersOffset = (threadID.x * 32 + threadID.y) * 4 * 3;
    float3 position = asfloat(Position.Load3(buffersOffset));
    float3 velocity = asfloat(Velocity.Load3(buffersOffset));

    Actualización y detección de colisiones...

    Position.Store3(buffersOffset, asuint(newPosition));
    Velocity.Store3(buffersOffset, asuint(velocity));
}

Buffer offset

En cuanto al offset a utilizar para acceder al buffer, lo mejor es verlo a través de un ejemplo (tener en cuenta que cada thread actualiza a una sola partícula):


Cantidad de partículas: 16
Cantidad de threads por grupo: 4
Dispatch: (4,1,1)
Dimensiones de cada grupo: (2,4,1)

Para empezar, estamos despachando 4 grupos de 8 threads c/u, lo que genera un total de 32 que actualizarán a 32 partículas. Siendo que son 32 partículas y cada buffer guarda 3 floats por partícula, el tamaño de cada uno será 32 * 3 * 4 bytes = 384 bytes.

Según las dimensiones del dispatch, el valor de SV_GroupID será (0,0,0) para el grupo 0, (1,0,0) para el grupo 1, (2,0,0) para el 2 y (3,0,0) para el 3.
Según las dimensiones de cada grupo, los valores de SV_GroupThreadID serán ((0,0,0), (0,1,0), (0,2,0), (0,3,0), (1,0,0), (1,1,0), (1,2,0), (1,3,0).
A partir de estas dos consideraciones y la fórmula de obtención de SV_DispatchThreadID, vemos que este valor para todos los threads del grupo 0 irá de (0,0,0) * (2,4,0) + (0,0,0) a (0,0,0) * (2,4,0) + (1,3,0). Para el grupo 1: (1,0,0) * (2,4,0) + (0,0,0) a (1,0,0) * (2,4,0) + (1,3,0) = (2,0,0) a (3,3,0). Para el grupo 2: (4,0,0) a (5,3,0). Y para el grupo 3: (6,0,0) a (7,3,0).

Tabla

Por lo que la fórmula queda como (SV_DispatchThreadID.x * dimensionDeY + SV_DispatchThreadID.y) * 12

Compute vs Geometry

262144 partículas (lo que limita los FPS es el renderizado de los quads por lo que no tiene mucho sentido la comparación)

Geometry Shader

GS262144

Compute Shader

CS262144

1048576 partículas (sólo actualización, sin renderizado)

Geometry Shader

GS1048576

Compute Shader

CS1048576

Coming Up

Estoy importando GPUPerfAPI de AMD a SlimDX usando PInvoke. Si funciona (hasta ahora sólo probé preparar el device y chequear los counters que soporta), voy a estar escribiendo sobre esto en el próximo post.

SlimDX y DirectX 11 - Stream Output

SO

Código fuente de la solución (los controles de cada programa figuran en un textbox en el form principal)

Stream Output

Los buffers de salida del Geometry Shader, aparte de ser enviados al Rasterizer, pueden ser obtenidos por la aplicación a través del Stream Output. Al igual que con los render targets del Output Merger, antes de cada renderizado se setean los buffer targets del SO.

Implementación

Los buffers a utilizar como SOtargets deben ser creados con el flag BindFlags.StreamOutput:

positionBuffer = new Buffer(device, initPositions, initPositions.Length * 4 * 3, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 4 * 3);

Como para el sistema de partículas voy a hacer ping-pong entre dos buffers para cada característica (posición y velocidad), se deberá especificar al momento de crearlos que se utilizarán como input del Input Assembler (vertex buffers) como así también como SO targets.

Así como existe VertexBufferBinding para asociar a cada buffer con su formato, semántica, stride, etc, existe StreamOutputBinding:

outbinding = new StreamOutputBufferBinding[]

{

    new StreamOutputBufferBinding(vertexbuffer, 0),

    new StreamOutputBufferBinding(velocitybuffer, 0)

};

Si optamos por manejar los shaders usando la clase Effect (como en mi solución), la especificación de cómo se realiza el bindeo recae en el shader:

technique11 GSUpdatePoints

{

	pass P0

	{

		SetVertexShader(CompileShader(vs_5_0, VShaderUpdatePoints()));

		SetGeometryShader(ConstructGSWithSO(CompileShader(gs_5_0, GShaderUpdatePoints()), "0:POSITION.xyz; 1:VELOCITY.xy;"));

		SetPixelShader(NULL);

	}

}

Utilizando ConstructGSWithSO, especificamos la semántica (POSITION) asociada a cada buffer del SO (0:) y las componentes a obtener (.xyz).

Si, en cambio, optamos por compilar el GS por separado y asociarlo directamente al contexto, la especificación se realiza en nuestra aplicación:

StreamOutputElement[] SOelements = new StreamOutputElement[]

{

    new StreamOutputElement(0, "POSITION", 0, 0, 3, 0),

    new StreamOutputElement(0, "VELOCITY", 0, 0, 2, 1)

};

int[] SOstrides = new int[]

{

    12,

    8

};

context.GeometryShader = new GeometryShader(device, shaderBytecode, SOelements, SOstrides, 0);

No lo probé por lo que puede que haya puesto mal algún parámetro, pero básicamente lo que se define es lo mismo: index del stream, semántica, index de la semántica, index de la primer componente a obtener, cantidad de componentes a obtener, index del buffer en el stream y stride.

Finalmente, se asocian los buffers al SO con:

contexto.StreamOutput.SetTargets(outbinding);

GSParticlesAndCollisions2D

La idea de este programita es mostrar un sistema de partículas muy básico (tan básico que ni siquiera se crean y destruyen partículas) y cómo se puede usar el GS para actualizar su estado en función del estado anterior. Las partículas se mueven con movimiento rectilíneo (aunque va cambiando de recta :P) uniformemente variado (básicamente, aceleración).

Me basé en D3DBook Dynamic Particle Systems. El autor agrega cosas interesantes como el manejo de distintos tipos de sistemas de partículas en un mismo GS y la emisión y destrucción de las partículas.

El renderizado consta de dos pasadas (dos técnicas de una pasada cada una). En la primera se actualiza el estado del sistema con un GS que se ejecuta por cada punto y retorna a través del Stream Output los buffers de posición y velocidad actualizados. En la segunda, se expande cada punto a quad y se renderiza en pantalla usando un shader prácticamente igual al mostrado en el post anterior.

Figuras colisionables

Plano normalizado, definido por las componentes de su normal (x,y,z) y la distancia desde el mismo hasta el origen en la dirección de su normal (d). Si bien la componente en Z en 2D no tiene sentido, tampoco molesta :P y deja en evidencia que en 3 dimensiones el plano puede ser representado por un float4.

//Bordes de la pantalla con normales hacia adentro

efectoUpdate.SetearParametroElement("CollisionObjects", "fPlanes", 0, new Vector4(1f, 0f, 0f, 1f)); //Izq

efectoUpdate.SetearParametroElement("CollisionObjects", "fPlanes", 1, new Vector4(-1f, 0f, 0f, 1f)); //Der

efectoUpdate.SetearParametroElement("CollisionObjects", "fPlanes", 2, new Vector4(0f, 1f, 0f, 1f)); //Aba

efectoUpdate.SetearParametroElement("CollisionObjects", "fPlanes", 3, new Vector4(0f, -1f, 0f, 1f)); //Arr

Para planos no tan simples, se puede utilizar la clase Plane de SlimDX:

Plane p;

p = new Plane(new Vector3(ubicacionPlanos, ubicacionPlanos, 0), new Vector3(-1f, -1f, 0));

p.Normalize();

efectoUpdate.SetearParametroElement("CollisionObjects", "fPlanes", 4, new Vector4(p.Normal, p.D));

Box, definida por las componentes de su punto mínimo (x,y) y su punto máximo (x,y). Para 3 dimensiones haría falta utilizar dos float3.

efectoUpdate.SetearParametroElement("CollisionObjects", "fBoxes", index, new Vector4(boxCenter[index].X - boxSemiWidthHeight[index], boxCenter[index].Y - boxSemiWidthHeight[index], boxCenter[index].X + boxSemiWidthHeight[index], boxCenter[index].Y + boxSemiWidthHeight[index]));

Esfera, definida por las componentes de su centro (x,y,z) y su radio (un escalar).

efectoUpdate.SetearParametroElement("CollisionObjects", "fSpheres", index, new Vector4(sphereCenter[index], 0f, sphereRadius[index]));

Colisiones

Plano

Si la distancia signada de la partícula respecto al plano es menor a 0, la misma se encuentra en el lado negativo del plano. Si en el estado anterior estaba en el positivo (>0) o sobre el plano (=0) entonces lo acaba de traspasar y debo manejar el rebote.

Al colisionar, el vector velocidad es reflejado en función de la normal del plano y la posición de la partícula se actualiza como el punto real de intersección entre el segmento recorrido en este frame y el plano. No debería hacer falta actualizar la posición dado que eventualmente la partícula volvería sobre su recorrido, pero lo hice para evitar que se pierdan partículas (vayan muy lejos y tarden en volver) si, por ejemplo, dejo de renderizar un segundo (probar comentando las dos últimas líneas del algoritmo y manteniendo con el mouse la ventana del programa para que deje de renderizar).

for (int i=0; i < fCantPlanes; i++)

{

	distToPlane = dot(fPlanes[i].xyz, Input[0].Pos) + fPlanes[i].w;

	newDistToPlane = dot(fPlanes[i].xyz, Output.Pos)  + fPlanes[i].w;



	if (newDistToPlane < 0 && distToPlane >= 0)

	{

		Input[0].Vel = reflect(Input[0].Vel, fPlanes[i].xy);



		//Punto de intersección

		float d = dot(-fPlanes[i].xyz * fPlanes[i].w - Input[0].Pos.xyz, fPlanes[i].xyz) / dot(fPlanes[i].xyz, fPlanes[i].xyz);

		Output.Pos.xyz = d * fPlanes[i].xyz + Input[0].Pos.xyz;

	} 

}

Box

No estoy seguro de si mi implementación de la colisión es la más eficiente, tal vez haya forma de ahorrar alguna que otra condición.

for (int i=0; i < fCantBoxes; i++)

{

	if (Output.Pos.x > fBoxes[i].x && Output.Pos.x < fBoxes[i].z && Output.Pos.y > fBoxes[i].y && Output.Pos.y < fBoxes[i].w)

	{

		if (Input[0].Pos.x = fBoxes[i].z) Input[0].Vel = reflect(Input[0].Vel, float2(1,0));

		if (Input[0].Pos.y = fBoxes[i].w) Input[0].Vel = reflect(Input[0].Vel, float2(0,1));

	}

}

En primera instancia, chequeo que la posición actual esté dentro de la caja verificando si se encuentra dentro del rango definido por sus extremos para cada coordenada. Si esto ocurre, verifico si en el estado anterior se encontraba fuera de la caja y respecto a qué coordenada para reflejar la velocidad en función del vector normal del lado que produce la colisión (notar que es indistinto usar normal y -normal dado que ambas definen a la misma superficie de reflexión).

Sphere

La colisión con la esfera no es matemáticamente exacta. La solución correcta resulta ser más complicada que lo que uno esperaría.

for (int i=0; i < fCantSpheres; i++)

{

	float3 casiNormal = Output.Pos - fSpheres[i].xyz;

	float3 casiNormalPrev = Input[0].Pos - fSpheres[i].xyz;

	float radiusSquared = fSpheres[i].w * fSpheres[i].w;

	if (dot(casiNormal, casiNormal) < radiusSquared && dot(casiNormalPrev, casiNormalPrev) >= radiusSquared)

	{

		Input[0].Vel = reflect(Input[0].Vel, normalize(casiNormal));

	}

}

Si la distancia entre la posición actual y el centro de la esfera es menor al radio y la distancia entre la posición anterior y el centro es mayor al radio, la partícula acaba de atravesar la esfera y es reflejada en función del vector Centro->Posición actual. Esto funciona considerablemente bien siempre y cuando la proporción entre distancia recorrida dentro de la esfera y distancia desde el centro al punto actual sea muy baja. Dejaría de funcionar para casos de demasiada velocidad o esferas demasiado pequeñas.

Círculos

El vector real de reflexión viene dado por el vector Centro->Punto de colisión por lo que habría que obtener a este punto para calcularlo.

Video

Coming up

Estoy entre Dynamic Shader Linkage, Compute Shader o seguir con el GS y las colisiones básicas pero en 3D (aunque supuestamente la idea de actualizar sistemas usando el GS quedó obsoleta con la aparición del CS).

SlimDX y DirectX 11 - Point to Quad Geometry Shader

P2Q

Solución

Para no armar una solución/proyecto por cada cosa que haga en SlimDX, hice un form Base.cs desde donde se elige y lanza (creando un thread) la prueba a ejecutar. De paso la uso para mostrar stats y demás sin recurrir a DirectWrite :P

Recién me entero de que activando el unmanaged code debugging en las propiedades del proyecto se pueden ver en la ventana de Output warnings y mensajes de error de DX, por lo que probablemente más adelante me proponga como tarea arreglar el código para no dejar recursos colgando :P

El código lo subo en el próximo post (igual copio textual el GS en este).

Pipeline de DX 10

Pipeline

Geometry Shader

Shader que trabaja a nivel de primitiva, el cual permite crear nuevas primitivas y/o descartarlas. Su input es un array de outputs del Domain Shader (Vertex Shader si no hubiere HS-Tesselator-DS) (dado que para trabajar sobre un triángulo, por ejemplo, requeriría de sus 3 vértices), y su output uno o más buffers símiles a los vertex buffers.

[maxvertexcount(4)]
void GShaderGSExpandPointToQuad(point GS_INPOS Input[1], inout TriangleStream<PS_INPOSTXR> outStream)
{
	Shader code...
}

El header es bastante simple. Se define la cantidad máxima de vértices de salida por ejecución del GS (maxvertexcount), el tipo de primitiva de entrada (point) con su struct asociada, la cantidad de vértices a leer antes de ejecutar el GS (por ser point: Input[1]), y el tipo de primitiva de salida (TriangleStream).

Ejemplo de GS: Point to Quad/Sprite

Es casi el Hello World de los GSs. Ni siquiera hace uso del Stream Output (sobre el cual voy a escribir en el próximo post).

Este GS recibe un array de puntos y, por cada uno, genera un Triangle Strip formado por 2 triángulos que le dan forma a un Screen Aligned Quad. Cada vértice de salida se agrega al Stream utilizando la función Append(vertex) y, si bien en este shader no hace falta, si hiciese falta resetear el Strip de salida se usaría la función RestartStrip().

[maxvertexcount(4)]
void GShaderGSExpandPointToQuad(point GS_INPOS Input[1], inout TriangleStream<PS_INPOSTXR> outStream)
{
	PS_INPOSTXR Output;
	
	float fQuadDimensionX = fQuadDimensionsCS.x * (cos(Input[0].Pos.z + fTimeTotal) + 1.0);
	float fQuadDimensionY = fQuadDimensionsCS.y * (cos(Input[0].Pos.z + fTimeTotal) + 1.0);
	Output.Pos.z = 0.0;
	Output.Pos.w = 1.0;
	Output.Col = Input[0].Col;
	
	Output.Pos.xy = Input[0].Pos.xy + float2(-fQuadDimensionX, fQuadDimensionY);
	Output.Txr = float2(0.0,0.0);
	outStream.Append(Output);
	Output.Pos.xy = Input[0].Pos.xy + float2(-fQuadDimensionX, -fQuadDimensionY);
	Output.Txr = float2(0.0,1.0);
	outStream.Append(Output);
	Output.Pos.xy = Input[0].Pos.xy + float2(fQuadDimensionX, fQuadDimensionY);
	Output.Txr = float2(1.0,0.0);
	outStream.Append(Output);
	Output.Pos.xy = Input[0].Pos.xy + float2(fQuadDimensionX, -fQuadDimensionY);
	Output.Txr = float2(1.0,1.0);
	outStream.Append(Output);
}

technique11 GSExpandPointToQuad
{

	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VShaderGSExpandPointToQuad()));
		SetGeometryShader(CompileShader(gs_5_0, GShaderGSExpandPointToQuad()));
		SetPixelShader(CompileShader(ps_5_0, PShaderGSExpandPointToQuad()));
	}
}

Por cada punto (Input[0]), voy creando cada vértice del quad usando la posición y el tamaño en Clip Space y agregándolo al Stream. Pos.z almacena un valor random generado al momento de crear los vertex buffers que modifica la fase de expansión del quad para que no estén todos sincronizados :P

Del lado de la aplicación no hay que codificar absolutamente nada ya que el GS se carga al cargar el efecto entero (si no se usase Effect habría que compilarlo usando new GeometryShader()).