Después de intentar algunas cosas sobre el papel, he encontrado algo que podría funcionar. Es una implementación adecuadamente paralelizada/vectorizada de la función en SSE.
Sin embargo, requiere reorganización de datos porque haremos un procesamiento paralelo para 4 triángulos a la vez.
Lo dividiré en pasos y usaré los nombres de las instrucciones aquí y allá, pero use los intrínsecos C (_mm_load_ps(), _mm_sub_ps() et al, están en xmmintrin.h en VC) - cuando hablar de registros esto solo significa __m128.
PASO 1.
No necesitamos la coordenada Y en absoluto, por lo que hemos creado punteros a pares de X y Z. de suministro al menos 4 pares (es decir, 4 triángulos total) por llamada. Llamaré a cada par X y Z un vértice.
PASO 2.
Uso MOVAPS (requiere los punteros a estar alineados a 16 bits) para cargar los dos primeros vértices apuntado por cada puntero en registros.
un registro cargado desde un se verá así: [a0.x, a0.z, a1.x, a1.z]
PASO 3.
Ahora, utilizando una sola instrucción de resta, se puede calcular los deltas (su v0, v1 , v2) durante 2 vértices a la vez.
Calcular v0, v1 y v2 no sólo para los 2 primeros triángulos, sino también para el último 2! Como dije, debe proporcionar un total de 4 vértices u 8 flotantes por entrada. Solo repita los pasos 2 y 3 para los datos.
Ahora tenemos 2 pares de vx registros, cada par que contiene el resultado para 2 triángulos. Me referiré a ellos como vx_0 (primer par) y vx_1 (segundo par) donde x es de 0 a 2.
PASO 4.
productos escalares. Para paralelizar el cálculo baricéntrico (más adelante), requerimos el resultado de cada producto de puntos para cada uno de los 4 triángulos, en 1 registro individual.
Entonces, donde calcularía dot01 por ejemplo, haremos lo mismo, pero para 4 triángulos a la vez. Cada v -registro contiene el resultado para 2 vectores, así que comenzamos por multiplicarlos.
Digamos que u y v - parámetros en función de su producto escalar - son ahora v0_0 y v1_0 (para calcular dot01):
Multiplicar u y v obtener: [(v0_0.x0) * (v1_0.x0), (v0_0.z0) * (v1_0.z0), (v0_0.x1) * (v1_0.x1), (v0_0.z1) * (v1_0.z1)]
Esto puede parecer confuso, debido a .x0/.z0 y .x1/.z1, pero mira lo que estaba cargado en el paso 2: a0, a1 .
Si por ahora esto todavía se siente difusa, recoger un pedazo de papel y escriba lo largo desde el principio.
A continuación, aún para el mismo producto escalar, hacer la multiplicación para v0_1 y v1_1 (es decir, el segundo par de triángulos).
Ahora tenemos 2 registros con 2 X & Z pares cada uno (4 vértices en total), multiplicados y listos para sumarse para formar 4 productos de puntos separados.SSE3 tiene una instrucción para hacer exactamente esto, y se llama HADDPS:
xmm0 = [A, B, C, D] XMM1 = [E, F, G, H]
HADDPS xmm0, XMM1 hace esto:
xmm0 = [A + B, C + D, E + F, G + H]
se llevará a las X & pares Z de nuestro primer registro, los de la segunda, sumarlos y almacenarlos en el primer, segundo, tercer y cuarto componente del registro de destino. Ergo: ¡en este punto tenemos este producto en particular para los 4 triángulos!
** Ahora repita este proceso para todos los productos dot: dot00 et cetera. **
PASO 5.
El último cálculo (por lo que pude ver por el código suministrado) es la materia barycentric. Este es un cálculo 100% escalar en su código. Pero sus entradas ahora no son resultados escalares de productos de puntos (es decir flotadores individuales), son vectores/registros SSE con un producto de puntos para cada uno de los 4 triángulos.
Así que si vas a vectorizar esto usando las operaciones SSE paralelas que operan en los 4 flotadores, eventualmente terminarás con 1 registro (o resultado) llevando 4 alturas, 1 para cada triángulo.
Como mi hora del almuerzo ya ha pasado, no voy a deletrear esto, pero teniendo en cuenta la configuración/idea que he dado, este es un último paso y no debería ser difícil de entender.
Sé que esta idea es un poco exagerada y requiere un poco de amor del código que se encuentra arriba y tal vez algún tiempo de calidad con lápiz y papel, pero será rápido (e incluso puede agregar OpenMP después si te gustaría).
Buena suerte :)
(y perdona mi explicación difusa, que puede azotar encima de la función si es necesario =))
ACTUALIZACIÓN
he escrito una implementación y no funcionó como esperaba, principalmente porque el componente Y se involucró más allá del fragmento de código que inicialmente pegó en su pregunta (lo busqué). Lo que he hecho aquí no es solo pedirte que reorganices todos los puntos en pares XZ y los alimentes por 4, sino que también alimentes 3 punteros (para los puntos A, B y C) con los valores Y de cada uno de los 4 triángulos. Desde una perspectiva local, esto es más rápido. Todavía puedo modificarlo para que requiera cambios menos intrusivos del final del llamado, pero háganme saber lo que es deseable.
A continuación, un descargo de responsabilidad: este código es directo como el infierno (algo que he encontrado que funciona bastante bien con los compiladores en términos de SSE ... pueden reorganizarse como corresponde y las CPU x86/x64 toman su parte también) También el nombre, no es mi estilo, no es de nadie, solo hazlo como mejor te parezca.
Espero que ayude y si no, con mucho gusto lo repasaremos de nuevo.Y si esto es un proyecto comercial también existe la opción de conseguir mí a bordo supongo;)
De todos modos, lo he puesto en Pastebin: http://pastebin.com/20u8fMEb
Dudo que esta función de hoja se pueda optimizar aún más porque solo hace 3 operaciones de FP. Posiblemente en los sitios de llamadas es posible una optimización. – hirschhornsalz
Si se llama mucho esta función, intente usar OpenMP si tiene sentido. – jn1kk
IMO que no es el enfoque correcto. SSE no está diseñado para operaciones horizontales. Hay algunas instrucciones horizontales, pero son (casi) todas lentas. Con SSE casi siempre es mejor calcular 4 de algo a la vez en lugar de intentar hacer 1 cosa 4 veces más rápido. – harold