DevelUY

Planeta de desarrolladores uruguayos

23 de agosto de 2016

Alejandro Segovia

Excellent Intro to Qt Quick

I came across this video that provides a great introduction to the Qt Quick controls in Qt 5.

It’s very interesting to see how a fully fledged, cross platform app that consumes a Web API can be developed in just over 15 minutes almost without a single line of code.

After seeing this video, I’ve been looking a little more into how C++ can be integrated into Qt Quick apps and, unfortunately, it doesn’t seem to leverage the signal-slot mechanism common to QWidgets applications.

This is a problem, since it means that reusing a large codebase might be a little more involved than just doing a seamless transition from writing QWidgets to slowling rolling out side-by-side Qt Quick panels.

Nonetheless, it’s very impressive and it’s definitely worth taking a look at if you need to develop a quick desktop UI in 10~15 minutes.

por Ale, el 23 de agosto de 2016 a las 15:59

18 de agosto de 2016

Enrique Almeida

Usabilizando GeneXus: Renombrar variables

Una de las cosas que ayuda mas a entender código escrito por otras personas, es que las variables, los objetos y las rutinas usadas en dicho código, tengan nombres coherentes y que permitan identificar rápidamente lo que se almacena en ellas.

A medida que pasa el tiempo, el significado de los diferentes objetos/variables del sistema  puede cambiar y por lo tanto conviene renombrarlo.

Si lo que renombro son tablas o atributos, le cambio el nombre, se genera un programa de reorganización para cambiar la base de datos y se regeneran todos los programas en forma que empiecen a usar dichos nombres.

Es renombrar objetos ejecutables (por ejemplo, procedures, transactiones, webpanels, etc) dentro de la KB, es una tarea que esta bien implementada, pues le cambio el nombre al objeto, hago un build all y se regeneran y compilan todos los objetos que se necesitan.

Las rutinas dentro de un objeto, se pueden renombrar y pero hay que cambiar en forma manual los lugares que hacen referencias a las mismas. No es un gran trabajo, pero hay que hacerlo en forma manual. El especificador indica cuando nos equivocamos en el cambio de nombre. Es una tarea que podría mejorarse, dando la opción al renombrar la subrutina que proponga un dialogo para cambiar todos los lugares que esta siendo invocada la misma.

Lo que si me parece que es muy mejorable, es la tarea de renombrar variables dentro de los objetos.
Hoy renombrar una variable, implica cambiar la definición de la misma (en el TAB de variables del objeto) y luego hay que ir por todos los demás TABs, cambiándola. Puede estar en las condiciones, en las rules, en el form, en el source.
Es una tarea en la cual se pueden introducir muchos errores, por lo que los programadores la evitan. Esto trae como  consecuencia, que una vez que una variable se nombro mal, como da mucho trabajo renombrarla, queda con el nombre mal por mucho tiempo.

Creo que si GeneXus ayudara en el renombre de variables, colaboraríamos mas para tener  código un poco mas facil de leer y entender.

Creo que son pequeñas cosas a mejorar que nos haría la vida un poco mas placentera y nos dejará pensar en problemas mas importantes.

PD1: Otra funcionalidad un poco mas avanzada, seria la de renombrar las variables basadas en un atributo y tiene el mismo nombre que dicho atributo, cuando lo renombro. Por ejemplo, si tengo el atributo/dominio CliId y en varios objetos de la KB tengo &CliId basada en dicho atributo, cuando renombre CliID a ClienteID, me pregunte si quiero renombrar tambien las variables basadas en dicho atributo y las renombre en forma acorde.

PD2: Hay dos  tipos de objetos que tienen problemas al renombrarse. Uno son los SDT cuando se tienen mas de un generador, porque muchas veces al renombrarlos las cosas dejan de compilar, porque quedan mal las referencias. Es un error que deberia solucionarse.

Otro caso en el cual se cambia el comportamiento al renombrar es con los External Objects del tipo SOAP, cuando manejan Location. En el codigo, pueden quedar referencias al nombre cuando se hace el GETLocation("NombreObjeto") y dicho nombre no se cambia cuando se renombra, por lo cual el programa deja de hacer lo que estaba haciendo. Hay que tener cuiado con este caso tambien.
Es un error que podria evitarse si GeneXus tuviera una sintaxis diferente.

Si en  vez de usar

&Location = GetLocation("NombreObjeto")

fuera

&Location = &NombreObjeto.GetLocation()

