Ver la versión completa : Pequeño tutorial OpenGL gpu940
ESTO NO PRETENDE SER UN TUTORIAL DE OPENGL, SOLO UNA PEQUEÑA AYUDA
Como muchos de por aquí tenéis interés en empezar a programar con la librería OpenGL de gpu940, y ya que yo ya tengo experiencia con ella, qué mejor que compartir mis conocimientos :D
Ya publiqué el otro día un pequeño programa de ejemplo, pero muy mal elaborado, sin comentarios y presuponiendo que ya se conocía la sintaxis OpenGL. Como me sentía culpable, ahora os traigo un tutorial más completo :)
Empecemos por los archivos de cabecera. Todo el código de #ifdef y extern "C" y todo eso es indispensable si usamos C++, si no, no compilará nuestro código.
#ifdef __cplusplus
extern "C" {
#endif
#include "GL/gl.h"
#include "gpu940.h"
#ifdef __cplusplus
}
#endif
#include <math.h> //necesario para la función gluPerspective
Vamos a añadir unas declaraciones de funciones que más adelante usaremos. Ya las explicaré.
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);
void draw();
Nuestra función principal, main()
Aquí inicializamos OpenGL y configuramos algunos parámetros, así como la matriz de proyección
int main()
{
glOpen(DEPTH_BUFFER); // Inicializa el OpenGL de gpu940
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Especifica el color que se usará para borrar la pantalla (rojo, verde, azul, alpha)
glClearDepth(1.0f);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST); // Activamos el test de profundidad
glShadeModel(GL_SMOOTH); // Sombreado suave
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ); // Correción de perspectiva
glMatrixMode(GL_PROJECTION); //Cambiamos a la matriz de proyección...
glLoadIdentity(); //Y la reseteamos
gluPerspective(45.0f,(GLfloat)320/(GLfloat)240,0.1f,100.0f); // Esta función la definiremos más adelante
glMatrixMode(GL_MODELVIEW); //Cambiamos a la matriz de modelado
while(true) // Este bucle se ejecutara indefinidamente, por lo que no podremos terminar la aplicación sin matarla
{
draw(); // Función definida por nosotros para dibujar
glSwapBuffers(); // Intercambia los buffers y muestra uno en pantalla. Esto es así porque usamos double buffer
};
glClose(); // Cerramos OpenGL, aunque en este ejemplo esta línea nunca se llegará a ejecutar :(
return 0;
}
gluPerspective es una función que váis a encontrar en muchos tutoriales de OpenGL, pero que gpu940 carece de ella, por lo que la escribimos nosotros. A mí me resulta muy útil ésta función.
//rutina de reemplazo, en gpu940 no tenemos gluPerspective()
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar){
const double pi180 = 0.017453292519943295769236907684886;
GLdouble top, bottom, left, right;
top = zNear * tan(pi180*fovy/2);
bottom = -top;
right = aspect*top;
left = -right;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(left, right, bottom, top, zNear, zFar);
glMatrixMode(GL_MODELVIEW);
}
Y finalmente, llega la hora de dibujar
void draw()
{
static GLfloat xrot=0; //variables estáticas para guardar la rotación del cubo
static GLfloat yrot=0;
static GLfloat zrot=0;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //limpiamos la pantalla
glLoadIdentity(); // reseteamos
glTranslatef(0.0f,0.0f,-5.0f); // Nos desplazamos un poco
glRotatef(xrot,1.0f,0.0f,0.0f); //
glRotatef(yrot,0.0f,1.0f,0.0f); // Rotamos en cada eje según nuestras variables de rotación
glRotatef(zrot,0.0f,0.0f,1.0f); //
glBegin(GL_QUADS); // Comenzamos a dibujar polígonos de cuatro vértices
glVertex3f(-1.0f, -1.0f, 1.0f); //Primer vértice del primer polígono
glVertex3f( 1.0f, -1.0f, 1.0f); //Segundo vértice
glVertex3f( 1.0f, 1.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f(-1.0f, -1.0f, -1.0f); //Primer vértice del segundo polígono
glVertex3f(-1.0f, 1.0f, -1.0f); //Segundo vértice
glVertex3f( 1.0f, 1.0f, -1.0f);
glVertex3f( 1.0f, -1.0f, -1.0f);
glVertex3f(-1.0f, 1.0f, -1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f, 1.0f, -1.0f);
glVertex3f(-1.0f, -1.0f, -1.0f);
glVertex3f( 1.0f, -1.0f, -1.0f);
glVertex3f( 1.0f, -1.0f, 1.0f);
glVertex3f(-1.0f, -1.0f, 1.0f);
glVertex3f( 1.0f, -1.0f, -1.0f);
glVertex3f( 1.0f, 1.0f, -1.0f);
glVertex3f( 1.0f, 1.0f, 1.0f);
glVertex3f( 1.0f, -1.0f, 1.0f);
glVertex3f(-1.0f, -1.0f, -1.0f);
glVertex3f(-1.0f, -1.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd(); // Terminamos con los polígonos de cuatro vértices
xrot+=1.3f; // Modificamos las variables de rotación
yrot+=1.2f;
zrot+=1.4f;
}
No os puedo ayudar sobre cómo compilarlo, ya que cada configuración es un mundo, pero podéis preguntarme cualquier problemas que tengáis...
No soy muy bueno enseñando, pero espero que lo que he intentado explicar os sirva de mucha ayuda :)
Esto se merece estar en el wiki, luego si saco algo de tiempo lo subo si no te importa. Muchas gracias.
Aprovecho que nadie ha posteado aún (:rolleyes:) para comentar una cosilla..
Para ejecutarlo, no se puede ejecutar directamente el archivo resultante de la compilación, sino que hay que crear un script que ejecute "./load940 gpu940" y después de eso, ya ejecutar el programa compilado. Cuando el programa finalice, ejecutar "./stop940"
Tenéis un script de ejemplo en el otro hilo, el del helloworld gpu940
EDIT: neglox, ¡te mato! jajaja, has posteado de mientras que yo escribia esto :quepalmo:
No me importa que lo subas al wiki, solo con una condición: que alguien haga antes el tutorial y diga si está bien y le funciona y es correcto, pues yo no lo he probado :P
JoJo_ReloadeD
17/06/2007, 21:28
Aprovecho que nadie ha posteado aún (:rolleyes:) para comentar una cosilla..
Para ejecutarlo, no se puede ejecutar directamente el archivo resultante de la compilación, sino que hay que crear un script que ejecute "./load940 gpu940" y después de eso, ya ejecutar el programa compilado. Cuando el programa finalice, ejecutar "./stop940"
Tenéis un script de ejemplo en el otro hilo, el del helloworld gpu940
EDIT: neglox, ¡te mato! jajaja, has posteado de mientras que yo escribia esto :quepalmo:
No me importa que lo subas al wiki, solo con una condición: que alguien haga antes el tutorial y diga si está bien y le funciona y es correcto, pues yo no lo he probado :P
Buen trabajo, se consigue rendimiento renderizando con el 940 ?
Buen trabajo, se consigue rendimiento renderizando con el 940 ?
No lo sé aún, estoy por hacer alguna prueba de rendimiento, ¿alguna sugerencia?
El programita ese de ejemplo de mi motor que puse en youtube, el de los dos modelos de quake2 andado, se movía a 15fps. Claro que ni estaba optimizado, ni sé la cantidad de polígonos que tenían los modelos, ni sé si el tamaño de textura era excesivamente para la negrita, etc...
LOL no era mi intención , pero ya que te lo has currado pues alguien te lo tenía que agradecer. Yo me comprometo a probarlo después de exámenes que si lo hago ahora puedo morir.
No lo sé aún, estoy por hacer alguna prueba de rendimiento, ¿alguna sugerencia?
Estoy pensando en un programa que marque los fps en una esquina y que vaya generando polígonos hasta que haya unos ¿5000? en pantalla, haciendo las siguientes pruebas:
-1º Polígonos planos
-2º Polígonos con texturas pequeñas (¿16x16?)
-3º en adelante- texturas más grandes, hasta 1024x1024
Y que saque al final un fichero de texto en el que se vean reflejados los números de polígonos, de vértices y los fps que les corresponda.
Bueno quizás que no lo marque en la esquna, que eso hará más inexacto los calculos (el texto son poligonos texturizados)
< - >
He hecho una pequeña prueba de rendimiento deprisa y corriendo, y los resultados son desesperanzadores, pero, al ser deprisa y corriendo puede que haya cometido errores.
Los resultados vienen a ser que mostrando alrededor de 200 polígonos planos, sin texturas, de 4 vertices, va a 8 frames por segundo. Para que vaya a 60 frames por segundo tiene que haber en pantalla entre 40 y 50 de esos polígonos :_(
Espero que el error haya sido mio, porque si no...:(
< - >
Olvidaros de esos desesperanzadores resultados, es IMPOSIBLE que sea tan poco eficiente, estoy seguro que el fallo es mio :) asi que tranquilos
mortimor
17/06/2007, 22:49
Veo esto muy interesante. A ver que sale de esas pruebas :)
civantoz
18/06/2007, 02:23
Bueno, pues como dije ya me he puesto a echar los primeros vistazos al ejemplo que te has currado y me ha llevado un ratillo hacerlo funcionar, y no completamente, bueno al menos retocando el Makefile adaptándolo a mis rutas del cross-compiler para gp2x y habiendo copiado dentro las librerias, includes y binarios del gpu940, por lo menos ya he conseguido compilar el ejemplo mediante el Makefile desde la consola de sistema por fin sin miles de warnings ni nada por el estilo.
Lo dejo por aquí por si alguien usa el mismo, en mi caso es el entorno preparado por D_Skywalk, que posiblemente lo use mas gente...
CC = /usr/local/gp2xdev/bin/gp2x-gcc
LDFLAGS = -L/usr/local/gp2xdev/lib -lGL -lgpu940 -lm
IFLAGS = -I/usr/local/gp2xdev/include
CFLAGS = -g -O2 -static `/opt/gp2x/bin/sdl-config --cflags`
NAME = helloworld
OBJECTS = main.o
all: $(NAME)
$(NAME): $(OBJECTS)
$(CC) $(CFLAGS) $(IFLAGS) $(OBJECTS) $(LDFLAGS) -o $@
.cpp.o:
$(CC) -c $(CFLAGS) $(IFLAGS) $(DEFINES) $(LDFLAGS) $< -o $@
clean:
rm -f $(OBJECTS) $(NAME)
El problema me viene intentando integrarlo con Kdevelop, en principio debería ser igual de sencillo, pero no doy con la tecla xD. Mismo código fuente sin retocar nada, cambiadas las opciones del 'configure' del proyecto, a lo que yo creo que debería estar bien xD, vamos tirando a ponerlas igual que en el Makefile retocado, pero tururu, el fichero configure lo genera perfecto, pero a la hora de construir el proyecto naranjas de la china.
Pego aquí la salida que da Kdevelop a ver si nuevamente me sacas del atasco este :)
cd '/home/civan/juego2x3d/gp2x' && WANT_AUTOCONF_2_5="1" WANT_AUTOMAKE_1_6="1" make
cd . && /bin/bash ./config.status Makefile
config.status: creating Makefile
make all-recursive
make[1]: se ingresa al directorio `/home/civan/juego2x3d/gp2x'
Making all in src
make[2]: se ingresa al directorio `/home/civan/juego2x3d/gp2x/src'
cd .. && /bin/bash ./config.status src/Makefile depfiles
config.status: creating src/Makefile
config.status: executing depfiles commands
make[2]: se sale del directorio `/home/civan/juego2x3d/gp2x/src'
make[2]: se ingresa al directorio `/home/civan/juego2x3d/gp2x/src'
/bin/bash ../libtool --tag=CC --mode=link arm-gp2x-linux-gcc -static -DGP2X -g -O2 -I/opt/gp2x/include/SDL -D_REENTRANT -R/opt/gp2x/lib -L/usr/local/gp2xdev/lib -lGL -lgpu940 -lm -o juego2x3d main.o -L/opt/gp2x/lib -Wl,-rpath,/opt/gp2x/lib -lSDL -lpthread
libtool: link: warning: library `/usr/local/gp2xdev/lib/libGL.la' was moved.
libtool: link: warning: library `/usr/local/gp2xdev/lib/libgpu940.la' was moved.
libtool: link: warning: library `/usr/local/gp2xdev/lib/libGL.la' was moved.
libtool: link: cannot find the library `/tmp/usr/local/gpu940/lib/libgpu940.la'
make[2]: *** [juego2x3d] Error 1
make[2]: se sale del directorio `/home/civan/juego2x3d/gp2x/src'
make[1]: *** [all-recursive] Error 1
make[1]: se sale del directorio `/home/civan/juego2x3d/gp2x'
make: *** [all] Error 2
*** Exited with status: 2 ***
Como verás dice algo de que las librerías fueron movidas, pero nada de eso, lo compruebo y están en ese directorio, así que a saber que tripa se le ha roto, además tambien te pongo los modificadores del 'configure' del proyecto, por si acaso he puesto alguna burrada, pero yo creo que está bien...
PESTAÑA "GENERAL"
-----------------
C/C++ preprocessor flags (CPPFLAGS):
-I/usr/local/gp2xdev/include
Linker flags (LDFLAGS):
-L/usr/local/gp2xdev/lib -lGL -lgpu940 -lm
PESTAÑA "C"
Compiler flags: CFLAGS
-static -DGP2X -g -O2 `/opt/gp2x/bin/sdl-config --cflags`
y eso es todo, si pudiera compilar con el Kdevelop sería mucho mas feliz :P jajajajaja
Bueno, muchas gracias a los que se lean el chorizo de marras.
< EDIT >
También decir que este mensaje casi que debería ir en el otro hilo, pero estoy un poco torpe a estas horas y no me he dado cuenta, pero me refiero al ejemplo que subiste la primera vez, si es posible borrar esta entrada o moverla pues bien, y sino... Efegea, ya te emborrone el hilo, lo siento xD
Saludos!
D_Skywalk
18/06/2007, 11:05
Wenas y gracias por el tuto efegea, a lo mejor algún día puedo añadir algo 3D en el abalon, aunque sea un efectillo cutre xD
civantoz, te recomiendo 2 cosas:
- La más fácil, elimina o mueve los .la de marras, normalmente no se necesitan y seguramente te quite el problema que citas.
- La menos fácil, edita los .la (que son ficheros de texto normales) y configura las rutas que dentro hay para que se adapten a tu path :D
Un Saludo y gracias por usar "entornos sky", parezco una azafata xDDD
civantoz
18/06/2007, 15:46
Bueno, después de tanto trapichear creo que me había cargado el entorno xD, así que lo he borrado, copiado de nuevo, añadido las librerías community y demás historias, he cambiado las rutas a /opt/gp2x, he modificado los makefiles, y por consola siguen funcionando correctamente, pero la cuestión sigue en Kdevelop, bueno, al menos lo de los ficheros .la ya está solucionado :) pero ahora hay nuevos errores que no se de que van...
cd '/home/civan/juego2x3d/gp2x' && WANT_AUTOCONF_2_5="1" WANT_AUTOMAKE_1_6="1" make
make all-recursive
make[1]: se ingresa al directorio `/home/civan/juego2x3d/gp2x'
Making all in src
make[2]: se ingresa al directorio `/home/civan/juego2x3d/gp2x/src'
arm-gp2x-linux-gcc -DHAVE_CONFIG_H -I. -I.. -I/home/civan/juego2x3d/src -I/opt/gp2x/include -I/opt/gp2x/include/GL -static -DGP2X -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o /home/civan/juego2x3d/src/main.c
/home/civan/juego2x3d/src/main.c:131:5: warning: no newline at end of file
mv -f .deps/main.Tpo .deps/main.Po
/bin/bash ../libtool --tag=CC --mode=link arm-gp2x-linux-gcc -static -DGP2X -g -O2 -L/opt/gp2x/lib -lm -lGL -lgpu940 -o juego2x3d main.o
mkdir .libs
arm-gp2x-linux-gcc -DGP2X -g -O2 -o juego2x3d main.o -L/opt/gp2x/lib -lm -lGL -lgpu940
/opt/gp2x/lib/gcc/arm-gp2x-linux/3.4.6/../../../../arm-gp2x-linux/bin/ld: ERROR: /opt/gp2x/lib/libGL.so uses software FP, whereas juego2x3d uses hardware FP
/opt/gp2x/lib/gcc/arm-gp2x-linux/3.4.6/../../../../arm-gp2x-linux/bin/ld: failed to merge target specific data of file /opt/gp2x/lib/libGL.so
/opt/gp2x/lib/gcc/arm-gp2x-linux/3.4.6/../../../../arm-gp2x-linux/bin/ld: ERROR: /opt/gp2x/lib/libgpu940.so uses software FP, whereas juego2x3d uses hardware FP
/opt/gp2x/lib/gcc/arm-gp2x-linux/3.4.6/../../../../arm-gp2x-linux/bin/ld: failed to merge target specific data of file /opt/gp2x/lib/libgpu940.so
collect2: ld returned 1 exit status
make[2]: *** [juego2x3d] Error 1
make[2]: se sale del directorio `/home/civan/juego2x3d/gp2x/src'
make[1]: *** [all-recursive] Error 1
make[1]: se sale del directorio `/home/civan/juego2x3d/gp2x'
make: *** [all] Error 2
*** Exited with status: 2 ***
Y las opciones del 'configure' que tengo añadidas ahora en el Kdevelop son las siguientes.
PESTAÑA GENERAL:
C/C++ preprocessor flags (CPPFLAGS):
-I/opt/gp2x/include -I/opt/gp2x/include/GL
Linker flags (LDFLAGS):
-L/opt/gp2x/lib -lm -lGL -lgpu940
PESTAÑA C:
Compiler flags (CFLAGS):
-static -DGP2X -g -O2
eso es todo por ahora, no entiendo muy bien que quiere decir el error, vamos, que el código usa números en coma flotante por hardware donde la librería los espera procesar por software...¿??¿? pero lo mismo debería decir en la compilación con el Makefile a través de consola y no se queja de nada xD.
Será problema por usar las librerías community, que creo que llevan las SDL de Paeryn con aceleración...? no se me ocurren muchas cosas inteligentes xD .
Bueno, muchas gracias por contestarme a las dudas antes, y a ver si consigo avanzar con estas otras...
Saludos!
Vaya no sé como se me han pasado éstos últimos comentarios :confused:
civantoz no puedo ayudarte, no tengo ni idea de qué te puede estar pasando. Ya sabes que yo también he tenido problemas al integrarlo con kdevelop, pero en mi caso era porque queria tirar por el camino de las autotools.
Lo que si te puedo sugerir es que uses kdevelop pero usando tus propios makefiles en lugar de las autotools. Para eso tienes que importar un proyecto existente y decirle que usa makefiles personalizados.
-----------------------
Cambiando de tema. Sobre el benchmark que hice, ya decía yo que estaba haciendo algo mal. He escrito a la lista de correo y el autor de gpu940 me ha corregido el error.
Ahora para 60fps tiene que haber 800 triangulos sin texturas en pantalla.
Para 30fps 1640 triangulos.
Así que supongo que 15fps serán 3300 triángulos.
Me quedo con los 30fps, porque mis modelos de quake2 moviendose a 15fps se veían muy fluidos :)
Voy a texturizarlos, seguiré informando ;)
< - >
Me parece increíble, el usar texturas, ya sea de 64x64 o de 512x512, no afecta apenas al rendimiento :asomb:
Puck2099
18/06/2007, 22:46
Hola, una dudadilla sobre el GPU940.
¿Es excluyente su utilización de minilib o SDL o puedes hacer que funcione a la vez para tener por ejemplo un juego en 2D usando SDL o minilib y aplicarle de vez en cuando objetos o efectos 3D?
Gracias
Puck2099, no puedes usarlos a la vez, ya que gpu940 se apropia del hardware gráfico :(
Yo conseguí combinar objetos 3D en un pequeño programita 2D SDL con un rasterizador 3D que alguien me comentó en estos mismos foros, lo siento no recuerdo el nombre, y no lo encuentro por el disco duro. Espero que el hilo no se haya perdido por el efecto 20k
Raydenito
18/06/2007, 23:48
Me parece increíble, el usar texturas, ya sea de 64x64 o de 512x512, no afecta apenas al rendimiento :asomb:
Yo no es que sepa muxo de OpenGl pero... no deberia notarse algo :confused: ?
Vamos digo :rolleyes:
Yo no es que sepa muxo de OpenGl pero... no deberia notarse algo :confused: ?
Vamos digo :rolleyes:
Claro que debería notarse, es evidente, algo falla :confused:
Si quieres hacer un motor rapido en la GP2X hay que usar aritmetica de punto fijo, olvidarse de filtrado de texturas, stencil, incluso el zbuffer...
Si quieres hacer un motor rapido en la GP2X hay que usar aritmetica de punto fijo, olvidarse de filtrado de texturas, stencil, incluso el zbuffer...
Todo eso ya lo sabía xD
¿A cuento de qué viene?
^_^U
Que no entiendo por que la gente espera algo del "OpenGL gpu940", creo que lo mejor es hacer uno desde cero, mirando lo que puede hacer la gp2x y olvidarse de cosas modernas.
:confused:
¿Y es que acaso gpu940 no es eso, desde cero mirando lo que puede hacer la gp2x, olvidandose de cosas modernas, sin filtrado de texturas y sin mas tonterias, usando punto fijo?
Pues yo creo que no...
glVertex3f(-1.0f, 1.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd(); // Terminamos con los polígonos de cuatro vértices
xrot+=1.3f; // Modificamos las variables de rotación
yrot+=1.2f;
zrot+=1.4f;
eso es culpa mía xDD
De todas formas, a ver si porto mi motor y asi hacemos comparaciones o me pico...
Rivroner
18/10/2007, 03:18
¿Y dónde me la puedo bajar? Porque no la encuentro :(
Gracias.
Puck2099, no puedes usarlos a la vez, ya que gpu940 se apropia del hardware gráfico :(
Yo conseguí combinar objetos 3D en un pequeño programita 2D SDL con un rasterizador 3D que alguien me comentó en estos mismos foros, lo siento no recuerdo el nombre, y no lo encuentro por el disco duro. Espero que el hilo no se haya perdido por el efecto 20k
¿No sería mi raster flash3d? Que lo tengo lleno de telarañas y me quedo de fabula.
¿No sería mi raster flash3d? Que lo tengo lleno de telarañas y me quedo de fabula.
Se llamaba Plush
Vaya no sé como se me han pasado éstos últimos comentarios :confused:
civantoz no puedo ayudarte, no tengo ni idea de qué te puede estar pasando. Ya sabes que yo también he tenido problemas al integrarlo con kdevelop, pero en mi caso era porque queria tirar por el camino de las autotools.
Lo que si te puedo sugerir es que uses kdevelop pero usando tus propios makefiles en lugar de las autotools. Para eso tienes que importar un proyecto existente y decirle que usa makefiles personalizados.
-----------------------
Cambiando de tema. Sobre el benchmark que hice, ya decía yo que estaba haciendo algo mal. He escrito a la lista de correo y el autor de gpu940 me ha corregido el error.
Ahora para 60fps tiene que haber 800 triangulos sin texturas en pantalla.
Para 30fps 1640 triangulos.
Así que supongo que 15fps serán 3300 triángulos.
Me quedo con los 30fps, porque mis modelos de quake2 moviendose a 15fps se veían muy fluidos :)
Voy a texturizarlos, seguiré informando ;)
< - >
Me parece increíble, el usar texturas, ya sea de 64x64 o de 512x512, no afecta apenas al rendimiento :asomb:
Puedes poner algun dato mas? tamaño de los triangulos, si hay clipping, backfaces, etc...
Powered by vBulletin® Version 4.2.5 Copyright © 2025 vBulletin Solutions Inc. All rights reserved.