se podría adaptar automáticamente al cambio de nombre.

por noreply@blogger.com (Enrique Almeida), el 18 de agosto de 2016 a las 12:45

14 de agosto de 2016

Alejandro Segovia

Implementing a Waypoint System in the Vortex Engine

This week, we’re back to developing new native components for the Vortex Engine. For this week, the objective was to develop a “Waypoint Tween” component that moves an entity’s position between a series of points.

The new Waypoint Tween Component is used to move a 3D model between four points.The new Waypoint Tween Component is used to move a 3D model between four points.

There are two main aspects to bringing the system to life: the component implementation in the Vortex Engine and the UI implementation in the Vortex Editor.

At the engine level, the system is implemented via a C++ component that is very fast at the time of performing the math necessary to interpolate point positions based on time and speed.

At the editor level, due to the flexibility of this system, exposing its properties actually required a significant amount of UI work. In this first iteration, points can be specified directly in the component properties of the inspector panel. Later in the game, the the plan is to allow the user to specify the points as actual entities in the world and then reference them.

Animation and Movement

Now, in the animated GIF above, it can be seen that the 3D model is not only moving between the specified points, but it also appears as if the model is running between these.

There are two factors at play here to implement this effect: the MD2 Animation and the Waypoint Tween.

The MD2 Animation and Waypoint Tween Components.The MD2 Animation and Waypoint Tween Components.

When enabled, the Animate Orientation property of the Waypoint Tween component orients the 3D model so that it’s looking towards the direction of the point it’s moving to.

This propery is optional, as there are some cases where this could be undesirable, for instance, imagine wanting to implement a conveyor belt that moves boxes on top of it. It would look weird if boxes magically rotated on their Oy axis. For a character, on the other hand, it makes complete sense that the model be oriented towards the point it’s moving to.

Regarding the run animation, if you have been following our series on the Vortex Editor, you will remember that when instantiated by the engine, MD2 Models automatically include an MD2 Animation Component that handles everything related to animating the entity.

More details can be found in the post where we detail how MD2 support is implemented, but the idea is that we set the animation to looping “run”.

When we put it all together, we get an MD2 model that is playing a run animation as it patrols between different waypoints in the 3D world.

Waypoint System in Practice

So how can the waypoint system be used in practice? I envision two uses for the waypoint system.

The first one is for environment building. Under this scenario, the component system is used to animate objects in the background of the scene. Case in point, the conveyor belt system described above.

The second use, which might be a little more involved, would be to offload work from scripts. The efficient C++ implementation of the waypoint system would allow a component developed in a scripting language to have an entity move between different points without having to do the math calculations itself.

The dynamic nature of the component would allow this script to add and remove points, as well as interrupting the system at any time to perform other tasks. An example would be a monster that uses the waypoint system to patrol an area of the scene and then, when it’s detected that a player is close to the monster, the system is interrupted and a different system takes over, perhaps to attack the player.

In closing

I had a lot of fun implementing this system, as it brings a lot of options to the table in terms of visually building animated worlds for the Vortex Engine.

The plan for next week is to continue working on the Editor. There is some technical debt on the UI I want to address in order to improve the experience and there are also a couple of extra components I want to implement before moving on to other tasks.

As usual, stay tuned for more!

por Ale, el 14 de agosto de 2016 a las 18:33

12 de agosto de 2016

Enrique Almeida

Quien paga las migraciones?

Un colega me pregutó como manejábamos los procesos de migración y fundamentalmente si le cobrábamos algo adicional a los clientes en los procesos de migraciones.

Cuando hablo de migración, me refiero cuando quiero cambiar la version de mi herramienta de desarrollo (en mi caso GeneXus), para una mas nueva y con mejores prestaciones, pero sin agregar funcionalidad ninguna.

Es muy dificil vender estos proyectos a los clientes, pues el valor de los mismos pasan bastante desapercibidos. Funcionalmente la aplicación luego de terminar el proyecto tiene que hacer exactamente los mismo que antes, pero al regenerar toda la aplicación se agrega el riesgo de que algo funcione diferente y peor que antes.

Si bien no hay una receta única para esto, en general es bueno tratar de absorber el costo de los proyectos de migración. En el futuro, se le puede cobrar a los clientes, por las nuevas funcionalidades que se van a desarrollar con las nuevas tecnologías o mejoras al sistema.
Por ejemplo al migrar a Evolution 3, puede cobrarse por hacer una aplicación responsive que se adapte a todos los dispositivos, o con navegación smooth, o que soporte HTML5 y los ultimas versiones de los navegadores.

Dentro de las diferencias de funcionamiento podemos tener :

  • Cambios en la performance (cambios en sentencias, optimizaciones, etc)
  • Cambios en la seguridad (aplicaciones mas seguras)
  • Cambios tecnológicos (soporte de nuevas tecnológicas, html, responsive, etc)

En los proyectos de migración, lo que hay que hacer es mitigar los riesgos que cosas que funcionaban bien, empiecen a funcionar mal. Para esto lo mejor es detectar las diferencias y analizarlas para ver si hay algun problema. Según mi experiencia, el porcentaje de objetos que funcionan diferente es bastante bajo de una versión a la siguiente, por lo que si hacemos el ejercicio de migrar cada 2 años, es un trabajo mas que razonable.

Bueno, resumiendo y para no hacer un post muy largo

Migrar es indispensable, por lo tanto hacelo rápido

Si queremos mantenernos en el mercado, es indispensable migrar, por lo que conviene hacerlo lo mas a menudo que se pueda. Por supuesto que hay que equilibrar las horas que dedicamos a las migraciones, con las horas de desarrollo de nuevas funcionalidades, que siempre deberian ser la prioridad numero uno.
El proceso de migracion es muy automatizable, por lo que una vez que nos convencemos que es una tarea que hay que voy a tener que realizar varias veces, conviene invertir en automatizar la mayoria de los procesos que involucran.

Hacer migraciones desechables. 

Una vez que se tiene automatizado o al menos estandarizado el proceso de migracion, se hace mucho mas fácil hacer migraciones de prueba a nuevas versiones. Por ejemplo, es muy bueno hacer una copia de mi KB y convertirla a la ultima versión de GeneXus y  ver si aparecen errores o warnings nuevos. También hacer pruebas para ver si todo se genera sin problemas. 

Migrar permite detectar errores

En las migraciones, es un buen momento de detección de errores. 
Durante el proceso de desarrollo muchas veces se realizan determinadas "pisadas" para que las cosas funcionen o se hacen pasos de forma no demasiado ortodoxa. Las migraciones son el momento donde quedan a la vista todas estas "no conformidades" del proceso de desarrollo. 

Esto es un valor agregado importante de las migraciones, aunque algunos la vean como algo malo, para mi es algo muy bueno, pues ayuda a mejorar el proceso de desarrollo. 
Algunos ejemplos :
  • Se borro un objeto en la KB, pero se dejo en producción. 
  • Se renombró un objeto, pero sigue estando el viejo y el nuevo. 
  • Se copio un programa externo y el mismo no esta en la KB. 
  • Se modifico un CSS, javascript en producción y no se documentó correctamente
  • Se uso un font no standard y no se documento correctamente
  • Se instaló en producción una versión mas nueva del  User Control y no se documento

Migrar trae riesgos y hay que minimizarlos 

Conviene detectar diferencias en :

  • Navegaciones / Performance
  • WSDL/REST
  • Lista de programas generados
  • Interfaz de la aplicación con el exterior (Planillas, file system, mail, etc)
  • Revisar logs de ejecución en busca de errores o lentitudes
  • Pruebas funcionales
Una vez detectadas las diferencias, hay que estudiarlas y ver cuanto nos pueden afectar. 
Si el salto de versión no es muy grande, son tareas muy manejables. 

Desarrollar y cobrar por nuevas funcionalidades.


Esta es la parte mas fácil si estamos capacitados en las nuevas funcionalidades que nos brindan la versión.

El resumen es:

  • Migrar rapido y a menudo (este paso debería ser casi automático)
  • Encontrar diferencias rapido  (este paso debería ser casi automático)
  • Iterar hasta estar convencido que todo funciona bien
Si se puede hacer esto, el costo de la migración es fácil de compensar con las nuevas funcionalidades y las mejoras en productividad. 

por noreply@blogger.com (Enrique Almeida), el 12 de agosto de 2016 a las 12:55

SUGERENCIA PARA GXSERVER. Duplicar un version y su estado.

Esta es una funcionalidad que me gustaría tener en GXServer. 


Tengo una KB, con objetos, con  base de datos con datos y un build all terminado en mi version de trabajo y es la misma desde la que instalo y esta en producción. 

Quiero hacer un cambio grande con reorganización que puede llevar un tiempo largo (mas de una semana)  y me gustaría poder trabajar tranquilo sin afectar mi capacidad de instalar desde la version original. 

Me gustaría poder contar con una opción que haga estos pasos en uno solo

  • Cree una version congelada
  • Cree una version derivada de la congelada
  • Copie la estructura de archivos de la version original a la nueva (TargetPath, subdirectorios y archivos de especificación)
  • Deje todos los objetos como especificados (igual que la version original)
  • (Paso opcional) Cree la estructura de la base de datos nueva con la misma estructura de la version original
  • (Paso opcional) Copie los datos de la base de datos de la version original a la base de datos de la nueva version. 
  • Copie todos los archivos del usuario que corresponden a esa version
La idea básica es DUPLICAR una version y todo su estado a una nueva version independiente, evitando el tiempo del build all y automatizando la copia de datos que es una etapa muy engorrosos para hacerla a mano. 

Una vez hecho esto, puedo ponerme a trabajar en dicha version, sin interferir con mis compañeros de grupo. 
Cuando pruebo el cambio y el mismo cumple con los requerimientos, debo llevar todos los cambios hacia la version original y esto se puede hacer con la opción de Bring Changes y todo volvería al proceso normal

Por ultimo, cuando ya no necesite mas esta version me gustaría contar con una opción de borrado de la version que haga

  • Borre la version Congelada
  • Borre la version derivada de la congelada
  • Borre todos los archivos del targetpath, de especificación y del usuario correspondiente a dicha version y archivos ari 
  • (opcional) Borre la base de datos o tablas generadas

Creo que estas opciones harian mas facil el uso de versiones en grupos de trabajo. 

por noreply@blogger.com (Enrique Almeida), el 12 de agosto de 2016 a las 11:20

11 de agosto de 2016

Alejandro Segovia

OpenGL from a 10,000ft view

This month marks 10 years since I started learning and using OpenGL, and what a ride has it been! I started off with basic OpenGL 1.1 back in the University under the advisory of my mentor and ex-Googler Gabriel Gambetta, then moving on to the programmable pipeline by teaching myself how to code shaders and then riding the wave of the mobile revolution with OpenGL ES on the iPhone.

OpenGL Logo. Copyright (C) Khronos Group.OpenGL Logo. Copyright (C) Khronos Group.

As part of this process, I’ve also had the privilege of teaching OpenGL to others at one of the most important private universities back home. This exposed me to learn evermore about the API and improve my skills.

Rather than doing a retrospective post to commemorate the date, I though about doing something different. In this post I’m going to explain how OpenGL works from a 10,000ft view. I will lay the main concepts of how vertex and triangle data gets converted into pixels on the screen and, in the process, explain how a video card works. Let’s get started!

What is OpenGL

At the most basic level, OpenGL can be seen as a C API that allows a program to talk to the video driver and request operations or commands to be performed on the system’s video card.

Titan X GPU by NVIDIA. Image courtesy of TechPowerup.comTitan X GPU by NVIDIA. Image courtesy of TechPowerup.com

So what is a video card? A video card (or GPU) is a special-purpose parallel computer, excellent at executing a list of instructions on multiple data at the same time. A video card has its own processors, its own memory and it’s good at performing one particular set of operations (namely, linear algebra) very very fast.

What OpenGL gives us is access to this device through a client/server metaphor where our program is the client that “uploads” commands and data to the video driver. The video driver, which is the server in this metaphor, buffers this data and, when the time is right, it will send it to the video card to execute it.

Our program’s “instance” inside the video driver is known as the OpenGL Context. The Context is our program’s counterpart in the video card and it holds all the data we’ve uploaded (including compiled shader programs) as well as a large set of global variables that control the graphics pipeline configuration. These variables comprise the OpenGL State and they’re the reason OpenGL is usually seen as a State Machine.

The Graphics Pipeline (Simplified)

Remember how I mentioned that the video card excels at performing a limited set of operations very very fast? Well, what are these operations?

The way the video card works is that data is fed into it through OpenGL and then it goes through a series of fixed steps that operate on it to generate, ultimately, a series of pixels. It is the job of the video card to determine which pixels should be painted and using which color. And that’s really all the video card does at the end of the day: paint pixels with colors.

The following image, taken from Wikipedia, shows a simplified view of the data processing pipeline that OpenGL defines.

A simplified view of the OpenGL pipeline. Source: WikipediaA simplified view of the OpenGL pipeline. Source: Wikipedia

In this image, imagine the data coming from the user program. This diagram shows what is happening to this data inside the video card.

Here:

  1. Per-Vertex Operations: are operations that are applied to the vertex data supplied. This data can be coming in hot from main system memory or be already stored in video card memory. Here is where vertices are transformed from the format they were originally specified in into something we can draw on the screen. The most common scenario here is to take a piece of geometry from Object Space (the coordinate system the artist used), place it in front of a virtual “camera”, apply a perspective projection and adjust its coordinates. In the old days, here is where all transformation and lighting would take place. Nowadays, when a shader program is active, this stage is implemented by the user’s Vertex Shader.
  2. Primitive Assembly: here’s where OpenGL actually assembles whatever vertex data we supplied into its basic primitives (Triangle, Triangle Strip, Triangle Fan, Lines or Points, among others). This is important for the next step.
  3. Rasterization: is the process of taking a primitive and generating discrete pixels. It amounts to, given a shape, determining what pixels said shape covers. If the conditions are right, texture memory can be sampled here to speed up the texturing process.
  4. Per-Fragment Operations: are operations performed on would-be pixels. If there is a shader program active, this is implemented by the user in the fragment shader. Texture mapping operations take place here, as well as (usually) shading and any other operations that the user can control. After this stage, a number of operations take place based on the State Machine. These operations include depth testing, alpha testing and blend operations.
  5. Framebuffer: finally, this is the image we are rendering our pixels to. It is normally the screen, but we can also define a texture or a Render Target object that we could then sample to implement more complex effects. Shadow Mapping is a great example of this.

Sample OpenGL Program

Having taken a (very quick) look at OpenGL, let’s see what a simple OpenGL program might look like.

We are going to draw a colored triangle on the screen using a very simple script that shows the basic interaction between our program and the video card through OpenGL.

A colored triangle drawn by a simple program exercising the OpenGL API.A colored triangle drawn by a simple program exercising the OpenGL API.

I’m using Python because I find that its super simple syntax helps put the focus on OpenGL. OpenGL is a C API however and, in production code, when working with OpenGL, we tend to use C or C++. There are other bindings available for Java and C# as well, but -mind you- these just marshal the calls into C and invoke the API directly.

This script can be divided in roughly 3 parts: initializing the window and OpenGL context, declaring our data to feed to the video card and a simple event loop. Don’t worry, I’ll break it down in the next section.

#!/opt/local/bin/python2.6
import pygame
from OpenGL.GL import *

def main():
	# Boilerplate code to get a window with a valid
	# OpenGL Context
	w, h = 600, 600
	pygame.init()
	pygame.display.set_caption("Simple OpenGL Example")
	scr = pygame.display.set_mode((w,h), pygame.OPENGL|pygame.DOUBLEBUF)
	
	glClearColor(0.2, 0.2, 0.2, 0.0)

	# Data that we are going to feed to the video card:
	vertices = [ \
		-1.0, -1.0, 0.0, \
		1.0, -1.0, 0.0,  \
		0.0, 1.0, 0.0 ]

	colors = [ \
		1.0, 0.0, 0.0, 1.0, \
		0.0, 1.0, 0.0, 1.0, \
		0.0, 0.0, 1.0, 1.0 ]
			

	# Here's the game loop, all our program does is
	# draw to a buffer, then show that buffer to the
	# user and read her input.
	done = False
	while not done:
		# Clear the framebuffer
		glClear(GL_COLOR_BUFFER_BIT)
		
		# Supply the video driver a pointer to our
		# data to be drawn:

		glVertexPointer(3, GL_FLOAT, 0, vertices)
		glEnableClientState(GL_VERTEX_ARRAY)

		glColorPointer(4, GL_FLOAT, 0, colors)
		glEnableClientState(GL_COLOR_ARRAY)

		# Now that all data has been set, we tell
		# OpenGL to draw it, and which primitive
		# our data describes. This will be used
		# at the primitive assembly stage.
		glDrawArrays(GL_TRIANGLES, 0, 3)

		# Clean up
		glDisableClientState(GL_COLOR_ARRAY)
		glDisableClientState(GL_VERTEX_ARRAY)
		
		# Show the framebuffer
		pygame.display.flip()

		# Process input:
		for evt in pygame.event.get():
			if evt.type == pygame.QUIT:
				done = True

if __name__ == "__main__":
	main()

If you’re familiar with OpenGL, you’ll notice I’m using mostly OpenGL 1.1 here. I find it’s a simple way to show the basic idea of how data is fed into the video card. Production-grade OpenGL will no doubt prefer to buffer data on the GPU and leverage shaders and other advanced rendering techniques to efficiently render a scene composed of thousands of triangles.

Also note that the data is in Python list objects and, therefore, the pyopengl biding is doing a lot of work behind the scenes here to convert it into the float arrays we need to supply to the video card.

In production code we would never do this, however, doing anything more efficient would require to start fiddling with pointer syntax that would undoubtedly make the code harder to read.

Putting it all together

Now, if you’re unfamiliar with OpenGL code, let’s see how our program is handled by the Graphics Pipeline.

		# Supply the video driver a pointer to our
		# data to be drawn:

		glVertexPointer(3, GL_FLOAT, 0, vertices)
		glEnableClientState(GL_VERTEX_ARRAY)

		glColorPointer(4, GL_FLOAT, 0, colors)
		glEnableClientState(GL_COLOR_ARRAY)

We start off by providing an array of vertices and colors to OpenGL, as well as a description of how this data is to be interpreted. Our calls to glVertexPointer and glColorPointer (in real life you would use glVertexAttribPointer instead) tells OpenGL how our numbers are to be interpreted. In the case of the vertex array, we say that each vertex is composed by 3 floats.

glEnableClientState is a function that tells OpenGL that it’s safe to read from the supplied array at the time of drawing.

		# Now that all data has been set, we tell
		# OpenGL to draw it, and which primitive
		# our data describes. This will be used
		# at the primitive assembly stage.
		glDrawArrays(GL_TRIANGLES, 0, 3)

glDrawArrays is the actual function that tells OpenGL to draw, and what to draw. In this case, we are telling it to draw triangles out of the data we’ve supplied.

After this call, vertex data will go through the per-vertex operations stage and then be handed off to the primitive assembly, which will effectively interpret the vertices as forming part of one (or more) triangles.

Next, the rasterization stage will determine which pixels on the framebuffer would be covered by our triangle and emit these pixels, which will then go to the per-fragment operations stage. The rasterization stage is also responsible for interpolating vertex data over the triangle, this is why we get a color degrade effect spanning the area of the triangle – it’s simply the interpolation of the colors at the three vertices.

This is all happening inside the video card in parallel to our event loop, that’s why we have no source code here to show.

		# Show the framebuffer
		pygame.display.flip()

Finally, after everything is said and done, the video card writes the resulting pixels on the framebuffer, and we then make it visible to the user by flipping the buffers.

In Closing and Future Thoughts

We’ve barely scratched the surface of what OpenGL is and can do. OpenGL is a big API that has been around for 20+ years and has been adding lots of new features as video card and video games companies continue to push for ever more realistic graphics.

Now, while 20+ years of backwards compatibility allow running old code almost unmodified on modern systems, design decisions accumulated over time tend to obscure the optimal path to performance, as well as to impose restrictions on applications that would benefit for more direct control of the video card.

Vulkan logo. (tm) Khronos Group.Vulkan logo. ™ Khronos Group.

These points, made by the Khronos group itself, have led to the design and development of a new graphics API standard called Vulkan. Vulkan is a break from the past that provides a slimmed down API more suitable for modern day hardware. In particular multi-threaded and mobile applications.

OpenGL, however, is not going away any time soon, and the plan for the Khronos group, at least for the time being, appears to be to offer both APIs side by side and let the developers choose the one more suitable to their problem at hand.

Additionally, with Apple focusing on Metal and Microsoft on DX12, OpenGL (in particular OpenGL ES 2.0) remains the only truly cross-platform API that can target almost every relevant device on the planet, be it an iPhone, an Android phone, a Windows PC, GNU/Linux or Mac.

Finally, the large body of knowledge surrounding 20+ years of OpenGL being around, coupled with OpenGL’s relative “simplicity” when compared to a lower-level API such as Vulkan, may make it a more interesting candidate for students learning their first hardware-accelerated 3D API.

As time marches on, OpenGL remains a strong contender, capable of pushing anything from AAA games (like Doom) to modern-day mobile 3D graphics and everything in-between. It is an API that has stood the test of time, and will continue to do so for many years to come.

por Ale, el 11 de agosto de 2016 a las 14:43